为什么使用array.forEach(() => { array.pop() })不能清空数组?

6
在nodejs REPL中,我试图清理一个定义为const array = [...]的数组,但发现使用array.forEach(() => /pop|shift/())不能起作用。执行这样的表达式后,数组仍然保留其值。
我非常清楚有更好的方法来清除数组,例如array.splice(0),但我真的很好奇这种行为的原因,至少对我来说是反直觉的。
以下是测试内容:

const a = [1, 2, 3]

a.forEach(() => {
  a.shift()
})

console.log(a) // [ 3 ]

const b = [1, 2, 3]

b.forEach(() => {
  b.pop()
})

console.log(b) // prints [ 1 ]


注意事项

  1. 一开始我使用的是arr.forEach(() => arr.pop()),所以我认为其中一个值短路了forEach,但将lambda包装在body-block { .. }中也会产生相同的结果。

  2. 结果在不同的node版本和浏览器中保持一致,因此似乎这是定义良好的行为。

  3. 剩余值的数量,即仍留在结果数组中的值,取决于输入数组的长度,并且似乎是Math.floor(array.length / 2)

  4. 剩余值始终按照使用/pop|shift/方法的顺序排序,因此其中一些调用实际上正在更改输入数组。

  5. 通过调用Array.prototype.forEach(array, fn)也可以返回相同的结果。


5
你正在从开头开始迭代,并在每次迭代中移除最后一个元素。这意味着你每次向前移动1个位置,同时减少1个长度。因此,你最终会得到 floor(initialLength / 2) 次迭代。 - John
我的Chrome 71输出有点不同:第一个日志是 [5, 6, 7],第二个日志是 [1, 2, 3] - Matthew Herbst
@MatthewHerbst 我修复了输入数组! - eridal
1
使用 array.length = 0; 来清空一个数组。 - Bergi
3个回答

4
请看这个引用:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach 如果数组现有元素的值被更改,那么传递给回调函数的值将是 forEach() 访问它们时的值;在访问之前被删除的元素不会被访问。
你正在从开头迭代并在每次迭代中移除最后一个元素。这意味着你每次向前移动1,并减少1的长度,因此你最终得到的迭代次数是floor(initialLength / 2)。你正在修改正在进行forEach的相同数组,这意味着对于那些被弹出的元素,回调将不会被调用。

我明白你的意思!你有什么想法,为什么这不会陷入无限循环?a = [1]; a.forEach((_, i) => {a.push(i)})..看起来forEach保留了初始数组值的某种副本。 - eridal
1
是的,请查看我刚才在@eridal引用上面包含的链接。 - John

4

在迭代数组时修改它通常是一个坏主意。实际上,在Java中尝试这样做会导致异常抛出。但让我们将forEach转换为旧式的for循环,也许你会看到问题所在。

for (let i = 0; i < a.length; ++i) {
    a.pop();
}

现在明白发生了什么了吗?每次迭代时,您都会通过弹出最后一个元素来缩短数组的长度1。因此,在迭代一半的元素后,循环将结束--因为到那时,它也会删除一半的元素,导致i的值大于当前数组的长度。
当您使用forEach时,同样的事情正在发生:每次迭代时,您都会通过弹出来缩短数组的长度,导致循环在迭代了一半的元素后终止。换句话说,随着数组的缩小,迭代器变量将向前移动过数组的末尾。

1
这对我很有帮助。谢谢。 - Alex Pappas

1

.pop

我们来做这个:

let arr = 'abcde'.split('');
arr.forEach((x,i,a) => {
    console.log('before', x,i,a);
    arr.pop();
    console.log('after', x,i,a);
});
console.log(arr);

你的索引在增加,但长度在减少,因此当索引在第一个元素时,你删除了最后一个元素,导致删除数组右半部分的结果。
.shift
相同:迭代索引向一个方向移动,而长度则向另一个方向移动,因此整个过程会中途停止。

let arr = 'abcde'.split('');
arr.forEach((x,i,a) => {
    console.log('before', x,i,a);
    arr.shift();
    console.log('after', x,i,a);
});
console.log(arr);


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