1

I gave up on this after doing some research. My question is below:

From the post, I understand the following code.

var funcs = {};
for (var i = 0; i < 3; i++) {          // let's create 3 functions
funcs[i] = function() {            // and store them in funcs
    var item = "item" + i;         // inside
    console.log("item: " + item + ", i: " + i); // each should log its value.
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Result:
item: item3, i: 3
item: item3, i: 3
item: item3, i: 3

But if I change the move var item = "item" + i outside of function(), the result will be different. Why is this?

var funcs = {};
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    var item = "item" + i;             // outside
    funcs[i] = function() {            // and store them in funcs
        console.log("item: " + item + ", i: " + i); // each should log its value.
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

Result:
item: item2, i: 3
item: item2, i: 3
item: item2, i: 3
Community
  • 1
  • 1
ls.
  • 395
  • 4
  • 13
  • The real problem is your declaration and usage of the 'i' variable. It is maintained in the top level scope, but you use it in a closure. The closures are called when you "and now let's run...". By then, the 'i' variable (used for the for(;;) will have completed its loop and have the value displayed. – Marvin Smit Nov 01 '14 at 09:38

2 Answers2

4

The key problem you're facing is that a closure capture variables by reference, not by value. For example:

var i = 0;
var f = function(){ i++; console.log(i); }
var g = function(){ i++; console.log(i); }
f(); g(); f(); g();

this will display 1, 2, 3, 4 because f and g are not capturing the value of the variable i, but the variable itself and therefore are both mutating the same variable.

A very common mistake is to create multiple closures in a loop forgetting that all of them will capture the same variable (the one used in the loop) and thus that when this loop variable is incremented all the already created closures will see it changing. For example in your code

for (var i = 0; i < 3; i++) {
    funcs[i] = function() {
        var item = "item" + i; // inside
        console.log("item: " + item + ", i: " + i);
    };
}

item is a variable local to the function but i instead comes from the outside and it's shared between all of them. This can be seen for example with

i = 42;
funcs[0](); funcs[1](); funcs[2](); // Displays 42 three times
i = 99;
funcs[0](); funcs[1](); funcs[2](); // Now they all display 99

In the second case of your question things are just slightly different because in

for (var i = 0; i < 3; i++) {
    var item = "item" + i;
    funcs[i] = function() {
        console.log("item: " + item + ", i: " + i);
    };
}

also the item variable is now shared as it's defined outside the function and the closure captures it. In the display you see the last value that's assigned to it.

The key point to remember that in Javascript a variable is local to the function, not local to the {...} block (like it's in C++ for example) and thus the discriminating factor is if the variables are declared inside the function or outside the function, not if they're declared inside or outside the loop.

To overcome this common problem and have a separate index for each of the closures created in a loop often you will see code like

var f = (function(i){return function(){ ... };})(i);

where a function is created and immediately called to return another function.

The reason is that the only way to create a separate new variable is a function, leaving us with the pattern of creating a function that returns a function: the useful closure (the inner) in this case will capture a variable that is different for each of the iterations (because it's a parameter of the outer).

Changing your code to

for (var i = 0; i < 3; i++) {
    funcs[i] = (function(i){return function() {
        var item = "item" + i;
        console.log("item: " + item + ", i: " + i);
    };})(i);
}

Will work as expected (i.e. each function will now have its own i value).

EDIT

In current Javascript all the above applies to var variables (that are function scoped) but you can also use let variable that are instead block scoped.

This means the code can now be simplified and for example with:

lambdas = [];
for (let i=0; i<10; i++) {
    let j = i*i; // Variable j is block scoped
    lambdas.push(function(){ return j; });
}
console.log(lambdas[3]()); // ==> output 9
console.log(lambdas[5]()); // ==> output 25

(with var j = i*i; instead you'll get 81 for both)

6502
  • 112,025
  • 15
  • 165
  • 265
  • Thanks. But could you please answer the question: what is the difference between my code 1&2? why putting `var item` outside loop will make a difference? – ls. Nov 04 '14 at 21:54
  • 1
    @ls.: The problem is not being outside the loop, but being inside or outside the **function**. If `item` it's outside the function then it's a variable shared by all the functions, if it's inside the function then it's a different variable for each function. – 6502 Nov 04 '14 at 22:26
  • I think I've got it! If you could add this comment, i.e. 'if item it's outside ... if it's inside the ...' in your answer, I'll vote it as answer. (because I really didn't get your original answer). Thanks a lot – ls. Nov 06 '14 at 01:05
0

In the first case the item variable doesn't exist until the function is run, at which point it is local to each function and gets set equal to "item" + i. And i is 3 because it is declared in the outer scope and 3 is what it was when the loop ended.

In the second case, the item variable exists in the functions' containing scope - the same scope as i - and it has been set to "item2" during the last iteration of the first loop. Inside that loop i had the values 0, 1, 2 - it only got set to 3 when the loop ended.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • I still don't understand. In the second case, why i in `console.log()` gets 3 which is the value when loop ended while i in `item` get 2 which is the value of the last iteration? what's main problem causing this difference and why? – ls. Nov 01 '14 at 11:40