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?
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With