1

I was reading an article online when I came across this:

// Sample 2
var funcs = [];

for(let x=0; x<3; x++) { 
  funcs.push(() => x); 
}

funcs.forEach(f => console.log(f()));
// Output: 0, 1, 2
// when using var instead of let in the for-loop: 3, 3, 3

Can someone please explain why this works? I would have thought the same would happen with var...

I know the difference between var and let. What I'm asking is why it doesn't work with var.

Bluefire
  • 13,519
  • 24
  • 74
  • 118
  • https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/let – dfsq Sep 07 '16 at 19:02
  • 1
    @Quentin That question asks something different and the answers do not answer mine. I know the difference between `var` and `let`. What I'm asking is why it doesn't work with `var`. – Bluefire Sep 07 '16 at 19:03
  • 1
    @Bluefire — So your question is actually http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example ? – Quentin Sep 07 '16 at 19:08
  • If you know the difference between `var` and `let`, then you should know why it doesn't work with `var`. –  Sep 07 '16 at 19:10
  • 2
    @squint I think it is not obvious that each iteration creates its own block scope. –  Sep 07 '16 at 19:14
  • @ftor: If one knows that `let` is scoped to a block and `var` is not, and they see a loop wherein a function gets a scoped variable with `let` but not with `var`, it becomes pretty darn obvious. –  Sep 07 '16 at 19:17
  • 2
    Either way, the OP stated *"What I'm asking is why it doesn't work with `var`."* The duplicate addresses exactly that. –  Sep 07 '16 at 19:18
  • 1
    @squint Maybe even more related: [let keyword in the for loop](http://stackoverflow.com/q/16473350/6445533) –  Sep 07 '16 at 19:27
  • 1
    @ftor: Sort of. Quentin had closed the question as one that tells that `let` is scoped to the loop, but the OP objected to that duplicate, stating that the real question is why it doesn't work with `var`. No matter what angle the OP puts on this, it has been asked and answered many times, and is clearly addressed by reading documentation. –  Sep 07 '16 at 19:33

1 Answers1

6

The code for (var x=...) declares a single variable whose value changes, but which is closed over for each function created. All functions reference the same value, with its latest changes. Using slightly older code hopefully makes this more obvious:

var callbacks = [];
for (var i=0;i<5;++i) callbacks.push( function(){ console.log(i) } );
callbacks.forEach(function(func){ func() })
// Outputs "5" five times

The code for (let x=...) declares a new variable each time the loop is run, so each function gets a new variable (and associated value). This is very convenient when creating callbacks, so you don't have to do the old trick of:

    var callbacks = [];
    for (let i=0;i<5;++i){
      // Create an anonymous function and invoke it, passing in 'i'.
      // Each time the function is run a *new* variable named 'n'
      // is created and closed over by the function returned inside.
      var cb = (function(n){ return function(){ console.log(n) }})(i);
      callbacks.push(cb);
    }
    callbacks.forEach(function(func){ func() })
    // Outputs "0","1","2","3","4"

And, for proof, here it is using let:

    var callbacks = [];
    for (let i=0;i<5;++i) callbacks.push( function(){ console.log(i) } );
    callbacks.forEach(function(func){ func() })
    // Outputs "0","1","2","3","4"
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • 1
    Perhaps more technical than what OP was wondering but *why* does a new variable get declared each time the loop is run? The declaration clause of a `for` loop isn't repeated when using `var` (after all, then all loop variables would be stuck at their initial value). Is this just a case of there being a special case in the spec or an inherent property of `let` declarations? I understand that they're block-scoped which is why the value doesn't "leak" like it would with `var`. I'm just wondering if there's a proper explanation for why the variable(s) are re-defined per iteration. – Mike Cluck Sep 07 '16 at 19:14
  • 3
    @MikeC See [this question and answer](http://stackoverflow.com/questions/16473350/let-keyword-in-the-for-loop), including [this bug report](https://bugs.chromium.org/p/v8/issues/detail?id=2560). In short: it is how it is because people did extra work to make it more useful in this case. – Phrogz Sep 07 '16 at 19:17
  • 1
    Awesome. That's exactly what I was looking for. Thanks! – Mike Cluck Sep 07 '16 at 19:19
  • Do 'let' keyword work in all browsers? – Taiti Sep 07 '16 at 22:34