Rethinking JavaScript for-loops

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

About these ads

58 thoughts on “Rethinking JavaScript for-loops

  1. 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

  2. Pingback: HTML Scripts Tips and Secrets » Blog Archive » Rethinking JavaScript for-loops | JavaScript, JavaScript

  3. Pingback: HTML all you need to know» Blog Archive » Rethinking JavaScript for-loops | JavaScript, JavaScript

  4. Pingback: Rethinking JavaScript for-loops | JavaScript, JavaScript – javascript - dowiedz się więcej!

    • 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, not for in

  5. 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

  6. 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.

  7. Pingback: for循环的小收获 – 足跡偏右

  8. “- 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

  9. Pingback: Rethinking JavaScript for-loops | JavaScript, JavaScript « Netcrema – creme de la social news via digg + delicious + stumpleupon + reddit

  10. 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

    Better to use the void unary operator:

        for (var i=arr.length, r=0; i-- || void (r = r*x); r += arr[i]);
    

    /be

  11. Instead of this:

    for( var i = 0, each;  each = arr[i];  i++ ) {
        doSomething( each );
    }

    Do this:

    for( var item, i = -1;  item = arr[++i]; ) {
        doSomething( item );
    }

    This is faster, and item is a much better name than each. 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

      var arr = [];
      for (var a=0; a<10000; a++) {
        arr.push(1 + parseInt(Math.random()*100));
      }
      
      console.time('test')
      /*
      //test 1
      for( var i = 0, item;  item = arr[i];  i++ ) {
          console.log(item);    
      }
      */
      //test 2
      for( var item, i = -1;  item = arr[++i]; ) {
          console.log(item);    
      }
      console.timeEnd('test')
      

      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 here

    • Let’s compare the two approaches, transforming the for in canonical whiles

      Angus clear version with ‘thing’:

      var i = 0;         //binding creation, undefined initialization, assignment 
      var thing;        //binding creation, undefined initialization
      while(each = arr[i];) { //array access, assignment
          doSomething( thing );
          i++;                                //adition-assignment, 
      }
      

      Obfuscated but more ‘efficient’ version

      var item;      //binding creation, undefined initialization
      var i = -1;     //binding creation, undefined initialization, assignment 
      while( item = arr[++i];) {  //adition-assignment, array access, assignment
          doSomething( item );
      }
      

      I see the same operations!

  12. 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

  13. 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 :D

  14. Pingback: Top Posts — WordPress.com

  15. Pingback: links for 2010-10-13 — Business Developer Talk

  16. Pingback: Replanteándonos los for() en Javascript | aNieto2K

  17. 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..

      var x, y;
      

      will create local variables x and y.

      If you don’t believe me try this:

      var l;
      var test = function(arr) {
          for (var i=0, l=arr.length; i<l; i++) {
              var member = arr[i];
          }
          alert(l);
      };
      test([0,1,2]);
      alert(l)
      
  18. 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?

    • 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…

  19. 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.

  20. Pingback: 如果免費的代價是揭露隱私@囧評20101008 – Razorcap Studios

  21. Pingback: Js Focus #1: eventi, ciclo for, ajax, jquery, prototype » jblog: link e appunti per webmaster

  22. Pingback: Cheatsheet: 2010 10.10 ~ 10.25 - gOODiDEA.NET

  23. Pingback: RSS Tidbits for 22 October 2010 « SKFox.com

  24. Pingback: JavaScript Operators

  25. Pingback: for循环的小收获 | Skyth.Com|中文博文推荐

  26. 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.

  27. 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 when i is falsey (i.e. 0). i-=1 returns the value of i after the decrement (same as --i) while i-- returns i and then decrements.

      compare these 2:

      var i = 1;
      if (i-=1) {
          console.log(i); //not reached
      }
      
      i = 1;
      if (i--) {
          console.log(i); //reached
      }
      

      Not sure why it over-recursed though

      P.S. I like Crockford’s work but occasionally find him a little over-pedantic ;-)

  28. Pingback: Guía Google de estilo Javascript | EtnasSoft

  29. Pingback: JavaScript之for循环重思 » 前端翻译小站

  30. Pingback: Rethinking JavaScript for-loops | JavaScript, JavaScript | プログラマダイジェスト

  31. Pingback: Javascript looping | Valbook

  32. Pingback: Lessons From A Review Of JavaScript Code |Layout to HTML

  33. Pingback: Lessons From A Review Of JavaScript Code - Goodfav Howto

  34. Pingback: Lessons From A Review Of JavaScript Code

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s