原本想稍微整理一下 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() 这两个新方法。
- 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);
-
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
汪
汪