js中使用reduce()方法实现数组去重的几种方式详解

来源:网站主作者:灯下变量头衔:程序员
导读:本期聚焦于灯下变量创作的《js中使用reduce()方法实现数组去重的几种方式详解》,敬请观看详情。数组去重是JavaScript开发中的高频需求,常见的实现方式有Set、filter配合indexOf等,但用reduce来实现数组去重往往被忽视。reduce本身是数组的归并方法,通过累积器可以很方便地把遍历过程中不重复的元素逐个收集起来,再配合includes判断或者对象键值记录,既能实现基本类型去重,也能处理对象数组按指定属性去重的场景。本文将详细讲解reduce去重的基本原理、多种写法对比以及处理复杂对象的进阶技巧,并分析各方案的性能差异,帮助你根据实际业务场景选择最合适的去重方案。

数组去重在前端开发中出现频率非常高,比如商品列表合并、标签聚合、搜索历史记录等场景都会用到。提到去重,大多数人第一反应是用Set配合扩展运算符,一行代码就能搞定。但reduce方法同样可以优雅地完成去重,而且它更像一把瑞士军刀,在去重的同时还能做数据转换、分组统计等附加操作,在一些复杂业务场景下反而更灵活。本文就来详细聊聊如何用reduce实现数组去重。

一、reduce方法的基本原理

在讲去重之前,先回顾一下reduce的工作机制。reduce是数组的归并方法,它接收一个回调函数和一个初始值,回调函数会依次处理数组中的每个元素,并把处理结果累积到一个变量上,最终返回这个累积结果。它的函数签名是arr.reduce(callback, initialValue),其中回调函数包含四个参数:累积器、当前元素、当前索引和原数组。

去重的核心思路正是利用了累积器这个特性:把累积器初始化为一个空数组,遍历过程中判断当前元素是否已经存在于累积器中,不存在就通过concat或扩展运算符把它追加进去,存在就直接返回累积器不变。这样遍历结束后,累积器里存的就是去重后的数组。

需要注意的是,回调函数中尽量不要用push直接修改累积器后再返回,虽然这样也能得到正确结果,但会破坏reduce纯函数的语义。推荐使用concat或者扩展运算符生成新数组返回,代码意图更清晰,也更容易配合链式调用。

二、reduce去重的基础写法

最经典的写法是配合includes方法判断元素是否已存在。每次遍历时先检查累积数组中有没有当前元素,没有就追加进去,代码如下:

const arr = [1, 2, 2, 3, 4, 4, 5];

const uniqueArr = arr.reduce((acc, cur) => {
  // 如果累积数组中没有当前元素,则追加
  return acc.includes(cur) ? acc : [...acc, cur];
}, []);

console.log(uniqueArr); // [1, 2, 3, 4, 5]

除了includes,也可以用indexOf来判断,判断条件写成acc.indexOf(cur) === -1,两种方式效果完全一样。如果去重的同时还想统计每个元素出现的次数,直接把累积器换成对象即可,这正是reduce相对于Set的优势所在:

const arr = ['apple', 'banana', 'apple', 'orange'];

const count = arr.reduce((acc, cur) => {
  acc[cur] = (acc[cur] || 0) + 1;
  return acc;
}, {});

console.log(count); // { apple: 2, banana: 1, orange: 1 }

还有一种思路是利用filter配合indexOf实现去重,原理是只保留元素第一次出现的索引位置。这种写法代码更短,但语义上不如reduce直观,而且无法在遍历过程中做额外的逻辑处理。

三、对象数组的去重处理

上面的写法只适用于基本类型,因为includesindexOf内部使用严格相等比较,两个内容相同的对象在引用上并不相等,所以对象数组直接用基础写法是无效的。这时需要根据对象的某个属性值来判断是否重复,例如根据id去重:

const users = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
  { id: 1, name: '张三' }
];

const uniqueUsers = users.reduce((acc, cur) => {
  // 判断累积数组中是否已存在相同id的对象
  const hasDup = acc.some(item => item.id === cur.id);
  return hasDup ? acc : [...acc, cur];
}, []);

console.log(uniqueUsers.length); // 2

如果需要支持多个属性组合判断,比如idname都相同才算重复,可以把判断条件改成item.id === cur.id && item.name === cur.name。还有一种更高效的做法是用一个辅助对象记录已经出现过的键值,把属性拼接成字符串作为键,查询速度是常量级的:

const seen = {};
const uniqueUsers = users.reduce((acc, cur) => {
  const key = cur.id + '_' + cur.name;
  if (!seen[key]) {
    seen[key] = true;
    acc.push(cur);
  }
  return acc;
}, []);

这种写法虽然引入了外部变量,看起来不那么纯粹,但在大数据量场景下性能明显更好,因为避免了每次都要遍历累积数组的some检查。

四、性能对比与方案选择建议

从时间复杂度角度分析,reduce配合includes的写法是双重循环结构,外层遍历数组长度为n,内层includes检查最坏也是n次比较,整体复杂度接近O(n²)。当数组规模达到几万条时,性能瓶颈会比较明显。而Set内部基于哈希表实现,整体复杂度接近O(n),是大数据量下的首选。

不过在实际业务中,去重往往不是孤立的操作。比如接口返回的数据需要去重后再映射成另一种结构,或者去重的同时要按某个字段分组,这种多步骤处理用reduce一次遍历就能完成,反而比先Set去重再map转换的方式少了多次循环。此时reduce的可扩展性优势就体现出来了。

另外一点需要提醒的是NaN的坑。includes内部使用SameValueZero算法,可以正确识别NaN,所以reduce配合includes能对包含NaN的数组正确去重。但如果换成indexOf判断,由于NaN === NaN返回false,NaN会被全部保留,结果不符合预期。所以在选择判断方式时要留意这种边界情况。

总结一下:Set适合纯基本类型、大数据量的简单去重;reduce适合需要边去重边做数据加工的场景,尤其是对象数组按属性去重时最为灵活。掌握这两种方案的适用边界,在实际开发中就能游刃有余地处理各类去重需求。

js数组去重reduce方法JavaScript修改时间:2026-08-31 05:45:02

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。