JavaScript closures in for-loops

JavaScript closures are not easy to get your head around. I've seen experienced programmers wrestle with the concept on more than one occasion, and once they are starting to get the hang of it they often seem to run into the same pitfall: using closures inside a for-loop.

The most common scenario is this: a script should loop through a set of DOM elements and one by one attach a locally defined function as an event handler. In this example I'm trying to make each list item (<li>) alert its numerical position when I click them:

function attachEventsToListItems( ) {
    var oList = document.getElementById('myList');
    var aListItems = oList.getElementsByTagName('li');
    for(var i = 0; i < aListItems.length; i++) {
        var oListItem = aListItems[i];
        // Here I try to use the variable i in a closure:
        oListItem.onclick = function() {
            alert(i);
        }
    }
}

Click these list items to see the result:

  • First item
  • Second item
  • Third item
  • Fourth item

Strange, eh? All of the items alert the same value: 4. The reason for this is quite complicated. The anonymous functions we define as event handlers 'inherit' the variable i from the scope of attachEventsToListItems, and not the for-loop. However, by the time the event handlers are executed, the for-loop has completed its iterations and the value of i in this function has become 4. The problem here is that the functions we define as event handlers don't create a new scope for i until they are executed.

The best way to get around this is to create a new scope for the 'current' value of i by executing a function inside the loop:

function attachEventsToListItems( ) {
    var oList = document.getElementById('myList');
    var aListItems = oList.getElementsByTagName('li');
    for(var i = 0; i < aListItems.length; i++) {
        var oListItem = aListItems[i];
        // Watch this:
        oListItem.onclick = (function(value) {
            return function() {
                alert(value);
            }
        })(i);
    }
}
  • First item
  • Second item
  • Third item
  • Fourth item

There's some tricky stuff going on so I'll highlight the important parts. What's going on here is this: instead of attaching the event handler (the inner function with the alert) directly I create an intermediate function (1), that takes one argument (2) and passes it on to the inner function. I then immediately call this function with i as the argument (3):

oListItem.onclick = (function1(value2) {
    return function() {
        alert(value);
    }
})(i)3;

Calling this function creates a new variable scope for each iteration because variable scope is created at execution time. This allows me to pass on the value of i to this new scope.

Now, if we want to use that value we need to do something special. Remember that we're trying to attach an event handler here so the intermediate function that is executed should return a function itself that can be attached. This function inherits the variable scope from the intermediate one and can safely use the value variable with the value it had during the execution of the loop.

Like I said: this is tricky stuff and to understand it it takes a bit more knowledge of the inner workings of JavaScript than is needed normally. However, I think it's a really useful trick for a problem that would otherwise be solved by attaching new properties to a DOM element. And that's just ugly ;)

Have something to say about this post? Share your thoughts!