ES6~ES11 特性介绍之 ES7~ES11 篇

原本想稍微整理一下 ES 新特性,没想到花了相当多的时间,本文也巨长,依然推荐使用 简悦 生成目录。
原文竟然由于过长无法发布,第一次知道简书还有文章字数限制。现在只能拆成两篇发布。

本系列文章

ES6~ES11 特性介绍之 ES6 篇
ES6~ES11 特性介绍之 ES7~ES11 篇

特性列表

ES7(ES 2016) 特性

2016 年 6 月推出的 ES2016 规范也就是 ES7 只做了微量的更新,添加了两个特性

  • Array.prototype.includes
  • 指数操作符

#01 Array.prototype.includes()

在上文 ES6 部分的 6.3.3 includes(), startsWith(), endsWith(), repeat() 一节中,我们介绍了字符串includes 方法来判断是否包含指定的「参数字符串」。ES2016 则继续补充了数组includes 方法,用来判断数组中是否包含指定的值:

const arr = [1, 2, 3];
console.log(arr.includes(1)); // true
console.log(arr.includes(4)); // false

includes 方法可以传入第二个参数,第二个参数表示「搜索的起始位置」,默认为 0:

const arr = [1, 2, 3];
console.log(arr.includes(1)); // true
console.log(arr.includes(1, 1)); // false。从 1 开始搜索,不存在元素 1

console.log(arr.includes(3, -1)); // true。负数表示倒数位置,-1 表示从倒数第一个元素开始,向后查找

ES2016 出现之前,我们通常使用 indexOf 来判断数组中是否包含某个指定值。但 indexOf 在语义上不够明确直观,同时 indexOf 内部使用 === 来判等,所以存在对 NaN 的误判,includes 则修复了这个问题:

const arr = [NaN];
console.log(arr.indexOf(NaN)); // -1。表示不存在
console.log(arr.includes(NaN)); // true。存在

#02 指数操作符

ES2016 引入了指数操作符 **,用来更为方便的进行指数计算,与 Math.pow() 等效:

console.log(Math.pow(2, 10));  // 1024。计算 2 的十次方
console.log(2 ** 10);  // 1024。计算 2 的十次方

ES8(ES 2017) 特性

#01 async/await

在 ES6 部分的 08 Generator 函数 一节介绍了 Generator 函数,其中 8.5 在异步编程上的应用 小节则是介绍了 Generator 自动调度器

而所谓的 async/await 其实就是实现一个自动调度的 Generator 函数的语法糖

async 其实就是 Generator 函数声明语句的语法糖,只是 async 函数多了一个限制,即返回值为 Promise 对象,即使在编码时返回是各种各样类型的值,但最后都会被封装成一个 Promise 对象。

await 相当于 yield。之前提及过使用第三方自动调度器则 yield 后面需要跟 Thunk 函数Promise 对象。而 await 后面则是统一跟 Promise 对象,如果不是 await 也会自动将后面表达式值转为 Promise 对象。

另一方面 async/await 在语义上也变得更为直观和清晰。当然更重要的是 async/await 内部帮我们实现了 Generator 自动调度器。所以通过 async/await 我们将非常容易的实现一个自动调度的 Generator 函数:

async function asyncByAsync() {
  const result1 = await req("/users");  // req 需要返回 Promise 对象,或者 req 也是一个 async
  const result2 = await req("/admin");  // req 需要返回 Promise 对象,或者 req 也是一个 async
  console.log(result1);
  console.log(result2);
}

#02 对象扩展

2.1 Object.values 和 Object. entries

Object.keys 配套,ES2017 引入了 Object.values 获取对象的所有值,以及 Object. entries 来获取对象的所有键值对:

const obj = {a: 1, b: 2, c: 3};

for (const value of Object.values(obj)) {
  console.log(value);  // 1、2、3
}

for (const [key, value] of Object.entries(obj)) {
  console.log(key, value); // a 1、b 2、c 3
}
2.2 Object.getOwnPropertyDescriptor

ES2017 之前已经存在 Object.getOwnPropertyDescriptor() 方法,用来获取对象中某个属性的描述对象。与此相对,ES2017 引入了 Object.getOwnPropertyDescriptors() 方法,用来获取对象中所有自身属性(非继承属性)的描述对象:

贴心小提示:两个方法名称非常像,不过第一个没有 s,第二个有 s

const obj = {a: 1, b: 2, c: 3};

console.log(Object.getOwnPropertyDescriptors(obj));
// a: {value: 1, writable: true, enumerable: true, configurable: true}
// b: {value: 2, writable: true, enumerable: true, configurable: true}
// c: {value: 3, writable: true, enumerable: true, configurable: true}

#03 SharedArrayBuffer 和 Atomics

JavaScript 是单线程的,但 Web Worker 则是多线程的,Web Worker 多线程之间通过 postMessageonmessage 等方式进行通信传递消息。但当数据量比较大时,直接传递数据效率较低。因此 ES2017 引入了 SharedArrayBuffer 来实现共享内存

// 新建 1KB 共享内存
const sharedBuffer = new SharedArrayBuffer(1024);

// 主线程将共享内存的地址发送出去
w.postMessage(sharedBuffer);

// sharedBuffer 不能直接读写
// 只能通过视图(TypedArray视图和DataView视图)来读写
// 视图的作用是以指定格式解读二进制数据。
// 在共享内存上建立视图,方便读写
const sharedArray = new Int32Array(sharedBuffer);

// Worker 线程
onmessage = function (ev) {
  // 主线程共享的数据
  const sharedBuffer = ev.data;

  // 在共享内存上建立视图,供写入数据
  const sharedArray = new Int32Array(sharedBuffer);
  // ...
};

引入共享内存就必然设计到多线程的竞争读写,那么最好是能够封装一些必要的「原子性操作」以供线程读写。Atomics 对象 并是提供「原子性操作」的角色。

值得注意的是提供了「原子性操作」并不意味着不再存在并发的资源竞争问题,有兴趣的可以查阅LevelDB 中的跳表实现 中的 「并发处理」部分。

Atomics 对象 提供了 store()load()exchange() 等等方法。

#04 字符串扩展

ES2017 引入了两个字符串补全函数 padStart()padEnd()。如果某个字符串不够指定长度,那么这两个函数可以用指定字「填补」直至达到指定长度。两个函数分别对应在字符串的头部和尾部补全字符串:

// 第一个参数表示最大长度为 5,不够则一直用 'ab' 填补在头部
'x'.padStart(5, 'ab'); // 'ababx'

// 因为第一个参数指定最大长度为 4
// 第二次用 'ab' 填补时由于达到了 4,所以只用 ab 中的 a 填补
'x'.padStart(4, 'ab');  // "abax"

'x'.padEnd(5, 'ab'); // 'xabab'

// 如果指定长度小于字符串现有长度
// 则不做处理,返回原字符串
'xxxx'.padStart(3, 'ab'); // 'xxxx'

// 省略第二个参数
'x'.padStart(5);  // '    x' 省略第二个参数,则默认使用空格 ' ' 填充
'x'.padEnd(5);  // 'x    '

#05 函数扩展

ES2017 规定函数的参数列表的结尾可以为逗号

function Dog(
  name,
  age,
  sex,  // ES2017 之前非法,ES2017 后合法
) { /* 函数体 */ }

和对象最后属性结尾可以为逗号的理由一样,如果没有该逗号,添加一个参数就需要修改两行(一行需要加个逗号,一行加属性),这在 git 多人协作时会干扰对本次修改的查看。

ES9(ES 2018) 特性

#01 异步迭代器

在上文 ES6 部分的 #11 Iterator 迭代器 一节中,我们已经介绍了 Iterator 迭代器的基本概念和用法:

const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();  // 获取迭代器

console.log(iterator.next());  // {value: 1, done: false}
console.log(iterator.next());  // {value: 2, done: false}
console.log(iterator.next());  // {value: 3, done: true}

但是上述代码表达的迭代器是一种针对「同步数据」的「同步迭代」,无法实现对「异步数据」的迭代,而 ES2018 引入了对「异步迭代」的支持

重温一下同步迭代器的三个概念

  • Iterable: 一个数据结构只要部署了 Symbol.iterator 属性,我们就可以称之为 Iterable 即可迭代的。Symbol.iterator 属性 为一个函数,该函数应该返回本数据结构的迭代器 Iterator 对象
  • Iterator:通过 Symbol.iterator 函数返回的对象即为用来访问数据结构的 Iterator 对象。该对象通常一开始指向数据结构的起始地址,同时具有 next() 函数,调用 next() 函数指向第一个元素,再次调用 next()函数指向第二个元素.... 重复并可迭代数据结构中所有元素。
  • IteratorResultIterator 对象 每一次调用 next() 访问的元素会被包装返回,返回值为一个对象,其中包含 value 属性表示值、done 属性表示是否已经迭代完成。

异步迭代器基本和同步迭代器基本一一对应

  • Async Iterable: 与同步迭代器的 Symbol.iterator 属性 相对应,异步迭代器需要部署的是 Symbol.asyncIterator 属性。
  • Iterator:与同步迭代器相对应,只不过其中的 next() 函数返回的是对 IteratorResult 结果的 Promise 封装
  • IteratorResult:与同步迭代器相对应。只是不再被直接返回,而是封装成了一个 Promise 对象。

异步迭代器的使用:

// 自定义一个实现了 `Symbol.asyncIterator ` 属性的 AsyncIterable 数据结构
const asyncIterable = new AsyncIterable(['a', 'b']);
// 得到异步迭代器对象
const asyncIterator = asyncIterable[Symbol.asyncIterator]();

// next() 返回的是一个 promise 对象
asyncIterator.next()  
.then(iterResult1 => {  // 通过 .then 处理结果
  // { value: 'a', done: false }  // 返回的 IteratorResult 结果
  console.log(iterResult1);
  // 迭代下一个异步数据
  return asyncIterator.next();
})
.then(iterResult2 => {
  // { value: 'b', done: false }
  console.log(iterResult2);
  return asyncIterator.next();
})
.then(iterResult3 => {
  // { value: undefined, done: true }
  console.log(iterResult3);
});

当然还可以结合 async/await 简化实现:

async function f() {
  const asyncIterable = new AsyncIterable(['a', 'b']);
  const asyncIterator = asyncIterable[Symbol.asyncIterator]();

  // { value: 'a', done: false }
  console.log(await asyncIterator.next());
  // { value: 'b', done: false }
  console.log(await asyncIterator.next());
  // { value: undefined, done: true }
  console.log(await asyncIterator.next());
}

之前也提及过同步迭代器可以使用 for...of 语句进行循环,而对于异步迭代器则可以使用 for await...of 语句:

async function f() {
  for await (const x of createAsyncIterable([1, 2])) {
    console.log(x);
  }
}

#02 Promise.prototype.finally()

ES2018Promise 方面也做了一些补充,添加了 finally() 方法,表示无论 Promise 实例最终成功或失败都会执行的方法:

const promise = new Promise(function(resolve, reject) {
  setTimeout(() => {
    const one = '1';
    reject(one);
  }, 1000);
});

promise
  .then(() => console.log('success'))
  .catch(() => console.log('fail'))
  .finally(() => console.log('finally'))

finally() 函数不接受参数,finally() 内部通常不知道 promise 实例的执行结果,所以通常在 finally() 方法内执行的是与 promise 状态无关的操作。

#03 对象的 Rest/Spread

在 ES6 部分的 2.1.3 rest 操作符 ... 节 和 6.5.1 spread 扩展运算符与数组 节分别简单介绍了 rest 运算符spread 运算符,如下所示:

// rest 运算符: 将元素组织成数组
const [a, ...b] = [1, 2, 3];
console.log(b);  // 输出 [2, 3]

(function(...arr) {
  console.log(arr);  // 输出 [1, 2, 3]
}(1, 2, 3));

// spread 运算符:将数组扩展为元素
const arr = [1, 2, 3];
console.log(...arr);  // 输出 1 2 3

但是之前 rest/spread 运算符 只能作用于数组,ES2018 可以实现将其作用于对象:

// 1. rest 运算符: 将元素组织成对象
const obj = {a: 1, b: 2, c: 3};
const {a, ...rest} = obj;
console.log(rest);  // 输出 {b: 2, c: 3}

(function({a, ...obj}) {
  console.log(obj);  // 输出 {b: 2, c: 3}
}({a: 1, b: 2, c: 3}));

// 2. spread 运算符:将对象扩展为元素
const obj = {a: 1, b: 2, c: 3};
const newObj ={...obj, d: 4};
console.log(newObj);  // 输出 {a: 1, b: 2, c: 3, d: 4}

// 可以用来合并对象
const obj1 = {a: 1, b:2};
const obj2 = {c: 3, d:4};
const mergedObj = {...obj1, ...obj2};
console.log(mergedObj);  // 输出 {a: 1, b: 2, c: 3, d: 4}

#04 正则表达式的扩展

4.1 正则表达式 dotAll 模式

正则表达式中,. 符号代表匹配任意单个字符。但特殊字符 \n\r 等「行终止符」无法被 . 匹配。

ES2018 引入了 s 修饰符实现 dotAll 模式. 真正匹配包含行终止符在内的任意单个字符):

const reg = /dog.age/;
reg.test('dog\nage');  // false。dot . 无法匹配 /n

const reg_dotall = /dog.age/s;  // 通过 s 修饰符实现 dotAll 模式
reg_dotall.test('dog\nage');  // true
4.2 正则表达式后行断言

ES2018 之前,JavaScript 正则表达式支持「先行断言」和「先行否定断言」。

  • 先行断言 x(?=pattern)
    紧接 x 之后的字符应该匹配 pattern,匹配上后返回的结果不包含匹配 pattern 的字符。由于 pattern 不消耗字符,所以这类断言也被视为「零宽断言」。
const reg = /aa(?=bb)/;  // aa 后面紧跟 bb
reg.exec('aabb');  // 输出 aa。aabb 符合上述模式,但匹配结果不会包含 bb 字符。
  • 先行否定断言 x(?!pattern)
    紧接 x 之后的字符应该不匹配 pattern
const reg = /aa(?!bb)/;  // aa 后面不能紧跟 bb
reg.exec('aabb');  // null
reg.exec('aab');  // 输出 aa。aab 满足上述模式,但注意匹配结果不会包含括号 () 里的内容

ES2018 补充了与先行相对应的「后行断言」和「后行否定断言」:

  • 后行断言 (?<=pattern)x
    紧接 x 之前的字符应该匹配 pattern
const reg = /(?<=aa)bb/;  // bb 前面紧接着 aa
reg.exec('aabb');  // 输出 bb。aabb 满足模式,但是匹配结果不包含括号内的内容即 aa
  • 后行否定断言 (?<!pattern)x
    紧接 x 之前的字符应该不匹配 pattern
const reg = /(?<!aa)bb/;  // bb 前面不能紧接着 aa
reg.exec('aabb');  // null

reg.exec('abb');  // 输出 bb。bb 前面没有紧接着 aa,满足模式。但输出结果不会包含 a
4.3 正则表达式 Unicode 属性类

ES2018 引入了 \p{...}\P{...} 来实现对 Unicode 属性的指定。例如指定查找范围为「希腊符号」,以前只能限定字符区间,现在可以直接指定属性

// \p 匹配满足条件的数据
const reg1 = /\p{Script=Greek}/u;  // 指定 Script 为 Greek,即只匹配「希腊字符」。
reg1.test('π') // true

// \P 与 \p 相反,表示匹配不满足条件的数据
const reg2 = /\P{Script=Greek}/u;  // 指定 Script 为 Greek,但 \P 表示匹配「非希腊字符」。
reg2.test('π') // false
4.4 正则表达式具名组匹配

ES2018 之前,我们可以通过 () 来实现分组:

const reg = /(\d{4})-(\d{2})-(\d{2})/;

const arr = reg.exec('2020-12-31');
const year = arr[1]; // 2020
const month = arr[2]; // 12
const day = arr[3]; // 31

上面的分组通过数组来对应,每个括号对应一个数组元素,通过下标来访问。这种方式在语义上不够清晰,在操作时也不够直观。所以 ES2018 引入了 ?<name> 实现给组进行命名

// 在 \d{4} 前面添加 ?<year>,给该组命名为 year
// ?<month> 和 ?<day> 同理
const reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const obj = reg.exec('2020-12-31');

// 直接通过 year 名称获取组匹配结果
const year = obj.groups.year; // 2020
const month = obj.groups.month; // 12
const day = obj.groups.day; // 31

#05 模板字符串的转义限制放宽

在 ES6 的 6.3.1 模板字符串 一节中介绍了模板字符串标签模板,其中标签模板默认会将字符串转义:

// 报错。\unicode 转义失败
const doc = latex`\newcommand{\unicode}{\textbf{Unicode!}}`; 

上述代码中的 \unicode 被当作 Unicode 字符进行转义,从而导致错误。ES2018 对此的更新是「放宽」转义限制。遇到无法转义的字符时返回 undefined 而不是报错,这样可以使得程序继续运行。同时函数可以从 6.3.1 模板字符串 一节中提及的第一个参数中的 raw 属性拿到原始字符串

但注意上述规则针对标签模板,普通模板字符串遇到无法转义的 Unicode 字符还是会报错:

const str = `hello \unicode`;

ES10(ES 2019) 特性

#01 字符串扩展

1.1 U+2028 和 U+2029(JSON Superset)

ES2019 之前,JavaScript 的字符串中是不能直接包含一些特殊 Unicode 字符,例如 U+000D回车U+000A 换行以及 U+2028 行分隔符U+2029 段分隔符等。

但是由于 JSON 格式规范允许字符串里直接包含 U+2028 行分隔符U+2029 段分隔符(非转义形式),这样将导致 JavaScript 在解析包含这两种特殊字符的 JSON 串时出错。

于是 ES2019 添加了字符串对这两个字符的支持,可以在字符串中使用这两种字符:

const str1 = eval("'\u2029'");  // 不报错。解析转义字符 \u2029
1.2 trimStart 和 trimEnd

ES2019 添加了 trimStarttrimEnd 这两个新方法,分别用来去除字符串的首部和尾部空白字符

const hiMessage = `   Hello Word!   `;

console.log(hiMessage.trimStart());  // 'Hello Word!   '
console.log(hiMessage.trimEnd());  // '   Hello Word!'

#02 数组扩展

ES2019 在数组类型上添加了 flat()flatMap() 这两个新方法。

  1. flat()

flat 函数用来将嵌套数组(多层次数组)「拉平」:

// [1, 2, 3, 4];
[1, 2, [3, 4]].flat();

// [1, 2, 3, 4, 5] 参数为层次设置,2 表示作用于内嵌 2 层
[1, 2, [3, [4, 5]]].flat(2); 

// [1, 2, 3, 4, 5],Infinity 表示无论多少层,拉平所有层次
[1, 2, [3, [4, [6]]]].flat(Infinity);  
  1. flatMap()
    flatMap 函数对数组每个元素进行一些「处理」,然后处理返回的是一个数组,那么数组会被 flat 拉平,上述的「处理」由回调函数给出:
// 先对每个元素进行 ele * 2 ,并返回数组,即 [2], [4], [6], [8]
// 再对返回值应用 flat 函数拉平
// [[2], [4], [6], [8]].flat(),最终得到 [2, 4, 6, 8]
[1, 2, 3, 4].flatMap(ele => [ele * 2]); // [2, 4, 6, 8]

// 注意 flatMap 中 flat 只会拉平一层
[1, 2, 3, 4].flatMap(ele => [[ele * 2]]); // [[2], [4], [6], [8]]

#03 对象扩展

ES2019 在对象 Object 上添加了 fromEntries 方法,该方法相当于 Object.entries 方法的逆过程,用来将一个 key-value键值对列表」转换成「对象」。

Object.fromEntries(iterable) 所接受的参数是像 ArrayMap 那样实现了 iterable 迭代协议(且迭代的每个元素包含 key\value 两个数值)的数据结构:

// Array
const dog = [['name', 'wangwang'], ['age', 5]];
const obj1 = Object.fromEntries(dog);
console.log(obj1);  // {name: "wangwang", age: 5}

// Map
const cat = new Map();
cat.set('name', 'miaomiao');
cat.set('age', 2);
const obj2 = Object.fromEntries(cat);
console.log(obj2);  // {name: "miaomiao", age: 2}

#04 Symbol 的扩展

在 ES6 部分的 6.1 节介绍了 Symbol,其中知晓传入的参数相当于「描述」:

let s1 = Symbol("dog");  // dog 为描述

ES2019 之前,获取一个 Symbol 值的描述需要通过 String 方法toString 方法

console.log(String(s1));  // "Symbol(dog)"
console.log(s1.toString());  // "Symbol(dog)"

ES2019 补充了属性 description,用来直接访问「描述」:

console.log(s1.description);  // 直接获取到 dog

#05 函数的扩展

ES2019 对函数的 toString() 方法进行了扩展,以前这个方法只会输出函数代码,但会省略注释和空格。ES2019toString() 则会保留注释、空格等,即输出的是原汁原味的原始代码

function sayHi() {
  /* dog dog */
  console.log('wangwang.');
}

sayHi.toString();  // 将输出和上面一样的原始代码

#06 JSON.stringify() 的优化

JSON 数据需要为是 UTF-8 编码,但在 ES2019 之前 JSON.stringify() 可能输出不符合 UTF-8 规范的字符。

原因在于 UTF-8 标准中为了表示 oxFFFF 的字符,将 0xD8000xDFFF 范围内的编码用来组合使用,不能单独使用。此时将这个范围内的编码传给 JSON.stringify() ,函数将返回乱码:

JSON.stringify('\uD800');  // '"�"'。 ES2019 之前

ES2019 则对这种情况进行特殊处理,如果遇到无对应编码结果的编码,则直接返回转义字符串:

JSON.stringify('\uD800');   // 返回 '"\\ud800"'。ES2019 之后

#07 无参 catch

ES2019 以前,catch 会带有参数,而现在可以不带参数:

// ES2019 之前
try {
   ...
} catch(error) {
   ...
}

// ES2019 之后
try {
   ...
} catch {
   ...
}

#08 Array.prototype.sort() 稳定

ES2019 之前数组的 sort() 方法是否稳定是不明确的,任由浏览器自己实现。ES2019 开始明确规定排序算法必须是稳定的。

ES11(ES 2020) 特性

#01 模块化扩展

1.1 动态导入 import()

在上文 ES6 部分的 04 模块 Module 节中介绍了模块相关的知识,其中模块导入使用 import 命令实现,但是了解到 import 命令是一种静态导入,所以在 ES2020 之前我们无法像 CommonJS 中的 require 那样动态导入模块。那么「条件导入」、「运行时确定模块」等功能就会被限制。

ES2020 引入了 import() 操作符来实现动态导入:

const module = 'animal';
if (type === 1) {
  module = 'dog';
} else if (type === 2) {
  module = 'cat';
}

// 导入模块的名称可以动态计算,即运行时再确定
const utils = await import(module);

// 使用 .then 进行回调处理
import(module)
  .then(module => module.sayHi())
  .catch(error => console.log(error)); 
1.2 import.meta

ES2020 还引入了 import.meta 对象,其中包含了模块的一些元信息,例如import.meta.url存储了浏览器 URL 或文件名。

1.3 export * as module from "xxx";

在 ES6 中 4.3 导入导出混合写法 一节中介绍了 importexport 混合用法,例如:

export * from 'my_module';

但是还有一种导入写法没有对应的混合写法:

import * as module from "xxx";

ES2020 补充了上述语句对应的混合导出写法:

export * as module from "xxx";

02 字符串扩展

2.1 matchAll

在正则表达式中,如果一个字符串有多个匹配,我们可以通过循环来依次获取:

const reg = /dog/g;
const str = 'dog1dog2dog3dog4';

const dogs = [];
let dog;
// 循环调用 exec,依次获取匹配结果
while (dog = reg.exec(str)) {
  dogs.push(dog);
}

console.log(dogs);  // array[4]

ES2020 字符串引入了 matchAll 方法,可以一次性取出所有匹配,并返回一个结果的迭代器

const reg = /dog/g;
const str = 'dog1dog2dog3dog4';

const dogs = [];
// 一次性取出所有匹配结果,并返回迭代器
for (const dog of str.matchAll(reg)) {
  dogs.push(dog);
}

console.log(dogs);  // array[4]

#03 BigInt 类型

在 JavaScript 中,数值类型 Number 被保存为** 64 位浮点数,所以计算精度和表示范围都有一定限制。ES2020 新增了 BigInt 数据类型,这也是 JavaScript 引入的第八种基本类型**。

BigInt 被用来表示整数,以 n 为后缀,且没有位数限制:

// BigInt 使用 n 作为整数后缀
const a = 416576138978n;  // 后缀使用 n
const b = 861387698979n;

console.log(a * b); // 358833561803815532703462n
 
// 普通整数精度丢失
console.log(Number(a) * Number(b));  // 3.5883356180381556e+23

BitInt 整数和普通整数不全等(类型不同)

console.log(123n === 123);  // false

自然还有配套的 BigInt 对象,可以通过对应的构造函数创建 BigInt 数值,

const num = BigInt('1233243423');

BigInt 对象具备 asUintNparseInt 等方法:

// 1233243423n。asUintN 将给定的 BigInt 转为 0 到 2^width - 1 之间对应的值。
BigInt.asUintN(1024, num);  

// 23423423n。类似于 Number.parseInt(),将一个字符串转换成指定进制的 BigInt。
//(现有浏览器不一定支持)
BigInt.parseInt('23423423', 10); 

#04 Promise 扩展

4.1 Promise.allSettled()
const promises = [
  fetch('/api-1'),
  fetch('/api-2'),
  fetch('/api-3'),
];

Promise.allSettled(promises)
  .then(results => console.log(results));

allSettled() 方法将一组 Promise 实例参数封装成一个 Promise 实例,只有当被封装的所有 Promise 实例全部返回结果后(不管是成功 fulfilled 还是失败 rejected)才算结束

同时所有 Promise 实例的返回结果被封装在 results 参数中传递给通过 .then() 函数指定的回调函数。

results 中的每个元素都对应一个 Promise 实例的执行结果,结果中的 status 表示执行成功与否。如果状态为 fulfilled,则另一个字段为 value 表示操作返回值。如果状态为 rejected,则另一个字段为 reason 表示错误信息:

Promise.allSettled(promises)
  .then(results => console.log(results));
// results
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]

#05 globalThis

ES2020 之前,浏览器Web WorkerNode.js 等不同环境的「顶层对象」有所不同。例如浏览器中可以通过 windowself 来获取顶层对象,Web Worker 通过 self 获取顶层对象,到了 Node.js 则是 global

这种不一致性会导致代码需要进行无谓的判断,添加复杂性。所以 ES2020 引入了统一的 globalThis 表示顶层对象。以后不管在哪个环境,都可以通过 globalThis 拿到顶层对象。

#06 链判断运算符

在平时的编程中,我们经常需要获取深层次属性,例如 system.user.addr.province.name。但在获取 name 这个属性前我们需要一步步的判断前面的属性是否存在,否则并会报错

const name = (system && system.user && system.user.addr && system.user.addr.province && system.user.addr.province.name) || 'default';

为了简化上述过程,ES2020 引入了「链判断运算符」?.

const name = system?.user?.addr?.province?.name || 'default';

?. 会判断运算符左侧的对象是否存在,如果不存在即为 nullundefined 那么就会不再继续而是提前返回 undefined,存在则继续往下运算。

「链判断运算符」还有另外几种形式:

a?.[x]
// 等同于
a == null ? undefined : a[x]

a?.b()
// 等同于
a == null ? undefined : a.b()

a?.()
// 等同于
a == null ? undefined : a()

#06 Null 判断运算符

在编程过程我们经常会遇到这样的常见,如果某个属性不为 nullundefined,那么就获取该属性,如果该属性为 nullundefined,则取一个默认值:

const name = dogName ? dogName : 'default';

可以通过 || 来简化:

const name =  dogName || 'default';

但是 || 的写法存在一定的缺陷,当 dogName0false 的时候也会走到 default 的逻辑。

所以 ES2020 引入了 ?? 运算符。只有 ?? 左边为 nullundefined时才返回右边的值:

const dogName = false;
const name =  dogName ?? 'default';  // name = false;

#07 for-in 循环的规则

ES2020for-in 循环进行了遍历规则的补充,详情可参阅proposal-for-in-order

参考资料

tc39 From GitHub
MDN Web Docs
ECMAScript 6 教程
Advanced ES6 Destructuring Techniques
The final feature set of ECMAScript 2016 (ES7)
ECMAScript 2017 (ES8): the final feature set
ECMAScript 2018: the final feature set
ES2018: asynchronous iteration
ECMAScript 2019: the final feature set
ECMAScript 2020: the final feature set

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容