JavaScript 数组去重:方法、性能与应用场景

95 阅读7分钟

在 JavaScript 开发中,数组去重是一个常见且基础的需求,无论是处理用户输入的数据,还是从后端获取的数据集,我们经常需要对数组中的重复元素进行清理。本文将详细介绍 JavaScript 中常见的数组去重方法,并分析它们的性能和适用情况,帮助你在实际开发中做出更合适的选择。

数组去重的应用场景

数据清洗

  • 在实际开发中,数据来源复杂多样,包括用户输入、API接口返回以及数据库查询结果等,这些数据中常存在冗余值。通过数组去重操作,可有效清除重复项,从而提升后续数据处理的准确性与效率。
  • 例如,在处理用户提交的表单数据时,网络异常或用户误操作可能导致数据重复录入,此时去重机制对保障数据完整性至关重要。

前端展示优化

  • 前端开发过程中,若页面展示的列表、下拉选项等内容存在重复数据,将导致界面混乱并影响用户体验。通过数组去重,可确保展示内容均为唯一值,使页面呈现简洁且结构清晰。
  • 以城市选择下拉菜单为例,若原始数据中存在重复的城市名称,去重后菜单选项将更加清晰明了,便于用户快速定位目标选项。

统计分析需求

  • 在数据统计与分析场景中,核心目标常聚焦于唯一值的数量及分布特征。数组去重能够快速提取唯一元素集合,为分析提供精准数据基础。
  • 例如,统计网站的独立访客数量时,用户可能在一天内多次访问并生成重复记录,此时需基于用户ID组成的数组进行去重处理,方能计算实际独立访客数量。

业务功能实现

  • 诸多业务逻辑的实现依赖数组去重技术。
  • 在表单提交场景中,通过去重机制可有效避免重复数据提交,保障业务逻辑的严谨性。

典型应用场景示例

  • 搜索关键词去重
    在搜索框的自动补全功能中,对历史关键词数组进行去重处理,可避免向用户展示冗余建议,帮助用户高效定位目标信息。
  • 用户行为记录分析
    统计用户单日访问的页面URL时,通过对URL数组去重,可精准掌握用户的浏览路径与兴趣分布,支撑行为分析。
  • 数据可视化
    在生成可视化图表时,对原始数据数组进行去重,可确保图表仅反映唯一数据点的分布规律,避免因重复值导致结果失真。
  • 多数据源整合
    合并多个来源的数据时,不同数据源常存在重复记录。通过数组去重,可将多个列表整合为唯一元素集合,便于后续统一处理与分析。

一、基础方法篇

1. 双重 for 循环(暴力去重)

function uniqueByLoop(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] === arr[j]) {
        arr.splice(j, 1)
        j-- // 删除元素后调整索引
      }
    }
  }
  return arr
}

let arr = [1, 2, 4, 5, 5, 2, 1, 1, 4, 6];
console.log(uniqueByLoop(arr));

原理:外层循环遍历数组中的每个元素,内层循环从外层循环当前元素的下一个位置开始,将后续元素与当前元素进行比较。如果发现相同元素,则使用 splice 方法将其从数组中删除,并调整内层循环的索引。

特点

  • 时间复杂度高:时间复杂度为 O(n²) ,当数据量较大时,性能会显著下降。
  • 修改原数组:该方法会直接修改原数组,可能会产生副作用,使用时需要注意。
  • 兼容性好:可以在所有 JavaScript 环境中使用。

2. forEach + indexOf

function uniqueByIndexOf(arr) {
  const result = []
  arr.forEach(item => {
    result.indexOf(item) === -1 && result.push(item)
  })
  return result
}

let arr = [1, 2, 4, 5, 5, 2, 1, 1, 4, 6];
console.log(uniqueByIndexOf(arr));
  • 原理:利用 forEach 方法遍历数组,对于每个元素,使用 indexOf 方法检查它在结果数组中是否已经存在。如果不存在,则将其添加到结果数组中。
  • 优化点:不修改原数组,而是返回一个新的去重后的数组。
  • 局限:无法正确识别 NaN,因为 indexOf(NaN) 始终返回 -1

二、ES5 进阶方案

3. filter 过滤法

function uniqueByFilter(arr) {
  return arr.filter((item, index) => 
    arr.indexOf(item) === index
  )
}

let arr = [1, 2, 4, 5, 5, 2, 1, 1, 4, 6];
console.log(uniqueByFilter(arr));
  • 优点:使用函数式编程的方式,代码简洁易读。
  • 缺点:同样存在无法识别 NaN 的问题。
  • 性能:比 forEach + indexOf 略优,但时间复杂度仍为 O(n²) 。

4. 对象键值唯一性

function uniqueByObjectKey(arr) {
  const obj = {}
  arr.forEach(item => obj[item] = true)
  return Object.keys(obj).map(k => 
    isNaN(k) ? NaN : Number(k)
  )
}

let arr = [1, 2, 4, 5, 5, 2, 1, 1, 4, 6];
console.log(uniqueByObjectKey(arr));

注意事项

  • 类型转换:所有元素会被转换为字符串类型,需要手动处理 Number 和 NaN 类型。
  • 重复判断问题:数字 1 和字符串 '1' 会被视为重复元素。

三、ES6+ 现代方案

5. Set 数据结构(推荐首选)

const uniqueBySet = arr => [...new Set(arr)];

let arr = [1, 2, 4, 5, 5, 2, 1, 1, 4, 6];
console.log(uniqueBySet(arr));

优势

  • 性能最优:时间复杂度为 O(n) ,在处理大规模数组时性能表现出色。
  • 代码简洁:只需一行代码即可实现去重。
  • 正确识别 NaNSet 中 NaN 被视为等于自身。

6. Map 数据结构

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

let arr = [1, 2, 4, 5, 5, 2, 1, 1, 4, 6];
console.log(uniqueByMap(arr));

特性

  • 保留插入顺序:可以保留元素的插入顺序。
  • 处理对象引用类型:能够处理对象引用类型的去重,这是基础方法无法实现的。
  • 性能接近 Set:性能与 Set 方案接近。

四、特殊场景处理

7. 排序后相邻去重

function uniqueBySort(arr) {
  return arr.concat().sort()
    .filter((item, index, array) =>
      !index || item !== array[index - 1]
    )
}

let arr = [1, 2, 4, 5, 5, 2, 1, 1, 4, 6];
console.log(uniqueBySort(arr));

适用场景:适用于允许改变元素顺序的情况。

注意事项

  • 排序问题:对字符串和数字混合的数组进行排序时,可能会产生意外的结果。
  • 深拷贝:如果需要深拷贝数组,可以使用 [...arr].sort()

8. reduce 累积器

  • reduce 是 JavaScript 数组的一个高阶方法,用于对数组中的每个元素执行一个提供的回调函数,并将其结果汇总为单个值。它接收两个参数:
    • 回调函数:用于处理数组中的每个元素,该回调函数接收四个参数,这里使用了其中两个:
      • acc(accumulator):累加器,它是上一次回调函数执行的返回值,初始值由 reduce 方法的第二个参数指定。
      • cur(currentValue):当前正在处理的数组元素。
    • 初始值:reduce 方法的第二个参数,这里是一个空数组 [],表示累加器 acc 的初始值。
const uniqueByReduce = arr => 
  arr.reduce((acc, cur) => 
    acc.includes(cur) ? acc : [...acc, cur], 
    []
  )

let arr = [1, 2, 4, 5, 5, 2, 1, 1, 4, 6];
console.log(uniqueByReduce(arr));
  • 优势:是函数式编程的典范,代码简洁且具有良好的扩展性。
  • 扩展性:方便添加复杂的去重逻辑。

总结

JavaScript 数组去重的方法多种多样,每种方法都有其独特的优缺点和适用场景。在实际开发中,我们需要根据具体需求进行选择:

  • 如果追求代码简洁和高性能,且项目运行在支持 ES6 的环境中,推荐使用 Set 数据结构进行去重。
  • 如果需要兼容不支持 ES6 的旧环境,可以考虑使用 filter + indexOf 等兼容性较好的方法。
  • 如果需要处理对象引用类型或保留元素的插入顺序,可以选择 Map 数据结构。
  • 如果对元素顺序没有要求,且数组元素类型较为单一,可以使用排序后相邻去重的方法。

希望通过本文的介绍,你能对 JavaScript 数组去重有更深入的理解,在实际开发中能够灵活运用各种方法,提高代码的质量和性能。