let const
let
它的用法类似于 var,但是所声明的变量,只在 let 命令所在的代码块内有效
不存在变量提升
var 命令会发生“变量提升”现象,即变量可以在声明之前使用,let 命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错
暂时性死区
只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响
1 | var tmp = 123; |
存在全局变量 tmp,但是块级作用域内 let 又声明了一个局部变量 tmp,导致后者绑定这个块级作用域,所以在 let 声明变量前,对 tmp 赋值会报错
不允许重复声明
块级作用域
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于 let,在块级作用域之外不可引用 但是浏览器的实现与规范不同,应避免使用该写法
const
const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const 只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心
如果真的想将对象冻结,应该使用 Object.freeze 方法
变量的解构赋值
基本用法
如果解构不成功,变量的值就等于 undefined。
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
默认值
解构赋值允许指定默认值。
ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于 undefined,默认值才会生效
对象的解构赋值
如果解构失败,变量的值等于 undefined
对象的属性没有次序,变量必须与属性同名,才能取到正确的值
如果变量名与属性名不一致,必须写成下面这样。
1 | let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; |
字符串的解构赋值
- 字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象
数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象
函数参数的解构赋值
函数的参数也可以使用解构赋值
用途
交换变量的值 从函数返回多个值 函数参数的定义 提取 JSON 数据 函数参数的默认值 遍历 Map 结构 输入模块的指定方法
字符串的扩展
字符的 Unicode 表示法
允许采用\uxxxx
形式表示一个字符,其中 xxxx 表示字符的 Unicode 码点 可将码点放入大括号
ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被 for…of 循环遍历。这个遍历器最大的优点是可以识别大于 0xFFFF 的码点,传统的 for 循环无法识别这样的码点。
JSON.stringify() 的改造
ES2019 改变了 JSON.stringify()的行为。如果遇到 0xD800 到 0xDFFF 之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用自己决定下一步的处理
模板字符串
反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中
模板字符串之中可以放入表达式,还能调用函数
如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的 toString 方法
字符串的新增方法
String.fromCodePoint()
用于从 Unicode 码点返回对应字符,但是这个方法不能识别码点大于 0xFFFF 的字符
String.raw()
该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法
codePointAt()
能够正确处理 4 个字节储存的字符,返回一个字符的码点
normalize()
用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化
includes(), startsWith(), endsWith()
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
repeat()
repeat 方法返回一个新字符串,表示将原字符串重复 n 次
padStart(),padEnd()
padStart()用于头部补全,padEnd()用于尾部补全
trimStart(),trimEnd()
trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
matchAll()
matchAll()方法返回一个正则表达式在当前字符串的所有匹配
数值的扩展
二进制和八进制表示
ES6 提供了二进制和八进制数值的新的写法,分别用前缀 0b(或 0B)和 0o(或 0O)表示。
Number.isFinite(), Number.isNaN()
判断是否为极限和 NaN
Number.isFinite(), Number.isNaN()
ES6 将全局方法 parseInt()和 parseFloat(),移植到 Number 对象上面,行为完全保持不变
Number.isInteger()
Number.isInteger()用来判断一个数值是否为整数
Number.EPSILON
一个极小的常量 Number.EPSILON。根据规格,它表示 1 与大于 1 的最小浮点数之间的差
安全整数和 Number.isSafeInteger()
ES6 引入了 Number.MAX_SAFE_INTEGER 和 Number.MIN_SAFE_INTEGER 这两个常量
Number.isSafeInteger()则是用来判断一个整数是否落在这个范围之内
Math 对象的扩展
Math.trunc 方法用于去除一个数的小数部分,返回整数部分
Math.sign 方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值
Math.cbrt 方法用于计算一个数的立方根
函数的扩展
基本用法
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面
参数变量是默认声明的,所以不能用 let 或 const 再次声明
与解构赋值默认值结合使用
1 | // 写法一 |
参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。如果传入 undefined,将触发该参数等于默认值,null 则没有这个效果。
函数的 length 属性
指定了默认值以后,函数的 length 属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length 属性将失真。
作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。
应用
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误
1 | function throwIfMissing() { |
rest 参数
ES6 引入 rest 参数(形式为…变量名),用于获取函数的多余参数,这样就不需要使用 arguments 对象了 sort 默认是按照 unicode 码点顺序排序
1 | const sortNumber = (...number) => number.sort() |
rest 参数之后不能再有其他参数
name 属性
函数的 name 属性,返回该函数的函数名
箭头函数
函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象,在箭头函数中,它是固定的
不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误
不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。
1 | function Timer() { |
上面代码中,Timer 函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的 this 绑定定义时所在的作用域(即 Timer 函数),后者的 this 指向运行时所在的作用域(即全局对象)。所以,3100 毫秒之后,timer.s1 被更新了 3 次,而 timer.s2 一次都没更新。
不能用 call()、apply()、bind()这些方法去改变 this 的指向
不适用场合
由于箭头函数使得 this 从“动态”变成“静态”,下面两个场合不应该使用箭头函数。
第一个场合是定义对象的方法,且该方法内部包括 this。
1 | const cat = { |
上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用 cat.jumps()时,如果是普通函数,该方法内部的 this 指向 cat;如果写成上面那样的箭头函数,使得 this 指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致 jumps 箭头函数定义时的作用域就是全局作用域。
第二个场合是需要动态 this 的时候,也不应使用箭头函数。
函数式相关
省略
数组的扩展
扩展运算符
扩展运算符(spread)是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。该运算符主要用于函数调用。替代函数的 apply 方法
扩展运算符的应用
- 复制数组
- 合并数组
- 与解构赋值结合
- 任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
Array.from
Array.from 方法用于将两类对象转为真正的数组
类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)所谓类似数组的对象,本质特征只有一点,即必须有 length 属性。因此,任何有 length 属性的对象,都可以通过 Array.from 方法转为数组,而此时扩展运算符就无法转换。
Array.from 还可以接受第二个参数,作用类似于数组的 map 方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.of()
Array.of 方法用于将一组值,转换为数组。这个方法的主要目的,是弥补数组构造函数 Array()的不足。因为参数个数的不同,会导致 Array()的行为有差异。
copyWithin()
在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
find() 和 findIndex()
数组实例的 find 方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为 true 的成员,然后返回该成员。如果没有符合条件的成员,则返回 undefined
数组实例的 findIndex 方法的用法与 find 方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
fill
fill 方法使用给定值,填充一个数组。fill 方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置
数组实例的 entries(),keys() 和 values()
keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
数组实例的 includes()
Array.prototype.includes 方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的 includes 方法类似
数组实例的 flat(),flatMap()
flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将 flat()方法的参数写成一个整数,表示想要拉平的层数,默认为 1。
flatMap()方法对原数组的每个成员执行一个函数(相当于执行 Array.prototype.map()),然后对返回值组成的数组执行 flat()方法。
数组的空位
不建议保留空位,es6 对于空位设置文 undefined
对象的扩展
属性的简洁表示
ES6 允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁
属性名表达式
ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性/方法名,即把表达式放在方括号内。
属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
方法的 name 属性
函数的 name 属性,返回函数名
如果对象的方法使用了取值函数(getter)和存值函数(setter),则 name 属性不是在该方法上面,而是该方法的属性的描述对象的 get 和 set 属性上面,返回值是方法名前加上 get 和 set。有两种特殊情况:bind 方法创造的函数,name 属性返回 bound 加上原函数的名字;Function 构造函数创造的函数,name 属性返回 anonymous。
属性的可枚举性和遍历
描述对象 对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor 方法可以获取该属性的描述对象。
enumerable 属性,称为“可枚举性”目前,有四个操作会忽略 enumerable 为 false 的属性。只有 for…in 会返回继承的属性,其他三个方法都会忽略继承的属性,只处理对象自身的属性。另外,ES6 规定,所有 Class 的原型的方法都是不可枚举的。大多数时候,我们只关心对象自身的属性。所以,尽量不要用 for…in 循环,而用 Object.keys()代替。
- for…in 循环:只遍历对象自身的和继承的可枚举的属性。
- Object.keys():返回对象自身的所有可枚举的属性的键名。
- JSON.stringify():只串行化对象自身的可枚举的属性。
- Object.assign(): 忽略 enumerable 为 false 的属性,只拷贝对象自身的可枚举的属性
super 关键字
super,指向当前对象的原型对象 目前,只有对象方法的简写法可以让 JavaScript 引擎确认,定义的是对象的方法 avaScript 引擎内部,super.foo 等同于 Object.getPrototypeOf(this).foo(属性)或 Object.getPrototypeOf(this).foo.call(this)(方法)。
对象的解构赋值
由于解构赋值要求等号右边是一个对象,所以如果等号右边是 undefined 或 null,就会报错,因为它们无法转为对象
解构赋值必须是最后一个参数
扩展运算符
- 对象的扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
- 由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组。
- 如果扩展运算符后面是一个空对象,则没有任何效果。
- 如果扩展运算符后面不是对象,则会自动将其转为对象。
- 如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象
- 对象的扩展运算符等同于使用 Object.assign()方法。
- 如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法。
1 | // 写法一 |
- 修改现有对象部分的属性
1 | let newVersion = { |
- 与数组的扩展运算符一样,对象的扩展运算符后面可以跟表达式
- 扩展运算符的参数对象之中,如果有取值函数 get,这个函数是会执行的
对象的新增方法
Object.is()
它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致
Object.assign()
Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。浅拷贝,同名属性的替换 ,可以用来处理数组,但是会把数组视为对象,如果要复制的值是一个取值函数,那么将求值后再复制。
Object.getOwnPropertyDescriptors
返回指定对象所有自身属性(非继承属性)的描述对象。
Object.getOwnPropertyDescriptors()方法的另一个用处,是配合 Object.create()方法,将对象属性克隆到一个新对象。这属于浅拷贝。
1 | const clone = Object.create(Object.getPrototypeOf(obj), |
proto
proto属性 用来读取或设置当前对象的 prototype 对象 建议使用下面的 Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)、Object.create()(生成操作)代替
如果一个对象本身部署了proto属性,该属性的值就是对象的原型。
Object.fromEntries()
Object.fromEntries()方法是 Object.entries()的逆操作,用于将一个键值对数组转为对象。