I love JavaScript and my work and after-work coding life is built up around it.
But it is also filled with mysterious concepts and techniques that I don’t know much about.
One such concept are ‘closures’. Recently I was watching a fascinating course on Front-end masters about it and thought it would be a good opportunity for me to document my learning here.
Starting point: Functions that return functions
Before we get to the main crux of closures we need to discuss this.
In JavaScript, functions can return functions.
I have made a quick example below where the outer function returns the inner function.
In this example, by setting up the myNewFunction
constant with the running of the outerFunction()
we are assigning innerFunction
to myNewFunction
.
This means that when you run myNewFunction()
you are running the innerFunction
.
Note that the outerFunction()
is only run once, to initialise myNewFunction
.
Note that if there was any variable within the outer
Okay cool but what’s the relevance to closures?
Let’s find out.
What is a ‘Closure’?
The MDN developer docs are always a good place to start, and it defines closures as:
… the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
It then goes on to say that a closure:
gives you access to an outer function’s scope from an inner function.
In a nutshell, this means that when you write a function (‘Function A’) to return another function (‘Function B’), the state of Function A is retained with Function B.
Let’s revisit Example A and see how closures change our understanding about functions returning functions:
- Closure basically means
innerFunction
can accessouterFunction
‘s scope. - Recall that when we setup
myNewFunction
,outerFunction
is run only once. and normally we would expect that no data within theouterFunction
scope that is not within theinnerFunction
scope would disappear once run. - However, with closure this means that if you setup any data within the
outerFunction
then theinnerFunction
can access this afterouterFunction
is gone and the data is saved and continues to be available to theinnerFunction
.
Some demonstrative examples
Let’s say I want to keep track of how many times my function is run. We definitely cannot do this within a normal function. This is because the scope of the inner function is specific to this function.
// Normal function cannot retain stateconst normalFunction = () => {
let counter = 0;
counter++;}
We can of course create a global state variable:
// set global state
let counter = 0;
const normalFunction = () => {
counter++;
}
But that means that everyone can access this global variable.
Closures allow you to retain state without global state, specific to the function that you have created.
const functionA = () => {
let counter = 0;
const functionB = () => {
return counter++;
}
return functionB;
}
In the example above, function A is the creating function which returns functionB when called.
const myFunction = functionA();
Now, when you run the newly created myFunction
you will retain the scope of functionA
despite the function itself no longer existing after running.
myFunction();myFunction();// Answer is 2, why? because counter from functionA is being added to!
In JavaScript, closures are created every time a function is created, at function creation time.
How is this even possible?
Long story short there is a hidden property [[scope]] attached when creating a function through closure. In our example above, when myFunction
is instantiated with the reference to FunctionB, the hidden property counter
is stored in this hidden property and as myFunction
is called the counter continues to increment cumulatively.
This phenomenon is aptly described by Will Sentance of “JavaScript the Hard Parts” as follows:
“The backpack of live data attached through a hidden property known as [[scope]] which persists when the inner function is returned out.”
When is this useful?
As you can imagine, this hidden scope property or ‘backpack’ can be very powerful. It basically allows programmers to access and interact with the functions and variables in the outer function on a long-running basis.
There are a number of practical applications of closures such as simulating private scope, creating delayed or specifically timed execution, or memoization, among many others.
Conclusion
So there are three takeaway’s from this exploration of Closures:
- They are products of functions returning functions.
- It allows a function returned from the call of another another function to retain the scope of the called function.
- This allows a number of amazing benefits, such as memoization.