3 ms·
MDN goes into great detail, but the important point is that JavaScript fixed this with let and const. > a = []; for (i = 0; i < 3; i++) a.push(() => i); a.
by anderskaseorg 3y ago
MDN goes into great detail, but the important point is that JavaScript fixed this with let and const.
> a = []; for (i = 0; i < 3; i++) a.push(() => i); a.map(f => f())
[ 3, 3, 3 ]
> a = []; for (let i = 0; i < 3; i++) a.push(() => i); a.map(f => f())
[ 0, 1, 2 ]
> a = []; for (i of "abc") a.push(() => i); a.map(f => f())
[ 'c', 'c', 'c' ]
> a = []; for (const i of "abc") a.push(() => i); a.map(f => f())
[ 'a', 'b', 'c' ]
- zeroimpl 3y agoIn contrast, Java would have a compile error so long as the variable was not declared final.
- ben0x539 3y agoBut isn't it completely wild that the `for (let i =`... version works like that? What does the loop 'desugar' into if written as a while-loop?
- anderskaseorg 3y agoBefore let/const, scopes could only be introduced at function level, so @babel/plugin-transform-block-scoping transpiles it using an extra function: var _loop = function (i) { a.push(() => i); }; for (var i = 0; i < 3; i++) { _loop(i); } The key is that the scoping happens for each iteration, not around the entire loop. That detail is nonobvious, given how many other languages have gotten it wrong, but I wouldn’t say it’s wild. (If you’re curious how Babel deals with the more complicated cases of break/continue, labelled break/continue, and return, try it out at https://babeljs.io/repl https://babeljs.io/repl.)
- ben0x539 3y agoRight, the wild thing for me is when you mutate `i` in the loop body. So at the same time `i` is scoped to the iteration so that you can capture it, but also mutating it affects the loop-scoped `i` that is incremented between iterations and checked for the termination condition. The iteration-scoped `i` is assigned back to the loop-scoped `i` at the end of the loop body. So if you have a closure close over `i` in the loop body and mutate it, whether that mutation affects the actual loop variable depends on whether the closure is called during the iteration it was created in or during a later iteration. Kinda spooky, but sure, less of a footgun than the original behavior.