-1

I'm trying to expand my modest level of JavaScript skills by learning how to use closures. In the code below, I thought I'd see console.log output counting down from 3 to 0. Instead, I'm getting -1, -1, -1, -1.

I know I'm dealing with scoping issues, but that's about it. What's missing? How should this be properly written, and why?

function closure_count_test (number)   
{    
    for (var x = 0; x <= number; x += 1)   
    {  
        setTimeout(function() {console.log(number - x);}, x * 1000);  
    }  
}  

closure_count_test(3); 
  • 1
    Yes. There is only **one** variable `x`. I gave a -1 because you know what the issue is (something related to "closure" and "scope"); and should have thus been able to find a duplicate. –  Jan 31 '13 at 07:04
  • because `x` inside your callback references to the same `x` in `closure_count_test`. The value of `x` is obtained when your callback is called. – Alvin Wong Jan 31 '13 at 07:06
  • http://stackoverflow.com/questions/5226285/settimeout-in-a-for-loop-and-pass-i-as-value , http://stackoverflow.com/questions/3448195/javascript-using-the-current-for-loop-counter-value-inside-a-function?lq=1 , etc –  Jan 31 '13 at 07:07
  • possible duplicate of [Doesn't JavaScript support closures with local variables?](http://stackoverflow.com/questions/643542/doesnt-javascript-support-closures-with-local-variables) –  Jan 31 '13 at 07:08
  • Thanks for the replies, everyone. I've been using SO as a resource for years, it's good to get constructive feedback to my first post. – Mark Johnson Feb 05 '13 at 03:24

4 Answers4

0

Your logic is correct. The problem is that setTimout only considers the latest value of the variables. So the setTimout always get x = 0 since it is the last in the loop.

you can see your desired output if you remove the setTimout function.

Jaspal Singh
  • 1,210
  • 1
  • 12
  • 17
  • The code in setTimeout only "considers" the *current* value of the variable; it just so happens to be last set the value during the last iteration of the loop. –  Jan 31 '13 at 07:11
0

this works because x being to be localised inside one more closure

function closure_count_test(number) {
    for (var x = 0; x <= number; x++)  ( // not need {} as here is only one operator
      function (x) { //Now x - local variable in anonymous function
        return setTimeout(function () {
            console.log(number - x);
        }, x * 1000);
      }(x) // pass x to anonymous function as argument
    ); 
}

closure_count_test(3);
zb'
  • 8,071
  • 4
  • 41
  • 68
  • This won't work because 1) the `console.log` will happen *immediately*, and 2) `setTimeout` will be passed `undefined` as the callback. (I didn't down-vote, but that is why this answer is wrong.) –  Jan 31 '13 at 07:09
0

How scoping works in basic words is, it makes variables of a main function, that are referenced in nested functions stay after function ends, and all of these functions can later access them. In provided example x is a variable defined in the main function, and all nested functions will later be able to reference it. By that time the value of x will be number+1, so your result makes perfect sense. To workaround that, you must avoid referencing the variable of the main function. Here's the normal technique:

function closure_count_test (number)   
{    
   for (var x = 0; x <= number; x += 1)   
   {  
       setTimeout(function(x) {
          return function() {console.log(number - x);}
       } (x), x * 1000);
   }  
}

What you do here, is you call several nested functions, which have their own x copied as an argument, and each of these have one nested function that will reference that argument via scope.

Kimitsu Desu
  • 101
  • 4
0

x is iterated by the for loop, but the function in setTimeout uses the variable x, which is not interpolated at the time the function is created. This causes it to use whatever the final value of x is (because the setTimeouts execute after the loop completes).

In order to get around this, you have to pass the value of x as it is to the callback of setTimeout. You can call a function that returns another function (the new callback):

for (var x = 0; x <= number; x += 1) {
    setTimeout(
        (function (x) {
            return function () { console.log(number - x) };
        })(x)
    , x * 1000);  
}

This passes x from the outer scope with its current value to the inner scope. The inner function uses whatever value x was when the function was created.

A function is returned to work properly with setTimeout.

http://jsfiddle.net/ExplosionPIlls/QhA3a/

Explosion Pills
  • 188,624
  • 52
  • 326
  • 405