JavaScript loop closures
From Trephine
| « Understanding JavaScript's this keyword | JavaScript call and apply » |
[subscribe] Recent blog entries
- Simple prototypal inheritance new!
- Adventures in Rhino - setters and getters
- Site improvements - fighting with Disqus
- JavaScript task chaining
- JavaScript string building benchmarks
- Efficient JavaScript string building
- Alternative JavaScript worker thread API
- Implementing JavaScript worker threads
- Thread safe DOM manipulation
- Site improvements - CSS sprites
- Trephine worker threads made easy
- Pitfalls of multithreaded browser development
- Site improvements - reducing dependencies
- The unsplittability of XML
Live Demos
JavaScript loop closures
JavaScript's variable scoping rules can cause problems for developers, especially when using loops to set up events. This article introduces one elegant approach to these situations called a loop closure.
What is a loop closure?
A loop closure is an anonymous function that represents the entire inside body of a loop and which is executed on each iteration.
To see an example, first let's consider a simple for loop which does not use the technique:
var list = [ 'a', 'b', 'c' ]; for (var i=0, l=list.length; i<l; i++) { var item = list[i]; alert(item); }
Running the above code should result in three alert() dialogs, displaying 'a', 'b' and 'c' respectively.
The equivalent code block using a loop closure looks like this:
var list = [ 'a', 'b', 'c' ]; for (var i=0, l=list.length; i<l; i++) (function(item){ alert(item); })(list[i]);
Running this code should produce exactly the same result as the previous non-closure method. In essence, the for loop contains a single command: the anonymous function being immediately executed.
The following snippet is logically equivalent, but uses curly braces to denote the for loop block and gives a name to the function before executing it:
var list = [ 'a', 'b', 'c' ]; for (var i=0, l=list.length; i<l; i++) { var f = function(item){ alert(item); }; f(list[i]); }
Why use loop closures?
Considering that the first two examples above produce the same outcome, why go through the trouble of creating a loop closure?
To answer that, let's consider another example, similar to the first for loop at the top of the article:
var list = [ 'a', 'b', 'c' ]; for (var i=0, l=list.length; i<l; i++) { var item = list[i]; setTimeout( function(){ alert(item); }, 1000 ); }
In this case, rather than executing the alert() calls right away, they're queued up to execute 1 second (1000 milliseconds) in the future. Running the above code will cause a series of three alert boxes, like before, but this time they all contain 'c'!
If you're wondering how that could be, consider that variables in JavaScript are scoped by their containing function, not the containing "block". The following snippet is logically equivalent to the previous, but more explicitly shows the variable scope boundaries by listing them all outside the for loop:
var list = [ 'a', 'b', 'c' ]; var i, l=list.length, item; for (i=0; i<l; i++) { item = list[i]; setTimeout( function(){ alert(item); }, 1000 ); }
The item variable is the same variable in each of the three iterations of the loop, even though the value is different each time. The anonymous function which is passed to setTimeout() isn't executed until 1 second in the future, and by that time the item variable has the value of 'c' (having been set to that in the last iteration of the loop).
Note that the length of time specified doesn't matter. It only matters that the code is executed after the current code path (for loop) finishes executing. A timeout of 0 would produce the same effect.
This is where loop closures help. Here is the same example again, this time produced with a loop closure:
var list = [ 'a', 'b', 'c' ]; for (var i=0, l=list.length; i<l; i++) (function(item){ setTimeout( function(){ alert(item); }, 1000 ); })(list[i]);
Running the above produces the expected outcome: three alert boxes containing the three list items sequentially, starting about 1 second after the code is run. This is because the item variable is a parameter to the loop closure function, and so is scoped completely within each iteration. In other words, now there are three item variables, one for each pass through the loop.
When not to use loop closures
The loop closure technique is very useful for encapsulating event-driven code which would otherwise run afoul of JavaScript's scoping model. It may not be the best solution in every case however since it defines the inner function on each loop iteration. Some JavaScript engines will be better than others at pre-compiling the loop rather than interpreting the JavaScript on each pass, so for very large datasets you may see performance degradation in some environments.
Nevertheless, there are a wide variety of cases where you can be fairly certain that there is a reasonable upper bound on the list size, and therefore it's safe to use the technique. One example would be a navigation or tab panel, where you may have 10 or 20 entries, but 2000 would obviously be way outside the scope of normal.
That's all there is to it. Hope this helps you become a better JavaScript developer - I welcome all comments and questions :)
Public domain declaration
Just so there's no confusion: all of the code snippets on this page are provided "AS IS", without warranty of any kind, express or implied.
All of the code snippets on this page are hereby released into the public domain by the me, the copyright holder. This applies worldwide. Or in case this is not legally possible: The copyright holder grants any entity the right to use this work for any purpose, without any conditions, unless such conditions are required by law.
If you'd feel better with a "real" license, you're free to use code snippets on this page under the MIT license as described on the about page.
Any links back to this site are always appreciated, but not required. Enjoy!
--Jim R. Wilson (jimbojw) 18:46, 19 March 2009 (UTC)