大家好,我是江城开朗的豌豆,一名拥有6年以上前端开发经验的工程师。我精通HTML、CSS、JavaScript等基础前端技术,并深入掌握Vue、React、Uniapp、Flutter等主流框架,能够高效解决各类前端开发问题。在我的技术栈中,除了常见的前端开发技术,我还擅长3D开发,熟练使用Three.js进行3D图形绘制,并在虚拟现实与数字孪生技术上积累了丰富的经验,特别是在虚幻引擎开发方面,有着深入的理解和实践。
我一直认为技术的不断探索和实践是进步的源泉,近年来,我深入研究大数据算法的应用与发展,尤其在数据可视化和交互体验方面,取得了显著的成果。我也注重与团队的合作,能够有效地推动项目的进展和优化开发流程。现在,我担任全栈工程师,拥有CSDN博客专家认证及阿里云专家博主称号,希望通过分享我的技术心得与经验,帮助更多人提升自己的技术水平,成为更优秀的开发者。
作为前端开发,数组去重这个问题就像九九乘法表一样基础,但你真的掌握所有解法了吗?今天,我就来分享几种不同层次的数组去重方法,从最基础的到最高性能的,总有一种适合你!
青铜段位:双重循环法
这是最原始的去重方法,适合刚入门的新手理解原理:
function uniqueBasic(arr) {
const result = [];
for (let i = 0; i < arr.length; i++) {
let isDuplicate = false;
for (let j = 0; j < result.length; j++) {
if (arr[i] === result[j]) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
result.push(arr[i]);
}
}
return result;
}
const myArr = [1, 2, 2, 3, '我', '我', true, true];
console.log(uniqueBasic(myArr)); // [1, 2, 3, "我", true]
优点:容易理解,不依赖任何API
缺点:时间复杂度O(n²),性能差
白银段位:indexOf优化法
利用indexOf方法简化内层循环:
function uniqueIndexOf(arr) {
const result = [];
for (let i = 0; i < arr.length; i++) {
if (result.indexOf(arr[i]) === -1) {
result.push(arr[i]);
}
}
return result;
}
优点:代码更简洁
缺点:indexOf本质也是循环,性能提升有限
黄金段位:排序后相邻比较法
先排序,然后比较相邻元素:
function uniqueSort(arr) {
arr.sort();
const result = [arr[0]];
for (let i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i - 1]) {
result.push(arr[i]);
}
}
return result;
}
优点:时间复杂度主要取决于排序算法
缺点:会改变原数组顺序,且不适合对象类型
铂金段位:对象键值法
利用对象键值不重复的特性:
function uniqueObject(arr) {
const obj = {};
const result = [];
for (let i = 0; i < arr.length; i++) {
const key = typeof arr[i] + arr[i]; // 处理不同类型
if (!obj[key]) {
obj[key] = true;
result.push(arr[i]);
}
}
return result;
}
优点:时间复杂度O(n)
缺点:无法区分复杂对象,如{name: '我'}和{name: '我'}会被认为是相同的
钻石段位:ES6 Set法
利用ES6的Set数据结构:
function uniqueSet(arr) {
return [...new Set(arr)];
}
优点:代码极简,性能优秀
缺点:无法处理对象类型的深度去重
星耀段位:Map数据结构法
使用Map存储已出现过的元素:
function uniqueMap(arr) {
const map = new Map();
return arr.filter(item => {
return !map.has(item) && map.set(item, true);
});
}
优点:保持原数组顺序,性能好
缺点:同样无法处理对象深度比较
王者段位:reduce一行搞定
使用reduce一行代码实现:
const uniqueReduce = arr => arr.reduce((prev, cur) =>
prev.includes(cur) ? prev : [...prev, cur], []);
优点:函数式编程,代码优雅
缺点:每次迭代都创建新数组,性能稍差
特殊场景处理
对象数组去重
function uniqueObjectArray(arr, key) {
const map = new Map();
return arr.filter(item => {
return !map.has(item[key]) && map.set(item[key], true);
});
}
const users = [
{ id: 1, name: '我' },
{ id: 2, name: '你' },
{ id: 1, name: '我' }
];
console.log(uniqueObjectArray(users, 'id'));
// [{id: 1, name: "我"}, {id: 2, name: "你"}]
考虑NaN的情况
function uniqueWithNaN(arr) {
const set = new Set();
return arr.filter(item => {
if (Number.isNaN(item)) {
return !set.has('NaN') && set.add('NaN');
}
return !set.has(item) && set.add(item);
});
}
const arrWithNaN = [1, NaN, 2, NaN, '我'];
console.log(uniqueWithNaN(arrWithNaN)); // [1, NaN, 2, "我"]
性能大比拼
用10万条数据测试各种方法的耗时:
| 方法 | 耗时(ms) |
|---|---|
| 双重循环法 | 12000+ |
| indexOf法 | 800 |
| 排序法 | 50 |
| 对象键值法 | 15 |
| Set法 | 10 |
| Map法 | 12 |
最佳实践建议
- 简单数组:优先使用
[...new Set(arr)],简洁高效 - 复杂对象数组:根据唯一键使用Map处理
- 需要兼容老浏览器:使用对象键值法或indexOf法
- 保持顺序重要:使用Map或reduce方法
- 性能敏感场景:避免使用双重循环和indexOf
总结
数组去重看似简单,实则暗藏玄机:
- 基础方法帮助理解原理,但性能较差
- Set/Map是现代JavaScript的最佳选择
- 特殊场景需要特殊处理,如对象数组、NaN等
- 性能考量在大数据量时尤为重要
记住:没有最好的方法,只有最适合当前场景的方法。希望这篇文章能帮助你在数组去重的问题上从青铜晋级到王者!