一、基础方法:双层循环与 indexOf
1. 双层循环法
function unique(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;
}
- 原理:外层遍历原数组,内层检查元素是否已存在于结果数组
- 时间复杂度:O(n²)
- 优缺点:兼容性好,但效率低,无法处理NaN(
NaN === NaN 为 false)
2. indexOf 优化法
function unique(arr) {
const result = [];
for (let item of arr) {
if (result.indexOf(item) === -1) {
result.push(item);
}
}
return result;
}
- 改进点:用
indexOf 简化内层循环
- 注意:
[NaN].indexOf(NaN) === -1,无法处理NaN
二、ES6 新特性:Set 与 Map
3. Set 方法(最推荐)
function unique(arr) {
return [...new Set(arr)];
}
- 原理:利用 Set 数据结构的唯一性
- 时间复杂度:O(n)
- 优缺点:代码极简,支持NaN,但无法处理对象(
{a:1} 和 {a:1} 视为不同元素)
- 兼容性:IE 不支持,需 polyfill
4. Map 方法(保留顺序)
function unique(arr) {
const map = new Map();
return arr.filter(item => {
if (!map.has(item)) {
map.set(item, true);
return true;
}
return false;
});
}
- 优势:可处理复杂类型(需自定义比较逻辑),保留元素顺序
三、高级场景:对象与排序去重
5. 对象键值对去重(处理复杂类型)
function unique(arr) {
const obj = {};
return arr.filter(item => {
const key = typeof item + JSON.stringify(item);
return obj.hasOwnProperty(key) ? false : (obj[key] = true);
});
}
- 原理:将元素转为字符串作为对象键,利用对象属性唯一性
- 适用场景:去重包含对象、数组的复杂数组
- 注意:
{a:1} 和 {a:1} 会被视为相同元素(因 JSON .stringify 结果相同)
6. 排序后相邻比较法
function unique(arr) {
return arr
.slice()
.sort()
.filter((item, index, arr) => {
return !index || item !== arr[index - 1];
});
}
- 原理:排序后相同元素相邻,过滤重复项
- 优缺点:效率较高(O(n log n)),但会打乱原数组顺序,无法处理NaN
四、问题
1. 问:Set 去重和 Map 去重的区别?
- 答:
- Set 去重利用其唯一性,代码简洁但无法保留顺序,且对象会被视为不同元素;
- Map 去重通过键值对记录元素存在性,可保留顺序,且能自定义复杂类型的比较逻辑(如通过属性值判断对象是否重复)。
2. 问:如何去重包含对象的数组?
- 答:
- 方案1:自定义比较函数(如比较对象的某个属性):
function uniqueObjects(arr, key) {
const map = new Map();
return arr.filter(obj => {
const id = obj[key];
if (!map.has(id)) {
map.set(id, true);
return true;
}
return false;
});
}
uniqueObjects([{id:1}, {id:2}, {id:1}], 'id');
- 方案2:转为字符串去重(适用于浅比较):
arr.filter((item, index) =>
JSON.stringify(item) === JSON.stringify(arr[index])
);
3. 问:去重方法的性能对比?
- 答:
- 效率从高到低:Set/Map(O(n))> 排序相邻比较(O(n log n))> indexOf(O(n²));
- 内存占用:Map 比 Set 略高,因需存储键值对;
- 推荐选择:简单数组用 Set,复杂类型用 Map,兼容性要求高用 indexOf。
五、性能优化与最佳实践
1. 大数据量优化
- 当数组长度超过 10000 时,使用 Set 比双层循环性能提升 50% 以上。
2. 自定义比较逻辑
- 若需根据对象属性去重,封装通用函数:
function uniqueBy(arr, fn) {
const seen = new Set();
return arr.filter(item => {
const key = typeof fn === 'function' ? fn(item) : item;
return !seen.has(key) && seen.add(key);
});
}
uniqueBy([{age:20}, {age:30}, {age:20}], o => o.age);
3. 兼容性处理