JS 数组或数组对象去重的9种方法

JS 数组或数组对象去重的9种方法

一、背景与问题

在前端开发中,处理数据去重是常见的需求。无论是用户输入的表单数据、API返回的列表数据,还是本地存储的缓存数据,重复数据都可能影响程序的性能和用户体验。对于数组去重,核心问题是:如何高效地识别并移除重复元素。

JS数组的重复元素可以是基本类型(如数字、字符串),也可以是对象(如包含id、name等属性的对象)。不同类型的去重需要不同的处理方式,而常见的误区包括:

  • 直接使用Set处理对象数组
  • 忽略类型转换导致的潜在问题
  • 对大数据量使用低效算法

本文将深入分析9种数组去重方法,涵盖不同场景下的实现原理、性能对比和实际应用建议。


二、基本原理

1. 哈希表原理(Set/Map)

  • 哈希表通过键值对存储数据,查找时间复杂度为O(1)
  • 适用于处理基本类型和简单对象
  • 对象需要通过唯一标识(如id)进行映射

2. 线性查找原理(includes/filter)

  • 遍历数组时逐个检查元素是否已存在
  • 时间复杂度O(n²),适合小数据量
  • 对象需要深度比较(JSON.stringify)

3. 排序去重原理(sort+splice)

  • 先排序再遍历,利用相同元素相邻的特性
  • 时间复杂度O(n log n),适合需要排序的场景
  • 对象需要先处理为可比较的键

4. 递归去重原理

  • 通过递归分解数组,逐层处理子数组
  • 适合处理嵌套结构的复杂数据

三、环境准备

# 安装开发依赖(如需)
npm install --save-dev eslint
// 示例环境
const assert = require('assert');

四、核心实现

方法1:Set去重(适用于基本类型)

function uniqueSet(arr) {
  return Array.from(new Set(arr));
}

原理分析:

  • Set构造函数会自动过滤重复元素
  • Array.from将Set转换为数组
  • 适用于字符串、数字等基本类型

局限性:

  • 无法处理对象数组(如[{id:1}, {id:1}])

方法2:filter+includes去重(适用于小数据量)

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

原理分析:

  • indexOf方法查找元素索引
  • 通过索引判断是否是首次出现
  • 时间复杂度O(n²)

改进版:

function uniqueFilterOpt(arr) {
  const seen = new Set();
  return arr.filter(item => {
    const key = typeof item === 'object' ? JSON.stringify(item) : item;
    return seen.has(key) ? false : seen.add(key);
  });
}

注意事项:

  • 对象需通过JSON.stringify转换为字符串
  • 会改变原数组顺序

方法3:sort+splice去重(适用于需要排序的场景)

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

原理分析:

  • 先排序使相同元素相邻
  • 通过splice移除重复项
  • 时间复杂度O(n log n)

适用场景:

  • 需要保持有序输出
  • 可处理简单类型数组

方法4:reduce+includes去重(适用于对象数组)

function uniqueReduce(arr) {
  return arr.reduce((acc, item) => {
    const key = typeof item === 'object' ? JSON.stringify(item) : item;
    return acc.includes(key) ? acc : [...acc, key];
  }, []);
}

原理分析:

  • 使用reduce累积唯一键
  • 通过includes判断是否已存在
  • 适用于对象数组的键值去重

性能优化:

function uniqueReduceOpt(arr) {
  return arr.reduce((acc, item) => {
    const key = typeof item === 'object' ? JSON.stringify(item) : item;
    const exists = acc.some(k => k === key);
    return exists ? acc : [...acc, key];
  }, []);
}

改进点:

  • 使用some代替includes提高性能
  • 避免不必要的数组复制

五、完整案例

场景:用户输入数据去重处理

<!-- 前端页面 -->
<input type="text" id="input" placeholder="输入内容">
<button onclick="process()">处理</button>
<pre id="output"></pre>

<script>
function process() {
  const input = document.getElementById('input').value;
  const arr = input.split(',').map(s => s.trim());
  
  // 使用Set去重
  const unique = uniqueSet(arr);
  
  // 显示结果
  document.getElementById('output').textContent = JSON.stringify(unique, null, 2);
}
</script>
// 后端处理(Node.js)
app.post('/process', (req, res) => {
  const { data } = req.body;
  const unique = uniqueSet(data.split(',').map(s => s.trim()));
  res.json({ result: unique });
});

性能测试:

// 压力测试代码
const bigArray = Array(100000).fill(null).map((_, i) => i % 100);
console.time('去重时间');
console.log(uniqueSet(bigArray));
console.timeEnd('去重时间');

六、源码解析

以uniqueReduceOpt方法为例:

function uniqueReduceOpt(arr) {
  return arr.reduce((acc, item) => {
    const key = typeof item === 'object' ? JSON.stringify(item) : item;
    const exists = acc.some(k => k === key);
    return exists ? acc : [...acc, key];
  }, []);
}

关键点解析:

  1. reduce初始值为一个空数组
  2. some方法比includes效率更高(O(n) vs O(n²))
  3. 使用展开运算符避免修改原数组
  4. 对象通过JSON.stringify转换为可比较的字符串

性能对比:

方法时间复杂度适用场景
SetO(n)基本类型数组
filter+includesO(n²)小数据量
sort+spliceO(n log n)需要排序
reduce+includesO(n²)对象数组
reduce+someO(n²)优化版对象数组

七、进阶使用

对象数组去重优化

function uniqueObjects(arr, key) {
  return arr.reduce((acc, item) => {
    const id = item[key];
    if (!acc.includes(id)) {
      acc.push(item);
    }
    return acc;
  }, []);
}

使用示例:

const data = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Alice' }
];
const unique = uniqueObjects(data, 'id');

多字段去重

function uniqueMultiKey(arr, keys) {
  return arr.reduce((acc, item) => {
    const key = keys.map(k => item[k]).join('|');
    if (!acc.includes(key)) {
      acc.push(item);
    }
    return acc;
  }, []);
}

使用示例:

const data = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 },
  { id: 1, name: 'Alice', age: 25 }
];
const unique = uniqueMultiKey(data, ['id', 'age']);

八、性能与工程实践

性能优化策略

  1. 数据预处理:将对象数组转换为唯一标识的数组
  2. 使用Map:替代Set处理对象数组
  3. 并行处理:使用Web Workers处理大数据量
  4. 记忆化:缓存已处理过的数据

异常处理方案

function safeUnique(arr) {
  try {
    return uniqueSet(arr);
  } catch (e) {
    console.error('去重失败:', e);
    return arr;
  }
}

安全性考虑

  • 避免使用eval或new Function处理用户输入
  • 对JSON.stringify的结果进行校验
  • 使用JSON.parse时添加try/catch

九、常见问题与踩坑

常见错误

错误原因解决方案
方法1无法处理对象数组使用JSON.stringify转换
方法2混淆对象引用使用深度比较
方法3排序不彻底确保排序算法的稳定性
方法4忽略类型转换添加类型校验逻辑

典型错误示例

// 错误:直接使用Set处理对象数组
const data = [{id:1}, {id:1}];
const unique = Array.from(new Set(data)); // 不会去重

正确实现

// 正确:使用Map处理对象数组
function uniqueObjects(arr) {
  return Array.from(new Map(arr.map(item => [JSON.stringify(item), item]))).map(([k, v]) => v);
}

十、最佳实践

推荐方案

场景推荐方法原因
基本类型数组Set简洁高效
对象数组Map可控性强
需要排序sort+splice保持有序输出
大数据量哈希表时间复杂度O(n)
多字段去重多字段hash灵活性强

实施建议

  1. 先做类型校验:确保输入数据符合预期
  2. 考虑内存占用:处理大数据时使用分页
  3. 添加日志记录:记录去重前后数据变化
  4. 使用性能分析工具:如Chrome DevTools的Performance面板

十一、总结

JS数组去重是一个看似简单但涉及多维度技术的难题。从基本类型的Set去重到复杂对象数组的处理,需要根据具体场景选择合适的方法。本文通过9种方法的对比分析,深入探讨了不同实现的原理、适用场景和性能特点。

在实际开发中,建议优先使用Set和Map处理基本类型和对象数组,对于特殊需求可采用排序、递归等方法。同时,要特别注意数据类型转换、性能优化和安全风险,确保在不同场景下都能稳定运行。

记住:去重不是简单的数据过滤,而是对数据结构和算法的深刻理解。掌握这些技术,将帮助你在处理复杂数据时更加得心应手。

最后修改于:2026年09月21日 00:09

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日