I will try to be very detailed because in comment was said by the AP that did not understand the initial explanation. And I’m going to explain why it actually happens, and I’m not going to say something that doesn’t actually show why this is happening because it would be something like, "It’s like that and done," which is what the AP concludes, One of the reasons the question is being asked is that the book used that proposes to explain JS in obscure points did not make clear why this happened. I tried to make it as clear as possible, if it did not serve the AP at least it can serve other people. Then looking for links I realized that has even been answered.
It works precisely because it keeps a reference. Do you know what a reference is? If you don’t understand this you won’t understand the explanation. I speak in reference because that is what the question said. I could speak of scope, which is something collateral to the subject if the question spoke in scope and was more important than understanding the reference.
One reference is a value addressing a location where it has another value, so in the general context of programming (may be different in specific contexts) it is always a memory address where it has a value. Is a indirect.
The first thing you need to understand about closures is that they are anonymous functions that are not executed until invoked, what you see there is a definition of it, not its execution, is not called, the execution will take place internally within the setTimeout()
at the right time. Forget the idea that the function is running there, you’re not seeing its execution, it’s hidden from you.
Another important point is that the setTimeout()
has nothing to do with the question, he’s just using a mechanism of callback, something common in anonymous functions that have closures or not.
So this code is creating 4 closures different, one in each loop passage, all 4 have a reference to the same address as variable index
(if you don’t understand what a variable is it is important to read this link), note that you do not have the value of index
at the time of the creation of this closure, in a reference, are different things. We can conclude that the 4 values that will enter into these closures are the same, that is, the 4 will be the address of the variable index
, after all the address where the variable is index
is not changing in every passage through the loop.
After the tie ends, and it ends as the condition (the AP showed not understand why it ends), ie, index
is not less than or equal to 3, therefore it is worth 4 (this is basic condition, it only ends when the condition becomes false, and to become false it can no longer have the value that made it true, so it can neither value a value less than 3 nor the value 3, this only happens when it becomes 4, if you do not understand this make a table test, that exists precisely to understand the functioning of the code), at some point forward (1 second forward in the example, note that all will execute almost encavalados because all take the same time) these events configured in setTimeout()
through closures will be executed with the value that is in the reference used, that is to use the value that is in index
, at this time, which will already be (1 second) after you have already closed the loop, so it has the value 4 in all executions.
Who knows this will help understand at least the condition:
for (var index = 0; index <= 3; index++) {
console.log(index); //varia de 0 até 3 porque esses valores dão condição verdadeira
console.log(index <= 3);
setTimeout(function() {
console.log(index)
}, 1000);
}
console.log("fora do for");
console.log(index); //houve 4 incrementos chegando neste número
//quando começa em 0 e vai incrementando a única forma de dar falso aqui é chegando no 4
console.log(index <= 3);
console.log("1 segundo depois vai executar o abaixo");
//note que não tem código aqui, é o setTimeout() que vai chamar a função com closure
Don’t forget to have the codes executed to see the results.
There are some techniques to solve this. One of them is to encapsulate in a function, this works in any version of JS:
for (var index = 0; index <= 3; index++) {
function timer(i) {
setTimeout(function() {
console.log(i)
}, 1000)
};
timer(index);
}
Another technique is to use the let
(quoted in the question) which causes the variable to cease to be a natural reference because the variable becomes local to the block (don’t forget to read this link explaining the operation of let
, it is important, there I speak of scope), then the reference happens to be created the moment it is being captured by closure, then each closure will have a reference to a "new variable" that is created only for this, this variable does not exist in your code, it is internal to closure and the value of it will be what is valid at the time of the creation of closure.
Note that being local you cannot demonstrate so easily when the variable is false because it no longer exists outside the scope (the for
). The scope is solution, not the explanation of why it gives that problem cited in the question. AP did not understand this.
for (let index = 0; index <= 3; index++) {
setTimeout(function() {
console.log(index)
}, 1000);
}
I put in the Github for future reference.
This technique was not possible a few years ago. One of the reasons let
to have been created is precisely to solve this question and to create their own references, they could not change the semantics of language into something that already existed, they created something new, so the new semantics would be opt-in.
This is explained by something called Binding which is how the variable binds its value. The var
does so in a universal way its existence and the let
makes it local to context.
It is only necessary to be careful with the original because the answers there do not consider the evolution of the JS.
– Maniero