reduce分组的核心逻辑是手动构造分组结构:以空对象为累加器,按字段值动态创建键并推入对应数组,需安全取值、字符串化键、兜底处理空值。

reduce 分组的核心逻辑是什么
reduce 本身不带分组语义,它只是按顺序累积计算。实现分组的关键在于:把累加器(第一个参数)初始化为一个空对象 {},每次遍历根据字段值动态创建或复用键,再把当前项推入对应数组。不是“分组函数”,而是“手动构造分组结构”的过程。
- 累加器必须是对象,不能是数组,否则无法用字段值作键
- 字段值需转为字符串(如
item.category + ''),避免undefined、null或对象作为键导致意外覆盖 - 若字段可能不存在,建议先用可选链 + 空值合并:
item?.type ?? 'unknown'
处理嵌套字段或动态字段名
当要按 user.profile.role 或运行时传入的 key = 'status' 分组时,不能直接写 item[key]——得防报错,也得支持路径访问。
- 对静态嵌套字段,可用 Lodash 的
get(item, 'user.profile.role', 'default'),但纯 JS 推荐封装简易安全取值函数 - 对动态字段名,确保
key是字符串且非空:if (!key || typeof key !== 'string') throw new Error('key must be a non-empty string') - 示例(安全取值 + 分组):
const groupBy = (arr, key) => { return arr.reduce((acc, item) => { const value = key.split('.').reduce((o, k) => o?.[k], item) ?? 'undefined'; const groupKey = String(value); if (!acc[groupKey]) acc[groupKey] = []; acc[groupKey].push(item); return acc; }, {}); };
常见翻车点:引用污染和浅拷贝陷阱
直接 push 原数组项不会出错,但若后续修改了原对象,分组结果里的对象也会变——因为是同一引用。
- 不想联动修改?分组时做浅拷贝:
acc[groupKey].push({ ...item })(仅一层)或用structuredClone(item)(现代环境) - 如果字段值是对象(比如按
item.tags数组分组),那String(item.tags)会变成"tag1,tag2",不可靠;应改用 JSON 序列化或自定义哈希函数 - 空数组输入时,
reduce正常返回初始值({}),无需额外判断,但记得测试undefined元素场景
替代方案对比:Map vs 普通对象
用 Map 当累加器能支持任意类型键(包括对象、函数),但多数分组需求字段是字符串或数字,普通对象更轻量、可枚举、调试友好。
-
Map写法略啰嗦:acc.set(groupKey, [...(acc.get(groupKey) || []), item]) - 对象键自动转字符串,
{ true: 1 }[true]和{ 'true': 1 }['true']都能取到,但{ [{}]: 1 }[{}]实际是{ '[object Object]': 1 } - 如果业务中字段值天然唯一且类型多样(比如按 React 组件实例分组),才考虑
Map;日常按 ID、状态、类别分组,对象足矣
实际写的时候,最常漏掉的是字段为空时的兜底,以及没意识到 reduce 的初始值必须匹配后续操作——比如初始设成 [] 却想当对象用,一访问 acc[key] 就是 undefined,再 push 就报错。










