ES12(ES2021)新特性

3 阅读4分钟

发布时间: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);

??=(空值合并赋值)

当左侧值为 nullundefined 时,才赋值:

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;

注意事项

  • 垃圾回收时机不可预测
  • 不要依赖它做关键业务逻辑
  • WeakRefFinalizationRegistry 适合底层工具、缓存、资源管理等高级场景

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()WeakRefAggregateError)。