0

My question might look similar to this question: JavaScript closure inside loops – simple practical example But the difference is that I declare a new local var in each iteration:

var funcs = []; 

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

}

for (var j = 0; j < 3; j++) {
  funcs[j](); 
}

So by declaring a new variable and pass it to the closure context, I expected the value of b in each function is different but my output is:

My value: 2
My value: 2
My value: 2

It always has the value of the last var b in the iterations. Can you help me to understand this?

Thank you.

Community
  • 1
  • 1
Khue Vu
  • 3,112
  • 4
  • 37
  • 40
  • 3
    You can't declare a variable local to the block scope with `var`, since var-declared variables are always hoisted. Use `let` instead (ES6 only). – Teemu Dec 12 '15 at 16:19
  • 1
    [`let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) – Andreas Dec 12 '15 at 16:21
  • Regardless of where a `var` declaration appears, it's always treated exactly as if it had appeared at the beginning of the enclosing function (or ` – Pointy Dec 12 '15 at 16:28
  • 2
    If you read all of the answers in the question you reference, you'll see this talked about too, e.g. http://stackoverflow.com/a/29558498/1615483 – Paul S. Dec 12 '15 at 16:44
  • @PaulS. Why did you mark it as duplicate? It seems totally different situation. Look at the first answer, it will make you more clear. I think, this answer is the correct answer for this question, where this will not mean anything in the question you referenced. – Tᴀʀᴇǫ Mᴀʜᴍᴏᴏᴅ Dec 12 '15 at 17:30
  • @PaulS The answer you just referenced is provided on "Apr 10" and `let` in Javascript is introduced in ECMAScript 2015 (6th Edition, ECMA-262). So, that answer has already been invalid / incorrect now, where he says, "JavaScript does not have block scope. Variables introduced with a block are scoped to the containing function or script". – Tᴀʀᴇǫ Mᴀʜᴍᴏᴏᴅ Dec 12 '15 at 17:40

1 Answers1

1

The variable b is not defined in each iteration. Your code is not any different from this:

var funcs = [];
var b; 

for (var i = 0; i < 3; i++) {
    b = i; 
    //... etc
}

Your intended behaviour is reached with the let keyword (ES6). As explained on MDN:

let allows you to declare variables that are limited in scope to the block, statement, or expression on which it is used. This is unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.

Alternatively, you could define b as a function argument, and then fix it with bind.

var funcs = []; 

var f = function(b) {console.log("My value: " + b);}; 

for (var i = 0; i < 3; i++) {
    funcs[i] = f.bind(null, i);
}

Or, you could define a closure to get the local scope you needed:

var funcs = []; 

for (var i = 0; i < 3; i++) {
    funcs[i] = (function () {
        var b = i; // now b is local: to this anonymous function
        return function() {console.log("My value: " + b);}; 
    })();
}

And, a small, but better, variant of the above passes i as an argument to the closure, so also there b is local to that closure:

var funcs = []; 

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

Then even better, define the function (factory) once:

var funcs = []; 

var f = function(b) {
    return function() {console.log("My value: " + b);}; 
})(i);

for (var i = 0; i < 3; i++) {
    funcs[i] = f(i);
}

But, you will agree the let syntax is so much more readable.

trincot
  • 317,000
  • 35
  • 244
  • 286