从 ES2016 开始,ECMA 修改了标准的制定原则:成文标准要从事实标准中诞生,实现先于标准存在,进入标准草案必须有 JavaScript 引擎实现的支持、社区里有充分的人气和足够的 Test262 测试。

旨在通过更频繁地发布小规模增量更新,促进标准和语言的快速发展,而版本命令规则使用 ECMAScript + 年份的形式。

所以,我们今年又可以看看 ES2020 新增了哪些特性啦。✌️

动态 import

如果你有某个在代码中不常使用的资源,直接导入其所有依赖项可能只是浪费资源的话。我们就可以在需要时通过 async/await 动态导入该依赖项。

新的动态 import 语法看起来像一个函数,但并不是函数,不过动态 import 语法也支持 await

1
2
3
4
5
// math.js

const add = (num1, num2) => num1 + num2;

export { add };
1
2
3
4
5
6
// index.js
const doMathAdd = async (num1, num2) => {
const math = await import('./math.js');
return math.add(5, 10);
};
doMathAdd(4, 2);

在这之前,我们通常会借助 require.ensure() 来实现该特性。

1
2
3
4
5
6
7
8
9
10
// index.js
const doMathAdd = (num1, num2) => {
return new Promise((resolve) => {
require.ensure(['./math.js'], function(require) {
const math = require('./math.js').default;
resolve(math.add(5, 10));
});
});
};
doMathAdd(4, 2);

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import

私有 Class 变量

Class 属性在默认情况下是公共的,可以被外部类检测或修改。现在你可以 # 前缀来定义私有类字段。

1
2
3
4
5
6
7
8
9
10
11
class Message {
#message = "Howdy";
greet() {
console.log(this.#message);
}
}

const greeting = new Message();

greeting.greet(); // Howdy
console.log(greeting.#message); // Private name #message is not defined

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/Private_class_fields

Promise.allSettled

Promise.allSettled() 方法返回一个在所有给定的 promise 已被决议或被拒绝后决议的 Promise,并带有一个对象数组 [{status, ?value, ?reason}] ,每个对象表示对应的 promise 结果。

1
2
3
4
5
6
7
8
9
10
const p1 = Promise.resolve(1);
const p2 = Promise.reject(2);

Promise.allSettled([p1, p2]).then(data => console.log(data));
// [
// Object { status: "fulfilled", value: 1},
// Object { status: "rejected", reason: 2}
// ]

Promise.all([p1, p2]).then(data => console.log(data)); // Uncaught (in promise) 2

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled

空值合并运算符

大家都知道由于 JavaScript 是动态类型的,在进行某些比较操作的时候经常会发生一些「隐式强制转换」,导致我们最终获取到的结果跟我们的预期不一致。例如下面这种情况,我们期望有效值的内容可以被正常打印出来:

1
2
3
4
5
6
7
8
9
let person = {
profile: {
name: '',
age: 0
}
};

console.log(person.profile.name || 'Anonymous'); // Anonymous
console.log(person.profile.age || 18); // 18

如果我们希望该判断条件更加严格点的话,只有值为 nullundefined 时才允许使用默认值,就得改成这种操作:

1
2
3
const { name, age } = person.profile;
console.log(name !== null && name !== undefined ? name : 'Anonymous'); // ''
console.log(age !== null && age !== undefined ? age : 18); // 0

ES2020 引入了一个新的运算符 ??,该运算符的工作原理与上述的条件一致,仅当初始值为 nullundefined 时才读取为运算符右边的值。

1
2
console.log(person.profile.name ?? 'Anonymous'); // ''
console.log(person.profile.age ?? 18); // 0

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator

可选链操作符

与空值合并运算符相似,JavaScript 处理假值时可能无法按照我们的意愿行事。如果我们想要的值是不确定的,我们可以返回一个默认值。但是如果它的路径是不确定的,该怎么办?

可选链操作符 ?. 可以按照操作符之前的属性是否有效,链式读取对象的属性或者使整个对象链返回 undefined?. 运算符的作用与 . 运算符类似,不同之处在于,如果对象链上的引用是 nullish (null 或者 undefined),. 操作符会抛出一个错误,而 ?. 操作符则会按照短路计算的方式进行处理,返回 undefined

1
2
3
4
5
6
const adventurer = {
name: 'Alice'
};

console.log(adventurer.dog?.name); // undefined
console.log(adventurer.dog?.other?.name); // undefined

可选链操作符也可用于函数调用,如果操作符前的函数不存在,也将会返回 undefined。

1
console.log(adventurer.someNonExistentMethod?.()); // undefined

结合其他运算符一起使用,效果更佳:

1
2
3
4
let person = {};

console.log(person?.profile?.name ?? 'Anonymous'); // Anonymous
console.log(person?.profile?.age || 18); // 18

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/%E5%8F%AF%E9%80%89%E9%93%BE

BigInt

BigInt 是一个内置的对象,它提供了一种方法来表示大于 2⁵³-1 的整数,这是 JavaScript 可以可靠地用 number 原语表示的最大数,并由 number 表示。MAX_SAFE_INTEGER 常量。BigInt 可用于任意大整数。

1
2
3
const max = Number.MAX_SAFE_INTEGER;

console.log(max); // 9007199254740991

超过此该值后,一切操作就开始变得有点奇怪…

1
2
3
4
console.log(max + 1); // 9007199254740992
console.log(max + 2); // 9007199254740992
console.log(max + 3); // 9007199254740994
console.log(Math.pow(2, 53) == Math.pow(2, 53) + 1); // true

现在我们可以使用新的 BigInt 数据类型解决此问题。通过在末尾加上字母 n,就可以创建一个 BigInt 类型的数据。我们无法将标准数字与 BigInt 数字混合使用,因此任何数学运算都需要使用BigInt 来完成。

1
2
3
4
5
6
const bigNum = 100000000000000000000000000000n;

console.log(bigNum * 2n); // 200000000000000000000000000000n
console.log(bigNum * bigNum); // 10000000000000000000000000000000000000000000000000000000000n
console.log(bigNum * bigNum * 2); // Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(bigNum * bigNum * 2n); // 20000000000000000000000000000000000000000000000000000000000n

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt

globalThis

在 JavaScript 中,编写可跨平台的访问全局对象的代码特别麻烦。在浏览器环境下,我们会通过 windowselfthisframes 等各种方式来访问;在 Node 环境下,我们又会通过 global 或者 this 来访问;其中,只有 V8 的 d8 或者 JavaScriptCore 的 jsc 之类的 shell 中才会提供访问全局对象的功能。

在松散模式下,可以在函数中返回 this 来获取全局对象,但是在严格模式和模块环境下,this 会返回 undefined。获取环境全局对象的唯一可靠的跨平台方法是 Function('return this')()。但是这在某些情况下会违反 CSP 规则(例如在 Chrome Apps中),因此 es6-shim 使用了类似如下的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var getGlobal = function () {
// the only reliable means to get the global object is
// `Function('return this')()`
// However, this causes CSP violations in Chrome apps.
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
var globals = getGlobal();

if (typeof globals.foo !== 'function') {
// no foo in this environment!
}

globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis,不必担心它的运行环境。为便于记忆,你只需要记住,全局作用域中的 this 就是 globalThis。有了这个功能,就不再需要在整个环境中进行全局搜索了:

1
2
3
if (typeof globalThis.foo !== 'function') {
// no foo in this environment!
}

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/globalThis

延伸阅读

https://github.com/tc39/proposals/blob/master/finished-proposals.md