工作中需要一边枚举, 一边删除元素. 然后, 悲剧了.

const log = console.log
const a=[1,2,3,4,5]
for (const key in a) {
    log("a[key]: ",a[key])  //f {}
    a.splice(key, 1)

}
log("a: ",a)  //f {}

这一段的输出是:

a[key]:  1
a[key]:  3
a[key]:  5
a:  [ 2, 4 ]
//本身我盼望的输出是:
a[key]:  1
a[key]:  2
a[key]:  3
a[key]:  4
a[key]:  5
a:  [ ]

这篇文章会对程序员形成严重的误导: https://love2dev.com/blog/javascript-remove-from-array/

//一段忽悠人的样例代码
var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];

for( var i = 0; i < array.length-1; i++){  //这个length-1也是错误写法, 会漏掉最后一个元素
   if ( array[i] === 5) {
     array.splice(i, 1); 
   }
}
log("array: ",array)  

//=> [1, 2, 3, 4, 6, 7, 8, 9, 0]

//真相大白
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];

for( var i = 0; i < arr.length-1; i++){ 
   if ( arr[i] === 5||arr[i]===6) { //这里再加一个条件就真相了.
     arr.splice(i, 1); 
   }
}
log("arr: ",arr)  
//=> [1, 2, 3, 4, 6, 7, 8, 9, 0]  看到没有, 新加的条件并没有生效, 因为6被跳过了.

结论: js不支持一边迭代一边删元素, 他的迭代本质上等同于for(let i=0; i<length; i++), 也就是说他的key是持续计数的. 因此, 要干类似的事情时, 需要我们先保存一个临时缓冲区, 等遍历好了之后再一次性处理掉. 例如:

//例如
var ar = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
const temp=[]

for( const i in ar){ 
   if ( (ar[i] === 5)||(ar[i]===6)) { 
     temp.push(i)
   }
}
log("temp: ",temp)  

for(const i in temp){
    ar.splice(temp[i], 1); 
}


log("ar: ",ar)  
//=> ar:  [ 1, 2, 3, 4, 6, 8, 9, 0 ]

当时我就震惊了, 这么干竟然不行, 缓存也不行, 6没有删除掉, 确错误的删除了7!

肿么办? 肿么办? 肿么办?

两个思路:

  1. 用filter, 但是, 这个开销有点大, 因为他会新建一个数组, 大规模搞的时候, 至少要清理内存.
  2. 用delete, 这个是用undefined占位的删除, 那么后续程序就要补充判断. 如果一次删的东西多, 这个判断其实还蛮难做的, 因为, 连续的删除就会有问题.
  3. 既然是数组, 把方向倒过来吧.

看上去就是把方向倒过来靠谱.


var ara = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

for (var i = ara.length - 1; i > -1; i--) {
    if ((ara[i] === 5) || (ara[i] === 6)) { 
        ara.splice(i, 1);
    }
}

log("ara: ", ara)
//=> ara:  [ 1, 2, 3, 4, 7, 8, 9, 0 ]

OK, 搞定