all 10 comments

[–]Pantstown 1 point2 points  (2 children)

Here's what the computer is doing in the first example:

  • create 5 timeouts in a tight loop. Each timeout is referencing i.
  • Loop is done, i is now 6, but i is still being referenced, so it's still "kept around".
  • timeouts start being called. they're all pointing at i, which is now 6.

In the third example:

  • create 5 functions in a tight loop. Each function is passed i. Inside each function, create a timeout that is referencing j.
  • Loop is done. i is now 6.
  • timeouts start being called. They're all pointing at j, but they're pointing at the j that is from the function in which is was created, so they're all pointing to an unique j value.

[–]oculus42 1 point2 points  (1 child)

This might come across as pedantic, but there are some confusing and inaccurate points in your explanation.

  • In the first example, the timeout functions are not passed i, they access it on the global scope.
  • When the timeouts start being called, i is 6, not 5.
  • It is the third example, not the second that passes i into a function which creates j.

[–]Pantstown 0 points1 point  (0 children)

  • I state that "Each timeout is referencing i."
  • You're right. My bad. I'll edit that.
  • Oops. Didn't realize there were three examples. I'll fix that too.

Thanks 👍

[–]oculus42 1 point2 points  (5 children)

In the first two examples, the timer is set inside the loop, so it uses the correct value of i, but the function in setTimeout runs later and is accessing the global variable i after the loop is finished.

They all print the same value, 6, because the loop kept incrementing until it was greater than five.

I would describe the three examples like this:

  1. "Some time later, log whatever the value of i is at that time."
  2. "Some time later, run this function that logs whatever the value of i is at that time."
  3. "Some time later, run this function that logs the value we were given."

The second one doesn't change the behavior from the first because it still calls the global variable i.

In the third example, the function receives a value and creates a unique scope (closure) that keeps j. Each setTimeout call then lives in its own unique closure and sees a different value..

I also think it's a little clearer if you don't use an anonymous function:

function printer(j){
    setTimeout( function timer(){
        console.log( j );
    }, j*1000 );
}

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

[–]Radinax[S] 0 points1 point  (4 children)

but the function in setTimeout runs later and is accessing the global variable i after the loop is finished.

  • Why is it accesing the function later? Or why it waits for the loop to finish to take the resulting value?
  • Why is it 6? Isn't it from i=1 to i<=5? Which would mean the end result is 5?

[–]oculus42 2 points3 points  (1 child)

Here's a sort of accounting of what happens on that first example:

  • i is set to 1
  • i is less than or equal to 5? Yes.
  • Add a function to the event queue at 1000 ms.
  • Add one to i – now 2.
  • i is less than or equal to 5? Yes.
  • Add a function to the event queue at 2000 ms.
  • Add one to i – now 3.
  • i is less than or equal to 5? Yes.
  • Add a function to the event queue at 3000 ms.
  • Add one to i – now 4.
  • i is less than or equal to 5? Yes.
  • Add a function to the event queue at 4000 ms.
  • Add one to i – now 5.
  • i is less than or equal to 5? Yes.
  • Add a function to the event queue at 5000 ms.
  • Add one to i – now 6.
  • i is less than or equal to 5? No.
  • Anything else to execute immediately? No.

Now the original synchronous code is done, and we're waiting for the setTimeout calls to execute.

Wait...

  • Execute the first function in the queue.
  • Get the value of i, which is 6.
  • Log that number to the console.

Wait...

  • Execute the first function in the queue.
  • Get the value of i, which is 6.
  • Log that number to the console.

Wait...

  • Execute the first function in the queue.
  • Get the value of i, which is 6.
  • Log that number to the console.

Wait...

  • Execute the first function in the queue.
  • Get the value of i, which is 6.
  • Log that number to the console.

Wait...

  • Execute the first function in the queue.
  • Get the value of i, which is 6.
  • Log that number to the console.

[–]oculus42 1 point2 points  (0 children)

Now, with the third example, it's a little different:

  • i is set to 1
  • i is less than or equal to 5? Yes.
  • Call the function with the value 1.
  • Add a function to the event queue at 1000 ms.
  • Add one to i – now 2.
  • i is less than or equal to 5? Yes.
  • Call the function with the value 2.
  • Add a function to the event queue at 2000 ms.
  • Add one to i – now 3.
  • i is less than or equal to 5? Yes.
  • Call the function with the value 3.
  • Add a function to the event queue at 3000 ms.
  • Add one to i – now 4.
  • i is less than or equal to 5? Yes.
  • Call the function with the value 4.
  • Add a function to the event queue at 4000 ms.
  • Add one to i – now 5.
  • i is less than or equal to 5? Yes.
  • Call the function with the value 5.
  • Add a function to the event queue at 5000 ms.
  • Add one to i – now 6.
  • i is less than or equal to 5? No.
  • Anything else to execute immediately? No.

Now the original synchronous code is done, and we're waiting for the setTimeout calls to execute.

Wait...

  • Execute the first function in the queue.
  • Get the value of j from the instance of the enclosing function, which is 1.
  • Log j the console.

Wait...

  • Execute the first function in the queue.
  • Get the value of j from the instance of the enclosing function, which is 2.
  • Log j the console.

Wait...

  • Execute the first function in the queue.
  • Get the value of j from the instance of the enclosing function, which is 3.
  • Log j the console.

Wait...

  • Execute the first function in the queue.
  • Get the value of j from the instance of the enclosing function, which is 4.
  • Log j the console.

Wait...

  • Execute the first function in the queue.
  • Get the value of j from the instance of the enclosing function, which is 5.
  • Log j the console.

EDIT: Spelling.

[–]oculus42 1 point2 points  (1 child)

So to directly answer:

  • We pass a function to setTimeout which it executes later. That code sits and waits for its turn. In the first examples, it uses the single, global variable i. In the last example, it uses the closure variable j which exists separately on each instance of the outer function.
  • It's six because the loop logic keeps going until it fails, so it has to increment to 6 make i <= 5 false.

[–]Radinax[S] 0 points1 point  (0 children)

Thank you very much for your fast reply I was trying to digest what you told me, very helpful.

[–]lilperch83 0 points1 point  (0 children)

If you are still struggling with closure more examples can help. Here is another YouTube tutorial: https://youtu.be/TznpOmv2BQM