JavaScript 是一门不断迭代、快速发展的语言,自 2015 年 ES2015(ES6)面世以来,许多新特性将以前所未有的速度被整合到现代浏览器和其他 JavaScript 运行时引擎(例如 Node.js)中。在本文中,我们将看一看 ES2020(ES10) 新增的一些最新和最出色的特性。

由于有相当多的用户不会主动去升级他们的浏览器,为了能在旧版本浏览器中运行这些新特性,我们使用 Babel 编译器来进行转码。这里我们使用的是 parcel-bundler编译器。

Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

https://www.babeljs.cn/docs/
yarn add parcel-bundler

1. Private Class Variables(类的私有字段)

类的主要目的之一是将我们的代码编写更多可重用的模块,当你创建一个在许多不同地方使用的类时,你可不想让它的一些私有变量暴露出来。

这时候,我们通过在变量或函数前面添加一个简单的哈希符号 #,将它们定义为私有变量完全保留给类内部使用,这样就不需要使用闭包来隐藏不想暴露给外界的私有变量了。

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

2. Promise.allSettled

我们知道 Promise.all 具有并发执行异步任务的能力。但是,如果参数中的任何一个 promise 返回 reject,那么整个 Promise.all 调用会立刻停止,并返回一个reject 的新 Promise 对象。

Promise.allSettled 跟 Promise.all 类似, 不同之处在于,它不会进行短路,也就是说当 Promise 全部处理完成后,我们可以拿到每个 Promise 的状态,而不管是否处理成功。查看下面代码,首先创建2个新的 Promise,使用 Promise.allSettled 方法执行,当传递给它的所有 Promise 都完成时,返回了一个数组,其中包含了每个 Promise 的状态数据。

const p1 = new Promise((res, rej) => setTimeout(res, 1000));

const p2 = new Promise((res, rej) => setTimeout(rej, 1000));

Promise.allSettled([p1, p2]).then(data => console.log(data));

// [
//   Object { status: "fulfilled", value: undefined},
//   Object { status: "rejected", reason: undefined}
// ]

3. Nullish Coalescing Operator(空位合并运算符)

因为 JavaScript 是动态类型,在定义变量时,需要牢记 JavaScript 对 true/false 值的处理。假设有一个对象,这时我们希望允许使用伪值,例如“空字符串“或“0”。这时候,利用||运算符设置默认值会产生相反的效果,因为默认值会覆盖原来有效的值。我们来看看下面代码会更加清晰明了:

let person = {
  profile: {
    name: "",
    age: 0
  }
};

console.log(person.profile.name || "Anonymous"); // 此处想要输出的是 空字符,而非 Anonymous
console.log(person.profile.age || 18); // 此处想要输出的是 0,而非 18

这时,我们就可以使用 ES2020 的新特性:空位合并运算符 ?? 来处理上面这个情况:

console.log(person.profile.name ?? "Anonymous"); // ""
console.log(person.profile.age ?? 18); // 0

4. Optional Chaining Operator(可选链运算符)

可选链运算符可以让我们在查询具有多个层级的对象时,不再需要进行冗余的各种前置校验。代码解释一切:

const articleData = await (
    await fetch('https://api/articles.json')
).json()

// 可选链调用
const nullIsCool = articleData.articles[0]?._next?.layout;

// 请不要让我再用这样的写法
const thisIsNasty = articleData.articles[0] &&
                    articleData.articles[0]._next &&
                    articleData.articles[0]._next.title;

可以看出,有了可选链运算符,只需要使用 ?. 来访问嵌套对象,可以大量简化繁琐的前置校验操作,代码更安全,更清爽。

5. BigInt(大整型)

Javascript 可以处理的最大数字是 2^53,我们可以查看 MAX_SAFE_INTEGER

const max = Number.MAX_SAFE_INTEGER;

console.log(max); // 9007199254740991

超出这个范围的整数计算就会变得很奇怪。

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,我们可以开始使用大数字并进行计算交互。

const bigNum = 100000000000000000000000000000n;

console.log(bigNum * 2n); // 200000000000000000000000000000n

6. Dynamic Import(动态引入)

如果你有一个功能很完善的功能模块文件,但其中某些功能可能很少使用,导入这些所有依赖项可能会浪费很多时间。现在,我们可以使用 async / await 在需要时才动态导入这些依赖项。

// math.js

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

export { add };
const doMath = async (num1, num2) => {
  if (num1 && num2) {
    const math = await import('./math.js');
    console.log(math.add(5, 10));
  };
};

doMath(4, 2);

参考

https://juejin.im/post/5e1bcaa1f265da3e140fa3ee#heading-8

https://alligator.io/js/es2020/