Closure Examples
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.
let func;
const init = function () {
const num = 10;
func = function () {
console.log(num * 2);
};
};
Let's now execute both functions.
init();
func();
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 func
function 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 num
variable is inside the backpack of the func
function.
Let's take it to the next level, and create a new function.
const reset = function () {
const def = 100;
func = function () {
console.log(def * 2);
};
};
Let's execute the function.
init();
func();
reset();
func();
What I want us to see here is what happens when we assign the func
variable a second function.
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.
console.dir(func);
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
.
console.log('Before reset');
//...
console.dir(func);
console.log('After reset');
//...
console.dir(func);
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.
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:
setTimeout(function () {
console.log('Hello World');
}, 5000);
More about timers later. Let's now execute our boardPassengers
function.
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.
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.
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.
const boardPassengers = function (numberOfPassengers, waitTimeInSeconds) {
// const perGroup = numberOfPassengers / 3;
//...
};
const perGroup = 360; // In the global scope
boardPassengers(180, 5);
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.