3045 字
15 分钟
ES2024新特性完全指南:Object.groupBy、Promise.withResolvers等核心功能详解(2026最新)
ECMAScript 2024(ES15)于2024年6月正式获批,带来了一系列实用的新特性。2026年今天,这些特性已在主流浏览器和Node.js中广泛支持。本文深度解析ES2024的核心新功能,并通过实际示例帮助你快速上手。

阅读提示本文为ES2024专题指南,与 《TypeScript 5.x实用技巧完全指南》 互为补充:前者覆盖ES新特性,后者聚焦TypeScript高级技巧。
一、ES2024概览
1.1 发布时间与背景
- 正式获批:2024年6月22日,第127届ECMA大会批准
- 规范版本:ECMAScript 2024(第15版,ES15)
- 提案流程:通过TC39委员会的4阶段提案流程
1.2 核心新特性一览
| 特性 | 提案阶段 | 用途 | 支持度 |
|---|---|---|---|
| Object.groupBy | Stage 4 | 数据分组 | 95%+ |
| Map.groupBy | Stage 4 | Map分组 | 95%+ |
| Promise.withResolvers | Stage 4 | Promise创建 | 90%+ |
| ArrayBuffer resizable | Stage 4 | 动态调整缓冲区 | 90%+ |
| ArrayBuffer transfer | Stage 4 | 转移缓冲区所有权 | 90%+ |
| RegExp v flag | Stage 4 | Unicode增强 | 85%+ |
| Atomics.waitAsync | Stage 4 | 异步等待 | 85%+ |
| Float16Array | Stage 4 | 半精度浮点数 | 80%+ |
| Set methods | Stage 4 | 集合运算 | 85%+ |
1.3 浏览器支持情况
| 浏览器 | 最低版本 | 支持特性 |
|---|---|---|
| Chrome | 122+ | 全部支持 |
| Firefox | 125+ | 全部支持 |
| Safari | 17.4+ | 大部分支持 |
| Edge | 122+ | 全部支持 |
| Node.js | 22+ | 全部支持 |
| Bun | 1.2+ | 全部支持 |
二、Object.groupBy 与 Map.groupBy
2.1 传统分组方式的问题
场景:将用户按角色分组
// 传统方式:reduceconst users = [ { name: 'Alice', role: 'admin' }, { name: 'Bob', role: 'user' }, { name: 'Charlie', role: 'admin' }, { name: 'David', role: 'guest' }];
const grouped = users.reduce((acc, user) => { const key = user.role; if (!acc[key]) { acc[key] = []; } acc[key].push(user); return acc;}, {});
console.log(grouped);// { admin: [Alice, Charlie], user: [Bob], guest: [David] }痛点:
- 代码冗长(5-6行)
- 需要手动初始化数组
- 容易出错(忘记初始化)
2.2 Object.groupBy
const grouped = Object.groupBy(users, user => user.role);
console.log(grouped);// { admin: [Alice, Charlie], user: [Bob], guest: [David] }``
**语法**:
```javascriptObject.groupBy(items, callbackFn)参数:
items:要分组的数组callbackFn:分组回调函数,返回分组键
特点:
- 返回普通对象(键为字符串)
- 无分组键时返回
undefined属性 - 简洁高效(一行代码)
2.3 Map.groupBy
// 场景:按复杂对象分组const products = [ { name: 'iPhone', category: { id: 1, name: '手机' } }, { name: 'MacBook', category: { id: 1, name: '手机' } }, { name: 'iPad', category: { id: 2, name: '平板' } }];
// Object.groupBy 无法处理对象键const groupedObj = Object.groupBy(products, p => p.category);// 问题:对象被转为字符串 "[object Object]"
// Map.groupBy 可以保留对象键const groupedMap = Map.groupBy(products, p => p.category);
console.log(groupedMap);// Map {// { id: 1, name: '手机' } => [iPhone, MacBook],// { id: 2, name: '平板' } => [iPad]// }语法:
Map.groupBy(items, callbackFn)特点:
- 返回 Map 对象
- 键可以是任意类型(对象、函数等)
- 保留键的原始类型
2.4 实际应用场景
场景1:数据报表分组
const orders = [ { id: 1, status: 'completed', amount: 100 }, { id: 2, status: 'pending', amount: 200 }, { id: 3, status: 'completed', amount: 150 }, { id: 4, status: 'cancelled', amount: 50 }];
// 按状态分组统计const byStatus = Object.groupBy(orders, o => o.status);
const stats = Object.fromEntries( Object.entries(byStatus).map(([status, orders]) => [ status, orders.reduce((sum, o) => sum + o.amount, 0) ]));
console.log(stats);// { completed: 250, pending: 200, cancelled: 50 }场景2:按日期分组
const logs = [ { time: '2026-07-01', level: 'error' }, { time: '2026-07-01', level: 'info' }, { time: '2026-07-02', level: 'error' }, { time: '2026-07-02', level: 'warn' }];
const byDate = Object.groupBy(logs, log => log.time);// { '2026-07-01': [...], '2026-07-02': [...] }场景3:多字段分组
const data = [ { region: '华东', product: 'A', sales: 100 }, { region: '华东', product: 'B', sales: 200 }, { region: '华北', product: 'A', sales: 150 }];
// 按区域+产品组合分组const combined = Object.groupBy(data, d => `${d.region}-${d.product}`);// { '华东-A': [...], '华东-B': [...], '华北-A': [...] }2.5 Object.groupBy vs Map.groupBy
| 维度 | Object.groupBy | Map.groupBy |
|---|---|---|
| 返回类型 | 普通对象 | Map对象 |
| 键类型 | 仅字符串 | 任意类型 |
| 键顺序 | 无序 | 有序(插入顺序) |
| 性能 | 略快 | 略慢 |
| 适用场景 | 简单分组 | 复杂键类型 |
三、Promise.withResolvers
3.1 传统Promise创建的问题
// 传统方式:需要外部变量let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej;});
// 使用setTimeout(() => { resolve('done');}, 1000);痛点:
- resolve/reject 需要外部声明
- 代码结构分散
- 容易忘记初始化
3.2 Promise.withResolvers
const { promise, resolve, reject } = Promise.withResolvers();
// 使用setTimeout(() => { resolve('done');}, 1000);
promise.then(console.log); // 'done'语法:
const { promise, resolve, reject } = Promise.withResolvers();特点:
- 一行代码创建Promise + resolve + reject
- 无需外部变量
- 代码更清晰
3.3 实际应用场景
场景1:事件监听Promise化
function waitForEvent(element, eventName) { const { promise, resolve } = Promise.withResolvers();
element.addEventListener(eventName, (e) => { resolve(e); }, { once: true });
return promise;}
// 使用await waitForEvent(button, 'click');console.log('Button clicked!');场景2:超时控制
function fetchWithTimeout(url, timeout = 5000) { const { promise: timeoutPromise, resolve: timeoutResolve } = Promise.withResolvers();
const timeoutId = setTimeout(() => { timeoutResolve(new Error('Timeout')); }, timeout);
const fetchPromise = fetch(url).then(response => { clearTimeout(timeoutId); return response; });
return Promise.race([fetchPromise, timeoutPromise]);}场景3:异步任务队列
class AsyncTaskQueue { constructor() { this.queue = []; this.pending = null; }
add(task) { const { promise, resolve } = Promise.withResolvers(); this.queue.push({ task, resolve }); this.process(); return promise; }
async process() { if (this.pending || this.queue.length === 0) return;
const { task, resolve } = this.queue.shift(); this.pending = task();
const result = await this.pending; resolve(result);
this.pending = null; this.process(); }}3.4 对比传统方式
// 传统:5行let resolve, reject;const promise = new Promise((res, rej) => { resolve = res; reject = rej;});
// ES2024:1行const { promise, resolve, reject } = Promise.withResolvers();四、ArrayBuffer Resizable & Transfer
4.1 传统ArrayBuffer的限制
// 传统ArrayBuffer:固定大小const buffer = new ArrayBuffer(1024);
// 无法调整大小buffer.resize(2048); // TypeError: ArrayBuffer.resize is not a function痛点:
- 创建后大小固定
- 无法动态调整
- 需要重新创建时复制数据
4.2 Resizable ArrayBuffer
// ES2024:可调整大小的ArrayBufferconst buffer = new ArrayBuffer(1024, { maxByteLength: 4096 });
console.log(buffer.resizable); // trueconsole.log.buffer.maxByteLength); // 4096
// 动态调整大小buffer.resize(2048);console.log(buffer.byteLength); // 2048
buffer.resize(512);console.log(buffer.byteLength); // 512
// 不能超过maxByteLengthbuffer.resize(8192); // RangeError语法:
new ArrayBuffer(length, { maxByteLength: maxLength })特点:
- 创建时指定最大大小
- resize() 动态调整
- 调整大小时保留数据
- 性能优于重新创建
4.3 Transfer ArrayBuffer
// ES2024:转移所有权const buffer1 = new ArrayBuffer(1024);const view1 = new Uint8Array(buffer1);view1[0] = 42;
// 转移到新bufferconst buffer2 = buffer1.transfer();
console.log(buffer1.byteLength); // 0(已分离)console.log(buffer2.byteLength); // 1024
const view2 = new Uint8Array(buffer2);console.log(view2[0]); // 42(数据保留)语法:
buffer.transfer()buffer.transfer(newLength)特点:
- 转移所有权,原buffer变为分离状态
- 避免数据复制
- 可指定新大小
4.4 实际应用场景
场景1:动态数据处理
// 数据量未知时动态调整const buffer = new ArrayBuffer(1024, { maxByteLength: 1024 * 1024 });let offset = 0;
function appendData(data) { const bytes = new TextEncoder().encode(data);
if (offset + bytes.length > buffer.byteLength) { buffer.resize(Math.min(buffer.byteLength * 2, buffer.maxByteLength)); }
new Uint8Array(buffer).set(bytes, offset); offset += bytes.length;}场景2:WebWorker数据传递
// 主线程const buffer = new ArrayBuffer(1024);const worker = new Worker('worker.js');
// 转移所有权给Workerworker.postMessage({ buffer: buffer.transfer() }, []);
// buffer已分离,主线程无法访问console.log(buffer.byteLength); // 0// Worker线程self.onmessage = (e) => { const buffer = e.data.buffer; const view = new Uint8Array(buffer); // 处理数据...
// 转移回主线程 self.postMessage({ buffer: buffer.transfer() }, []);};4.5 SharedArrayBuffer 支持
// SharedArrayBuffer也支持resizableconst sharedBuffer = new SharedArrayBuffer(1024, { maxByteLength: 4096 });
sharedBuffer.grow(2048);console.log(sharedBuffer.byteLength); // 2048注意:
- SharedArrayBuffer使用
grow()而非resize() - 只能增大,不能缩小
- 需要安全上下文(HTTPS + COOP/COEP)
五、RegExp v Flag(Unicode增强)
5.1 传统Unicode匹配的问题
// 传统方式:u flagconst regex = /[\p{L}]/u; // 匹配任何字母
// 问题:集合操作复杂// 匹配字母但排除汉字const regex2 = /[\p{L}--[^\p{Script=Han}]]/u; // 语法不支持5.2 v Flag特性
// ES2024:v flagconst regex = /[\p{L}]/v;
// 集合操作:差集const regex2 = /[\p{L}--\p{Script=Han}]/v;
// 集合操作:交集const regex3 = /[\p{L&&\p{Script=Latin}]/v;
// 集合操作:并集const regex4 = /[\p{L}\p{N}]/v; // 字母或数字语法:
/pattern/v特点:
- 增强Unicode支持
- 支持集合操作(交集、并集、差集)
- 支持多字符字符串
- 兼容u flag
5.3 集合操作详解
| 操作 | 语法 | 说明 |
|---|---|---|
| 并集 | [A\B] | A或B |
| 交集 | [A&&B] | A且B |
| 差集 | [A--B] | A但非B |
5.4 实际应用场景
场景1:匹配特定语言字符
// 匹配中文汉字const hanRegex = /\p{Script=Han}/v;
// 匹配英文字母(排除其他字母)const latinRegex = /[\p{L}--\p{Script=Han}--\p{Script=Hiragana}]/v;场景2:验证用户名
// 用户名:字母、数字、下划线,但排除特殊符号const usernameRegex = /^[\p{L}\p{N}_--[\p{S}\p{P}]]+$/v;场景3:多字符匹配
// 匹配表情符号const emojiRegex = /\p{Emoji}/v;
// 匹配完整表情(包括组合)const fullEmojiRegex = /\p{RGI_Emoji}/v;六、Atomics.waitAsync
6.1 传统Atomics.wait的限制
// Atomics.wait:阻塞主线程const sharedBuffer = new SharedArrayBuffer(4);const view = new Int32Array(sharedBuffer);
// 阻塞等待(不能在主线程使用)Atomics.wait(view, 0, 0, 1000);痛点:
- 阻塞线程
- 不能在主线程使用
- 只能在Worker中使用
6.2 Atomics.waitAsync
// ES2024:异步等待const sharedBuffer = new SharedArrayBuffer(4);const view = new Int32Array(sharedBuffer);
// 异步等待(不阻塞)const result = Atomics.waitAsync(view, 0, 0, 1000);
console.log(result.async); // trueconsole.log(result.value); // Promise
await result.value;// "ok" - 值已改变// "timed-out" - 超时语法:
Atomics.waitAsync(typedArray, index, value, timeout)特点:
- 非阻塞等待
- 返回Promise
- 可在主线程使用
- 与notify配合使用
6.3 实际应用场景
场景1:跨Worker同步
// Worker 1:等待信号const result = Atomics.waitAsync(view, 0, 0);await result.value;console.log('Received signal');
// Worker 2:发送信号Atomics.store(view, 0, 1);Atomics.notify(view, 0, 1);场景2:生产者-消费者
class AsyncQueue { constructor(capacity) { this.buffer = new SharedArrayBuffer(capacity * 4 + 8); this.data = new Int32Array(this.buffer); this.head = 0; this.tail = capacity; }
async consume() { while (this.head === this.tail) { await Atomics.waitAsync(this.data, 0, 0).value; } const item = this.data[this.head + 2]; this.head++; Atomics.notify(this.data, 0, 1); return item; }
produce(item) { while (this.head === this.tail) { // 队列满,等待 } this.data[this.tail + 2] = item; this.tail++; Atomics.notify(this.data, 0, 1); }}七、Set Methods(集合运算)
7.1 传统集合运算的问题
// 传统方式:手动实现const setA = new Set([1, 2, 3]);const setB = new Set([2, 3, 4]);
// 交集const intersection = new Set([...setA].filter(x => setB.has(x));
// 并集const union = new Set([...setA, ...setB]);
// 差集const difference = new Set([...setA].filter(x => !setB.has(x));7.2 ES2024 Set Methods
// 交集const intersection = setA.intersection(setB);// Set { 2, 3 }
// 并集const union = setA.union(setB);// Set { 1, 2, 3, 4 }
// 差集const difference = setA.difference(setB);// Set { 1 }
// 对称差集(异或)const symmetricDifference = setA.symmetricDifference(setB);// Set { 1, 4 }
// 子集判断setA.isSubsetOf(setB); // falsesetA.isSubsetOf(setA); // true
// 超集判断setA.isSupersetOf(setB); // false
// 无交集判断setA.isDisjointFrom(setB); // false7.3 方法一览
| 方法 | 语法 | 返回类型 |
|---|---|---|
| intersection | a.intersection(b) | Set |
| union | a.union(b) | Set |
| difference | a.difference(b) | Set |
| symmetricDifference | a.symmetricDifference(b) | Set |
| isSubsetOf | a.isSubsetOf(b) | boolean |
| isSupersetOf | a.isSupersetOf(b) | boolean |
| isDisjointFrom | a.isDisjointFrom(b) | boolean |
八、Float16Array
8.1 传统Float32Array的限制
// Float32Array:32位浮点数const f32 = new Float32Array([1.5, 2.5]);// 每个元素占用4字节8.2 Float16Array
// ES2024:16位半精度浮点数const f16 = new Float16Array([1.5, 2.5]);// 每个元素占用2字节
console.log(f16.BYTES_PER_ELEMENT); // 2特点:
- 节省50%内存
- 精度较低(适合不需要高精度的场景)
- 适合AI/ML场景(半精度足够)
8.3 应用场景
- 机器学习模型存储
- 大规模数值数据
- 内存受限环境
九、浏览器兼容性处理
9.1 Polyfill方案
// Object.groupBy polyfillif (!Object.groupBy) { Object.groupBy = function(items, callbackFn) { const result = {}; for (const item of items) { const key = callbackFn(item); if (!result[key]) { result[key] = []; } result[key].push(item); } return result; };}
// Map.groupBy polyfillif (!Map.groupBy) { Map.groupBy = function(items, callbackFn) { const result = new Map(); for (const item of items) { const key = callbackFn(item); if (!result.has(key)) { result.set(key, []); } result.get(key).push(item); } return result; };}
// Promise.withResolvers polyfillif (!Promise.withResolvers) { Promise.withResolvers = function() { let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; };}9.2 Babel配置
module.exports = { presets: [ ['@babel/preset-env', { targets: { chrome: '122', firefox: '125', safari: '17.4' }, useBuiltIns: 'usage', corejs: 3 }] ]};9.3 检测支持情况
// 检测特性支持const features = { 'Object.groupBy': typeof Object.groupBy === 'function', 'Map.groupBy': typeof Map.groupBy === 'function', 'Promise.withResolvers': typeof Promise.withResolvers === 'function', 'ArrayBuffer resizable': 'resizable' in ArrayBuffer.prototype, 'RegExp v flag': (() => { try { new RegExp('', 'v'); return true; } catch { return false; } })(), 'Atomics.waitAsync': typeof Atomics.waitAsync === 'function'};
console.table(features);十、总结
ES2024带来了多个实用的新特性:
| 特性 | 核心价值 | 推荐指数 |
|---|---|---|
| Object.groupBy | 简化数据分组 | ⭐⭐⭐⭐⭐ |
| Map.groupBy | 复杂键类型分组 | ⭐⭐⭐⭐ |
| Promise.withResolvers | 简化Promise创建 | ⭐⭐⭐⭐⭐ |
| ArrayBuffer resizable | 动态内存管理 | ⭐⭐⭐⭐ |
| RegExp v flag | Unicode增强匹配 | ⭐⭐⭐ |
| Atomics.waitAsync | 异步同步原语 | ⭐⭐⭐ |
| Set methods | 集合运算简化 | ⭐⭐⭐⭐ |
| Float16Array | 内存优化 | ⭐⭐⭐ |
行动建议:
- 立即使用
Object.groupBy和Promise.withResolvers(支持度最高) - 评估
ArrayBuffer resizable用于动态数据处理场景 - 使用
Set methods简化集合运算代码 - 为不支持的环境添加 polyfill
📚 相关文章推荐
- 《TypeScript 5.x实用技巧完全指南》
- 《Docker Compose v2 完全实战指南》
- 《AI编程工具横评:GitHub Copilot vs Cursor vs Claude Code》
- 《Vim完全指南》
- 《SSH完全指南》
Sources
- ES2024即将发布!5个可能大火的JS新方法
- JavaScript新特性ECMAScript 2024(ES2024)核心功能的完整指南
- ECMAScript 2024 (ES15) 新特性更新
- JavaScript ES15 新特性正式发布!全网最详细讲解!
- ECMA 2024(ES15) 新特性
- TypeScript 5.0 现已发布
:::info 免责声明 本文为技术信息分享,不构成任何建议。实际使用请根据项目需求和环境支持情况选择合适的特性。 :::
ES2024新特性完全指南:Object.groupBy、Promise.withResolvers等核心功能详解(2026最新)
https://971918.xyz/posts/docs/es2024-features-guide/