What is wrong with my javascript scope?

JavascriptScopeClosures

Javascript Problem Overview


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 Solutions


Solution 1 - Javascript

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);
}

Solution 2 - Javascript

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:

Solution 3 - Javascript

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?

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNaftaliView Question on Stackoverflow
Solution 1 - JavascriptIngo BürkView Answer on Stackoverflow
Solution 2 - JavascriptbfavarettoView Answer on Stackoverflow
Solution 3 - JavascriptIzkataView Answer on Stackoverflow