Closure
Closure is not a part of JavaScript specification that is defined by ECMAScript. It is rather a phenomenon common to programming languages that have characteristics of functional programming. Therefore, definitions of Closure vary. After consulting various resources that explain what Closure is in their own terms, I've summed up Closure as follows:
Closure refers to a phenomenon where a function remembers the lexical context of its outer function even after the outer function's execution context has been removed from the call stack.
Now, let's get into details what each word means.
Execution Context
First, a quick recap of 🔗Execution Context would help. Let's take the following code snippet:
const outer = function () {
var a = 1;
var inner = function () {
console.log(++a);
};
inner();
};
// console.log(a) // ReferenceError: a is not defined
outer(); // 2
// console.log(a) // ReferenceError: a is not definedAnd here's rough visualization of how JavaScript engine executes the code by pushing and popping off stack:

Couple of things to note:
- When
inneris invoked and it has to incrementa's value by 1,ais not contained ininner's own environment record. However, via scope chain, as we can see in step 4.,inner's execution context has reference to its outer execution context's environment record, which is that ofouter. As a consequence,inner()can access and modifyouter's function-scoped variableaand the code runs without breaking. - Execution context of
innerandouteris removed from call stack in order as they finishes execution.
Closure
In order to understand Closure in line with execution context, let's imagine the following situation:
What happens when the execution of outer is removed before inner's? Let's change the above code snippet so that outer returns not the returned value of inner but inner function itself.
const outer = function () {
let a = 1
const inner = function () {
return ++a
}
return inner
}
const myInner = outer()
console.log(myInner()) // 2
console.log(myInner()) // 3As banal as the code might appear, its mechanics is not comprehensible with our current knowledge of execution context. Here, the corresponding visual model for the code:

Here, let's fasten on step 3. and 4. that,
- by the time we invoke
myInner, execution context of outer is already removed from call stack and there seems no way formyInnerto access and modifya. - nonetheless, code ran without throwing any error.
The way JavaScript Garbage Collector works prevents code from breaking. Garbage Collector decides what to garbage-collect based on reference count. If a value is not referenced anywhere in the code, its reference count is 0 and should be released from memory In the above code, however, outer returns a function inner, which refers to a . This means that there's a possibility for a to be referenced in the future, even outer is popped off from call stack. inner creates a sort of closure over outer and a is not marked to be garbage-collected.
Here is the revised visual model:

To summarize, we call Closure a situation where a reference to a function-scoped variable from its inner function makes the variable accessible even after function is removed from call stack.
If we add console.dir(inner) to inner before return ++a, we see that inner has hidden property [[Scopes]] , which has Closure scope, apart from Script scope and Global scope.

Best Practice
As we see in Figure3, outer's environment record { a : 1 } persists after the function has executed. While Closure intentionally prevents function-scoped variable from being garbage-collected, it would save us some memory if we release variable from memory when we are done using myInner. We can do so by making it explicit that myInner is not referenced anywhere and, as a consequence, setting a's reference count to 0. In the below code, we've achieved it by adding innerFunc = null to the last line. We replace myInner's value with primitive data type, instead of reference data type. Theoretically, we could have set it to any other primitive data type, such as undefined, but I guess null makes our intention most clear.
const outer = function () {
let a = 1
const inner = function () {
return ++a
}
return inner
}
const myInner = outer()
console.log(myInner()) // 2
console.log(myInner()) // 3
myInner = nullnew Function Syntax
While most functions, when nested, remember lexical context of its closest outer function, there is one exception. Those are functions created by new Function syntax. new Function() takes function body and parameters as string type arguments and generates function out of them. When unable to find variable in its own environment record, they are set to always reference global environment record, not its closest outer environment record.
Let's refactor the above code using new Function().
const outer = function () {
let a = 1
const inner = new Function('a++')
return inner
}
const myInner = outer()
console.log(myInner()) // Uncaught ReferenceErrors: a is not defined at evalAs we see, function-scoped variable a is not visible to inner and therefore error is thrown. What if a were global variable?
let a = 1
const inner = new Function('a++')
inner()
console.log(a) // 2This works totally fine.
Then what is the rationale for preventing new Function() construct from accessing and function-scoped variable?
- First,
new Function()creates function dynamically and therefore allowing its access to a function-scoped variable might expose the source code to malicious code injection. - Also,
new Function()'s reference to local variable is prone to errors. JavaScript code is compressed by minifier when shipped for production. Minifier reduces size of source code by removing spaces, comments and replacing local variables' name with with shorter ones. Therefore, a long, human-centric variable name likeconst numOfComputersInStock = 100can be replaced byconst a = 100, while the string provided tonew Function()(e.g.new Function(console.log(numOfComputersInStock))remains unchanged during compile time. Such behavior could cause errors. It is therefore recommended that local variables should be passed tonew Function()as arguments, rather than being accessed directly.
const logNum = new Function('num', 'console.log(num)')
const numOfComputersInStock = 100
logNum(numOfComputersInStock) // 100window and document.Motivation
Closure is a good example of encapsulation. With help of Closure, we can hide private data from overwriting and only expose to public what we want. By doing so, Closure lets us achieve something similar to access modifiers such as public, private, protected in Class, which makes Closure important not only to functional programming paradigms but also to object-oriented programming.
Let's take the following code for example.
const makeCounter = function () {
let count = 1
const increment = function() {
count++
}
const decrement = function() {
count--
}
return {
get count () {
return count
},
increment,
decrement
}
}
const myCounter = makeCounter();
myCounter.count // 1
myCounter.increment()
myCounter.increment()
myCounter.increment()
myCounter.decrement()
myCounter.getCount() // 3
myCounter.count = -100
myCounter.getCount() // 3To achieve encapsulation, we've
- put in
returnstatement what we want to expose as public members ofmyCountermodule - and kept it to function-scope what we want as private members.
In the above code, though incomplete (e.g. setting value to count directly doesn't throw an error), myCounter exposes count as a read-only getter and requires that the user of myCounter module access and modify myCounter via designated APIs(increment, decrement). Otherwise, direct assignment to count doesn't change the value of count.
Use Case
Module Pattern
Partially Applied Function
Currying
References
- 코어 자바스크립트(Core JavaScript), 재남, 정. 위키북스, 2019. pp. 115-145.
- "The "Variable scope, closure", The Modern JavaScript Tutorial
- "The "new Function" syntax", The Modern JavaScript Tutorial
- "JavaScript Minification II", A List Apart