JavaScript——數組去重總結筆記

本文是筆者閱讀某數組去重文章後的我的筆記,每個方法都有動手測試。
文章連接:
juejin.im/post/5b0284… github.com/mqyqingfeng…javascript

測試代碼以下:java

var arr = [];
// 生成[0, 100000]之間的隨機數
for (let i = 0; i < 100000; i++) {
  arr.push(0 + Math.floor((100000 - 0 + 1) * Math.random()))
}

console.time('測試時長:');
arr.unique();
console.timeEnd('測試時長:');
複製代碼

注:每種方法都是對同一已去重的數組arr進行測試。git

一、最簡單粗暴—雙層循環嵌套

沒有什麼算法是for循環解決不了的,一個不夠,就再嵌套幾個o.o....github

寫法1:嵌套循環目標數組,循環中設定開關判斷是否有重複
Array.prototype.unique = function(){
    let newArr = [];
    let arrLen = this.length;
    for(let i = 0; i < arrLen; i++){
        let bRepeat = true;
        for(let j = 0;j < arrLen; j++){
            if(this[i] === newArr[j]){
                flg = false;
                break;
            }
        }
        
        if(bRepeat){
            newArr.push(this[i]);
        }
    }
    return newArr;
}
複製代碼
寫法2:嵌套循環目標數組與新數組,循環中設定開關判斷是否有重複
Array.prototype.unique = function(){
    let newArr = [];
    
    for(let i = 0, arrLen = this.length; i < arrLen; i++){
        let bRepeat = true;
        for(let j = 0, resLen = newArr.length; j < resLen; j++){
            if(this[i] === newArr[j]){
                flg = false;
                break;
            }
        }
        
        if(bRepeat){
            newArr.push(this[i]);
        }
    }
    return newArr;
}
複製代碼
寫法3:嵌套循環目標數組與新數組,不設定開關,經過內層循環計數變量j與新數組的長度比較,來判斷是否有重複元素。
Array.prototype.unique = function(){
    let newArr = [];
    
    for(let i = 0, arrLen = this.length; i < arrLen; i++){
        for(var j = 0, resLen = newArr.length; j < resLen; j++){
            if(this[i] === newArr[j]){
                break;
            }
        }
        
        //若是新數組長度與j值相等,說明循環走完,沒有重複的此元素
        if(j === resLen){
            newArr.push(this[i]);
        }
    }
    return newArr;
}
複製代碼

雙層循環時長:算法

寫法1:28014.736083984375ms
寫法2:3643.562255859375ms
寫法3:2853.471923828125ms
複製代碼

優勢: 兼容性好,簡單易懂
擴展思考:有效的減小循環次數及臨時變量的產生可提高代碼執行效率。數組

二、利用indexOf

ES6給數組原型添加indexOf()方法,返回在該數組中第一個找到的元素位置,若是它不存在則返回-1瀏覽器

方法1:for循環內,利用indexOf判斷
Array.prototype.unique = function(){
    let newArr = [];
    
    for(let i = 0, arrLen = this.length; i < arrLen; i++){
        if(this.indexOf(this[i]) === -1){
            newArr.push(this[i]);    
        }
    }
    return newArr;
}
複製代碼
方法2:Array.prototype.filter()+Array.prototype.indexOf()。

filter()方法使用指定的函數測試全部元素,並建立一個包含全部經過測試的元素的新數組。經過數組的過濾效果,拿每個元素的索引與indexOf(當前元素)返回的值比較。數據結構

Array.prototype.unique = function(){
    let newArr = this.filter((item, index) => {
        return this.indexOf(item) === index;
    });
    return newArr;
}
複製代碼
方法3:Array.prototype.forEach()+Array.prototype.indexOf()
Array.prototype.unique = function(){
    let newArr = [];
    this.forEach(item => {
        if(this.indexOf(item) === -1){
            newArr.push(item);
        }
    });
    return newArr;
}
複製代碼
方法4:for-of循環+indexOf

ES6新增for-of循環,比for-in循環,forEach更強大好用dom

Array.prototype.unique = function(){
    let newArr = [];
    for(let item of this){
         if(this.indexOf(item) === -1){
            newArr.push(item);
        }
    }
    return newArr;
}
複製代碼

測試:函數

方法1: 4826.351318359375ms
方法2: 4831.322265625ms
方法3: 4717.027099609375ms
方法4: 4776.078857421875ms
複製代碼

擴展思考:一層循環的效果差很少。關於遍歷,建議用for-of。

三、Array.prototype.includes()

includes用於找元素,找到返回true,找不到返回false。相比較indexOf,includes更天然,且能判斷NaN
Array.prototype.unique = function(){
    let newArr = [];
    for(let item of this){
         if(!newArr.includes(item)){
            newArr.push(item);
        }
    }
    return newArr;
}
複製代碼

測試:3700.76220703125ms
結論:相比較indexOf更快!且代碼更優雅

四、Array.prototype.sort()

方法1:先對數組進行排序,在遍歷過程當中判斷元素是否與後一個元素相同
Array.prototype.unique = function(){
    let newArr = [];
    this.sort();
    for(let i = 0, arrLen = this.length; i < arrLen; i++){
        if(this[i] !== this[i+1]){
            newArr.push(this[i]);    
        }
    }
    return newArr;
}
複製代碼
方法2:先對數組進行排序,在遍歷過程當中判斷元素是否與新數組的最後一個元素相同
Array.prototype.unique = function(){
    let newArr = [];
    this.sort();
    for(let i = 0, arrLen = this.length; i < arrLen; i++){
        if(this[i] !== newArr[newArr.length - 1]){
            newArr.push(this[i]);    
        }
    }
    return newArr;
}
複製代碼

測試:

100.182861328125ms
 89.683837890625ms
複製代碼

擴展思考:

有序數組的遍歷速度要比無序數組快好多倍!!!並且不僅是javascript語言,其餘語言也這樣,好比java,Python!!若是實戰中存在大數組遍歷,建議可先排序!
複製代碼

結合以上分析再優化:

Array.prototype.unique = function(){
    //加concat(),防止污染原數組,固然,僅針對一維數組
    return this.concat().sort().filter((item, index) =>{
        return item !== this[index+1];
    });
}
複製代碼

測試:

89.2060546875ms
複製代碼

五、reduce

reduce() 方法接收一個函數做爲累加器,數組中的每一個值(從左到右)開始縮減,最終計算爲一個值。

Array.prototype.unique = function(){
    //僅針對一維數組
    return this.concat().sort().reduce((total, item) =>{
        if(!total.includes(item)){
            total.push(item);
        }
        return total;
    }, []);
}
複製代碼

測試:

4022.2578125ms
複製代碼

這個慢多了o.o....

六、Object 鍵值對

原理是利用對象的鍵的惟一性。
但須要注意:

  • 沒法區分隱式類型轉換成字符串後同樣的值,即字面量相同的數字和字符串,好比 1 和 '1';
  • 沒法處理複雜數據類型,好比對象(由於對象做爲 key 會變成 [object Object]);
  • 特殊數據,好比key爲 'proto',由於對象的 proto 屬性沒法被重寫。

6.1 普通對象

解決第1、第三點問題,實現一:

Array.prototype.unique = function () {
  const newArray = [];
  const tmp = {};
  for (let i = 0, arrLen = this.length; i < arrLen; i++) {
    if (!tmp[typeof this[i] + this[i]]) {
      tmp[typeof this[i] + this[i]] = 1;
      newArray.push(this[i]);
    }
  }
  return newArray;
}
複製代碼

解決第二點問題,實現二:

Array.prototype.unique = function () {
  const newArray = [];
  const tmp = {};
  for (let i = 0, arrLen = this.length; i < arrLen; i++) {
    // 使用JSON.stringify()進行序列化
    if (!tmp[typeof this[i] + JSON.stringify(this[i])]) {
      // 將對象序列化以後做爲key來使用
      tmp[typeof this[i] + JSON.stringify(this[i])] = 1;
      newArray.push(this[i]);
    }
  }
  return newArray;
}
複製代碼

優化:

// 使用 JSON.stringfiy 處理
    Array.prototype.unique = function () {
            return this.filter((item, index) => {
                return this.findIndex(element => {
                    return JSON.stringfy(item) === JSON.stringfy(element)
                }) === index;
            });
       }
    }
複製代碼

測試:

實現一:104.859130859375ms
實現二:120.89697265625ms
複製代碼

6.2 ES6

ES6新增了Set和Map 數據結構,性質與java相似。如set對象相似數組,成員都是不重複的。

  • Array.from()+Set()
    from用於將類數組對象轉爲真正的數組,是數組的靜態方法

    Array.prototype.unique = function(){
              return Array.from(new Set(this));
      }
    複製代碼

    測試:

    20.884033203125ms
    複製代碼

    利用擴展運算符在簡化:

    Array.prototype.unique = function(){
                  return [...new Set(this)];
      }
    複製代碼

    測試:

    16.0419921875ms
    複製代碼

是否是速度更快了??

  • Map

    方法1:

    Array.prototype.unique = function () {
          let newArray = [];
          let map = new Map();
          for (let item of this) {
              if (!map.has(item)) {
                map.set(item, 1);
                newArray.push(item);
              }
          }
           return newArray;
      }
    複製代碼

    方法2:更優雅的寫法

    Array.prototype.unique = function () {
          let map = new Map();
          return this.filter(item => {
              return map.has(item) || map.set(item, 1);
          });
      }
    複製代碼

    測試:

    方法1: 20.84130859375ms
      方法2: 16.893798828125ms
    複製代碼

七、結論

結合ES6的一些新特性,數組去重速率能夠提升上百倍!代碼的簡介性和優雅度也大大提升! 雖然數組去重有不少種方法,寫法不一樣,速度不一樣,但並非最快的就是最好的,適合場景纔是第一要求。 好比如下數組:

let arr = [1, '1', { name: 1, age: 12 }, { name: 1, age: 12 }, , , {},{}, undefined,undefined ,NaN,NaN,null,null, [],[]];
複製代碼

這種數組就得不是上面每一種方法都實用了。

再好比:

var arr = [1, 2, NaN];
arr.indexOf(NaN); // -1
arr.includes(NaN) // true
複製代碼

因此,選擇哪一種去重方法,必須結合業務、數據元素類型以及瀏覽器兼容性等因素來考慮。

相關文章
相關標籤/搜索