There is a popular programming term closure
, that JavaScript developers should know. Unfortunately, most developers struggle to get their heads around this concept.
In this article, I will explain Closure
in clear terms using simple-to-understand examples.
To dive into Closure
, it is essential we understand the following concepts:
- Scope
- Lexical Environment
- Functions returning Functions
Let's get started
Scope
Consider the analogy below:
- In our homes, there are items that are **accessible by all **members of the household. For instance, the remote control of the TV set.
- However, there are some personal or specific items that are only accessible to you, and you can restrict anyone from using those items. An example is your personal toothbrush or your mobile phone.
The above is essentially how scope
works in JavaScript.
Scope
is "access". It permits a developer to restrict access to certain variables in specific areas.
When we define a variable
, we determine the part of our code that variable
should be visible and accessible. This permits the variable
to exist within some boundaries, outside of these boundaries, the variable
is inaccessible.
Every variable has a scope, If a variable is not in the current scope, it will not be available for use.
JavaScript has the following kinds of scopes:
- Global Scope
- Local or function Scope
- Block Scope
Global Scope
For variables
declared outside of any function
or curly braces {}
the scope is the whole program (you can access such variables wherever you want). These are called global.
If a variable is defined in the global scope, it is accessible anywhere in your code (including functions, objects, etc.)
See the code below :
let user = "Emmanuel"; // global scope
// any code below can access the user variable
function greetUser() {
// code here can also access the user variable
return user;
}
In the code above:
- The
user
variable is declared outside of thegreetUser
function giving it a global scope. - Any code we write can access the
user
variable.
Local or function Scope
We can declare variables
inside of a function. When variables
are declared within a function, they are only accessible within the function. These variables are said to keep a local scope.
See the code below:
// code here cannot access the user variable
function greetUser(){
let user ="Emmanuel" // user is a Local variable or has a local scope
// any code here, have access to the user variable
console.log(user)
}
//code here cannot access the user variable.
- In the code above, the
user
variable is declared inside thegreetUser
function hence, has a local scope. - Any code above or below the
greetUser
function cannot access theuser
variable - Aside from the body of the
greetUser
function, If you try accessing theuser
variable, you will get theReferenceError: user is not defined
*Local variables get created when a function is declared and deleted when the function is executed.
*
See the examples below:
function isLocal(){
const user ="Emmanuel"
console.log(user)
}
isLocal() // execute the function
console.log(`Where is ${user}`) // error
The output of the code will be
Emmanuel
ReferenceError: user is not defined
The error
is displayed because the user
variable is deleted when the isLocal()
function is executed.
Because local variables are only accessible inside the body of the function, variables with the same name can be used in different functions.
See the code snippet below
function greetUser(){
let user ="Emmanuel" // user is a Local variable
// any code here, have access to the user variable
console.log(`Hi there ${user}`)
}
function sayHello(){
let user="Emmanuel" // same variable name used in the sayHello function
console.log(`Hello ${user}`)
}
//call both functions
greetUser()
sayHello()
The output of the code below will be :
Hi there Emmanuel
Hello Emmanuel
Block Scope
Prior to ES6 (2015), JavaScript had only Global and Function Scope. The introduction of the let
and const
keywords in ES6 provided Block Scope in JavaScript.
Variables declared within a code block or curly braces
{...}
have block scope, and cannot be accessed from outside the block.
For example
{
// do some tasks with local variables that should not be seen outside
let message = "Hello"; // only visible in this block
alert(message); // Hello
}
// message can NOT be used here
alert(message); // Error: message is not defined
- We can use code blocks to separate a piece of code that does its own task, with variables that only belong to it.
Let's now explore the concept of nested function.
Nested Function
A nested function is a function defined inside another function.
See the code below:
// Example 1
function getUserDetails(fName, lName){
// greetUser function is created inside the parent function
function greetUser(fName,lName){
return (`Hello ${fName} ${lName}`)
}
//Example 2
function increaseCount(){
let count = 0;
// newCount is a nested function
return function newCount(){
return count++
}
}
greetUser
, and "newCount" are nested functions because they are declared inside another function.
- A nested function can be returned, either as a property of a new object or as a result by itself.
- The returned value can then be used somewhere else. Regardless of where it is used, it still has access to the same outer variables.
- In a nested function, we might have
variables
in the outer scope as well as the inner scope. - The variables of the outer scope are accessible inside the inner scope.
In the examples above, the greetUser
function is nested in the getUserDetails
function. This permits the greetUser
function to access the fName
and lName
variables passed as parameters in the outer function, getUserDetails
.
Likewise, the newCount
function can access the outer variable count
.
Let's explore why the above is possible in the next topic.
Lexical environment
During code execution, the JavaScript engine creates a special internal object called Lexical environment.
This Lexical environment has an environment record
which is an object
created to store all local variables.
The lexical environment will be pre-populated with all declared variables in the code.
When a function executes, prior to its execution, the lexical environment provides the environment record to store the parameters
and or variables
declared in the body of the function.
If a variable is declared in the global scope, a lexical environment is also created to store the variable in the environment record.
Like the example above, when you declare a global variable
and a function
, you have two lexical environments respectively:
- Outer environment
- Inner environment
- The
sumNum
is the inner environment whilst thex
variable forms part of the outer environment.
Lexical Scoping (Accessing variables declared in the outer scope)
Lexical scoping describes how the JavaScript engine determines variable names when functions are nested.
The term "lexical" relates to the fact that lexical scoping uses the location where a variable is declared within the code to determine where that variable is available.
Consider a scenario where you live in a confined house with a lower fence wall. Within the house, you can look outside the fenced wall to have visibility of your environment (and access stuff outside the fenced wall).
Likewise in JavaScript, each scope can "look outside" to view the outer scope and access variables in the outer scope. Furthermore, nested functions can access variables declared in the outer scope.
*A variable defined in the global scope can be accessed inside the inner scope or any block scope even when it is not declared in the block scope. *
To rephrase, an inner or block scope can** "look outside"** to access variables declared in the global scope.
See the code below:
let x = 20; // global scope
function sumNum(){
// inner scope
let y = 10;
let z= 40;
console.log(x+y+z)
}
//execute the function.
sumNum()
The output of the code will be 70
. Do you know why?
Accessing a variable in the lexical environment
When a code wants to access a variable, the environment record of the internal environment is searched first, followed by the record in the outer environment, then the outer one, and so on, until it gets to the global scope. If the variable is not found anywhere, an error
will be thrown.
When the code above is executed:
- The
y
andz
variables are instantly found in the environment record of the internal environment of thesumNum
function. - However, when the code wants to access the
x
variable, it cannot find thex
variable declared in theenvironment record
of the inner environment. - It "looks outside" the inner environment and searches the outer environment. It then finds the outer environment in the global scope and accesses the value stored in the
x
variable. - Hence
console.log(x + y + z)
produces the expected output of70
.
*In effect, a block or function scope can look at its inner environment for a variable, if not found, it checks the outer environment for the variable, and if it was declared there, use the value declared in the outer environment. This concept is known as lexical scoping *
Take a look at this other example
function outer(){
let user="Emmanuel"; //local scope
// inner function
function init(){
console.log(user);
}
init()
}
//execute the outer function
outer()
outer()
creates a local variable user
and a function init
which is an inner function.
Note that the init()
function has no declared variables. However, since inner functions have access to the variables declared in the outer functions, init()
can access the user
variable declared in the parent function, outer()
.
Function Returning a Function
In a nested function, when the outer function is executed, it can return an inner function. Because functions are values, the inner function can be assigned to a variable and executed later in our code.
See the code below
function outerFunction(){
return function innerFunction(){
//code goes here
}
}
//return inner function and assign to a variable
const useFunction = outerFunction();
//useFunction stores the innerFunction
Take a look at this other example
function outer(){
let a = 10
return inner(){
//code goes here
let b = 20;
console.log(a + b)
}
}
//return inner function
const getInner = outer()
Here we have two functions
- An outer function,
outer
which has a variablea
, and returns an inner functioninner
- The body of the inner function, we declare a variable
b
and assign it a value of20
. - The inner function can have access to the outer variable
a
. - The scope of the variable
a
is limited to theouter
function, whilst the scope of the variableb
is limited to theinner
function. - We then execute the
outer
function
Keep in mind, anytime we execute a function, any variables defined in the function scope cease to exist.
**
In effect, in the console.log(a+b)
, the variable b
exists prior to the execution of the outer
function.
Once the outer function has been executed, the b
variable, **no longer exists.
What this means is that in console.log(a+b)
, the variable b
exists only during the execution of the outer()
function. Once the outer function has finished executing, the variable b
no longer exists.
Variables of a function are recreated anytime the function is called, and live up until the function completes execution.
The question is, what happens to variables when the function call that created them has completed its execution?
This question introduces the concept of closure
.
Closure
From the above, we know that variables
get deleted when the function that created them has completed its execution.
** Hence, it should not be technically possible to access the b
variable in the inner function.**
That said, the inner function can access the variables of the surrounding function due to closures
in JavaScript.
A closure gives you access to an outer function’s scope from an inner function.
When an inner function is called, the function body remembers the environment in which it was created, not the environment in which it is called.
Take note of this phrase, "the environment in which it was created". Because the inner function knows the place of birth ( environment in which it was created), the inner function preserves the scope chain of the surrounding function at the time the surrounding function (outer function) was executed.
Even though you will expect the variables declared in the outer function to be deleted after it has been called, the inner function has already **accessed and preserved **the variables in the outer function.
*The inner function then carries the preserved variables along with it (everywhere it goes)
*
The following code shows an example of this. It defines a function, mainFunction
, that creates a local variable. It then returns a function that accesses and logs this local variable to the console.
function mainFunction(name){
let user= name //local variable
return function innerFunction(){
console.log(`Hello ${user}`)
}
}
const callInner = mainFunction("Emma"); // execute mainFunction
console.log(callInner())
The output of the code above will be Hello Emma
.
Let's explore the details:
- When the outer function
mainFunction
is executed, it returns an inner function that is assigned to thecalInner
variable - This inner function remembers the environment in which it was created and hence it is able to access and preserve any variable in that environment.
- On execution of the
mainFunction()
we would expect the local variableuser
to get deleted. But note that, prior to its execution, theinnerFunction
was able to access and preserve theuser
variable declared in the outer scope. - No matter where the
innerFunction
is called, we are still able to remember the variable it has preserved even when themainfunction
has finished execution. TheinnerFunction
is aclosure
. - Executing the
callInner()
function, therefore, produces the outputHello Emma
.
Examples of closures
In this example, we want to create a function that allows us to send emails to users, the user reads the content of the mail when he clicks on the button.
To achieve this, we declare two functions composeMail
and emailContent
.
-
composeMail
will accept thesenderName
as a parameter - In the body of the
emailContent
we write the content of the email to send. - Because these two functions are related, we will nest them, with the
composeMail
as an outer function andemailContent
as an inner functon. - The
emailContent
will be aclosure
, it is able to look outside its scope and access and preserve any variable or parameter defined in the outer scope.
We would also define a click
event listener, that listens for a click of a button to enable us to read the content of the email.
Examine the code below:
const btn = document.getElementById("btn") // access a button element with id "btn"
// define composeMail and emailContent functions
const composeMail = (senderName)=> {
// nested function
const emailContent = (content)=> {
return `Hi ${senderName},${content}`
}
return emailContent
}
// execute the composeMail
const readEmail = composeMail("Emmanuel");
// view content of mail
function viewInbox(){
console.log(readEmail(`Your article on Closures is receiving a lot of views, Congrats !!!`));
}
// add a click event listener
btn.addEventListener('click',viewInbox)
The output of the code will be
Hi Emmanuel,Your article on Closures is receiving a lot of views, Congrats !!!
Let's walk through the code step by step
- We access the button element defined in our
html
and assign it tobtn
variable - We define the
composeMail
andemailContent
functions. - The
composeMail
accepts a parametersenderName
which when executed, will receive the name of the user to send the email. - When the
composeMail
is executed, it returns theemailContent
function which we assign to thereadMail
variable to be executed later. - Prior to the execution of the
composeMail
function, theemailContent
is able to access and preserve any variable declared in thecomposeMail
.For this instance, it will besenderName
. - On execution of the
composeMail
function, the returned function is assigned toreadMail
- The
emailContent
function has safely stored thesenderName
variable hence anywhere we execute theemailContent
we still have access to the value passed to thecomposeMail
during execution. - Finally, we define the
viewInbox
function and in the body of the function we execute theemailContent
. - Now, on click of a button, the
viewInbox
function is executed, and theemailContent
function still has access to thesenderName
variable. - We can now say the
emailContent
function is aclosure
.
Eg.2 Which variables are accessible?
Which of the timeOfDay
variables is accessible, in the code below?
function greetUser(name) {
let timeOfDay = "Good evening";
//define another function
return function() {
console.log(`${timeOfDay} ${name}, what would you like to do today`);
};
}
let timeOfDay = "Good morning"; // change time of day
// run the greetUser function
let greeting = greetUser("Emmanuel");
// call the returned function
greeting(); // what will it show?
In the example above, the function greetUser
has a local variable timeOfDay
, it also creates another anonymous function. That new function can be called somewhere else in our code.
In the global scope, we also declare another timeOfDay
variable and assign a value to it.
When we execute the return function, which of the timeOfDay
variable will it access, and what would be the final output?
Result:
"Good evening Emmanuel, what would you like to do today"
Explanation:
- Regardless of where this new function is called, it will have access to its outer variable from its creation place, and not the invocation place. Creation refers to where the function was first defined ( place of birth)
- The value of the
timeOfDay
variable at its place of birthGood evening
and notGood morning.
, hence the output.
Note that, if there were no timeOfDay
variable in the greetUser
function, then we would search outside that function, for it, and it would be found in the global scope.
In that case, the value of the variable will be:Good morning
. Hence the result would be Good morning Emmanuel, what would you like to do today.
*The ability to reference a specific instance of a local variable in a surrounding scope is called closure
.
*
A function that references variables from local scopes around it is called a closure
.
This action not only frees you from having to worry about the lifetime of variables but also makes it possible to use function values in unique methods.
The Scope Chains in Closure
In closure
, the inner function has three scope chains:
- Access to its own scope: variables declared between its curly braces
{}
- Access to the outer function’s variables: variables declared outside the inner function
- Access to any global variables that may be defined
Let's wrap up with another example:
const lessonNum = 2; //global variable
const learnFruit = (fruit) => {
const fruitName = fruit; // variable in the outer scope
//inner function
return getFruit = (fruit)=>{
let userName ="Emmanuel"; //inner variable
console.log(`Hello ${userName},welcome to lesson number ${lessonNum},here you learn about fruits.The fruit you picked is ${fruitName},and it begins with the letter ${(fruitName.charAt(0)).toUpperCase()}`)
}
}
const sayAlphabet = learnFruit('mango');
// execute the inner function
sayAlphabet()
- Executing
learnFruit
returns the inner functiongetFruit
which is aclosure
. - The
getFruit
has access to three scope chains:
- The
lessonNum
variable in the global scope - The
fruitName
variable in the outer scope - The
userName
variable in the inner scope
To reiterate the concept of closure, once the learnFruit()
has been executed, we would have expected the variable declared in its scope to be deleted.
However, prior to execution, the getFruit
function is able to access and keep copies of the outer variables.
Regardless of where the getFruit
is run, we can still use any variables defined in the scope chain.
Rule of Thumb
A rule of thumb to identify a closure: if inside a function you see a strange variable (not defined inside that function), most likely that function is a closure. Because the strange variable has been captured and preserved.
Importance of closures
Closures permit you to save a snapshot of the scope where the function was initially declared.
Because they exist within outer functions, it allows you to run the outer function, and execute the inner function (closure) later, with the values from the outer function saved.
This is optimal than calling two different functions if these two functions are linked because it empowers you to write clean and maintainable code
In summary
Closure is like keeping a copy of the local variables of the function.
It helps eliminate the duplication of effort with a program or to hold values that need to be reused throughout a program so that the program doesn't need to recalculate the value each time it's used.
I would love to read your input on this article, kindly leave a comment below. Don't forget to follow me on Twitter, and share this article to help others
Top comments (1)
Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍