本篇文章參考以下博文
文章目錄
前言
最近在處理數據時搜到一個去重方法,發現使用起來逼格很高,在此總結記錄一下衆多去重方法,並比較一下不同方法間的性能,作爲以後裝逼的談資。
測試模板
通過 from 方法創建兩個數組,一個是 十萬數據,一個是 五萬數據 ,連接這兩個數組之後去重,查看去重時間。
// distinct.js
let arr1 = Array.from(new Array(100000), (x, index)=>{
return index
})
let arr2 = Array.from(new Array(50000), (x, index)=>{
return index+index
})
let start = new Date().getTime()
console.log('開始數組去重')
function distinct(a, b) {
// 數組去重
}
console.log('去重後的長度', distinct(arr1, arr2).length)
let end = new Date().getTime()
console.log('耗時', end - start)
一、數組去重性能測試
1.1 Array.filter() + indexOf
這個方法是通過 filter 篩選,數組中每一個元素的索引,是不是等於它在數組中第一次出現的位置,如果是說明這個元素第一次出現,可以 return 出去,如果不是說明前面已經有相同的數據了,就不 return 。
function distinct(a, b) {
let arr = a.concat(b);
return arr.filter((item, index)=> {
return arr.indexOf(item) === index
})
}
執行時間如下。
由於是第一個,還沒有參照,先做個記錄。
1.2 for 循環嵌套
外層循環遍歷,內層循環檢查是否重複。
function distinct(a, b) {
let arr = a.concat(b);
for (let i=0, len=arr.length; i<len; i++) {
for (let j=i+1; j<len; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
// splice 會改變數組長度,所以要將數組長度 len 和下標 j 減一
len--;
j--;
}
}
}
return arr
}
難怪大佬們都不喜歡用 for 循環,這效率,差的不是一個量級。
1.3 for…of + includes()
跟上面的用法差不多,都是循環,只不過把 for 變成了 for…of 當然也可以使用 indexOf() 代替 includes() 。
function distinct(a, b) {
let arr = a.concat(b)
let result = []
for (let i of arr) {
!result.includes(i) && result.push(i)
}
return result
}
看到 for…of 的效率了不,以後乖乖把 for 都替換掉。
1.4 利用hasOwnProperty
利用 hasOwnProperty 判斷是否存在對象屬性,利用了對象屬性不能重複的特點。
function distinct(a, b) {
let arr = a.concat(b)
var obj = {};
return arr.filter(function(item, index, arr){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
這個性能提升就很明顯了,直接從八千多降到一百多。
1.5 Array.sort()
先快排,然後查看相鄰數據有沒有重複的。
function distinct(a, b) {
let arr = a.concat(b)
arr = arr.sort()
let result = [arr[0]]
for (let i=1, len=arr.length; i<len; i++) {
arr[i] !== arr[i-1] && result.push(arr[i])
}
return result
}
驚不驚喜,意不意外,你就說迪奧不迪奧吧。
1.6 new Set()
這個比較經典了,通過 ES6 的 Set 對象實現,應爲 Set 的成員具有唯一性,感覺天生是爲去重而創造的。
function distinct(a, b) {
return Array.from(new Set([...a, ...b]))
}
Emmmmmm…這說明在去重的時候,專門做去重的,就是比專門做排序的方法快,快一毫秒也是快。
1.7 利用Map數據結構去重
準確的說,這是用了 map 對象裏面的一些方法, map 中存儲的鍵值對會提供一些數組沒有的方法,來達到去重的目的。
function distinct(a, b) {
let arr = a.concat(b)
let map = new Map();
let array = new Array(); // 數組用於返回結果
for (let i = 0; i < arr.length; i++) {
if(map.has(arr[i])) { // 如果有該key值
map.set(arr[i], true);
} else {
map.set(arr[i], false); // 如果沒有該key值
array .push(arr[i]);
}
}
return array ;
}
ES6 的東西就是好用,這幾個新提供的對象在性能方面優秀的一批。
1.8 for…of + Object
這個是利用對象的屬性不會重複的特性,上面1.4方法也是用了屬性不重複特性,不過本方法是直接利用特性去重,而上面的方法是把屬性不重複的這一特性轉化成了代碼,然後再通過代碼驗證。
function distinct(a, b) {
let arr = a.concat(b)
let result = []
let obj = {}
for (let i of arr) {
if (!obj[i]) {
result.push(i)
obj[i] = 1
}
}
return result
}
這有點嚇人,恐怖如斯。 JS 有個特點,它的某些極致的功能,很多都是通過它的一些特性去解決的,這就很有意思。
1.9 reduce
給大夥來個娛樂局,下面這個是通過 reduce 的回調函數, reduce 中回調函數各個形參的含義可以戳這裏
function distinct(a, b) {
let arr = a.concat(b)
arr = arr.reduce((prev, current) => {
return prev.includes(current) ? prev : prev.concat(current);
}, []);
return arr
}
娛樂局嘛,這個的時間有點長的離譜了,不過 reduce 方法用來去重,不是去簡單數據類型的,而是用來對引用數據類型進行去重的,比如說:
let arr = [{id: 123, name: 1}, {id: 123, name: 2}, {id: 123, name: 3}]
let newobj = {};
arr = arr .reduce((prev, current) => {
newobj[current.id] ? '' : newobj[current.id] = prev.push(curVal);
return prev
}, [])
//arr[{id: 123, name: 1}]
上面的方法中,就是對數組中的對象按照 id 屬性進行去重的。
總結
爲了方便查看,我做了個對照表格
名次 | 方法 | 耗時(ms) |
---|---|---|
1 | for…of + Object | 16 |
2 | new Set() | 23 |
3 | Array.sort() | 24 |
4 | Map數據結構 | 30 |
5 | hasOwnProperty | 131 |
6 | for…of + includes() | 8660 |
7 | Array.filter() + indexOf | 8852 |
8 | for 嵌套 | 14546 |
9 | reduce | 58715 |
上面測試用到for循環的時候,都先把arr.length計算出來,然後再進行比較,而不是直接 i<arr.length
這是爲了提升 for 循環的效率,具體提升有多少,各位可以跳轉下面這個博客,提升的性能超乎你的想象。
for (let i=1, len=arr.length; i<len; i++)
JavaScript,for循環效率測試,不同遍歷循環測試,數組添加效率測試,大數組拼接測試,for循環遍歷修改 和 string replace效率