A closure is an inner function referencing one or more items in its lexical scope (definition area).
Note: An inner function is a function defined inside a block, module, or another function.
In other words, a closure is a function that retains access to its lexical scope's features even after its execution closes.
For instance, consider the following code:
function createProfile() {
const myName = "Oluwatobi";
function showName() {
return myName;
}
return showName();
}
createProfile();
showName()
is the closure in the example above because:
- It is an inner function.
- It retains a reference to its lexical scope's variable.
Note:
- The
createProfile()
function's body isshowName()
's lexical scope. - The
showName()
function retained a reference to its lexical scope's state (variable's value) by callingmyName
. - The reason
showName()
is a closure is not because it was returned nor invoked withincreateProfile()
. Instead, it is a closure because it is an inner function that retains a reference to its lexical scope's feature (that is,myName
). - All closures have three scopes:
- Local scope: The space within the closure.
- Parent scope: The space of the closure's block, module, or parent function. In other words, the parent scope is the outer/enclosing environment.
- Global scope: The space containing all the scopes/code.
So, now that we know what a closure is, we can discuss how browsers will process createProfile()
's invocation.
How Do Browsers Process a Closure's Lexical Scope's Invocation?
Below is a walk-through of how JavaScript will process the createProfile()
function's invocation.
1. Allocate memory
{ // This is the createProfile() function's opening block
The first thing JavaScript does when browsers invoke createProfile()
is to allocate the memory it needs to process the function's invocation.
2. Parse the variable's declaration
const myName;
JavaScript's second task is to analyze myName
's declaration, following the hoisting principles.
3. Parse the function's declaration
function showName() {}
The computer's third step is to analyze showName()
's declaration, following the hoisting principles.
4. Initialize the variable
myName = "Oluwatobi";
The computer initializes myName
with the "Oluwatobi"
string value, following the hoisting principles.
5. Return the invoked function's output
return showName();
The fifth thing JavaScript does is to return showName()
's output to the createProfile()
function.
6. Return the variable's content
return myName;
JavaScript returns the variable's value to the showName()
function's invocation.
7. Empty the allocated memory
} // This is the createProfile() function's closing block
The computer ends the createProfile()
function's execution and empties the allocated memory. Therefore, all the values JavaScript stored in createProfile()
's allocated memory will vanish forever!
So, what makes closures unique? Let's discuss this now.
What Makes Closures Unique?
No external scope can access a function's data by default, but a closure's scope can.
For instance, the snippet below returned an error because the global scope cannot access the createProfile()
function's space.
// Define the createProfile function:
function createProfile() {
const myName = "Oluwatobi";
}
// Call myName variable:
myName;
// The call above will return:
// Uncaught ReferenceError: myName is not defined
You can, however, use closures to access createProfile()
's scope from another environment.
Here's an example:
// Define the createProfile function:
function createProfile() {
const myName = "Oluwatobi";
function showName() {
return myName;
}
return showName;
}
// Invoke the createProfile and store its output in a variable:
const firstName = createProfile();
// Check the content inside the firstName variable:
firstName;
// The call above will return:
// function showName() {
// return myName;
// }
// Invoke the function stored in the firstName variable:
firstName();
// The invocation above will return:
// "Oluwatobi"
Note: createProfile()
returned its inner function (the closure)—not the result of an invocation.
Question: Since the firstName
variable contains only a function (showName()
), how did the showName
external function get access to createProfile()
's variable?
Answer: The reason the firstName
variable's function gained access to createProfile()
's variable is because:
- The
firstName
variable's function is a closure. - Closures retain reference to their lexical scope's data—even after JavaScript has closed their definition space's execution.
Let's discuss the reasons above in more detail.
How Do Closures Retain Access to Their Lexical Scope's Features?
Closures retain access to their lexical scope's features by storing referenced items in-memory.
For instance, reconsider our previous example:
// Define the createProfile function:
function createProfile() {
const myName = "Oluwatobi";
function showName() {
return myName;
}
return showName;
}
// Invoke createProfile and store its output in a variable:
const firstName = createProfile();
// Check the content inside the firstName variable:
firstName;
// The call above will return:
// function showName() {
// return myName;
// }
// Invoke the function stored in the firstName variable:
firstName();
// The invocation above will return:
// "Oluwatobi"
Here's is how the computer will analyze the snippet above:
1. Allocate memory
{ // This is the createProfile() function's opening block
The first thing JavaScript does when browsers invoke createProfile()
is to allocate the memory it needs to process the function's invocation.
2. Parse the variable's declaration
const myName;
JavaScript's second task is to analyze myName
's declaration, following the hoisting principles.
3. Parse the function's declaration
function showName() {}
The computer's third step is to analyze showName()
's declaration, following the hoisting principles.
4. Initialize the variable
myName = "Oluwatobi";
The computer initializes myName
with the "Oluwatobi"
string value, following the hoisting principles.
5. Return the function
return showName;
The fifth thing JavaScript does is to return the showName
function to createProfile()
.
Note the following:
- The computer will store the returned
showName
function in thefirstName
variable since that's where thecreateProfile()
invocator lives. - JavaScript will also store
myName
's value in thefirstName
variable's memory becauseshowName
referenced it. - The
firstName
variable's copy ofshowName
andmyName
are new code instances the computer created duringcreateProfile()
's execution. - Browsers will not run the
"return myName"
code at this stage because we did not invokeshowName
.
6. Empty the allocated memory
} // This is the createProfile() function's closing block
The computer ends the createProfile()
function's invocation and empties the allocated memory. Therefore, all the values JavaScript stored in createProfile()
's allocated memory will vanish forever!
TLDR
The main gist of the walk-through above is this:
-
The
const firstName = createProfile()
statement made the computer do the following:- Invoke
createProfile()
. - Store the invocation's returned function in the
firstName
variable. - Store
myName
's current state (whichshowName()
referenced) in thefirstName
variable's memory.
- Invoke
Therefore, here is what will happen whenever you invoke firstName
's function:
1. Allocate memory
{ // This is the showName() function's opening block
The first thing JavaScript does when browsers invoke showName()
is to allocate the memory it needs to process the function's invocation.
2. Return the variable's content
return myName;
The computer analyzes the return
statement by doing the following:
- It checks if there's a
myName
variable definition locally within theshowName()
function's local scope. But there's none, so the computer moves up to check the next scope—thefirstName
variable's environment. - JavaScript searches for
myName
in thefirstName
variable's memory. Fortunately, it foundmyName
there! Therefore, the computer getsmyName
's content ("Oluwatobi"
) and returns it.
Note: The computer found myName
in firstName
's memory because JavaScript also stored all the data showName()
referenced while storing showName
inside firstName
.
3. Empty the allocated memory
} // This is the showName() function's closing block
The computer ends showName()
's execution and empties the allocated memory. Therefore, all the values JavaScript stored in showName()
's allocated memory will vanish forever!
Note:
- The
myName
variable will persist even after the computer closesshowName()
's execution. This is because JavaScript storedmyName
infirstName
's memory—not insideshowName()
's memory. - The computer will empty
firstName
's memory when it closes its scope's execution.
So, now that we know how closures retain access to their lexical scope's features, we can discuss a few more examples.
Example 1: Generate Prepublication Years
// Define the generateYear function:
function generateYear() {
let publicationYear = 2023;
function decreasePublicationYear() {
return --publicationYear; // The -- operator subtracts one from the publicationYear variable.
}
return decreasePublicationYear;
}
// Invoke generateYear and store its output in a variable:
const prePublicationYear1 = generateYear();
// Invoke another instance of generateYear and store its output in a variable:
const prePublicationYear2 = generateYear();
// Invoke the function stored in the prePublicationYear1 variable:
prePublicationYear1();
// The invocation above will return: 2022
// Invoke the prePublicationYear1's function again:
prePublicationYear1();
// The invocation above will return: 2021
// Invoke the prePublicationYear1's function one more time:
prePublicationYear1();
// The invocation above will return: 2020
// Invoke the function stored inside the prePublicationYear2 variable:
prePublicationYear2();
// The invocation above will return: 2022
The snippet above consistently returns a value that is one (1) less than the previous year because:
- JavaScript also stored all the data
decreasePublicationYear()
referenced while storingdecreasePublicationYear
insideprePublicationYear
. -
prePublicationYear
's copy of thepublicationYear
variable remains accessible even after the computer closesdecreasePublicationYear()
's execution.
Note:
-
prePublicationYear1
andprePublicationYear2
have different versions ofgenerateYear()
's output because the computer creates new instances during eachgenerateYear()
's execution. - The subtraction happens on
prePublicationYear
's copy of thepublicationYear
variable—not on the one inside thegenerateYear()
function. - The
--publicationYear
code uses the decrement operator to subtract one from thepublicationYear
variable and return the value after the decrement.
Example 2: Generate the Multiple of a Constant
// Define the generateMultipleOfX function:
function generateMultipleOfX(x) {
return function multiplyXByY(y) {
return x * y;
};
}
// Invoke generateMultipleOfX and store its output in a variable:
const multipleOfX = generateMultipleOfX(3);
// Invoke the multipleOfX variable's function:
multipleOfX(2);
// The invocation above will return: 6
// Invoke the multipleOfX variable's function again:
multipleOfX(10);
// The invocation above will return: 30
// Invoke the multipleOfX variable's function one more time:
multipleOfX(1);
// The invocation above will return: 3
The snippet above consistently multiplies multipleOfX
's argument by 3
because:
- JavaScript also stored all the data
multiplyXByY()
referenced while storingmultiplyXByY()
inside themultipleOfX
variable. (In the instance above,multiplyXByY
referenced3
(parameterx
's value)). -
multipleOfX
's copy of the value3
remains accessible even after the computer closesmultiplyXByY()
's execution.
Note:
- The multiplication happened on
multipleOfX
's copy ofgenerateMultipleOfX
's argument. - Your closure can be an anonymous function. We named the ones above for ease of referencing.
Example 3: Create a Publication Year Calculator
// Define the publicationYearCalculator function:
const publicationYearCalculator = (function () {
let publicationYear = 2023;
function updatePublicationYear(value) {
publicationYear += value;
}
return {
addOne: function () {
updatePublicationYear(1);
},
subtractOne: function () {
updatePublicationYear(-1);
},
defaultYear: function () {
return publicationYear;
},
};
})();
// Check the publicationYearCalculator variable's content:
publicationYearCalculator;
// The invocation above will return:
// {addOne: ƒ, subtractOne: ƒ, defaultYear: ƒ}
// Invoke publicationYearCalculator's defaultYear() method:
publicationYearCalculator.defaultYear();
// The invocation above will return: 2023
// Invoke publicationYearCalculator's addOne() method:
publicationYearCalculator.addOne();
// The invocation above will return "undefined" because
// we programmed addOne() to invoke updatePublicationYear()
// only rather than return the result of the invocation.
// So, addOne() will increment publicationYear by one but
// will not return any value.
// Invoke publicationYearCalculator's defaultYear() method:
publicationYearCalculator.defaultYear();
// The invocation above will return: 2024
// Invoke publicationYearCalculator's subtractOne() method thrice:
publicationYearCalculator.subtractOne();
publicationYearCalculator.subtractOne();
publicationYearCalculator.subtractOne();
// Similar to the addOne() invocation, the invocations above
// will return "undefined" because we programmed subtractOne()
// to invoke updatePublicationYear() only rather than return
// the invocation's result.
// Invoke publicationYearCalculator's defaultYear() method:
publicationYearCalculator.defaultYear();
// The invocation above will return: 2021
The defaultYear()
method remembers the publicationYear
's updated state because:
- JavaScript also stored all the data
updatePublicationYear()
referenced while storingupdatePublicationYear
's invocator inside thepublicationYearCalculator
variable. (In the instance above,updatePublicationYear
referencedpublicationYear
). -
publicationYearCalculator
's copy ofpublicationYear
remains accessible even after the computer closesupdatePublicationYear()
's execution. - The calculation happened on
publicationYearCalculator
's copy ofpublicationYear
—not on the one inside the anonymous function.
Note the following:
- The
publicationYearCalculator
variable's function is an Immediately Invoked Function Expression (IIFE). - Developers use the term "module pattern" to reference the neat organization of private and public features inside an object (as we've done in the
publicationYearCalculator
's IIFE above). -
publicationYearCalculator
's IIFE contains two private and three public items.-
publicationYear
andupdatePublicationYear()
are the private features. -
addOne()
,substractOne()
, anddefaultYear()
are the public items because they are part of the returned object.
-
- Although private items are inaccessible outside their lexical scope, you can access them through the public closures—because they have access to in-memory copies of their referenced features even after JavaScript has closed the IIFE's execution.
-
updatePublicationYear()
,addOne()
,substractOne()
, anddefaultYear()
are all closures because they are inner functions that reference some of their lexical scope's features.
Tip: Exposing only the code you wish to make globally available is recommended. Private code restricts unwarranted access and accidental conflict with other interfaces/namespaces.
Overview
This article discussed what JavaScript closure is. We also used examples to see how it works.
Top comments (0)