1

I'm learning JS. Why does logging funcs2[1](); log 4 and funcs[1](); logs 5?

Note that this is not a duplicate of this question. I know that funcs[1](); logs 5 (and not 1) because the function called is bound to the current value of i, which is 5 when the loop terminates.

But that's not my question. I want to know why funcs2[1](); log 4 and not 5.

var funcs = [];
for (var i = 0; i < 5; i++) {
    funcs.push(function () {
        return i;
    });
}

console.log(funcs[1]());
5

var funcs2 = [];
for (var i = 0; i < 5; i++) {
    var x = i;
    funcs2.push(function () {
        return x;
    });
}

console.log(funcs2[1]());
4
Community
  • 1
  • 1
Jumbalaya Wanton
  • 1,601
  • 1
  • 25
  • 47

3 Answers3

2

because when i is increased to 5, it will not enter into the loop so that finally x is 4.

this is a famous JS closure issue, internal function only keeps the parent context's variable object in it's internal [[scope]] property but not variable. so when the loop is done, i in parent's vairable object is equal 5, while x is equal 4.

hjl
  • 2,794
  • 3
  • 18
  • 26
  • Thanks. Your answer is also correct, but I chose @dfsq because it's better worded. – Jumbalaya Wanton Dec 29 '14 at 16:01
  • never mind, but i will consider this a closure issue but not function/block scope issue. to understand closure is a critical path in JS learning – hjl Dec 29 '14 at 16:10
  • We are talking about the same things. The issue is because there is no block-level scope. Hence the problem with closure. If inside of each `for` iteration there was separate scope the value in closure would be correct. – dfsq Dec 29 '14 at 16:15
  • @dfsq - actually they are not exactly the same. look at my updated case for funcs3, `k` is in `foo`'s scope, it seems like `k` should be destroyed when `foo` ends execution and `funcs3[1]` can never refer to `k`, but actually `funcs3[1]` keeps another reference of `k` via closure so that `k` is not GCed. – hjl Dec 29 '14 at 16:30
2

Since there is no block scope in javascript, after both loops finish execution, the value of variable i is 5 in both cases.

However the value of the x in the second case is 4 because this is the last iteration index and i++ happens after x = i assignment.

dfsq
  • 191,768
  • 25
  • 236
  • 258
  • So `i++` is evaluated for the last time (i increments to 5), and the body of the loop does not execute, and in funcs2, since I am returning x, it would have been stuck at 4? – Jumbalaya Wanton Dec 29 '14 at 15:57
  • Yes, `i` is incremented the last always and then `i < 5` is compared again, but you never enter the loop when `i` == `5`. Put the `console.log(x, i);` after `var x = i` and you will see. – dfsq Dec 29 '14 at 16:00
  • Yes, I just logged i **inside the loop** of `funcs` and saw that it never reaches 5. – Jumbalaya Wanton Dec 29 '14 at 16:02
  • well function scope is not the root cause of OP's problem, closure mechanism is. – hjl Dec 29 '14 at 16:04
0

sorry, my edit on original post is rejected. to further discuss on this, i make another answer with an additional case:

var funcs3 = [];
function foo() {
    for (var k = 0; k < 5; k++) { // k is in foo's scope
        funcs3.push(function () {
            return k;
        });
    }
}

foo(); // k can't be accessed after from here
console.log(funcs3[1]()); // 5, k is still in closure 
hjl
  • 2,794
  • 3
  • 18
  • 26