reduce的核心是累积器持续演进,能统一实现分组、树形结构、异步聚合、多维统计等数据转换。关键在明确定义每步状态更新逻辑,使复杂变换分解为可累积的小演进。

reduce 是 JavaScript 中最灵活、最具表达力的数组方法之一,它不只用于求和或计数,而是能以统一模式驱动各类数据结构转换——从扁平数组到嵌套对象、分组统计、树形结构、状态机聚合,甚至模拟 Promise.all 或管道处理。关键在于理解其“累积器(accumulator)持续演进”的本质。
把数组转成按字段分组的对象
这是 reduce 最经典也最实用的场景。相比 Object.groupBy(尚处 Stage 4),reduce 兼容性更好、逻辑更透明。
category)作为键,将当前项推入对应数组中。
- 若该键尚不存在,先初始化为空数组
- 避免直接修改原数组项,必要时用结构赋值做浅拷贝
- 支持多级分组:把
acc[key]替换为acc[key] ??= {}即可向下嵌套
示例:
const products = [{id:1,cat:'tech'}, {id:2,cat:'book'}];<br>
products.reduce((acc, item) => {<br>
const key = item.cat;<br>
acc[key] = acc[key] || [];<br>
acc[key].push(item);<br>
return acc;<br>
}, {});<br>
// → { tech: [{id:1,cat:'tech'}], book: [{id:2,cat:'book'}] }
从扁平数组构建树形结构(带 parent_id)
后端常返回带 id 和 parent_id 的扁平菜单/组织架构数据,前端需转为 children 嵌套格式。reduce 可一步完成映射 + 挂载。
id → node 映射表(含空 children 数组),再遍历一次,将每个节点挂到其父节点的 children 中;根节点(parent_id 为空或 undefined)直接存入结果数组。
- 必须两遍遍历:第一遍建索引,第二遍连关系;不能边建边挂(父节点可能尚未创建)
- 使用
Map替代普通对象可避免原型污染风险(如 id 为constructor) - 注意处理循环引用:提前检测
parent_id === current.id并跳过
实现类似 Promise.all 的并行聚合(reduce + Promise)
reduce 天然适合串行链式调用,但稍加改造也能支持并行聚合——关键是让累积器始终是 Promise,并用 Promise.all 包裹当前项与已有结果。
Promise.resolve([]);每轮将“已得结果数组”与“当前 Promise 结果”合并为新数组,并用 Promise.all 并发执行。
- 写法:
acc.then(arr => Promise.all([arr, asyncFn(item)]).then(([a, r]) => [...a, r])) - 更简洁写法(推荐):
acc.then(arr => asyncFn(item).then(r => [...arr, r]))—— 这是串行;要并行,就得提前展开所有异步操作,reduce 不是最佳选择;此时应先map出 Promise 数组,再Promise.all - 真正适合 reduce 的是“依赖前序结果”的异步流,例如:依次请求 token → 用 token 请求用户 → 用用户 ID 请求权限
统计+归类混合:生成带总数、占比、排序的报表对象
当需要同时计算频次、百分比、Top N 排名时,reduce 可在一个遍历中累积多个维度数据,避免多次循环。
说明: 累积器是一个含count(Map)、total、top3(数组)等字段的对象;每次更新计数后,动态维护 top3(插入排序或用堆,简单场景可用 sort 截取)。
- 用
Map存频次比普通对象更安全(支持任意类型 key,如 Symbol、对象引用) - 百分比计算放在最后,避免每轮重复除法;可在 reduce 后接一个
Object.fromEntries()转换 - 排序逻辑若较重(如复杂比较函数),建议提取为独立函数,保持 reduce 主体简洁
掌握 reduce 的核心不是记住模板,而是看清“每一步如何基于上一步结果生成新状态”。只要这个状态定义清晰、可累积、可组合,再复杂的转换也能被拆解成一次次小演进。











