去重是開發中常常碰到的一個問題,但在實際開發中大多數是後臺接口去重,簡單高效,基本不會讓前端處理去重。固然這並非說前端去重就沒有必要了,依然須要會熟練使用,本文主要介紹幾種常見的數組去重的方法的思路前端
爲了測試這些方法的性能,如下提供一個簡單測試模版用來計算數組去重的耗時數組
const arr1 = Array.from(new Array(100000), (x, index)=>{
return index
});
const arr2 = Array.from(new Array(50000), (x, index)=>{
return index+index
});
const start = new Date().getTime();
console.log('開始數組去重');
function unique(a, b) {
let arr = a.concat(b);
// 數組去重
}
console.log('去重後的長度', unique(arr1, arr2).length);
let end = new Date().getTime();
console.log('耗時', end - start);
複製代碼
利用對象的屬性不會重複的特性
這裏運用了一個簡單的哈希結構,當數組中的數據出現過一次後,就在 obj 中將這個元素的值的位置標記爲 1(或 true),後面若出現相同的屬性值,由於該位置已是 1,因此就不會再添加到新數組裏,從而達到了去重的效果markdown
function unique(arr) {
if (!Array.isArray(arr)) {
return;
}
let obj = {};
let newArr = [];
for(let i = 0; i < arr.length; i ++) {
if(!obj[arr[i]]) {
obj[arr[i]] = 1;
newArr.push(arr[i]);
}
}
return newArr;
}
// [1, "true", 15, false, undefined, null, NaN, 0, "a", {}]
// 兩個 true 直接去掉了
unique([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]);
// [1, "true", 15, false, undefined, null, NaN, 0, "a", {a:{b:1}}, [1,2]]
unique([1,1,'true','true',true,true,15,15,false,'false',false, undefined,'undefined', null,'null', NaN, NaN,'NaN', 0, 0, 'a', 'a',{a:{b:1}},{a:{b:1}}, -0,+0, [1,2], [1,2]])
複製代碼
利用上面的測試模板,該方法處理 15W 的數據大概須要 18ms數據結構
ES6 的 set 與解構賦值去重
ES6 新增了 Set 這一數據結構,相似於數組,但 Set 的成員具備惟一性,不考慮兼容性,這種去重的方法代碼最少,這種方法還沒法去重對象性能
function unique(arr) {
if (!Array.isArray(arr)) {
return;
}
return [...new Set(arr)]
}
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
unique([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]);
// [1, "true", true, 15, false, "false", undefined, "undefined", null, "null", NaN, "NaN", 0, "a", {a: {b: 1}}, {a: {b: 1}}, [1, 2], [1, 2]]
unique([1,1,'true','true',true,true,15,15,false,'false',false, undefined,'undefined', null,'null', NaN, NaN,'NaN', 0, 0, 'a', 'a',{a:{b:1}},{a:{b:1}}, -0,+0, [1,2], [1,2]])
複製代碼
`ES6 的 Set 與 Array.from()`
與上面方法相似,性能也差很少,只是 `new Set()` 後獲得類數組轉換成數組的方式不同
```js
function unique(arr) {
if (!Array.isArray(arr)) {
return;
}
return Array.from(new Set(arr));
}
複製代碼
Array.sort()
sort() 將數組進行排序而後比較相鄰元素是否相等,從而排除重複項(這種方法只作了一次排序和一次循環,因此效率會比下面的方法要高),不能很好去重 NaN
和 對象
測試
function unique(arr) {
if (!Array.isArray(arr)) {
return;
}
arr = arr.sort();
let res = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
res.push(arr[i]);
}
}
return res;
}
function unique(arr) {
if (!Array.isArray(arr)) {
return;
}
return arr.concat().sort().filter(function(item, index, arr){
return !index || item !== arr[index - 1]
})
}
// [0, 1, 15, NaN, NaN, "NaN", {}, {}, "a", false, null, "true", true, undefined]
unique([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}]);
// [0, 1,[1,2], [1,2], 15, NaN, NaN, "NaN", {}, {}, "a", false, "false", false, null, "null", "true", true, "undefined", undefined]
unique([1,1,'true','true',true,true,15,15,false,'false',false, undefined,'undefined', null,'null', NaN, NaN,'NaN', 0, 0, 'a', 'a',{a:{b:1}},{a:{b:1}}, -0,+0, [1,2], [1,2]])
複製代碼
利用 indexOf
indexOf() 方法可返回某個指定元素在數組中首次出現的位置,該方法首先定義一個空數組 res,而後調用 indexOf 方法對原來的數組進行遍歷判斷,若元素不在 res 中則將其 push 進 res,最後將 res 返回,這種方法沒法去掉對象
和NaN
ui
function unique(arr) {
if (!Array.isArray(arr)) {
return;
}
let res = [];
for(let i = 0; i < arr.length; i ++) {
if (res.indexOf(arr[i]) == -1) {
res.push(arr[i])
}
}
return res;
}
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {}, {}]
unique([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
// [1, "true", true, 15, false, "false", undefined, "undefined", null, "null", NaN, NaN, "NaN", 0, "a", {}, {}, [1,2], [1,2]]
unique([1,1,'true','true',true,true,15,15,false,'false',false, undefined,'undefined', null,'null', NaN, NaN,'NaN', 0, 0, 'a', 'a',{a:{b:1}},{a:{b:1}}, -0,+0, [1,2], [1,2]])
複製代碼
性能挺糟糕spa
filter + indexOf
prototype
function unique(arr) {
if (!Array.isArray(arr)) {
return
}
return arr.filter(function(item, index, arr) {
//當前元素,在原始數組中的第一個索引==當前索引值,不然返回當前元素
return arr.indexOf(item, 0) === index;
});
}
// [1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {}, {}]
unique([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
// [1, "true", true, 15, false, "false", undefined, "undefined", null, "null", "NaN", 0, "a", {}, {}, [1,2], [1,2]]
unique10([1,1,'true','true',true,true,15,15,false,'false',false, undefined,'undefined', null,'null', NaN, NaN,'NaN', 0, 0, 'a', 'a',{a:{b:1}},{a:{b:1}}, -0,+0, [1,2], [1,2]])
複製代碼
看起來代碼比較簡潔,可是性能也不怎麼樣。。。3d
雙層循環 + include
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 檢測數組是否有某個值
array.push(arr[i]);
}
}
return array
}
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
unique([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
// [1, "true", true, 15, false, "false", undefined, "undefined", null, "null", NaN, "NaN", 0, "a", {}, {}, [1,2], [1,2]]
unique([1,1,'true','true',true,true,15,15,false,'false',false, undefined,'undefined', null,'null', NaN, NaN,'NaN', 0, 0, 'a', 'a',{a:{b:1}},{a:{b:1}}, -0,+0, [1,2], [1,2]])
複製代碼
雙層循環 + push
雙重for(或while)循環是比較笨拙的方法,它實現的原理很簡單:先定義一個包含原始數組第一個元素的數組,而後遍歷原始數組,將原始數組中的每一個元素與新數組中的每一個元素進行比對,若是不重複則添加到新數組中,最後返回新數組;由於它的時間複雜度是O(n^2),若數組長度很大則將會很是耗費內存
function unique(arr) {
if (!Array.isArray(arr)) {
return
}
let res = [arr[0]]
for (let i = 1; i < arr.length; i++) {
let flag = true
for (let j = 0; j < res.length; j++) {
if (arr[i] === res[j]) {
flag = false;
break
}
}
if (flag) {
res.push(arr[i])
}
}
return res
}
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {}, {}]
unique([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
複製代碼
這種方法沒法去重 對象
和 NaN
。但一樣是雙層循環,這個方法會比下面的方法性能好很多
雙層循環 + splice
雙層循環,外層循環元素,內層循環時比較值,值相同時,則刪去這個值,這種方法沒法去重 對象
和 NaN
function unique(arr) {
if (!Array.isArray(arr)) {
return
}
let len = arr.length;
for(let i = 0; i < len; i ++) {
for (let j = i + 1; j < len; j ++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
len --;
j --;
}
}
}
return arr;
}
// [/a/, /a/, "1", ƒ, {}, {}, NaN, NaN, null, "null", "undefined"]
unique([/a/, /a/, "1", 1, String, 1,{}, {}, String, NaN, NaN, null,'null', 'undefined',undefined])
複製代碼
這種方法佔用的內存較高,效率也是最低的
hasOwnProperty
利用 hasOwnProperty 判斷是否存在對象屬性
function unique(arr) {
var obj = {};
return arr.filter(function(item, index, arr){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}]
unique([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
複製代碼
ES6 的 Map 數據結構
建立一個空 Map 數據結構,遍歷須要去重的數組,把數組的每個元素做爲 key 存到 Map 中。因爲 Map 中不會出現相同的 key 值,因此最終獲得的就是去重後的結果
function unique(arr) {
let map = new Map();
let res = 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 值
res.push(arr[i]);
}
}
return res;
}
//1, "true", true, 15, false, undefined, "undefined", null, "null", NaN, "NaN", 0, "a", {}, {}
unique([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, 'undefined',null,null, 'null',NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
複製代碼
reduce + includes
function unique(arr){
return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]);
}
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
unique([1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}])
複製代碼
去重 {}, NaN, undefined, null
{}
的比較真心很差作,有殘缺性的比較能夠這樣寫 JSON.stringify({}) == '{}'
for-in
+ call
+ for
方案// 判斷空對象
function isEmptyObj (obj) {
if (Object.prototype.toString.call(obj) === "[object Object]") {
for (let i in obj) {
// 存在屬性或方法,則不是空對象
return false
}
return true;
} else {
return false;
}
}
function unique (arr) {
let temp = [];
let emptyObjMark = true; // 標識位
let NaNObjMark = true; // 標識位
// 傳入值須存在且長度小於等於 1 時直接返回數組
if (arr && arr.length <= 1) {
return arr;
} else {
// 遍歷當前數組
for (let i = 0, len = arr.length; i < len; i++) {
// 標識位的做用是用來判斷是否存在 NaN 和 空對象,第一次找到保留到新數組中
// 而後標識位改成 false 是爲了再次找到時不推入數組
if (isEmptyObject(arr[i])) {
emptyObjMark && temp.indexOf(arr[i]) == -1 ? temp.push(arr[i]) : '';
emptyObjMark = false;
} else if (arr[i] !== arr[i]) {
NaNObjMark && temp.indexOf(arr[i]) == -1 ? temp.push(arr[i]) : '';
NaNObjMark = false;
} else {
temp.indexOf(arr[i]) == -1 ? temp.push(arr[i]) : '';
}
}
}
return temp;
}
// [1, "true", true, 5, "F", false, undefined, null, NaN, {}, "{}", 0, "a"]
unique([1,1,'true',true,true,5,'F',false, undefined, null,null,undefined, NaN,{},{},'{}', 0, 1, 'a', 'a', NaN])
複製代碼