Reputation: 312
I am trying to remove a word from an array based on an index which doesn't exist in another array, but I am observing odd behavior when I use the splice and filter methods.
Can anyone explain the scenario below? Why is it happening like this in both cases, even though the same object is being altered on iteration?
Words
['one', 'two', 'three', 'four', 'five', 'six', 'seven']
Removable Words
['four', 'two', 'eight']
let words = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
let removedWords = ['four', 'two', 'eight'];
words.forEach((word) => {
console.log(word);
if (removedWords.includes(word)) {
words = words.filter((removableWord) => removableWord !== word)
}
});
/* Output */
//one
//two
//three
//four
//five
//six
//seven
let words = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
let removedWords = ['four', 'two', 'eight'];
words.forEach((word, index) => {
console.log(word);
if (removedWords.includes(word)) {
words.splice(index, 1);
}
});
/* Output */
//one
//two
//four
//six
//seven
As mentioned in this Mozila document forEach() does not make a copy of the array before iterating. Shouldn't it behave the same as splice after filtering and assigning back to the original object?
Note: Just to add on this, the splice method makes changes on original array and the filter method creates a new copy of the array and doesn't alter the original array, but in the given example (after filtering), the result is assigned back to the original array.
Upvotes: 2
Views: 235
Reputation: 19381
Your first example also works, but your console.log may confuse you. You log once for every word in the for loop before filtering.
Just log the result words
after the loop to see that it works.
let words = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
let removedWords = ['four', 'two', 'eight'];
words.forEach((word, index, iterationArray) => {
console.log(index, word, iterationArray.length, words.length);
if (removedWords.includes(word)) {
words = words.filter((removableWord) => removableWord !== word)
}
});
console.log(words);
Answer to the OPs comment:
So I guess, that you are confused by
"forEach() does not make a copy of the array before iterating"
forEach()
does not make a copy: but you make a copy inside the loopwords
is a reference to the original array ['one', 'two', 'three', 'four', 'five', 'six', 'seven']
words.forEach()
which is a function that returns an iterator on this array (The iterator will always point to this original array, no matter if you change where the words
reference points to later)
iterationArray
to forEach
which is the array that the iterator usesconsole.log
inside the loop: note, that iterationArray
will not change, but words.length
will change (because you assign new arrays to it)words.filter
(e.g. ['one', 'two', 'three', 'five', 'six', 'seven']
) and change the words
variable to point to this new array - BUT this does not change the iterator that you have already created before: i.e. the forEach
loop still "points" to the original arrayFor the 2nd example:
let words = ['one', 'two', 'three', 'four', 'five', 'six', 'seven'];
let removedWords = ['four', 'two', 'eight'];
words.forEach((word, index, iterationArray) => {
console.log(word, index, iterationArray.length);
if (removedWords.includes(word)) {
words.splice(index, 1);
}
});
console.log(words);
forEach
will not make a copy['one', 'three', 'four', 'five', 'six', 'seven']
four
(and you missed the word trhee
which is now at index 1 which the iterator has already processedconsole.log
inside the loop: you can see that the iterationArray
is changedUpvotes: 2
Reputation: 4233
When you say let words = <an array>
the reference to that array object (say ref1) is stored in the variable words
. When you call forEach
on that reference (ref1), it will keep referring to that reference perpetually.
Inside the loop, after filtering, you are getting a new filtered array which is a different array in memory. You may use the same words
variable to hold the reference (ref2 / ref3), but this doesn't change the one on which forEach
is acting on.
However, when you use splice, the original array edits itself.
Note:
Not only that, you are producing 2 different filtered arrays successively with each call to filter
.
['one', 'two', 'three', 'five', 'six', 'seven']
['one', 'three', 'five', 'six', 'seven']
Eventually, your first method works and produces the desired result, but you create 'X' copies of arrays if you have X items to be removed from words
.
Your second method is better on performance because it doesn't keep producing copies of the array for each removal, but you have to think about which index you are removing, and how forEach
will continue after an index is removed.
In either case, your console log
is placed in the wrong place.
Upvotes: 0