JavaScript数组去重实现方法详解
数组去重是JavaScript开发中非常常见的需求,比如处理用户多次提交的重复数据、过滤接口返回的冗余结果等场景都会用到。本文将介绍几种常用的数组去重方法,分析它们的实现逻辑和适用场景,帮助大家根据实际需求选择合适的方案。
一、使用Set结构去重
ES6引入的Set数据结构天生具有去重特性,它只存储唯一值,重复的元素会被自动过滤,这是目前最简洁的去重方式。
// 使用Set实现数组去重
function uniqueBySet(arr) {
// 将数组转为Set,自动去重,再转回数组
return Array.from(new Set(arr));
}
// 测试示例
const testArr1 = [1, 2, 2, 3, 3, 3, 'a', 'a', true, true];
console.log(uniqueBySet(testArr1)); // 输出:[1, 2, 3, 'a', true]这种方法的优点是代码量少、逻辑清晰,Set使用的是SameValueZero算法比较值,能正确处理NaN的去重(因为Set认为NaN等于NaN,而常规全等比较认为NaN不等于NaN),同时可以处理字符串、布尔值等基本类型的去重。不过它无法处理对象类型的去重,因为对象的引用地址不同,即使内容相同也会被认为是不同的值。
二、使用双重循环+索引判断去重
这是ES6之前比较常用的传统去重方式,通过两层循环遍历数组,判断当前元素是否已经存在于新数组中。
// 双重循环实现数组去重
function uniqueByLoop(arr) {
const result = [];
for (let i = 0; i < arr.length; i++) {
// 判断当前元素是否已经在结果数组中
let isExist = false;
for (let j = 0; j < result.length; j++) {
if (arr[i] === result[j]) {
isExist = true;
break;
}
}
// 不存在则加入结果数组
if (!isExist) {
result.push(arr[i]);
}
}
return result;
}
// 测试示例
const testArr2 = [1, 2, 2, 3, 3, 3, 'a', 'a', NaN, NaN];
console.log(uniqueByLoop(testArr2)); // 输出:[1, 2, 3, 'a', NaN, NaN]这种方法的兼容性很好,支持所有浏览器环境,但缺点也很明显:时间复杂度是O(n²),数组长度较大时性能较差;同时因为使用全等比较,无法处理NaN的去重,上述示例中两个NaN都会被保留。
三、使用indexOf方法去重
利用数组的indexOf方法,判断当前元素在结果数组中的索引,如果不存在(返回-1)就加入结果数组,相比双重循环代码更简洁。
// 使用indexOf实现数组去重
function uniqueByIndexOf(arr) {
const result = [];
for (let i = 0; i < arr.length; i++) {
// indexOf返回当前元素在结果数组中的索引,不存在则返回-1
if (result.indexOf(arr[i]) === -1) {
result.push(arr[i]);
}
}
return result;
}
// 测试示例
const testArr3 = [1, 2, 2, 3, '3', 3, 'a', 'a'];
console.log(uniqueByIndexOf(testArr3)); // 输出:[1, 2, 3, '3', 'a']这种方法的逻辑比双重循环更清晰,同样存在无法处理NaN的问题,因为indexOf也无法找到NaN的索引,所以如果数组中有NaN,也会被重复保留。另外indexOf使用的是严格相等比较,不会做类型转换,所以数字3和字符串'3'会被认为是不同的值。
四、使用includes方法去重
ES6新增的includes方法可以直接判断数组是否包含某个元素,返回布尔值,比indexOf的判断更直观。
// 使用includes实现数组去重
function uniqueByIncludes(arr) {
const result = [];
for (let i = 0; i < arr.length; i++) {
// includes判断结果数组是否包含当前元素
if (!result.includes(arr[i])) {
result.push(arr[i]);
}
}
return result;
}
// 测试示例
const testArr4 = [1, 2, 2, 3, NaN, NaN, 'b', 'b'];
console.log(uniqueByIncludes(testArr4)); // 输出:[1, 2, 3, NaN, 'b']includes方法可以正确判断NaN是否存在,所以这种方法能处理NaN的去重,这是它比indexOf更优的地方。不过它的时间复杂度依然是O(n²),性能和indexOf方法接近。
五、使用对象哈希去重
利用对象的键名唯一特性,将数组元素作为对象的键,遍历数组时判断键是否存在,实现O(n)时间复杂度的去重。
// 使用对象哈希实现数组去重
function uniqueByHash(arr) {
const hash = {};
const result = [];
for (let i = 0; i < arr.length; i++) {
// 将元素转为字符串作为键,避免对象键名自动转字符串的问题
const key = typeof arr[i] + JSON.stringify(arr[i]);
if (!hash[key]) {
hash[key] = true;
result.push(arr[i]);
}
}
return result;
}
// 测试示例
const testArr5 = [1, '1', 2, 2, {name: 'test'}, {name: 'test'}, true, true];
console.log(uniqueByHash(testArr5)); // 输出:[1, '1', 2, {name: 'test'}, true]这种方法的性能比循环类方法更好,时间复杂度是O(n),适合处理大数组。不过需要注意键名的处理:对象的键名会自动转为字符串,所以数字1和字符串'1'如果不做区分会被认为是同一个键,示例中通过拼接类型的方式来避免这个问题。另外如果数组中有对象,相同内容的对象依然会被认为是不同的,因为JSON.stringify不同引用地址的对象结果虽然相同,但示例中用typeof+JSON.stringify的方式可以把内容相同的对象去重,不过要注意如果对象中有函数、undefined等特殊值,JSON.stringify会处理成null或者忽略,需要根据实际场景调整。
六、方法对比与选择建议
| 去重方法 | 时间复杂度 | 是否支持NaN去重 | 兼容性 | 适用场景 |
|---|---|---|---|---|
| Set结构去重 | O(n) | 是 | ES6及以上环境 | 现代浏览器环境,处理基本类型数组去重,追求代码简洁 |
| 双重循环去重 | O(n²) | 否 | 所有环境 | 需要兼容低版本浏览器,数组长度较小的场景 |
| indexOf去重 | O(n²) | 否 | 所有环境 | 兼容低版本浏览器,不需要处理NaN的场景 |
| includes去重 | O(n²) | 是 | ES6及以上环境 | 现代环境,需要处理NaN,不需要极致性能的场景 |
| 对象哈希去重 | O(n) | 是 | 所有环境 | 大数组去重,需要高性能的场景,注意特殊值的处理 |
实际开发中,如果不需要兼容低版本浏览器,优先选择Set结构去重,代码简洁且性能不错;如果需要兼容IE等老环境,数组长度较小可以用indexOf或者双重循环,数组长度较大建议使用对象哈希的方式。如果数组中包含对象类型,需要根据对象的去重规则调整对应的方法,比如按对象某个属性去重,可以在现有方法基础上增加属性判断逻辑。
JavaScript数组去重Set去重indexOfincludes哈希表去重 本作品最后修改时间:2026-05-22 14:01:26