数组去重

78 阅读3分钟

一、基础方法:双层循环与 indexOf

1. 双层循环法
function unique(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;
}
  • 原理:外层遍历原数组,内层检查元素是否已存在于结果数组
  • 时间复杂度:O(n²)
  • 优缺点:兼容性好,但效率低,无法处理NaN(NaN === NaN 为 false)
2. indexOf 优化法
function unique(arr) {
  const result = [];
  for (let item of arr) {
    if (result.indexOf(item) === -1) {
      result.push(item);
    }
  }
  return result;
}
  • 改进点:用 indexOf 简化内层循环
  • 注意[NaN].indexOf(NaN) === -1,无法处理NaN

二、ES6 新特性:Set 与 Map

3. Set 方法(最推荐)
function unique(arr) {
  return [...new Set(arr)];
  // 等价于 Array.from(new Set(arr))
}
  • 原理:利用 Set 数据结构的唯一性
  • 时间复杂度:O(n)
  • 优缺点:代码极简,支持NaN,但无法处理对象({a:1}{a:1} 视为不同元素)
  • 兼容性:IE 不支持,需 polyfill
4. Map 方法(保留顺序)
function unique(arr) {
  const map = new Map();
  return arr.filter(item => {
    if (!map.has(item)) {
      map.set(item, true);
      return true;
    }
    return false;
  });
}
  • 优势:可处理复杂类型(需自定义比较逻辑),保留元素顺序

三、高级场景:对象与排序去重

5. 对象键值对去重(处理复杂类型)
function unique(arr) {
  const obj = {};
  return arr.filter(item => {
    // 生成唯一键(类型+值字符串化)
    const key = typeof item + JSON.stringify(item);
    return obj.hasOwnProperty(key) ? false : (obj[key] = true);
  });
}
  • 原理:将元素转为字符串作为对象键,利用对象属性唯一性
  • 适用场景:去重包含对象、数组的复杂数组
  • 注意{a:1}{a:1} 会被视为相同元素(因 JSON .stringify 结果相同)
6. 排序后相邻比较法
function unique(arr) {
  return arr
    .slice() // 复制数组避免修改原数组
    .sort()
    .filter((item, index, arr) => {
      return !index || item !== arr[index - 1];
    });
}
  • 原理:排序后相同元素相邻,过滤重复项
  • 优缺点:效率较高(O(n log n)),但会打乱原数组顺序,无法处理NaN

四、问题

1. 问:Set 去重和 Map 去重的区别?
    • Set 去重利用其唯一性,代码简洁但无法保留顺序,且对象会被视为不同元素;
    • Map 去重通过键值对记录元素存在性,可保留顺序,且能自定义复杂类型的比较逻辑(如通过属性值判断对象是否重复)。
2. 问:如何去重包含对象的数组?
    • 方案1:自定义比较函数(如比较对象的某个属性):
      function uniqueObjects(arr, key) {
        const map = new Map();
        return arr.filter(obj => {
          const id = obj[key];
          if (!map.has(id)) {
            map.set(id, true);
            return true;
          }
          return false;
        });
      }
      uniqueObjects([{id:1}, {id:2}, {id:1}], 'id'); // [{id:1}, {id:2}]
      
    • 方案2:转为字符串去重(适用于浅比较):
      arr.filter((item, index) => 
        JSON.stringify(item) === JSON.stringify(arr[index])
      );
      
3. 问:去重方法的性能对比?
    • 效率从高到低:Set/Map(O(n))> 排序相邻比较(O(n log n))> indexOf(O(n²));
    • 内存占用:Map 比 Set 略高,因需存储键值对;
    • 推荐选择:简单数组用 Set,复杂类型用 Map,兼容性要求高用 indexOf。

五、性能优化与最佳实践

1. 大数据量优化
  • 当数组长度超过 10000 时,使用 Set 比双层循环性能提升 50% 以上。
2. 自定义比较逻辑
  • 若需根据对象属性去重,封装通用函数:
    function uniqueBy(arr, fn) {
      const seen = new Set();
      return arr.filter(item => {
        const key = typeof fn === 'function' ? fn(item) : item;
        return !seen.has(key) && seen.add(key);
      });
    }
    // 按对象年龄去重
    uniqueBy([{age:20}, {age:30}, {age:20}], o => o.age);
    
3. 兼容性处理
  • 若需兼容 IE,可用 Polyfill 或手动实现 Set 功能:
    if (!Set) {
      class Set {
        // 手动实现 Set 核心逻辑
      }
    }