Jonas: There is an almost mystical feature
of JavaScript functions that many developers
fail to fully understand.
And what I'm talking about is something called closures.
So when I ask 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 scope 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 passenger count
and it'll start at zero,
but we will be able to manipulate it.
And I'm calling this function here secure booking
because this passenger count 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 passenger count variable.
So the variable that is defined in the parent function.
So that's important.
And then let's just log the new
passenger count to the console.
So passenger count.
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 passenger count
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 passenger count is in the local scope,
but of course this scope also gets access to all variables
of the parent 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'll be stored in the booker variable.
So the global context now also contains the booker variable.
Alright, now what else happens
when the secure booking function returns?
That's right, it's execution context pops off
the stack and disappears.
So the secure booking function has done its job
and has now finished execution.
Now, it's very important to note
that it's only the execution context itself
that is gone from the stack.
The variable environment containing passenger count
is still stored in memory.
And in particular this variable environment
has been transferred to the heap in this case,
but more on that later.
So again, the execution context is gone at this point,
but the variable passenger count set to zero
still lives on in the engine's memory.
And this is very important
to keep in mind moving forward, alright?
But most of this is actually nothing new
at this point, right?
All we did was analyze the call stack
and the scope chain as we call the secure booking function.
And this is gonna be very important to later understand
and see the closure.
So as of yet we didn't see any closure yet.
All we did was using the knowledge that we already have
to understand how this booker function was created.
But now let's go back to our code
to actually use the booker function
and 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 1, 2, 3 passengers.
And so what this means is that the booker function
was in fact able to increment the passenger count
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 passenger count 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 passenger count 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 here 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 passenger count 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 know really explain how it actually works.
So this is how we left the call stack and scope chain
after the last slide.
And let's now start by remembering
that the execution context of secure booking
was popped off the call stack
after that function finished running together
with its variable environment.
Now also remember how we said
that this environment which contains
the passenger count variable is still stored in memory.
But why is that?
Didn't we learn earlier in the lecture about
garbage collection that variables stored in the stack
are destroyed as soon as the execution context
pops off the stack?
Well, yes we did, because that's usually the case.
There is however, one exception that we talked about
and that exception is the existence of a closure.
So remember how we said that if an object is reachable
by a closure, it cannot be garbage collected
and will therefore stay in the heap indefinitely.
And this actually applies to objects in the heap
and to variable environments for variables
that are stored in the stack as long as they're reachable
by a closure.
So in summary, the passenger count variable still exists
because it is in fact reachable by a closure.
That's the only reason, otherwise it would really be gone.
But in this case, the engine moved the variable environment
to the heap where it can stay forever.
Again, because it's reachable by a closure
and can therefore not be garbage collected.
So this is why the passenger count variable still lives on.
So the way memory and garbage collection work
play an essential part here.
Alright?
And well, I told you that in this lecture
everything would come together
and so make sure that you understand all the stuff
that we learned earlier.
And so in this case that we learned about memory management
and garbage collection.
But anyway, now it's time to finally run the booker function
and see what's exactly gonna happen here.
And by the way, note that booker is really
this function here.
So located in the global scope, okay?
So the first thing that's gonna happen
when we run this function
is that a new execution context is put on the top
of the call stack
and the variable environment of this context is empty
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 scope of the global scope
just like this.
But maybe now by looking at this scope chain,
you're starting to see a problem here.
How will the booker function access
the passenger count variable?
It's nowhere to be found in the scope chain
of this function, right?
So this is where we now start to unveil
the magic of the closure.
I mean, we already know that the closure exists
because we saw it in our code,
but we don't really know what it is
and why it exists in the first place.
So the magic is basically this.
Any function always has access to the variable environment
of the execution context in which the function was created.
In the case of booker, this function was created,
so it was born in the execution context of secure booking,
which we popped off the stack previously, right?
So therefore the booker function will get access
to this variable environment which contains
the passenger count variable.
So that's the magic trick right there.
This is how the booker function will be able to read
and to manipulate the passenger count variable.
And so this connection is what we call the 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 that 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 passenger count 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 parent 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'll 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 passenger count 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 so after running this function,
the passenger count becomes 1, 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 the closure is still there,
still attached to the function and the value is still 1.
And so now this function executes increasing
the passenger count to two and logging a message again.
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 the 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.
Alright, 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 parent scope.
And the function then doesn't lose the connection
to the variables stored in this parent 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 the 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 scopes internal property.
And this internal scopes property here
is basically the variable environment
of the booker function.
Now in here we can actually see the closure
coming from secure booking.
Alright?
And so this is where we see the passenger count,
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.
Alright?
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.
Alright, 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.