ES15(ES2024)新特性

7 阅读5分钟

发布时间:2024年6月 ES15(ES2024)正式引入了原生分组、Promise.withResolvers()Set 集合运算、正则 /v 标志,以及 ArrayBuffer / SharedArrayBuffer 的增强能力等内容。

重要说明:装饰器(Decorators)虽然讨论度很高,但截至当前仍属于 TC39 Stage 3 提案不是 ES2024 正式标准的一部分。本文会在最后单独作为“补充阅读”说明,避免和正式标准混淆。


1. Object.groupBy() 和 Map.groupBy()

原生分组功能,无需再手写 reduce() 或依赖 Lodash。

Object.groupBy()

根据回调返回的键进行分组,返回普通对象:

const items = [
  { type: 'fruit', name: '苹果', price: 5 },
  { type: 'veggie', name: '胡萝卜', price: 3 },
  { type: 'fruit', name: '香蕉', price: 4 },
  { type: 'veggie', name: '白菜', price: 2 }
];

const grouped = Object.groupBy(items, item => item.type);
console.log(grouped);

Map.groupBy()

返回 Map,键可以是任意类型:

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const grouped = Map.groupBy(numbers, n => (n % 2 === 0 ? 'even' : 'odd'));

console.log(grouped.get('even'));  // [2, 4, 6, 8]
console.log(grouped.get('odd'));   // [1, 3, 5, 7, 9]

更多示例

const students = [
  { name: '张三', score: 95 },
  { name: '李四', score: 72 },
  { name: '王五', score: 85 },
  { name: '赵六', score: 58 }
];

const grades = Object.groupBy(students, s => {
  if (s.score >= 90) return 'A';
  if (s.score >= 80) return 'B';
  if (s.score >= 60) return 'C';
  return 'D';
});

const words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry'];
const byLetter = Object.groupBy(words, w => w[0]);

Object.groupBy() vs Map.groupBy()

  • Object.groupBy():分组键最终会转成属性键,更适合字符串分组
  • Map.groupBy():分组键可以是对象、数字、Symbol 等任意值
const byRef = Map.groupBy(
  [{ x: 1 }, { x: 1 }, { x: 2 }],
  item => item.x > 1
);

2. Promise.withResolvers()

更优雅地创建“外部可控制”的 Promise:

// 旧写法
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

// 新写法
const { promise, resolve, reject } = Promise.withResolvers();

事件驱动场景

function waitForEvent(element, eventName) {
  const { promise, resolve, reject } = Promise.withResolvers();

  element.addEventListener(eventName, resolve, { once: true });
  setTimeout(() => reject(new Error('超时')), 5000);

  return promise;
}

超时控制

function withTimeout(promise, ms) {
  const { promise: timeoutPromise, resolve: resolveTimeout } = Promise.withResolvers();

  const timer = setTimeout(() => resolveTimeout(null), ms);

  return Promise.race([
    promise.finally(() => clearTimeout(timer)),
    timeoutPromise
  ]);
}

测试中的 Mock

function createMockFetch() {
  const { promise, resolve } = Promise.withResolvers();
  return {
    promise,
    succeed(data) {
      resolve({ ok: true, json: () => Promise.resolve(data) });
    }
  };
}

3. String.prototype.isWellFormed()

检查字符串是否是格式良好的 Unicode 字符串:

'hello'.isWellFormed();               // true
'你好世界'.isWellFormed();             // true
'🎉'.isWellFormed();                  // true
'hello\uD800world'.isWellFormed();   // false
'\uD800'.isWellFormed();             // false

应用场景

function safeSend(data) {
  if (!data.isWellFormed()) {
    throw new Error('数据包含无效的 Unicode 字符');
  }
  return fetch('/api', { body: data });
}

4. String.prototype.toWellFormed()

把无效 Unicode 序列替换成替代字符 U+FFFD

'hello\uD800world'.toWellFormed();  // 'hello�world'
'你好\uD800世界'.toWellFormed();      // '你好�世界'

对比

let str = 'test\uD800data';

str.isWellFormed();                 // false
str.toWellFormed();                 // 'test�data'
str.toWellFormed().isWellFormed();  // true

5. 正则表达式 /v 标志

/v 可以看作 /u 的增强版,支持更强的 Unicode 集合表达与字符串集合。

集合运算

const lowercase = /[[\p{Letter}]--[A-Z]]/v;

lowercase.test('a');  // true
lowercase.test('A');  // false
lowercase.test('中'); // true

更好的 Emoji / 字符串集合支持

const emoji = /[\q{🇨🇳}|\q{📦}|\q{🎉}]/v;

emoji.test('🇨🇳');  // true
emoji.test('📦');   // true

注意

  • /v/u 不能同时使用
  • /v 适合更复杂的 Unicode 匹配场景

6. Set 新增集合操作方法

ES2024 为 Set 带来了更接近数学集合语义的一组原生方法。

union()(并集)

let a = new Set([1, 2, 3]);
let b = new Set([3, 4, 5]);

a.union(b);  // Set {1, 2, 3, 4, 5}

intersection()(交集)

let a = new Set([1, 2, 3, 4]);
let b = new Set([3, 4, 5, 6]);

a.intersection(b);  // Set {3, 4}

difference()(差集)

let a = new Set([1, 2, 3, 4]);
let b = new Set([3, 4, 5, 6]);

a.difference(b);  // Set {1, 2}

symmetricDifference()(对称差集)

let a = new Set([1, 2, 3]);
let b = new Set([3, 4, 5]);

a.symmetricDifference(b);  // Set {1, 2, 4, 5}

isSubsetOf() / isSupersetOf() / isDisjointFrom()

let a = new Set([1, 2]);
let b = new Set([1, 2, 3, 4]);
let c = new Set([8, 9]);

a.isSubsetOf(b);     // true
b.isSupersetOf(a);   // true
a.isDisjointFrom(c); // true

实际应用

let selectedTags = new Set(['js', 'react']);
let availableTags = new Set(['js', 'react', 'vue', 'angular']);

let unselected = availableTags.difference(selectedTags);
let shared = availableTags.intersection(new Set(['react', 'vue']));

7. ArrayBuffer / SharedArrayBuffer 能力增强

ES2024 引入了可调整大小的 ArrayBuffer可增长的 SharedArrayBuffer,以及更方便的转移能力。

可调整大小的 ArrayBuffer

let buffer = new ArrayBuffer(8, { maxByteLength: 16 });
let view = new Int32Array(buffer);

view[0] = 1;
view[1] = 2;

console.log(buffer.byteLength);  // 8
buffer.resize(12);
console.log(buffer.byteLength);  // 12
console.log(buffer.maxByteLength);  // 16

transfer() 和 transferToFixedLength()

let buffer = new ArrayBuffer(1024, { maxByteLength: 2048 });

let moved = buffer.transfer();
// 原 buffer 会变成 detached

let fixed = moved.transferToFixedLength();
// fixed 成为固定长度 ArrayBuffer

Growable SharedArrayBuffer

let sab = new SharedArrayBuffer(8, { maxByteLength: 16 });
console.log(sab.byteLength);  // 8

sab.grow(12);
console.log(sab.byteLength);  // 12

使用价值

  • 流式处理二进制数据
  • 提前申请最大容量,按需扩容
  • 减少频繁复制缓冲区的成本

8. Atomics.waitAsync()

提供非阻塞版本的共享内存等待机制:

const sab = new SharedArrayBuffer(4);
const int32 = new Int32Array(sab);

let result = Atomics.waitAsync(int32, 0, 0);
// result.async === true
// result.value 是一个 Promise

result.value.then(() => {
  console.log('共享内存的值已改变');
});

对比 Atomics.wait()

Atomics.wait(int32, 0, 0);      // 阻塞,通常只能在 Worker 中使用
Atomics.waitAsync(int32, 0, 0); // 非阻塞

9. 补充:Decorators 仍处于 Stage 3 提案

当前状态

  • 不是 ES2024 正式标准的一部分
  • 在 TC39 提案流程中仍处于 Stage 3
  • Babel / TypeScript / 部分框架生态中已经广泛使用,但标准仍未最终定稿

为什么容易被误认为是 ES2024?

  • 社区已经用了很多年
  • 新版装饰器语法与旧版提案差异较大
  • 很多文章会把“流行的新语法”误写成“已入标准”

提案示例(仅作了解)

function log(originalMethod, context) {
  return function(...args) {
    console.log(`调用 ${String(context.name)},参数:`, args);
    return originalMethod.call(this, ...args);
  };
}

class Calculator {
  @log
  add(a, b) {
    return a + b;
  }
}

如果你的项目使用装饰器,请优先以当前构建工具(如 Babel、TypeScript)的具体实现和配置为准,而不是把它当作“所有 JS 运行时原生支持的 ES2024 语法”。


总结

正式纳入 ES2024 的重点内容

特性说明重要性
Object.groupBy() / Map.groupBy()原生分组⭐⭐⭐⭐⭐
Promise.withResolvers()更方便创建外控 Promise⭐⭐⭐⭐⭐
String.isWellFormed()检查 Unicode 格式⭐⭐⭐
String.toWellFormed()修复 Unicode 格式⭐⭐⭐
正则 /v 标志更强的 Unicode / 集合表达⭐⭐⭐⭐
Set 集合操作方法并集、交集、差集等⭐⭐⭐⭐⭐
Resizable / Growable Buffer缓冲区扩容与转移⭐⭐⭐
Atomics.waitAsync()共享内存异步等待⭐⭐

常见但不属于 ES2024 正式标准的相关内容

项目状态
DecoratorsStage 3 提案

ES2024 的一个明显趋势是:把过去常见的工程需求原生化,例如分组、集合运算、外控 Promise,以及更灵活的二进制缓冲区管理。这样既减少了工具库依赖,也让底层语义更统一。