最近公司遇到的一个需要用到对象数组去重的需求,这还不简单?经过长达十数分钟的挣扎,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就是我想出来的.....

你真的研究过对象数组去重吗?-LMLPHP

都是泪啊,不说了,上代码!

   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。我真是个人才。。。

感谢大家能一直看到这个地方,因为是第一次写文章,写的不好的地方,还请包涵;写的不对的地方,还请指正。再次感谢!

08-07 21:21