你铁定已经知道的 ES 2020

321 阅读6分钟

本文首发于 我的个人博客

年底了,才想起来今年的 ES 版本还没正经看过。ES 标准的发展已经逐渐让人开始忘记其归属哪个版本了,只知道有这么个东西存在,至于 Babel 支持了没?TypeScript 支持了没?Chrome 支持了没?Node 支持了没?进标准了吗?能用于生产了吗?只能靠查了。

上一次正儿八百讨论 ES 标准还是大概快 2 年前,是的,我特意翻了一下,ES 2019 那一篇是在 19 年 2 月份写的,和 ES2018 一起……所以想必到这个时候,大家该关心的已经关心的差不多了,不关心的也不差这会儿了。

我就当给自己补个卡,大家随意吧。

先看标准

如果不是踩到深坑没别的办法,其实不推荐大家去看 ECMA-262,这东西太细了。你不必非得对螺丝的技术规范了如指掌,才能开始拧螺丝,拧不动的时候再来翻,误不了事儿。

对于大部分朋友,我比较推荐看 TC39 的 Github 上整理的这份表格,一目了然。这里我截取了其中归属 ES 2020 的部分,就当做本文的目录吧。

ProposalPublication Year
String.prototype.matchAll2020
import()2020
BigInt2020
Promise.allSettled2020
globalThis2020
for-in mechanics2020
Optional Chaining2020
Nullish coalescing Operator2020
import.meta2020

总体来看,ES 2020 的内容还是很实用的,其中不乏大家期待已久终于转正的特性。下面就来逐个介绍下:

String.prototype.matchAll

可以看做是对 String.prototype.match 的增强,返回一个 Iterator,内容包含了所有正则匹配到的结果(支持捕获组)

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]

import()

Dynamic Import,众望所归终于到来,ES 2019 发布时这项特性还在 Stage 3,今年妥妥转正。

通过 import(),我们可以像用 Axios 发起一个 HTTP 请求一样,去异步加载一个模块,并得到一个 Promise 的返回。

import(`./module-1.js`)
  .then(module => {
    module.doSomething();
  })

import.meta

这个看起来像是访问某个对象的属性的语句,实际上这一整段是一个对象,用于获取当前模块被引用的环境上下文,目前仅支持 url 一个参数。

有了这个语法,我们可以在加载模块的时候给 URL 添加查询参数,以此对模块进行传值。

需要注意的是,import 本身就是一个关键字,同时还可以是一个方法,现在看来可能还是一个对象,但我们不能直接做 console.log(import) 这种骚操作,会报错:

Error: import can only be used in import() or import.meta

因此尽管 import 背后很可能就是一个特别的对象,但我们并不能直接访问它,还是老老实实记住 import.meta 这个固定搭配吧

BigInt

BigInt 是一个新增的内置对象,用于表示比 Number 类型能可靠表示的最大整数 Number.MAX_SAFE_INTEGER (2^53 - 1)更大的整数。

当然这只是 BigInt 的设计用途,硬要用它代替 Number 来表示一般的自然数也不是不可以。比如 1 可以表示为 1n。

BigInt 有两种表达方式,可以当做函数使用,也可以在整数的字面直接量后面加 n 来表示。

const previouslyMaxSafeInteger = 9007199254740991n

const alsoHuge = BigInt(9007199254740991)
// ↪ 9007199254740991n

const hugeString = BigInt("9007199254740991")
// ↪ 9007199254740991n

const hugeHex = BigInt("0x1fffffffffffff")
// ↪ 9007199254740991n

const hugeOctal = BigInt("0o377777777777777777")
// ↪ 9007199254740991n

const hugeBin = BigInt("0b11111111111111111111111111111111111111111111111111111")
// ↪ 9007199254740991n

BigInt 是一种新的数据类型,和 Number 不完全等价,在一些极端场景的判断中需要特别注意。但是 BigInt 可以和 Number 一起进行比大小,因此在排序等场景下可以同时出现没有问题。

typeof 1n === 'bigint'           // true
typeof BigInt('1') === 'bigint'  // true

0n === 0 // false
0n == 0  // true

1n < 2 // true

另外特别需要强调「整数」这一点,BigInt 的四则运算是不会出现小数的,除法运算的结果会向下取整。

const rounded = 5n / 2n
// ↪ 2n, not 2.5n

Promise.allSettled

类似 Promise.all,但不要求全部 fulfilled,只要全部都返回了即可。

globalThis

JavaScript 的顶级对象在不同环境中表现不一致:

  • 浏览器中的顶级对象是 window,但 Node 和 Web Worker 中没有 window。
  • 浏览器和 Web Worker 中 self 也指向顶层对象,但是 Node 没有 self。
  • Node 中的顶级对象是 global,但其他环境都不支持。

globalThis 被设计用来统一这些顶级对象,不管哪种环境,都可以通过 globalThis 来访问顶级对象

for-in mechanics

for-in 语句并不新鲜,但一直以来,该语句在不同环境下的遍历顺序是不尽相同的,这一次只是规范了一下它的遍历顺序。

不过尽管如此,在对遍历顺序有要求的场景下,还是排序最为靠谱,不建议依靠这种不确定的特性。大部分时候 for-ofObject.entries() 等已经可以很好的满足我们了。

Optional Chaining

可选链式操作,这应该是今年大家用的最多的特性了,在 TypeScript 的支持下,这一特性可以说已经比较普及了,我也已经用得很习惯了。

简单来说,这是一个语法糖,引入了 ?. 操作符,用来更好的处理如下的场景:

if (
  user &&
  user.profile &&
  user.profile.name
) {
  // 这种场景下很容易出现 Cannot read property 'name' of undefined
}

// 现在可以写作
if (user?.profile?.name) {}

?. 操作符可以跟在任何你怀疑可能为 undefined 或 null 的属性后面,返回能访问到的最深层级的属性的值,如果中途遇到 undefined 或 null 则提前返回。

obj?.props      // 静态属性
obj?.[expr]     // 动态属性
func?.(...args) // 函数调用

Nullish coalescing Operator

空值合并,这是和 Optional Chaining 同期出现的特性,同样拜 TypeScript 所赐得到了不错的普及。

这同样也是个语法糖,引入了 ?? 操作符,作为用 || 取默认值的一种补充。

|| 取默认值时,只要左值为 falsy,就会用右值代替,而在用 ?? 时,仅当左值为 undefined 或 null 时,才会取右值,否则即便是 falsy 的值,也会得到返回。

简单案例说明一下:

const obj = {
  nullValue: null,
  num: 0,
  text: '',
  isOk: false
};

// 只要 falsy 就取默认值
const undefValue = obj.undefValue || 'defaultVal'; // result: 'defaultVal'
const nullValue = obj.nullValue || 'defaultVal';   // result: 'defaultVal'
const text = obj.text || 'Hello, world!';          // result: 'Hello, world!'
const num = obj.num || 300;                        // result: 300
const isOk = obj.isOk || true;                     // result: true

// 仅 undefined 或 null 才去默认值
const undefValue = obj.undefValue ?? 'defaultVal'; // result: 'defaultVal'
const nullValue = obj.nullValue ?? 'defaultVal';   // result: 'defaultVal'
const text = obj.text ?? 'Hello, world!';          // result: ''
const num = obj.num ?? 300;                        // result: 0
const isOk = obj.isOk ?? true;                     // result: false