/*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?
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);
}
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);
})();
}
I'm liking that I can get so much mileage out of this answer .
If you need help applying that answer, let me know.
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.
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.