JS数组合并去重技巧全解析
JS数组合并去重是前端开发中常见的需求。本文深入解析了JavaScript中数组合并去重的多种高效方法,**重点推荐使用Set结合展开运算符处理原始类型数组,简洁且性能卓越**。对于对象数组,则强调**基于唯一ID的Map去重法,避免了直接使用Set比较对象引用的局限性**。同时,文章也探讨了JSON.stringify序列化去重的适用场景和局限性。**性能优化方面,建议优先选择Set和Map方案,避免循环嵌套indexOf的低效方法**。掌握这些策略,能有效提升代码效率和可维护性,让数组操作更得心应手。
在JavaScript中合并两个数组并去除重复项,最简洁高效的方法是使用Set结合展开运算符。1. 对于原始类型值,直接使用[...new Set([...arr1, ...arr2])]即可完成合并与去重,Set会自动处理唯一性,包括将NaN视为单一值;2. 对于对象数组,因Set基于引用判断相等,需采用基于唯一ID的Map去重法:遍历合并后的数组,以对象id为键存入Map,后出现的同id对象会覆盖前者,最后转回数组;3. 也可尝试JSON.stringify序列化对象后用Set去重,但该方法受限于属性顺序、不可序列化值(如函数、undefined)及循环引用问题,适用场景有限;4. 性能上,Set和Map方案平均时间复杂度为O(n),远优于循环嵌套indexOf的O(n²),优化关键在于选择合适算法:原始类型用Set,对象类型优先基于唯一ID使用Map,并避免对无重复数据做冗余去重操作。该策略兼顾效率、可读性与实用性,是处理数组合并去重的推荐方案。
在JavaScript中合并两个数组并去除重复项,通常最简洁且高效的方法是利用Set
数据结构的特性,结合展开运算符(...
)来完成。这不仅能快速合并,还能自动处理原始类型值的去重。

合并和去重数组,我通常会倾向于使用Set
。它提供了一种非常直观且性能不错的方案。
function mergeAndDeduplicateArrays(arr1, arr2) { // 使用展开运算符合并数组,然后通过Set自动去重 const combinedSet = new Set([...arr1, ...arr2]); // 将Set转换回数组 return Array.from(combinedSet); // 或者更简洁地:return [...new Set([...arr1, ...arr2])]; } // 示例 const arrayA = [1, 2, 3, 4, 'a', 'b', null, undefined]; const arrayB = [3, 4, 5, 6, 'b', 'c', null, undefined, NaN]; // NaN会被视为一个,但两个NaN是不同的 const result = mergeAndDeduplicateArrays(arrayA, arrayB); console.log(result); // [1, 2, 3, 4, 'a', 'b', null, undefined, 5, 6, 'c', NaN]
这个方法对于数字、字符串、布尔值、null
、undefined
等原始类型表现完美。Set
内部会确保每个值都是唯一的,NaN
虽然不等于自身,但在Set
中会被视为唯一的一个值。

为什么Set是处理数组去重合并的优选方案?
在我看来,Set
之所以成为JavaScript中处理数组去重合并的首选,主要在于其设计哲学与实际效率。它就是为了存储不重复的值而生的,这与我们的需求完美契合。
首先,Set
的内置去重能力是其最大的优势。当你向一个Set
添加元素时,它会自动检查该元素是否已经存在。如果存在,就不会重复添加;如果不存在,则会加入。这种机制省去了我们手动编写复杂的循环和条件判断来检查重复项的麻烦,代码自然就更简洁、更易读。

其次,从性能角度看,Set
的查找和插入操作通常具有接近O(1)的平均时间复杂度(虽然在最坏情况下可能退化,但对于大多数实际场景,其性能表现都非常出色)。这意味着无论数组有多大,处理原始类型时,Set
都能提供相当快的去重速度。相比之下,如果采用传统的手动遍历加indexOf
或includes
去重,时间复杂度可能达到O(n^2),在大数据量面前会显得非常慢。
再者,Set
在处理NaN
和undefined
等特殊值时,行为也符合直觉。NaN
在JavaScript中是一个很特别的值,它不等于自身。但Set
在处理NaN
时,会将其视为一个唯一的元素。比如,你往Set
里加两个NaN
,最终Set
里只会有一个NaN
。这避免了一些潜在的陷阱。
总的来说,Set
提供了一种声明式、高效且语义清晰的方式来解决数组合并去重的问题,让开发者可以把精力放在更核心的业务逻辑上,而不是纠结于去重的实现细节。
合并去重时,如何处理数组中包含对象的情况?
这是一个常见的“坑”,也是Set
直接去重方案的局限性所在。当数组中包含对象时,Set
的默认去重机制可能不会按照你预期的那样工作。因为Set
使用严格相等(===
)来判断值的唯一性。对于对象来说,即使两个对象拥有完全相同的属性和值,只要它们在内存中是不同的引用,Set
就会认为它们是两个不同的对象。
比如:[{id: 1, name: 'A'}]
和 [{id: 1, name: 'A'}]
,这两个对象在Set
看来是不同的。
要解决这个问题,我们需要引入一些自定义的逻辑,核心思路是:定义“重复”的标准。通常,我们会基于对象的一个或多个唯一属性(比如id
、uuid
、key
等)来判断对象是否重复。
以下是几种处理策略:
基于唯一ID属性去重(推荐且常用) 这是最常见也最实用的方法。如果你的对象都有一个唯一的标识符(如
id
),你可以利用这个id
来判断对象是否重复。function mergeAndDeduplicateObjectsById(arr1, arr2) { const combined = [...arr1, ...arr2]; const uniqueMap = new Map(); // 使用Map来存储唯一的对象,key为id for (const item of combined) { if (item && item.id !== undefined) { // 确保对象存在且有id属性 // 如果Map中没有这个id,或者新对象的版本更“新”(根据业务逻辑决定) // 这里我们简单地以id为key存储,后来的同id对象会覆盖前面的 uniqueMap.set(item.id, item); } } return Array.from(uniqueMap.values()); } const arrObj1 = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}]; const arrObj2 = [{id: 2, name: 'Robert'}, {id: 3, name: 'Charlie'}]; const resultObj = mergeAndDeduplicateObjectsById(arrObj1, arrObj2); // 结果可能取决于你希望保留哪个同ID对象,这里是后者覆盖前者 console.log(resultObj); // 预期输出:[{id: 1, name: 'Alice'}, {id: 2, name: 'Robert'}, {id: 3, name: 'Charlie'}]
这种方法高效,因为
Map
的set
和get
操作也是接近O(1)的平均时间复杂度。将对象转换为字符串去重(适用于简单、可序列化对象) 如果你确定对象的所有属性都是可JSON序列化的,并且属性顺序不影响唯一性判断,可以考虑将对象
JSON.stringify
成字符串,然后用Set
去重字符串。function mergeAndDeduplicateObjectsByStringify(arr1, arr2) { const combined = [...arr1, ...arr2]; const uniqueStrings = new Set(); const uniqueObjects = []; for (const item of combined) { try { const itemString = JSON.stringify(item); if (!uniqueStrings.has(itemString)) { uniqueStrings.add(itemString); uniqueObjects.push(item); } } catch (e) { console.warn("无法序列化对象,跳过:", item, e); // 处理不可序列化的对象,比如包含循环引用 } } return uniqueObjects; } const arrObj3 = [{a:1, b:2}, {b:2, a:1}, {c:3}]; // 注意:JSON.stringify对属性顺序敏感 const arrObj4 = [{a:1, b:2}, {d:4}]; // 如果属性顺序不同,即使内容一样,也会被认为是不同的 // [{a:1, b:2}] 和 [{b:2, a:1}] 可能会被认为是两个不同的字符串 const resultStr = mergeAndDeduplicateObjectsByStringify(arrObj3, arrObj4); console.log(resultStr);
局限性:
JSON.stringify
对属性的顺序是敏感的,{a:1, b:2}
和{b:2, a:1}
会被序列化成不同的字符串。此外,它不能处理循环引用、函数、undefined
等非JSON值。因此,这种方法只适用于结构非常简单且可控的对象。
选择哪种方法,取决于你的具体业务场景和对象结构。通常,基于唯一ID属性的去重是最健壮和推荐的方案。
合并去重操作对性能有什么影响?我们应该如何优化?
合并和去重操作的性能影响,很大程度上取决于你选择的算法、数组的大小以及数组中元素的类型。理解这些影响,能帮助我们做出更明智的优化决策。
性能影响分析:
时间复杂度:
Set
方法(针对原始类型): 通常是O(N),其中N是合并后数组的总元素数。这是因为Set
的插入和查找操作平均是常数时间。这是最理想的情况。- 基于
Map
的ID去重(针对对象): 同样接近O(N)。通过对象的唯一ID作为Map
的键,查找和插入效率很高。 - 循环
+
indexOf
/includes
: 这是效率最低的常见方法,时间复杂度为O(N^2)。每添加一个元素,都需要遍历已处理的部分来检查是否重复。当N很大时,性能会急剧下降。 JSON.stringify
后Set
去重: 涉及到字符串化(O(K) K为对象属性数)和字符串的哈希计算(可能与字符串长度有关),整体性能介于O(N)和O(N^2)之间,取决于对象的复杂度和数量。
内存占用:
Set
和Map
都需要额外的内存来存储其内部数据结构。对于非常大的数组,这可能会是一个考虑因素,但通常在现代浏览器环境中不是瓶颈。JSON.stringify
会创建新的字符串副本,也增加了临时内存消耗。
优化策略:
选择正确的算法:
- 优先使用
Set
和展开运算符来处理包含原始类型(数字、字符串、布尔值等)的数组去重。这是最直接、最高效的方案。 - 对于包含对象的数组,务必使用基于唯一标识符(如
id
)的Map
或自定义遍历逻辑去重。 避免直接将对象放入Set
,因为它只会比较引用。
- 优先使用
避免不必要的去重:
- 如果数据来源本身就保证了唯一性,或者你只关心合并而不关心去重,那就不要进行去重操作。
- 在某些场景下,如果数据量非常大且频繁进行合并去重,可以考虑增量更新。只对新增或变化的部分进行去重,而不是每次都处理整个数据集。
考虑数据结构的设计:
- 在后端或数据源层面就保证数据的唯一性,减少前端去重的负担。例如,数据库查询结果就避免重复。
- 如果对象去重是常见需求,确保你的对象有一个明确且易于访问的唯一ID。
性能瓶颈分析:
- 对于性能敏感的应用,如果发现合并去重是瓶颈,可以使用浏览器开发者工具(如Chrome DevTools的Performance面板)进行剖析,找出具体是哪一步耗时最多。有时候,瓶颈可能不在去重本身,而在前置的数据处理或后置的DOM操作。
总的来说,对于JS数组的合并去重,我的经验是:对于原始类型,Set
就是王者,直接用就好;对于对象,关键在于你如何定义“重复”,然后用Map
或自定义遍历来高效实现这个定义。过早的微优化往往是浪费时间,但选择正确的、高效率的算法是基础。
今天关于《JS数组合并去重技巧全解析》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于的内容请关注golang学习网公众号!

- 上一篇
- Linux容器部署指南:Docker与K8s实战教程

- 下一篇
- HTML表单风险评分评估方法
-
- 文章 · 前端 | 4分钟前 |
- 响应式设计5大HTML适配方法
- 351浏览 收藏
-
- 文章 · 前端 | 5分钟前 |
- Promise.resolve的使用与场景解析
- 388浏览 收藏
-
- 文章 · 前端 | 7分钟前 |
- JavaScript异步错误处理详解
- 326浏览 收藏
-
- 文章 · 前端 | 9分钟前 |
- Promise.catch错误捕获实用技巧分享
- 133浏览 收藏
-
- 文章 · 前端 | 9分钟前 |
- 事件循环如何优化I/O密集型应用性能?
- 203浏览 收藏
-
- 文章 · 前端 | 18分钟前 |
- React数组映射传props的正确方法
- 415浏览 收藏
-
- 文章 · 前端 | 21分钟前 |
- Material-UI图标加载失败原因及解决办法
- 228浏览 收藏
-
- 文章 · 前端 | 21分钟前 |
- JS使用sumBy计算对象数组总和方法
- 215浏览 收藏
-
- 文章 · 前端 | 22分钟前 | CSS JavaScript HTML锁定样式 pointer-events aria-disabled
- HTML锁定样式实现方法及伪类应用
- 103浏览 收藏
-
- 文章 · 前端 | 24分钟前 |
- JavaScriptWebWorkers入门指南
- 191浏览 收藏
-
- 文章 · 前端 | 25分钟前 |
- PX与EM区别:CSS单位对比解析
- 281浏览 收藏
-
- 文章 · 前端 | 27分钟前 |
- 事件循环优化UI流畅更新技巧
- 283浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 511次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 498次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 千音漫语
- 千音漫语,北京熠声科技倾力打造的智能声音创作助手,提供AI配音、音视频翻译、语音识别、声音克隆等强大功能,助力有声书制作、视频创作、教育培训等领域,官网:https://qianyin123.com
- 164次使用
-
- MiniWork
- MiniWork是一款智能高效的AI工具平台,专为提升工作与学习效率而设计。整合文本处理、图像生成、营销策划及运营管理等多元AI工具,提供精准智能解决方案,让复杂工作简单高效。
- 155次使用
-
- NoCode
- NoCode (nocode.cn)是领先的无代码开发平台,通过拖放、AI对话等简单操作,助您快速创建各类应用、网站与管理系统。无需编程知识,轻松实现个人生活、商业经营、企业管理多场景需求,大幅降低开发门槛,高效低成本。
- 166次使用
-
- 达医智影
- 达医智影,阿里巴巴达摩院医疗AI创新力作。全球率先利用平扫CT实现“一扫多筛”,仅一次CT扫描即可高效识别多种癌症、急症及慢病,为疾病早期发现提供智能、精准的AI影像早筛解决方案。
- 166次使用
-
- 智慧芽Eureka
- 智慧芽Eureka,专为技术创新打造的AI Agent平台。深度理解专利、研发、生物医药、材料、科创等复杂场景,通过专家级AI Agent精准执行任务,智能化工作流解放70%生产力,让您专注核心创新。
- 174次使用
-
- 优化用户界面体验的秘密武器:CSS开发项目经验大揭秘
- 2023-11-03 501浏览
-
- 使用微信小程序实现图片轮播特效
- 2023-11-21 501浏览
-
- 解析sessionStorage的存储能力与限制
- 2024-01-11 501浏览
-
- 探索冒泡活动对于团队合作的推动力
- 2024-01-13 501浏览
-
- UI设计中为何选择绝对定位的智慧之道
- 2024-02-03 501浏览