
本文详解如何使用 JavaScript 的 reduce() 方法,将原始数组按某个字段(如 city)归类,生成每个分组包含 city 和对应 data 子数组的规范结构,避免常见误区(如误用对象作为累加器导致返回非数组结果)。
本文详解如何使用 javascript 的 `reduce()` 方法,将原始数组按某个字段(如 `city`)归类,生成每个分组包含 `city` 和对应 `data` 子数组的规范结构,避免常见误区(如误用对象作为累加器导致返回非数组结果)。
在实际开发中,常需将扁平数组按某一属性(如地区、类别、状态等)聚合成带层级结构的数据,用于渲染分组列表、侧边导航或数据看板。但许多开发者初试 reduce() 时容易陷入一个典型陷阱:以空对象 {} 为初始值,虽能完成分组逻辑,最终却得到形如 { London: [...], Manchester: [...] } 的对象——而业务场景往往要求严格返回数组格式,且每个元素必须是统一结构的对象(含 city 字段与 data 数组),便于后续遍历、排序或与 UI 框架(如 React 列表渲染)直接对接。
正确做法是:始终以空数组 [] 作为 reduce 的初始累加器,并在每轮迭代中主动查找或创建目标分组对象,再向其 data 属性中追加当前项。以下是完整、可运行的实现:
const locations = [
{ "city": "London", "district": "Brixton", "id": "Eue3uFjUHKi6wh73QZLX" },
{ "city": "Manchester", "district": "Bury", "id": "QZiiUBgzZaJt2HcahELT" },
{ "city": "London", "district": "Hackney", "id": "v2itdyO4IPXAMhIU8wce" }
];
const groupedLocations = locations.reduce((groups, item) => {
const { city } = item;
// 在已有的 groups 数组中查找是否存在同 city 的分组
const existingGroup = groups.find(group => group.city === city);
if (existingGroup) {
// 若存在,直接将当前 item 推入其 data 数组
existingGroup.data.push(item);
} else {
// 若不存在,新建分组对象并加入 groups 数组
groups.push({ city, data: [item] });
}
return groups;
}, []); // ⚠️ 关键:初始值必须是 []
console.log(groupedLocations);
// 输出符合预期的数组结构:
// [
// { city: "London", data: [ /* 两个 London 对象 */ ] },
// { city: "Manchester", data: [ /* 一个 Manchester 对象 */ ] }
// ]✅ 关键要点总结:
- reduce() 的返回类型完全由回调函数每次 return 的值决定,因此初始值 [] + 每次 return groups 保证最终结果必为数组;
- 使用 find() 查找已有分组比 filter()[0] 更高效,避免重复遍历;
- 新建分组时直接初始化 data: [item],比先 push({ city, data: [] }) 再 push(item) 更简洁;
- 此方案天然支持任意键名(只需替换 city 为 item.category 或 item.status 等),扩展性强。
⚠️ 注意事项:
- 若原始数组极大(万级),find() 的线性查找可能影响性能;此时可先用 Map 或对象做 O(1) 索引缓存,再转为数组输出(进阶优化);
- 确保 city 值为字符串或可安全比较的类型,若含 null/undefined,建议提前校验或使用 String(item.city) 统一处理;
- 如需保持分组顺序与首次出现顺序一致(如本例中 "London" 先于 "Manchester"),当前实现已天然满足;若需按字母序等规则排序,可在 reduce 后链式调用 .sort((a, b) => a.city.localeCompare(b.city))。
该方法简洁、可读、无外部依赖,是现代 JavaScript 中处理结构化分组任务的推荐实践。










