views:

931

answers:

7

I am very puzzled about this code:

var closures = [];
function create() {
  for (var i = 0; i < 5; i++) {
    closures[i] = function() {
      alert("i = " + i);
    };
  }
}

function run() {
  for (var i = 0; i < 5; i++) {
    closures[i]();
  }
}

create();
run();

From my understanding it should print 0,1,2,3,4 (isn't this the concept of closures?).

Instead it prints 5,5,5,5,5.

I tried Rhino and Firefox.

Could someone explain this behavior to me? Thx in advance.

+6  A: 

Yes closures are working here. Each time you loop the function you are creating grabs the i. Each function you create shares the same i. The problem you are seeing is that since they all share the same i they also share the final value of i since it is the same captured variable.

Edit: This article by Mr. Skeet explains closures in some depth and addresses this issue in particular in a way that is much more informative then I have here. However be careful as the way that Javascript and C# handle closures have some subtle differences. Skip to the section called "Comparing capture strategies: complexity vs power" for his explanation on this issue.

Andrew Hare
So what would be his fix (I'm curious now too)?
Jess
Jon's answer has the fix.
Andrew Hare
It's a nice article but it appears that there are some differences in how closures are implemented between C# and Javascript. This makes the article not as helpful with regards to the OP's question.
Outlaw Programmer
You make a good point - the main thing I wanted to communicate was how multiple closures share a variable.
Andrew Hare
@Andrew: It's probably worth explicitly saying in your answer that JavaScript captured variables *don't* work the same way - which is why my attempted fix didn't work.
Jon Skeet
Nice job. Much better.
Jon Skeet
+6  A: 

I think this might be what you want:

var closures = [];

function createClosure(i) {
    closures[i] = function() {
        alert("i = " + i);
    };
}

function create() {
    for (var i = 0; i < 5; i++) {
        createClosure(i);
    }
}
Outlaw Programmer
A: 

Here is what you should do to achieve your result:

<script>
var closures = [];
function create() {  
    for (var i = 0; i < 5; i++) {   
     closures[i] = function(number) {      
     alert("i = " + number);   
     };  
    }
}
function run() {  
    for (var i = 0; i < 5; i++) {   
     closures[i](i); 
    }
}
create();
run();
</script>
Billy
How is this an example of closures at all now? You're basically just storing functions in the array, then explicitly providing 'i' through the function's arguments.
Outlaw Programmer
+4  A: 

The solution is to have a self-executing lambda wrapping your array push. You also pass i as an argument to that lambda. The value of i inside the self-executing lambda will shadow the value of the original i and everything will work as intended:

function create() {
    for (var i = 0; i < 5; i++) (function(i) {
        closures[i] = function() {
            alert("i = " + i);
        };
    })(i);
}

Another solution would be to create yet another closure which captures the correct value of i and assigns it to another variable which would "get caught" in the final lambda:

function create() {
    for (var i = 0; i < 5; i++) (function() {
        var x = i;

        closures.push(function() {
            alert("i = " + x);
        });
    })();
}
Ionuț G. Stan
To make the first implementation more clear and understandable, you could use a different parameter name than i to the inner function!
Chetan Sastry
@Chetan Sastry, I wasn't exactly after that. As you can see, even the placement of the self-executing lambda is weird. As if there was no problem in the beginning.
Ionuț G. Stan
+14  A: 

Fixed Jon's answer by adding an additional anonymous function:

function create() {
  for (var i = 0; i < 5; i++) {
    closures[i] = (function(tmp) {
        return function() {
          alert("i = " + tmp);
        };
    })(i);
  }
}

The explanation is that JavaScript's scopes are function-level, not block-level, and creating a closure just means that the enclosing scope gets added to the lexical environment of the enclosed function.

After the loop terminates, the function-level variable i has the value 5, and that's what the inner function 'sees'.


As a side note: you should beware of unnecessary function object creation, espacially in loops; it's inefficient, and if DOM objects are involved, it's easy to create circular references and therefore introduce memory leaks in Internet Explorer.

Christoph
+2  A: 

John Resig's Learning Advanced JavaScript explains this and more. It's an interactive presentation that explains a lot about JavaScript, and the examples are fun to read and execute.

It has a chapter about closures, and this example looks a lot like yours.

Here's the broken example:

var count = 0; 
for ( var i = 0; i < 4; i++ ) { 
  setTimeout(function(){ 
    assert( i == count++, "Check the value of i." ); 
  }, i * 200); 
}

And the fix:

var count = 0; 
for ( var i = 0; i < 4; i++ ) (function(i){ 
  setTimeout(function(){ 
    assert( i == count++, "Check the value of i." ); 
  }, i * 200); 
})(i);
Sébastien RoccaSerra
+1  A: 

Just defining an inner function, or assigning it to some variable:

closures[i] = function() {...

does not create a private copy of the whole execution context. The context isn't copied until the nearest outer function is exiting (at which point those external variables could be garbage collected, so we'd better grab a copy).

This is why wrapping another function around your inner function works - the middle guy actually executes and exits, cuing the innermost function to save his own copy of the stack.

John Rice