老生常谈JS数组去重

一、数组去重需求

项目需要对类似这种数组去重:

const lists = [{
  id: 1,
  name: '法外狂徒',
  age: 20
},
{
  id: 3,
  name: '张三',
  age: 20
},
{
  id: 1,
  name: '法外狂徒',
  age: 20
},
{
  id: 4,
  name: '李四',
  age: 20
}]

该数据去重时以id作为标识。

二.方案

1. 利用filter,findIndex函数

如果你对filter, findIndex函数还不太了解,可以点击下面链接:

数组的filter方法介绍(点我)

数组的findIndex方法介绍(点我)

function uniqueArray (list) {
  const res = list.filter((item, index, self) => {
    return self.findIndex(list => list.id === item.id) == index
  }) 
  return res
}

该方法的思路:两层循环。filter函数循环数组中的每一项,找到该项的id与数组(self)中相同id的项,比对这两个index值是否相等。当出现index值不等,就表示出现了重复数据,那么该重复项将不会被返回

以上面lists数组为例:

  1. 从第一项开始:lists[0]id为1,index值为0
  2. lists数组里查找id为1的项,此时可以找到该项的index为0
  3. 上面两个index值相等,那么返回该项lists[0]
  4. 第二项,lists[1],按照上面的逻辑,lists[1]这项也被返回
  5. 再下一项,lists[2],此时index值为2,但查找到的项的index值为0,表明出现了重复项,那么lists[2]将不再被返回。
  6. 依次类推,直到得到全部结果。

2. 利用reduce函数

如果你对reduce函数还不太了解,可以点击下面链接:

数组的reduce方法介绍(点我)

简单介绍一下reduce:该方法会对数组中的每个元素执行一个操作(这个操作是你定义的回调函数),并将结果汇总为单个值返回。

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

如上所述,它接受一个回调函数,以及一个可选的initialValue初始值作为参数。

而回调函数接收四个参数:

  • accumulator:累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue
  • currentValue:数组中正在处理的元素。
  • index(可选):数组中正在处理的当前元素的索引。 如果提供了initialValue,则起始索引号为0,否则从索引1起始。
  • array(可选):调用reduce()的数组

也就是:

  • 如果你提供了initialValue初始值,那么第一次循环currentValue的值为数组第一项arr[0]
  • 如果你没提供initialValue初始值,那么此时的accumulator是上一次调用回调时返回的值,为arr[0],此时currentValue的值就变成了arr[1]注意这个加黑的返回
   const arr = ['java', 'python', 'js']
   arr.reduce((accumulator, currentValue, index) => {
     console.log('accumulator:' + accumulator, index)
     console.log('currentValue:'+ currentValue, index)
   })
   //大家仔细看下打印的结果:
	// accumulator:java 1
	// currentValue:python 1
	// accumulator:undefined 2
	// currentValue:js 2

上面代码确实如我们所说 因为没有提供初始值,上面数组是从数组下标为1开始循环的。

但是为什么第二次循环时accumulator值为undefined呢,那是因为我们在回调里没有显式的return返回值的操作,那么accumulator就拿不到上次循环的返回值,变为undefined

所以你一定要记住reduce是对数组的累计操作,在使用中一定要return返回你希望得到的数据

function uniqueArray(list) {
  let obj = {}
  const res = list.reduce((accumulator, currentValue) => {
  	const {id, name} = currentValue
     if (!obj[id]) accumulator.push(currentValue), obj[id] = name
     return accumulator
   }, [])
   return res
}

该方法的思路: 利用对象属性的唯一特性去重。将数组项的id作为对象的属性,如果对象还没有该属性,表示数组中的该项不重复,那么就将该项添加到accumulator中,并且给对象的该属性赋一个值,上面代码是把name值赋给该属性。实际操作中你可以给它赋任意一个基本数据类型的值。那我要是不给它赋值呢?各位可以去掉后面的赋值语句运行一下就知晓。

仍以上面lists数组为例:

  1. 由于提供了初始值:[],那么第一次循环的currentValuelists[0]{id: 1,name: '法外狂徒',age: 20}, accumulator为初始值[],符合我们的判断条件,lists[0]被放入数组accumulator内并对obj属性赋值。第一次循环结束,accumulator变为:[{id: 1,name: '法外狂徒',age: 20}], obj变为{1: "法外狂徒"}
  2. 第二次循环时,currentValue变为lists[1]{id: 3,name: '张三',age: 20},仍符合判断条件, lists[1]被放进accumulator内并对obj属性赋值。第二次循环结束,accumulator变为:[{id: 1,name: '法外狂徒',age: 20}, {id: 3,name: '张三',age: 20}]obj变为:{1: "法外狂徒", 3: "张三"}
  3. 重复上面步骤,直到循环结束。

3. 利用map函数

基于第2种的实现思路,可以使用map函数:

function uniqueArray(list) {
  let obj = {}, arr = []
  list.map((item, index) => {
    const {id, name} = item
    if(!obj[id]) obj[id] = name, arr.push(item)
  })
  return arr
}

这样理解起来似乎比较容易。

其中:

const {id, name} = item
//相当于
const id = item.id
const name = item.name

4. 利用reduce、find函数

弄懂了第1,2两种方法之后,这个方法算是它们的一个变种,也比较容易理解。

function uniqueArray(list) {
  const res = list.reduce((accumulator, currentValue) => {
    (!accumulator.find(item => item.id == currentValue.id)) && accumulator.push(currentValue)
    return accumulator
  }, [])
  return res
}

思路:当累加器累计返回的数组accumulator里不包含当前元素currentValue这个重复数据的话,就将其放入accumulator数组内,否则什么也不做。

5. 利用for循环、splice函数

上面利用reduce方法基本都是生成一个新数组,数组里存放的是去重后的数据。而此方法则是直接从原数组里找到重复的数据,并删除该项数据。

function uniqueArray(list) {
  let obj = {}
  for(let i = 0; i < list.length; i ++) {
    if(obj[list[i].id]) {
      list.splice(i, 1)
      i--
    } else {
      obj[list[i].id] = list[i].name
    }
  }
}

注意当删除重复项的时候需要将索引手动的减1,这里牵扯到一个数组塌陷问题。

关于数组塌陷,简单举例:

let arr = [1, 2, 3, 4, 5, 6]
for( let i = 0; i < arr.length; i ++) {
  if(arr[i] > 3) {
  	arr.splice(i, 1)
  }
}
// 运行后发现 arr为 [1, 2, 3, 5]

上面代码是想删除数组arr中大于3的项,但是结果并不是我们预期的。
在这里插入图片描述

可以看到在循环到i = 3的时候,删除了 4这一项,删完之后 i = 3这一项就变成了 5,下次循环时i 变为 4,6这项被删除。那么被圈住的这项事实上被过了。




三、总结

对第1,2种方法,进行了详尽的,近乎啰嗦的解释。后面引出其他方法,其实大致思路是不变的。

希望此文对你有所启发。




如果此文帮到了你,你可以随意赞赏,以示鼓励。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值