/*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?
/*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?
Share Improve this question edited Dec 22, 2017 at 6:26 Brock Adams 93.5k23 gold badges240 silver badges304 bronze badges asked Apr 28, 2010 at 15:58 Carlos OuroCarlos Ouro 5656 silver badges16 bronze badges 3 |4 Answers
Reset to default 13JavaScript 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);
}
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);
})();
}
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.
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.
no
's scope here. – Daniel Bingham Commented Apr 28, 2010 at 16:04no
's scope is global unless Trouts is doing this within a function. (Loops don't set a new scope in js.) – user65663 Commented Apr 28, 2010 at 16:07