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