在数组沙海里淘金:JavaScript 去重的 5 种高效方法

119 阅读5分钟

前言

在JavaScript的世界里,数组如同一片广袤的沙漠,重复元素则是散落其中的碎石。当我们需要从这片沙海中淘出纯净的"金子"时,数组去重便成为不可或缺的工具——它能避免重复数据导致的资源浪费和用户体验问题,确保数据处理的高效与精准。

本文将专注于介绍最实用的数组去重方法,助你在代码的沙海中快速找到最优的解决方案👏

一、淘金术大比拼:最实用的数组去重方法

1. Set淘金法:最高效的"沙金筛"

Set是ES6为我们带来的高效淘金工具,它就像一个精密的筛子,能自动过滤掉所有重复的"沙粒"。

const arr = [1, '1', 2, 2, NaN, NaN];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, '1', 2, NaN]

Set内部基于哈希表实现,查找和插入操作接近O(1)时间复杂度,整体效率为O(n)。能正确区分数字1和字符串'1',并自动处理NaN。这就像用筛子淘金,只需一遍过筛就能得到纯净的金粒。

在JavaScript中,NaN是一个特殊的值,它不等于任何值,包括它自己(NaN !== NaN)。

2. Map淘金法:精准的"金粒分类器"

Map方法通过利用Map数据结构的键唯一性特性,为每个元素创建一个唯一标识:

function uniqueByMap(arr) {
  const map = new Map();
  return arr.filter(item => {
    if (!map.has(item)) {
      map.set(item);
      return true;
    }
    return false;
  });
}

Map的键可以是任意值类型,包括对象和原始类型,这使得它能正确处理不同类型值的区分,如1'1'。不过 Map 对对象 key 按引用判断,内容相同的不同对象不会被去重。

3. Filter+FindIndex组合法:灵活的"金粒筛选器"

Filter与findIndex的组合方法允许我们自定义去重条件,特别适合对象数组:

// 对象数组按id去重
const arr = [{id:1, name:'Alice'}, {id:2, name:'Bob'}, {id:1, name:'Charlie'}];
const uniqueArr = arr.filter((item, index, self) => {
  return self.findIndex(t => t.id === item.id) === index;
});
console.log(uniqueArr); // [{id:1, name:'Alice'}, {id:2, name:'Bob'}]

findIndex方法允许我们定义自定义比较逻辑,可以按对象的特定属性(如id、name)去重。但是时间复杂度为O(n²),不适合大数据集。

4. 排序后相邻比较法:高效的"金粒排列术"

排序后相邻比较法通过先对数组排序,然后只需一次遍历就能发现相邻重复元素:

function uniqueBySort(arr) {
  if (arr.length === 0) return [];
  const sortedArr = [...arr].sort(); // 创建副本,避免修改原数组
  const result = [sortedArr[0]];
  for (let i = 1; i < sortedArr.length; i++) {
    if (sortedArr[i] !== sortedArr[i - 1]) {
      result.push(sortedArr[i]);
    }
  }
  return result;
}

const arr = [3, 1, 2, 2, 4, 5];
console.log(uniqueBySort(arr)); // [1, 2, 3, 4, 5] (原顺序被改变)

排序后只需一次遍历,时间复杂度O(n log n)。但会改变原数组顺序(已通过[...arr]避免修改原数组)。

5. JSON.stringify转换法:对象数组的"指纹识别"

JSON.stringify方法通过将对象转换为字符串,利用字符串的唯一性来实现去重:

function uniqueByJSON(arr) {
  const seen = new Set(); // 使用Set替代对象,提高效率
  return arr.filter(item => {
    const str = JSON.stringify(item);
    if (!seen.has(str)) {
      seen.add(str);
      return true;
    }
    return false;
  });
}

const arr = [{id:1, func: function(){}}, {id:1}];
console.log(uniqueByJSON(arr)); // [{id:1}] (将两个对象视为相同)

JSON字符串可以唯一表示对象的结构和内容,能处理对象的深度比较(如[{id:1}, {id:1}]会被视为相同)。但无法处理函数、循环引用等特殊值(如示例中函数被忽略导致错误去重)。

二、实战建议:选择最适合的淘金工具

在实际项目中,我们应根据数据类型和规模选择合适的淘金方法:

  1. 处理简单数据:优先使用Set方法,简洁高效。

  2. 处理对象数据

    • 若只需按简单属性去重,使用filter+findIndex组合
    • 若需要深度比较,使用JSON.stringify方法
  3. 处理大数据量

    • 对于基本类型数组,使用Set或排序后相邻比较法
    • 对于对象数组,使用Map或filter+findIndex组合
  4. 处理特殊值

    • 使用Set方法可自动处理NaN
    • 对于对象中的NaN属性,可在键生成逻辑中特殊处理

小技巧:在实际应用中,可以结合使用Set和Map来处理更复杂的情况:

// 对象数组按id去重
let uniqueArr = [...new Map(arr.map(item => [item.id, item])).values()];
// 处理包含NaN的数组
let uniqueArrWithNaN = [...new Set(arr)];

三、特殊场景的淘金技巧:多维数组去重

多维数组的去重可以使用JSON.stringify,但也有其局限性:

function uniqueMultiDimensional(arr) {
  const seen = new Set();
  return arr.filter(item => {
    const str = JSON.stringify(item);
    if (!seen.has(str)) {
      seen.add(str);
      return true;
    }
    return false;
  });
}

那么其“多维”与“局限性”都是怎么体现的呢?让我用两个数组作为例子来讲清楚:

// 示例1:普通多维数组
const multiArr = [[1, 2], [3, 4], [1, 2], [5, 6]];
console.log(uniqueMultiDimensional(multiArr)); 
// [[1, 2], [3, 4], [5, 6]] ✅ 正确去重
// 示例2:包含函数的多维数组(局限性体现)
const arrWithFunc = [[1, 2, function(){}], [1, 2]];
console.log(uniqueMultiDimensional(arrWithFunc)); 
// [[1, 2, function(){}]] ❌ 错误去重(函数被忽略,导致两个不同数组被视为相同)

结语:淘金不止于去重

数组去重不仅是前端开发中的基础技能,更是对数据处理思维的锻炼。它教会我们如何在纷繁复杂的数据中找到价值,如何选择最合适的工具来解决问题。

记住:没有一种方法能解决所有去重问题。Set方法简洁高效,但无法处理对象深度比较;Map方法安全可靠,但需要配合键生成逻辑;filter+findIndex灵活多样,但性能一般;排序后相邻比较法高效简洁,但会改变顺序。

在未来的开发中,当你面对重复数据时,不妨先问自己:这是一片细沙还是金矿?需要一把简单的筛子,还是一位经验丰富的金匠?选择正确的"淘金术",让数据处理变得高效而优雅。

如发现错误或有更好方法,欢迎在评论区留言交流!