JavaScript篇:数组去重的九阳神功:从青铜到王者的多种解法

321 阅读4分钟

        大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。

        我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。

作为前端开发,数组去重这个问题就像九九乘法表一样基础,但你真的掌握所有解法了吗?今天,我就来分享几种不同层次的数组去重方法,从最基础的到最高性能的,总有一种适合你!

青铜段位:双重循环法

这是最原始的去重方法,适合刚入门的新手理解原理:

function uniqueBasic(arr) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    let isDuplicate = false;
    for (let j = 0; j < result.length; j++) {
      if (arr[i] === result[j]) {
        isDuplicate = true;
        break;
      }
    }
    if (!isDuplicate) {
      result.push(arr[i]);
    }
  }
  return result;
}

const myArr = [1, 2, 2, 3, '我', '我', true, true];
console.log(uniqueBasic(myArr)); // [1, 2, 3, "我", true]

优点:容易理解,不依赖任何API
缺点:时间复杂度O(n²),性能差

白银段位:indexOf优化法

利用indexOf方法简化内层循环:

function uniqueIndexOf(arr) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    if (result.indexOf(arr[i]) === -1) {
      result.push(arr[i]);
    }
  }
  return result;
}

优点:代码更简洁
缺点:indexOf本质也是循环,性能提升有限

黄金段位:排序后相邻比较法

先排序,然后比较相邻元素:

function uniqueSort(arr) {
  arr.sort();
  const result = [arr[0]];
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] !== arr[i - 1]) {
      result.push(arr[i]);
    }
  }
  return result;
}

优点:时间复杂度主要取决于排序算法
缺点:会改变原数组顺序,且不适合对象类型

铂金段位:对象键值法

利用对象键值不重复的特性:

function uniqueObject(arr) {
  const obj = {};
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    const key = typeof arr[i] + arr[i]; // 处理不同类型
    if (!obj[key]) {
      obj[key] = true;
      result.push(arr[i]);
    }
  }
  return result;
}

优点:时间复杂度O(n)
缺点:无法区分复杂对象,如{name: '我'}{name: '我'}会被认为是相同的

钻石段位:ES6 Set法

利用ES6的Set数据结构:

function uniqueSet(arr) {
  return [...new Set(arr)];
}

优点:代码极简,性能优秀
缺点:无法处理对象类型的深度去重

星耀段位:Map数据结构法

使用Map存储已出现过的元素:

function uniqueMap(arr) {
  const map = new Map();
  return arr.filter(item => {
    return !map.has(item) && map.set(item, true);
  });
}

优点:保持原数组顺序,性能好
缺点:同样无法处理对象深度比较

王者段位:reduce一行搞定

使用reduce一行代码实现:

const uniqueReduce = arr => arr.reduce((prev, cur) => 
  prev.includes(cur) ? prev : [...prev, cur], []);

优点:函数式编程,代码优雅
缺点:每次迭代都创建新数组,性能稍差

特殊场景处理

对象数组去重

function uniqueObjectArray(arr, key) {
  const map = new Map();
  return arr.filter(item => {
    return !map.has(item[key]) && map.set(item[key], true);
  });
}

const users = [
  { id: 1, name: '我' },
  { id: 2, name: '你' }, 
  { id: 1, name: '我' }
];
console.log(uniqueObjectArray(users, 'id')); 
// [{id: 1, name: "我"}, {id: 2, name: "你"}]

考虑NaN的情况

function uniqueWithNaN(arr) {
  const set = new Set();
  return arr.filter(item => {
    if (Number.isNaN(item)) {
      return !set.has('NaN') && set.add('NaN');
    }
    return !set.has(item) && set.add(item);
  });
}

const arrWithNaN = [1, NaN, 2, NaN, '我'];
console.log(uniqueWithNaN(arrWithNaN)); // [1, NaN, 2, "我"]

性能大比拼

用10万条数据测试各种方法的耗时:

方法耗时(ms)
双重循环法12000+
indexOf法800
排序法50
对象键值法15
Set法10
Map法12

最佳实践建议

  1. 简单数组:优先使用[...new Set(arr)],简洁高效
  2. 复杂对象数组:根据唯一键使用Map处理
  3. 需要兼容老浏览器:使用对象键值法或indexOf法
  4. 保持顺序重要:使用Map或reduce方法
  5. 性能敏感场景:避免使用双重循环和indexOf

总结

数组去重看似简单,实则暗藏玄机:

  1. 基础方法帮助理解原理,但性能较差
  2. Set/Map是现代JavaScript的最佳选择
  3. 特殊场景需要特殊处理,如对象数组、NaN等
  4. 性能考量在大数据量时尤为重要

记住:没有最好的方法,只有最适合当前场景的方法。希望这篇文章能帮助你在数组去重的问题上从青铜晋级到王者!