← 返回题目列表

map、filter、reduce 有什么区别?使用时要注意什么?

高频 简单 第 4 / 29 题 更新于 2026/07/29
JavaScriptArray函数式编程

简化版

map 用来一对一转换数组元素,返回等长新数组;filter 用来筛选元素,返回满足条件的新数组;reduce 用来把数组累积成一个值,可以是数字、对象、数组等。

详细版

三者都不会直接改变原数组,但回调里修改对象元素仍可能产生副作用。

[1, 2, 3].map(x => x * 2);      // [2, 4, 6]
[1, 2, 3].filter(x => x > 1);   // [2, 3]
[1, 2, 3].reduce((s, x) => s + x, 0); // 6

面试重点:

  • map 关注转换。
  • filter 关注保留或丢弃。
  • reduce 关注累积。
  • reduce 最好提供初始值,避免空数组报错和类型推断混乱。

完整版教学

一、三者都是数组迭代抽象

map/filter/reduce 都会按顺序遍历数组元素。 它们的区别不在于“能不能循环”,而在于表达的意图不同。 好的代码会让读者一眼看出你想转换、筛选还是汇总。

const nums = [1, 2, 3, 4];

如果目标是把每个数翻倍,用 map。 如果目标是留下偶数,用 filter。 如果目标是求总和,用 reduce。 同样是遍历 4 个元素,语义却完全不同。

二、map 是一对一转换

map 对每个元素执行回调,并把每次回调返回值组成新数组。 输入数组有几个元素,输出数组通常就有几个元素。 它适合数据结构转换、字段提取、列表渲染前加工。

const users = [
  { id: 1, name: 'A' },
  { id: 2, name: 'B' }
];

const names = users.map(user => user.name);
// ['A', 'B']

数字例子:长度为 2usersmapnames 长度仍是 2。 如果你的回调里只做副作用却没有使用返回值,通常不该用 map,应该考虑 forEach

三、filter 是条件筛选

filter 回调返回真值时保留元素,返回假值时丢弃元素。 它不改变元素本身,只决定元素是否进入新数组。 输出数组长度可能小于或等于输入数组长度。

const nums = [1, 2, 3, 4, 5];
const even = nums.filter(n => n % 2 === 0);
// [2, 4]

这里输入 5 个元素,输出 2 个元素。 面试里常问“filter 是否会改变原数组”,答案是不会改变数组结构。 但如果数组元素是对象,回调中修改对象属性仍会影响共享对象。

四、reduce 是把多个元素折叠成一个结果

reduce 接收累积器和当前元素。 它可以求和,也可以分组、构建 Map、拍平数组、统计频次。 关键是初始值决定累积器的类型和第一次迭代行为。

const sum = [1, 2, 3].reduce((acc, cur) => acc + cur, 0);
// 6

const count = ['a', 'b', 'a'].reduce((acc, cur) => {
  acc[cur] = (acc[cur] ?? 0) + 1;
  return acc;
}, {});
// { a: 2, b: 1 }

流程如下:

初始 acc = 0
读 1 -> acc = 1
读 2 -> acc = 3
读 3 -> acc = 6

reduce 很强,但过度使用会让简单逻辑变难读。

五、reduce 为什么建议总是给初始值

如果不给初始值,reduce 会把数组第一个元素当作初始累积器,并从第二个元素开始遍历。 空数组没有第一个元素,因此会报错。 给初始值可以让行为稳定,也让累积器类型更清楚。

[].reduce((a, b) => a + b); // TypeError
[].reduce((a, b) => a + b, 0); // 0
写法空数组累积器初始类型
无初始值报错第一个元素类型
有初始值返回初始值初始值类型

在 TypeScript 项目里,初始值还能帮助类型推断。

六、副作用和性能要看场景

map/filter/reduce 返回新数组或新结果,不直接修改原数组。 但它们不是自动不可变保护。 如果元素是对象,回调里修改对象会影响原数组里的同一个对象。

const list = [{ count: 1 }];
const next = list.map(item => {
  item.count += 1;
  return item;
});

console.log(list[0].count); // 2

记忆钩子:数组方法不改“数组壳”,不代表不会改“对象芯”。

性能方面,链式调用会产生中间数组。 小中型列表可读性优先,超大数据或热点逻辑再考虑一次循环完成。

七、常见误区与追问

  • 误区:map 可以用来纯做遍历副作用。 如果不用返回的新数组,语义上更适合 forEach 或普通循环。
  • 误区:这些方法一定不会产生副作用。 方法本身不改原数组结构,但回调里仍可能修改对象元素。
  • 误区:reduce 永远比循环高级。 复杂累积逻辑用 reduce 可能降低可读性,清晰更重要。
  • 追问:reduce 不传初始值有什么风险? 空数组会报错,累积器类型也可能和预期不同。
  • 追问:filter(Boolean) 有什么作用? 它会过滤掉所有假值,包括 0、空字符串、falsenullundefined
  • 追问:链式 filter().map() 有什么成本? 会产生中间数组,数据量很大时可能需要优化。

八、加强记忆

把三者按动词记:map 转换,filter 筛选,reduce 累积。答题时用 [1,2,3] 的结果举例,再强调 reduce 初始值和对象元素副作用,基本就能覆盖常见追问。