There is an almost mystical feature
of Java script functions
that many developers fail to fully understand.
And what I'm talking about is something called closures.
So when I asked my students,
what's the hardest JavaScript concept to understand,
then many people say that it's closures.
However, I believe that with the right explanation,
it's actually not that hard,
especially when you already understood everything
that you learned before in this course,
such as execution context,
the call stack, and the sculpt chain,
because closures kind of bring all of these concepts
together in a beautiful, almost magical way.
So enough talk, let's see what closures are all about.
So I'm gonna start by creating a new function here
called secure booking.
And it is this function that will create the closure.
Now, the first thing that I need to tell you about closures
is that a closure is not a feature that we explicitly use.
So we don't create closures manually,
like we create a new array or a new function.
So a closure simply happens automatically
in certain situations, we just need
to recognize those situations.
And so that's what we're gonna do here in this example.
So we will create one of those situations
so that we can then take a look at a closure.
So anyway, let's now continue writing this example
and I'm calling this one passengerCount
and it will start at zero, but we will be
able to manipulate it.
And I'm calling this function here,
secure booking because this passengerCount variable
cannot be manipulated and accessed from the outside.
So, and now what's special about this function
is that it will return a new function.
And what we do in this function is to update
the passengerCount variable.
So the variable that is defined in the parent function.
So that's important.
And then let's just log the new passengerCount
to the console.
So passengerCount, alright,
and now let's call the secure booking function
and then store the result in a variable called Booker.
And so this is actually pretty similar to what we did
previously in the lecture of functions
returning other functions.
So we have one function here that we call
and this function will return this new function.
And so as we call secure booking,
it will return exactly this function
and it will then be stored inside this Booker.
And so this here is gonna be now a function as well, right?
So let's analyze in detail
what happens when this line of code here
is executed using all the concepts
that we already know about.
So this is exactly the code that we just wrote.
Now, before we start running
the secure booking function down here,
our code is running in the global execution context.
And in there, we currently only have
this secure booking function.
And so we can also say that the global scope
now contains secure booking.
Then when secure booking is actually executed,
a new execution context is put
on top of the execution stack.
Now, remember, each execution context
has a variable environment,
which contains all its local variables.
In this case, it only contains the passengerCount
set to zero.
This variable environment is also
the scope of this function.
And so the scope chain of this execution context
looks like this.
So passengerCount is in the local scope,
but of course this scope also gets access
to all variables of the parent's scopes.
And in this case, just a global scope.
Anyway, in the next line of the secure booking function,
a new function is returned and it will be stored
in the Booker variable.
So the global context now also contains the Booker variable.
And now what else happens when
the secure booking function returns?
Well, that's right.
Its execution context pops off the stack and disappears.
So the secure booking function has done its job
and has now finished execution.
It really is gone now and that's important to be aware of
and to keep in mind.
And for now, that's actually all we did.
So all this is nothing new at this point, right?
All we did was to analyze the call stack
and the scope chain as we call the secure booking function.
And this is gonna be important to later on
understand the closure.
So as of yet, we didn't see the closure yet.
All we did was use the knowledge that we already have
to understand how this Booker function was created,
because that's gonna be important for the next step.
So let's now go back to our code
to actually use the Booker function,
and then finally see the closure in action.
So now that we understand how the Booker function
was created, let's now actually call it here.
So calling it a couple of times here, and as we can see,
it doesn't need any arguments.
There's no list of parameters, right?
So let's call it here three times
and now let's reload the page here.
And indeed we get one, two, three passengers.
And so what this means is that the Booker function
was in fact able to increment the passengerCount
to one, then to two and then to three.
But now if we think about this,
then how is this even possible?
How can the Booker function update
this passengerCount variable that's defined
in a secure booking function
that actually has already finished executing.
And so, as I just said,
this function has already finished its execution.
It is gone.
So its execution context is no longer on the stack,
as we just saw in the slide,
but still this inner function here,
which is the Booker function,
is still able to access the passengerCount variable
that's inside of the Booker function
that should no longer exist.
And maybe you can guess that what makes this possible
is a closure, but before I explain
exactly how the closure works,
I want you to appreciate once more,
how strange this actually is.
So again, this Booker function here
is simply a function that exists
out here in the global environment
or in the global scope, right?
And the environment in which the function was created.
So this year basically, this environment
is no longer active.
It is in fact gone.
But still the Booker function somehow continues
to have access to the variables
that were present at the time that the function was created.
And in particular, this passengerCount variable here.
And so that's exactly what the closure does.
So we can say that a closure makes a function
remember all the variables that existed
at the function's birthplace essentially, right?
So we can imagine the secure booking
as being the birthplace of this function.
So of the Booker function, essentially.
And so this function remembers everything at its birthplace,
by the time it was created.
And this cannot simply be explained
with the scope chain alone.
So we need to also understand the closure.
And so let me now, really explain how it actually works.
So this is how we left the call stack
and the sculpt chain after the last slide.
And the most important thing to notice here
is that the execution context of secure booking
is no longer on call stack,
because again, this function has finished
execution long ago.
So now it's time to finally run the Booker function
and see exactly what's gonna happen here.
And note that Booker is really this function here,
located in the global scope.
Anyway, the first thing that's gonna happen
is that a new execution context is created
and put on top of the call stack
and the variable environment of this context is emptied
simply because there are no variables
declared in this function.
Now what about the scope chain?
Well, since Booker is in the global context,
it's simply a child's scope of the global scope,
just like this, but maybe now
you're starting to see the problem.
So how will the Booker function access
the passengerCount variable?
It's nowhere to be found in the scope chain, right?
So this is where we start to unveil
the secret of the closure
and the secret is basically this.
Any function always has access to the variable environment
of the execution context in which the function was created.
Now, in the case of Booker, this function was created.
It was born in the execution context of secure booking,
which was popped off the stack previously, remember?
So, therefore the Booker function
will get access to this variable environment,
which contains the passengerCount variable.
And this is how the function will be able to read
and manipulate the passengerCount variable.
And so it's this connection that we call closure.
So let's say all that again, to make this really clear.
So a function always has access to the variable environment
of the execution context in which it was created,
even after a debt execution context is gone.
And this last part is really important.
The closure is then basically this variable environment
attached to the function,
exactly as it was at the time and place
that the function was created.
And this probably still sounds confusing, but don't worry.
I have some more familiar analogies in the next slide.
For now, we are just trying to understand the mechanism
behind the closure, so how it all works behind the scenes.
So what matters the most here is that the Booker function
has access to the passengerCount variable
because it's basically defined in the scope
in which the Booker function was actually created.
So in a sense, the scope chain is actually preserved
through the closure, even when a scope
has already been destroyed
because its execution context is gone.
This means that even though the execution context
has actually been destroyed,
the variable environment somehow keeps living
somewhere in the engine.
Now we can say that the Booker function closed over
its parents scope or over its parent variable environment.
And this includes all function arguments.
Even though in this example, we don't have any.
And now this attached or closed over variable environment
stays with the function forever.
It will carry it around and be able to use it forever.
To make it a bit more digestible,
we can also say that thanks to the closure,
a function does not lose connection to variables
that existed at the function's birthplace.
That's a bit more intuitive, right?
But anyway, let's see what happens now
with execution of the Booker function.
So the function attempts to increase
the passengerCount variable.
However, this variable is not in the current scope.
And so JavaScript will immediately look into the closure
and see if it can find the variable there.
And it does this even before looking at the scope chain.
For example, if there was a global passengerCount variable
set to 10, it would still first use the one in the closure.
So the closure basically has priority over the scope chain.
And so after running this function,
the passengerCount becomes one.
This message is logged.
And then the execution context is popped off the stack.
Then execution moves to the next line.
We get a new execution context and a closure is still there,
still attached to the function and the value is still one.
And so now this function executes,
increasing the passengerCount to two
and logging a message again.
Okay, and that's what closures are
and how they work behind the scenes.
And I know that this is all quite complex.
So let me give you a couple different definitions
of closure now, some more formal ones
and some more intuitive and maybe easier to grasp.
And the most formal definition of closure
is the one we already saw,
which is that a closure is the closed over variable
environment of the execution context
in which a function was created
even after that execution context is gone,
or in other words, even after the function
to which the execution context belongs has returned.
Next and a bit easier to understand,
a closure gives a function access to all the variables
of its parent function.
So the function in which it is defined
even after that parent function has returned.
So the function keeps a reference to its outer scope
even after that outer scope is gone,
which basically preserves the scope chain throughout time.
All right, another definition,
or let's say analogy is that a closure makes sure
that a function does never lose connection
to the variables that existed at the function's birthplace.
It remembers the variables,
even after the birthplace is gone.
It's like a person who doesn't lose connection
to their hometown.
In this analogy, the person is the function
and the hometown is the function's parents scope,
and the function then doesn't lose the connection
to the variables stored in this parent's scope.
And I hope that makes sense.
Finally, some people like to think of
this attached variable environment as a backpack.
So in this analogy, a function has a backpack,
which it carries around wherever it goes.
And this backpack contains all the variables
that were present in the environment
in which the function was created.
Then whenever a variable can't be found
in the function scope,
JavaScript will look into the backpack
and take the missing variable from there.
So kind of similar to the other definitions,
but maybe a little bit more visual.
So these are some different ways of defining closure,
but they all mean the same thing.
So they all represent the same idea.
Finally, we need to understand that we do not
have to create closures manually.
And this is also what I already touched on
at the beginning of the lecture.
So instead, this is something that JavaScript
does completely automatically, we don't have to do anything.
Also, there is no way for us to explicitly access
closed over variables.
That's because closures are not like a tangible thing.
They're not like an object or so that we can access.
So we cannot just reach into a closure
and take variables from it.
That's impossible because a closure
is just an internal property of a function.
We can observe that a closure happens
because functions magically keep having access
to variables that should no longer exist,
but we cannot directly access these variables.
However, what we can do is to actually take a look
at this internal property.
So at this backpack, so to say, in a console.
So let's quickly do that before we finish this lecture.
And we can do this by using console.dir
and then of the Booker function itself.
So similar to console.log
but this one is a bit different.
And so here we now get this function itself.
So we can get the arguments, the name property
that we already took a look at before.
And then down here,
we have this scope's internal property.
And this internal scope's property here
is basically the variable environment
of the Booker function.
Now in here, we can actually see the closure
coming from secure booking, all right?
And so this is where we see the passengerCount,
which currently stands at three.
And so this closure here basically,
is the variable environment of this secure booking.
So that's the one that is being preserved by the closure.
All right?
And by the way, whenever you see these double brackets here,
that means that it is an internal property,
which we cannot access from our code.
All right, this was a long video about closures.
Now in the next lecture,
we're gonna take a look at three more examples
of closures and also analyze how they work,
because it's really important that you understand
this concept of closures.
It's a feature that's used all the time in JavaScript
and many times, even without us realizing
that closures are happening.
So if you want to become confident as a programmer,
you always need to know how exactly
everything in your code works.
And that of course includes closures.