1

A little while ago, I asked a question using this sample JS code...

for (var myindex = 0; myindex < mylist.length; myindex += 1) {
    var copyindex = myindex;
    MyAsync(mylist[myindex], function () { alert(copyindex); });
}

(The answer was essentially to call a named function, which keeps each variable in the loop separate.)

My question for today is, what's actually going on in this sample code above? Each alert call is being passed the same copyindex variable, even though a new one is being declared each time the loop goes around.

Is this behavior required by the JS language? (That by declaring a variable with block scope, I'm really declaring it at the top of the function.)

Or, is this perhaps an implementation detail of the few browsers I tested with, and future JS platforms are under no obligation to link the many instances of copyindex together?

Community
  • 1
  • 1
billpg
  • 3,195
  • 3
  • 30
  • 57
  • 1
    I disagree with the flag-as-duplicate. That question asks how to fix the problem. This question is instead asking what exactly the problem is. – billpg Jun 06 '14 at 09:34
  • A good answer should explain what the problem is before explaining how to solve it. The answer to that question does it, so the scope of your question is included there. – Pablo Lozano Jun 06 '14 at 09:41

1 Answers1

2

Each alert call is being passed the same copyindex variable, even though a new one is being declared each time...

That's a common misunderstanding. var does not have block scope, so your code really is this:

var copyindex;
for (var myindex = 0; myindex < mylist.length; myindex += 1) {
    copyindex = myindex;
    MyAsync(mylist[myindex], function () { alert(copyindex); });
}

More (on my blog): Poor, misunderstood var and, of course, in the specification — it's covered in §10.5 - Declaration Binding Instantiation.


ES6 will introduce block-scoped variables via the let keyword. Where you use the let keyword matters quite a lot.

Let's take a simpler example, starting with var:

for (var x = 0; x < 3; ++x) {
    setTimeout(function() { console.log("x = " + x); }, 0);
}
console.log("typeof x = " + typeof x);

We know that will give us

number
3
3
3

...because var is hoisted to the top of the scope, and so the same x is used by all three functions we create, and x exists outside the loop. (We see the typeof result first, of course, because the others happen after the minimal timeout.)

If we use let instead, in that same place:

for (let x = 0; x < 3; ++x) {
    setTimeout(function() { console.log("x = " + x); }, 0);
}
console.log("typeof x = " + typeof x);

We get

undefined
3
3
3

...because x is scoped to the loop, not loop iterations. All three functions still use the same x, the only difference is that x doesn't exist outside the loop.

But if we use let within the body:

for (let n = 0; n < 3; ++n) {
    let x = n;
    setTimeout(function() { console.log("x = " + x); }, 0);
}
console.log("typeof x = " + typeof x);

We get

undefined
0
1
2

...because now x is scoped to the body of each iteration, not the loop as a whole.

You can try these yourself using NodeJS, just be sure to give it the --harmony flag to enable ES6 features.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks! Just to pin your 99% perfect answer down, is what you've described with var a *requirement* of JS? – billpg Jun 06 '14 at 09:40
  • @billpg: Yes, `var`'s behavior is defined quite clearly in the specification; I've added the link above. Even better, implementations have always (to the best of my knowledge) actually gotten it right. It'll be interesting to see how implementations fare with `let`... :-) – T.J. Crowder Jun 06 '14 at 09:43