js 数组去重是 JavaScript 开发高频基础操作,核心目标就是把数组里面重复的元素剔除,只保留一份,主流方案包含 Set 去重 indexOf 去重 filter 结合 includes reduce 去重 双重 for 循环去重,不同方法在兼容性 性能 数据类型识别上有差别,需要重点区分基本类型和对象类型,Set 方法写法最简洁,是现代 ES6 之后首选方案,但是它使用严格相等判断,也就是 SameValueZero 规则,NaN 可以识别重复,这点和 indexOf 不一样,indexOf 无法识别 NaN,对象类型不管哪种基础去重方法,默认都是按引用地址判断,两个内容一模一样但是不同引用的对象,会判定成不重复,这个是面试和开发最容易踩坑的点,选择方法的时候,要根据浏览器兼容要求,数组元素类型,是否保留原数组顺序这几个条件来挑选,老项目如果需要兼容非常老旧浏览器,就不能使用 Set,要选择传统循环方案。
常用方法对照表
| 方法名称 | 核心原理 | ES 版本 | 是否识别 NaN | 优点 | 缺点 |
|---|---|---|---|---|---|
| Set 展开运算符 | 利用 Set 集合元素唯一特性 | ES6 | 可以识别 | 代码最短,性能优秀 | 老 IE 不支持,对象按引用判断 |
| filter + indexOf | 判断当前元素第一次出现索引是否等于当前下标 | ES5 | 不能识别 NaN | ES5 兼容好 | 数组很大的时候性能一般,NaN 失效 |
| filter + includes | includes 内部 SameValueZero 判断 | ES7 | 可以识别 | 写法简洁,支持 NaN | 低版本浏览器不支持 includes |
| reduce 累加器 | 遍历数组,判断累加数组是否已经存在元素 | ES5 | 默认不能识别 NaN | 灵活,可自定义判断逻辑 | 代码相对长 |
| 双重 for 循环 | 两层循环对比元素,重复就 splice 删除 | ES3 | 不能识别 NaN | 兼容性最强 | 时间复杂度高,大数据数组很慢 |
方案一 Set 展开运算符 现代首选
代码核心写法
const arr = [1,2,2,3,NaN,NaN]
const newArr = [...new Set (arr)]
原理:new Set 会自动剔除重复值,之后展开运算符把 Set 转回数组,保持原始顺序,支持 NaN 去重,适合数字 字符串 布尔 这些基本类型数组。
方案二 filter + indexOf 兼容 ES5 写法
代码核心写法
const newArr = arr.filter ((item,index)=> arr.indexOf (item) === index)
原理:遍历每一项,如果该项第一次出现的索引,等于当前遍历下标,代表是第一次出现,保留,如果索引不一样,说明前面已经出现过,属于重复项过滤掉,缺点就是 indexOf 对 NaN 永远返回负一,所以 NaN 无法去重。
方案三 filter + includes
代码核心写法
const newArr = arr.filter ((item,index)=> !newArr.includes (item))
原理:维护一个结果数组,每遍历一项,用 includes 判断结果数组里面是否已经存在该元素,不存在就 push 进去,includes 可以正确识别 NaN。
方案四 reduce 实现去重
代码核心写法
const newArr = arr.reduce ((prev,current)=>{
if (!prev.includes (current)) prev.push (current)
return prev
},[])
原理:reduce 初始化空数组作为累加器,遍历原数组,判断累加器里面有没有当前元素,没有就追加,这个方案优势是可以自定义判断规则,后续改造方便。
方案五 双重 for 循环 极致兼容
原理:外层循环取出元素,内层循环对比后面所有元素,如果相等,就用 splice 删除重复项,优点是可以运行在非常古老的浏览器,缺点双重循环时间复杂度是 O (n 平方),数组数据量大的时候性能很差,并且同样无法识别 NaN。
对象数组去重补充重点
如果数组里面是对象,比如用户列表,不能直接用 Set 去重,因为对象比较是引用比较,需要自定义去重规则,一般根据对象里面唯一字段,比如 id 来去重,使用 reduce 或者 Map,以 id 作为键,实现根据属性值去重,这个属于进阶场景。
选型操作步骤
第一步确认数组元素类型,是基本类型还是对象数组,第二步确认项目浏览器兼容要求,是否需要支持老旧浏览器,第三步确认是否需要对 NaN 进行去重,第四步根据需求挑选对应的 js 数组去重方法,第五步测试结果,确认顺序和去重结果是否符合预期,大数据量优先选择 Set 或者 Map 方案,性能更优。
综合来说现代项目优先推荐 Set 展开运算符实现 js 数组去重,写法简洁性能好,如果需要 ES5 兼容就使用 filter 加 indexOf,对象数组需要自定义字段去重,不能直接套用基本类型的去重代码。
【常见问题】
问题 1 js 数组去重 Set 方法为什么可以去除 NaN
回答 1 js 数组去重 Set 内部使用 SameValueZero 比较规则,可以正确识别 NaN 相等,传统 indexOf 方法不能识别 NaN,所以 indexOf 实现的 js 数组去重无法去掉重复 NaN。
问题 2 js 数组去重的时候两个内容一样的对象为什么没有去掉
回答 1 js 数组去重基础方法是按照引用地址判断对象,两个对象即使内容完全相同,只要是不同引用,就会判定不重复,对象数组去重需要改成根据 id 这类唯一属性自定义逻辑。
问题 3 js 数组去重双重 for 循环适合大数据数组吗
回答 1 js 数组去重双重 for 循环时间复杂度高,不适合大数据数组,大数据场景优先选择 Set 或者 Map 实现 js 数组去重,运行性能更好。
【免责申明】
本文基于 JavaScript 标准语法整理,不同 JS 引擎对方法实现细节存在微小差异,仅作为前端学习参考,生产环境需要根据目标浏览器做兼容性测试。


