Array.prototype.reduce
reduce 对数组元素依次累积,回调接收 (accumulator, currentValue, index, array) 四个参数,返回最终累积值。
#type / concept
#status / evergreen
#resource / javascript
#resource / ecmascript
[!info] related notes
- 相关概念: array-prototype-map, higher-order-function-callback-mismatch, ecmascript-collection-reference-types
- 对比方法:
map,filter,forEach,reduceRight
Array.prototype.reduce
一句话定义
reduce 对数组每个元素依次执行回调,将结果累积到一个值上并返回。
签名
array.reduce(callbackFn)
array.reduce(callbackFn, initialValue)
callbackFn 参数
回调函数接收 4 个参数,比 map 多一个 accumulator:
array.reduce(function callback(accumulator, currentValue, index, array) {
// accumulator : 上一次回调的返回值(或初始值)
// currentValue : 当前正在处理的元素
// index : 当前索引
// array : 调用 reduce 的数组本身
}, initialValue);
| 参数 | 含义 |
|---|---|
accumulator | 累积值,上一次回调的返回值 |
currentValue | 当前元素 |
index | 当前索引 |
array | 原数组本身 |
执行逻辑
有 initialValue
从索引 0 开始,accumulator 初始为 initialValue:
第 0 步: acc = initialValue, current = arr[0]
第 1 步: acc = 上一步返回值, current = arr[1]
第 2 步: acc = 上一步返回值, current = arr[2]
...
最后一步的 acc 就是最终返回值
无 initialValue
取 arr[0] 作为初始 accumulator,从索引 1 开始遍历:
第 0 步: acc = arr[0], current = arr[1]
第 1 步: acc = 上一步返回值, current = arr[2]
...
空数组 + 无 initialValue → TypeError
[].reduce((acc, cur) => acc + cur) // TypeError
[].reduce((acc, cur) => acc + cur, 0) // 0(安全)
基本用法
求和
[1, 2, 3, 4].reduce((acc, cur) => acc + cur, 0) // 10
展开过程:
acc=0, cur=1 → 1
acc=1, cur=2 → 3
acc=3, cur=3 → 6
acc=6, cur=4 → 10
求最大值
[3, 7, 2, 9].reduce((max, cur) => cur > max ? cur : max) // 9
无 initialValue,arr[0]=3 作为初始 accumulator。
数组扁平化
[[1, 2], [3, 4], [5]].reduce((acc, cur) => acc.concat(cur), [])
// [1, 2, 3, 4, 5]
计数
const arr = ['a', 'b', 'a', 'c', 'b', 'a'];
arr.reduce((count, item) => {
count[item] = (count[item] || 0) + 1;
return count;
}, {});
// { a: 3, b: 2, c: 1 }
数组转对象
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
users.reduce((map, user) => {
map[user.id] = user;
return map;
}, {});
// { 1: { id: 1, name: 'Alice' }, 2: { id: 2, name: 'Bob' } }
管道 / compose
const pipe = (...fns) => (input) => fns.reduce((acc, fn) => fn(acc), input);
const double = x => x * 2;
const addOne = x => x + 1;
const square = x => x * x;
const transform = pipe(double, addOne, square);
transform(3); // ((3*2)+1)^2 = 49
与 map / filter 组合
reduce 可以替代 map + filter,但可读性通常更差:
// 用 reduce 同时做过滤 + 映射(不推荐,仅作理解)
[1, 2, 3, 4, 5].reduce((acc, cur) => {
if (cur % 2 === 0) acc.push(cur * 10);
return acc;
}, []);
// [20, 40]
// 推荐写法
[1, 2, 3, 4, 5].filter(x => x % 2 === 0).map(x => x * 10);
reduceRight
从右到左遍历,参数和逻辑完全相同:
[1, 2, 3].reduceRight((acc, cur) => acc + '-' + cur)
// "3-2-1"
经典陷阱
1. 忘记 return
// ❌ 回调没有 return,acc 变成 undefined
[1, 2, 3].reduce((acc, cur) => { acc + cur }, 0)
// ✅ 用花括号必须显式 return
[1, 2, 3].reduce((acc, cur) => { return acc + cur }, 0)
// ✅ 或用简写
[1, 2, 3].reduce((acc, cur) => acc + cur, 0)
2. 空数组无 initialValue
[].reduce((acc, cur) => acc + cur) // TypeError: Reduce of empty array with no initial value
始终传 initialValue 是更安全的习惯。
3. 参数错位
详见 higher-order-function-callback-mismatch。
reduce 回调有 4 个参数,把现成函数传给 reduce 时更容易错位:
function add(a, b) { return a + b; }
[1, 2, 3].reduce(add)
// add(1, 2) → 3
// add(3, 3) → 6
// 结果 6(这个例子恰好正确,但依赖于 acc 和 value 的顺序)
与相关方法对比
| 方法 | 返回值 | 用途 |
|---|---|---|
reduce | 累积值 | 求和、分组、扁平化、管道 |
reduceRight | 累积值 | 从右累积 |
map | 新数组 | 一对一映射 |
filter | 新数组(子集) | 过滤 |
forEach | undefined | 副作用遍历 |