2

I run the two following code snippets.

The first loop is giving the expected result when I assign a different name for the local variable.

for(var i = 0; i < 3; i++) {
    setTimeout((function() {
     var i2 = i;//named i2 here
        return function(){console.log(i2)};
    })(), 10);
}

The second loop will print undefined instead. I thought var i = i will just re-declare the original i. And I expect it pint out some number. How come I get undefined here?

for(var i = 0; i < 3; i++) {
    setTimeout((function() {
     var i = i;
        console.log(i);
        return function(){console.log(i)};
    })(), 10);
}
skanecode
  • 65
  • 7

4 Answers4

3

The scope of the initialization expression of var is the body of the function, and the local variable i is already in that scope. So you can't reference an outer variable with the same name. You can think of

var x = <expression>;

as equivalent to:

var x;
x = <expression>;

If you look at it this way, you can see why var i = i; won't work, it's equivalent to:

var i;
i = i;

The assignment uses the uninitialized value of the local variable.

The usual idiom to solve this is to make i a parameter to the function, which you then pass in the argument list of the IIFE.

for(var i = 0; i < 3; i++) {
    setTimeout((function(i) {
        console.log(i);
        return function(){console.log(i)};
    })(i), 10);
}

For more information, see JavaScript closure inside loops – simple practical example

Barmar
  • 741,623
  • 53
  • 500
  • 612
1

Scope

The inner var i declares a new variable called i within a scope that already has a variable called i.

The declaration is assigned the value of the scope's most prominent i, which is the one you just declared.

Since the value of the newly declared i is undefined, the assignation of itself to itself is of its value of undefined.

Fred Gandt
  • 4,217
  • 2
  • 33
  • 41
1

The variable i is already being used by the loop for in the outer scope. Now you are declaring a new variable i in an inner scope

var i = i;

Once that statement is ran the inner scope is the counter i of the loop is more accessible because you overridden it with new i.

Basically what you are doing here is: defining a new variable i and assign to it a value of that variable you just declared which is undefined.

Eaiset solution is to declare j and assign to it the value i.

for(var i = 0; i < 3; i++) {
    setTimeout((function() {
        var j = i;
        console.log(j);
        return function(){console.log(j)};
    })(), 10);
}

OR just use i while passing it the function setTimeout

for(var i = 0; i < 3; i++) {
    setTimeout((function(i) {
        console.log(i);
        return function(){console.log(i)};
    })(), 10);
}
Oussama Ben Ghorbel
  • 2,132
  • 4
  • 17
  • 34
1

My guess is it should be return function(){console.log(i)}; in the second snippet. And the reason it still does not work is, that you're basically assigning to var i the same i you just declared with var. You'll see what I mean if you split declaration and assignment:

var i;
i = i;

But yeah, I can see how js quirks like that can be quite frustrating, because one has to always watch out and reinvent new variable names as one goes down the call stack. This is one reason why I fell in love with TypeScript, because it compiles this:

for (let i = 0; i < 3; ++i) {
    window.setTimeout(() => {
        console.log(i);
    });
}

into this:

var _loop_1 = function (i) {
    window.setTimeout(function () {
        console.log(i);
    });
};
for (var i = 0; i < 3; ++i) {
    _loop_1(i);
}

which will yield the expected result.

Patrick Günther
  • 320
  • 2
  • 10