Closures in JavaScript in simple terms (and real life examples)
Closures are special thing in the JS world. It’s time to finally understand them.
Many agree that closures are confusing at the beginning, but once you put some time into understanding them you will find them perhaps even intuitive.
There are lot’s of benefits coming from understanding them i.e.:
- In opinion of many software leads, understanding closures is one of these things that separate the junior from the rest.
- There are patterns coming from functional programming i.e. partial application that make use of closures.
- if you are a React girl/guy, the React Hooks rely on closures
- It’s almost a certain question to be asked on JavaScript job interview.
- many other benefits coming from understanding closures
Yet I still see developers working for 5 and more years and ignoring the concept of closures — and they only refresh the knowledge or memorize the answers before a job interview.
Introduction
Developers often learn best by tracking down the examples.
In this article we will:
- cover a bit of the theory regarding JavaScript closures (just enough to be productive and understand what are we writing)
- look into examples in more detail
By the end of the article we will be able to understand what is a closure and how we can benefit from it in the JavaScript world.
What’s a closure in JavaScript
In simple terms, closure gives us access to an outer function scope from an inner function. The closure in JavaScript has three scope chains:
- it has access to its own scope (between it’s own curly brackets)
- it has access to the outer function scope
- it has access to the global scope
In JavaScript, closures are created every time a function is created, at function creation time.
A simple example to begin with
function outer() {
const b = 50;
function inner() {
const a = 100;
console.log(`a is ${a} and b is ${b}, the sum is ${a+b}`);
} return inner;
}// first invocation of outer
const fnFirst = outer();// second invocation of outer
const fnSecond = outer();// what's inside?
// note console.dir() and not console.log() commandconsole.dir(fnFirst);
console.dir(fnSecond);
To understand what is closure let’s walk through this example.
Function outer() invoked for the first time
- We create variable
b
. The scope of variableb
is limited to theouter
function. The value is set to100
. To keep things more readable, I’ve initialised it withconst
. This means, that the value cannot be reassigned. - Then we declare function
inner
— nothing is executed. - Next, we find
return inner
. It turns out thatinner
is afunction
and as such we return the function body.
Please note that the statementreturn inner
does not execute functioninner
. Function is executed only when followed by()
i.e.inner()
. - The content returned by
return inner
is stored intofnFirst
variable.
In this case, it’s the body of functioninner
. - Function
outer()
finishes execution.
All variables within the scope ofouter()
no longer exist.
This is very important to understand, so I will repeat that. :)
Once a function completes its execution, any variables defined inside the function scope no longer exist.
This means that our variable b
declared inside outer
function, exists only when outer
function is being executed.
When we execute the function for the second time, the variables of the function are created again (from scratch).
Let’s track down what happens when we invoke function outer
for the second time (and store it into different variable).
Function outer() invoked for the second time
- We create variable
b
. The scope of variableb
is limited to theouter
function. The value is set to100
. - Then we declare function
inner
so nothing is executed. - Similarly,
return inner
return the body ofinner
— remember the function is not called. - The contents returned by the return statement are stored in
fnSecond
. - Function
outer()
finishes execution.
All variables within the scope ofouter()
no longer exist.
Here comes the important bit (once again but it’s worth repeating).
When the outer()
function is invoked for the second time, the variable b
is created from fresh.
The variable b
cease to exist once outer
finishes executing.
The part that is confusing the devs
So far it looks rather clear.
We have returned our function into two different variables fnFirst
and fnSecond
. These two variables are in fact functions, which we can verify with help of typeof
.
The variables fnFirst
and fnSecond
both have the function body of inner
console.log(fnFirst);// outputƒ inner() {
const a = 100;
console.log(`a is ${a} and b is ${b}, the sum is ${a + b}`);
}
Here comes the tricky part
The console.log
wants to print variable b
.
How we will be able to access the variable b
which was declared in the scope of function outer
?
An example to help us:
// continuedconst fnFirst = outer();
const fnSecond = outer();// for the first time
// output -> a is 100 and b is 50, the sum is 150
fnFirst();// for the second time
// output -> a is 100 and b is 50, the sum is 150
fnFirst();// for the third time
// output -> a is 100 and b is 50, the sum is 150
fnFirst();// for the first time
// output -> a is 100 and b is 50, the sum is 150
fnSecond();
The following happens on the invocation of fnFirst()
.
- Variable
a
is created, it’s value is set to100
. - In the second line, the function tries to add
a + b
.
Variablea
has just been created, that part is clear.
But there is nob
in the function body.
We’ve said earlier, that theb
variable exists only when functionouter
is executed.
We still don’t know how does inner
function knows about the variable b
.
Closures to the rescue
If we would do console.dir(fnFirst)
, we would see something similar in the console:
We can see that there is a Closure that has variable b = 50
That’s our answer!
The inner
function has preserved the value of variable b
and continues to closure it.
The
inner
function preserves the scope chain of the enclosing function at the time the enclosing function was executed.
In simple terms, this means thatinner
function can accessouter
function variables with help from closure.
As we have said in the beginning.
The inner function has access to three scope chains:
- the inner scope i.e.
a
- the outer scope i.e.
b
- the global scope
In JavaScript, closures are created every time a function is created, at function creation time.
More difficult example
In the job interview you will be probably be given a code snippet similar to that one:
Then the interviewer would probably ask you the following questions:
- tell me what this snippet is? (we already know it’s closure)
- walk me through what’s happening? (most important part)
- what will be the output in the console?
To answer that questions, let’s walk through what happens when we invoke fnFirst
for the first time (let’s remember that we are looking at inner
function as this is what has been returned):
fnFirst invoked for the first time
- Variable
c
is created and initialised as20
. - Message is logged into the console with
a,b,c
variables.
We already know that variablesa
andb
are available and come from the closure.
We can think of it likea(first_time)
andb(first_time)
.
Hencea = 10
andb = 100
. - The variable
b
, coming from the closure andc
variable coming frominner
function are incremented. - When the function
fnFirst
completes execution, the variables inside it (c
) cease to exist.
However, the valueb
was preserved to the closure and as such is preserved to the next function call.
fnFirst() invoked for the second, third, and … time
It’s similar as the first invocation, the only difference is step 2 — the value of b
variable is already incremented.
- The variable
c
is created and initiated as20
- The variable
b
, coming from the closure, is incremented by1
hence it’s value is101
. In next function call it will be102
, then103
and so on. b
variable, coming from the closure andc
variable coming frominner
function are yet again incremented.- When the function
fnFirst
completes execution, the variables inside it ( i.e.c
) cease to exist. However, the valueb
was preserved to the closure and as such is preserved to the next function call.
You can also use closures for a pattern called partial application but that is a story for another article.
Summary
Closures are initially difficult to understand especially when introduced with advanced jargon definitions.
If that’s the case, we can try to ignore the jargon initially, go through some examples, understand them, and then come back to the jargon.
With a bit of practice you will probably come to realise that closure is in fact intuitive, powerful tool for us.
PS
There is no escape from closure s— you still have to understand it on a job interview (or at an exam).