Jun, Hyunje

Code, specifically for web

Programming languages

Some geeky hobbies

Variable scope of JavaScript was weird. JavaScript used to have only dynamic scope with var, which makes all variables in a function have the same function scope regardless of presence of block or name shadowing. It was why ES2015 introduced new lexical declarations, let and const.

let i = 10;
{
let i = 20;
console.log(i); // => 20
}
console.log(i); // => 10

It seems like working with for loop as expected.

let i = 10;
for (let i = 0; i < 5; i++) {
console.log(i); // => 0, 1, 2, 3, 4
}
console.log(i); // => 10

But yesterday one thing confused me. Please guess the result of the following code.

let fs = [];
for (let i = 0; i < 5; i++) {
fs.push(() => i);
}
console.log(fs.map(f => f()));

My guess was [5,5,5,5,5], as I thought the i is shared through the for loop. A long version of my guess is like below:

  1. A lexical variable i is created on for (let i = 0;
  2. A closure returning the i is pushed to fs
  3. i is increased by 1
  4. A closure returning the i is pushed to fs
  5. i is increased by 1
  6. … iterates while i < 5
  7. When the loop finishes, i is not GCed as there are closures referencing the i
  8. And i is now 5
  9. All the closures return i, which is 5
  10. The result is [5,5,5,5,5]

And basically, I was wrong. The result is [0,1,2,3,4], which means i in the block is actually a new i in every iteration:

  1. A lexical variable i is created on for (let i = 0;
  2. A closure returning the variable is pushed to fs
  3. A new i is bound with the value of the old i
  4. i is increased by 1
  5. A closure returning the variable is pushed to fs
  6. A new i is bound with the value of the old i
  7. i is increased by 1
  8. … iterates …
  9. All the i s returned by the closures are actually all different bindings
  10. The result is [0,1,2,3,4]

In short, when used with lexical declaration, for loop binds a new variable for each iteration, while a previous value is actually inherited. It is specified in Standard ECMA-262.

I am not sure other languages with lexical declaration and C-styled for loop do the same, but at least C++ doesn’t do this and I was a bit confused. I understand that capturing a variable with closure is very common in JS and sharing the variable through the closures is error-prone, but I still think that the case should be handled by programmers, not language spec.

read other posts