Congratulations on making it to here! Let's face it, JavaScript has got some weird (and some bad) sides. Closures are on the weird side of JavaScript. Let's see what the term closure actually means.
When you declare a local variable, that variable has a restricted scope, that is, it cannot be used outside that particular scope within which it is declared (depends on var and let). As discussed earlier, local variables are not available outside the block (as in the case of let) or function scope (as in the case of var or let).
Let's take a look at the following example to understand what the preceding paragraph states:
function() {
var a = 1;
console.log(a); // 1
}
console.log(a); // Error
When a function is fully executed, that is, has returned its value, its local variables are no longer required and cleaned from memory. However, a closure is a persistent local variable scope.
Consider the following example:
function counter () {
var count = 0;
return function () {
count += 1;
return count;
}
}
Clearly, the returned function makes use of the local variable to the counter() function. What happens when you call counter?
let myCounter = counter(); // returns a function (with count = 1)
myCounter(); // now returns 2
myCounter(); // now returns 3
Look carefully, we are not executing counter() again and again. We stored the returned value of the counter in the myCounter variable and then kept calling the returned function.
The returned myCounter function will count up by one each time it's called. When you call myCounter(), you are executing a function that contains a reference to a variable (count), which exists in a parent function and technically should've been destroyed after its complete execution. However, JavaScript preserves used variables inside a returned function in a kind of different stack. This property is called a closure.
Closures have been around for a long time, so what's different? Using it with the let keyword. Have a look at this one:
for(var i=0;i<5;i++){
setTimeout(function() {
console.log(i);
}, 1000);
}
The output will be:
5 5 5 5 5
Why? Because till the time setTimeout fires, the loop has already ended and the i variable was already 5. But this does not happen with let:
for(let i=0;i<5;i++){
setTimeout(function() {
console.log(i);
}, 1000);
}
Output:
0 1 2 3 4
The fact that let binds variables to the block (thus, in this case, the for loop) means that it binds the variable to every iteration. So, when the loop is finished, you have five setTimeout functions (with i = 0, 1, 2, 3, 4) waiting to fire one after another.
let achieves this by creating a closure of its own in every iteration. This happens behind the scenes with let, so you do not need to code that aspect.
To fix this code without let, we'll need to create an Immediately Invoked Function Expression (IIFE), which looks something like this:
for(var i=0;i<5;i++){
(function(arg) {
setTimeout(function() {
console.log(arg);
}, 1000);
}(i));
}
This is more or less what let does behind the scenes. So what happened here?
We created an anonymous function that is immediately invoked at every loop cycle with the correct i value associated with it. Now, this function has the correct i value passed as arg in the function argument. Finally, we use console.log after a second to get the correct output as 0 1 2 3 4.
So you can observe, a simple let statement can simplify the code a lot in such cases.