3

I'm reading You Don't Know JS book series.

In Ch.5 of Scope & Closure title of this book, there's this for loop i'm unable to understand

for (var i=1; i<=5; i++) {
setTimeout( function timer(){
    console.log( i );
}, i*1000 );}

It prints 6 in console 5 time after 1s interval. Even author is trying to explain why it's happening but i'm unable to get what he's trying to say.

You can find this code with explanation here: https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch5.md

Head to 'Loop + Closure' section on the page you'll find this code.

Can anyone please explain me this in simple language?? Why it print 6 in console for 5 time instead of 1, 2,..., 5 after 1sec interval.

Thanks in Advance for your time & effort.

K D
  • 5,889
  • 1
  • 23
  • 35
Kishan Patel
  • 509
  • 7
  • 16
  • See answers to this question: http://stackoverflow.com/q/750486/165674 – Dheeraj Vepakomma Jan 18 '17 at 10:29
  • 1
    by the time your timeout has fired the function (as it is delayed), the loop will have finished so the value of `i` will be the last value (which is 6) – Pete Jan 18 '17 at 10:30
  • When first setTimeout starts, loop has ended, and value of i gets from closing, at the time of the call is equal to 6. If you want save current value of i use this code for bind() `for (var i=1; i<=5; i++) { setTimeout( function timer(i){ console.log( i ); }.bind(null, i), i*1000 );}` or this code: `for (var i=1; i<=5; i++) { (function(i){setTimeout( function timer(){ console.log( i ); }, i*1000 );}})(i)` Read about closing in javascript – Smiranin Jan 18 '17 at 10:34

6 Answers6

4

Yeah because the loop runs through within the second and at that time the value of i is 6(with the last i++ which doesn't enter in the loop). hence it uses that value to render 5 times.

you can use following to print 1,2,3,4,5

for (let  i=1; i<=5; i++) {
setTimeout( function timer(){
    console.log( i );
}, i*1000 );}

with let you define scope of the i which is part of ES6

K D
  • 5,889
  • 1
  • 23
  • 35
  • So you are trying to say that the timer function will execute after loop finished running for 5 time?? And if yes then how it'll print 6 for 5 times? – Kishan Patel Jan 18 '17 at 10:31
  • 1
    as we mentioned i <= 5, after **i** reaches to "5", it make one more attempt to loop. => increament the value of i using i++ => check if i < = 5 and as the value of i is "6" it doesn't enters the loop. hence the value is "6" on last attempt. I hope it answers you question and also give you a solution how to get desired output – K D Jan 18 '17 at 10:33
  • if you google. "for loop how it works" it will give you the same answer as i mentioned. it is irrespective of language/script you are using. you will get same behavior in JS, C#, VB ... etc\ – K D Jan 18 '17 at 10:37
  • did you got answer for your confusion or you need more information? Please let us know – K D Jan 18 '17 at 10:50
  • Can you explain your code please?? I know that with the help of 'let' we can achieve block scoping.But my question here is how it make difference here? – Kishan Patel Jan 18 '17 at 11:05
  • i hope this post will add some light on **let** of ES6 http://stackoverflow.com/questions/762011/whats-the-difference-between-using-let-and-var-to-declare-a-variable – K D Jan 18 '17 at 11:07
  • Because of let, it simply scopes its value which is being passed further and uses accordingly. In short with the help of *let* it create separate variable for every iteration and use it in limited scope. – K D Jan 18 '17 at 11:08
2

This situation takes place because there is another scope in SetTimeoutFunction.

You can see your 1 2 3 4 5 if you use ES6 let.

for (let i=1; i<=5; i++) {
  setTimeout( function timer(){
    console.log( i );
  }, i*1000 );
}

Here you create special variable with let, which can be used inside SetTimeout scope.

clinton3141
  • 4,751
  • 3
  • 33
  • 46
Grynets
  • 2,477
  • 1
  • 17
  • 41
1

The Simple logic exists here. First time it enters in to loop and call timer function. But timer function will wait for 1 second. in this 1 sec the for loop will execute all 6 times and finally the value in i = 6.

and timer function also executes 6 times with in a second.

The value in i = 6 before timer executes first time only. similarly this i has the value 6 next 5 times also.

If you want you just change your code as follows

  for (var i=1; i<=11; i++) {
    setTimeout( function timer(){
    console.log( i );
   }, i*1000 );}

Then it prints 11 in 10 times

Vasu
  • 35
  • 8
  • Yes you can use "let" to print 1,2,3,4,5. With this we can understand that every time the scope of the i is different with in the timer function when we use 'let' – Vasu Jan 18 '17 at 10:48
1

Let me explain step by step :

for (let  i=1; i<=5; i++) {
setTimeout( function timer(){
    console.log( i );
}, i*1000 );}

1st iteration:

i is 1, 1st timer is set for 1*1000 = 1 sec

2nd iteration:

i is 2, 2nd timer is set for 2*1000 = 2 sec

3rd iteration:

i is 3, 3rd timer is set for 3*1000 = 3 sec

4th iteration:

i is 4, 4th timer is set for 4*1000 = 4 sec

5th iteration:

i is 5, 5th timer is set for 5*1000 = 5 sec

When i becomes 6, it exits the loop, so now we have 5 timers with times of 1,2,3,4 and 5 seconds and the current value of i is 6. All the timers are set but none has executed since 1 second has still not passed.

So after 1 second, the 1st timer finishes and 6 is printed After 2 seconds, the 2nd timer finishes and 6 is printed and so on....

hence you get an output : 66666 with an interval of 1 second each but the actual timer values are 1,2,3,4 and 5 seconds

nikhil.g777
  • 882
  • 3
  • 12
  • 24
0

The loop runs right after you started it and increases the value of i to 6. The callback function in setTimeout runs 1 to 5 secs after that and prints the actual value of i, which is always 6, since the loop set it to 6 in the 0th second.

inf3rno
  • 24,976
  • 11
  • 115
  • 197
0

Because i is defined outside the context of the function executed by setTimeout, when the timeout is reached, the value of i is 6, thus all the timeouts will execute the function and print the current value of i.

As you said, the title of the chapter is Closures, you can solve this problem using closures.

for (var i=1; i<=5; i++) {

 //This is a IIFE (Immediately-invoked function expression)
 //Simply put, a function that will be executed right after its definition
 (function(curr) {
   setTimeout( function timer(){
     console.log( curr );
   }, i*1000 );}
 })(i)

}

In the example above the curr variable is enclosed in that context, and will hold the value used when the function was called.

Tiago Engel
  • 3,533
  • 1
  • 17
  • 22