js的组成
我们可以使用js来完成如下的相关功能
- 人机交互
- 后端
- 页面特效和动画
- 数据处理和数据交互
js就是一门语言
这门语言可以使我们方便的取去操作页面和浏览器中的元素
所以js由以下三个部分组成
- ECMAScript
- JS的核心语法,也就是这个语言是怎么写的
- DOM => document object model
- 文档对象模型
- 使用js提供的API(一些属性和方法)来操作页面的相关元素
- BOM => browser object model
- 浏览器对象模型
- 使用js提供的API 来操作浏览器中的相关要素
变量
变量是一个容器,这个容易中可以存储对应的值
所谓的变量,也就是值会变化的量
也即是说我们这个容器中存储的内容是可以随时发生改变的
6种定义变量的方式
// ES5定义变量的方式
var num = 14
console.log(num) // => 14
// 变量的值是可以被修改的
num = 22
console.log(num) // => 22
// ES6 中定义变量的方式
let num = 23
// const定义的量为常量
// 也就是说常量所对应的值是固定不变的,是无法修改的
// 我们可以认为 常量是一种特殊的变量
// num => 常量 22 => 字面量
const num = 22
// 其它的一些js行为也可以被视为变量的创建
// 1. 定义函数
// 定义了变量 fun 来存储我所对应的函数代码块
const fun = function() { ... }
// 2. 定义类
// 定义变量Person 来存储我整个类的代码
class Person { ... }
// 3. 模块导入
// 定义一个path变量来接收模块导出的内容
import path from './path.js'
// 4. 定义唯一值 (独一无二的值)
let symbol1 = new Symbol(100)
let symbol2 = new Symbol(100)
symbol1 === symbol2 // => false
复制代码
变量的命名规范
- 严格区分大小写
let num = 200
console.log(NUM) // => error
复制代码
- 使用数字,字母,下划线和$ 组成 其中不能以数字开头
let $box; // => $开头的变量一般是存储 使用JQuery获取的元素
let _box; // => 以下划线开头的一般代表全局变量或者公共变量 (这种写法主要是约定俗称的规范)
let 2box; // => error
let box1; // => 合法的变量
let 变量; // => 合法的变量 极度不推荐
复制代码
- 命名应该见名知意(语义化)
let a = 222 // => bad
let num = 222 // => good
复制代码
- 命令的方式有 驼峰命令法(CamelCase) 和 帕斯卡命名法(PascalCase)
// 小驼峰 => 第一个单词的首字母小写,其余每一个有意义的单词或简写的首字母都要大写
let userInfo
// 大驼峰(帕斯卡命名法) => 所有的有意义的单词或简写的首字母都大写 一般用于定义类或者构造函数(约定俗成)
let UserInfo
// 下划线命令法
let user_info
复制代码
- 不能使用关键字和保留字
- 关键字 -> 当下有特殊含义的叫做关键字
- 保留字 -> 未来可能存为关键字的叫做保留字
let break = 222 // => error
复制代码
数据类型
- 基本数据类型
- Number 数值 =>常规数值 + NaN(代表对应的值不是一个数值类型,但是NaN本身属于数值类型)
- String 字符串 => 所有使用单引号或双引号或反引号包裹起来的内容都是字符串
- Boolean 布尔值 => true | false
- Undefined 未定义
- Null 空对象指针
- Symbol 唯一值
- 引用数据类型
- 对象数据类型 Object
- 普通对象 { … }
- 数组对象 [ … ]
- 正则对象 / ^ 基本数据类型
Number
- 常规数字 = 正数 + 负数 + 小数 + 整数 + 零 ….
- NaN => not a number 表示当前变量不是一个数值,但是NaN本身是一个数值类型的值
// NaN 和任何值都是不相等的,包括它自己本身 console.log(NaN === NaN) // => false // 判断一个值是不是有效数字, 使用方法 isNaN() // isNaN([val]) => 检测一个值是不是有效数字,如果是有效数字返回false,不是有效数字,返回true1 console.log(isNaN(2)) // => false console.log(isNaN(NaN)) // => true // 在使用isNaN进行检测的时候,会先判断参数是不是number类型,如果不是会先尝试使用Number()方法,将参数转换为number类型后再进行判断,如果值以及是数值类型,那么就直接进行判断 console.log(isNaN('10')) // => false // 所以凡是可以使用isNaN进行转换后,变成有效数值的,那么这个值就是有效数值,调用isNaN方法的时候,将会返回false 复制代码
// 其它类型值转换为数值类型 // 1. Number(val) => 如果参考可以转换为数字,那么就转换为数字,如果不能转换为数字,那么就返回NaN // 字符串->数字 必须保证当前字符串的值不能包含任何一个非有效的数值字符(第一个点除外) console.log(Number('12.3')) // => 12.5 console.log((Number('12.5px'))) // => NaN console.log(Number('12.5.6')) // => NaN console.log(Number('')) // => 0 空字符串会被转换为数值零 // 布尔->数字 console.log(Number(true)) // => 1 console.log(Number(false)) // => 0 console.log(isNaN(true)) // => false // null|undefined->数字 console.log(Number(null)) // => 0 console.log(Number(undefined)) // => NaN // 引用数据类型->数字 // 先将引用数据类型调用toString方法转换为字符串,后在按照字符串的规则进行转换 // 大部分对象调用toString方法后结果为[object Object] console.log(Number({name: '10'})) // => NaN console.log(Number({})) // => NaN console.log([]) // => 0 [].toStirng() -> '' console.log([12]) // => 12 [12].toString() -> '12' console.log([12, 13]) // => NaN [12,13].toString() -> '12,13' 复制代码
// 字符串转数值的其它方法 let str = '12.5px' console.log(Number(str)) // => NaN console.log(parseInt(str)) // => 12 console.log(parseFloat(str)) // => 12.5 // parseInt|parseFloat(字符串类型的值, 进制) // 对于字符串来说,这两个方法是从左往右依次查找有效数字字符,一直到非有效的数字字符为止(不管后边是否还有数字,都不在找了),将找到的转变为数字返回 // 所谓的有效字符 指点的是数字 和 第一个点 // 在进行相等(==)判断的时候,也可能出现其它类型的值转换为数值 console.log('10' == 10) // => true 字符串转换为数值后在进行比较 复制代码
// Number方法和parseInt|parseFloat方法的区别 // Number方法是v8引擎提供的方法,是走v8判断机制 -> 浏览器默认转换的时候会调用的方法 // parseInt|parseFloat 是浏览器自己封装的方法 -> 只有自己手动调用的时候,才会执行的方法 console.log(number(true)) // => true -> 1 -> 1 console.log(parseInt(true)) // => 因为parseInt|parseFloa只能解析字符串类型的值,所以会将参数转换为字符串后再进行判断 // true -> 'true' -> NaN 复制代码
String
所有使用
单引号
,双引号
,反引号
包起来的都是字符串// 其它类型值转换为字符串 // toString方法 let v = 12 console.log(v.toString()) // => '12' console.log(12.toString()) // => error // (NaN) ---> 括号表达式 // 基本类型值在调用toString方法的时候,需要外层包裹一层括号表达式 // 引用类型的值可以直接调用toString方法 console.log((NaN).toString()) // => 'NaN' // 绝大多数数据调用toString方法的结果就是在值的外边加上引号 // 比较特别的是null和undefined是没有办法调用toString方法 // null和undefined是有toString方法的,就是无法直接调用 // 这是因为null和undefined表示空或者是未定义,此时浏览器解析时候是无法调用方法的 console.log((null).toString()) // => error console.log((undefined).toString()) // => error console.log({}.toString()) // => '[object Object]' console.log(/^$/.toString()) // => '/^$/' console.log([].toString) // => '' console.log([12].toString) // => '12' console.log([12, 23].toString) // => '1223' // 字符串拼接 // 四则运算法则中,除加法之外,其余都是数学运算,但是+可能表示加法,也可能表示字符串拼接 console.log('10' + 10) // => 1010 只要+两边有一个为字符串,那么+表达的作用就是字符串拼接 console.log('10' - 10) // => 0 '10'使用浏览器内置的Number方法转换为数字10 所以结果为0 console.log('10px' - 10) // => NaN console.log(10 + null + true + [] + undefined) // => 10 + 0 + 1 + '' + undefined([]在转换的过程中会先转换为空字符串,所以直接变成字符串拼接) => 结果为 11undefined 复制代码
Boolean
只有2个值 true(真) / false(假)
// 把其它类型的值转boolean的时候 // 0, NaN, '', undefined, null -> false // 其余 -> true // 中间没有任何的中间过程 // Boolean函数 console.log(Boolean(0)) // => false console.log(Boolean('')) // => false console.log(Boolean(' ')) // => true console.log(Boolean(null)) // => false console.log(Boolean(undefined)) // => false console.log(Boolean([])) // => true 中间没有任何的中间过程,所以直接是true console.log(Boolean([12])) // => true // !/!! -> 取反/取反再取反 (先转换为boolean值,再取反) console.log(!1) // => false console.log(!!1) // => true !! <=> Boolean函数 // 逻辑判断 if(1) { ... } // => 逻辑执行 if('3px' - 3) { ... } // => '3px' - 3 --> Number('3px') -3 --> NaN --3 -> NaN 复制代码
null 和 undefined
null -> 空对象指针, undefined -> 未定义
// null 和 undefined 都是没有 // null -> 空指针, // undefined -> 定义未赋值 // null -> 一开始不知道值的时候,设置的默认值,后期再进行赋值操作 let num = null let num = 0 // 上述两个方法都可以设置num的初始值,区别在于 设置为0的时候,是只要一个确切的值的,是有值的,会在内存中有存储空间 // null是空值,在内存中是不占存储空间的,但是所有的变量全部初始值为null,不利于语义化,但是使用null性能会好上一点点 // undefined -> 我没有给初始值,V8给变量的初始值 // 所以在变量定义了,未赋值的条件下, 这个变量的默认值就是undefined let u // => undefined 复制代码
引用数据类型
普通对象
任何一个对象,都是由零到多组键值对(属性名-属性值)组成的
属性名不能重复,后一个属性名会覆盖前一个同名属性
const Person = { name: 'Klaus', // => 多个键值对之间使用逗号隔开 age: 18 // 所有的属性名都是字符串或数值, 如果是字符串类型,引号可以省略, age: 18 <=> 'age': 18 } // 获取属性名对应属性值 console.log(person.name) // => 对象.属性名 console.log(person['age']) // => 对象[属性名] 此时的属性名可以为变量,但是如果是一个具体的值的时候,需要使用字符串 console.log(person.size) // => undefined 如果当前属性名不存在,默认的属性值是undefined 复制代码
// 原则上,属性名可以是数字(不推荐),此时只可以使用中括号语法来获取属性名对应的属性值 const obj = { 1: 100 } console.log(obj[1]) // => 100 console.log(obj.1) // error 复制代码
const obj = { name: 'Klaus' } // 设置属性名 obj.age = 18 obj['gender'] = 'male' // 如果属性名对应的属性值已经存在,就不是添加属性名,而是修改属性名对应的属性值 obj.name = 'Alice' console.log(obj.name) // => Klaus 复制代码
const obj = { name: 'Klaus', age: 18 } // 删除属性名 // 1. 真删除:彻底删除 delete obj.age // 2. 假删除: 属性还在,值为空 person.name = null // 或 person.name = undefined 复制代码
数组对象
// 数组是一种特殊的对象数据类型 // 1. 数组的属性名是从0递增的索引值 (索引=> 从0开始连续递增,代表每一项位置的属性名) // 2. 数组的属性名是数值类型,所以我们只能通过中括号语法来通过属性名获取到对应的属性值 // 3. 数组有一个默认属性,length代表着数组的长度 (不需要设置,会自动修改和更新) let arr = ['Klaus', 18, true] /* arr => { 0: 'Klaus', 1: 18, 2: true, length: 3 } */ console.log(arr.length) console.log(arr[0]) // 向数组末尾添加内容 arr[arr.length] = 'demo' 复制代码
堆栈内存
- 浏览器想要执行JS代码,
- 从电脑内存中分配出一块内存,以执行js代码(提供js执行所需要的运行环境), 这部分内存被称之为
栈内存(Stack)
-> ‘饭店’ - 浏览器分配一个主线程用于自上而下执行JS代码 -> ‘服务员’
- 从电脑内存中分配出一块内存,以执行js代码(提供js执行所需要的运行环境), 这部分内存被称之为
JS中内存的划分
- 栈 -> 执行代码 + 存储变量和基本数据类型值以及引用类型值的引用地址
- 堆 -> 用来存储引用数据类型对应的值
变量的存储过程
JS中数据分为2种
-
基本数据类型 -> 按值操作(直接操作的是数据值)-> 也被叫做值类型
-> 值一般存储在栈(Stack)中
-
复杂数据类型 -> 操作的是对应的堆内存中的值在内存中的引用地址 -> 也被叫做引用类型
-> 值一般存储在堆(Heap)中,使用其存储在栈中的地址进行关联
// 基本数据类型在内存中的存储过程 let a = 12 // 1. 创建变量a, 放置到栈内存的变量存储区域 // 2. 创建一个值12,放置到当前栈内存的值存储区域 -> 只有简单的值可以这么存储 // 3. = 代表这赋值,在内存中是让变量和值相互关联的过程 复制代码
// 引用数据类型在内存中的存储过程 let per = { name: 'Klaus' } // 1. 在内存中分配出一块新的内存,专门用于存储引用类型值,这种内存被称之为堆内存(Heap) // => 所有的内存都有一个十六进制的值来表示某一块内存的地址 // 2. 把对象中的键值对依次存储到堆内存中,并将这个堆内存的地址存储到栈内存的值存储空间中 // 3. 将堆内存地址和变量进行关联 复制代码
修改变量的值
基本数据类型
let a = 12 let b = a a = 13 console.log(a) // => 12 复制代码
let o1 = { name: 'Klaus' } let o2 = o1 o2.name = 'Alice' console.log(o1.name) // => 'Alice' 复制代码
阶段练习
let n = [10, 20] let m = n let x = m m[0] = 100 x = [30, 40] x[0] = 200 m = x m[1] = 300 n[2] = 400 console.log(m) // => [200, 300] console.log(n) // => [100, 20, 400] console.log(x) // => [200, 300] 复制代码
let a = { n: 1 } let b = a a.x = a = { n: 2 } // 连续赋值等价于从往右依次赋值 /* a.x = a = {n: 2} <=> 可以从左往右划分为两行代码(有先后执行顺序) a.x = { n:2 } a = { n:2 } */ console.log(a.x) // => undefined console.log(b) // => { n: 1, x: { n: 2 } } 复制代码
数据类型检测
JS中检测数据类型的方法有且只有4种:
typeof [val]
: 用来检测数据类型的运算符,不是函数,调用不需要加小括号instanceof
: 也是一个运算符,用于检测当前实例是否属于某个类的实例 (基于类)constructor
: 使用构造函数的方式去检测数据类型 (基于类)Object.prototype.toString.call()
: 检测数据类型最好的办法
console.log(typeof 1) // => 'number' -> 返回的就是1的数据类型, 返回的number本身是一个字符串类型的值,全部都是小写的 console.log(typeof typeof 1) // => 'string' let a = NaN console.log(typeof a) // => 'number' console.log(typeof null) // => 'object' -> typeof运算符认为null是空对象指针,所以认为null的数据类型是object, 这是typeof运算符的局限性,但是本质上null是空对象指针,是基本数据类型,不是引用数据类型 console.log(typeof undefined) // => 'undefined' // typeof 无法区分普通对象类型和特殊对象类型,统一返回的都是object console.log(typeof {}) // => 'object' console.log(typeof /^$/) // => 'object' console.log(typeof []) // => 'object' // 但是特别的是 typeof 可以区分出函数类型 console.log(typeof function(){}) // => 'function' 复制代码
JS中的操作语句
判断
条件成立做什么,条件不成立做什么
JS中的条件判断有3种
- if/ else if/ else
- 三元运算符
- switch … case …
if/else if/else
/* if(条件成立) { 条件1成立需要执行的代码 } else if(条件2成立) { 条件2成立需要执行的代码 } ... else { 以上条件都不成立的时候需要执行的代码 } // => 只要有一个条件成立就执行其中的代码块,后边的代码块就一律不会执行 */ // A && B => A和B同时成立,表达式才成立 // A || B => A和B只要有一个成立,表达式就直接成立 let a = 10 if(a) { // a在条件判断框中,会先转换为boolean值,在进行判断 .... // 所以这里的代码块会被执行 } if(a=2) { // 如果条件块中的代码是语句的时候,只要语句合法,那么条件就认定为true .... // 所以这里的代码块会被执行 } 复制代码
三元运算符 --- 简单if-else的简写方式
// 条件 ? 条件成立执行语句 :条件不成立执行语句 // 如果if-else比较简单的话,可以使用三元运算符代替 let a = a > 10 ? 10 : 5 // 1. 如果需要处理的事情比较多,可以使用括号包起来,每一件事情使用逗号进行分割 // 2. 如果不需要处理事情,可以使用null或undefined来进行占位 a > 10 && a < 20 ? ( a++, console.log(a) ) : null 复制代码
switch ... case ...
一个变量在不同值情况下,需要有不同的操作
switch(a) { case 1: // 在switch...case...中每一个case的比较使用的都是全等(绝对相等) console.log(1) break // 每一个case的最后都最好需要加上break, // 如果不加break,就表示当前代码块和后面的代码块都需要执行,直到遇到break为止或者全部执行完毕 case 2: console.log(2) break case 3: console.log(3) break case 4: console.log(4) break case 5: console.log(5) break default: console.log('end') // 因为是默认条件,也就是最后一个条件,所以这里不需要加break,加了也没有意义 } switch(a) { // 不加break可以实现变量在某些值的时候,执行相同的功能,此处就相当于 a === 1 || a === 5 (注意: 是全等) case 1: case 5: a += 2 default: a -= 2 } 复制代码
// == 和 === 的区别 // == -> 相等 --- 如果左右两边数据类型不同,默认先转化为相同类型(优先转换为数值类型),然后在进行比较 // === -> 全等 --- 只有类型和值都相同的情况下,才会相等 --- 推荐使用 5 == ’5‘ // => true 复制代码
案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>基于CSS实现鼠标滑过显示下拉列表内容</title> <style> .box { /* 默认值是content-box: 也就是设置的内容本质是内容区的宽度和高度,设置padding或margin会改变盒子的大小 border-box: 设置的宽高就是盒子的宽高,设置padding或margin不会改变盒子的大小 */ box-sizing: border-box; text-align: center; margin: 20px auto; width: 200px; height: 40px; line-height: 40px; border: 1px solid lightblue; position: relative; } .box .detail { position: absolute; /* detail是box的子元素,所以默认边框会左移1px */ left: -1px; /* 定位后,上移,detail的上边框和box的下边框重叠 */ top: 38px; box-sizing: border-box; width: 500px; height: 100px; text-align: center; line-height: 100px; border: 1px solid lightblue; display: none; /* 默认不显示 */ } .box:hover { /* box的下边框和背景色一致,来覆盖detail和box重叠的边框色 */ border-bottom-color: #fff; } .box:hover .detail { display: block; /* 因为box和detail同时定位,所以降低detail的层级,以便于box的下边框可以覆盖detail的上边框 */ /* 因为detail是box的子元素,如果设置在box上,那么box和其子元素detail一起都会发生改变,所以这里只能设置在子元素detail上 */ z-index: -1; /* z-index的默认值是0 */ } </style> </head> <body> <!-- 基于CSS实现鼠标滑过显示下拉列表内容 --> <div class="box"> 购物车 <div class="detail">购物车中的相关信息</div> </div> </body> </html> 复制代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>基于js点击显示下拉列表内容</title> <style> .box { box-sizing: border-box; text-align: center; margin: 20px auto; width: 200px; height: 40px; line-height: 40px; border: 1px solid lightblue; position: relative; cursor: pointer; /* 鼠标移动上去的时候,显示一个小手 */ } .box .detail { position: absolute; left: -1px; top: 38px; box-sizing: border-box; width: 500px; height: 100px; text-align: center; line-height: 100px; border: 1px solid lightblue; display: none; z-index: -1; } </style> </head> <body> <!-- 基于js点击显示下拉列表内容 --> <div class="box"> 购物车 <div class="detail">购物车中的相关信息</div> </div> <script> // 想操作谁就先获取谁 --- 获取元素 const box = document.querySelector('.box') const detail = document.querySelector('.detail') // 在对应的时候做对应的事情 --- 绑定事件 // onxxx - 当xxx的时候执行绑定的函数 (xxx就是函数名) // onxxx --- 都是小写的,没有大写字母 box.onclick = () => { // 元素.style.xxxx 获取元素在行内设置的样式值,如果有就返回设置的值,如果没有就返回空字符串 // 所以原则上需要在行内设置display样式后,才可以获取到这个样式的值, // 但是一开始默认就是隐藏的(false),点击以后会自动设置上对于的行内样式,所以这里此步可以省略 let isShow = detail.style.display === 'block' // 元素.style.xxx == yyy -> 以设置行内样式的方式设置元素的名为xxx的样式属性,值为yyy detail.style.display = isShow ? 'none' : 'block' // js中操作的样式名采用的是小驼峰命名法 box.style.borderBottomColor = isShow ? 'lightblue' : '#fff' } </script> </body> </html> 复制代码
传统DOM编程(命令式编程)的逻辑思路
-
想操作谁就先获取谁 — 获取元素
-
在对应的时候做对应的事情 — 绑定事件
-
需要做什么 —- 编写事件处理函数
现代框架(响应式编程)的逻辑思路
-
界面长什么样 —- 定义视图
-
有哪些数据和方法 —- 声明相关数据和方法
-
数据和视图进行绑定
—- 使用框架提供的特有语法(vue的模板语法,react的jsx语法等)将对应的数据插入视图中,将对应的方法在视图中进行绑定
—- 具体如何绑定和渲染主要由框架来进行处理,我们只要关心界面的处理逻辑即可,当数据发送改变的时候通知框架,对应的数据改变了,框架就会自动去更新我们的界面视图
—- 也就是数据驱动界面显示,所以这里的数据又被称之为状态
循环
重复做一些事
- for循环
- for … in …
- for …. of … (ES6新增)
- while
- do … while …
for循环
- 创建循环初始值
- 设置(验证)循环执行条件
- 条件成立, 执行循环体中内容
- 当前循环结束,步长累加操作
// ()中放置条件 // (设置初始值,循环执行条件,步长) for (var i = 0; i < 5; i++) { // 大括号中写的代码块 叫做 循环体 console.log(i) /* => 0, 1, 2, 3, 4 总共循环了5次 */ } // var这里设置的变量是全局变量 console.log(i) // => 5 复制代码
for (var i = 10; i > 4; i-=2) { i = i < 7 ? i++ : i -- } console.log(i) // => 4 复制代码
// 循环体中有2个关键词 break和continue // continue --- 结束当前循环,进入下一次循环 // break --- 结束所有轮次的循环,循环结束 复制代码
阶段案例
有一个输入框,判断输入的内容是正数,负数还是零
const inputEl = document.getElementById('input') inputEl.addEventListener('blur', e => { // 文本框输入的内容可以通过value属性来获取或者可以事件对象的target属性的value值来进行获取 // 表单元素输入的内容的默认类型都是字符串 // const val = Number(inputEl.value) const val = Number(e.target.value) // Number将字符串转换为数值类型的时候,是可以正常转换负数的 Number('-12') -> -12 isNaN(val) ? console.error('not a number') : console.log( val > 0 ? '正数' : val === 0 ? '零' : '负数') }) 复制代码
- 对象数据类型 Object