Meet Array.prototype.forEach()
.
It's purpose is to execute the code you provide it over each item of the array: essentially a loop.
Here's its definition:
array.forEach(function callback(currentValue [, index [, array]]) {
// code for this iteration
}[, thisArg]);
Let's explain it below. 😉
It's Parameters
forEach
accepts up to two parameters:
- the
callback
function, which is executed over each item of the array - the
thisArg
(optional), which changes the value ofthis
inside of the callback function
Now, a deeper look at each one. 👇
1) The callback
function
The method that is called on each element of the array. It takes up to three parameters:
- currentValue: current item of the array,
🍎
on the first iteration - index (optional): index of the current item,
0
on the first iteration - array (optional): the whole array, the
same
on all iterations
const array = ["🍎", "🍌", "🍍"];
array.forEach(function(current, index, array) {
console.log(current);
console.log(index);
console.log(array);
console.log("\n");
});
// LOGS
// { current: '🍎' }
// { index: 0 }
// { array: [ '🍎', '🍌', '🍍' ] }
//
// { current: '🍌' }
// { index: 1 }
// { array: [ '🍎', '🍌', '🍍' ] }
//
// { current: '🍍' }
// { index: 2 }
// { array: [ '🍎', '🍌', '🍍' ] }
The index
Parameter
The index
parameter is optional. It is handy when the logic depends on the item's position in the array.
const fruitEmojis = ["🍎", "🍌", "🍍"];
const fruitNames = ["apple", "banana", "pineapple"];
fruitEmojis.forEach(function logFruitName(currentFruitEmoji, index) {
const fruitName = fruitNames[index];
console.log({ emoji: currentFruitEmoji, name: fruitName });
});
// LOGS
// { emoji: '🍎', name: 'apple' }
// { emoji: '🍌', name: 'banana' }
// { emoji: '🍍', name: 'pineapple' }
The array
Parameter
The last parameter is array
. It is the value of the whole array at the beginning of the execution of the callback method.
Handy: It is useful when you have a generic method that you pass to forEach
which requires access to the array.
The method being generic you cannot know in advance the array it will be called on. Which also mean you cannot rely on the closure as it is unknown.
Thus the array
parameter in that case is your only option to get access to the current array.
See this Stackoverflow response reply from redneb for more info.
2) The thisArg
Parameter
Overrides the this
keyword value inside the callback
function.
By default this
would refer to the window object, and it will be overridden by the value you pass it.
const array = [1];
array.forEach(function basicCall(current) {
console.log(this);
});
// LOGS
// ... the whole Window object actually (in the browser)
const thisArg = { context: "custom" };
array.forEach(function explicitThisArg(current) {
console.log(this);
}, thisArg);
// LOGS
//`{context: "custom"}`, which is the custom `this` value passed
My Usage of forEach
I generally use it when I want to apply a side-effect to some object or another array. (I try avoiding side-effect as much I can.)
Example
In this case we have a list of emojis and the corresponding list of names. We want to create an object where the key will be the name and the value will contain the emoji.
The two arrays are sorted the same way: at any given index items from both array correspond.
const fruitEmojis = ["🍎", "🍌", "🍍"];
const fruitNames = ["apple", "banana", "pineapple"];
let fruitMap = {};
fruitEmojis.forEach(function addKeyPairToFruitMap(currentFruitEmoji, index) {
const key = fruitNames[index];
fruitMap[key] = currentFruitEmoji;
});
console.log(fruitMap);
// LOGS
// { apple: "🍎", banana: "🍌", pineapple: "🍍" }
Note that fruitMap
is created before calling forEach on fruitEmojis
. And we update the object during the executions of addKeyPairToFruitMap
.
Information To Consider
Before using the forEach
methods on arrays here's some information worth knowing.
1) Returns undefined
, thus NOT Chainable
The forEach
array method always returns undefined
, thus it is NOT chainable. Which means that in a call chain, it can only be placed at the end.
const fruitEmojis = ["🍎", "🍌", "🍍"];
let fruitMap = {};
fruitEmojis
.forEach(function addKeyPairToFruitMap(currentFruitEmoji) {
return currentFruitEmoji;
})
.map(function logEmoji(emoji) {
console.log("Calling `.map` will throw an error!");
}
);
// LOGS (console.error)
// ... (omitted details)
// .map(function logEmoji(emoji) {
// ^
// TypeError: Cannot read property 'map' of undefined
// ... (omitted details)
2) Callback Function Can Modify Original Array
We can add/remove/update items from the array from inside the callback function.
Addition
Adding does NOT affect the items for the call: only the items initially present are processed.
But after the execution we see that it was affected.
const fruitEmojis = ["🍎", "🍌", "🍍"];
let fruitMap = {};
fruitEmojis.forEach(function addKeyPairToFruitMap(currentFruitEmoji, index) {
fruitEmojis.push(`test ${index}`);
console.log({index});
});
console.log({fruitEmojis});
// LOGS
// `forEach`:
// { index: 0 }
// { index: 1 }
// { index: 2 }
// logging the array:
// { fruitEmojis: [ '🍎', '🍌', '🍍', 'test 0', 'test 1', 'test 2' ] }
Deletion
Deletion DOES affect the number of items for the call. If the next planned item is removed it will not be processed.
let fruitEmojis = ["🍎", "🍌", "🍍"];
let fruitMap = {};
fruitEmojis.forEach(function addKeyPairToFruitMap(currentFruitEmoji, index) {
fruitEmojis.shift();
fruitEmojis.pop();
fruitEmojis.splice(0, 1);
console.log({index});
});
console.log({fruitEmojis});
// LOGS
// `forEach`:
// { index: 0 }
// logging the array:
// { fruitEmojis: [] }
Modification
Modification DOES affect the items themselves for the call, but not the count. If we modify the next planned item, this modification is available when it is then processed.
Note that the forth item is due to this statement the following statement which adds an item to the array on the last execution: fruitEmojis[index + 1] = "AAAAAARH!";
.
let fruitEmojis = ["🍎", "🍌", "🍍"];
let fruitMap = {};
fruitEmojis.forEach(function addKeyPairToFruitMap(currentFruitEmoji, index) {
fruitEmojis[index + 1] = "AAAAAARH!";
console.log({currentFruitEmoji, index});
});
console.log({fruitEmojis});
// LOGS
// `forEach`:
// { currentFruitEmoji: '🍎', index: 0 }
// { currentFruitEmoji: 'AAAAAARH!', index: 1 }
// { currentFruitEmoji: 'AAAAAARH!', index: 2 }
// the array
// { fruitEmojis: [ '🍎', 'AAAAAARH!', 'AAAAAARH!', 'AAAAAARH!' ] }
3) Cannot Be Stopped
You cannot stop the execution or "break the loop" when calling the forEach method.
If you are trying to stop the execution you should probably use a different array method (eg. find, filter, reduce, some, includes) or use a for-loop instead.
Conclusion
I hope this article about the forEach
method has brought you value. 🙂
It will be part of a series on JavaScript arrays, so stay tuned for the next one! 🎉
Until then, happy coding! 😎
Top comments (7)
"Cannot Be Stopped" => It's possible to throw an exception to break the method if you really need, see that stackoverflow post
Thank you for the this reminder in the article !
Agreed ;) Thank you for mentioning this. And throwing an exception can stop any kind of code.
Then this technique is a hack, it is not according to the design of the language like the
break
statement for imperative loops. Nor is it a feature of theforEach
loop.So throwing an exception in a
forEach
callback is possible, but when you reach that point in your code, refactoring to afor of
or afor i
loop would actually improve readability. :)Yes totally agree as mentioned - though, we can wondering why such a break thing is not available for this method ? I mean, particularly, in the way to have a better understanding of the method itself and JavaScript more generaly...
Thanks for the great read! It's always interesting seeing other peoples perspectives on it.
I feel like
forEach
is in this weird space betweenfor of
and the more functional operatorsmap/reduce
. Almost every use case for looping over an array is to change its shape, either by manipulating each item (which makesmap
ideal) or by aggregating the array contents (which makesreduce
ideal).Using your key/value map example, I would typically use
reduce
as follows:It's very similar, but has notably fewer side effects. You can also make it really terse, if that's your kind of thing, and if you use the spread operator to shallow copy you can reduce the footprint for unintentional side effects even further:
Just some thoughts. :)
Thanks for this insightful explanation @mikegeyser !
It's true that if I'm using
.forEach
quite often, it's worth considering using more "specialised" methods such as.reduce
.Which is a great transition to announce the topic for the next article of this series:
Array.prototype.reduce
!Stay tuned! ✌
Great article! Small typo in the description of the callback: "1 on the first iteration" - should be 0 on the first iteration :)
Thanks @vasil9v ! Fixed :)