Closures | JavaScript 🔥 | Lecture 128
Summary
TLDRThis video script delves into the intricate world of closures in JavaScript, a concept often deemed challenging yet deeply powerful. The instructor methodically demystifies closures, guiding learners through their inner workings using relatable analogies and practical examples. By linking closures to familiar notions like execution contexts, scope chains, and variable environments, the video unveils the seamless interplay that allows functions to retain access to variables from their birthplaces, even after those environments have ceased to exist. With its comprehensive explanations, code walkthroughs, and insightful definitions, this script illuminates a pivotal JavaScript feature, empowering developers to harness its capabilities confidently.
Takeaways
- 🔑 Closures are an automatic mechanism in JavaScript where a function remembers the variables from the environment in which it was created, even after that environment is gone.
- 🧩 Closures allow functions to access and manipulate variables from their parent's scope, even after the parent function has finished executing.
- 🕰️ Closures preserve the scope chain and variable environment of a function, even when the execution context in which the function was created is no longer on the call stack.
- 🎁 A closure is like a function carrying a 'backpack' containing all the variables that existed at the function's birthplace, which it can access anytime.
- 🌍 Closures ensure that a function never loses its connection to the variables from its 'hometown' (parent scope), even after its parent scope is gone.
- 🔎 Closures are not something we create manually; JavaScript handles them automatically when functions are created within other functions.
- 🚫 We cannot directly access or manipulate the closed-over variables in a closure, but we can observe the closure's effects.
- 🧠 Understanding closures requires a solid grasp of execution contexts, the call stack, and the scope chain.
- 🔗 Closures bring together concepts like execution contexts, call stacks, and scope chains in a beautiful and powerful way.
- 🚀 Mastering closures is crucial for becoming a confident and proficient JavaScript programmer.
Q & A
What is a closure in JavaScript?
-A closure in JavaScript is a feature where an inner function has access to the variables of its outer (enclosing) function's scope, even after the outer function has executed and its execution context is no longer on the call stack.
How are closures created in JavaScript?
-Closures are created automatically in certain situations when an inner function is defined within an outer function. This happens without the need for explicit actions by developers, like manually creating closures.
Why is the concept of closures considered difficult to understand by many developers?
-Many developers find closures difficult to understand because they involve several JavaScript concepts like execution context, call stack, and scope chain, working together in a complex way.
What is the 'secure booking' function's role in demonstrating closures?
-The 'secure booking' function demonstrates closures by encapsulating a 'passengerCount' variable and returning an inner function that modifies this variable, showcasing how the inner function retains access to the outer function's variables even after the outer function has executed.
Can closures be manually created like objects or arrays?
-No, closures are not created manually like objects or arrays. They occur automatically in certain situations when functions are created and executed.
How does the 'Booker' function illustrate the concept of closures?
-The 'Booker' function, returned from 'secure booking', illustrates closures by accessing and modifying the 'passengerCount' variable of its parent function, demonstrating how inner functions retain access to the variables of their outer functions.
Why can the 'Booker' function still access the 'passengerCount' variable after the 'secure booking' function has finished executing?
-The 'Booker' function can still access the 'passengerCount' variable due to closures, which allow functions to remember and access the variables from their birthplace's scope, even after that scope's execution context is gone.
What is the significance of the variable environment in closures?
-The variable environment in closures is significant because it contains all the local variables of a function's execution context. It is attached to the inner function, allowing it to access and manipulate these variables even after the outer function's execution context has been popped off the call stack.
How does the concept of closures alter the traditional understanding of the scope chain?
-Closures extend the traditional understanding of the scope chain by preserving the scope chain through the closure, even after a function's execution context has been destroyed, allowing inner functions to access variables from their enclosing functions' scopes.
How can developers observe closures in action within their code?
-Developers can observe closures in action by using `console.dir` to inspect functions in the console. This method allows them to see the 'scopes' internal property, where the closure's variable environment, including accessible variables like 'passengerCount', is displayed.
Outlines
🚪 Understanding Closures: The Mystical Feature of JavaScript Functions
This paragraph introduces the concept of closures in JavaScript functions, which many developers find challenging to understand. It emphasizes that with the right explanation and an understanding of concepts like execution context, call stack, and scope chain, closures become less complicated. The paragraph sets up an example to demonstrate how closures work, where a function called 'secureBooking' is created to manipulate a 'passengerCount' variable. This function returns another function, which can access and update the 'passengerCount' variable even after the 'secureBooking' function has finished executing. The paragraph ends by explaining that this is possible because of closures, which allow functions to remember variables from their birthplace.
🔑 Unveiling the Closure: How the 'Booker' Function Accesses 'passengerCount'
This paragraph continues the example from the previous paragraph, analyzing how the 'Booker' function, created by the 'secureBooking' function, can access and update the 'passengerCount' variable. It demonstrates that calling the 'Booker' function multiple times increments the 'passengerCount' variable, even though the 'secureBooking' function has finished executing. The paragraph highlights the seemingly strange behavior of the 'Booker' function being able to access a variable from a function that no longer exists in the execution context. It sets the stage for explaining how closures make this possible.
🧩 The Mechanism Behind Closures: Preserving Variable Environments
This paragraph dives deeper into the mechanism behind closures, explaining that 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. It uses the example of the 'Booker' function, which was created in the execution context of 'secureBooking', and therefore has access to the 'passengerCount' variable through the closure. The paragraph provides multiple definitions and analogies to help understand closures, such as a function preserving the scope chain throughout time, not losing connection to its birthplace, and carrying a backpack of variables from its creation environment.
🔍 Exploring Closures: Examples, Accessing Closed Variables, and Importance
This paragraph clarifies that closures are created automatically by JavaScript, and developers do not need to create them manually. It also mentions that closed-over variables cannot be accessed directly, as closures are an internal property of a function. The paragraph demonstrates how to inspect the closure using 'console.dir' to view the internal scope property containing the closed-over variables. It emphasizes the importance of understanding closures, as they are frequently used in JavaScript, often without developers realizing it. The paragraph concludes by stating that understanding closures is crucial for becoming a confident programmer, as it is essential to know how everything in the code works.
Mindmap
Keywords
💡Closure
💡Execution Context
💡Call Stack
💡Scope Chain
💡Variable Environment
💡Birthplace
💡Lexical Environment
💡Scope
💡Global Scope
💡Higher-Order Functions
Highlights
A closure makes a function remember all the variables that existed at the function's birthplace essentially, right?
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.
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.
A function does not lose connection to variables that existed at the function's birthplace.
Thanks to the closure, a function does not lose connection to variables that existed at the function's birthplace.
A closure gives a function access to all the variables of its parent function, even after that parent function has returned.
A closure makes sure that a function does never lose connection to the variables that existed at the function's birthplace.
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.
We do not have to create closures manually. This is something that JavaScript does completely automatically.
There is no way for us to explicitly access closed over variables.
Closures are an internal property of a function.
We cannot directly access these variables, but we can observe that a closure happens because functions magically keep having access to variables that should no longer exist.
Closures are a feature that's used all the time in JavaScript, and many times, even without us realizing that closures are happening.
To become a confident programmer, you need to know how exactly everything in your code works, including closures.
A closure is not a feature that we explicitly use. We don't create closures manually, like we create a new array or a new function. A closure simply happens automatically in certain situations, we just need to recognize those situations.
Transcripts
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.
Browse More Related Video
5.0 / 5 (0 votes)