I have understood why the output of this code should be 3 3 3.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
I am not able to understand, though, why the output of this code is 0 1 2.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
I want more clarity with the output of the second for loop.
There is a difference of the scope with let and var, which causes them to behave differently.
Here is a quote from other Stack Overflow answer about the differences between var and let.
The main difference is scoping rules. Variables declared by
varkeyword are scoped to the immediate function body (hence the function scope) whileletvariables are scoped to the immediate enclosing block denoted by{ }(hence the block scope).
So, in summary, var is referring to the same variable address. But, as let is blocked scope (according to the quote above), every callback in setTimeout() will make i have a different value then the previous one.
An experiment that is possible is to make let behave like var. To make let behave like var, you can use (and run) the code below.
To see how let is behaving like var, read ahead about var hoisting!
let i;
for (i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
As we moved let to the main scope, it behaves like var (as var is positioned at the top of the file with a value of undefined at runtime).
This makes the let variable behave like var, making the output of the for loop the same as if the let variable was var.
Thanks to Nick Vu for the experiment!
Here is a quick overview of var hoisting below.
The actual code (shown below):
web = "stackoverflow.com";
var web;
Is understood as:
var web;
web = "stackoverflow.com";
Basically, defining a variable with var anywhere will always result it being at the top of the file, resulting in the weird behaviour with the for loop.
This is why many people prefer let over var; because of how confusing var hoisting can be!
Always use let, unless var is absolutely needed!
The first loop var i is hoisted and always referred to only 1 variable during that loop, setTimeout will postpone your console.log with call stack, so that's why the value is 3 all. Here is how var i is constructed with hoisting under the hood.
var i; //hoisted
//`var` in `for` gets removed
for (i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
But let i is different, i value will be initialized under that block scope in iteration each time, so that's why the result is 0 1 2.
For an experiment, to make let has a similar result as the var loop.
let i; //move `i` to the upper block scope
for (i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1); //all logs will share the same `i`
}