【前端+数组去重】前端JavaScript 数组去重完全指南:9种方案+实战+面试

📅 2026/7/15 10:38:42
【前端+数组去重】前端JavaScript 数组去重完全指南:9种方案+实战+面试
JavaScript 数组去重完全指南:9种方案+实战+面试📖 目录引言方法一:利用对象属性键唯一性方法二:使用Set(最简洁推荐)方法三:filter+indexOf方法四:双重循环(splice删除)方法五:排序后去重方法六:使用Map对象方法七:reduce+includes方法八:使用第三方库(如 Lodash)方法九:对象数组去重实战方案 A:基于Map按属性去重(推荐)方案 B:基于reduce+findIndex按属性去重方案 C:使用Set+JSON.stringify处理简单对象性能实测对比性能对比与总结对象数组去重方案专项对比场景决策树(方案选择流程图)面试高频追问常见错误与陷阱参考资料与延伸阅读引言你在面试中是否曾被「数组去重」问倒?在处理接口返回的大量对象列表时,是否为「按 id 去重」而手忙脚乱?数组去重看似简单,背后却牵涉到类型判断、性能优化、ES6+ 特性运用等多层考量。本文从最朴素的实现出发,逐一拆解 9 种原生 JavaScript 方案——从经典的对象属性、现代 Set/Map,到对象数组实战与第三方库,并配有时间复杂度横向对比与实测数据,帮你把去重吃透,面试、生产一次通关!文章摘要:本文面向前端开发者与面试候选人,系统梳理 JavaScript 数组去重的 9 种原生方案与 3 种对象数组实战策略。从经典对象属性到现代 Set/Map,从 Lodash 工具库到性能实测对比,覆盖基础类型与复杂对象的全方位去重场景。核心结论:基础类型首选Set(O(n),一行搞定);对象数组按属性去重推荐Map(O(n),生产首选);大数据量务必避开 O(n²) 方案。文末附有场景决策流程图与性能实测代码,助你面试通关、业务提效。TL;DR(核心结论)场景推荐方案一句话理由基础类型去重[...new Set(arr)]一行搞定,O(n),正确处理 NaN对象数组按属性去重Map按 key 去重(O(n))生产环境首选;小数据可用reduce + findIndex兼容性与面试filter + indexOf/splice双循环仅适用小规模或原理演示,大数据避用工程化场景_.uniqBy(arr, 'id')(Lodash)已引入时最省心;否则优先原生 Set/Map方法一:利用对象属性键唯一性这是最经典的手写实现之一,核心思路是:使用对象的属性键必须唯一的特性,将数组元素作为 key 存入对象,从而过滤掉重复值。functionuniqueByObject(arr){constobj={};constresult=[];for(leti=0;iarr.length;i++){constitem=arr[i];if(!obj.hasOwnProperty(item)){result.push(item);obj[item]=true;}}returnresult;}constlist=[1,2,2,'a','a',{},{}];console.log(uniqueByObject(list));// [1, 2, 'a', {}, {}]优点:时间复杂度 O(n),遍历一次即可完成。逻辑简单,容易理解。缺点:因为对象属性只能是字符串或 Symbol,所以1和'1'会被视为同一个 key,可能造成误去重。需要额外维护一个对象和一个结果数组,内存占用较高。无法正确定性处理undefined、null等特殊值(它们会被转为字符串"undefined"、"null")。注意:可以通过Map或Object.create(null)解决部分问题,但方案一的本意是利用简单对象,因此通常不推荐用于包含多种类型的数组。方法二:使用Set(最简洁推荐)ES6 引入的Set数据结构天然不允许重复值,是当今去重的首选方式。functionuniqueBySet(arr){return[...newSet(arr)];// 或者 Array.from(new Set(arr));}constlist=[1,2,2,NaN,NaN,{},{}];console.log(uniqueBySet(list));// [1, 2, NaN, {}, {}]优点:代码极度简洁,一行搞定。内部实现高效,时间复杂度 O(n)。能够正确处理NaN(两个NaN在 Set 中被视为相等)。支持所有基本类型和引用类型(但引用类型只是比地址,不同对象仍视为不重复)。缺点:需要 ES6 环境支持,不考虑低版本 IE 时已无此问题。对于引用类型的去重,仍需配合其他逻辑(如根据 id 去重对象数组)。结论:在现代前端项目中,若无特殊要求,首选Set方式。方法三:filter+indexOf利用数组indexOf只返回元素第一次出现位置的特点,结合filter过滤掉后续重复项。functionuniqueByFilter(arr){returnarr.filter((item,index)={returnarr.indexOf(item)===index;});}constlist=[1,2,2,'a','a'];console.log(uniqueByFilter(list));// [1, 2, 'a']优点:代码简洁,函数式风格。可以在过滤过程中直接对元素进行额外操作(例如只保留符合某一条件的去重值)。缺点:每次遍历都需调用indexOf,时间复杂度达到 O(n²),在数据量大时性能急速下降。indexOf使用严格相等,无法正确处理NaN(indexOf(NaN)返回 -1,导致所有NaN都会被保留)。方法四:双重循环(splice删除)这是最原始的思路:依次用当前元素与后面所有元素比较,重复则删除。functionuniqueBySplice(arr){constarray=[...arr];// 避免修改原数组for(leti=0;iarray.length;i++){for(letj=i+1;jarray.length;j++){if(array[i]===array[j]){array.splice(j,1);j--;// 删除后索引回退}}}returnarray;}constlist=[1,2,2,3,3,3];console.log(uniqueBySplice(list));// [1, 2, 3]优点:不依赖任何 API,兼容性极好。能够直观理解去重过程。缺点:双重循环叠加splice删除,时间复杂度 O(n³)(循环 O(n²) +splice本身 O(n)),性能极差。无法处理NaN(NaN !== NaN),比较将永远失败。修改原数组(需拷贝避免副作用)。此方法仅用于理解原理,生产环境绝不使用。方法五:排序后去重先对数组排序,使相同元素相邻,再通过前后比较去重。functionuniqueBySort(arr){constsorted=[...arr].sort();constresult=[];letprev;for(leti=0;isorted.length;i++){if(!i||prev!==sorted[i]){result.push(sorted[i]);}prev=sorted[i];}returnresult;}constlist=[2,1,2,'a','a'];console.log(uniqueBySort(list));// [1, 2, 'a']优点:时间复杂度 O(n log n),优于双重循环方法。逻辑清晰。缺点:会改变原数组的顺序(可通过传回原位置处理,但增加复杂度)。仍需注意NaN问题(NaN !== NaN比较会出错,可在比较时额外判断isNaN)。不适合含多种数据类型的数组,sort默认将元素转为字符串比较,导致[1, '1']可能相邻但类型不同。此方法在需要保持原始顺序的场景下不推荐。方法六:使用Map对象ES6 的Map可以用任意类型作为键,比普通对象更强大。functionuniqueByMap(arr){constmap=newMap();constresult=[];for(leti=0;iarr.length;i++){constitem=arr[i];if(!map.has(item)){map.set(item,true);result.push(item);}}returnresult;}constlist=[1,'1',1,'a','a'];console.log(uniqueByMap(list));// [1, '1', 'a']优点:时间复杂度 O(n),性能优秀。可区分1和'1',不会误去重。支持NaN作为键的正确比较。缺点:需要 ES6 环境。额外内存开销(Map + 结果数组),但通常可接受。此方法是方法一的最佳替代,适用于需要区分类型的场景。方法七:reduce+includesfunctionuniqueByReduce(arr){returnarr.reduce((acc,cur)={if(!acc.includes(cur)){acc.push(cur);}returnacc;},[]);}constlist=[1,2,2,3];console.log(uniqueByReduce(list));// [1, 2, 3]优点:函数式风格,代码紧凑。逻辑简单。缺点:每次循环调用includes,导致 O(n²) 时间复杂度。includes基于 SameValueZero 算法,可正确处理NaN(这点优于indexOf)。性能不理想,数据量大时明显变慢。方法八:使用第三方库(如 Lodash)lodash的_.uniq方法可以直接去重,还支持_.uniqBy和_.uniqWith处理复杂场景。// 需引入 lodashimport_from'lodash';constlist=[1,2,2,3];console.log(_.uniq(list));// [1, 2, 3]// 按对象属性去重constusers=[{id:1,name:'Alice'},{