一、数组去重需求
项目需要对类似这种数组去重:
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
函数还不太了解,可以点击下面链接:
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
数组为例:
- 从第一项开始:
lists[0]
的id
为1,index
值为0 - 从
lists
数组里查找id
为1的项,此时可以找到该项的index
为0 - 上面两个
index
值相等,那么返回该项lists[0]
- 第二项,
lists[1]
,按照上面的逻辑,lists[1]
这项也被返回 - 再下一项,
lists[2]
,此时index
值为2,但查找到的项的index
值为0,表明出现了重复项,那么lists[2]
将不再被返回。 - 依次类推,直到得到全部结果。
2. 利用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
数组为例:
- 由于提供了初始值:
[]
,那么第一次循环的currentValue
为lists[0]
:{id: 1,name: '法外狂徒',age: 20}
,accumulator
为初始值[]
,符合我们的判断条件,lists[0]
被放入数组accumulator
内并对obj
属性赋值。第一次循环结束,accumulator
变为:[{id: 1,name: '法外狂徒',age: 20}]
,obj
变为{1: "法外狂徒"}
- 第二次循环时,
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. 利用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种方法,进行了详尽的,近乎啰嗦的解释。后面引出其他方法,其实大致思路是不变的。
希望此文对你有所启发。
如果此文帮到了你,你可以随意赞赏,以示鼓励。