const products = ['beans', 'pizza', 'chicken', 'salmon']
// Log to the console each element of the array
products.forEach(product => console.log(product))
// beans
// pizza
// chicken
// salmon
In this “JavaScript iteration in the real world” series of articles, I want to share with you some of the most common JavaScript iteration methods using examples that resemble as closely as possible what their usage looks like in the real world. I will try to avoid `foo` and `bar` or overly complex examples. There won’t be a lot of mentions of your pets and friends either I’m afraid. Instead, we’ll take a deep look at how the method works, why we need it, and what it would look like in a typical modern front-end codebase.
In this article, we will take the example established in the previous articles and show how forEach()
can help us. It’s not a requirement to read the previous articles in order to follow along with the examples in this article.
forEach()
So what is the Array.forEach()
method? In essence, this is a very simple method. It does only one thing but it does it very well. It will execute the function we provide for each element of the array we want to iterate on.
So in the opening example, we saw that the function we provided logs the current array element to the console. That’s it. Quite straightforward. But of course, to use it correctly we have to dig a little deeper.
If you’ve read the previous article in the series about map()
you would rightly be thinking that this sounds suspiciously the same. And in a way, you’d be right. But there is one fundamental difference between the two, and in that difference lies the decision of which to use.
Let me bring back an example we used in the previous article:
const
products = ['beans', 'pizza', 'chicken', 'salmon']
products.map( (product, index) => console.log(product, index))
// 'beans’, 0
// 'pizza’, 1
// 'chicken’, 2
// 'salmon’, 3
I used this example to show how map()
gives us the index of the element in the array. But, removing the index part, looks exactly identical to our first forEach()
example. So what’s going on? Well, the truth is this example of map()
is an anti-pattern that should not be used. I only used it to illustrate a point. forEach()
is what we should have used all along for something like this.
The reason lies in the fact that map()
returns a new element on each iteration and pushes it into a new array. So when the loop finishes map()
returns a whole new array for us with the results of the iteration. Whereas forEach()
doesn’t do that. It just executes the function. So you have to ask yourself if you need the returned array. If you are not going to use the resulting new array from a map()
operation, such as in the example above where you are only logging the elements, then you should not use map()
. That’s where we use forEach()
instead.
Let’s emphasise this point because it’s the key to understanding when to use each of those. map()
returns an array, forEach()
doesn’t. If you are using the returned array later in your code, use map()
, otherwise forEach()
is your friend.
Now that we’ve established what this method is and is not, we are ready to head to our online shop to see how we can apply forEach()
in the real world.
In the real world
Like in previous articles, we will continue with the realistic context on which to base our examples. We are working on the codebase of an online shop. This is a simple app I created especially to demonstrate our examples in action. You can check out the web app and see forEach()
used directly, and you can see the code in its GitHub repo. But we will go through everything here first.
So let’s begin exploring a realistic context where forEach()
might come in handy. It’s a little tricky at first, because so far in all our previous methods we have always taken the original array of products displayed in the shop and then done something to them and showed the result on the screen. But as we have learnt, forEach()
does not return an array and it’s not used for that.
We are going to start with a simple logging example first. Let’s get a tiny bit fancier than the humble console.log()
. What happens if we do this to each of our product objects in the shop?
products.forEach(product => console.table(product))
The result will look like this in our console [open the console with Command + Option + J (Mac) or Control + Shift + J (Windows, Linux, Chrome OS].
We have an array of products, each element is an object representing a product with several properties. forEach()
took each element and logged a neat table for it in our console. The table shows the keys and values of each product object.
Great, this is cute and all, but beyond development work, this is of no use in our production-ready online shop.
In order to think of a realistic context for its use, we are going to leave the comfort of our array methods and explore local storage for a minute. Bear with me as I set this up for you, it will all come together in the end.
I don’t want to spend a lot of time on Window.localStorage
, you can read more about it here, but in a nutshell, our window object provides us with a method to store some data in our user’s browser which persists even after reloading or closing, and reopening the page. We can store data there and then we can read it again when we need to. An example usage for this is to store there whether you have shown a user a pop-up modal asking them to subscribe to a newsletter when they visit your site. This way you can ensure that the user sees it only once.
If you open your dev tools, I’m using Chrome’s in the image below, you will find a tab called Application. On the left-hand pane, you can open the Local Storage tab and select your current URL. You will see the key/value table where we can save or read data.
We are going to store some data about each of our products here. That’s what we’ll need forEach()
to do.
Suppose that the prices in our shop fluctuate regularly depending on market prices. And these prices are saved in our database, which is where we get the list of products to show on the screen. We want a feature that shows the user a badge on a product if its price has dropped compared to the last time they saw it. We don’t know what the user saw the last time they visited. So we can save in our user’s local storage the price of each product on the screen every time they visit the page. This way before we display the new prices we can do a comparison with the price saved in the local storage and if the new price is lower we can display a “Price Drop” badge on that product.
Now for the purposes of our example, we really don’t need to develop any of this. The only part we are interested in is to save the price of each product in local storage and to see how forEach()
is ideal for this. The rest of this is to hammer home the real-world usage of this method.
products.forEach((product) => localStorage.setItem(product.name, product.price)
As you can see, we are starting with our products array. We apply forEach()
on it and pass the method a callback function to execute on each iteration. The function accepts the currently selected element of the array as the first argument, which we call `product`.
Then we execute the function we want. In this case we are using the `localStorage` API that allows us to save an entry using it’s own setItem()
method. Without worrying too much about this part we simply pass it the first argument which will be the key in the local storage table, and the second argument which will be the value. In this case we will save the name of the product as the key, and the price as the value. If this works correctly it should save each product and its price to our local storage. And this is what it would look like:
So forEach()
has executed this function for each of our products and saved its name and current price to local storage. We did not need a returned array with the results, and it did not modify the main products array still displayed on the screen. I hope this makes it abundantly clear the kind of situation where forEach()
should be used over map()
.
What’s more
Exactly as we have seen with the previous array methods we looked at, this one also provides us with three arguments we can use. The first is the currently selected element from the array which we have already seen in the example above. The other two are the index
and the array
.
myArray.forEach((element, index, array) => //Do stuff)
The index is the position of the currently selected element in the array, and the final argument is the original array itself we are iterating on.
We called the second argument “index” here, and the third argument “array”. But like `element` these are also variable names. You can name them whatever you want. You can call them “sun” and “jindo” and nothing would change. Remember it’s not the name that matters here, but the position of the argument. The first is always the element, the second is always the index, and the third is the array we started with. However, your obscure pop culture reference notwithstanding, we tend to name things in a way that makes it easier for readability. So the second argument is almost always called “index” or “i” by convention, and the third usually is “array” or “arr”.
What if we need our callback function to have several lines? Then we simply add curly braces.
products.forEach(product => {
const previousPrice = localStorage.getItem(product.name)
if (previousPrice !== product.price) {
localStorage.setItem(product.name, product.price)
}
})
In this example we first get the price we have previously stored in local storage, then we compare it to the current price. If the two prices are not the same then we save the new price. Otherwise, we do nothing.
What it looked like prior to ES6
With ES6 we have arrow functions and a lot of great stuff that make our code more readable. But it is important to know what things looked like before because you will come across this way of writing it in tons of older codebases that are still in use, and you will come across it in older examples on Stack Overflow and the like. This is how we wrote it before ES6:
products.forEach(function(product) {
console.table(product)
})
As you can see, before we got the arrow function we needed the function
keyword, the arrow is gone and we need the curly braces. We do not need the return
keyword, because as we have already seen forEach()
does not push the result to a new array.
What it looks like in a for loop
At the heart of all array methods, is a for loop doing all the work. These higher-order methods are a way to go one level of abstraction up and hide the inner workings of the for loop, and to have a clean and readable way to apply the method. Out of all of the methods forEach()
is the closest to the functionality of a for a loop.
We won’t need to do this, but if we were to write a for loop instead of a forEach()
it would look like this.
function logTable(products) {
for(let i = 0; i < products.length; i++) {
console.table(product)
}
}
So the implementation of forEach()
itself under the hood is quite transparent.
// callback is the function we create and pass into forEach
Array.prototype.forEach = function(callback) {
// ‘this’ refers to the array
for (let i = 0; i < this.length; i++) {
// you can see the three arguments our callback function receives
callback(this[i], i, this)
}
}
That’s all it is. If you look closely its behaviour is identical to the for loop itself. It receives a callback function and executes it once every iteration. It just saves us having to write the whole for loop every time. forEach()
is a much shorter, more succinct, and more readable implementation.
Conclusion
While our friendly forEach()
is not so common these days in our map()
-dominated frontend frameworks, it is still a very versatile and important method to learn. It’s the first step up from the bare bones of a for loop and as such it’s open to executing anything we want it to execute. As long as we know when we need to use it and when not, then it is a great tool to understand and use.
As always, the best way to get good at using it is to practice a lot. Open the console and try out some of these examples or even fork the GitHub project of the example and try changing things and saving other product properties to local storage for example. And if you find yourself stuck or have any questions I’m always happy to lend a hand, you can find me on Twitter for everything dev