65

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?

12
  • 1
    "like it should" - did you mean "like I want it to"? :) Commented Nov 11, 2013 at 19:19
  • 1
    You are only missing, that Javascript is broken as hell, I had the exact same WTF moment about a week ago … :( Commented Nov 11, 2013 at 19:19
  • @VisioN Not really. I know how they work. I am wondering why they do not work like they should in this case. Commented Nov 11, 2013 at 19:21
  • 1
    @Neal Well, I personally see that j is not initialized in the scope of setTimeout but in the scope of timer function, whereas in the second example you create an anonymous function, where you pass i, implicitly initialising j in the scope of closure. This creates and executes 3 functional blocks, setting 3 timeouts at once. Commented Nov 11, 2013 at 19:27
  • 4
    It surprises me that someone who has answered over 1000 JavaScript/jQuery questions doesn't know how variable scope works in the language. Commented Nov 11, 2013 at 19:41

3 Answers 3

37

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);
}
Sign up to request clarification or add additional context in comments.

7 Comments

Is there any way to prevent the code from defining it like this?
Just edited a pseudo-code solution in.
Haha that is basically what I do in the end of my OP.... silly hacks.
@Neal You edited that last part into your question, didn't you? Because if not, I must be really blind to not have seen it when writing my answer.
@IngoBürk Nope. That was there the whole time. That was the whole point of my question... If that was the only way to do it.
|
6

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:

3 Comments

Sooo hacky!! Why does javascript make you do these things?
It's not hacky, it's just how scope works in this language.
There's nothing hacky about it: Function scope versus block scope is a simple language feature which comes with effects like this. It just feels hacky as a lot of other languages have block scope which would prevent this behavior.
3

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?

2 Comments

I like that you gave a with example, but it doesn't create a scope "like functions do". There are important differences, which led to its removal from "strict mode".
@BlueSkies Pseudo-scope, then. For the most part with practical use, it acts like it does.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.