What is wrong with my javascript scope? [duplicate]
The following alerts 2
every time.
function timer() {
for (var i = 0; i < 3; ++i) {
var j = i;
setTimeout(function () {
alert(j);
}, 1000);
}
}
timer();
Shouldn't var j = i;
set the j
into the individual scope of the setTimeout?
Whereas if I do this:
function timer() {
for (var i = 0; i < 3; ++i) {
(function (j) {
setTimeout(function () {
alert(j);
}, 1000);
})(i);
}
}
timer();
It alerts 0
, 1
, 2
like it should.
Is there something I am missing?
Javascript has function scope. This means that
for(...) {
var j = i;
}
is equivalent to
var j;
for(...) {
j = i;
}
In fact, this is how Javascript compilers will actually treat this code. And, of course, this causes your little "trick" to fail, because j
will be incremented before the function in setTimeout
gets called, i.e. j
now doesn't really do anything different than i
, it's just an alias with the same scope.
If Javascript were to have block scope, your trick would work, because j
would be a new variable within every iteration.
What you need to do is create a new scope:
for(var i = ...) {
(function (j) {
// you can safely use j here now
setTimeout(...);
})(i);
}
The alternative the the IIFE is a function factory:
function timer() {
for (var i = 0; i < 3; ++i) {
setTimeout(createTimerCallback(i), 1000);
}
}
function createTimerCallback(i) {
return function() {
alert(i);
};
}
timer();
This being said, this is one of the most asked questions in the javascript tag. See:
- JavaScript closure inside loops – simple practical example
- Javascript infamous Loop issue?
An alternative is to use the (normally abused) keyword with
:
function timer() {
for (var i = 0; i < 3; ++i) {
with({j: i}) {
setTimeout(function () {
alert(j);
}, 1000);
}
}
}
timer();
It creates a new scope like functions do, but without the awkward syntax. I first saw it here: Are there legitimate uses for JavaScript's “with” statement?