說到去重有很多的實現,在ES6下根本不是問題:
let array=[1,2,3,4,1,2,3,4]
let result=null
// 利用 indexOf 只會返回第一次匹配結果的特性,過濾出第一次出現的項
result=array.filter((value,i)=>array.indexOf(value)===i)
// Set 對象數據結構決定了其元素的唯一性,再按需通過擴展符號將其轉為數組
result=[...new Set(array)]
然而在ES6之前,兼顧性能的習慣性思路是基于 for
的遍歷實現,copy 一個新的集合并與原始集合做遍歷、比對,類似如下這種實現方法:
var uniq1=function(arr){
var temp=[],
item,
i,n;
for(i=0;i<arr.length;i+=1){ // 開始去重
item=arr[i];
temp.push(item);
for(n=0;n<temp.length;n+=1){
if(item===temp[n-1]){ // 如temp與arr中的元素重復則刪除
temp.splice(temp.length-1,1);
}
}
}
return temp;
};
上面這個例子會創建一個臨時的空數組對象,在將元素逐個放入其中時與前一個元素作比對,如果值相同則從臨時數組中剔除,直至遍歷完所有比對對象……這個方法盡可能的優化了性能,但是出現了嵌套循環,代碼實現邏輯復雜、對部分閱讀者并不友好對嗎?下面介紹一種或許可以減壓并且性能表現不俗的實現方法。
先說說思路,我們習慣對數組元素逐一比較后取得不重復值的方式來實現去重,這讓編程者思維局限在數組里。其實js世界為我們提供了現成的去重偏方——對象{},因為對象的鍵名是唯一的,我們利用這一特性,把值作為對象的鍵名就可以實現去重,于是:
var uniq2=function(arr){
var i,temp=[],
key,newArr=[];
for(i=0;i<arr.length;i+=1){//遍歷數組,將值作為鍵名放入對象中
temp[arr[i]] = (temp[arr[i]]+1)||1;//對象值隨便賦,用作重復次數的統計是個不錯的選擇
}
for(key in temp){//把生成的臨時對象轉回數組
newArr.push(key);
}
return newArr;
};
邏輯清晰,效果很好。由于是直接對對象鍵名的覆蓋,無需比對重復值,循環體的運算復雜度大大減少,這在做大量數據操作時對性能的提升會很明顯,因為無論如何僅僅是對數組的一次遍歷而已。我們寫個簡單的測試得到如下結果:
優勢明顯!但是帶來一個新問題,我們遍歷結果發現所有的值都變成了字符串!還有,如果是對象集合的去重呢?我們知道對象的鍵名是字符串,不能直接用對象字面量,真是沒有銀彈!怎么辦?我們依舊沿襲“偷懶的作風”,用js給我們提供的現成方法JSON.stringify()把對象轉成字符串,在去重完成后用JSON.parse()方法轉回對象字面量……好吧,這太容易了,所以,我再偷下懶,完整的實現代碼就由各位自由發揮吧,很容易不是嗎。
據說寫個ES6代碼在用Babel轉換下不就好了……然而用戶需求是無止境的依然有人不放棄對<ie8的支持。并且我發現第二種方法即使加上JSON的解析,在性能上依然優于[...new Set(arr)]
:
可能這點數據量還不足以體現這種方法的巨大優勢,我們加點料再測一次:
完勝啊!
關于去重還有很多話題可以展開,比如算法、對結果排序的影響等,但歸根結底還是性能優化、高效處理。這是個很有趣味的主題!