0

Consider the following code snippet:

for (let i = 0; i < 5; i++) {
  i+=1;
  setTimeout(() => console.log(i), 100);
}
console.log('after for loop');

If let i were creating a new block scope variable with each iteration, I would expect it to output:

1
2
3
4
5

because, being a new block scope variable, i+=1; would only make changes to my local copy of i. Also, if i was a new block scope variable, that would explain why the setTimeout callback doesn't log "6" 3 times (as it does if let i is changed to var i).

In case it helps, here is what I'm imagining it would be doing under the hood if it were creating a new block scope variable for each iteration:

for (let I = 0; I < 5; I++) {
  let i = I;
  i+=1;
  setTimeout(() => console.log(i), 100);
}
console.log('after for loop');

However, the top snippet actually outputs:

1
3
5

which would make sense if i were shared between all iterations except if i were shared between all iterations why would the setTimeout callback not print the same number 3 times?

In short my question is:

Why, in the top snippet, is i+=1; both updating the loop variable as if i in i+=1; is not a local copy for each iteration and also behaving in the setTimeout callback as if i is a local copy for each iteration.

Rocky Sims
  • 3,523
  • 1
  • 14
  • 19
  • 1
    The `i` in the function is the **exact same** `i` as the block-scoped `i` declared in the loop header. The function contains no declaration or parameter; the variable is part of the function's closure. – Pointy Jul 04 '19 at 21:16
  • Your assumption is incorrect. In a `for` loop, a declaration in the for loop header is treated as if it appeared inside the block. – Pointy Jul 04 '19 at 21:18
  • 1
    @Bergi I don't think this question is a duplicate (at least not of the questioned linked above). The linked question doesn't include incrementing `i` inside the `for`'s code block and so does not directly address my question about how javascript handles that. It is perhaps a duplicate answer though... now that I understand what's happening I recognize the part of your answer that talks about creating a new lexical environment at certain times would have provided my answer if I'd understood it. – Rocky Sims Jul 04 '19 at 22:41
  • Yes, it doesn't talk about incrementing `i` in the loop body specifically, but it explains in general how the value of `i` gets transported between the block scopes. I can reopen it if you want. – Bergi Jul 05 '19 at 09:20

1 Answers1

2

When the variable declared in the for loop declaration gets reassigned within the loop body, that reassignment will persist for the next iteration. It's a lot clearer if you look at how Babel transpiles it:

for (let i = 0; i < 5; i++) {
  i+=1;
  setTimeout(() => console.log(i), 100);
}
console.log('after for loop');

results in

"use strict";

var _loop = function _loop(_i) {
  _i += 1; // <---------------
  setTimeout(function() {
    return console.log(_i);
  }, 100);
  i = _i; // <---------------
};

for (var i = 0; i < 5; i++) {
  _loop(i);
}

console.log("after for loop");

If you change or log i outside of the synchronous execution of the for loop body, it will (essentially) refer to the _i above, acting like a completely independent block-scoped variable. But if you change i inside the synchronous for loop body, the next iteration will start with the changed i.

Snow
  • 3,820
  • 3
  • 13
  • 39
  • Thanks, it's starting to make more sense. I take it something along the lines of `i = _i;` is happening at the end of each for loop iteration even in vanilla javascript? It's not just a quirk of how babel handles it? – Rocky Sims Jul 04 '19 at 21:27
  • 1
    Relevant video on this exact issue (and other scoping subtleties): https://youtu.be/Nzokr6Boeaw – RichieAHB Jul 04 '19 at 21:28
  • @RichieAHB Right - Babel is transpiling the code faithfully (otherwise, it'd be broken, and people wouldn't use it). – Snow Jul 04 '19 at 21:29
  • @RichieAHB thanks for the video link. Glad to have confirmation that vanilla javascript really is doing some fancy local variable creation and then copying it (with any changes) for use in the next iteration. – Rocky Sims Jul 04 '19 at 21:44