Array forEach - doesn't iterate through all elements of mutating array

Let arr=[1,2,3,4,5,6,7,8,9,10];
let array=[];
arr forEach(function(a,b,c){array.push(arr.pop())},this);
console.log(array);

this should copy all the array items, but it doesn't and array only contains [10,9,8,7,6]

It seems it has been changed in last few versions, cause it worked fine earlier. It must iterate through all items.

Versions of what?

While mutating an array while you're iterating on it has never been a good idea, the spec's semantics around that have always been somewhat deterministic.

1 Like

I doubt this, as I can't see how logically this would do what you want it to do. Let's work with a smaller array, and follow it step-by-step:

let arr=[1,2,3,4];
let array=[];
arr.forEach(function(){array.push(arr.pop())});

// We start with this:
arr=[1,2,3,4]
array=[]

// One the first iteration, we pass in the first item, `1`, into the callback
// then pop one item from array and put it onto array.
// so now we have this.
arr=[1,2,3]
array=[4]

// On the next iteratio, we pass the second item, `2`, into the callback.
// then pop one item from array and put it onto array.
// so now we have this.
arr=[1,2]
array=[4,3]

// And now there's nothing left in arr to pass into the callback,
// because we've popped the later elements are already gone.

It sounds like the behavior you're expecting, is for the entire array to be duplicated when you start doing a .forEach(), and then you iterate over this hidden duplicate array while mutating the original. But, this sounds incredibly inefficient - I don't know of any languages that do this sort of thing.

forEach means that for each element it will be iterated doesn't matter the array length, so more like a copy of the array which will iterate through all elements.

have seen differences of forEach behaviour between v92 and v97 of chrome browser.

Versions of browsers, let's say chrome.
Well, the one possible way is that you keep a copy of the arr and iterate over it, but if another element is being removed it won't reflect, so associated copies of array elements is the only way.

There's no way for it to prevent mutation. It's not about the length, it's that if it's on the, say, second item, and you unshift one to the front, should forEach visit it, or not? If you pop one off the back, should forEach visit it, or not?

The language has made choices about these, whether your agree with them or not is immaterial.

The main thing is that it makes no sense to mutate something you're midway iterating on, and if you are, you probably should be building your own iteration abstraction (with a for loop, perhaps) rather than relying on one that assumes the common case of not changing your horse midstream.

1 Like

or, you make a copy instead of mutating it - ie, arr.map, or arr.reduce, or something.

@amkmishra - I just tested your code snippet on Chrome v92, and it showed the values 10,9,8,7,6

Specifically, I put this in a webpage, and loaded it within chrome 92:

<html>
  <body>
    <script>
let arr=[1,2,3,4,5,6,7,8,9,10];
let array=[];
arr.forEach(function(a,b,c){array.push(arr.pop())},this);
console.log(array);
document.write(String(array));
    </script>
  </body>
</html>

May be some issue with my chrome then, showing non deterministic behaviour probably.

Kidding.

By the way I would really like to have a way to create copies of array that can mutate the original array only for the elements it was copied with, but it will become another DS probably.

It's not non-deterministic, you get the same result each time.

Your issue is that you've put the forEach call in yet another loop in your screenshot, completely messing with any reasoning. Just don't write code like that. The following might help you to understand what's going on in the code:

const arr = [1,2,3,4,5,6,7,8,9,10];
const array = [];
for (let i=0; i<arr.length; i++) {
    arr.forEach((_, j) => {
        console.log(i, j, JSON.stringify(arr), JSON.stringify(array));
        array.push(arr.pop());
    });
    console.log(i, arr.length, 'stopped')
}
console.log(arr.length, 'stopped')
console.log(JSON.stringify(arr), JSON.stringify(array));

Not sure what you were trying to achieve originally, maybe something like this?

const arr = [1,2,3,4,5,6,7,8,9,10];
const array = arr.slice().reverse();
console.log(array);
1 Like