前段时间遇到数组去重的问题,网上查到的资料也是参差不齐,有的实用有的简洁有的好记,所以在此把遇到的一些方法做一个记录,同时对一些常用方法时间消耗、以及难易程度做个比较。
Ps所有代码均在Firefox内做过测试。
一、利用es6中的set去重
set 是es6中新的数据结构,类似于数组,但其成员唯一,没有重复的值,无序。一般用[...myset]的形式转化为数组之后遍历。优点是代码简洁方便记忆速度也较快,缺点是……额还没发现缺点,要不然为啥放第一个23333
function func(arr){
return Array.from(new Set(arr));
}
var arr=[1,2,3,4,4,4,NaN,NaN,'NaN',true,'true',null,null,{},{},{}];
console.log(func(arr));
这是在Firefox浏览器内测试的结果,注意看到还无法去掉空对象
二、使用map
Map是js中的键值对结构,其查找速度极快。用其排序原理即遍历数组,向map内添加键值对,由于键不允许存在重复,自然利用map存储的元素也不会重复了。优点是效率极高,占用空间也较小,缺点是需要熟练掌握map用法。
var funcmap = function(arr){
let temp = new Map();
return arr.filter(item=>{return !temp.has(item) && temp.set(item,1);});
}
同上set,一样无法去掉空对象
三、使用对象属性无重复来对数组去重
此法相比上个方法由于是向对象内添加属性,其操作速度自然不如以上,但可以去除空对象,以及类似于字符串NaN,字符串true这种。(后文解释为什么会去除字符串NaN,字符串true)
var funcobject = function(arr) {
var array = [];
var obj = {};
for (var i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
array.push(arr[i]);
obj[arr[i]] = 1;
} else {
obj[arr[i]]++;
}
}
return array;
};
四、利用sort去重
Sort去重原则上还是判断是否相等。相比上面键值对,对象属性等骚操作速度自然是不如了,不过sort支持度广泛,容易理解,排序速度也不是太慢。
var funcsort = function(arr) {
arr.sort();
var array = [];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i - 1]) {
array.push(arr[i - 1]);
}
}
return array;
};
对于空对象,涉及到js底层对对象的比较等,有兴趣可以再行研究~这里贴一张结果的图片
五、利用indexof去重
Es5中提供的方法indexof,根据元素判断是否在数组中,类似于上面的方法,优点好理解记忆,方法直观清晰。缺点对ie支持不好(ie:我不背锅哈)具体可查ie浏览器对es5的支持程度,还有速度实际是更慢的,因为循环和比较相比上面做的多了。
var funcindexof = function(arr) {
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array.indexOf(arr[i]) === -1) {
array.push(arr[i]);
}
}
return array;
};
六、使用splice去重
嵌套两层循环,寻找相同元素,如果相同则删除。
与上个类似,可以理解为上个方法的不同实现,同样要考虑到ie对splice的支持,其速度理论上还不如上面的indexof。
var funcsplice = function(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
};
七、filter结合indexof去重
该方法优势在于,不输于第一个方法的简洁,仅用一行代码就可处理,巧妙结合filter,indexof等数组方法的特性。不过注意indexof的性能较低,处理几百个元素还行,如果在性能差的移动端处理上千元素,就会出现阻塞页面的卡顿。
var funcfilter = function(arr) {
return arr.filter((item, index) => arr.indexOf(item, 0) === index);
};
相比上面方法五的indexof为啥少了两个NaN,原因如下:
接下来就是几个不怎么常用的方法了,用来在面试官面前装逼拓宽知识面用的
八、利用includes去重
Includes方法检测数组是否包含该元素,返回值是true或者false,和indexof有异曲同工之妙~
var funcincludes = function(arr) {
var array = [];
for (var i = 0; i < arr.length; i++) {
if (!array.includes(arr[i])) {
array.push(arr[i]);
}
}
return array;
};
同样是对NaN处理的不同,使得includes方法可以去除多余的NaN,如果有这样的需求可以利用此方法
这样看差别应该就很明显了吧
九、 利用reduce去重
reduce() 是数组的归并方法,与forEach()、map()、filter()等迭代方法一样都会对数组每一项进行遍历,但是reduce() 可同时将前面数组项遍历产生的结果与当前遍历项进行运算,这一点是其他迭代方法无法企及的。Reduce函数形式如下
arr.reduce(function(prev,cur,index,arr){
...
}, init);
其中arr 表示原数组;
prev 表示上一次调用回调时的返回值,或者初始值 init;
cur 表示当前正在处理的数组元素;
index 表示当前正在处理的数组元素的索引,若提供 init 值,则索引为0,否则索引为1;
init 表示初始值。
是不是看起来很复杂,但其实实际使用一般只用到prev和cur,分别对应previous上一个返回值和current当前值。
var funcreduce = function(arr) {
return arr.reduce((prev, cur) => {
prev.indexOf(cur) === -1 && prev.push(cur);
return prev;
}, []);
};
实际上还是如上indexof的变种。
十、reduce和includes结合去重
var funcreduceincludes = function(arr) {
return arr.reduce((prev, cur) => (prev.includes(cur) ? prev : [...prev, cur]),[]);
};
一行代码搞定,极客首选之一
本质上还是includes,只不过由reduce帮忙遍历而已
可以看到结果同上includes
十一、使用hasOwnProperty去重
hasOwnProperty是用来检测对象是否包含某一属性的方法。区别于中括号,hasOwnProperty不会对原型链上端的对象进行属性判断。所以与上面“使用对象属性不重复去重”略有小小区别(当然别瞎修改原型链就没问题)
var funchasOwnProperty = function(arr) {
var array = [];
var temp = {};
for (var i = 0; i < arr.length; i++) {
if (!temp.hasOwnProperty(arr[i])) {
temp[arr[i]] = 1;
array.push(arr[i]);
}
}
return array;
};
看到与上面对象属性去重之后结果是一致的。
其实本质上,js在内部声明某个对象的属性时,会做一个toString的操作,这也就解释了为啥NaN和‘NaN’会被去重,同样的,如果打印观察的话,空对象{ }在做对象的属性名时也会转化为"[object Object]"。
总结:
总体上来看,去重的思路大概就是三种
一是利用键值对里键不重复
二是利用对象里属性名不可重复
三则是直接对元素比较判断
而去重的工具无非就是各种遍历和查找的方法,在这些方法之上结合上面的思路,也就完成了数组去重。
方法不论好坏,也不仅限于上面有限种类,根据业务需求来实现代码,才是最好的数组去重方法~