JavaScript里的reduce是数组原型上的一个高阶方法,它的核心职责是把一个数组里的多项数据,按照开发者的规则逐步合并,最终得出一个单一的结果。这个结果可以是数字、字符串、对象,甚至是另一个数组。和其他遍历方法不同,reduce最鲜明的特点就是存在一个跨轮次保留的“累加器”,每一轮计算完都会把新值交还给下一轮使用。

从底层逻辑看,reduce并不会凭空变出结果,而是依靠你传入的回调函数反复执行。回调函数每次被调用时,都会拿到上一次算出来的累积值,再加上当前正在遍历的数组元素,经过你的加工后返回新的累积值。这样一轮一轮走下去,直到数组末尾,最后那次返回的值就是reduce整体返回的东西。
如果调用reduce时没有给初始值,它会直接把数组第一项当作第一次的累积值,然后从第二项开始跑回调;如果给了初始值,就从第一项开始,用初始值去和第一项做计算。这个细节在空数组上尤其关键:没初始值又遇到空数组,reduce会直接报错,而带了初始值就能安全返回该初始值。
reduce的基础语法与参数含义
reduce方法接收两个参数,第一个是必填的回调函数,第二个是可选的初始值。回调函数本身又有四个形参,分别是累积器、当前元素、当前索引和原数组。日常开发中,前两个用得最多,后两个按需取用。
下面是一段最基础的求和示例,展示了带初始值和不带初始值的写法差异:
const nums = [1, 2, 3, 4];
// 带初始值0,从第一项开始累加
const sum1 = nums.reduce(function(acc, cur) {
return acc + cur;
}, 0);
console.log(sum1); // 10
// 不带初始值,首次acc为1,cur为2
const sum2 = nums.reduce(function(acc, cur) {
return acc + cur;
});
console.log(sum2); // 10
可以看到,两种写法在这个例子里结果一样,但语义不同。带初始值能明确表达“从零开始加”的意图,也避免了空数组报错。在实际项目里,建议始终显式传入初始值,代码更稳健,读者也更省心。
当初始值是对象或数组时,reduce的威力才真正显现。比如把一串键值对聚合成一个映射表,或者用reduce做分组,都比for循环清晰很多。此时要注意,回调里必须返回同一个引用或新对象,否则下一轮就拿不到正确累积状态。
累积过程如何一步步发生
为了看清“一步步累积”的节奏,我们可以在回调里打印中间状态。每一次返回的值,都会原封不动地成为下一轮的第一个参数。这就好比流水线上的传递带,上一道工序的输出是下一道工序的输入。
以下代码模拟了从左到右的归并轨迹:
const list = [5, 10, 15];
const result = list.reduce(function(acc, cur, idx) {
console.log('第' + idx + '轮 累积器:' + acc + ' 当前:' + cur);
const next = acc + cur;
console.log('第' + idx + '轮 算完得到:' + next);
return next;
}, 0);
console.log('最终:' + result);
// 第0轮 累积器:0 当前:5
// 第0轮 算完得到:5
// 第1轮 累积器:5 当前:10
// 第1轮 算完得到:15
// 第2轮 累积器:15 当前:15
// 第2轮 算完得到:30
// 最终:30
从这个输出能直观看到,累积器如何从左往右吞掉每一个元素。索引从0开始,对应“当前元素在数组里的位置”。如果你传了初始值,第一轮索引就是0、当前元素就是list[0];没传初始值,第一轮索引是1、当前元素是list[1],因为list[0]已经被默认塞进累积器了。
这种逐步归并的模式,让reduce不仅能做算术加总,也能做字符串拼接、对象合并。只要你能用代码描述“旧状态加新元素等于新状态”,就可以套进reduce里。
用reduce做复杂一点的累积
除了数字求和,reduce常用来把数组转成对象结构。比如有一组用户记录,想按城市分组,用reduce会比先建空对象再循环push更紧凑。
示例代码如下:
const users = [
{ name: '小明', city: '北京' },
{ name: '小红', city: '上海' },
{ name: '小刚', city: '北京' }
];
const byCity = users.reduce(function(group, user) {
if (!group[user.city]) {
group[user.city] = [];
}
group[user.city].push(user.name);
return group;
}, {});
console.log(byCity);
// { 北京: ['小明', '小刚'], 上海: ['小红'] }
这里初始值是一个空对象,每一轮我们根据user.city往对应数组里塞名字,最后返回同一个group。因为对象引用不变,下一轮还能继续往里加,最终就得到了分组映射。
需要注意,如果在回调里写了异步操作,reduce并不会等你异步结束再进入下一轮,它只是同步地调用回调。若真要异步串行累积,得配合async/await与for循环,或改用Promise的reduce封装,不能指望原生reduce自动处理异步时序。
常见误区与写法建议
不少人在初学时会把map和reduce搞混。map是“每项变一项,数量不变”,reduce是“多项变少项,甚至变一项”。如果你只是想转换数组长度不变的内容,用map更贴切;若要汇总、压缩、归并,才选reduce。
另一个坑是忘记返回累积值。一旦回调里漏了return,下一轮的acc就会是undefined,整个计算链立刻断裂。写reduce时,务必确认每一轮都return了最新的累积状态。
// 错误示范:忘了return
const bad = [1, 2, 3].reduce(function(acc, cur) {
acc + cur; // 没有return
}, 0);
console.log(bad); // undefined
// 正确示范
const good = [1, 2, 3].reduce(function(acc, cur) {
return acc + cur;
}, 0);
console.log(good); // 6
总结来说,reduce的本质就是一个带记忆的遍历器。它把“上一次的结果”交到你手里,你决定怎么和“当前元素”结合,再交回去。摸清这个传递节奏,就能用很低的代码量完成各种归并任务。
JavaScriptreduce数组累积修改时间:2026-08-07 19:09:42