Everyone knows at least one version of the For loop, it’s a classic and probably almost every language out there has a version of it. JavaScript, however, has three (or 4 if you’re really picky about it), and they’re not exactly the same, I’m referring of course to:
The classical For loop
The For… of and For…in pair
And the fancy, functional version: .forEach
There are differences between all versions, so in this article, I want to cover all three of them and how or when to use them for optimal results. Let’s get cracking.
The classic For loop
So we’re all clear, this is the classic For loop, where you define your internal counter, set a break condition and a step change (usually incrementing or decrementing the counter).
The syntax being:
for([counter definition];[breaking condition definition];[step definition]){
//... your repeating code goes here
}
Now, I’m sure you’ve written that code before, and the most common form for it is:
for(let counter = 0; counter < 10; counter++) {
console.log(counter)
}
And while that code works perfectly well, the sections of the For loop are more flexible than that. In fact, you should think about them as
for(
[EXPRESSION EXECUTED ONLY ONCE AT THE START OF THE LOOP];
[BOOLEAN CONDITION CHECKED ON EVERY STEP];
[EXPRESSION EXECUTED ON EVERY STEP OF THE LOOP]
)
Meaning, you can do For loops with more than one counter, or have code executed on every step that doesn’t necessarily affect your counters. Just to name a few examples.
This, for instance, is a perfectly valid loop:
for(let a = 0, b = 0; a < 10 && b < 100; a++, b+=10) {
console.log(a, b)
}
/*
0 0
1 10
2 20
3 30
4 40
5 50
6 60
7 70
8 80
9 90
*/
You can even push it further, and move out of the normal use cases from above:
for(let a = 0, b = 0; a < 10 && b < 100; console.log("Your counters are at:", ++a, b+=2)){}
/*
Your counters are at: 1 2
Your counters are at: 2 4
Your counters are at: 3 6
Your counters are at: 4 8
Your counters are at: 5 10
Your counters are at: 6 12
Your counters are at: 7 14
Your counters are at: 8 16
Your counters are at: 9 18
Your counters are at: 10 20
*/
You can even substitute the middle expression for a function call, as long as you remember that the function’s returned value will be cast to a boolean.
function isItDone(a) {
console.log("fn called!")
return a < 10
}
for(let a = 0; isItDone(a); a++) {
console.log(a)
}
/*
fn called!
0
fn called!
1
fn called!
2
fn called!
3
fn called!
4
fn called!
5
fn called!
6
fn called!
7
fn called!
8
fn called!
9
fn called!
*/
And what about dealing with asynchronous code inside a classical **For **loop? Thanks to our new friend async/await, that’s very easy to do:
const fs = require("fs")
async function read(fname) {
return new Promise( (resolve, reject) => {
fs.readFile(fname, (err, content) => {
if(err) return reject(err)
resolve(content.toString())
})
})
}
(async () => {
let files = ['file1.json', 'file2.json']
for(let i = 0; i < files.length; i++) {
let fcontent = await read(files[i])
console.log(fcontent)
console.log("-------")
}
})()
Notice how we can simply use our loop like no asynchronous mechanics is working in the back. That’s all async/await, but thanks to it, we’re back to depending on a basic construct such as a For loop to iterate over a set of asynchronous instructions.
In the past, if you wanted to achieve the same thing using callbacks or promises, the logic would’ve been a lot more complex. Which is why libraries such as async.js were born.
BTW, a small note: the for loop in my example is inside an IIFE simply because as you probably already know, the await instruction needs to be inside an async function, otherwise Node won’t allow it.
The For... in and For… of pair
Yeah, they’re quite similar variations of the previous version but at the same time, they’re different loops.
Let me quickly define them:
The **For..in **loop deals with non-symbol, enumerable properties from an object (keyword there being “object” since almost everything in JavaScript is an object). This is very useful for cases when you’re using your custom object as a hash map or dictionary (a very common practice).
**Note,* however, that the iteration is done on an arbitrary order, so don’t rely on the loop to pick the right order you’ll need and make sure you control that part if it makes sense.*
let myMap {
uno: 1,
dos: 2,
tres: 3
}
for(let key in myMap) {
console.log(key, "=", myMap[key]);
}
/*
uno = 1
dos = 2
tres = 3
*/
Pretty simple, isn’t it? But be warned, because like I said, almost everything in JavaScript is an object, so you can end-up doing a For… in when you’re actually wanting to do a For… of. For instance, if you wanted to iterate over each character in a String (which is an Object), here is what would happen if you used For… in:
for(let k in "Hello World!") {
console.log(k)
}
/*
0
1
2
3
4
5
6
7
8
9
10
11
*/
Instead of iterating over each letter of the string, we iterated over each property and as you can see, we’re actually dealing with a structure (for the String type) very similar to an Array. And that makes sense after all, since doing "Hello World!"[1] does not only work but also returns the actual character at that position (i.e the letter ‘e’).
If instead, you wanted to iterate over each character, you’d need to use the other variant: For…of
for(let char of "Hello World!") {
console.log(char)
}
/*
H
e
l
l
o
W
o
r
l
d
!
*/
Now, that makes more sense, doesn’t it? Same use case, but with it, you’re accessing the values of an iterable (Strings are iterable, and so are Arrays, Maps, Sets, and Array-like structures such as arguments or NodeList ). And of course, your own objects, if you define them as iterables.
Following the above example, there is no direct way to get the current index for the loop, unless of course, you define it outside of your loop and update it on every step or you could get both index and value if you use the entries method for arrays, like so:
let myArr = ["hello", "world"]
for([idx, value] of myArr.entries()) {
console.log(idx, '=', value)
}
/*
0 '=' 'hello'
1 '=' 'world'
*/
And finally, just to keep up with the comparison, what about asynchronous code? Exactly the same!
const fs = require("fs")
async function read(fname) {
return new Promise( (resolve, reject) => {
fs.readFile(fname, (err, content) => {
if(err) return reject(err)
resolve(content.toString())
})
})
}
(async () => {
let files = ['file2.json', 'file2.json']
for(fname of files) {
let fcontent = await read(fname)
console.log(fcontent)
console.log("-------")
}
for(idx in files) {
let fcontent = await read(files[idx])
console.log(fcontent)
console.log("-------")
}
})()
Both loops react exactly in the same way with the await construct, allowing you to write simpler and cleaner code.
The fancy and functional .forEach loop
This one is probably my favorite of the lot, and that is simply because I’m a big fan of declarative syntax or a declarative way of writing your code over imperative. And while the above versions of the loop work great and have their own very good use cases, they’re also very imperative in the sense that we’re required to write what needs to happen with our data instead of simply writing what we want to happen to it.
Anyways, leaving philosophical debates aside, the **.forEach **method is yet another version of the For loop, this one, however is part of the Array object and is meant to receive a function and an extra, optional, parameter to re-define the context of that function while executing it.
For every element inside the array, our function will be executed and it’ll receive three arguments (yes you read that right three, not one as you’re used to using it). And they are:
The current element being processed.
The index of the element, this already simplifies the task we tried to achieve with the For…of loop
The actual array being processed. Just in case you need to do something with it.
To continue with the examples, let’s see a quick one:
a = ["hello", "world"]
a.forEach ( (elem, idx, arr) => {
console.log(elem, "at: ", idx, "inside: ", arr)
})
/*
hello at: 0 inside: [ 'hello', 'world' ]
world at: 1 inside: [ 'hello', 'world' ]
*/
Quick and simple, but you see how we can use all attributes quite easy inside our function. And here is an example of when you’d want to use the second, optional parameter on the forEach
method:
class Person {
constructor(name) {
this.name = name
}
}
function greet(person) {
console.log(this.greeting.replace("$", person.name))
}
let english = {
greeting: "Hello there, $"
}
let spanish = {
greeting: "Hola $, ¿cómo estás?"
}
let people = [new Person("Fernando"), new Person("Federico"), new Person("Felipe")]
people.forEach( greet, english)
people.forEach( greet, spanish)
By overwriting the context of our called function greet, I’m able to change its behavior without affecting its code.
And to finalize, showing that this method can also be used with asynchronous code, here is the example:
const fs = require("fs")
async function read(fname) {
return new Promise( (resolve, reject) => {
fs.readFile(fname, (err, content) => {
if(err) return reject(err)
resolve(content.toString())
})
})
}
let files = ['file1.json', 'file2.json']
files.forEach( async fname => {
let fcontent = await read(fname)
console.log(fcontent)
console.log("-------")
})
Notice how I no longer need the IIFE because I’m declaring the callback as async.
Conclusion
That is all I wanted to share about the For loops in JavaScript, I hope that now you have a clearer understanding of them and can pick and choose which one you prefer based on that knowledge and our current coding needs.
Have I missed something interesting about them? Leave a comment below and share it with everyone!
Otherwise, I’ll see you on the next one!
Top comments (0)