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

相关推荐
P7Dreamer8 小时前
微信小程序处理Range分片视频播放问题:前端调试全记录
前端·微信小程序
RedHeartWWW8 小时前
初识next-auth,和在实际应用中的几个基本场景(本文以v5为例,v4和v5的差别主要是在个别显式配置和api,有兴趣的同学可以看官网教程学习)
前端·next.js
C_心欲无痕8 小时前
前端页面中,如何让用户回到上次阅读的位置
前端
C_心欲无痕8 小时前
前端本地开发构建和更新的过程
前端
C_心欲无痕8 小时前
JavaScript 常见算法与手写函数实现
开发语言·javascript·算法
Mintopia8 小时前
🌱 一个小而美的核心团队能创造出哪些奇迹?
前端·人工智能·团队管理
蚊道人9 小时前
Nuxt 4 学习文档
前端·vue.js
悠哉摸鱼大王9 小时前
前端音视频学习(一)- 基本概念
前端
stella·9 小时前
后端二进制文件,现代前端如何下载
前端·ajax·状态模式·axios·request·buffer·download
奋斗猿9 小时前
Less vs Scss 全解析:从语法到实战的前端样式预处理器指南
前端