0

0

js如何实现数组去重

幻夢星雲

幻夢星雲

发布时间:2025-08-14 17:25:02

|

989人浏览过

|

来源于php中文网

原创

最高效的js数组去重方法是利用set对象,因其底层基于哈希表,平均时间复杂度为o(n),性能最优;2. filter结合indexof方法兼容性好但时间复杂度为o(n^2),适合小规模数组;3. reduce结合map同样具有o(n)时间复杂度,灵活性高,适用于需要自定义去重逻辑或处理对象数组的场景;4. 对于对象数组去重,需基于唯一标识属性(如id)或组合键使用filter加set/map,或利用map覆盖机制保留最后出现的元素;5. set、filter+indexof和reduce+map三种方法在现代javascript环境中均能保持元素的原始顺序,其中set在实际引擎实现中按插入顺序迭代,确保去重后顺序一致;综上,优先推荐使用set进行基本类型去重,在需要兼容性或复杂逻辑时选用reduce加map方案,对象数组则依据属性定义唯一性标准进行处理,所有方法均能有效保持原始顺序。

js如何实现数组去重

JS数组去重,说白了,就是把一个数组里重复的元素给剔除掉,只留下那些独一无二的。这事儿在前端开发里挺常见的,比如你从后端拿到一份数据列表,里面可能因为各种原因出现了重复项,这时候你就得想办法把它“洗干净”。选择哪种方法,其实挺看你的具体场景和对性能、代码简洁度的偏好。

利用Set对象去重是最现代也最直接的方式。Set是ES6引入的一种新的数据结构,它最大的特点就是成员的值都是唯一的。所以,把数组扔进Set里走一圈,再把它变回数组,基本上就搞定了。

const originalArray = [1, 2, 2, 'hello', 'world', 'hello', null, null, undefined, undefined, NaN, NaN, {}, {}];
const uniqueArrayBySet = [...new Set(originalArray)];

console.log(uniqueArrayBySet);
// 结果:[1, 2, 'hello', 'world', null, undefined, NaN, {}, {}]
// 注意:Set在处理NaN时,会认为所有NaN都是同一个值,但对于对象,它会认为两个空对象字面量({})是不同的,因为它们的引用地址不同。

这种方法代码量极少,可读性也好,对于基本数据类型(数字、字符串、布尔值、null、undefined、NaN)去重非常高效和准确。但记住一点,它对对象的去重是基于引用地址的,两个内容完全一样的对象,如果内存地址不同,Set会把它们当作两个不同的元素。

除了Set,我们也可以利用数组自身的

filter()
方法结合
indexOf()
来去重。这个方法相对传统,理解起来也比较直观:遍历数组,如果当前元素是它第一次出现,就保留它。

const originalArray = [1, 2, 2, 'hello', 'world', 'hello', null, null, undefined, undefined, NaN, NaN];
const uniqueArrayByFilter = originalArray.filter((item, index, self) => {
  // indexOf会返回元素在数组中第一次出现的索引
  // 如果当前元素的索引和它第一次出现的索引相同,说明它是第一次出现,就保留
  return self.indexOf(item) === index;
});

console.log(uniqueArrayByFilter);
// 结果:[1, 2, 'hello', 'world', null, undefined, NaN]
// 注意:indexOf在处理NaN时,会有些“奇怪”,因为NaN !== NaN,所以indexOf(NaN)总是-1,可能导致多个NaN被保留。
// 同样,它也无法直接处理对象去重。

这种方式的优点是兼容性好,在老旧浏览器环境里也能跑。但它的性能在数组元素较多时会明显下降,因为

indexOf
在每次迭代时都要从头开始搜索,导致时间复杂度上升。

再者,如果我们需要更精细的控制,或者要兼顾性能和顺序,

reduce()
方法结合
Map
或普通JavaScript对象来做查找表也是个不错的选择。

const originalArray = [1, 2, 2, 'hello', 'world', 'hello', null, null, undefined, undefined, NaN, NaN];
const seenMap = new Map(); // 或者用一个普通对象 {}
const uniqueArrayByReduce = originalArray.reduce((accumulator, currentItem) => {
  // Map的key可以是任何类型,包括NaN,并且对NaN的判断是正确的
  if (!seenMap.has(currentItem)) {
    seenMap.set(currentItem, true);
    accumulator.push(currentItem);
  }
  return accumulator;
}, []);

console.log(uniqueArrayByReduce);
// 结果:[1, 2, 'hello', 'world', null, undefined, NaN]
// 这个方法能正确处理NaN,并且保持了元素的原始顺序。

这种方法在性能上通常比

filter
+
indexOf
要好,因为它利用了哈希表的O(1)查找特性。同时,它也灵活,可以扩展去处理更复杂的去重逻辑,比如对象数组去重。

哪种JS数组去重方法更高效?性能考量与选择

谈到效率,这事儿就得具体分析了。在我看来,大多数时候,

Set
方法是你的首选。为什么呢?因为它底层实现通常是哈希表(或者说,是高度优化的数据结构),查找和插入的平均时间复杂度都是O(1)。这意味着,即使你的数组有几万甚至几十万个元素,它的处理速度也相当快,总的时间复杂度接近O(N)——也就是线性时间,数组多大,就大致花多少时间。

相比之下,

filter()
结合
indexOf()
的效率就没那么理想了。
indexOf()
在数组中查找元素,最坏情况下需要遍历整个数组。所以,在
filter
的每次迭代里,你又进行了一次潜在的O(N)操作,整个过程下来,时间复杂度就变成了O(N^2)。当数组规模小的时候,这点差异你可能感觉不到,但一旦数组达到几千甚至上万个元素,你就会明显感觉到页面卡顿,甚至浏览器崩溃。这种方法,我个人是不太推荐在生产环境中大规模使用的,除非你明确知道数组规模很小且不会增长。

至于

reduce()
结合
Map
(或者普通对象)的方案,它的性能表现和
Set
非常接近,平均时间复杂度也是O(N)。因为它同样利用了哈希表的快速查找能力。在某些老旧环境不支持Set,或者你需要更精细控制(比如你想在去重时对元素做一些额外处理)的场景下,这个方法就显得尤为有用。它能很好地平衡性能和灵活性。

所以,如果问我哪种最快,我通常会毫不犹豫地指向

Set
。如果兼容性是首要考量,或者你需要处理的对象类型比较特殊,
reduce
Map
会是更稳妥的选择。

如何处理对象数组去重?深入探讨复杂数据类型的挑战

处理对象数组去重,这可就不是简单地用Set或者

indexOf
能搞定的了。因为JavaScript在比较对象时,默认是比较它们的内存地址(引用),而不是它们的内容。所以,即使两个对象长得一模一样,比如
{id: 1, name: 'A'}
和另一个
{id: 1, name: 'A'}
,它们在内存中是两个独立的个体,Set会认为它们是不同的。

先锋多用户商城系统
先锋多用户商城系统

修改自网上仿乐购商城,新增功能:1、数据库在线备份与导入功能,可以随时备份数据库,数据受损可以导入数据库,确保数据安全;2、增加组合商品概念,可以用于组配商品销售(比如外套有蓝色和红色,鞋子有40码和41码等),买一送一、组合销售(比如上衣+围巾+长裙做为一个套装商品)和加价购买等销售方式;3、按照商品重量和送货距离实时计算精确运费,并可在订单中予以显示,使运费金额实现实时动态准确显示、清晰明了;

下载

这时候,我们就需要定义一个“唯一性”的标准。通常,我们会基于对象的一个或多个属性来判断它们是否重复。比如,如果你的对象都有一个

id
属性,并且
id
是唯一的,那么我们就可以根据
id
来去重。

一个比较常见的做法是结合

filter()
Set
(或者
Map
)来记录已经“见过”的
id

const users = [
  { id: 1, name: 'Alice', age: 30 },
  { id: 2, name: 'Bob', age: 25 },
  { id: 1, name: 'Alice', age: 30 }, // 重复项
  { id: 3, name: 'Charlie', age: 35 },
  { id: 2, name: 'Bob', age: 26 } // id重复,但age不同
];

const seenIds = new Set();
const uniqueUsers = users.filter(user => {
  // 如果这个id之前没见过
  if (!seenIds.has(user.id)) {
    seenIds.add(user.id); // 把它标记为已见过
    return true; // 保留这个用户对象
  }
  return false; // 否则,丢弃
});

console.log(uniqueUsers);
// 结果:
// [
//   { id: 1, name: 'Alice', age: 30 },
//   { id: 2, name: 'Bob', age: 25 },
//   { id: 3, name: 'Charlie', age: 35 }
// ]

这种方式非常实用,因为它允许你自定义去重的逻辑。如果对象的唯一性需要由多个属性共同决定,比如

id
type
,你可以组合它们作为Set的key,例如
user.id + '-' + user.type

另一种稍微复杂但更通用的方法是利用

Map
来存储对象,以某个属性(或组合属性)作为key,对象本身作为value。这在需要保留“最新”或“最旧”的重复项时特别有用。

const users = [
  { id: 1, name: 'Alice', age: 30 },
  { id: 2, name: 'Bob', age: 25 },
  { id: 1, name: 'Alice', age: 30 },
  { id: 3, name: 'Charlie', age: 35 },
  { id: 2, name: 'Bob', age: 26 } // id重复,但age不同,我们希望保留最新的(即这个)
];

const uniqueMap = new Map();
users.forEach(user => {
  // 以id作为key,每次遇到重复id,新的对象会覆盖旧的,从而保留最后一个
  uniqueMap.set(user.id, user);
});

const uniqueUsersFromMap = Array.from(uniqueMap.values());
// 或者 [...uniqueMap.values()]

console.log(uniqueUsersFromMap);
// 结果:
// [
//   { id: 1, name: 'Alice', age: 30 },
//   { id: 2, name: 'Bob', age: 26 }, // 注意,这里保留了age为26的Bob
//   { id: 3, name: 'Charlie', age: 35 }
// ]

这种

Map
的方式,不仅能去重,还能灵活控制保留哪个重复项(比如最后一个出现的)。当然,如果你需要根据所有属性都完全一致才算重复,那么
JSON.stringify()
可以临时作为key,但要注意它的局限性:属性顺序不同会生成不同的字符串,且不能处理循环引用。

去重时如何保持原始顺序?兼顾效率与数据完整性

去重的时候,很多时候我们不仅要剔除重复项,还希望剩下的元素能保持它们在原始数组中的相对顺序。这其实是个挺重要的细节,因为数据的顺序有时会承载特定的业务含义。

幸运的是,我们前面提到的一些主流去重方法,在现代JavaScript环境中,大多都能很好地保持原始顺序。

  1. Set
    方法: 在ES6规范中,
    Set
    的迭代顺序并没有明确规定。但在实际的现代JavaScript引擎实现中(比如V8,也就是Chrome和Node.js用的),
    Set
    保持元素插入顺序的。所以,如果你用
    [...new Set(arr)]
    ,结果数组的顺序会和原始数组中第一次出现的元素顺序一致。这对我来说,是Set能成为首选去重方法的重要原因之一,因为它同时兼顾了简洁、高效和顺序。

  2. filter()
    结合
    indexOf()
    这个方法是天然保持顺序的。它的逻辑就是“如果当前元素是它第一次出现,我就保留它”。自然而然地,保留下来的元素,它们的相对顺序和原始数组是完全一致的。所以,如果你对性能要求不是特别极致,或者数组规模不大,这个方法在保持顺序方面是完全可靠的。

  3. reduce()
    结合
    Map
    (或对象):
    这种方式也能够完美地保持原始顺序。在
    reduce
    的迭代过程中,我们是按照原始数组的顺序一个接一个地处理元素的。当一个元素被判断为不重复并被
    push
    到累加器数组中时,它就是按照原始顺序被添加进去的。Map本身也保持了key的插入顺序,这进一步保证了最终结果的顺序性。

// 示例:使用reduce + Map 保持顺序
const disorderedArr = ['apple', 'banana', 'apple', 'orange', 'banana', 'grape'];
const uniqueAndOrdered = disorderedArr.reduce((acc, item) => {
    if (!seenMap.has(item)) { // 假设seenMap已定义
        seenMap.set(item, true);
        acc.push(item);
    }
    return acc;
}, []);
console.log(uniqueAndOrdered); // ['apple', 'banana', 'orange', 'grape'] - 顺序完全保留

所以,你不需要为了保持顺序而额外做太多工作。选择Set或者

reduce
+
Map
通常就能满足你的需求,它们在保持顺序的同时,也提供了不错的性能。只有在极少数需要兼容非常老旧的Set实现,或者对
filter
+
indexOf
的O(N^2)性能有严格限制时,你才需要特别去权衡。但就我个人经验而言,现代开发中,Set的方案几乎是万金油。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

420

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

536

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

313

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

77

2025.09.10

chrome什么意思
chrome什么意思

chrome是浏览器的意思,由Google开发的网络浏览器,它在2008年首次发布,并迅速成为全球最受欢迎的浏览器之一。本专题为大家提供chrome相关的文章、下载、课程内容,供大家免费下载体验。

863

2023.08.11

chrome无法加载插件怎么办
chrome无法加载插件怎么办

chrome无法加载插件可以通过检查插件是否已正确安装、禁用和启用插件、清除插件缓存、更新浏览器和插件、检查网络连接和尝试在隐身模式下加载插件方法解决。更多关于chrome相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

748

2023.11.06

es6新特性
es6新特性

es6新特性有:1、块级作用域变量;2、箭头函数;3、模板字符串;4、解构赋值;5、默认参数;6、 扩展运算符;7、 类和继承;8、Promise。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

106

2023.07.17

es6新特性有哪些
es6新特性有哪些

es6的新特性有:1、块级作用域;2、箭头函数;3、解构赋值;4、默认参数;5、扩展运算符;6、模板字符串;7、类和模块;8、迭代器和生成器;9、Promise对象;10、模块化导入和导出等等。本专题为大家提供es6新特性的相关的文章、下载、课程内容,供大家免费下载体验。

195

2023.08.04

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

54

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
如何进行WebSocket调试
如何进行WebSocket调试

共1课时 | 0.1万人学习

TypeScript全面解读课程
TypeScript全面解读课程

共26课时 | 5.1万人学习

前端工程化(ES6模块化和webpack打包)
前端工程化(ES6模块化和webpack打包)

共24课时 | 5.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号