3 ms·
On the closure and event loop one, I suspect some people might wonder if you can change the output by adjusting the timing; if an iteration took over 100ms, wou
by chrismorgan 5y ago
On the closure and event loop one, I suspect some people might wonder if you can change the output by adjusting the timing; if an iteration took over 100ms, would it then emit different values? Well, you can try it:
for (var i = 0; i < 3; i++) {
const end = Date.now() + 100;
while (Date.now() < end);
setTimeout(() => {
console.log(i);
}, 100);
}
The answer is no, it will still log three thrice, because of how the event loop works: execution is single-threaded, which means that anything you queue up, whether microtasks like promises or tasks like timeouts, has to wait until you finish what you’re executing. There’s no preemption.
- sakex 5y agoInterestingly for that one, swapping the var declaration in the loop by a let will print 1, 2, 3.
- molf 5y agoThis is a major reason why `let` was introduced. Variable declarations with `var` should be avoided, linters commonly reject such code.
- afdbcreid 5y agoThis is one of the very purposes of `let`, scoped variables in `for` loops.
- andai 5y agoCan someone explain this? Is each iteration of the loop body considered a separate block scope? Edit: Each time the loop is run, a new anonymous function is created. But in the "let" version, each of those functions is bound to a different version of "i", which means "i" is actually being re-declared as a new and separate variable every time the loop body runs?
- woojoo666 5y ago`var` is function scoped, `let` is block scoped.
- masklinn 5y agoIt's not just that `let` is block-scoped, it's that a for-let loop scopes the variable inside the loop iteration, so each iteration has a completely different binding. You can have block scoping and your loops be scoped the other way around still: r := []func() int{} for i := range [5]int{} { r = append(r, func() int { return i }) } fmt.Println(r[0]()) // 4 r = r[:0] for i := 0; i < 5; i++ { r = append(r, func() int { return i }) } fmt.Println(r[0]()) // 5 You can even have different loop types be scoped differently e.g. in C#, a C-style for loop will update the binding in-place but a foreach loop will not: var r = new List<Func<int>>(); for(int i=0; i<5; ++i) { r.Add(() => i); } Console.WriteLine(r[0]()); // 5 r.Clear(); foreach(int i in Enumerable.Range(0, 5)) { r.Add(() => i); } Console.WriteLine(r[0]()); // 0
- xg15 5y agoRight, I forgot that it was triggered by var vs let. > so each iteration has a completely different binding. That was what confused me about this rule as well: If you write "for (let i = 0; i<n; i++)", this will actually create n+1 independent variables, all named "i" but bound to different scopes. The variables can even be independently modified: You can write for (var i=0; i<3; i++) setInterval(() => {console.log(i); i+=100}, 1000); This will print the "expected" output: 3 103 203 303 403 ... i.e. all the closures bound to the same variable. The same output is produced by let i; for (i=0; i<3; i++) setInterval(() => {console.log(i); i+=100}, 1000); However, if you write for (let i=0; i<3; i++) setInterval(() => {console.log(i); i+=100}, 1000); you get: 0 1 2 100 101 102 200 201 202 ... i.e. each closure logs - and increments(!) - its own copy of i. Neither "for" nor "let" on their own have this behaviour, it's a special language rule which seems to be triggered by using "let" in the first clause of a "for" statement. I can understand the intention behind the design, but I feel that, in order to make a common usecase less surprising, they made the whole thing actually more surprising.