Every programming language has loops. Loops perform an operation (i.e., a chunk of work) a number of times, usually once for every item in an array or list, or to simply repeat an operation until a certain condition is met.
JavaScript in particular has quite a few different types of loops. I haven’t even used all of them, so for my own curiosity, I thought I’d do a high-level overview of them. And as it turns out, there are pretty good reasons I haven’t used at least a couple of the different types.
So, for
now let’s spend a while
exploring the different types of loops, what we can do
with each
of one, and why you might use one over another. (You’ll think that little play on words is absolutely hilarious by the end.)
The while
and do...while
loops
First up is the while
loop. It’s the most basic type of loop and has the potential to be the easiest to read and the fastest in many cases. It’s usually used for doing something until a certain condition is met. It’s also the easiest way to make an infinite loop or a loop that never stops. There is also the do...while
statement. Really, the only difference is that the condition is checked at the end versus the beginning of each iteration.
// remove the first item from an array and log it until the array is empty
let queue1 = ["a", "b", "c"];
while (queue1.length) {
let item = queue1.shift();
console.log(item);
}
// same as above but also log when the array is empty
let queue2 = [];
do {
let item = queue2.shift() ?? "empty";
console.log(item);
} while (queue2.length);
The for
loop
Next is the for
loop. It should be the go to way to do something a certain number of times. If you need to repeat an operation, say, 10 times, then use a for
loop instead. This particular loop may be intimidating to those new to programming, but rewriting the same loop in the while
-style loop can help illustrate the syntax make it easier to stick in your mind.
// log the numbers 1 to 5
for (let i = 1; i <= 5; i++) {
console.log(i);
}
// same thing but as a while loop
let i = 1; // the first part of a for loop
// the second
while (i <= 5) {
console.log(i);
i++; // the third
}
("end");
The for...of
and for await...of
loops
A for...of
loop is the easiest way to loop through an array.
let myList = ["a", "b", "c"];
for (let item of myList) {
console.log(item);
}
They aren’t limited to arrays though. Technically they can iterate through anything that implements what is called an iterable protocol. There are a few built-in types that implement the protocol: arrays, maps, set, and string, to mention the most common ones, but you can implement the protocol in your own code. What you’d do is add a [Symbol.iterator]
method to any object and that method should return an iterator. It’s a bit confusing, but the gist is that iterables are things with a special method that returns iterators; a factory method for iterators if you will. A special type of function called a generator is a function that returns both a iterable and iterator.
let myList = {
*[Symbol.iterator]() {
yield "a";
yield "b";
yield "c";
},
};
for (let item of myList) {
console.log(item);
}
There is the async
version of all the things I just mentioned: async
iterables, async
iterators, and async
generators. You’d use an async
iterable with for await...of
.
async function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
// this time we're not making an iterable, but a generator
async function* aNumberAMinute() {
let i = 0;
while (true) {
// an infinite loop
yield i++;
// pause a minute
await delay(60_000);
}
}
// it's a generator, so we need to call it ourselves
for await (let i of aNumberAMinute()) {
console.log(i);
// stop after one hour
if (i >= 59) {
break;
}
}
One unobvious thing about for await...of
statement is that you can use it with non-async iterables and it will work just fine. The reverse, however, is not true; you can’t use async
iterables with the for...of
statement.
The forEach
and map
loops
While these are not technically loops per se, you can use them to iterate over a list.
Here is the thing about the forEach
method. Historically it was much slower than using a for
loop. I think in some cases that may not be true anymore, but if performance is a concern, then I would avoid using it. And now that we have for...of
I’m not sure there is much reason to use it. I guess the only reason that it still may come up is if you have a function ready to use as the callback, but you could easily just call that same function from inside the body of for...of
.
forEach
also receives the index for each item though, so that may be a thing you need too. Ultimately, the decision to use it will probably come down to whether any other code you’re working with uses it, but I personally would avoid using it if I’m writing something new.
let myList = ["a", "b", "c"];
for (let item of myList) {
console.log(item);
}
// but maybe if I need the index use forEach
["a", "b", "c"].forEach((item, index) => {
console.log(`${index}: ${item}`);
});
Meanwhile, map
essentially converts one array into another. It still has the same performance impact that forEach
has, but it is a bit nicer to read than the alternative. It’s certainly subjective though, and just like with forEach
you’ll want to do what the rest of your other code is doing. You see it a ton in React and React-inspired libraries as the primary way to loop through an array and output a list of items within JSX.
function MyList({items}) {
return (
<ul>
{items.map((item) => {
return <li>{item}</li>;
})}
</ul>
);
}
The for...in
loop
This list of loops in JavaScript wouldn’t be complete without mentioning the for...in
statement because it can loop through the fields of an object. It visits fields that are inherited through the object’s prototype chain too, though, and I’ve honestly always avoided it for that reason.
That said, if you have an object literal, then for...in
might be a viable way to iterate through the keys of that object. Also it’s worth noting that if you’ve been programming JavaScript for a long time, you may remember that the order of keys use to be inconsistent between browsers, but now the order is consistent. Any key that could be an array index (i.e., positive integers) will be first in ascending order, and then everything else in the order as authored.
let myObject = {
a: 1,
b: 2,
c: 3,
};
for (let k in myObject) {
console.log(myObject[k]);
}
Wrapping up
Loops are something that many programmers use every day, though we may take them for granted and not think about them too much.
But when you step back and look at all of the ways we have to loop through things in JavaScript, it turns out there are several ways to do it. Not only that, but there are significant — if not nuanced — differences between them that can and will influence your approach to scripts.
All About JavaScript Loops originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.