Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What really happens to function expressions under the hood?

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?

like image 760
spartan117 Avatar asked Jan 26 '23 21:01

spartan117


2 Answers

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.

like image 133
Nicholas Tower Avatar answered Jan 28 '23 09:01

Nicholas Tower


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
like image 27
Scott Sauyet Avatar answered Jan 28 '23 11:01

Scott Sauyet