你真的研究過物件陣列去重嗎?

2022-08-07 18:00:45

    最近公司遇到的一個需要用到物件陣列去重的需求,這還不簡單?經過長達十數分鐘的掙扎,emm...,還是去網上粘一個吧...

    下了班,越想越氣,我已經菜到這種程度了?    痛定思痛,最終在週末花了一下午的時間整理了下物件陣列去重相關的方法

1.雙重for迴圈

在公司的時候第一個想到的就是雙重for迴圈了,實現下。

   let arr = [
      {id: 1, name: 'lsm'},
      {id: 2, name: 'mjl'},
      {id: 1, name: 'lsm'}
    ]
    for (let i = 0; i < arr.length; i++) {
      for (let j = i + 1; j < arr.length; j++) {
        if (arr[i].id === arr[j].id) {
          arr.splice(j, 1)
          j--
        }
      }
    }
    console.log('arr--', arr)

實現倒是實現了,但是看著這麼一大坨......,不行啊,咱寫程式碼要優雅。換方法

2.forEach搭配findindex

想了半天,終於想出來半個,沒錯外層的forEach就是我想出來的.....

都是淚啊,不說了,上程式碼!

   let arr1 = [
      {id: 1, name: 'lsm'},
      {id: 2, name: 'mjl'},
      {id: 1, name: 'lsm'}
    ]
    let newArr1 = []
    arr1.forEach((item, index) => {
     arr1.findIndex(el => el.id == item.id) == index && newArr1.push(item)
    })

    console.log('newArr1', newArr1)

emm,真不錯,優雅

我來解釋下這段程式碼:首先我們先定義一個空陣列newArr1,然後通過forEach遍歷arr1,。當拿到陣列中的每個物件之後,開始進行去重。這裡使用findIndex函數根據物件中的id去重,當內層迴圈(findIndex)中物件的id等於外層迴圈中物件的id的時候,返回內層迴圈該符合條件物件的索引,並且拿該索引與外層迴圈當前索引進行比較。如果相等就把物件push到newArr1中,當遍歷到第三個物件的時候,外層index = 2; 但是內層迴圈中符合條件的索引為0,因此不進行push操作,達到去重效果。

總結來說 :進行雙重回圈,內層迴圈根據findInde函數的特性,找到第一個符合條件的值的索引並與外層索引比較。當有重複的物件時,內層迴圈的索引與外層迴圈的索引並不一致,實現去重。

findIndex作用:找到遍歷的陣列中的第一個符合判斷條件的值,並返回該值對應的索引,停止遍歷

3.filter搭配findIndex

作為一名優秀的程式設計師,只會兩種去重方式怎麼行,繼續去粘,,,繼續總結...

    let arr2 = [
      {id: 1, name: 'lsm'},
      {id: 2, name: 'mjl'},
      {id: 1, name: 'lsm'}
    ]
    arr2 = arr2.filter((item, index) => {
      return arr2.findIndex(el => el.id == item.id) == index
    })
    console.log('arr3--', arr2)

說明下:這種方式,和上述的那種方式是很類似,內層都是用到findIndex的特性,找到內外層迴圈一致的索引。但是外層用的是filter函數,用該函數的好處,我們不需要單獨再去重新定義一個新的陣列。根據該函數的特性,返回一個包含符合條件物件的陣列

filter作用:實現陣列過濾。判斷filter回撥函數中的條件是否為true,如果為true,返回該遍歷項,最終包裝到一個陣列中統一返回

4.forEach搭配some

在總結完上述三個方法之後,我就在想了:陣列去重,如果這個陣列是個基本資料型別的陣列,我們只要遍歷一層,迴圈體裡面只要配合indexOf、includes等方法,就可以找出符合條件的值了,程式碼如下:

    let arr = [ 1, 1, 1, "1", "lsm", "52", 2, 81, 2, 81]
    let newArr = []
    arr.map((item, index) => {
      //!newArr.includes(item) && newArr.push(item)
      newArr.indexOf(arr1[i]) === -1 && newArr.push(arr1[i])
    })
    console.log('newArr--', newArr)

但是,如果是參照型別的陣列,我們沒法通過indexOf、includes直接找到符合條件的值。只能通過雙重回圈的方式,通過內層的迴圈找出符合條件的值。而且根據上述案列,我們可以總結出,內層的迴圈必須要返回一個具體的值用於外層的判斷。那some可不可以呢,some也是返回一個具體的boolean,上程式碼

    let arr3 = [
      {id: 1, name: 'lsm'},
      {id: 2, name: 'mjl'},
      {id: 1, name: 'lsm'}
    ]
    let newArr3 = []
    arr3.forEach((item, index) => {
      !newArr3.some(el => el.id == item.id)  && newArr3push(item)
    })
    console.log('newArr2', newArr2)

經過測試,去重成功,我真是人才呀...

說明一下:思路還是雙重回圈,內層迴圈使用some,根據some特性,判斷newArr3中有沒有物件的id等於當前外層物件中的id,沒有的時候返回false,取反,左側返回true,執行右側的push,當遍歷到第三個物件的時候,內層some判斷為true(newArr3中有了id判斷相等的物件),不執行push操作,達到去重效果

some作用:判斷一個陣列中有沒有符合條件的值,只要陣列中有一項符合條件返回true,但是不會終止迴圈,可以使用return終止迴圈(該例中並沒用到該特性)

其實在寫這段程式碼的時候,寫著寫著就感覺不太對勁了。從程式碼中不難看出,內層的some的第一次迴圈newArr3是一個空陣列,遍歷的每一項el是沒有值的,是一個undefined,然後el.id 相當於 undefined.id ??? 系統不會報錯的嗎?

其實事實是這樣的:當我們使用陣列方法的時候,會先去執行該方法,然後會判斷有沒有遍歷項,當沒有遍歷項的時候(陣列為空)壓根就不會進行迴圈,但是由於方法是執行了的,會有一個返回值,該返回值具體是多少,根據使用的陣列方法而定。 比如:

  • [].some(...)      =>     false
  • [].map(...)        =>     []
  • [].forEach(...)    =>     undefined
  • ......

5.filter和find

按照上面的總結,想進行參照型別陣列去重,得進行雙重for迴圈,內層迴圈要有一個具體的返回值。外層迴圈用來提供去重的物件,可以使用forEach,map等單純提供遍歷的方法,但是需要重新定義一個新的陣列接收符合條件的物件。也可以使用filter,根據條件,直接返回一個新的陣列,不需要重新定義一個新的陣列,也不用進行push操作。和findIndex方法類似的還有find方法,程式碼如下

let arr4 = [
      {id: 1, name: 'lsm'},
      {id: 2, name: 'mjl'},
      {id: 1, name: 'lsm'}
    ]
    arr4 = arr4.filter(item => {
      return arr4.find(el => el.id == item.id) === item
    })
    console.log('arr4', arr4)

和findIndex方法類似,只不過find返回的是符合條件的遍歷項。然後拿該返回項與當前外層的物件比較,物件比較的是兩者的參照地址,當遍歷到第三層物件的時候,內層遍歷出來的實際上是第一個物件,二者的參照地址是不相同的,從而達到去重的效果

這裡不推薦使用find,因為find方法去重,是在陣列中的物件地址都不相同的前提下,如果程式碼中有如下操作則達不到去重效果

    let obj = {id: 10, name: 'lsm'}
    let arr = []
    arr.push(obj)
    arr.push(obj)
    //arr中的兩個物件參照地址實際上是一樣
    //Set不能對物件去重,但是這種情況可以

find作用:找出符合判斷條件的第一項,並進行返回

6.map結合some | find | findexIndex

可能大家看到這裡就在吐槽了,上面總結的時候為什麼,forEach和Filter要混著舉例。但是其實寫這篇文章,最終目的並不是給大家例舉出各種情況,而是想讓大家瞭解除參照資料型別陣列的去重思路。陣列的遍歷方法有很多,可以有很多中搭配方式實現去重,只是單純靠背的話,總有一天會忘,就像我這隻菜菜,只會個for迴圈......

最終總結:

1. 實現參照型別陣列去重,主要靠雙重回圈。

2. 外層的迴圈可以是forEach、map這種方法,單純的給內層迴圈提供去重物件。這要我們在最外面定義一個新的陣列,用來存放符合條件的陣列。也可以用filter方法,根據filter的特性返回符合條件的陣列,不用自定義新陣列。

3. 內層的函數實現去重,並且內層的函數要有一個有具體含義的返回值,用於外層函數的判斷。可以是some,findIndex,find等。

具體情況是不是與上述總結一直呢。我們最後使用map和some、find、findexIndex再來證明一遍

    let arr5 = [
      {id: 1, name: 'lsm'},
      {id: 2, name: 'mjl'},
      {id: 1, name: 'lsm'}
    ]
    let newArr5 = []
    arr5.map((item, index) => {
      // !newArr5.some(el => el.id == item.id) && newArr5.push(item)
      // arr5.findIndex(el => el.id === item.id) === index && newArr5.push(item)
      arr5.find(el => el.id === item.id) === item && newArr5.push(item)
    })
    console.log('arr5', newArr5)

有問題嗎?沒有問題。no problem。我真是個人才。。。

感謝大家能一直看到這個地方,因為是第一次寫文章,寫的不好的地方,還請包涵;寫的不對的地方,還請指正。再次感謝!