Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'setTimeOut' calls in JavaScript 'for' loops, why do they fail? [duplicate]

Let me clarify my question. I'm not asking how to make the following code work. I am aware that you can use the let keyword or an iffe that captures its own value of i. I just need clarification on how the value i is accessed in the following code. I read the following blog post about how it is that the following code does not work. Blog post

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

The writer claims that the code will not work, because we are passing the variable i as a reference instead of a value. That is, instead of providing the value of i per iteration we provide the variable to the callback in setTimeout as a reference which. In effect, when the loop terminates and callbacks fire, we will have reference to the variable i which will be 6. Is this how it works?

Here is my understanding. My understanding is that we are not "passing" anything to the callbacks of the setTimeout function, when the loop is executed. We are merely setting up the asynchronous calls. When the closure callback functions do execute, they then look for the variable i based on lexical scoping rules. That is, the closures look in the scope were the callbacks have closure over, which again, in this case would be 6 since it is done after the for loop completes.

Which one is it, does the function resolve the value of i to 6 based on the variable being passed as a reference on each iteration or because of lexical scoping?

like image 340
Hugo Perea Avatar asked Dec 31 '17 06:12

Hugo Perea


1 Answers

You are correct that lexical scoping is the cause of this behavior. When the timer functions run (which will be after the currently running code completes), they attempt to resolve i and they must look up the scope chain to find it. Because of lexical scoping, i exists only once in the scope chain (one scope higher than the timer functions) and, at that point, i is 6 because, at that point, the loop has terminated.

The var keyword causes variables in JavaScript to have either function or Global scope (based on where that declaration is). In your code, var i causes the i variable to exist Globally (because your code is not inside of a function) and each timer function must resolve that same, single i when they eventually run. Since the timer functions won't run until the loop is done, i is at its last value (6) that the loop caused it to be.

Change var i to let i to create block scope for i to solve the problem.

let creates block scope for the variable. Upon each iteration of the loop, you enter the loop block again and a separate scope is created for i that each timer function gets to itself.

for (let i = 1; i <= 5; i++) {
  setTimeout(function() { console.log(i); }, 1000*i);
}
like image 192
Scott Marcus Avatar answered Oct 03 '22 04:10

Scott Marcus