const - JS | lectureA Closer Look At Functions

Closure Examples

A Closer Look At Functions

Let's now create two more situations in which closures will appear so that you can start identifying closures in your code in the future. Both of these examples will demonstrate that we don't need to return a function from another function to create a closure.

Let's start with our first example.

script.js
let func;

const init = function () {
  const num = 10;
  func = function () {
    console.log(num * 2);
  };
};

Let's now execute both functions.

script.js
init();
func();

Closure Example 1

We indeed get twenty which is the result of 10 * 2. This proves that the function func really does close-over any variables of the execution context in which it was defined. And that is true even when the variable itself (func) was technically not defined inside the variable environment of the function init.

Wwhat I mean is, the func variable was defined in the global scope,but then as we assigned it a function in the init function, it then closed-over the variable environment of the init function. And that includes the variable num. Therefore, it is able to access the variable num and print out the result; even after the init function has finished executing. That's just what we learned in the previous lecture.

So when we executed the funcfunction after the init function, the variable environment of init is no longer there. But func closed-over the variable environment and therefore it is able to access the variable. Using the analogy of our previous lecture, the numvariable is inside the backpack of the func function.

Let's take it to the next level, and create a new function.

script.js
const reset = function () {
  const def = 100;
  func = function () {
    console.log(def * 2);
  };
};

Let's execute the function.

script.js
init();
func();

reset();
func();

What I want us to see here is what happens when we assign the func variable a second function.

Closure Example 1.1

We can see that we get 200, which is exactly 100 * 2. This proves that the function func that we reassigned in the reset function also closed-over its vraiable environment. Hence, that's how it can then access the variable def which is set to 100.

Let's now inspect the variable environment like we did in the previous lecture.

script.js
console.dir(func);

Closure Inspect

We can see in the above image that the closure has the value of def. It no longer has num as a property. Which is the closure that it had before. We can see that by inspecting the variable environment of the func function, beofore executing reset.

script.js
console.log('Before reset');
//...
console.dir(func);

console.log('After reset');
//...
console.dir(func);

Closure Inspect - 1

You can now clearly see that that before we executed reset, the closure had the value of num. Then when we reassigned the func variable to a new function, the old closure disappered and now the closure is def.

It's actually really fascinating that a closure can change like this as the variable is reassigned. So it's really true that a closure always makes sure that a function does not lose the connection to the variablles that were present at its birthplace. It's always going to remember them. In our case, the function func was kind of born inside the function init first, and then it was essentially reborn again in the function reset.

Hence, first, the closure contained the num variable of its first birthplace, and then as it was reborn to follow our analogy; then it remembered the def variable.

I hope this first example was clear. If that's the case, we can move on to the next example. This example is going to be a timer. A timer is another great example where we don't need to return a function.

script.js
const boardPassengers = function (numberOfPassengers, waitTimeInSeconds) {
  const perGroup = numberOfPassengers / 3;

  // Our timer function
  setTimeout(function () {
    console.log(`We ara now boarding all ${perGroup} passengers`);
    console.log(`There are 3 groups, each with ${perGroup} passengers`);
  }, waitTimeInSeconds * 1000);

  console.log(`We will start boarding in ${waitTimeInSeconds} seconds`);
};

From the above code, you've noticed that we've used a setTimeout function that we haven't seen before. Don't worry; we will learn more about it later. But this is such a good use case that I wanted to use a timer now.

The setTimeout function takes two parameters: the callback function to be executed and the time in milliseconds. For example if we want to log Hello World after 5 seconds, we will write:

script.js
setTimeout(function () {
  console.log('Hello World');
}, 5000);

More about timers later. Let's now execute our boardPassengers function.

script.js
boardPassengers(180, 5);

So immediately when we call the function, the perGroup variable will be created, then setTimeout will be called, and it will basically register the callback function to be executed after 5 seconds. But immediately the last console log will be printed out. It won't wait 5 seconds for the callback function to be executed.

Now, after 5 seconds, the callback function will be executed. Let's see what happens to the numberOfPassengers variable and the perGroup variable.

Closure Timer Example

From the above result, we notice that everything worked as expected. Again, keep in mind that the callback function in the setTimeout function was executed completely independently of the function (boardPassengers) that called it. But still the callback function was able to use all the variables that were in the variable environment in which it was created. That is, the numberOfPassengers variable, and the perGroup variable.

This is one more time a clear sign of a closure being created. So the only way in which the callback function can have access to the variables that were defined in the boardPassengers function that has long finished it's execution is if it created a closure.

To finish, let's now prove that the closure has priority over the scope chain.

script.js
const boardPassengers = function (numberOfPassengers, waitTimeInSeconds) {
  const perGroup = numberOfPassengers / 3;
  //...
};

const perGroup = 360; // In the global scope

boardPassengers(180, 5);

If the scope chain has priority over the closure, then our callback function will use the perGroup variable that was defined in the global scope. Because we can imagine the callback function being executed in the global scope. So if it wasn't for the closure, the callback function would use the perGroup variable defined in the global scope. Let me actually demonstrate that.

script.js
const boardPassengers = function (numberOfPassengers, waitTimeInSeconds) {
  // const perGroup = numberOfPassengers / 3;
  //...
};

const perGroup = 360; // In the global scope

boardPassengers(180, 5);

Closure & Scope Chain

You can see that we now have 360 as the value of perGroup. But then if we uncomment the perGroup variable in the boardPassengers function, the callback will close-over the the entire variable environment of the boardPassengers function, and use the perGroup variable defined in it.

With these two lectures on closure, I hope you are now able to identify closures as they happen in your code or mine throughout the course because we will see some closures happening in upcoming lectures.