发布时间:2021年6月 ES12 新增了逻辑赋值、数字分隔符、
Promise.any()、WeakRef等特性。
1. 逻辑赋值运算符(Logical Assignment Operators)
把逻辑运算和赋值运算组合在一起:
||=(逻辑或赋值)
当左侧为假值时,才进行赋值:
let x = null;
x ||= 'default'; // x = 'default'
let y = 'hello';
y ||= 'default'; // y = 'hello'
let z = 0;
z ||= 100; // z = 100,因为 0 是假值
大致可以理解为:
x || (x = 'default');
严格来说,规范实现会保证左值只求值一次;上面的写法只是便于理解语义。
&&=(逻辑与赋值)
当左侧为真值时,才进行赋值:
let x = 1;
x &&= 2; // x = 2
let y = null;
y &&= 2; // y = null
等价语义可理解为:
x && (x = 2);
??=(空值合并赋值)
当左侧值为 null 或 undefined 时,才赋值:
let x = null;
x ??= 'default'; // x = 'default'
let y = 0;
y ??= 'default'; // y = 0(0 不是 null/undefined)
等价语义可理解为:
if (x === null || x === undefined) {
x = 'default';
}
三者对比
// ||= 在假值时赋值(null, undefined, 0, '', false, NaN)
// &&= 在真值时赋值
// ??= 只在 null/undefined 时赋值
let a = 0;
a ||= 100; // 100
a = 0;
a &&= 100; // 0
a = 0;
a ??= 100; // 0
实际应用
let config = {};
config.theme ??= 'light';
config.debug ??= false;
let user = {};
user.name ||= '匿名用户';
2. 数字分隔符(Numeric Separators)
使用下划线 _ 分隔长数字,提高可读性:
基本用法
let price = 1_000_000;
let binary = 0b1010_0001_1100;
let color = 0xFF_FF_FF;
let num = 1.234_567_890e5;
let big = 1_000_000_000_000n;
规则
1__000; // SyntaxError,不能连续多个下划线
100_; // SyntaxError,不能在数字末尾
1_.0; // SyntaxError,小数点前不能紧跟下划线
1._0; // SyntaxError,小数点后不能紧跟下划线
适用场景
- 金额、编号、时间戳等长整数
- 二进制 / 十六进制常量
- BigInt 大整数
3. Promise.any()
接收一组 Promise,返回第一个成功的结果:
与 Promise.race() 的区别
let p1 = new Promise((_, reject) => setTimeout(() => reject('失败1'), 100));
let p2 = new Promise(resolve => setTimeout(() => resolve('成功2'), 200));
let p3 = new Promise(resolve => setTimeout(() => resolve('成功3'), 300));
Promise.race([p1, p2, p3])
.then(res => console.log(res))
.catch(err => console.log(err));
// 输出:失败1
Promise.any([p1, p2, p3])
.then(res => console.log(res));
// 输出:成功2
全部失败时
let p1 = Promise.reject('失败1');
let p2 = Promise.reject('失败2');
Promise.any([p1, p2]).catch(err => {
console.log(err instanceof AggregateError); // true
console.log(err.errors); // ['失败1', '失败2']
console.log(err.errors.length); // 2
});
实际应用
async function fetchFastest(urls) {
try {
return await Promise.any(urls.map(url => fetch(url)));
} catch (err) {
console.log('所有源都失败了:', err.errors);
}
}
Promise.any([
db1.query('SELECT 1'),
db2.query('SELECT 1'),
db3.query('SELECT 1')
]).then(result => console.log('最快响应:', result));
4. WeakRef 和 FinalizationRegistry
WeakRef(弱引用)
创建对象的弱引用,不会阻止垃圾回收:
let obj = { data: 'important' };
let weakRef = new WeakRef(obj);
let cached = weakRef.deref();
if (cached) {
console.log(cached.data);
}
obj = null;
// 之后对象可能被回收,weakRef.deref() 可能得到 undefined
使用场景:缓存
class Cache {
#cache = new Map();
set(key, value) {
this.#cache.set(key, new WeakRef(value));
}
get(key) {
let ref = this.#cache.get(key);
if (!ref) return undefined;
let value = ref.deref();
if (value === undefined) {
this.#cache.delete(key);
}
return value;
}
}
FinalizationRegistry(终结注册)
对象被垃圾回收时,可触发注册回调:
let registry = new FinalizationRegistry((heldValue) => {
console.log(`对象 ${heldValue} 已被回收`);
});
let obj = { name: '张三' };
registry.register(obj, 'user-张三');
obj = null;
注意事项
- 垃圾回收时机不可预测
- 不要依赖它做关键业务逻辑
WeakRef和FinalizationRegistry适合底层工具、缓存、资源管理等高级场景
5. String.prototype.replaceAll()
替换所有匹配项:
'hello world hello'.replace(/hello/g, 'hi');
// 'hi world hi'
'hello world hello'.replaceAll('hello', 'hi');
// 'hi world hi'
与 replace() 的区别
'aabbcc'.replace('b', 'x'); // 'aaxbcc'
'aabbcc'.replaceAll('b', 'x'); // 'aaxxcc'
注意
'aabb'.replaceAll(/b/, 'x'); // TypeError
'aabb'.replaceAll(/b/g, 'x'); // 'aaxx'
- 传入正则时必须带
g标志 - 如果只是普通字符串替换,
replaceAll()会更直观
6. AggregateError
当多个错误需要一起抛出时,可以使用 AggregateError。在 ES2021 中,它最常见的场景就是 Promise.any() 全部失败时:
try {
await Promise.any([
Promise.reject(new Error('错误1')),
Promise.reject(new Error('错误2'))
]);
} catch (err) {
console.log(err instanceof AggregateError); // true
console.log(err.message); // 'All promises were rejected'
console.log(err.errors); // [Error('错误1'), Error('错误2')]
}
手动创建 AggregateError
throw new AggregateError([
new Error('缺少用户名'),
new Error('缺少密码')
], '表单校验失败');
总结
| 特性 | 说明 | 重要性 | ||
|---|---|---|---|---|
| ` | =, &&=, ??=` | 逻辑赋值运算符 | ⭐⭐⭐⭐ | |
数字分隔符 _ | 提高长数字可读性 | ⭐⭐⭐ | ||
Promise.any() | 返回第一个成功的 Promise | ⭐⭐⭐⭐ | ||
WeakRef | 对象的弱引用 | ⭐⭐ | ||
FinalizationRegistry | 对象回收通知 | ⭐⭐ | ||
String.replaceAll() | 替换全部匹配项 | ⭐⭐⭐⭐ | ||
AggregateError | 聚合多个错误 | ⭐⭐ |
ES2021 的亮点在于:一部分特性显著提升了日常代码可读性(逻辑赋值、数字分隔符、replaceAll()),另一部分则增强了异步与底层能力(Promise.any()、WeakRef、AggregateError)。