JS数据去重的7种实用方法总结(附完整代码与原理)
作者:前端开发小透明
前言
在 JavaScript 开发中,“数据去重” 是高频需求 —— 比如处理接口返回的重复列表、用户输入的重复值等。不同场景下,适合的去重方法不同,有的追求简洁,有的追求性能,有的需要兼容复杂数据类型。今天就整理 7 种常用的去重方法,每种都附带代码和核心原理,帮你轻松应对各种去重场景。
一、基础方法:利用 Set 去重(最简洁)
核心原理:ES6 新增的 Set 集合本身具有 “值唯一” 的特性,能自动忽略重复值,再通过扩展运算符(...)或 Array.from() 转回数组即可。
代码实现
// 待去重数组(包含数字、字符串、重复值) const arr = [1, 2, 2, "3", "3", 4, 4, 5]; // 方法 1:扩展运算符转数组 const uniqueArr1 = [...new Set(arr)]; // 方法 2:Array.from() 转数组(兼容不支持扩展运算符的环境) const uniqueArr2 = Array.from(new Set(arr)); console.log(uniqueArr1); // 输出:[1, 2, "3", 4, 5] console.log(uniqueArr2); // 输出:[1, 2, "3", 4, 5]
优缺点
优点:代码最简洁,一行搞定;性能优秀(底层是哈希表实现,时间复杂度 O (n));
缺点:无法区分 “值相同但类型不同” 的数据(比如
1和"1"会被视为不同值,这其实是合理的,若需强制相同需额外处理);不支持对象 / 数组等引用类型的去重(引用不同会被视为不同值)。
适用场景
处理简单数据类型(数字、字符串、布尔值)的数组,追求代码简洁和性能。
二、传统方法:利用 indexOf/includes 去重
核心原理:创建一个新数组,遍历原数组,判断当前元素是否在新数组中(用 indexOf 或 includes),不在则加入新数组,最终得到去重后的数组。
代码实现(indexOf 版)
const arr = [1, 2, 2, 3, 3, 3];
const uniqueArr = [];
for (let i = 0; i < arr.length; i++) {
// indexOf 返回 -1 表示元素不在新数组中
if (uniqueArr.indexOf(arr[i]) === -1) {
uniqueArr.push(arr[i]);
}
}
console.log(uniqueArr); // 输出:[1, 2, 3]代码实现(includes 版,更直观)
const arr = [1, 2, 2, 3, 3, 3];
const uniqueArr = [];
for (let item of arr) {
// includes 直接返回布尔值,判断元素是否存在
if (!uniqueArr.includes(item)) {
uniqueArr.push(item);
}
}
console.log(uniqueArr); // 输出:[1, 2, 3]优缺点
优点:兼容性好(支持 ES5 及以上,无需担心环境问题);逻辑直观,容易理解;
缺点:性能较差(
indexOf/includes每次都会遍历新数组,时间复杂度 O (n²),数据量大时会卡顿);同样不支持引用类型去重。
适用场景
处理小规模的简单类型数组,或需要兼容低版本浏览器的场景。
三、优化性能:利用对象键名唯一性去重
核心原理:对象的键名(key)具有唯一性(不能重复),遍历原数组时,把元素作为对象的键名存储,同时判断键名是否已存在,不存在则加入新数组。
代码实现
const arr = [1, 2, 2, "2", 3, 3];
const uniqueArr = [];
const tempObj = {}; // 临时对象,用于存储已存在的元素
for (let item of arr) {
// 把元素转为字符串作为键名(避免 1 和 "1" 被视为不同键)
const key = typeof item + item;
if (!tempObj[key]) {
tempObj[key] = true; // 标记为已存在
uniqueArr.push(item);
}
}
console.log(uniqueArr); // 输出:[1, 2, "2", 3]关键细节
为什么要加
typeof item?比如1(数字)和"1"(字符串),直接用item当键名会都是"1",导致误判;加typeof后,键名会变成"number1"和"string1",能正确区分。若想强制把
1和"1"视为相同值,可去掉typeof,直接用const key = item。
优缺点
优点:性能优秀(对象的键名查询是哈希表操作,时间复杂度 O (n));支持区分 “值相同类型不同” 的场景;
缺点:需额外处理键名类型(避免误判);不支持引用类型去重(对象作为键名会被转为
[object Object],导致所有对象都被视为相同)。
适用场景
处理大规模的简单类型数组,追求高性能,且需要区分值类型。
四、进阶方法:利用 filter + indexOf 去重
核心原理:filter 方法会筛选出满足条件的元素,结合 indexOf 判断 “当前元素在原数组中的第一次出现位置是否等于当前索引”—— 如果等于,说明是第一次出现,保留;否则是重复值,过滤掉。
代码实现
const arr = [1, 2, 2, 3, 3, 4];
// filter 回调函数:返回 true 则保留元素
const uniqueArr = arr.filter((item, index) => {
// indexOf 会返回元素在数组中第一次出现的索引
return arr.indexOf(item) === index;
});
console.log(uniqueArr); // 输出:[1, 2, 3, 4]优缺点
优点:代码简洁(一行搞定);逻辑优雅,适合函数式编程风格;
缺点:性能差(
filter遍历一次,indexOf每次又遍历一次,时间复杂度 O (n²));不支持引用类型去重。
适用场景
处理小规模简单类型数组,追求函数式编程风格,不关心极致性能。
五、ES6 进阶:利用 Map 去重
核心原理:Map 的键(key)可以是任意类型,且具有唯一性。遍历原数组时,用 Map.has() 判断元素是否已存在,不存在则用 Map.set() 存储,并加入新数组。
代码实现
const arr = [1, 2, 2, "3", "3", 4];
const uniqueArr = [];
const tempMap = new Map();
for (let item of arr) {
if (!tempMap.has(item)) { // 判断元素是否已在 Map 中
tempMap.set(item, true); // 存储元素(值随便设,只要有标记即可)
uniqueArr.push(item);
}
}
console.log(uniqueArr); // 输出:[1, 2, "3", 4]优缺点
优点:性能优秀(时间复杂度 O (n));支持更多键类型(比如
NaN,Set也支持,但indexOf不支持NaN的判断);缺点:代码比
Set稍繁琐;不支持引用类型去重(引用不同会被视为不同键)。
适用场景
处理简单类型数组,或需要存储 NaN 等特殊值的场景(indexOf 无法判断 NaN,但 Map.has(NaN) 可以)。
六、复杂场景:引用类型(对象 / 数组)去重
前面的方法都无法处理对象 / 数组等引用类型(比如 { id: 1 } 和 { id: 1 } 会被视为不同值,因为引用不同)。此时需要通过 “比较内容” 来实现去重,常用方法是 JSON.stringify 转字符串 或 手动比较属性。
方法 1:JSON.stringify 转字符串(简单对象适用)
核心原理:把对象转为 JSON 字符串,再用 Set 或对象键名去重(字符串可以被唯一识别)。
// 待去重的对象数组(id 相同视为重复)
const arr = [
{ id: 1, name: "小明" },
{ id: 1, name: "小明" }, // 重复
{ id: 2, name: "小红" }
];
const uniqueArr = [...new Set(arr.map(item => JSON.stringify(item)))]
.map(str => JSON.parse(str)); // 转回对象
console.log(uniqueArr);
// 输出:[{ id: 1, name: "小明" }, { id: 2, name: "小红" }]方法 2:手动比较属性(复杂对象适用)
核心原理:指定一个唯一标识(比如 id),遍历数组时,判断新数组中是否已有该标识的对象,没有则加入。
const arr = [
{ id: 1, name: "小明" },
{ id: 1, name: "小明" },
{ id: 2, name: "小红" }
];
const uniqueArr = [];
for (let item of arr) {
// 用 some 判断新数组中是否已有相同 id 的对象
const isRepeat = uniqueArr.some(uniqueItem => uniqueItem.id === item.id);
if (!isRepeat) {
uniqueArr.push(item);
}
}
console.log(uniqueArr);
// 输出:[{ id: 1, name: "小明" }, { id: 2, name: "小红" }]优缺点
优点:能处理引用类型的去重;
缺点:
JSON.stringify有局限性(比如无法处理function、undefined、Symbol等属性);手动比较属性需指定唯一标识,灵活性较低。
适用场景
处理对象数组,且对象结构简单(无特殊属性),或有明确唯一标识(如 id)的场景。
七、排序后去重:利用 sort + 相邻比较
核心原理:先通过 sort() 把数组排序(相同元素会相邻),再遍历排序后的数组,比较当前元素和前一个元素,不同则加入新数组。
代码实现
const arr = [3, 1, 2, 2, 3, 1, 4];
const uniqueArr = [];
// 先排序(注意:sort 默认按字符串排序,数字需加比较函数)
const sortedArr = arr.sort((a, b) => a - b); // 排序后:[1, 1, 2, 2, 3, 3, 4]
for (let i = 0; i < sortedArr.length; i++) {
// 比较当前元素和前一个元素,不同则保留
if (sortedArr[i] !== sortedArr[i - 1]) {
uniqueArr.push(sortedArr[i]);
}
}
console.log(uniqueArr); // 输出:[1, 2, 3, 4]优缺点
优点:逻辑简单,适合已排序或可排序的数组;
缺点:会改变原数组的顺序(若需保留原顺序则不适用);性能受排序影响(
sort时间复杂度约 O (n log n));不支持引用类型去重。
适用场景
处理数字数组,且不关心原数组顺序的场景。
总结:不同场景如何选?
| 场景需求 | 推荐方法 | 时间复杂度 |
|---|---|---|
| 简单类型 + 代码简洁 | Set 去重 | O(n) |
| 简单类型 + 高性能 + 大规模 | 对象键名去重 / Map 去重 | O(n) |
| 低版本浏览器兼容 | indexOf/includes 去重 | O(n²) |
| 函数式编程风格 | filter + indexOf 去重 | O(n²) |
| 对象数组(有唯一标识) | 手动比较属性去重 | O(n²) |
| 数字数组(不关心顺序) | sort + 相邻比较去重 | O(n log n) |
记住:没有 “最好” 的去重方法,只有 “最适合” 的 —— 根据数据类型、数据规模和环境需求选择即可!
到此这篇关于JS数据去重的7种实用方法总结的文章就介绍到这了,更多相关JS数据去重方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
