你以为 reduce 是数组分组的唯一出路?今天 Object.groupBy 把这件事彻底原生化了
写过 JS 的人都踩过这个坑——做数据分组第一个想到的就是写一个 reduce,先建空对象,判断 key 有没有,有就 push,没有就新建,结果一个简单分组写了十几行,还每次都要重新写。ES2024 终于把这件事原生化了,一行替代十几行。
reduce 分组:每个项目都在重复的模板
经典写法是这样的:
function groupByStatus(orders) {
const acc = { pending: [], paid: [], shipped: [], refunded: [] };
for (const o of orders) {
acc[o.status].push(o);
}
return acc;
}
有几个明显的问题:第一步必须手填所有的 key,少写一个就编译通过但线上少一个分类;中间那个 acc[o.status].push(o) 是运行时才能发现错误的隐式 any;而且每次换一种分组维度就要新写一个函数。
这个问题存在了多少年?至少十年。
一行解决:Object.groupBy 和 Map.groupBy
ES2024 引入的两个静态方法把这件事变成了一个表达:
const byStatus = Object.groupBy(orders, (o) => o.status);
就这么一行。第一个参数是数组,第二个是分组 key 函数,返回一个对象,key 是函数的返回值,value 是满足条件的元素数组。
Map.groupBy 一样的用法,返回一个 Map:
const byStatus = Map.groupBy(orders, (o) => o.status);
byStatus.get("paid"); // 直接拿某组的值
什么时候用 Object.groupBy,什么时候用 Map.groupBy
两个方法的区别不是性能,而是返回类型适合的场景:
用 Object.groupBy:key 是字符串或数字,结果需要 JSON 序列化,或者你只是想在渲染侧按字段读数据。大多数后台管理列表、表格分组都用这个。
用 Map.groupBy:key 是对象、Date、布尔值,或者你需要保持 key 的插入顺序、需要调用 Map 的 .get()/.has()/.keys() 方法。典型的比如按用户对象分组、按日期实例分组。
// Object.groupBy:key 是字符串
const byCategory = Object.groupBy(products, (p) => p.category);
// Map.groupBy:key 是对象(按用户实例分组)
const byUser = Map.groupBy(orders, (o) => o.user); // o.user 是一个对象引用
特别注意:Object.groupBy 的 key 永远被转成字符串,如果你的分组 key 是数字 1 和字符串 1,它们会合并成一组。这种时候必须用 Map.groupBy。
实战例子:从分页 API 到分组数据
最典型的场景:调一个分页接口把所有数据拿回来,按某个字段分组展示给用户。
// 分页拿数据
async function fetchAllOrders() {
const res = await fetch("/api/orders");
const orders = await res.json();
// 按状态分组,一行
const byStatus = Object.groupBy(orders, (o) => o.status);
// 渲染侧直接读,key 不存在就是 undefined
return {
pending: byStatus.pending ?? [],
paid: byStatus.paid ?? [],
shipped: byStatus.shipped ?? [],
};
}
之前这一套要写一个 fetch 加一个 reduce,现在两行搞定。
浏览器支持
Chrome 117+、Edge 117+、Firefox 119+、Safari 17.4+、Node.js 21+。这是 Baseline 2024,全球覆盖率约 94%,基本可以直接用,不用 polyfill。
下一步
回去翻你项目里的 groupBy 相关的 reduce 函数,数一数有多少行。把那些换成 Object.groupBy,代码直接少 80%。尤其是那种只分两组的 if-else 分组逻辑,根本不需要函数定义,直接在用的时候一行搞定。
评论区
登录后可评论。