前段时间面试被问到数组有哪些去重的方式, 除了双重for循环我竟然想不出第二个, 惭愧惭愧. 前两年的工作都是html+css, css3, h5, 响应式页面, 外加一些cms, jQuery, bootstrap等. 感觉一直在前端领域的边缘徘徊, js基础薄弱, 更别说es6了, 现在换的这份工作才感觉刚刚步入正轨, 等待我学习的领域无限宽广
1. 双重for循环 (不能过滤掉 NaN、Object)
双重for循环 + splice的方式在人群中应该是最高龄的方法了(年代久远).
let arr = [1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 9, 9, 9];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--; //splice会改变原始数组, 删除重复元素后仍需从上次位置与前一个比较, 所以j--
}
}
}
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
上面有可以优化的地方, 如果数据量很大, 每次循环都会计算arr.length的值, 非常影响性能
let arr = [1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 9, 9, 9];
let arrLength = arr.length; //循环之前创建一个变量存储数组长度
for (let i = 0; i < arrLength; i++) {
for (let j = i + 1; j < arrLength; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--;
arrLength = arr.length; //删除元素后需要重新获取数组长度, 否则很容易出现死循环(undefined == fundefined)
}
}
}
console.log(arr) //[1, 2, 3, 4, 5, 6, 7, 8, 9]
2. indexOf去重 (不能过滤掉 NaN、Object)
indexOf()去重思路是: 创建一个空数组, 原数组的元素不在新建的空数组中, 就push()进去
let arr = [1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 9, 9, 9];
let arr2 = []
for (let i = 0; i < arr.length; i++) {
if (arr2.indexOf(arr[i]) === -1) { //indexOf()在数组中没找到指定元素则返回 -1
arr2.push(arr[i]);
}
}
console.log(arr2); //[1, 2, 3, 4, 5, 6, 7, 8, 9]
for in循环一样的道理, 不过for in循环的效率很低, 推荐使用for循环
let arr = [1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 9, 9, 9];
let arr2 = []
for (i in arr) {
if (arr2.indexOf(arr[i]) === -1) {
arr2.push(arr[i])
}
}
console.log(arr2); //[1, 2, 3, 4, 5, 6, 7, 8, 9]
3. includes去重 (不能过滤掉 Object)
includes()去重思路和indexOf()类似, 只是api不同而已
let arr = [1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 9, 9, 9];
let arr2 = []
for (let i = 0; i < arr.length; i++) {
if (!arr2.includes(arr[i])) { //includes()数组中查不到元素返回false
arr2.push(arr[i])
}
}
console.log(arr2); //[1, 2, 3, 4, 5, 6, 7, 8, 9]
4. 利用对象key值唯一的特性去重 (可以过滤掉 Object, 但是不能区分NaN和'NaN')
这种方法本质原理和indexOf(), includes() 相同, indexOf(), includes()是判断某个元素是否在数组中, 此种方式以数组元素作为对象的key来判断.
let arr = [1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 9, 9, 9];
let arr2 = [];
let obj = {};
for (let i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) { //js对象获取属性有两种方式: . 和 []
obj[arr[i]] = 'a'; //随便赋一个值, 这样对象中就存在arr[i]这个key了
arr2.push(arr[i])
}
}
console.log(arr2); //[1, 2, 3, 4, 5, 6, 7, 8, 9]
5. sort() 去重 (不能过滤NaN、Object)
sort去重的思路就是, 先排序再比较, 这样就不用像双重for循环那样每两个都要比较一次了
let arr = [9, 9, 9, 1, 6, 1, 6, 6, 1, 7, 1, 2, 3, 4, 5, 8, 5, 5];
let arr2 = [];
arr.sort();
if (arr.length > 0) { //以下相邻元素比较会少push第一个, 所以比较前先把第一个push进arr2
arr2.push(arr[0])
}
for(let i = 1; i < arr.length; i++) {
if(arr[i] != arr[i - 1]) {
arr2.push(arr[i])
}
}
console.log(arr2); //[1, 2, 3, 4, 5, 6, 7, 8, 9]
6. ES6的Set (不能过滤掉 Object)
Set类似于数组,但是成员的值是唯一的, Set本身是一个构造函数,用来生成 Set 数据结构。
let arr = [1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 9, 9, 9];
arr = Array.from(new Set(arr)) //Array.from()将拥有 length 属性的对象或可迭代的对象转换为数组
console.log(arr); //[1, 2, 3, 4, 5, 6, 7, 8, 9]
也可以这样:
let arr = [1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 9, 9, 9];
arr = [...new Set(arr)] //思路: 数组转Set, Set去重后再转成数组
console.log(arr); //[1, 2, 3, 4, 5, 6, 7, 8, 9]
7. ES6的Map (不能过滤掉 Object)
Map它类似于对象, 但是key不限于字符串, 可以理解是对象的扩展. 同样, map中的key不会重复, map去重数组和第4个原理完全相同.
let arr = [1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 9, 9, 9];
let arr2 = [];
let map = new Map();
for (let i = 0; i < arr.length; i++) {
if (!map.has(arr[i])) {
map.set(arr[i]);
arr2.push(arr[i])
}
}
console.log(arr2); //[1, 2, 3, 4, 5, 6, 7, 8, 9]
8. 利用递归去重
递归去重本质和双重for循环相同, 和第五个也类似, 只是这里是递归.
let arr = [1, 1, 1, 1, 2, 3, 4, 5, 5, 5, 6, 6, 6, 7, 8, 9, 9, 9];
function test(num) {
if (num >= 1) {
if (arr[num] == arr[num - 1]) {
arr.splice(num, 1)
}
test(num - 1)
}
}
test(arr.length - 1)
console.log(arr)
总结:
总体看来, 数组去重的思路无外乎两种:
1. 两两比较然后删除其一或者赋值到新数组, 区别就在于两两比较的方法不同
2. 特定的api, 如Set.