ES2021新特性

ECMAScript 2021 新特性

1 String.prototype.replaceAll()
在JavaScript中,replace()方法只替换字符串中的第一个实例。如果我们想替换字符串中的所有匹配项,唯一的办法就使用全局正则表达式。
新特性replaceAll()会返回一个新的字符串,字符串中所有的匹配项都会被替换掉。模式可以是字符串或正则表达式,替换的内容可以是字符串或是为每个匹配项执行的函数。

原本的replace()只能替换掉第一个匹配项:

1
2
3
const str = "审批 审批 审批 审批 "
const newStr = str.replace("审批", "人事")
console.log(newStr) // 人事 审批 审批 审批

如果想要完全匹配替换需要写全局正则表达式:

1
2
3
const str = "审批 审批 审批 审批"
const newStr = str.replace(/审批/g, "人事")
console.log(newStr) // 人事 人事 人事 人事

新的replaceAll()特性:

1
2
3
const str = "审批 审批 审批 审批"
const newStr = str.replaceAll("审批", "人事")
console.log(newStr) //人事 人事 人事 人事

2 Promise.any
ES2020已经通过了Promise的allSettled()方法。ES2021 Promise阵营将有一个新的成员,any()。
当Promise列表中的任意一个promise成功resolve则会短路并返回第一个resolve的结果状态, 如果所有的promise均reject,则抛出异常AggregateError,表示所有请求失败。
Promise.any()与Promise.race()十分容易混淆,务必注意区分。Promise.race() 一旦某个promise触发了resolve或者reject,就直接返回了该状态的结果。
即使Promise在resolve的之前被reject,Promise.any()仍将返回第一个resolve的结果:

1
2
3
4
5
6
7
Promise.any([
new Promise((resolve, reject) => setTimeout(reject('workflow'), 100)),
new Promise((resolve, reject) => setTimeout(resolve('staff'), 1000)),
new Promise((resolve, reject) => setTimeout(resolve('holiday'), 2000))
])
.then((value) => console.log(`结果:${value}`)) // 结果:staff
.catch((err) => console.log(err));

当所有promise都为reject会抛出异常AggregateError: All promises were rejected:

1
2
3
4
5
6
7
Promise.any([
Promise.reject('Error one'),
Promise.reject('Error two'),
Promise.reject('Error three')
])
.then((value) => console.log(`结果:${value}`))
.catch((err) => console.log(err)); // AggregateError: All promises were rejected

3 逻辑运算符和赋值表达式
在JavaScript中,有很多赋值操作符和逻辑操作符,在新的草案下,我们可以组合逻辑运算符和赋值运算符。
a &&= b 当a值存在时,将b变量赋值给a:

1
2
3
4
a &&= b
//等价于
// 1. a && (a = b)
// 2. if (a) a = b

a ||= b 当a值不存在时,将b变量赋值给a:

1
2
3
4
a ||= b
//等价于
// 1. a || (a = b)
// 2. if (!a) a = b

a ??= b 当a值为null或者undefined时,将b变量赋值给a:

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
let a
let b = 10
a ??= b
console.log(a) // 10

a = false
a ??= b
console.log(a) // false

const navigations = [
{
title: '工作台',
path: '/'
},
{
title: '审批',
path: '/workflow'
},
{
path: '/setting'
}
];

for (const navigation of navigations) {
page.title ??= '默认';
}

console.table(pages);

// (index) title path
// 0 "工作台" "/"
// 1 "审批" "/workflow"
// 2 "默认" "/setting"

4 Private Methods & Private Accessors

4.1 私有方法 Private Methods

私有方法只能在定义它的类内部访问,专用方法名称以#开头。由于setType()是私有方法,所以personObj.setType返回undefined,用undefined作函数会引发TypeError。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
// Private method
#setType() {
console.log('Private');
}
// Public method
show() {
this.#setType();
}
}

const personObj = new Person();
personObj.show(); // "Private"
personObj.setType(); // TypeError: personObj.setType is not a function

4.2 私有访问者 Private Accessors

可以通过在函数名称前添加#,使得访问器函数私有。在下面的代码中,name是公共的,可以像普通属性一样读取它, 而age则是私有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
// Public accessor
get name() {
return 'able';
}
set name(value) {}

// Private accessor
 get #age() {
return 18;
}
set #age(value) {}
}

const personObj = new Person();
console.log(personObj.name); // "able"
console.log(personObj.age); // undefined

5 WeakRefs

当我们通过const, let, var创建一个变量时,垃圾收集器GC将永远不会从内存中删除该变量,只要它的引用仍然存在且可访问。而WeakRef对象包含针对对象的弱引用,针对对象的弱引用是不会阻止垃圾收集器GC的回收的,则GC可以在任何时候删除它。

例如有如下场景:
跟踪某个对象调用某一特定方法的次数,超过1000条则做对应提示。

如果我们使用Map,虽然可以实现需求,但是会发生内存溢出,因为传递给doSomething()的每个对象都永久保存在map中,并且不会被GC回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let map = new Map();

function doSomething(obj) {
// ...
}

function useObject(obj) {
doSomething(obj);
let called = map.get(obj) || 0;
called++;
if (called > 1000) {
console.log('调用次数已超过1000次');
}
map.set(obj, called);
}

所以,可以通过WeakMap()或者WeakSet()来使用WeakRefs。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let wmap = new WeakMap();

function doSomething(obj) {
// ...
}

function useObject(obj) {
doSomething(obj);
let called = wmap.get(obj) || 0;
called++;
if (called > 1000) {
console.log('调用次数已超过1000次');
}
wmap.set(obj, called);
}

因为是弱引用,所以WeakMap、WeakSet的键值对是不可枚举的。WeakSet和WeakMap相似,但是每个对象在WeakSet中只可能出现一次,WeakSet中所有对象都是唯一的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let ws = new WeakSet();
let foo = {};
let bar = {};

ws.add(foo);
ws.add(bar);

ws.has(foo); // true
ws.has(bar); // true

ws.delete(foo); // 删除foo对象

ws.has(foo); //false foo已删除
ws.has(bar); // bar仍存在

WeakSet与Set相比有以下两个区别:
WeakSet只能是对象集合,而不能是任何类型的任意值
WeakSet弱引用,集合中对象引用为弱引用,如果没有其他对WeakSet对象的引用,则会被GC回收

最后,WeakRef实例有一个方法deref(),返回引用的原始对象,如果原始对象被回收,则返回undefined。下面其在斐波那契数列计算中缓存的妙用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const cache = new Map();

const setValue = (key, obj) => {
cache.set(key, new WeakRef(obj));
};

const getValue = (key) => {
const ref = cache.get(key);
if (ref) {
return ref.deref();
}
};

const fibonacciCached = (number) => {
const cached = getValue(number);
if (cached) return cached;
const sum = calculateFibonacci(number);
setValue(number, sum);
return sum;
};