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

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄1 天前
JavaScript 隐式全局变量解析
javascript
汉堡大王95271 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大1 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师1 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端