(versión abreviada en español)
If you think the introduction of Array.prototype.forEach and friends will send the for-loop the way of the dodo, think again. There’s life in the old dog yet.
The for-loop is often seen as something of a one trick pony, most suited to the classic form of list iteration:
for (var i=0; i<arr.length; i++) { //do something to each member }
but with the wealth of higher order functions now available both natively and in frameworks we can just do this (or variants thereof)
arr.forEach(function(each)) { //do something to each });
Ironically as high-order functions gradually render the traditional pattern obsolete, so might we become liberated from our old habits and branch out to explore more interesting patterns of for-looping.
To whet your appetite – here’s an ultra-compact way to generate and alert the first n members of the Fibonacci series:
for ( var i=2, r=[0,1]; i<15 || alert(r); r.push(r[i-1] + r[i-2]), i++ ); //alerts "0,1,1,2,3,5,8,13,21,34,55,89,144,233,377"
The Basics
The anatomy of the for-loop comprises 4 components:
repeatingCode
}
– All four sections are optional
– The initialCode need not be a variable assignment – any valid expression is okay.
– The iteratingCondition and repeatingExpression cannot contain variable declarations.
– The curly brackets are optional if the repeatingCode consists of one or fewer statements.
– The repeatingExpression will be evaluated after the repeatingCode section.
We can summarize the process in pseudo code terms – (the function invocation notation is purely for readability):
initialCode(); while(iteratingCondition()) { repeatingCode(); repeatingExpression(); }
Exploring patterns
In which the use of for-loops will evolve from the familiar to the slightly nutty. The intent is to demonstrate the flexibility of the construct and the power of the language – not to provide best practice templates.
Conventional Array Iterator
for (var i=0; i<arr.length; i++) { var member = arr[i]; doSomething(member); }
Storing Array Length for Efficiency
for (var i=0, l=arr.length; i<l; i++) { var member = arr[i]; doSomething(member); }
Merging the iteratingCondition with the repeatingExpression
for (var i=arr.length; i--;) { var member = arr[i]; doSomething(member); }
This works because when i
reaches zero the iterating condition is coerced to false and we quit the loop. Of course this is only useful if you’re ok iterating in reverse sequence.
Assigning the Member in the iteratingCondition
We can move member variable assignment from the repeatingCode block to the iteratingCondition. When each
is undefined the looping will quit.
This reduces code bulk and requires no array length checking. The syntax becomes more direct – which, to my mind, means more elegant. This technique is only useful if your array is dense and there is no risk that members will have “falsey” values (null
, 0
, ""
or false
).
for (var i=0, each; each = arr[i]; i++) { doSomething(each); }
Sparse Array Testing
We can reverse the above pattern to actively check for a sparse array or list. Here we are efficiently testing for undefined arguments:
var func = function(a,b,c) { for (var i=0; arguments[i] !== undefined; i++); var allArguments = (i >= arguments.callee.length); //... }
No repeatingCode block
The repeatingCode and repeatingExpression serve the same purpose, so if your repeating code can easily fit into one statement you can drop the entire repeatingCode block:
function sum(arr) { for (var i=arr.length, r=0; i--; r += arr[i]); return r; } sum([3,5,0,-2,7,8]); //21
A finally clause hiding out in the iteratingCondition
We can use the logical boolean ||
operator to define a final statement to be invoked when we are through with the iteration. This little function will sum the members of an array and then alert the value when its done.
function shoutOutSum(arr, x) { for (var i=arr.length, r=0; i-- || alert(r); r += arr[i]); } shoutOutSum([3,5,0,-2,7,8]); //alerts "21"
Of course if your finally clause doesn’t return a falsey value you are in trouble – now iteration will continue indefinitely. To insure against this you would have to &&
the final expression with false – which starts getting a little clumsy:
function sumAndMultiply(arr, x) { for (var i=arr.length, r=0; i-- || ((r = r*x) && false); r += arr[i]); return r; } sumAndMultiply([3,5,0,-2,7,8], 5); //105
Update: Brendan Eich suggested using the void operator instead:
function sumAndMultiply(arr, x) { for (var i=arr.length, r=0; i-- || void (r = r*x); r += arr[i]); return r; }
No variable declaration in the initialCode section
You do not need to use a variable declaration for initialCode. So as not to be confused by variable hoisting, many developers define all variables at the beginning of the function, and some JavaScript experts (including Douglas Crockford) will go as far as to avoid variable declaration in for-loops.
function myFunction(arr) { var i; //... for (i=0; i < arr.length; i++) { //... } //... }
Having said that you will almost always want to use the initialCode for some kind of variable assignment. But you don’t have to. This code is pretty poor usage of a for-loop, but I wanted to prove the point.
var i = 0; for ( console.log('start:',+new Date); i<1000 || console.log('finish:',+new Date); i++ );
Wrap Up
I’ve explored just a few variations of the traditional for-loop syntax – no doubt you use other techniques, I’d like to hear about them. I’m not suggesting you need to rush out and use all these patterns tomorrow – or even at all!. Nevertheless, exploring new uses for familiar tools is a great way to develop a deeper relationship with the language and ultimately ensures the continual development and success of the language itself.
Further Reading
ECMA-262, 5th edition
section 12.6.3 (The for statement)
sections 15.4.4.14 to 15.4.4.22 (High OrderArray Functions)
nice post, one inconsistency though: in “Assigning the Member in the iteratingCondition” you are not assigning anything to “member”:
for (var i=0, var each; each = arr[i]; i++) {
doSomething(member);
}
probably copy-and-paste error. 🙂
Cheers
another copy and paste error- teach me to write in such a hurry
thanks for pointing it out !
You miss one of the ones that i use the most.
array = [1,2,3,4]
for(var i in array){
... some code
}
Yeah – I use this all the time for object iteration (not safe or necessary for array iteration though)
I guess the article specifically addressing about the
for
statement, notfor in
I have to 2nd what you said about using for in. I only use that when iterating over an object’s members rather than on an array.
I would really love to have an uniform iterator interface for Arrays and Objects. I like the way Python handles it.
I have been thinking that something along “each(, function() {});” would do the trick. This way you could do things like “each(nums, function(num) {alert(num)}” and “each(fruitBasket, function(fruitName, amount) {…});”. You get the idea. 🙂
I heard some original version of Prototype plugged “each” directly to Array and Object. This meant you could do things like “.each(function(…) {…})”. Sadly the solution didn’t work out.
Fortunately it’s possible to work around this problem by defining a “Hash” class and using it instead of Object for the cases that need “each”.
Hi Juho
High order iterators are specced by ES5 and available in nearly all browsers. And like Prototype.js they are built into Array prototype.
I agree it would be nice if Object iteration were as way. The Hash object in Prototype.js has a nice interface though
I really like playing around with language features like this, where everyone thinks they understand it, but in reality are just copying out what they have done before and don’t really know why it works the way it does.
Really nice post, thanks!
Another typo I think: when you talk about “limitingCondition” you mean “iteratingCondition”, right?
Thanks! Typo fixed (article was riddled with them – too many projects going on!)
“- The limitingCondition and repeatingExpression cannot contain variable assignments.”
On the one hand, you first name it “iteratingCondition” and then “limitingCondition”. On the other hand, you’re doing assignments in these code blocks all over the examples…
Yeah – two typos (I definitely rushed this post!)
1. For variable assignments read variable declarations
2. For limitingCondition read iterationCondition
thanks for pointing out
Better to use the void unary operator:
/be
The more I think about this, the more I like it. I had never considered using void in conjunction with logical operators.
Shame on you and anyone who uses void.
Instead of this:
Do this:
This is faster, and
item
is a much better name thaneach
. The latter name invites confusion.Hi Michael – I agree ‘each’ is a bad name choice.
I’m not convinced about your example being faster. I ran test1 and test2, 5 times each
test 1 was actually very slightly faster (firefox on mac). In reality I think there is no difference. The same number of increments occur in both examples and as per ES5 the increment process for postfix and prefix operators is identical. Only the return value of the incrementor differs (old value vs. new value) but since the result of
i
never gets assigned that difference doesn’t seem to be relevant hereLet’s compare the two approaches, transforming the for in canonical whiles
Angus clear version with ‘thing’:
Obfuscated but more ‘efficient’ version
I see the same operations!
A nice read on the old classic. Thanks!
Another typo: the second piece of code (arr.forEach()…) has unbalanced parentheses.
Otherwise great article.
thanks Harvey! (memo to self – never try to write 2 articles at once!)
Just a note that, in general, all of the higher order for-in/for-each structures are terrible for performance when compared to an optimal for loop. In fact, many for loops can be optimized for performance as well, such as reducing the work done each iteration by using a decrementing loop rather than an incrementing loop whenever the order doesn’t matter to the loop body’s repeating code. You should check out Nicholas Zakas’s “High Performance JavaScript” (O’Reilly) book for lots more info, or you can watch the loop-related part of his Google Tech Talk on YouTube here.
Yeah thanks for reinforcing the performance thing (I noted it in a couple of other blog posts). Its basically the cost of a function call repeated n times. Developers need to make their own judgment on whether its worth it
Makes sense to optimize the loop variation of an numeric index in a language where the numeric index is converted to a string by the engine? Without authentic vectors with integer typed indices not worth reverse the progress of an index. Remember Knuth’s quotes about the evil 😀
Storing Array Length for Efficiency ..
Note to you, it’s not smart or efficient to use global variables in a loop..
Brunis, I’m not using a global variable in the loop
In general..
will create local variables x and y.
If you don’t believe me try this:
There was no var l; in the “Storing Array Length for Efficiency” example..
Its been like this since day one:
yes, and that creates a global variable l .. what do you do when all your loops uses l and several loops run concurrently..
let’s say 5 loops start at 0 .. and set arr.length is 1000 in all of them..
then a mouseclick triggers a loop that set’s l to 10 ..
what happens?
It doesn’t create a global variable, it creates a local variable. Please review the example I posted at the beginning of this thread
His example of this:
for (var i=0, l=arr.length; i<l; i++)
does in fact create a local variable. If he has nested loops then a different variable must be used to represent the length of each array.
And what has that got to do with local or global scope?
You do realize that this is a single scope?
function f(){
for (var x;;) for (var y;;) for (var z;;) ;
}
The scope inside f will have (from the start!) three variables: x, y and z. They exist and are accessible in all loops at any point. On the outside (say, window or global) they do not exist.
So either read up on scoping in JavaScript or figure out what the right term is for what you’re trying to say…
Brunis, `for (var x,y,z=5;;)` makes local variables, period… Please note that javascript doesn’t have block scoping.
you’re right.. my apologies 🙂
Peter,
I understand scope. My reference to nested loops was in response to Brunis saying “et’s say 5 loops start at 0 .. and set arr.length is 1000 in all of them..”
If I misinterpreted what he said than I apologize.
Just as a minor nitpick, a little late to the fight.
You can actually only leave 3 of the sections blank. Repeated code cannot be left blank.
The reason I say this is because a null statement is still a statement.
The use of brackets “{” and “}” is block statement, and the use of “;” at the end of your example is not a termination of the for statement but a null statement for repeating condition.
Angus, I really like the short version for loop you mentioned in the post:
for (var i=arr.length; i--;) {
var member = arr[i];
doSomething(member);
}
I referenced this part a lot in my code.
I’m also a fan of Crockford’s JavaScript standard. 🙂 So I was tempted to change i– to i -= 1 , then I realized this would not work. The browser started to yell about too much recursion. Look like they are not the same in this case.
Hi Grace
Glad you liked it. The reason
i-=1
might work differently is that, since we are using the expression as the “while” clause, the loop will quit wheni
is falsey (i.e.0
).i-=1
returns the value of i after the decrement (same as--i
) whilei--
returnsi
and then decrements.compare these 2:
Not sure why it over-recursed though
P.S. I like Crockford’s work but occasionally find him a little over-pedantic 😉