JavaScript `Array.prototype.reduce()` 的妙用:不只是求和!

JavaScript Array.prototype.reduce() 的妙用:不只是求和!

在 JavaScript 中,reduce() 常被误解为"只是一个求数组总和的工具"。但实际上,reduce 是最强大、最灵活的数组高阶函数之一 ------它能模拟 mapfilterfind,还能实现分组、扁平化、数据转换、状态累积等复杂逻辑。

本文将带你深入 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 链式调用的性能开销;
  • 逻辑集中:复杂转换可在一处完成。

⚠️ 注意事项

  1. 务必提供 initialValue (除非你明确知道不需要);
    • 否则第一个元素会被当作初始值,可能引发 bug;
  2. 不要滥用 :简单场景优先用 map/filter,语义更清晰;
  3. 避免副作用reduce 应是纯函数,不要修改外部变量;
  4. 可读性权衡 :过于复杂的 reduce 可拆分为多个步骤。

五、总结:reduce 的思维模型

reduce 的本质是:遍历数组,逐步构建一个最终结果。

你想做什么? reduce 能否实现?
求和、求积 ✅ 当然
转换为新数组 ✅(模拟 map)
筛选元素 ✅(模拟 filter)
分组、计数 ✅ 最佳场景之一
扁平化
构建映射对象 ✅ 高效
状态累积(如表单校验)

掌握 reduce,你就拥有了 "用一个函数解决大多数数组问题" 的能力。但记住:强大不等于万能,在可读性和性能之间找到平衡,才是专业开发者的标志。

🌟 终极建议 :当你发现自己写了多个 map/filter 链,或者需要在循环中维护复杂状态时------试试 reduce

相关推荐
chilavert31836 分钟前
技术演进中的开发沉思-225 Prototype.js 框架
开发语言·javascript·原型模式
一入程序无退路40 分钟前
若依框架导出显示中文,而不是数字
java·服务器·前端
m0_6265352042 分钟前
代码分析 关于看图像是否包括损坏
java·前端·javascript
wangbing112543 分钟前
layer.open打开的jsf页面刷新问题
前端
Mintopia44 分钟前
🌏 父子组件 i18n(国际化)架构设计方案
前端·架构·前端工程化
WebGISer_白茶乌龙桃44 分钟前
前端又要凉了吗
前端·javascript·vue.js·js
小飞侠在吗1 小时前
vue2 watch 和vue3 watch 的区别
前端·javascript·vue.js
脾气有点小暴1 小时前
Vue3 中 ref 与 reactive 的深度解析与对比
前端·javascript·vue.js