javascript - Why array.forEach(() => { array.pop() }) would not empty the array - Stack Overflow

While on the nodejs REPL I was trying to clean up an array defined as const array = [...]and just fou

While on the nodejs REPL I was trying to clean up an array defined as const array = [...] and just found out that using array.forEach(() => /pop|shift/()) would not work. After such expression the array will still hold values in it.

I'm well aware of better methods to clean the array, like array.splice(0), but I'm really curious about this behavior as seems counter-intuitive, at least for me.

Here's the test:

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 ]

While on the nodejs REPL I was trying to clean up an array defined as const array = [...] and just found out that using array.forEach(() => /pop|shift/()) would not work. After such expression the array will still hold values in it.

I'm well aware of better methods to clean the array, like array.splice(0), but I'm really curious about this behavior as seems counter-intuitive, at least for me.

Here's the test:

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 ]


Notes

  1. At first I was using arr.forEach(() => arr.pop()), so I though that one of the values was short-circuiting the forEach but wrapping the lambda in a body-block { .. } will also produce the same results.

  2. The results are consistent across different node versions and browsers .. so it seems like it's well-defined behavior.

  3. The quantity of leftover values, those still in the result array, change depending on the length of the input array, and seems to be Math.floor(array.length / 2)

  4. The leftover values are always ordered accordingly to the /pop|shift/ method used, so some of the calls are actually changing the input array.

  5. It also returns the same results by calling Array.prototype.forEach(array, fn)

Share Improve this question edited Jan 7, 2019 at 21:15 Sebastian Speitel 7,3562 gold badges21 silver badges38 bronze badges asked Jan 7, 2019 at 21:07 eridaleridal 1,3381 gold badge11 silver badges24 bronze badges 4
  • 5 You're iterating from the beginning and removing the last element each iteration. This means you're moving forward 1, and reducing the length by 1, each iteration. Hence why you're ending up with floor(initialLength / 2) iterations. – John Commented Jan 7, 2019 at 21:10
  • My output on Chrome 71 is a bit different: [5, 6, 7] for the first log and [1, 2, 3] for the second – Matthew Herbst Commented Jan 7, 2019 at 21:12
  • @MatthewHerbst I fixed the input arrays! – eridal Commented Jan 7, 2019 at 21:14
  • 1 Use array.length = 0; to empty an array. – Bergi Commented Jan 7, 2019 at 21:21
Add a ment  | 

3 Answers 3

Reset to default 4

Check out this quote from here: https://developer.mozilla/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

If the values of existing elements of the array are changed, the value passed to callback will be the value at the time forEach() visits them; elements that are deleted before being visited are not visited.

You're iterating from the beginning and removing the last element each iteration. This means you're moving forward 1, and reducing the length by 1, each iteration. Hence why you're ending up with floor(initialLength / 2) iterations. You're modifying the same array that you're forEaching, which as stated above means that you will not have the callback invoked for those pop'd elements.

Modifying an array while iterating over it is generally a bad idea. In fact, in Java, trying to do so would cause an exception to be thrown. But let's convert the forEach into an old-school for loop, and maybe you'll see the issue.

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

Is it clearer now what's going on? Each iteration you're shortening the length of the array by 1 when you pop the last element off. So the loop will end after iterating over half the elements -- because by then, it will have REMOVED half the elements, too, causing the value of i to be more than the current length of the array.

The same thing is happening when you use forEach: you're shortening the array with each iteration when you pop, causing the loop to terminate after only half the elements have been iterated. In other words, the iterator variable will move forward past the end of the array as the array shrinks.

.pop

Let's do this instead:

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);

Your index is incrementing but your length is decrementing, so you're deleting the last elements when your index is in the first elements, thus the result where you delete the right half of the array.

.shift

Same: the iterating index goes one way, the length in another, so the whole things stop mid-work:

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);

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744708022a4589171.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信