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 多线程之间通过 postMessage、onmessage 等方式进行通信传递消息。但当数据量比较大时,直接传递数据效率较低。因此 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()函数指向第二个元素.... 重复并可迭代数据结构中所有元素。
  • IteratorResult:Iterator 对象 每一次调用 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()

ES2018 在 Promise 方面也做了一些补充,添加了 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 添加了 trimStart 和 trimEnd 这两个新方法,分别用来去除字符串的首部和尾部空白字符:

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) 所接受的参数是像 Array、Map 那样实现了 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() 方法进行了扩展,以前这个方法只会输出函数代码,但会省略注释和空格。ES2019 的 toString() 则会保留注释、空格等,即输出的是原汁原味的原始代码:

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

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

#06 JSON.stringify() 的优化

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

原因在于 UTF-8 标准中为了表示 oxFFFF 的字符,将 0xD800 到 0xDFFF 范围内的编码用来组合使用,不能单独使用。此时将这个范围内的编码传给 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 导入导出混合写法 一节中介绍了 import 和 export 混合用法,例如:

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 对象具备 asUintN、parseInt 等方法:

// 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 Worker、Node.js 等不同环境的「顶层对象」有所不同。例如浏览器中可以通过 window 或 self 来获取顶层对象,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';

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

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

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

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

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

#06 Null 判断运算符

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

const name = dogName ? dogName : 'default';

可以通过 || 来简化:

const name =  dogName || 'default';

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

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

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

#07 for-in 循环的规则

ES2020 对 for-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

汪

汪

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容