5 ms·
I find this same semantic in JavaScript weird albeit practical. Many descriptions of either for-loops or let-variables don't mention that their combination is a
by tuukkah 4y ago
I find this same semantic in JavaScript weird albeit practical. Many descriptions of either for-loops or let-variables don't mention that their combination is a special case: each iteration gets its own instance of the variable, but somehow the result of i++ gets copied to the following iterations.
Here you can see that the i from the previous iteration gets copied first and i++ applies to the next iteration:
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// prints 0...9 (as expected?)
I always thought of the i++ happening at the end of the previous iteration, but that's wrong as it produces a different result if written explicitly:
for (let i = 0; i < 10; ) {
setTimeout(() => {
console.log(i);
}, 1000);
i++;
}
// prints 1...10 (as expected?)
(Also, these don't change if I remove the curly braces, so the let is not scoped within curly braces as I thought...)
- roywiggins 4y agoMy knowledge of javascript semantics is pretty limited, but you can see what's happening more clearly: for (let i = 0; i < 10; ) { setTimeout(() => { console.log(i, foo); }, 1000); let foo = "baz-"+i; i++; } // "1 baz-0", "2 baz-1, etc It's behaving as each loop iteration is creating its own closure, and the inner function is referring to those variables by reference. So any changes you make to them inside the body of the loop will end up visible to the inner function.
- tuukkah 4y agoMy point is that for the i++ to make the loop advance (whether it is written inside the block or outside it in the header), behind the scenes, the value of the old i is copied to the new i at the beginning of each iteration. I think to then understand why my two examples produce different results, you have to know that the i++ in the header happens to be executed after that copying has been made (an arbitrary choice?), while the i++ within the body will be (naturally) executed before the copying. I suppose the way it works can be intuitive, but it can also be confusing if you think of the i++ as the last action of each iteration in both of my example cases. Actually, there's a third way to write the example loop, but who can guess which result it gives? for (let i = 0; i++ < 10; ) { setTimeout(() => { console.log(i); }, 1000); } ... it will print 1...10! So even within the loop header, one part is run before the copying and another part after the copying. How is this intuitive and where is this documented apart from the language spec? EDIT: My bad, of course the iteration condition has to be checked at the beginning of the iteration, so in this third example i == 1 during all of the first iteration). Here's the relevant spec section 14.7.4.3 ForBodyEvaluation: https://tc39.es/ecma262/multipage/ecmascript-language-statements-and-declarations.html#sec-forbodyevaluation https://tc39.es/ecma262/multipage/ecmascript-language-statem... There we can finally see that on the first iteration, an environment is created (and iteration variables copied) before the test ("step 2"). After that, a new environment is created (and iteration variables copied) towards the end of each iteration, before the increment step ("step 3.e" and "step 3.f").
- tuukkah 4y agoAnother commenter links to a great video that explains the same thing about the spec and how confusing the environment creations can be: https://news.ycombinator.com/item?id=33160373 https://news.ycombinator.com/item?id=33160373