【翻译】如何在JavaScript中不使用reduce()对数组进行分组

4 阅读4分钟

原文链接: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(),不妨试试这些方法。或许从此不再回头。