数组去重
ES6 Set (日常开发首选)
const arr = [1, 1, "2", "2", true, true, NaN, NaN];
// 1. 配合扩展运算符
const uniqueArr = [...new Set(arr)];
// 2. 配合 Array.from
const uniqueArr = Array.from(new Set(arr));
console.log(uniqueArr);
优点:语法简洁,执行效率高,能够正确是被并去重 NaN 缺点:无法去重引用类型数据(比如两个内容一模一样的对象 {a: 1} 和 {a: 1},因为它们的内存地址不同,Set 会认为它们是两个东西)。
传统做法
传统做法的主要逻辑就是:循环遍历数组,和数组当中出现这个元素的第一次位置进行比较。是:留下,不是:删除。
const arr = [1, 1, "2", "2", true, true];
const uniqueArr = arr.filter((item, index) => {
// indexOf 永远只返回这个元素在数组中【第一次】出现的索引
return arr.indexOf(item) === index;
});
类似的还有以下这些逻辑
//双重循环
const handleRemoveRepeat = (arr)=>{
for(let i =0;len = arr.length;i<len;i++){
for(let j =i+1;j<len;j++){
if(arr[i] === arr[j]){
arr.splice(j,1)
j--;len--;
}
}
}
return arr;
}
// indexOf去重
const handleRemoveRepeat = (arr)=>{
let uniqueArr = [];
for(let i =0;len = arr.length;i<len;i++){
if(uniqueArr.indexOf(arr[i]) === -1) uniqueArr.push(arr[i]);
}
return uniqueArr;
}
// includes去重
// 和上面的方法类似
const handleRemoveRepeat = (arr)=>{
let uniqueArr = [];
for(let i =0;len = arr.length;i<len;i++){
if(!uniqueArr.includes(arr[i])) uniqueArr.push(arr[i]);
}
return uniqueArr;
}
这上面的几种方法是在一些老项目当中使用的。 优点:ES5 语法,兼容性好 缺点:
- 性能差,(filter 里嵌套了 indexOf,时间复杂度是 )
- 致命盲区:无法去重 NaN(因为 indexOf(NaN) 永远返回 -1,导致所有的 NaN 都会被漏掉)。
Hash Map 现代较为推荐的
如果数组内容极其庞大,上面的两种方法可能会造成卡顿,这时候建议用空间换时间
const arr = [1, 1, "1", "1", true, true, NaN, NaN];
function uniqueArrByMap(arr) {
const map = {};
const res = [];
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
// 为了防止数字 1 和字符串 '1' 发生键名冲突,我们把类型也拼接到键名里
const key = typeof item + item;
if (!map[key]) {
// 没有登记过,放行,并登记造册
res.push(item);
map[key] = true;
}
}
return res;
}
console.log(uniqueByMap(arr));
// 输出: [1, '1', true, NaN]
优点:遍历一次就可以完成任务,时间复杂度是极其优秀的 。 缺点: 稍微占用了额外的内存空间来存放 map 对象。
数组去重进阶
实际工作工程当中,业务往往较为复杂。如何根据对象的某个特定字段(比如 id)来进行“对象数组去重”
我们建一个专门的“花名册”(Map)。让数组里的对象排队挨个走过来,我们只看它的 id。如果花名册上没有这个 id,就把它放进结果队伍里,并在花名册上记下这个 id;如果查到 id 已经记过了,直接踢掉。
const users = [
{ id: 1, name: "Alice", age: 20 },
{ id: 2, name: "Bob", age: 25 },
{ id: 1, name: "Alice_v2", age: 21 }, // id 重复了,会被踢掉
{ id: 3, name: "Charlie", age: 22 },
];
function uniqueById(arr) {
const res = new Map();
// filter 会遍历数组,并且只保留返回 true 的元素
return arr.filter((item) => {
// 如果 Map 中没有这个 id
if (!res.has(item.id)) {
// 把 id 存入 Map 当作标记,值随便设一个(比如 1)
res.set(item.id, 1);
return true; // 放行
}
return false; // 拦截
});
}
console.log(uniqueById(users));
// 输出只包含 id: 1, 2, 3 的三个对象,id 为 1 的第二个对象被成功过滤