Can someone break down what's going on here clearly?
function timerCheck() {
for(var i=0; i<5; i++) {
setTimeout(function() {
console.log("Hello" + i);
}, 3000);
}
}
So as some of you may know, calling this function will not work as expected. What will end up happening is that this function will get called 5 times all at once with i set to 5 each time. This will be the output after 3 seconds:
Hello5
Hello5
Hello5
Hello5
Hello5
I also understand that using the setInterval method is the right way to approach this kind of problem, but I am curious what's going on under the hood here. I really want to understand how Javascript works. Please note that I do not have a computer science background, just a self-taught coder.
This might help you understand what's going on better:
function timerCheck() {
for(var i=0; i<5; i++) {
console.log("Hi" + i);
setTimeout(function() {
console.log("Hello" + i);
}, 3000);
console.log("Bye" + i);
}
}
You'll see
Hi0
Bye0
Hi1
Bye1
Hi2
Bye2
Hi3
Bye3
Hi4
Bye4
Immediately printed to the console, because all five iterations of the loop finish very quickly, and then after five seconds you'll see:
Hello5
Hello5
Hello5
Hello5
Hello5
because the timeouts (which were all set at approximately the same time) all occur at once, and since the loop already finished: i == 5
.
This is caused by the scope of i
. The variable i
has a scope of everywhere after it is declared in timerCheck();
There is no local i inside your anonymous function in setTimeout set there is no var i
, and i
isn't given as an argument to the function.
You can fix this easily with a closure, which will return a function that has a local copy of i:
function timerCheck() {
for(var i=0; i<5; i++) {
setTimeout((function(loc_i) {
return function() {
console.log("Hello" + loc_i);
};
})(i), 3000);
}
}
Which will output:
Hello0
Hello1
Hello2
Hello3
Hello4
To understand this:
(function(loc_i) {
return function() {
console.log("Hello" + loc_i);
};
})(i)
You have to know that a function can be executed immediately in Javascript. IE. (function(x){ console.log(x); })('Hi');
prints Hi
to the console. So the outer function above just takes in an argument (the current value of i
) and stores it into a local variable to that function called loc_i
. That function immediately returns a new function that prints "Hello" + loc_i
to the console. That is the function that is passed into the timeout.
I hope that all made sense, let me know if you're still unclear on something.
Variable scope in JavaScript is limited to functions.
In your example, the variable i
is declared inside of timerCheck
. This means that at the end of the loop, i
will be equal to 5
.
Now, adding in the call to setTimeout
doesn't change the fact that i
is scoped to timerCheck
and that i
has been modified to equal 5
by the time the code inside each setTimeout
call has run.
You can create a function that "captures" the value of i
so that when you call it from inside your loop you get new variable scope for the setTimeout
call:
function createTimer(j) {
setTimeout(function() {
console.log("Hello" + j);
}, 3000);
}
function timerCheck() {
for(var i=0; i<5; i++) {
createTimer(i);
}
}
Since createTimer
takes a parameter j
, when you pass i
from inside the for loop in timerCheck
to createTimer
, j
is now scoped to createTimer
so that each setTimeout
call has its own j
.
This is actually an add-on to Andrews answer
If you try to set a variable that sets the output it explains the scope also.
function test()
{
for(var i=0; i<5; i++) {
t = "Hello " + i + "<br/>";
document.write(t);
setTimeout(function() {
document.write(t);
}, 3000);
}
}
as you can see the writes will be as expected but at the time the setTimeout fires the t variable will be the last set. Which will be Hello 4.
so the output will be
Hello 0
Hello 1
Hello 2
Hello 3
Hello 4
from the loop and
Hello 4
Hello 4
Hello 4
Hello 4
Hello 4
from the setTimeout