目录


前言: 本文是对阮一峰老师

《ECMAScript 6 入门》 的一篇读书笔记。

ES6的更新主要是体现在以下部分:

  • 表达式:声明,解构赋值
  • 内置对象:字符串拓展、数值拓展、对象拓展、数组拓展、函数拓展、正则拓展、Symbol、Set、Map、Proxy、Reflect
  • 语句与运算:Class、Module、Iterator
  • 异步编程:Promise、Generator、Async

1. 声明

(1)新增了​​const​​​和​​let​​命令:

  • ​const​​:声明常量
  • ​let​​:声量变量

(2)​​const​​​和​​let​​命令有以下特点:

  • ​const​​​命令必须在声明时就赋值,​​let​​命令可以声明时赋值或声明之后再赋值
  • 作用域为块级作用域:​​{}​
  • 不能重复声明
  • 不存在变量提升,即它所声明的变量一定要在声明后使用,否则报错。
  • 暂时性死区,如果区块内存在let或const命令,这个区块对这写声明的变量,从一开始就形成了块级作用域,凡是在声明之前就使用这个变量就会报错

2. 解构赋值

解构赋值遵循“模式匹配”,即只要等号两边的模式相等,左边的变量就会被赋予对应的值。

(1)数组的解构赋值

具有 ​​Iterator​​ 接口的数据结构,都可以采用数组形式的解构赋值。

  • 解构成功:​​let [foo, [[bar], baz]] = [1, [[2], 3]];​
  • 解构失败:​​let [bar, foo] = [1];​​​,​​foo​​​为​​undefined​
  • 不完全解构:​​let [x, y] = [1, 2, 3];​
  • 允许指定默认值:​​let [x, y = 2] = [1];​​,默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

(2)对象的解构赋值

  • 对象的解构赋值的本质其实是先找到同名的属性,在赋值给对应的变量:​​let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };​​​,本质上是给属性值​​foo​​​、​​bar​​赋值
  • 对象的解构赋值中,对象的属性是没有次序的,变量必须与属性同名,才能取到值,例:​​let { bar, foo } = { foo: 'aaa', bar: 'bbb' };​
  • 如果解构赋值失败,变量的值为​​undefined​
  • 对象的解构赋值中,可以将现有对象的方法赋值给某个变量,例:​​let { log, sin, cos } = Math;​
  • 对象和数组的解构赋值一样,可以进行嵌套,指定默认值。如果是嵌套对象,并且子元素所在的父属性不存在,那么就会报错。

(3)字符换的解构赋值

  • 解构赋值规则:只要等号右边的值不是对象或数组,就先将其转为对象
  • 字符串在解构赋值时被转换成为了一个类数组的对象
const [a, b, c, d, e] = 'hello';
console.log(a, b, c, d, e) // h e l l o
  • 类数组的对象都有一个​​length​​​属性,因此可以给这个属性进行及解构赋值:​​let {length : len} = 'hello';​

(4)数值和布尔值的解构赋值

  • 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true
  • 注意​​null​​​和​​undefined​​不能转换为对象,所以如果右边是这两个值,就会报错

(5)函数参数的解构赋值

  • 函数参数表面上是一个数组,在传入参数的那一刻,就会被解构为​​x​​​和​​y​​。
function add([x, y]){
return x + y;
}
add([1, 2]); // 3

解构赋值的应用场景:

  • 交换变量:​​[x, y] = [y, x];​
  • 从函数返回多个值:
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
  • 函数参数的定义:
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
  • 提取JSON数据:
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
  • 定义函数参数的默认值:
function Func({ x = 1, y = 2 } = {}) {}
  • 遍历Map结构:

Map 结构原生支持 ​​Iterator​​ 接口,配合变量的解构赋值,获取键名和键值就非常方便。

const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
  • 输入模块的指定方法:
const { readFile, writeFile } = require("fs")

3. 字符串的扩展

(1)字符的Unicode表示法

ES6加强了Unicode的支持,使用​​\uxxxx​​​形式表示一个字符,其中​​xxxx​​​表示字符的码点,此方式限制码点在​​\u0000​​​-​​\uFFFF​​之间。若超出以上范围,要是用两个双字节的形式表示

(2)遍历器接口

为字符串添加了遍历器接口,使字符串可以被​​for...of​​循环遍历:

for (let code of 'hello') {
console.log(code) // 输出:h e l l o (输出次数为字符串长度)
}

这个接口还可以识别大于​​0xFFFF​​的码点,而for循环会认为包含两个字符,且打印不出来:

let text = String.fromCodePoint(0x20BB6);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
for (let i of text) {
console.log(i); // 输出:𠮶
}

(3)模板字符串

1)模板字符串
传统的JavaScript语言中输出模板经常使用的是字符串拼接的形式,这样写相当繁琐,在ES6中引入了模板字符串来解决以上问题。

模板字符串是增强版的字符串,用反引号​​``​​来标识,他可以用来定义单行字符串,也可以定义多行字符串,或者在字符串中嵌入变量。

注意:

  • 如果在字符串中使用反引号,就要使用​​\​​来转义
  • 如果在多行字符串中有空格和缩进,那么它们都会被保留在输出中
  • 模板字符串中嵌入变量,需要将变量名写在​​${}​​之中
  • 模板字符串中可以放任意的表达式,也可以进行运算,以及引用对象的属性,甚至可以调用函数
  • 如果模板字符中的变量没有声明,会报错
  • 如果
// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// 字符串中调用函数
` ${fn()} `

2)标签模板

模板字符串不仅有上述功能,还可以标签模板的功能,它紧跟在一个函数名后面,这个函数将被调用来处理这个模板字符串。

标签模板其实不是模板,而是一种函数调用的特殊形式,所谓的标签就是函数,跟在后面的模板字符串就是函数的参数。

但是如果模板字符串中的有变量,就会先将模板字符串处理成为多个参数,在进行函数的调用。

注意: tag函数的第一个参数是一个数组,它保存着模板字符串中没有被变量替换的部分,其他参数都是被变量替换的参数。

let a = 5;
let b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);

标签模板的作用:

  • 标签模板的一个重要作用就是过滤HTML字符串,防止用户输入恶意的内容。对用户的提供的变量进行解析,经过处理之后,里面的特殊字符都会被转义。
  • 另外一个重要作用就是多语言的转换(国际化处理)

4. 字符串新增方法

(1)String.fromCodePoint()

ES5 中提供了​​String.fromCharCode()​​​方法,用于从 ​​Unicode​​​ 码点返回对应字符。但是该方法不能识别码点大于​​0xFFFF​​​的字符( 32 位的 UTF-16 字符)。 在ES6中提供了​​String.fromCodePoint()​​方法,解决了以上问题。

需要注意:​​fromCodePoint​​​方法定义在​​String​​​对象上,而​​codePointAt​​方法定义在字符串的实例对象上。

(2)String.raw()

​String.raw()​​ 是一个模板字符串的标签函数,用来获取一个模板字符串的原始字符串。该方法会把所有变量替换,而且对斜杠进行转义,如果斜杠已经转义,那么会再次转义。

String.raw`Hi\n${2+3}!`
// 实际返回 "Hi\\n5!",显示的是转义后的结果 "Hi\n5!"

String.raw`Hi\\n`
// 实际返回 "Hi\\\\n",显示的是转义后的结果 "Hi\\n"

(3)codePointAt()

在JavaScript中,字符以​​UTF-16​​​的格式储存,每个字符固定为2个字节。而那些Unicode码点大于​​0xFFFF​​的字符( 32 位的 UTF-16 字符),JavaScript会把它们看做是两个字符,这些字符需要4个字节来储存。

ES6中提供了​​codePointAt()​​​方法,可以正确的处理需要4个储存的字符,返回一个字符的码点,而对于正常的需要2个字节储存的常规字符,他的返回结果与​​charCodeAt()​​方法相同。

(4)includes(), startsWith(), endsWith()

在ES6之前,我们只能使用indexOf方法来确定一个字符串中是否包含在另一个字符串中,ES6又为我们提供了三个方法:

  • ​includes()​​:返回布尔值,表示是否找到了参数字符串。
  • ​startsWith()​​:返回布尔值,表示参数字符串是否在原字符串的头部。
  • ​endsWith()​​:返回布尔值,表示参数字符串是否在原字符串的尾部。

注意: 这三个方法是实例方法,需要以下形式来调用:

let s = 'Hello world!';
s.startsWith('Hello') // true

这三个方法有第二个可选参数,它是一个数值, 表示开始搜索的位置。不过endWith()方法表示的的是对前n个字符进行搜索,而其他两个方法是从第n个字符开始搜索。

(5)repeat()

​repeat​​方法返回一个新字符串,表示将原字符串重复n次。

'x'.repeat(3) // "xxx"

需要注意:

  • 如果参数是小数,那么会被舍弃小数部分
  • 如果参数是负数或者​​Infinity​​,会报错
  • 如果参数是NAN,那么会被视为0,返回空字符串
  • 如果参数是字符串,那么会先将其转换为数字

(6)padStart(),padEnd()

在ES2017中引入了字符串不全长度的功能,如果某个字符串长度不够,就在头部或者尾部补全,​​padStart()​​​用于头部补全,​​padEnd()​​用于尾部补全。

这两个方法都接收两个参数,第一个参数为数值,表示字符串补全生效后的长度,第二个参数为字符串,表示用来补全的字符串。

'x'.padStart(5, 'ab') // 'ababx'

'x'.padEnd(4, 'ab') // 'xaba'

需要注意:

  • 如果原字符串的长度大于或等于第一个参数,则补全不生效,返回原字符串
  • 如果补全的字符串与原字符串长度和大于第一个参数,那么就会截出超出长度的补全字符串
  • 如果省略第二个参数,那么默认使用空格补全长度

​padStart()​​的常见用途:

  1. 为数值补全指定位数:
'1'.padStart(10, '0') // "0000000001"
  1. 提示字符串格式:
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

(7)trimStart(),trimEnd()

这两个方法是ES2019新增的方法,他们主要用于消除字符串首尾的空格,​​trimStart()​​​消除字符串头部的空格,​​trimEnd()​​消除尾部的空格。这两个方法返回的都是新的字符串,不会修改原字符串。

之前我们使用​​trim()​​​方法来消除字符串首尾的空格,而这​​trimStart()​​​和​​trimEnd()​​ 方法只能消除首部或者尾部的空格。

这两个方法除了能够消除首尾空格之外,还能消除首尾的tab键换行符等不可见的空白符。