前段時間遇到數組去重的問題,網上查到的資料也是參差不齊,有的實用有的簡潔有的好記,所以在此把遇到的一些方法做一個記錄,同時對一些常用方法時間消耗、以及難易程度做個比較。
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]"。
總結:
總體上來看,去重的思路大概就是三種
一是利用鍵值對裏鍵不重複
二是利用對象裏屬性名不可重複
三則是直接對元素比較判斷
而去重的工具無非就是各種遍歷和查找的方法,在這些方法之上結合上面的思路,也就完成了數組去重。
方法不論好壞,也不僅限於上面有限種類,根據業務需求來實現代碼,纔是最好的數組去重方法~