0%

【译】从 ES2016 到 ES2020 的所有特性

自 ECMA2015 (6th) 大幅更新之后, ECMA 标准变更成每年6月发布一个版本进行小幅度更新。为方便温习和查找,汇总一下近五年的所有版本特性。本文共涵盖了 ES2016、ES2017、ES2018、ES2019、ES2020 五个版本的更新内容。翻译有删改,仅供快速查找使用。

前言:关于ECMA

ECMA 相关stage-x 处于某个阶段,描述的是 ECMA 标准相关的内容。根据提案划分界限,stage-x 大致分为以下阶段:

  • stage-0:还是一个设想,只能由 TC39 成员或 TC39 贡献者提出。
  • stage-1:提案阶段,比较正式的提议,只能由 TC39 成员发起,这个提案要解决的问题必须有正式的书面描述。
  • stage-2:草案,有了初始规范,必须对功能语法和语义进行正式描述,包括一些实验性的实现。
  • stage-3:候选,该提议基本已经实现,需要等待实验验证,用户反馈及验收测试通过。
  • stage-4:已完成,必须通过 Test262 验收测试,下一步就纳入 ECMA 标准。

总结起来就是数字越大,越成熟。

ES2016 新特性

ES2016 只更新了两个特性:

  • Array.prototype.includes()
  • 指数运算符

Array.prototype.includes()

该方法用于检测数组中是否包含某个值,包含则返回 true,否则返回 false。

1
2
3
4
5
6
let array = [1, 2, 4, 5];

array.includes(2);
// true
array.includes(3);
// false

结合 fromIndex 使用:

可以为 .includes() 提供一个起始索引,默认是 0,接受负数值。

1
2
3
4
5
6
7
8
9
10
11
12
let array = [ 1, 3, 5, 7, 9, 11 ];

array.includes(3, 1);
// find the number 3 starting from array index 1
// true
array.includes(5, 4);
//false
array.includes(1, -1);
// find the number 1 starting from the ending of the array going backwards
// false
array.includes(11, -3);
// true

指数操作符 (**)

在 ES2016 前我们会这样写:

1
2
3
4
Math.pow(2, 2);
// 4
Math.pow(2, 3);
// 8

现在,有了指数运算符之后,可以这样写:

1
2
3
4
2 ** 2;
// 4
2 ** 3;
// 8

这在多次操作指数运算的时候很有用:

1
2
3
4
2 ** 2 ** 2
// 16
Math.pow(Math.pow(2, 2), 2);
// 16

Math.pow() 需要连续调用,这会使代码看起来很长不宜阅读。使用指数运算符的方式更快更简洁。

ES2017 新特性

ES2017 介绍了更多新特性,如 String padding,Object.entries(), Object.values(), 原子性操作, 以及 Async、Await 等。

字符串填充 ( String.padStart() 和 String.padEnd() )

.padStart() 对字符串头部进行填充, .padEnd() 对字符串尾部进行填充:

1
2
3
4
"hello".padStart(6);
// " hello"
"hello".padEnd(6);
// "hello "

为什么只填充1个空格而不是6个?是因为 “hello” 一共是五个字符,而 .padStart.padEnd 的入参是填充后的字符串长度,所以之只会填充一个空格。

使用 padStart 实现文本右对齐

1
2
3
4
5
6
7
8
9
10
const strings = ["short", "medium length", "very long string"];

const longestString = strings.sort((s1, s2) => s2.length - s1.length).map(str => str.length)[0];

strings.forEach(str => console.log(str.padStart(longestString)));

// very long string
// medium length
// short

第一步获取了数组中最长字符串的长度,接下来用该长度填充数组中的每个字符串,即打印出一组右对齐的字符串。

自定义填充值

除了默认的空格,还可以使用字符串和数字进行填充。

1
2
3
4
5
6
"hello".padEnd(13," Alberto");
// "hello Alberto"
"1".padStart(3, 0);
// "001"
"99".padStart(3, 0);
// "099"

Object.entries() 和 Object.values()

首先创建一个Object:

1
2
3
4
5
const family = {
father: "Jonathan Kent",
mother: "Martha Kent",
son: "Clark Kent",
}

在上个版本的 javascript 中,我们可以使用如下方式获取 Object 中的值:

1
2
3
4
Object.keys(family);
// ["father", "mother", "son"]
family.father;
"Jonathan Kent"

Object.keys() 仅会返回对象中所有的键名。

现在又多了两种可以访问对象的方法:

1
2
3
4
5
6
7
Object.values(family);
// ["Jonathan Kent", "Martha Kent", "Clark Kent"]

Object.entries(family);
// ["father", "Jonathan Kent"]
// ["mother", "Martha Kent"]
// ["son", "Clark Kent"]

Object.values() 以数组形式返回对象所有值。
Object.entries() 同样以数组形式返回对象中的键值对。

Object.getOwnPropertyDescriptors()

这个方法会返回对象所有自身属性的描述。描述性的字段有:valuewritable, get, set, configurableenumerable

1
2
3
4
5
6
7
8
9
10
11
12
13
const myObj = {
name: "Alberto",
age: 25,
greet() {
console.log("hello");
},
}
Object.getOwnPropertyDescriptors(myObj);
// age: {value: 25, writable: true, enumerable: true, configurable: true}

// greet: {value: ƒ, writable: true, enumerable: true, configurable: true}

// name: {value: "Alberto", writable: true, enumerable: true, configurable: true}

尾行逗号

这仅仅是语法上的一个小改变。现在在写 Object 属性值时,我们可以在每个值后边加上一个逗号,不论它是否是最后一个。

1
2
3
4
5
6
7
8
9
10
11
// from this
const object = {
prop1: "prop",
prop2: "propop"
}

// to this
const object = {
prop1: "prop",
prop2: "propop",
}

注意上述第二个例子中的最后一个逗号,即使你不写它也不会报错,只是写上会更方便的开发者们协作。

共享内存和原子性操作

下述引自 MDN:

多个共享内存的线程能够同时读写同一位置上的数据。原子操作会确保正在读或写的数据的值是符合预期的,即下一个原子操作一定会在上一个原子操作结束后才会开始,其操作过程不会中断。

这些原子操作属于 Atomics 模块。与一般的全局对象不同,Atomics 不是构造函数,因此不能使用 new 操作符调用,也不能将其当作函数直接调用。Atomics 的所有属性和方法都是静态的(与 Math 对象一样)。

方法示例:

  • add / sub
  • and / or / xor
  • load / store

Atomics 通常和 SharedArrayBuffer 对象(通用的固定长度二进制数据缓冲区)一起使用。
来看一下几个 Atomics方法的使用示例:

Atomics.add(), Atomics.sub(), Atomics.load(), and Atomics.store()

Atomics.add() 共接受三个参数:array、index、value。并返回该索引在执行操作前的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// create a `SharedArrayBuffer`
const buffer = new SharedArrayBuffer(16);
const uint8 = new Uint8Array(buffer);

// add a value at the first position
uint8[0] = 10;

console.log(Atomics.add(uint8, 0, 5));
// 10

// 10 + 5 = 15
console.log(uint8[0])
// 15
console.log(Atomics.load(uint8, 0));
// 15

要从数组中检索特定的值,可以使用 Atomics.load() 并传递两个参数,一个数组和一个索引。
Atomics.sub() 的使用方式与 Atomics.add() 类似,只不过它是减去某个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// create a `SharedArrayBuffer`
const buffer = new SharedArrayBuffer(16);
const uint8 = new Uint8Array(buffer);

// add a value at the first position
uint8[0] = 10;

console.log(Atomics.sub(uint8, 0, 5));
// 10

// 10 - 5 = 5
console.log(uint8[0])
// 5
console.log(Atomics.store(uint8, 0, 3));
// 3
console.log(Atomics.load(uint8, 0));
// 3

上述示例调用 Atomics.sub() 方法,实现 unit8[0] - 5 ,相当于 10 - 5。如同 Atomics.add() 一样,该方法也会返回数组中该索引在执行操作前的值。

使用 Atomics.store() 来存储一个值,使用 Atomics.load() 来加载一个值。

Atomics.and(), Atomics.or(), Atomics.xor()

这三个方法都在数组的给定位置执行按位的 AND、OR 和 XOR 操作。不再赘述。

Async 和 Await

ES2017 提供了两个操作 Promise 的新方法:”async/await”。

回顾一下 Promise

在介绍新语法之前,让我们快速浏览下之前我们是怎么使用 Promise 的:

1
2
3
4
5
6
7
8
9
10
11
// fetch a user from github
fetch('api.github.com/user/AlbertoMontalesi').then( res => {
// return the data in json format
return res.json();
}).then(res => {
// if everything went well, print the data
console.log(res);
}).catch( err => {
// or print the error
console.log(err);
})

上述是一个非常简单的例子:请求一个 Github 用户的数据,并打印。下面来看个复杂点的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function walk(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject ("the value is too small");
}
setTimeout(() => resolve(`you walked for ${amount}ms`),amount);
});
}

walk(1000).then(res => {
console.log(res);
return walk(500);
}).then(res => {
console.log(res);
return walk(700);
}).then(res => {
console.log(res);
return walk(800);
}).then(res => {
console.log(res);
return walk(100);
}).then(res => {
console.log(res);
return walk(400);
}).then(res => {
console.log(res);
return walk(600);
});

// you walked for 1000ms
// you walked for 500ms
// you walked for 700ms
// you walked for 800ms
// uncaught exception: the value is too small

来看下,如何用新语法 async / await 来重写 Promise

Async 和 Await
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function walk(amount) {
return new Promise((resolve, reject) => {
if (amount < 500) {
reject ("the value is too small");
}
setTimeout(() => resolve(`you walked for ${amount}ms`),amount);
});
}

// create an async function
async function go() {
// use the keyword `await` to wait for the response
const res = await walk(500);
console.log(res);
const res2 = await walk(900);
console.log(res2);
const res3 = await walk(600);
console.log(res3);
const res4 = await walk(700);
console.log(res4);
const res5 = await walk(400);
console.log(res5);
console.log("finished");
}

go();

// you walked for 500ms
// you walked for 900ms
// you walked for 600ms
// you walked for 700ms
// uncaught exception: the value is too small

让我们来分解一下上述代码都做了什么:

  • 创建一个异步函数需要在 function 前面添加 async 关键词
  • 这个关键词会告诉 Javascript 返回一个 Promise
  • 如果指定 async 函数返回一个非 Promise 的值,那么这个值将会被包含在 Promise 中然后被返回
  • 顾名思义, await 会告诉 Javascript 等待 promise 返回结果
错误处理

通常在 promise 中,我们使用 .catch() 捕获最终的错误。现在有一点不同了:

1
2
3
4
5
6
7
8
9
10
11
async function asyncFunc() {

try {
let response = await fetch('http:your-url');
} catch (err) {
console.log(err);
}
}

asyncFunc();
// TypeError: failed to fetch

ES2018 新特性

对象扩展运算符

还记得 ES6 中我们可以使用扩展运算符来做什么吗:

1
2
3
4
5
6
const veggie = ["tomato", "cucumber", "beans"];
const meat = ["pork", "beef", "chicken"];

const menu = [...veggie, "pasta", ...meat];
console.log(menu);
// Array [ "tomato", "cucumber", "beans", "pasta", "pork", "beef", "chicken" ]

现在,扩展运算符同样适用于对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let myObj = {
a: 1,
b: 3,
c: 5,
d: 8,
}

// we use the rest operator to grab everything else left in the object.
let { a, b, ...z } = myObj;
console.log(a); // 1
console.log(b); // 3
console.log(z); // {c: 5, d: 8}

// using the spread syntax we cloned our Object
let clone = { ...myObj };
console.log(clone);
// {a: 1, b: 3, c: 5, d: 8}
myObj.e = 15;
console.log(clone)
// {a: 1, b: 3, c: 5, d: 8}
console.log(myObj)
// {a: 1, b: 3, c: 5, d: 8, e: 15}

使用扩展运算符,我们可以轻松的复制对象(浅复制)。

异步的迭代

使用异步的迭代,我们可以异步的遍历数据。
引自文档

异步迭代器很像迭代器,只不过迭代器的 next 方法返回一对 { value, done }

为此,我们将使用一个 for-await-of 循环,它将迭代转换成 Promise。

1
2
3
4
5
6
7
8
9
10
11
12
const iterables = [1, 2, 3];

async function test() {
for await (const value of iterables) {
console.log(value);
}
}

test();
// 1
// 2
// 3

在执行过程中,[Symbol.asyncIterator]() 方法将会创造一个异步的迭代器,每次访问序列中的下一个值时,我们都会隐式地等待迭代器方法返回 Promise。

Promise.prototype.finally()

引自 MDN:

finally() 方法返回一个 Promise。在 Promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。避免了同样的语句需要在 then() 和 catch() 中各写一次的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const myPromise = new Promise((resolve, reject) => {
resolve();
});

myPromise
.then(() => {
console.log('still working');
})
.catch(() => {
console.log('there was an error');
})
.finally(()=> {
console.log('Done!');
})

.finally() 同样会返回一个 promise,所以我们可以继续链式调用 thencatch 方法,但是它们是基于之前的 promise 进行调用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const myPromise = new Promise((resolve, reject) => {
resolve();
});

myPromise
.then(() => {
console.log('still working');
return 'still working';
})
.finally(()=> {
console.log('Done!');
return 'Done!';
})
.then(res => {
console.log(res);
})
// still working
// Done!
// still working

从上边代码可以看到 finally 后边的 then 返回的值是由第一个 then 创建的,而不是 finally

正则表达式的新特性

在新版的 ECMA 中,共更新了 4 个关于正则的特性。

  • 正则表达式的 s (doAll) 标志
  • 正则表达式捕获组命名
  • 正则表达式反向断言 (Lookbehind Assertions)
  • unicode 字符转义 (Unicode property escapes)
s (doAll) 标志

引自MDN

dotAll 属性表明是否在正则表达式中一起使用 “s“ 修饰符(引入 /s 修饰符,使得.可以匹配任意单个字符,包括换行符和回车符)

1
2
/foo.bar/s.test('foo\nbar');
// true
捕获组命名

想要引用正则匹配到的某一部分字符串可以为捕获组编号。每个捕获组的数字都是唯一的,可以对应的数字引用它们,但是这使正则表达式难以阅读和维护。例如 /(\d{4})-(\d{2})-(\d{2})/ 匹配一个日期,但如果不看上下文的代码,就无法确定哪一组对应于月份,哪一组是一天。当然,如果哪一天需要交换日期和月份的顺序,那么对应的组引用也需要更新。现在,可以使用 (?<name>...) 来为捕获组命名,以表示任何标识符名称。重写上述例子:/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u 每一个命名都是唯一且遵循 ECMA 命名规范的。命名的组可以通过匹配结果的 result 属性来访问。对组的数字引用也会被建立,就像未命名的组一样。看下边几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';

let { groups: { one, two } } = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
console.log(`one: ${one}, two: ${two}`);
// one: foo, two: bar
反向断言

使用反向断言可以确保匹配之前或者之后没有其他匹配。反向断言的语法表示为 (?<=...)
例如:匹配一个美元数值且不包含美元符号可以这样写 /(?<=$)\d+(\.\d*)?/,这个表达式会匹配 $10.53 并返回 10.53,而并不会匹配 €10.53。而 (?<!...) 匹配的规则正相反,它会匹配不存在表达式中的匹配项,例如 /(?<!$)\d+(?:\.\d*)/ 不会匹配 $10.53,但是会匹配 €10.53

Unicode 字符转义

Unicode 字符转义是一种新的转义序列,u 作为字符转义的标志, \p{...}\P{...} 用来添加转义符。有了这个特性,匹配 Unicode 字符可以这样写:

1
2
3
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π');
// true

解除模板字符限制

当使用 Tagged 模板字符串时,转义字符的限制被移除了(阅读更多

ES2019 新特性

Array.prototype.flat() / Array.prototype.flatMap()

Array.prototype.flat() 会递归地展平一个数组并作为新值返回,它接受一个表示递归深度的值,未传值则默认深度为1。可以用 Infinity 去展平所有嵌套的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const letters = ['a', 'b', ['c', 'd', ['e', 'f']]];
// default depth of 1
letters.flat();
// ['a', 'b', 'c', 'd', ['e', 'f']]

// depth of 2
letters.flat(2);
// ['a', 'b', 'c', 'd', 'e', 'f']

// which is the same as executing flat with depth of 1 twice
letters.flat().flat();
// ['a', 'b', 'c', 'd', 'e', 'f']

// Flattens recursively until the array contains no nested arrays
letters.flat(Infinity)
// ['a', 'b', 'c', 'd', 'e', 'f']

Array.prototype.flatMap() 与深度值为1的 flat 几乎相同,但它并非仅仅展平数组。 flatMap 接收一个处理函数,使用 flatMap() 可以在展平的同时更改对应的值并返回一个新的数组。

1
2
3
let greeting = ["Greetings from", " ", "Vietnam"];
greeting.flatMap(x => x.split(" "))
// ["Greetings", "from", "", "", "Vietnam"]

这有点类似于 map() 方法,只不过多了一次展平操作。

Object.fromEntries()

Object.fromEntries() 将一组键值对转换成对象。

1
2
3
4
5
6
7
const keyValueArray = [
['key1', 'value1'],
['key2', 'value2']
]

const obj = Object.fromEntries(keyValueArray)
// {key1: "value1", key2: "value2"}

我们可以将任何可迭代的值作为 Object.entries() 方法的参数,不论它是一个 Array 还是 Map,或是其他实现了迭代协议的值。

注:可迭代协议( Iteration Protocols )是 ES2015 提出的,通常通过常量 Symbol.iterator 访问该对象的可迭代属性。

1
2
3
4
5
6
7
8
var someString = "hi";
typeof someString[Symbol.iterator]; // "function"
var iterator = someString[Symbol.iterator]();

iterator + ""; // "[object String Iterator]"
iterator.next() // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }

阅读更多关于迭代协议的内容

String.prototype.trimStart() / .trimEnd()

String.prototype.trimStart() 移除字符串前面的空白符,String.prototype.trimEnd() 移除字符串后面的空白符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let str = "    this string has a lot of whitespace   ";

str.length;
// 42

str = str.trimStart();
// "this string has a lot of whitespace "
str.length;
// 38

str = str.trimEnd();
// "this string has a lot of whitespace"
str.length;
// 35

也可以使用 .trimStart()trimEnd() 的别名: .trimLeft().trimRight()

可选的 catch 捕获参数

在 ES2019 之前,你必须为 catch 捕获传递一个表示异常的变量,现在这个变量不是必要的了。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Before
try {
...
} catch(error) {
...
}

// ES2019
try {
...
} catch {
...
}

这在你想忽略错误参数的时候很有用。

Function.ptototype.toString()

.toString() 方法返回一个代表函数源码的字符串。

1
2
3
4
5
6
7
8
function sum(a, b) {
return a + b;
}

console.log(sum.toString());
// function sum(a, b) {
// return a + b;
// }

注释也会被包含其中:

1
2
3
4
5
6
7
8
9
10
function sum(a, b) {
// perform a sum
return a + b;
}

console.log(sum.toString());
// function sum(a, b) {
// // perform a sum
// return a + b;
// }

Symbol.prototype.description

.description 返回 Symbol 对象可选描述的字符串。

1
2
3
4
5
6
const me = Symbol("Alberto");
me.description;
// "Alberto"

me.toString()
// "Symbol(Alberto)"

ES2020 特性

BigInt 类型

BigInt 是 JavaScript 第七个原始类型,它允许开发者操作非常大的整型。
数字类型可以处理 2 ** 53 - 19007199254740991 以内的数。可以通过常量 MAX_SAFE_INTEGER 来访问这个值。

1
Number.MAX_SAFE_INTEGER; // 9007199254740991

顾名思义,若操作的 number 值超过最大值时,运行结果就会变的奇怪。使用 BigInt 类型则没有明确的界限,因为它的界限取决于运行设备的内存。
定义 BigInt 类型,你即可以通过给 BigInt() 构造函数传递一个字符串值来创建,也可以像平常一样使用字面量语法来创建,但是要在尾部加上一个字符 n

1
2
3
4
const myBigInt = BigInt("999999999999999999999999999999");
const mySecondBigInt = 999999999999999999999999999999n;

typeof myBigInt; // "bigint"

注意,BigInt 类型与常规类型的数字并不是完全兼容的,这意味这你确定最好仅在操作比较大的数据时使用它。

1
2
3
4
5
6
7
const bigInt = 1n; // small number, but still of BigInt type
const num = 1;

num === bigInt; // false -> they aren't strictly equal
num == bigInt; // true
num >= bigInt; // true -> they can be compared
num + bigInt; // error -> they can't operate with one another

总之,使用 JS 做比较复杂的数学运算时 BigInt 是个不错的选择。它在替换专门用于处理大量数字的库方面表现良好。现在至少在整型方向有所进展,而目前我们对 BigDecimal 的提案了解的还很少。

动态导入(Dynamic imports)

动态导入,允许在浏览器端动态地加载代码模块。使用 import() 语法来导入你的代码块。

1
2
3
4
5
6
7
8
import("module.js").then((module) => {
// ...
});

// or
async () => {
const module = await import("module.js");
};

import() 返回一个 promise,resolve 中会返回代码模块加载后的内容。可以使用 ES6 的 .then() 方法或者 async/await 来处理加载结果。

空值合并操作符(??)

空值合并操作符(??)是一个新的 JS 运算符,当所访问的值是 null 或者 undefined 时,它会提供一个默认值。

1
2
3
4
5
const basicValue = "test";
const nullishValue = null;

const firstExample = basicValue ?? "example"; // "test"
const secondExample = nullishValue ?? "example"; // "example"

但是这跟 逻辑或(||)有什么区别呢?当第一个数是虚值 (在 Boolean 上下文中认定为 false 的值),如 false, 0, 或者"",以及空值 nullundefined,那么 逻辑或 将会使用第二个操作数。而空值合并操作符仅仅是在第一个值为空值而不是虚值的时候才会使用第二个操作数。如果你的代码可以接受除了 nullundefined 以外的任何值,那么空值合并操作符就是最佳选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const falseValue = false;
const zeroValue = 0;
const emptyValue = "";
const nullishValue = null;

const firstExampleOR = falseValue || "example"; // "example"
const secondExampleOR = zeroValue || "example"; // "example"
const thirdExampleOR = emptyValue || "example"; // "example"
const forthExampleOR = nullish || "example"; // "example"

const firstExample = falseValue ?? "example"; // false
const secondExample = zeroValue ?? "example"; // 0
const thirdExample = emptyValue ?? "example"; // ""
const forthExample = nullish ?? "example"; // "example"

可选链(?.)

与空值合并操作符类似,只不过可选链是处理 Object 中 nullundefined 的。鉴于直接从空值中国获取属性值会报错,现在可选链会直接将空值返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {
prop: {
subProp: {
value: 1,
},
},
};

obj.prop.subProp.value; // 1
obj.prop.secondSubProp.value; // error

obj?.prop?.subProp?.value; // 1
obj?.prop?.secondSubProp?.value; // undefined

当然,这只是一个语法糖,但也是一个很受欢迎的补充。记住不要在代码里到处使用这些操作符,他们虽然用起来方便,但从性能角度来说,它比普通的 . 开销要大。而且,若是代码是经过 Babel 和 TypeScript 转义的,则更要谨慎使用。

GlobalThis

由于 JavaScript 的代码可以运行在多个不同的环境,例如 浏览器、Node.js、Web Worker 等,要实现这种交叉兼容性绝非易事,globalThis 的出现方便了这些操作。
globalThis 是一个新的全局属性,通常它引用的是当前环境下的全局对象。就像是 self 对于 Web Workers,window 对于浏览器,global 对于 Node.js,以及其他实现了ES2020标准的运行环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Hacky globalThis polyfill you had to use pre-ES2020
const getGlobal = () => {
if (typeof self !== "undefined") {
return self;
}
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
throw new Error("Couldn't detect global");
};

getGlobal() === globalThis; // true (for browser, Web Worker and Node.js)
globalThis === window; // true (if you're in browser)

Promise.allSettled()

这个新增的方法看起来有点像 Promise.all()
Promise.all() 的参数中的 promise 若有一个失败,则此实例回调失败。而 Promise.allSettled()不论成功或者失败,都会返回处理结束后的对象数组。

String.matchAll()

如果你之前使用正则,那么相比于在 while 循环中使用 RegExp.exec() 并开启标志 g 来匹配,String.matchAll() 会是更好的选择。它会返回一个包含了所有匹配结果的数组,包括捕获组的匹配结果。

1
2
3
4
5
6
const regexp = /t(e)(st(\d?))/g;
const str = "test1test2";
const resultsArr = [...str.matchAll(regexp)]; // convert iterator to an array

resultsArr[0]; // ["test1", "e", "st1", "1"]
resultsArr[1]; // ["test2", "e", "st2", "2"]

原文链接