Rethinking JavaScript for-loops (original) (raw)

(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:

for (initialCode; iteratingCondition; repeatingExpression) { 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)