JavaScript Array.prototype.reduce() 的妙用:不只是求和!
在 JavaScript 中,reduce() 常被误解为"只是一个求数组总和的工具"。但实际上,reduce 是最强大、最灵活的数组高阶函数之一 ------它能模拟 map、filter、find,还能实现分组、扁平化、数据转换、状态累积等复杂逻辑。
本文将带你深入 reduce 的核心机制,并通过 10+ 个实用场景,展示它的真正威力。
一、reduce 基础回顾
js
arr.reduce(callback(accumulator, currentValue, index, array), initialValue)
accumulator(累加器):上一次回调的返回值,或初始值;currentValue:当前元素;initialValue(可选) :第一次调用callback时的accumulator值。
💡 核心思想:把数组"归约"成一个单一值(但这个"值"可以是对象、数组、字符串等任意类型!)
二、经典用法:不止于求和
1. 求数组总和 / 乘积
js
const sum = [1, 2, 3].reduce((acc, n) => acc + n, 0); // 6
const product = [1, 2, 3].reduce((acc, n) => acc * n, 1); // 6
2. 找最大/最小值
js
const max = [3, 1, 4].reduce((acc, n) => acc > n ? acc : n); // 4
// 或用 Math.max
const max2 = [3, 1, 4].reduce((acc, n) => Math.max(acc, n));
三、高级妙用:reduce 的真正实力
✅ 1. 模拟 map
js
const doubled = [1, 2, 3].reduce((acc, n) => {
acc.push(n * 2);
return acc;
}, []); // [2, 4, 6]
📌 虽然不如
map简洁,但展示了reduce的通用性。
✅ 2. 模拟 filter
js
const evens = [1, 2, 3, 4].reduce((acc, n) => {
if (n % 2 === 0) acc.push(n);
return acc;
}, []); // [2, 4]
✅ 3. 数组去重(基于值)
js
const unique = [1, 2, 2, 3, 1].reduce((acc, n) => {
if (!acc.includes(n)) acc.push(n);
return acc;
}, []); // [1, 2, 3]
// 更高效(用 Set)
const unique2 = [...new Set([1, 2, 2, 3, 1])];
✅ 4. 按属性分组(Group By)
js
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 25 }
];
const groupedByAge = users.reduce((acc, user) => {
const key = user.age;
if (!acc[key]) acc[key] = [];
acc[key].push(user);
return acc;
}, {});
// 结果:
// {
// 25: [{ name: 'Alice', ... }, { name: 'Charlie', ... }],
// 30: [{ name: 'Bob', ... }]
// }
💡 这是
reduce最常见的实战场景之一!
✅ 5. 统计频次(Count Occurrences)
js
const fruits = ['apple', 'banana', 'apple', 'orange'];
const count = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1;
return acc;
}, {});
// { apple: 2, banana: 1, orange: 1 }
✅ 6. 扁平化嵌套数组(Flatten)
js
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
// [1, 2, 3, 4, 5]
// 深度扁平化(递归)
function flattenDeep(arr) {
return arr.reduce((acc, val) =>
Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val),
[]);
}
📌 ES2019 已有
flat()和flatMap(),但理解reduce实现很有价值。
✅ 7. 链式操作合并(替代多次遍历)
假设要对数组:过滤偶数 → 平方 → 求和
传统写法(三次遍历):
js
const result = arr
.filter(x => x % 2 === 0)
.map(x => x * x)
.reduce((a, b) => a + b, 0);
用 reduce 一次遍历完成:
js
const result = arr.reduce((sum, x) => {
if (x % 2 === 0) sum += x * x;
return sum;
}, 0);
✅ 性能更优(尤其大数据量时)!
✅ 8. 将数组转为对象(key-value 映射)
js
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
const userMap = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {});
// { 1: { id: 1, name: 'Alice' }, 2: { id: 2, name: 'Bob' } }
// 后续可通过 userMap[1] 快速查找,O(1) 时间复杂度
✅ 9. 实现管道(Pipeline)或状态机
js
const operations = [
(x) => x + 1,
(x) => x * 2,
(x) => x.toString()
];
const result = operations.reduce((value, fn) => fn(value), 5);
// ((5 + 1) * 2).toString() → "12"
💡 函数式编程中常见模式。
✅ 10. 处理异步操作(谨慎使用)
虽然 reduce 不是为异步设计的,但可顺序执行 Promise:
js
const urls = ['url1', 'url2', 'url3'];
urls.reduce((promise, url) => {
return promise.then(results =>
fetch(url).then(res => res.json()).then(data => [...results, data])
);
}, Promise.resolve([]))
.then(allData => console.log(allData));
⚠️ 注意:这会串行执行 ,如需并行请用
Promise.all。
四、使用 reduce 的最佳实践
✅ 优点
- 灵活性极高:可生成任意类型的累积结果;
- 单次遍历 :避免多次
map/filter链式调用的性能开销; - 逻辑集中:复杂转换可在一处完成。
⚠️ 注意事项
- 务必提供
initialValue(除非你明确知道不需要);- 否则第一个元素会被当作初始值,可能引发 bug;
- 不要滥用 :简单场景优先用
map/filter,语义更清晰; - 避免副作用 :
reduce应是纯函数,不要修改外部变量; - 可读性权衡 :过于复杂的
reduce可拆分为多个步骤。
五、总结:reduce 的思维模型
reduce的本质是:遍历数组,逐步构建一个最终结果。
| 你想做什么? | reduce 能否实现? |
|---|---|
| 求和、求积 | ✅ 当然 |
| 转换为新数组 | ✅(模拟 map) |
| 筛选元素 | ✅(模拟 filter) |
| 分组、计数 | ✅ 最佳场景之一 |
| 扁平化 | ✅ |
| 构建映射对象 | ✅ 高效 |
| 状态累积(如表单校验) | ✅ |
掌握 reduce,你就拥有了 "用一个函数解决大多数数组问题" 的能力。但记住:强大不等于万能,在可读性和性能之间找到平衡,才是专业开发者的标志。
🌟 终极建议 :当你发现自己写了多个
map/filter链,或者需要在循环中维护复杂状态时------试试reduce!