目录
全网最全情景,深入浅出解析JavaScript数组去重:数值与引用类型的全面攻略
作者:watermelo37
涉及领域:Vue、SpingBoot、Docker、LLM、python等
---------------------------------------------------------------------
温柔地对待温柔的人,包容的三观就是最大的温柔。
---------------------------------------------------------------------
深入浅出解析JavaScript数组去重:数值与引用类型的全面攻略
一、引言:我们为什么需要关注数组去重?
在日常开发中,数组去重是一个不可避免的话题。不管是简单的数值数组去重,还是复杂的引用类型数组去重,掌握多种方法可以帮助开发者高效、优雅地解决实际问题。在这篇博客中,我们将从基础到进阶,结合大量代码案例,系统介绍数组去重的各种技巧。
前排提醒:
本文主要讨论的去重情况包括:
1、数值类去重,数组的元素往往是基础数据类型,这种情况相对容易,有很多种方法可以做到。
2、引用类去重,数组的元素往往是对象、数组甚至多类型混合。这种情况下的去重会复杂很多,并且还可以分为去除完全重复和部分重复两种。
①去除完全重复就是两个引用类型的元素完全一致,其中还包括元素内容一致但键值对顺序不一致的特殊情况。
②去除部分重复就比如两个元素的name一致,就要去除其中的一个,并根据某种规则留下一个特定的元素。
二、数值类去重
1、使用 Set 去重
Set 是去重的“万金油”,可以自动移除重复的元素,适合大多数基础类型数组去重。简洁高效,推荐使用。但仅适合基础数据类型。
let numbers = [1, 2, 2, 3, 4, 4, 5];
let uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers);
// 输出:[1, 2, 3, 4, 5]
2、遍历 + includes()
对于初学者,遍历结合 includes() 是一种直观的方法,易于理解,适合新手学习,但对大数组性能较低。初学者做开发学习时候遇不到大数组大数据,所以这个方法是完全可行的。
let numbers = [1, 2, 2, 3, 4, 4, 5];
let uniqueNumbers = [];
for (let num of numbers) {
if (!uniqueNumbers.includes(num)) {
uniqueNumbers.push(num);
}
}
console.log(uniqueNumbers);
// 输出:[1, 2, 3, 4, 5]
3、使用 filter() 和 indexOf()
filter() 和 indexOf() 是函数式编程的经典组合,用于去重非常直观,写法简洁,可作为 Set 的替代方案,但是indexOf() 遍历时间复杂度较高。
let numbers = [1, 2, 2, 3, 4, 4, 5];
let uniqueNumbers = numbers.filter((num, index) => numbers.indexOf(num) === index);
console.log(uniqueNumbers);
// 输出:[1, 2, 3, 4, 5]
4、使用 reduce()
reduce() 可以通过累积器动态生成去重数组,灵活且功能强大,灵活性强,可结合复杂逻辑处理,但代码相对复杂。
这里使用reduce()核心是利用它的累加器,累加器不只可以用来累加,可以用来做任何事情,包括特殊情况下作为forEach()和map()的替代。
let numbers = [1, 2, 2, 3, 4, 4, 5];
let uniqueNumbers = numbers.reduce((acc, num) => {
if (!acc.includes(num)) {
acc.push(num);
}
return acc;
}, []);
console.log(uniqueNumbers);
// 输出:[1, 2, 3, 4, 5]
5、嵌套数组去重:结合 flat()
如果数组中有嵌套的情况,可以配合 flat() 进行去重。
let nestedNumbers = [1, [2, 2], [3, 4, 4], 5];
let uniqueNumbers = [...new Set(nestedNumbers.flat())];
console.log(uniqueNumbers);
// 输出:[1, 2, 3, 4, 5]
三、引用类去重——去除完全重复的对象元素
引用类去重的场景更为复杂,因为对象和数组属于引用类型,哪怕键值完全相同也会分属于不同的引用地址,直接比较无法判断重复性。我们会从简单到复杂,详细介绍解决方案。
完全重复是指两个对象的键值对完全相同。
1、JSON.stringify() + Set
将对象转换为字符串表示,再利用 Set 去重,这样代码简洁,适合结构简单的对象数组,但是对嵌套对象或顺序无关的对象有局限性(比如某个相同元素name在前,id在后,这样就无法去重了)。
let objects = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice" }
];
let uniqueObjects = Array.from(new Set(objects.map(JSON.stringify))).map(JSON.parse);
console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
2、使用 Map() 方法
通过 Map 的键值对特性保存唯一对象,适合复杂数据结构,性能优于 Set。
let objects = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice" }
];
let uniqueObjects = Array.from(new Map(objects.map(obj => [JSON.stringify(obj), obj])).values());
console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
Tips:
在JavaScript中,Map 和 Set 是两种不同的集合类型,它们都是ES6中引入的,用于存储和管理一系列值。它们之间有几个关键的区别:
Map 对象
- Map 对象保存键值对,并且能够记住键的原始插入顺序。
- 任何值(对象或者原始值)都可以作为一个键或一个值。
- Map 对象提供了许多实用的方法,如 set、get、has 和 delete,来操作映射。
- Map 对象是可迭代的,这意味着它们可以用于 for...of 循环。
Set 对象
- Set 对象只保存唯一的值,即不允许重复。
- Set 对象同样保存元素的插入顺序。
- Set 对象提供了 add、has 和 delete 等方法来操作集合。
- Set 对象也是可迭代的,适用于 for...of 循环。
一言以蔽之,一个是键值对集合,一个是数值集合。
四、特殊情况:对象的键值对可能顺序不同,但其内容相同
当对象的键值对顺序不同,但其内容相同时,使用 JSON.stringify() 或 Map() 方法会出现问题,因为 JSON.stringify() 会把对象的键值对顺序也纳入到字符串化的过程,而 Map() 是基于键值对的映射,在对象属性的顺序不同的情况下,结果也可能不一致。
比如这样的数据就是无法去重的:
let objects = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ name: "Alice", id: 1 }
];
为了处理这类键顺序不同但内容相同的对象去重,我们可以采取以下两种方法:
1、自定义函数比较对象内容
通过自定义函数来统一对象的键的顺序,确保不论顺序如何都能正确去重。
function deepEqual(obj1, obj2) {
const keys1 = Object.keys(obj1).sort();
const keys2 = Object.keys(obj2).sort();
if (keys1.length !== keys2.length) {
return false;
}
for (let i = 0; i < keys1.length; i++) {
const key = keys1[i];
if (obj1[key] !== obj2[key]) {
return false;
}
}
return true;
}
let objects = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ name: "Alice", id: 1 }
];
let uniqueObjects = [];
for (let obj of objects) {
if (!uniqueObjects.some(existingObj => deepEqual(existingObj, obj))) {
uniqueObjects.push(obj);
}
}
console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
2、标准化对象的键顺序
另一个方法是将对象的键排序,确保对象在进行去重时顺序一致。可以使用 JSON.stringify() 对每个对象进行处理,但是要先标准化它们的键顺序,然后进行比较。
function sortObjectKeys(obj) {
const sortedObj = {};
Object.keys(obj).sort().forEach(key => {
sortedObj[key] = obj[key];
});
return sortedObj;
}
let objects = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ name: "Alice", id: 1 }
];
// 将对象的键值对顺序标准化
let uniqueObjects = [...new Set(objects.map(obj => JSON.stringify(sortObjectKeys(obj))))].map(item => JSON.parse(item));
console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
3、使用 Map 存储标准化后的对象
可以将对象标准化为字符串后,存储在 Map 中以实现去重。
function sortObjectKeys(obj) {
const sortedObj = {};
Object.keys(obj).sort().forEach(key => {
sortedObj[key] = obj[key];
});
return sortedObj;
}
let objects = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ name: "Alice", id: 1 }
];
let map = new Map();
objects.forEach(obj => {
map.set(JSON.stringify(sortObjectKeys(obj)), obj);
});
let uniqueObjects = Array.from(map.values());
console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
五、引用类去重——去除部分重复的对象元素
比如根据id去重、根据name去重,这种情况往往是某些不会重复的字段因为数据库的迁徙、合并或者增删修改导致了重复,除了要进行去重以外,还要根据某种规则留下一个特定的元素(因为两个元素并不完全一致)
function sortObjectKeys(obj) {
const sortedObj = {};
Object.keys(obj).sort().forEach(key => {
sortedObj[key] = obj[key];
});
return sortedObj;
}
let objects = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ name: "Alice", id: 1 }
];
// 将对象的键值对顺序标准化
let uniqueObjects = [...new Set(objects.map(obj => JSON.stringify(sortObjectKeys(obj))))].map(item => JSON.parse(item));
console.log(uniqueObjects);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
1、保留每个 id 对应的最后一个对象
let objects = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Charlie" }
];
let uniqueById = Array.from(new Map(objects.map(obj => [obj.id, obj])).values());
console.log(uniqueById);
// 输出:[{ id: 2, name: 'Bob' }, { id: 1, name: 'Charlie' }]
2、根据多字段组合去重
这种往往是数据库迁徙、合并的时候出现的问题,新旧数据混合。
let objects = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice", age: 25 },
{ id: 1, name: "Alice", age: 30 }
];
let uniqueByFields = objects.reduce((acc, obj) => {
if (!acc.some(item => item.id === obj.id && item.name === obj.name)) {
acc.push(obj);
}
return acc;
}, []);
console.log(uniqueByFields);
// 输出:[{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
3、根据特定规则保留对象:保留最高分的学生记录
比如在多次测评中,某人获得了不同的成绩,要保留其最高分。
let students = [
{ id: 1, name: "Alice", score: 85 },
{ id: 2, name: "Bob", score: 90 },
{ id: 1, name: "Alice", score: 95 }
];
let highestScoreStudents = Array.from(
students.reduce((acc, student) => {
if (!acc.has(student.id) || acc.get(student.id).score < student.score) {
acc.set(student.id, student);
}
return acc;
}, new Map()).values()
);
console.log(highestScoreStudents);
// 输出:[{ id: 2, name: 'Bob', score: 90 }, { id: 1, name: 'Alice', score: 95 }]
六、混合数组去重
对于包含基础数据类型和引用类型的混合数组,可以分别处理后合并。
let mixedArray = [1, "a", { id: 1 }, "a", { id: 1 }, 2, 1];
// 分离基础类型和引用类型
let primitives = mixedArray.filter(item => typeof item !== "object");
let objects = mixedArray.filter(item => typeof item === "object");
// 分别去重后合并
let uniqueMixedArray = [
...new Set(primitives),
...Array.from(new Map(objects.map(obj => [JSON.stringify(obj), obj])).values())
];
console.log(uniqueMixedArray);
// 输出:[1, 'a', 2, { id: 1 }]
七、对比与总结
1、使用场景对比
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
Set | 基础类型数组去重 | 简洁高效 | 无法处理引用类型 |
遍历 + includes | 基础类型数组去重 | 易理解 | 性能较低 |
filter() + indexOf() | 基础类型数组去重 | 通用 | 性能较低 |
reduce() | 复杂逻辑处理或混合类型数组去重 | 灵活,可扩展逻辑 | 写法稍复杂 |
JSON.stringify | 引用类型数组去重 | 简洁 | 无法处理嵌套或无序字段的对象 |
Map | 引用类型数组去重 | 性能较优,适合复杂数据结构 | 写法稍繁琐 |
2、总结
如果是基础类型数组,优先选择 Set。
对于引用类型数组,根据需求选择 Map 或 JSON.stringify()。
其余情况根据实际需求进行混合调用,就能更好的实现数组去重。
只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
其他热门文章,请关注:
你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解
极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图
通过array.filter()实现数组的数据筛选、数据清洗和链式调用
el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能
TreeSize:免费的磁盘清理与管理神器,解决C盘爆满的燃眉之急
Dockerfile全面指南:从基础到进阶,掌握容器化构建的核心工具
在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver
JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南
Idea启动SpringBoot程序报错:Port 8082 was already in use;端口冲突的原理与解决方案
PDF预览:利用vue3-pdf-app实现前端PDF在线展示
标签:深入浅出,obj,name,最全,JavaScript,Alice,let,数组,id From: https://blog.csdn.net/RenGJ010617/article/details/143950308