I was going through the basics of javascript on freecodecamp just to refresh my memory and when I got to ES6 and the explanation of the differences between var and let, one of the examples gave me (and my colleagues) a headache.
'use strict';
let printNumTwo;
for (let i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo());
// returns 2
console.log(i);
// returns "i is not defined"
I was expecting the printNumTwo
function to return undefined
, thinking that by the time it was called the variable i
did not exist. One of my colleagues said that when the function expression was assigned to the variable, the i
got a value of 2
so when you call the function it will always return 2
.
To test this theory, we modified the original example to this:
'use strict';
let printNumTwo;
for (let i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
i++;
}
}
console.log(printNumTwo());
// returns 3
console.log(i);
// returns "i is not defined"
To everyone's surprise calling the function after the for loop returns 3
instead of 2
or the originally expected undefined
.
Can anyone please shed some light on why is this behavior? What really happens when you assign a function expression to a variable or when you call such one?
You are making and using closures. A closure is a function, plus the environment in which it was declared. When you write this line of code:
printNumTwo = function() {
return i;
};
That function has a reference to the i variable. For as long as this function exists, that variable will not be garbage collected and can continue to be referenced by this function. It's not saving a snapshot of what the value was, but saving a reference to the actual variable. If that variable changes, as in your second example, then the reference sees that modified value.
I don't know if an ASCII visualization will help. This is how I think about it. Note that I extended the loop to (i < 5)
; that extra iteration might clarify things.
+-------------+
| printNumTwo | --------------------------
+------+------+ Loop starts
| for (let i = 0; i < 5; i++)
| --------------------------
| +-------------+ \
| | | |
| | i = 0 | |-- discarded
| | | |
| +-------------+ /
|
| +-------------+ \
| | i++ | |
| | // i = 1 | |-- discarded
| | | |
| +-------------+ /
|
| +-------------+ \
| | i++ | |
+-------> | // i = 2 | |-- kept since `printNumTwo`
| printNumTwo | | still has a reference
| i++ | |
+-------------+ /
+-------------+ \
| i++ | |
| // i = 4 | |-- discarded
| | |
+-------------+ /
--------------------------
i++
i < 5: false Loop ends
`i` now out of scope
--------------------------
> printNumTwo() //=> 3
> i // not defined
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