Closures are a very important part of JavaScript that every developer should know well. But, this topic often becomes a real challenge for beginners and can be difficult to understand. In this article, I want to explain closures in JavaScript in a very simple way.
To understand closures, you need to know three main concepts:
🔹 Scope
🔹 Lexical environment
🔹 The closure itself
✔️ Scope
Scope is a part of code where you can use a variable, function, or object. In other words, it's the part of code where this item is "seen".
Most of the time (let's pretend the var
keyword doesn't exist), the scope is the code block inside curly brackets - {...}
.
This isn't exactly right, but to keep it simple, we can say every function, and also code blocks like if
, for
, while
, etc. (when using let
or const
variables), makes a new local scope.
In JavaScript, there are 2 main scopes:
Global Scope - variables declared outside of functions or code blocks can be used anywhere in the code. They are global.
Local Scope - as mentioned, variables inside a function (Function Scope) or code block (Block Scope) can only be used inside that block.
Let's look at an example.
As we can see, the playerHealth
variable is in the global scope, so we can use it both globally and inside a function. But the damageFromMonster
variable was declared inside a function, which means it's within a local scope. That's why, when we try to access it outside the function, we get an error.
This example also shows that different scopes can interact with each other. Furthermore, local scopes have access to variables from the outer scope. And this leads us to the concept of "lexical environment".
✔️ Lexical environment
The Lexical environment is a data structure that stores information about variables, functions, and other objects in the current execution context. It's an invisible object that exists for every block of code or function in JavaScript. It only exists in the computer's memory during code execution, so it can't be physically seen.
This object consists of two parts:
an object with variables in the current scope,
a reference to the parent lexical environment.
Let's go back to the previous example. We have 2 lexical environments.
🌍 Global, which doesn't have a reference to a parent lexical environment because one doesn't exist.
🔐 Local, which refers to the global lexical environment.
❗️I want to emphasize that the examples above are not real code, but just an attempt to visually imagine how the lexical environment object might look.
It's also important to note that the local lexical environment for the monsterAttack
function is created not when we declare the function, but when we call it (monsterAttack()
). This is a crucial point for understanding closures, which we will discuss next.
✔️ Closure
Closures in JavaScript - it is a feature where a function "remembers" its lexical environment in which it was created. This means that a function can access external variables even after the outer code has finished its execution.
In other words, when calling a function, its lexical environment is established, storing the current values of variables at the time of the call, as well as referencing its outer environment. This allows the function to "see" the variables that were accessible to it when it was declared.
Indeed, this concept might seem a bit confusing at first. However, in practice, everything becomes much clearer.
Let's look at an example.
Imagine we're inside a video game. In game's editor, we create a character. Let's choose a wizard to whom we assign a name (name
). This wizard will immediately have a basic spell (castSpell
), as well as 100 mana (mana
) for its use. The creation of such a character can be represented by the following function.
When we click the "Create" button, a character is created, and it might look something like this:
At this point, a lexical environment for the createWizard
function will be created.
When this function completes execution, its lexical environment disappears, but some parts (such as the mana
and name
variables) are "remembered" due to the closure of the castSpell
function.
After this, we execute our first attack using our spell.
At this point, another lexical environment will be created, now for the castSpell
function (which is stored in the lightWizard
variable). This environment will exist within the lexical environment created with the lightWizard
character, and therefore it will reference it and its variables (name
and mana
).
With the next attack, the mana will be reduced. This is because a new lexical environment for lightWizard
is created during the new spell cast (castSpell
). This environment will include the current mana value, which is 80. After the subsequent attack, only 70 will remain, and so on.
Now, a new player appears in the game. He also uses the editor and creates a new wizard.
For this character, a new lexical environment is created, which differs from the previous one only in name.
Then, a battle between the two wizards begins. Voldemort attacks Harry with a full 100 mana.
Harry strikes back, but he has less power since he has already used some of his mana.
As you've probably realized by now, a new lexical environment is created with each attack, storing the current data, including the remaining mana
.
Even though both wizards were created using the same createWizard
function, their execution contexts are different. Each one has its own separate variables. So, when creating a new wizard with createWizard()
, you always start with 100 mana.
This example is quite basic, but it illustrates the core concept. I chose this approach to avoid complex explanations and to focus on the main idea of this significant and interesting topic.
I hope this article sparks your interest and encourages you to learn more about closures.
Top comments (1)
Great thank you