简体   繁体   中英

Not able to understand the output of this for loop in JS

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 var keyword are scoped to the immediate function body (hence the function scope) while let variables 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` }

In case of var you are always referring to the same variable address, because var is function scoped.

let is block scoped. For every callback in setTimeout you have a different value of i , since it is a different block.

Read

This is too lengthy but I make sure that you get it,

In the first for loop , variable is declared using 'var' keyword. Variables declared using 'var' keyword are function scoped. As our first 'for' loop is not enclosed within any function, the variable is global , and the value of this global variable was incremented at each iteration of the first for loop. At the end of the first for loop, the variable 'i' will have the value of 3 as the value is overwritten at each iteration of the first for loop. So, at the time of execution of the first callback function from the 'callback queue' , the value of 'i' will be 3.

So, the first callback function will print numeric value 3 at console. The second and third callback function will also print the same numeric value 3 as it points the same variable.

But in the case of second for loop, we've used 'let' keyword to declare the variable. Variables declared using 'let' keyword are block scoped . A block is nothing but a pair of curly braces. So, in second for loop, a new value is used for each iteration. So, the last 3 callback functions will have the numeric values 0, 1, 2 respectively for the variable 'i' and hence would print 0, 1 and 2.

We can make the callback function to use the new variable value every time using 'var' keyword itself. As variables in javascript are function scoped (unless you use 'let' or 'const' keyword), you can call a function by passing the iteration variable. This will create a new variable every time.

for(var i=0 ;i <3 ; i++){
     setTimeOut(()=>console.log(i),1);
}

Js engine will examine the first for loop in the following steps :

  1. i will be hoisted, to look like this :
    var i;
    for(i = 0 ;i < 3 ; i++){
       setTimeOut(()=>console.log(i),1);
    }
  1. After the loop get's over , it will run setTimeOut for 3 times which will print value of i which is now 3
{ 
   i = 3 
   setTimeOut(()=>console.log(i,1);
}

{ 
   i = 3
   setTimeOut(()=>console.log(i,1);
}

{ 
   i = 3
   setTimeOut(()=>console.log(i,1);
}
for(let i=0 ;i <3 ; i++){
     setTimeOut(()=>console.log(i),1);
}

Js engine will examine the second for loop like this :

  1. i will be hoisted, to look like this :
    for(let i=0 ;i < 3 ; i++){
       setTimeOut(()=>console.log(i),1);
    }

for the first iteration , the value of i would be 0 , and for the second iteration the value of i would be 1 and for the third it's value would be 2 .

Imagine , it as three different blocks of scope like this :

{ 
   i = 0 
   setTimeOut(()=>console.log(i,1);
}

{ 
   i = 1 
   setTimeOut(()=>console.log(i,1);
}

{ 
   i = 2
   setTimeOut(()=>console.log(i,1);
}

First loop

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1);
}

Second loop

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1);
}

Solution of first loop
Because when we declare the variable using var it will be either in global scope(when declare in global execution context) or in function scope(when declare in function execution context).So as we know in the first loop we declare the variable in the global execution context so it will have global scope. When setTimeout encounters in first loop it will give the setTimeout handler in the API's. But as we know js and time never waits for anything so js will go to next iterations and js engine will execute the for loop very fastly and when the 1s of setTimeout happens then it will push the handler functions in execution stack and now the value of i(global scope) is 3. Here the main thing is that the same i is bound to every function of setTimeout because of the concept of closures(if beginner then to understand think that i is a global variable). So console.log(i) will print 3 in each handler function of setTimeout.

Solution of second loop
Till ES6 developers feels very difficult because there is no block scope in javascript like other languages. So in ES6, keyword let is introduced which introduces the concept of block scope. Means the scope of variables declared using let will be only in that block(variable enclosed in the first curly braces). In some cases and generally in loops it makes more sense that variable has block scope.
Now , the variable i declared in second loop will have block scope of for loop. It will be available only in the one round of for loop. Now, the i is bound to every iteration of for loop. So, the new iteration will have seperate set of i here.
So, in second loop there will be 3 i which will bind to every iteration of for loop. Now, when first iteration of for loop executes the value of i is 0 and it is bound to this iteration and setTimeout statement runs it is stored in background by javascript and will execute the function when 1s will be completed. But js waits for nobody. So next iterations of for loop will occur and in second iteration i value will be 1 bound to this iteration and so on.
When 1s happens the function of setTimeout will pushed to execution stack and each function have its i because for every iteration of for loop it has its own i which is bound to it(Here the concept of closures also come into picture).
So now every function execute of setTimeout and it will print 0,1 and 2.

Some more info
As told upward that i is bound to every iteration. So, when one iteration completed it will destroy. Then in next iteration how the for loop increemented 1 to the previous i. So, here the javascript actually uses var in background to maintain the for loop.
Any doubt or suggestion, make a comment

Because of the event queue in JavaScript, the setTimeout callback function is called after the loop has been executed. Since the variable i in the first loop was declared using the var keyword, this value was global. During the loop, we incremented the value of i by 1 each time, using the unary operator ++. By the time the setTimeout callback function was invoked, i was equal to 3 in the first example.

In the second loop, the variable i was declared using the let keyword: variables declared with the let (and const) keyword are block-scoped (a block is anything between { }). During each iteration, i will have a new value, and each value is scoped inside the loop.

简单地说, setTimeout 导致日志稍后运行,并且使用var有一个i实例,而使用let每个循环都有一个新实例。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM