前言
陣列去重在日常開發中的使用頻率還是較高的,也是網上隨便一抓一大把的話題,所以,我寫這篇文章目的在於歸納和總結,既然很多人都在提的陣列去重,自己到底瞭解多少呢。又或者是如果自己在開發中遇到了去重的需求,自己能想到更好的解決方案嗎。
這次我們來理一理怎麼做陣列去重才能做得最合適,既要考慮相容性,也要考慮效能和程式碼的優雅。
我的學習路徑是模仿冴羽(github: mqyqingfeng)的學習方式,感謝像冴羽這樣優秀的人在前面領跑,我不想光看不做,所以多實踐多輸出,希望未來能走出我自己的路。
一、入門方案
function unique(origin) {
var result = [];
for(var i = 0; i < origin.length; i++) {
var arrayItem = origin[i];
for(var j= 0; j< result.length; j++) {
var resultItem = result[j];
// 如果在結果陣列迴圈中找到了該元素,則跳出迴圈,進入下一個源陣列元素的判斷
if(resultItem === arrayItem) {
break;
}
}
// 如果把結果陣列迴圈完都沒有找到該元素,就將該元素壓入結果陣列中
if(j === result.length) {
result.push(arrayItem);
}
}
return result;
}
var array = [`a`, `b`, `c`, `1`, 0, `c`, 1, ``, 1, 0];
console.log(unique(array)); // ["a", "b", "c", "1", 0, 1, ""]
以上程式碼是最簡單實現陣列去重的方式,優點在於相容性極好,缺點就是兩次 for 迴圈,時間複雜度為 O(n^2),效能較差。
二、陣列的 indexOf 屬性
陣列中的 indexOf 屬性是 ES5 的規範,只有 IE8 及更早版本不支援該方法。相對來說,如果你不需要相容 IE8 的話,儘量用 indexOf 來判斷一個元素是否在陣列中。
function unique(origin){
var result = [];
for(var i = 0; i< origin.length; i++) {
var item = origin[i];
if(result.indexOf(item) === -1) {
result.push(item);
}
}
return result;
}
三、陣列的 filter 屬性
陣列的 filter() 方法建立一個新的陣列,新陣列中的元素是通過檢查指定陣列中符合條件的所有元素。
filter 的回撥有三個引數,其中第三個引數是當前元素屬於的陣列物件,這樣我們可以繼續利用 indexOf 屬性啦。
function unique(origin) {
var result = origin.filter(function (item, index, array){
// 獲取元素在源陣列的位置,只返回那些索引等於當前元素索引的值。
return array.indexOf(item) === index;
});
return result;
}
filter 相容到 IE9, 這種方法沒有 for 迴圈,主要利用了 filter 和 indexOf 屬性,所以程式碼相對比較優雅。
四、利用 Object 的 key value
function unique(origin) {
var result = [];
var hashTable = {};
for(var i = 0; i< origin.length; i++) {
// 如果鍵對應的值,為真,意味著物件的鍵中已經有重複的鍵了。
if(!hashTable[origin[i]]) {
// 將元素作為物件的鍵,預設鍵對應的值為 true,
hashTable[origin[i]] = true;
// 如果物件中沒有這個鍵,則將這個元素放入結果陣列中去。
result.push(origin[i]);
}
}
return result;
}
這種方案的事件複雜度為 O(n), 但是物件的鍵,預設是字串型別,這意味著什麼呢,數字 1 和 字串 `1`,在鍵中是相等的,所以,上面這種方法不適合字串和數字混合的去重。
所以我們將元素的型別也放入物件的鍵中:
function unique(origin) {
var result = [];
var hashTable = {};
for(var i = 0; i< origin.length; i++) {
var current = origin[i];
// 字串拼接元素的型別和元素
var key = typeof(current) + current;
if(!hashTable[key]) {
hashTable[key] = true;
result.push(current);
}
}
return result;
}
五、陣列的 sort 方法
function unique(origin) {
return origin.concat.sort().filter(function(item, index, array) {
// !index 表示第 0 個元素應該被返回。
return !index || item !== origin[index-1]
})
}
function unique(array) {
array.sort(); // 排序字串
array.sort(function(a, b) {
return a-b; // 排序數字
})
for(let i=0; i<array.length; i++) {
if(array[i] === array[i+1]) {
array.splice(i, 1);
i--; // 應該將前一個數刪除,而不是刪除後一個數。是因為元素被刪除之後,後面元素的索引會遷移,所以要 i--;
}
}
return array;
}
sort 方法的優點在於利用了排序,返回後一個和前一個不相等的元素。比較簡潔和直觀。缺點在於改變了元素的本來的排序位置。
六、ES6 Set
ES6 提供了新的資料結構 Set,它類似於陣列,但是成員的值都是唯一的,沒有重複的值。向 Set 加入值的時候,不會發生型別轉變,所以 5 和 `5` 是兩個不同的值。Set內部判斷兩個值是否相同,用的是類似於 “===”的演算法,但是區別是,在set內部認為NaN 等於 NaN ;
Set 可以轉換為陣列,所以很容易實現去重
function unique(origin) {
return Array.from(new Set(origin));
}
七、ES6 Map
ES6 新增了 Map 資料結果,通過 has 和 set 方法就能很方便的對前面的 object key value 方案進行優化。
function unique(origin){
const map = new Map()
return origin.filter((item) => !map.has(item) && map.set(item, true))
}
八、型別判斷
一些常見的資料型別是 ===
和 indexOf
是無法檢測的,舉個例子:
console.log({} === {}) // false;
console.log(NaN === NaN) // false;
console.log(/a/ === /a/); // false;
console.log(1 === new String(`1`)) // false;
var arr = [NaN];
console.log(arr.indexOf(NaN)); // -1
所以在判斷的時候,如果資料裡有 NaN 和物件時要避免使用 indexOf
和 ===
;
前面 Set 那裡說過了,所以 Set 方法是可以去重 NaN的。
總結
資料去重在網上已經看煩了,但還是想專門寫一篇文章來實踐和總結,能在工作中多幾個思路也是極好的。感謝那些熱愛分享和喜歡輸出的人。
歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。
掘金專欄 JavaScript 系列文章
- JavaScript之變數及作用域
- JavaScript之宣告提升
- JavaScript之執行上下文
- JavaScript之變數物件
- JavaScript之原型與原型鏈
- JavaScript之作用域鏈
- JavaScript之閉包
- JavaScript之this
- JavaScript之arguments
- JavaScript之按值傳遞
- JavaScript之例題中徹底理解this
- JavaScript專題之模擬實現call和apply
- JavaScript專題之模擬實現bind
- JavaScript專題之模擬實現new
- JS專題之事件模型
- JS專題之事件迴圈
- JS專題之去抖函式
- JS專題之節流函式
- JS專題之函式柯里化
- JS專題之陣列去重