删除数组中元素问题辨析

181 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

删除一个元素

当我们想要删除数组中某个元素的时候,非常直观的思路是这样的:

  1. 获取该元素在数组中的索引;
  2. 然后使用数组的 splice 方法实现删除功能。

很容易我们会写出如下的函数:

let commonArrayDelete = function(deletedArr,deletedEle){
    let index = deletedArr.indexOf(deletedEle)
    console.log(deletedArr.splice(index,1))
    console.log('',deletedArr)
}

下面我们构造四种不同的数组进行测试。

测试1:纯数字元素组成的数组:

let numArr = [1,2,3,4,5,6,7,8,9,10];
commonArrayDelete(numArr,3)

输出:
被删除元素 [3]
原始数组 (9) [1, 2, 4, 5, 6, 7, 8, 9, 10]

测试2:纯字符串元素组成的数组:

let strArr = ['a','b','c','d'];
commonArrayDelete(strArr,'c')

输出:
被删除元素 ["c"]
原始数组 (3) ["a", "b", "d"]

测试3:纯数组元素组成的数组:

let arrArr = [[1,2,3],[4,5,6],[7,8,9]]
commonArrayDelete(arrArr,[1,2,3])

输出:
被删除元素 [7, 8, 9]
原始数组 (2) [[1,2,3],[4,5,6]]

测试4:纯对象元素组成的数组:

let objArr = [
    {name:'小a',id:1},
    {name:'小b',id:2},
    {name:'小c',id:3},
    {name:'小d',id:3},
    {name:'小e',id:5},
    {name:'小f',id:7},
  ]
commonArrayDelete(objArr,{name:'小d',id:3})

输出:
被删除元素 [{name: "小f", id: 7}]
原始数组 (5) [
    {name:'小a',id:1},
    {name:'小b',id:2},
    {name:'小c',id:3},
    {name:'小d',id:3},
    {name:'小e',id:5},
  ]

通过实验我们可以发现,测试1纯数字元素和测试2纯字符串元素数组得到了我们想要的结果,而纯数组元素和纯对象元素组成的数组却是删除了最后一个元素,why?

多思考一步,以下三个知识点就浮现在脑海中:

  1. 对象地址引用。数组在声明的时候在内存中开辟了一块空间并分配给了各个对象元素,而 arr [i] 指向了第 i 个对象的地址,也就是说只有通过 arr[i] 才能引用到数组中声明的第 i 个对象。另外,传入给函数参数 deletedEle 的对象的值虽然是 {name:'小d',id:3},但是该对象的地址并不是数组中的那个值同样为 {name:'小d',id:3} 的元素的地址。
  2. arr.indexOf(item,start)。该方法可返回数组中某个指定的元素位置,该方法将从头到尾地检索数组,看它是否含有对应的元素。开始检索的位置在数组 start 处或数组的开头(没有指定 start 参数时)。如果找到一个 item,则返回 item 的第一次出现的位置。开始位置的索引为 0。如果在数组中没找到指定元素则返回 -1。
  3. arr.splice(index,howmany,item1,.....,itemX)。它是一个比较强大的方法,可以实现对数组的增加和删除。该方法会改变原始数组,如果删除了元素则会返回被删除元素,否则返回空数组([])。当传入 -1 给第一个参数 index 时,不论其他参数如何,都会删除最后一个元素,并将其返回。

通过这三个知识点的总结,我相信大家都能理解测试3和测试4中发生了什么。

那么我们要如何删除对象元素数组中的对象元素呢?删除元素我们想要知道该元素在数组中的索引位置,如果知道了这个位置就能通过 arr.splice() 方法删除元素。

这时候,另一个数组方法映入我们眼帘 ——

arr.findIndex(callback(element[, index[, array]])[, thisArg])

这个方法主要传入一个回调函数(关于另一可选参数 thisArg 参见findIndex)。该方法针对数组中的每个元素, 都会执行该回调函数, 执行时会自动传入下面三个参数: element 当前元素。index 当前元素的索引。array 调用 findIndex 的数组。该方法的返回值是数组中满足回调函数判断逻辑的的第一个元素的索引。否则,返回-1。

此时,我们可以在回调函数中设置某些判断条件,来判断数组中的对象元素是否是我们想要删除的元素,并获取对应的 index。

对于测试4,我们按照下面的方式修改函数,就能得到想要的输出了:

let commonArrayDelete = function(deletedArr,deletedEle){
    let index = deletedArr.findIndex(ele => ele.id === deletedEle.id)
    console.log("被删除元素",deletedArr.splice(index,1))
    console.log('原始数组',deletedArr)
}
commonArrayDelete(objArr,{name:'小d',id:3})

输出:
被删除元素 [{name: "小c", id: 3}]
原始数组 (5) [
    {name:'小a',id:1},
    {name:'小b',id:2},
    {name:'小d',id:3},
    {name:'小e',id:5},
    {name:'小f',id:7},
  ]

上面的方法适合于删除数组中的一个元素,如果有多个元素需要删除那么这些方法就不太合适了。

删除多个元素

当我们想要删除一个数组里的多个元素时,非常直观的一种方法就是采用循环的方式,对数组中的每一个元素进行逻辑判断。

下面举一个比较简单的例子:对于给定的数组 [1,2,3,4,5,6,7,8,9,10],删掉其中的偶数。很自然会写出下面这样的代码

let  loopArrayDelete0 = function(deletedArr){
    for(let i = 0; i < deletedArr.length; i++){
        if( deletedArr[i] % 3 == 0){
            console.log(`删除的元素:${deletedArr.splice(i, 1)}`)
        }
    }
}

得到的输出结果如下,很棒棒哟,是我想要的结果。

loopArrayDelete0(numArr)
删除的元素:3
删除的元素:6
删除的元素:9

接下来再换个条件,这次是想要删除大于3的所有元素。那么代码会这样写:

let  loopArrayDelete0 = function(deletedArr){
    for(let i = 0; i < deletedArr.length; i++){
        if( deletedArr[i] > 3){
            console.log(`删除的元素:${deletedArr.splice(i, 1)}`)
        }
    }
}

loopArrayDelete0(numArr)
删除的元素:4
删除的元素:6
删除的元素:8
删除的元素:10

但是当看到下面的输出时就会傻眼了,为什么会酱紫??我们想要删掉的是 4 5 6 7 8 9 10呀,怎么 5 7 9没了??

我们要知道 splice 方法会改变原始数组,当我们删掉一个元素后,会发生串位现象,下面这个图可以说明。

全部过程如下

删除的元素:4
此时的索引i:3
剩余数组: (9) [1, 2, 3, 5, 6, 7, 8, 9, 10]

删除的元素:6
此时的索引i:4 
剩余数组: (8) [1, 2, 3, 5, 7, 8, 9, 10]

删除的元素:8
此时的索引i:5 
剩余数组: (7) [1, 2, 3, 5, 7, 9, 10]

删除的元素:10
此时的索引i:6 
剩余数组: (6) [1, 2, 3, 5, 7, 9]

出于以上原因,我们这里需要换换思考角度:

  1. i 表示的是数组中元素的位置,既然数组中发生了串位现象,那么非常直接的想法是把 i 也向后串一位,由此先 i--,当结束当前循环时,又有 i++ ,所以在进入下一个循环周期时 i 并没有改变,指向的位置仍是上一循环删除的那个元素所在的位置,而这个位置上的元素在本次循环中是串上来的元素。
  2. 另一个想法是,既然从头开始循环,删除会发生串位现象,那么从尾开始循环不就没有这个问题了嘛。从尾开始的话用 while 循环更为简洁。
let loopArrayDelete1 = function(deletedArr){
    for(let i = 0; i < deletedArr.length; i++){
        if( deletedArr[i] > 3){
            console.log(`此时的index:${i}`,deletedArr)
            console.log(`删除的元素:${deletedArr.splice(i--, 1)}`)
        }
    }
}
let loopArrayDelete1 = function(deletedArr){
    // 逆向循环 
    for(let i = deletedArr.length - 1; i >= 0; i--){
        if( deletedArr[i] > 3){
            console.log(`删除的元素:${deletedArr.splice(i, 1)}`)
            console.log(`此时的index:${i}`,deletedArr)
        }
    }
}
let loopArrayDelete1 = function(deletedArr){
    let i  = deletedArr.length
    while(i--){
        if( deletedArr[i] > 3){
            console.log(`删除的元素:${deletedArr.splice(i, 1)}`)
            console.log(`此时的index:${i}`,deletedArr)
        }
    }
}

对于对象元素数组同样适用

let loopArrayDelete1 = function(deletedArr){
    for(let i = 0; i < deletedArr.length; i++){
        if( deletedArr[i].id == 3){
            console.log(`此时的index:${i}`,deletedArr)
            console.log(`删除的元素:${deletedArr.splice(i--, 1)}`)
        }
    }
}

我们转转脑筋,从另一个视角来看待删除。删除,是舍弃掉不满足某些约束条件的元素,也就是留下满足约束条件的元素。就好像滤纸可以分类固体和液体。所以,对 es6 有了解的同学就会想到 arr.filter() 方法。

filter(function(currentValue,index,arr)) 把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。该方法并不会改变原数组,而是创建一个新的数组。

let loopArrayDelete3 = function(deletedArr){
    const tmp = deletedArr.filter((item)=>item.id == 3)
    console.log(tmp)
}

欢迎大家分享下自己在实际项目开发中遇到的那些需要删除数组元素的情景,互帮互助开阔眼界呀~