5

Running the following code:

for (var i=0; i<3; i++) { 
   setTimeout( function() { console.log(i); } , 500 ); 
}

Outputs "3" three times. It's outputting the final value of i as opposed to the value of i when the inner function is created.

If I want the output to be 1, 2, and 3, how would I write this code? How can I get it to use the value of i at the time the function is defined as opposed to its final value?

palswim
  • 11,856
  • 6
  • 53
  • 77
Parand
  • 102,950
  • 48
  • 151
  • 186

4 Answers4

6
for (var i=0; i<3; i++) {
   setTimeout( function(val) { return function() { console.log(val); } }(i), 500 );
}

So, at setTimeout time (at the time we define the function for setTimeout), we're calling the anonymous function taking val as a parameter. This creates a closure for each function call, storing the value of val within the scope of the function we just called. I used a self-invoking function, which creates an immediate closure.

In the code you provided, the code creates a closure, but for the larger scope of the entirety of the code, so i is local to the whole code, meaning that at run-time, the anonymous function will use the variable i that the rest of the code uses.

palswim
  • 11,856
  • 6
  • 53
  • 77
  • This example uses two anonymous functions, whereas @z5h's answer uses a named function, which may illustrate the concept more clearly. – palswim Sep 08 '10 at 21:58
4
function f(i){
  return function(){console.log(i);};
}

for (var i=0; i<3; i++) { 
   setTimeout( 
     f(i)
   , 500 ); 
}
Mark Bolusmjak
  • 23,606
  • 10
  • 74
  • 129
2

The modern alternative to an explicit closure (which can get a bit hairy to read when you've got a double-wrapped function) is Function#bind. Once you've hacked in support for browsers that don't do ECMAScript Fifth Edition yet, you can say:

for (var i=0; i<3; i++) {
    setTimeout(function(i) { console.log(i); }.bind(window, i), 500);
}

the window is the value that this will be inside the function (you don't need a this here, so we just use the default global object). In the case where you're just calling another function/method, like here with console.log, you can use that to excise the function expression completely:

for (var i=0; i<3; i++) {
    setTimeout(console.log.bind(console, i), 500);
}
Community
  • 1
  • 1
bobince
  • 528,062
  • 107
  • 651
  • 834
1

alternative:

for (var i=0; i<3; i++) {
   (function(val){
       setTimeout(function() {
           console.log(val);
       },500)
   }(i));
}
David Murdoch
  • 87,823
  • 39
  • 148
  • 191