数组去重的方式

现有如下数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let arr = [
{},
{},
'',
'',
233,
233,
'233',
'abc',
undefined,
undefined,
null,
null,
NaN,
NaN,
[2],
[2],
[2, 3]
]

将使用以下几种方式分别实现数组去重

使用Set数据结构

1
2
3
4
5
6
Array.prototype.myUnique = function(){
return Array.from(new Set(this));
}
console.log(arr.myUnique());
// 最终结果为:
// [{}, {}, '', 233,'233', 'abc', undefined, null, NaN,[2],[2],[2, 3]]

对于 {}、[] 这种引用数据类型并未实现去重。

使用for循环以及include方法

1
2
3
4
5
6
7
8
9
10
11
12
Array.prototype.myUnique = function(){
let arr = [];
for(let i = 0; i < this.length; i++){
if(!arr.includes(this[i])){
arr.push(this[i]);
}
}
return arr;
}
console.log(arr.myUnique());
// 最终结果为:
// [{}, {}, '', 233,'233', 'abc', undefined, null, NaN,[2],[2],[2, 3]]

对于 {}、[] 这种引用数据类型并未实现去重。

使用for循环以及indexOf方法

1
2
3
4
5
6
7
8
9
10
11
12
13
Array.prototype.myUnique = function(){
let arr = [];
for (let i = 0; i < this.length; i++) {
if (arr.indexOf(this[i]) === -1) {
arr.push(this[i]);
}
}
return arr;
}

console.log(arr.myUnique());
// 最终结果为:
// [{}, {}, '', 233,'233', 'abc', undefined, null, NaN, NaN, [2], [2], [2, 3]]

我们会发现indexOf方法并未识别到存在NaN,所以除了对于 {}、[] 这种引用数据类型,这种方式NaN也无法实现去重。

使用reduce方法

1
2
3
4
5
6
7
8
9
10
11
Array.prototype.myUnique = function(){
return this.reduce((accumulator, currentValue) => {
if (!accumulator.includes(currentValue)) {
accumulator.push(currentValue);
}
return accumulator;
}, [])
}
console.log(arr.myUnique());
// 最终结果为:
// [{}, {}, '', 233,'233', 'abc', undefined, null, NaN,[2],[2],[2, 3]]

其实与使用for循环以及includes方法一样,只不过使用reduce会显得更加优雅。

在此简单回顾下reduce的使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// reduce() 方法对数组中的每个元素执行一个由您提供的reduce函数(依次执行),将其结果汇总为单个返回值。
参数:
参数一: callback 函数(执行数组中每个值的函数,包含四个参数):
prev 必需 (上一次调用回调返回的值,或者是提供的初始值(initialValue))
cur 必需(数组中当前被处理的元素)
index 可选 (当前元素在数组中的索引)
arr 可选 (调用 reduce 的数组)
参数二:initialValue 可选 (表示初始值,作为第一次调用 callback 的第一个参数。)
提供初始值,cur 从数组第一项开始,若不提供初始值,则 cur 从第二项开始执行,对应的第一次 prev 是数组第一项的值。

结论:
如果没有提供initialValue,reduce 会从索引1的地方开始执行 callback 方法,跳过第一个索引。
如果提供initialValue,从索引0开始。

注意:
1. reduce是一个对数组累积操作的方法,使用时要加上 return 返回累积操作的数据。这样 prev 才能获取上一次执行的结果,否则是 undefined

2. 空数组执行 reduce 操作且不提供初始值时reduce会报错,错误信息如下:
`Reduce of empty array with no initial value at Array.reduce`

使用filter

1
2
3
4
5
6
7
8
Array.prototype.myUnique = function(){
return this.filter((v, i) => {
return this.indexOf(v) === i;
})
}
console.log(arr.myUnique());
// 最终结果为:
// [{}, {}, '', 233,'233', 'abc', undefined, null, [2], [2], [2, 3]]

由于数组使用indexOf判断v所处的index位置都会返回-1,所以使用上述filter组合indexOf去重的方式会将NaN全部过滤掉了,切引用数据类型还是没有被去重。

使用JSON.stringify

1
2
3
4
5
6
7
8
const array = [{id: 1}, {id: 2}, {id: 1}];

Array.prototype.myUnique = function(){
return Array.from(new Set(this.map(JSON.stringify)), JSON.parse);
}

console.log(array.myUnique());
// [ { id: 1 }, { id: 2 } ]

补充Array.from:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Array.from() 是 ECMAScript 6 中新增的一个方法,它可以从类数组对象或可迭代对象(如字符串、SetMapNodeList 等)创建一个新的数组实例。

基本语法:Array.from(arrayLike[, mapFn[, thisArg]])
arrayLike:必需,任何具有 length 属性且索引为整数的可迭代对象,或者是实现了 @@iterator 接口的对象。
mapFn:可选,一个映射函数,对每个元素调用该函数并将其结果收集到新数组中。
thisArg:可选,执行映射函数时的上下文对象。

示例:
// 从类数组对象创建数组
let arrayLike = {0: 'a', 1: 'b', 2: 'c', length: 3};
let arr = Array.from(arrayLike);
console.log(arr); // 输出: ["a", "b", "c"]

// 从 NodeList 创建数组
let divs = document.querySelectorAll('div');
let divArray = Array.from(divs);
console.log(divArray.length); // 输出: 找到的div元素数量

// 同时使用映射函数
let numbers = Array.from([1, 2, 3, 4], x => x * 2);
console.log(numbers); // 输出: [2, 4, 6, 8]

如果引用数据的顺序不一样,转的string就不等了。所以还是自己实现一个方法好一些。

使用自定义比较函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Array.prototype.myUnique = function () {
const seen = new Map();
return this.filter(item => {
if (typeof item === 'object' && item !== null) {
const key = 'object' + Object.keys(item).sort().map(k => `${k}:${item[k]}`).join('|');
if (!seen.has(key)) {
seen.set(key, true);
return true;
}
} else {
if (!seen.has(item)) {
seen.set(item, true);
return true;
}
}
return false;
});
}
console.log(arr.myUnique());
// 最终结果为:
// [{}, '', 233, '233', 'abc', undefined, null, NaN, [2], [2, 3]]

这个方法利用了 Map 数据结构的特性,用键来存储数组中的元素,保证了元素的唯一性。对于对象,通过将属性名排序并拼接成字符串来判断是否重复。

这种方法适用于任何类型的数组,包括混合了基本数据类型和引用数据类型的数组。