数组.splice从剩余元素中删除值

13

我发现了array.splice的一个奇怪副作用,并对代码进行了精简以重现该问题。 是的,用array.filter可以将其压缩至一行,但我想知道自己是否犯了错误或其他情况是否在发生。

var array = [];

for (var i = 0; i < 10; i++) {
  array.push({
    value: i
  });
}

array.forEach(function(item, index, intArray) {
  if (item.value % 2 == 1) {
    item.odd = true;
  } else {
    item.odd = false;
  }

  if (item.odd) {
    console.log("Removing " + item.value);
    intArray.splice(index, 1);
  }

});

console.log(array);

运行此JavaScript会按预期删除奇数元素,但它还会删除第2、4、6和8个项目的item.odd值。删除intArray.splice行会恢复奇数数组元素,但也会将所有元素的item.odd值带回来。
我已在Firefox和Chrome中进行了测试。即使仅将项传递到回调函数中,并通过array.indexOf计算索引并从循环外部引用数组,行为仍然存在。

3
我在想,当你遍历数组时,是否存在对该数组进行编辑的问题。比如说,index值可能是原始数组中的索引而不是修改后新数组中的索引。 - gen_Eric
我有类似的想法。然而,在这种情况下,我期望出现的错误是项目和索引不匹配,因此会删除太多或太少的元素,而不是元素内部的值消失。此外,如果是这种情况,我会注意传递索引到回调函数和即时计算索引之间的差异 - 但在实践中它们是相同的。 - Brian Bauman
2个回答

9

我认为当你在每个奇数位置切割数组时,forEach会跳过下一个项,这个项是偶数。因此,这些项根本没有被修改。

var array = [];

for (var i = 0; i < 10; i++) {
  array.push({
    value: i
  });
}

array.forEach(function(item, index, intArray) {
  console.log(item); // only prints out 0, 1, 3, 5, 7, 9

  if (item.value % 2 == 1) {
    item.odd = true;
  } else {
    item.odd = false;
  }

  if (item.odd) {
    console.log("Removing " + item.value);
    intArray.splice(index, 1);
  }

});

console.log(array);

换句话说,forEach仅访问每个索引一次。 所以说它到达第一个项目,即索引为1的项目。 它删除了项目1。 项目2现在位于索引1。 但是索引1已经被访问过了,因此它继续移动到索引2处的项目,这现在是项目3。

1
这就解释了为什么 0 是唯一具有 odd 属性的“偶数”项。 - Mark C.
我已经仔细查看了代码,感觉没问题。我想我们必须在允许对快照进行操作的便利性和允许对正在操作的数组进行迭代修改之间做出选择。 - Brian Bauman

0

由于splice是一个具有破坏性的操作(destructive),而forEach不是一种“实时”的操作[它不会自动更新],在使用splice(index, 1)之后进行的迭代将会跳过“旧索引+1”(下一个迭代索引),因为它已经“掉落”到新的索引位置(旧数组元素原本所在的位置)。无论删除了多少个元素,使用splice删除数组元素将总是导致这种意外的“滑移”,从旧的索引到新的索引,每次使用splice都会跳过一个数组元素。

当使用循环中的splice删除数组元素时,我使用一个带有负迭代的for循环。当数组下降时,for循环不会跳过它,因为它下降到了下一个(负)迭代:

for ( let i = array.length - 1; i >= 0; i-- ){
  // do destructive stuff with splice
}

=> 从数组的末尾开始(不要忘记减1,因为数组是从0开始计数);在每次迭代中,继续当i >= 0时;并递减i。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接