发布时间: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 正式标准的相关内容
| 项目 | 状态 |
|---|---|
| Decorators | Stage 3 提案 |
ES2024 的一个明显趋势是:把过去常见的工程需求原生化,例如分组、集合运算、外控 Promise,以及更灵活的二进制缓冲区管理。这样既减少了工具库依赖,也让底层语义更统一。