8
/*Test scope problem*/
for(var i=1; i<3; i++){
    //declare variables
    var no = i;
    //verify no
    alert('setting '+no);

    //timeout to recheck 
    setTimeout(function(){
        alert('test '+no);
    }, 500);
}

It alerts "setting 1" and "setting 2" as expected, but after the timeout it outputs "test 2" twice - for some reason the variable "no" is not reset after the first loop...

I've found only an "ugly" workaround:

/*Test scope problem*/
var func=function(no){
    //verify no
    alert('setting '+no);

    //timeout to recheck 
    setTimeout(function(){
        alert('test '+no);
    }, 500);
}
for(var i=1; i<3; i++){
    func(i);
}

Any ideas on how to workaround this problem in a more direct way? or is this the only way?

3
  • 1
    I would, also, love to see a detailed explanation of what is happening to no's scope here. Commented Apr 28, 2010 at 16:04
  • 2
    @Daniel Bingham - no's scope is global unless Trouts is doing this within a function. (Loops don't set a new scope in js.) Commented Apr 28, 2010 at 16:07
  • @fig-gnuton Oh. Well that's a simple explanation for it :D Thanks! Commented Apr 28, 2010 at 16:09

4 Answers 4

13

JavaScript does not have block scope, and variable declarations are hoisted. These facts together mean that your code is equivalent to:

var no;

/*Test scope problem*/
for(var i=1; i<3; i++){
    //declare variables
    no = i;
    //verify no
    alert('setting '+no);

    //timeout to recheck 
    setTimeout(function(){
        alert('test '+no);
    }, 500);
}

By the time your timeout function executes, the loop is long finished, with no retaining its final value of 2.

A way around this would be to pass the current value of no into a function that creates a fresh callback for each call to setTimeout. Creating a new function each time means each setTimeout callback is bound to a different execution context with its own set of variables.

var no;

/*Test scope problem*/
for(var i=1; i<3; i++){
    //declare variables
    no = i;
    //verify no
    alert('setting '+no);

    //timeout to recheck 
    setTimeout( (function(num) {
            return function() {
                alert('test '+num);
            };
        })(no), 500);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Great answer, it think t fully explains it. I originally thought the scope always worked within the {}'s in Javascript thus the question.
2

This is essentially the same as your fix, but using a different syntax to achieve the scoping adjustment.

/*Test scope problem*/
for (var i = 1; i < 3; i++) {
  //declare variables 
  var no = i;
  //verify no 
  alert('setting ' + no);

  //timeout to recheck
  (function() {
    var n = no;
    setTimeout(function() { 
      alert('test ' + n);
    }, 500);
  })();
} 

Comments

1

Javascript does not have lexical scoping(the for-loop does not create a new scope), and your solution is the standard workaround. Another way to write this might be:

[1, 2].forEach(function(no){
    //verify no
    alert('setting '+no);

    //timeout to recheck 
    setTimeout(function(){
        alert('test '+no);
    }, 500);
})

forEach() was introduce in ECMAScript 5 and is present in modern browsers but not IE. You can use my library to emulate it though.

Comments

1

I'm liking that I can get so much mileage out of this answer.

If you need help applying that answer, let me know.

EDIT

Sure. Let's look at your original code.

//timeout to recheck 
setTimeout(function(){
    alert('test '+no);
}, 500);

See that anonymous function? The one you pass into setTimeout()? It isn't called until the timer says so - which at 500ms is well after the loop has exited.

What you're hoping for is for no to be evaluated "in place" but its not - it's evaluated at the time the function is called. By that point, no is 2.

In order to get around this, we need a function that executes during the iteration of the loop, which itself will return a function that setTimeout() can use in the way we expect it to.

setTimeout(function( value )
{
  // 'value' is closed by the function below
  return function()
  {
    alert('test ' + value );
  }
}( no ) // Here's the magic
, 500 );

Since we create anonymous function and immediately call it, a new scope has been created in which we can close off variables. And that scope closes around value, not no. Since value receives a new (ahem) value for each loop, each of these lambdas has it's own value - the one we want it to.

So when setTimeout() fires, it's executing the function returned from our closure function.

I hope that explains it.

1 Comment

Yeah, not seeing how this relates to this question. Can you explain a closure to a C/Java developer only just learning Javascript?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.