The for loop in JavaScript
The for
loop is most commonly used for iterating over an array and performing an action on each item. For example:
var brothers = ['Reuben', 'Simeon', 'Levi'];
for(var counter = 0; counter < brothers.length; counter++) {
console.log('Hello ' + brothers[counter] + '!');
}
In JavaScript the three parts in the parentheses are known as initialization
, condition
and final-expression
, and perhaps surprisingly, they are all optional. The part inside the curly braces is the statement.
The steps taken when a for
loop runs are:
- The
initialization
is evaluated. - The condition is checked to see if it evaluates to
true
orfalse
. - If it’s
false
everything stops, if it’s true the statement is run once. - The
final-expression
is run. - Go back to 2.
Initialization
initialization
is an expression or a list of variable assignments. Typically, and in our example above, it’s used to set the initial value of a counter. Because arrays are zero indexed, the counter is usually started at 0 when looping over an array from the start.
(Typing “counter” or something similar every time would get a bit tedious so developers tend to just use i
.)
The counter can just as easily be started at the end of the array though. If you wanted to reverse the values in an array and didn’t know about array.prototype.reverse()
one possible way to do it would be like so:
var brothers = ['Reuben', 'Simeon', 'Levi'];
var reverse = function(arr) {
var i = 0,
counter = 0;
for(var ii = arr.length; ii > i; ii--) {
var tmpValue = arr.pop();
arr.splice(counter, 0, tmpValue);
counter++;
}
return arr;
};
console.log(reverse(brothers));
In this example we are starting the loop counter at the number of values in the array. Each time the loop goes round it decrements the value stored in ii
and increments the value stored in counter
.
initialization
doesn’t have to be a single variable assignment. If we look at our brothers example again we can see that each time the loop goes round it recalculates brothers.length
as part of the conditional. This is potentially a waste of time if the array is very large, why not just assign the value of brothers.length
to a variable?
var brothers = ['Reuben', 'Simeon', 'Levi'];
for(var i = 0, brothersCount = brothers.length; i < brothersCount; i++) {
console.log('Hello ' + brothers[i] + '!');
}
We could also declare the variables outside the loop and take advantage of initialization
being optional:
var brothers = ['Reuben', 'Simeon', 'Levi'],
i = 0,
brothersCount = brothers.length;
for(; i < brothersCount; i++) {
console.log('Hello ' + brothers[i] + '!');
}
Now, remember I said initialization
can be an expression? Well it can be any expression with one restriction: it can’t use the in
operator. The spec uses the keywords ExpressionNoIn
and VariableDeclarationListNoIn
to describe what initialization
can be.
To see why, we need to look at what the in
operator does. It’s an operator that returns true if what is on the left of it is in what is to the right of it. In the case of arrays it returns true if the specified index is in the specified array.
For example in our brothers
array there are 3 items so the indices are 0, 1 and 2. Therefore 2 in brothers
returns true and 3 in brothers
returns false.
However, there is also a for-in loop that uses the in
operator. It is typically used to iterate over properties in an object like so:
var brothers = {
first: 'Reuben',
second: 'Simeon',
third: 'Levi'
}
for(var position in brothers) {
console.log('Hello ' + brothers[position] + '!');
}
Let’s imagine we don’t know what the first value in the array is but we want to get it. Using our knowledge of in
in the context of arrays (and ignoring the simple array[0]
) we might try:
for(var arrayHasValues = 0 in brothers; arrayHasValues; arrayHasValues = false) {
console.log('Hello ' + brothers[0] + '!');
}
The thinking here is that we are using var arrayHasValues = 0 in brothers
to check if the array has at least one value. If it has, arrayHasValues
is true, the loop runs, then it’s stopped by final-expression
setting arrayHasValues
to false.
Just to reiterate, using the in
operator this way is absolutely fine if it’s not in initialization
. Try running the following and you’ll see it returns true:
var brothers = ['Reuben', 'Simeon', 'Levi'];
var arrayHasValues = 0 in brothers;
console.log(arrayHasValues);
The problem is that the in
operator in initialization
the way we’ve used it is checking for undefined
in brothers
as if it’s a for-in loop, so it doesn’t work. The spec has made it invalid and it will return a console error if we run it that way. A good job too because it’s mighty confusing for me!
To fix the loop so that it does get the first value in an array just wrap the expression that uses the in
operator in parenthesis. for(var arrayHasValues = (0 in brothers); arrayHasValues; arrayHasValues = false)
will work.
Condition
The second part of the for loop is condition
. It’s an expression that is evaluated before each iteration, and if it evaluates to true the loop runs, if it’s false the loop doesn’t run.
condition
is optional and if it’s omitted the loop runs as if it’s true. So for(; true; i++)
is the same as for(; ; i++)
. We could use that in our original example, along with the break
keyword that stops the loop.
var brothers = ['Reuben', 'Simeon', 'Levi'],
i = 0;
for(; ; i++) {
console.log('Hello ' + brothers[i] + '!');
if(i === brothers.length - 1)
break;
}
As an aside, there’s also a continue
keyword that stops the rest of the statement being executed in the iteration where continue
works, but the loop carries on. In the following example all the numbers between 0 and 29 except multiples of 7 are output to the console.
for(var i = 0; i < 30; i++) {
if(i % 7 === 0)
continue;
console.log(i);
}
Final-expression
The last part inside the parentheses is final-expression
, the expression that is evaluated after the statement in each iteration of the loop. Usually it’s the counter increment, but we could swap it with part of the statement. Let’s say we want to add the items in an array to an object we could do something like:
var brothers = ['Reuben', 'Simeon', 'Levi']
i = 0,
obj = {};
for(; i < brothers.length; obj[i] = brothers[i - 1]) {
i++;
}
console.log(obj);
Remember, final-expression
is just an expression. It doesn’t have to be a counter, that’s just the most common use. Remember also that it too is optional, so we can change our for loop again to:
var brothers = ['Reuben', 'Simeon', 'Levi'],
i = 0;
for(;;) {
if(i === brothers.length)
break;
console.log('Hello ' + brothers[i] + '!');
i++;
}
Empty statement
Believe it or not the statement itself can be empty, as long as there’s something in final-expression
that makes condition
false at some point. For example we might want to add our brothers' last names into each item in the array and we could do it like so:
var brothers = ['Reuben', 'Simeon', 'Levi'];
for(var i = 0, ii = brothers.length; i < ii; brothers[i++] += ' Jameson');
console.log(brothers);
When using the empty statement it’s important to remember the semi-colon at the end or whatever comes next will be treated as the statement. In the previous example it’s console.log(brothers)
. The reason for this is that the curly braces are optional if the statement only takes up one line.
The following two badly indented code blocks produce different outputs:
var brothers = ['Reuben', 'Simeon', 'Levi'];
for(var i = 0, ii = brothers.length; i < ii; i++)
brothers[i] += ' Jameson';
console.log(brothers);
var brothers = ['Reuben', 'Simeon', 'Levi'];
for(var i = 0, ii = brothers.length; i < ii; i++) {
brothers[i] += ' Jameson';
console.log(brothers)
}
A quick word on optimisation
I touched on this earlier when we looked at initialization
and brothers.length
being calculated each time the loop goes round. Moving it out of the loop and assigning it to a variable means the work involved it counting the length of the brothers
array only happens once.
This idea of moving code out of the loop that doesn’t change is called loop-invariant code motion. There’s a nice example of loop-invariant code motion on jsPerf where you can see the benefits of moving loop-invariant code out of a loop.
So next time you find yourself writing something like:
change it to:
That way the expression foo[bar] === baz
is only evaluated once.
If you want to get really into loop optimisation there’s some interesting stuff (and some impenetrable stuff) in this Wikipedia article.
Health warning/conclusion
Most of the examples I’ve used here shouldn’t be used in production code unless there’s a very good reason. The common for(var i = 0; i < len; i++)
is well known and easy to debug. If I was faced with having to debug for(;;foo[i++])
before doing the research for this article I would have been in difficulties, so be nice to the developers who have to maintain your code.
Nonetheless the for
loop is an interesting control, and is specced in a way that is less restricted than I had assumed. While most of what I learnt researching this article might never be practical, it helped me understand JavaScript as a language a little bit better, and that’s a good thing.
Acknowledgement
A huge thanks to Toby for reading, checking and pushing me to further knowledge on this topic.