原文链接:allthingssmitty.com/2025/10/06/…
作者:Matt Smith
前端开发者经常对数组进行转换操作:过滤、映射、归约。有时还会进行分组。过去实现分组需要自定义 reduce() 逻辑,这种操作往往感觉比实际价值更耗费冗余代码。
但这种情况正在改变。JavaScript 现已原生支持通过 Object.groupBy() 和 Map.groupBy() 进行数据分组。这些静态方法使分组操作更具表达力、更简洁且可读性大幅提升,无需依赖外部库或复杂的 reduce() 模式。
什么是 Object.groupBy() 和 Map.groupBy()?
这两个方法均在 ES2024 中引入,允许您根据回调函数生成的键对数组元素进行分组。
Object.groupBy(array, callback)
该方法返回一个普通 JavaScript 对象。键值为回调函数生成的字符串,值为匹配每个键的元素数组。
Map.groupBy(array, callback)
该方法返回一个 Map 对象,支持非字符串键值并保持插入顺序。
快速示例
Object.groupBy()
const items = ['apple', 'banana', 'orange', 'blueberry'];
const grouped = Object.groupBy(items, item => item[0]);
console.log(grouped);
// {
// a: ['apple'],
// b: ['banana', 'blueberry'],
// o: ['orange']
// }
回调函数返回每种水果的首字母,并按此进行分组。
Map.groupBy()
const items = [1.1, 2.3, 2.4, 3.5];
const grouped = Map.groupBy(items, Math.floor);
console.log(grouped);
// Map {
// 1 => [1.1],
// 2 => [2.3, 2.4],
// 3 => [3.5]
// }
由于 Map.groupBy() 支持使用非字符串键(如数字或对象),因此在某些场景下更具灵活性。
用 groupBy() 替代 reduce()
在引入 groupBy() 之前,分组操作需要手动使用 reduce():
const grouped = items.reduce((acc, item) => {
const key = item[0];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(item);
return acc;
}, {});
现在,使用 Object.groupBy():
const grouped = Object.groupBy(items, item => item[0]);
groupBy() 方法能减少代码中的冗余。你只需编写需要实现的功能,而非具体实现方式。最终生成的代码更具表达力,也更易于理解。
这种变通方案类似于在
findLast()出现之前使用.reverse().find()的做法。如果你仍在使用这种方法,以下是应该考虑切换的原因。
何时应使用 Object.groupBy() 与 Map.groupBy()?
使用 Object.groupBy() 的情况:
- 仅需字符串键
- 需要可序列化为 JSON 的结果
- 处理的是普通对象
使用 Map.groupBy() 的情况:
- 需要非字符串键(数字、对象)
- 关注键的插入顺序
- 需要使用
.keys()、.values()、.entries()方法
日常应用:按状态分组
假设你有一份任务列表,需要按状态进行分组:
const tasks = [
{ id: 1, title: 'Design', status: 'todo' },
{ id: 2, title: 'Develop', status: 'in-progress' },
{ id: 3, title: 'Test', status: 'todo' },
{ id: 4, title: 'Deploy', status: 'done' },
];
const grouped = Object.groupBy(tasks, task => task.status);
console.log(grouped);
// {
// todo: [{...}, {...}],
// in-progress: [{...}],
// done: [{...}]
// }
简单易读。
更多实用场景
按计算范围分组
const products = [
{ name: 'Basic', price: 10 },
{ name: 'Pro', price: 50 },
{ name: 'Enterprise', price: 200 },
];
const grouped = Object.groupBy(products, product => {
if (product.price < 20) return 'budget';
if (product.price < 100) return 'mid-range';
return 'premium';
});
console.log(grouped);
// {
// budget: [...],
// 'mid-range': [...],
// premium: [...]
// }
按严重性分组日志
const logs = [
{ message: 'Login', severity: 'info' },
{ message: 'Crash', severity: 'error' },
{ message: 'Timeout', severity: 'warning' },
{ message: 'Load', severity: 'info' },
];
const grouped = Object.groupBy(logs, log => log.severity);
这种模式在仪表盘、开发工具和分析工具中非常常见。
需要注意的陷阱
Object.groupBy() 总是将键转为字符串
这意味着:
const result = Object.groupBy([1, '1'], x => x);
console.log(result); // { '1': [1, '1'] }
数字1和字符串'1'最终都会归入字符串键"1"之下。
若需区分非字符串键,请改用Map.groupBy()方法。
Map.groupBy()无法进行JSON序列化
若尝试:
JSON.stringify(Map.groupBy([1, 2, 3], x => x % 2));
// TypeError: Converting circular structure to JSON
Map适用于运行时逻辑,但不适合用于API响应或本地存储。处理JSON时请使用Object.groupBy()。
浏览器支持
groupBy() 支持所有现代浏览器(Chrome 117+、Firefox 119+、Safari 17.4+、Edge 117+)及Node.js 21+。
以下是适用于旧版环境的Object.groupBy()基础聚合填充:
function groupByPolyfill(array, callback) {
return array.reduce((acc, item) => {
const key = callback(item);
acc[key] ??= [];
acc[key].push(item);
return acc;
}, {});
}
此聚合填充方案虽无法覆盖所有边界情况,但能很好地处理大多数典型用例。
下一步行动取决于你
Object.groupBy() 和 Map.groupBy() 是 JavaScript 标准库中优雅的新增功能。它们取代了冗长的 reduce() 模式,使数据转换更具表达力且更符合声明式编程风格。
以下是两者的快速使用场景对比:
| 用例 | Object.groupBy() | Map.groupBy() |
|---|---|---|
| 仅字符串键 | ✅ | ✅ |
| 非字符串键(数字、对象) | ❌ | ✅ |
| JSON序列化 | ✅ | ❌ |
| 保留插入顺序 | ❌ | ✅ |
使用.entries()迭代 | ❌ | ✅ |
| 简洁性与可读性 | ✅ | ✅ |
若你仍在使用reduce(),不妨试试这些方法。或许从此不再回头。