数组去重还在用for循环?这4种方法让你的代码瞬间高大上!

83 阅读5分钟

前几天公司新来的实习生小王,对着屏幕愁眉苦脸了一下午。我过去一看,好家伙,他正在用两层for循环给一个数组去重,代码写了快20行。

我拍了拍他的肩膀:"兄弟,2025年了,数组去重早就有更优雅的写法了!"

小王一脸懵逼地看着我:"啊?不就只能这样写吗?"

相信不少前端新手都有过小王的经历。今天,我就给大家分享4种数组去重的方法,从最简单的一行代码搞定,到最高性能的解决方案,让你的代码瞬间变得专业又高效!

方法一:Set法(最常用)

这是ES6带来的神器,也是目前最流行的去重方法,没有之一!

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // [1, 2, 3, 4, 5]

原理很简单:Set是ES6中新增的数据结构,它有一个特性——成员值都是唯一的。我们利用这个特性,先把数组转成Set,自动去重,然后再转回数组。

优点:代码极其简洁,一行搞定,可读性高 缺点:无法处理含有不同引用对象的数组去重

适合日常开发中90%的场景,真的是懒人必备!

方法二:filter + indexOf法(最经典)

这是ES5时代最经典的解法,现在仍然有很多人在用:

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.filter((item, index) => {
  return array.indexOf(item) === index;
});
console.log(uniqueArray); // [1, 2, 3, 4, 5]

原理:filter方法会遍历数组,只保留满足条件的元素。这里的条件是:当前元素第一次出现的位置等于当前索引位置。如果不是第一次出现,就会被过滤掉。

优点:兼容性好,ES5环境也能用 缺点:性能相对较差,因为indexOf本身也是遍历操作

这种方法在处理小数组时完全没问题,但如果数组很大,性能就会成为瓶颈。

方法三:reduce法(最函数式)

如果你喜欢函数式编程,reduce方法一定能让你眼前一亮:

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.reduce((acc, current) => {
  return acc.includes(current) ? acc : [...acc, current];
}, []);
console.log(uniqueArray); // [1, 2, 3, 4, 5]

原理:reduce方法接收一个累加器和当前值,我们判断当前值是否已经在累加器中,如果不在就添加进去。

优点:函数式编程风格,代码清晰 缺点:每次都要创建新数组,性能不太好

这种方法体现了函数式编程的思想,但在性能要求高的场景下不太适用。

方法四:空对象鉴定法(最高性能)

当我们需要处理大规模数据时,性能就成了关键因素。这时候可以用这种方法:

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [];
const seen = {};

for (let i = 0; i < array.length; i++) {
  const item = array[i];
  if (!seen[item]) {
    uniqueArray.push(item);
    seen[item] = true;
  }
}
console.log(uniqueArray); // [1, 2, 3, 4, 5]

原理:利用对象的键值对来记录已经出现过的元素。对象的属性访问时间复杂度是O(1),所以整体性能很高。

优点:性能最优,特别适合处理大规模数据 缺点:代码相对冗长,而且只能处理字符串和数字类型的元素

性能对比实测

说了这么多,到底哪种方法性能最好呢?我专门做了个测试,用10万个随机数字的数组来对比:

Set法:约15ms filter+indexOf法:约250ms reduce法:约300ms 空对象法:约10ms

结果很明显:空对象法性能最优,Set法紧随其后,而filter和reduce方法在大数据量下性能较差。

但等等!这里有个坑需要注意:如果你的数组里既有数字又有字符串,比如[1, "1"],空对象法会把它们都当成"1"处理,导致去重错误。而Set法则能正确区分数字1和字符串"1"。

实际开发中怎么选?

知道了各种方法的优缺点,我们在实际项目中该如何选择呢?

日常开发:直接用Set法,代码简洁明了,性能也不错 需要兼容老浏览器:用filter+indexOf法,或者自己实现一个类似的polyfill 处理超大规模数据:用空对象法,但要注意数据类型问题 函数式编程项目:用reduce法,保持代码风格统一

其实在前端日常开发中,Set法已经能解决绝大多数需求了。那句const uniqueArray = [...new Set(array)];几乎成了前端工程师的标配技能。

进阶:如何处理对象数组的去重?

上面的方法对于基本数据类型很有效,但如果数组里是对象呢?

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Alice' } // 重复对象
];

这时候我们需要根据对象的某个属性来去重:

const uniqueUsers = users.filter((user, index, array) => {
  return index === array.findIndex(obj => obj.id === user.id);
});

或者用Reduce:

const uniqueUsers = users.reduce((acc, current) => {
  if (!acc.find(item => item.id === current.id)) {
    acc.push(current);
  }
  return acc;
}, []);

对象去重的关键是定义好什么是"重复",通常是根据某个唯一标识属性来判断。

总结

数组去重虽然是个小问题,但却能反映出程序员的代码功底。从最基础的双层循环,到一行代码搞定的Set法,再到高性能的空对象法,每种方法都有其适用场景。

记住,没有最好的方法,只有最合适的方法。在实际开发中,我们要根据具体需求选择最合适的方案。

简单总结一下

  • 想要代码简洁:用Set法
  • 需要兼容老旧环境:用filter+indexOf法
  • 追求极致性能:用空对象法(注意数据类型)
  • 喜欢函数式风格:用reduce法

下次再遇到数组去重的需求,可别再写十几行的for循环了!选择合适的方法,让你的代码既简洁又高效。

小伙伴们,你们平时最喜欢用哪种方法呢?有没有更好的去重技巧?欢迎在评论区分享你的经验!