JavaScript Closure - What and Why?
I have read and studied the concept of ‘closure’ in JavaScript multiple times, but it has been very difficult to get familiar with it.
This article is for refreshing myself about closures, but hope it could help readers too. This article refers to MDN official documentation and a book called Modern JavaScript Deep Dive published in Korea.
MDN defines a closure as:
the combination of a function and the lexical environment within which that function was declared.
If you are not familiar with what a lexical environment is:
A lexical environment is a component of the execution context where manages identifiers and values binding to the identifiers, and records reference to the outer scope.
In JavaScript, a function’s outer scope, that is, reference value of the “outer lexical environment reference” in its lexical environment is decided based on where (the environment) the function is declared, and it is decided when the function declaration is evaluated and its lexical environment is formed. This is called a lexical scope which means it does not change regardless of where the function was called.
So, what does this mean?
We can call an inner function a closure when it can still refer to the outer function’s variables even when the outer function’s lifetime has completed.
As the earlier definition of a closure states, “the lexical environment within which that function was declared” points to the scope where the function is declared, that is, its outer scope which means the lexical environment of the execution context.
const x = 1;
function outer () {
const x= 10;
const inner = function() {
console.log(x)
}; return inner;
} const innerFunc = outer();
innerFunc(); //returns 10
In the example above, outer
function’s lifetime will be completed once it returns inner
function, however, when we call innerFunc
, we can still access the x
variable declared in the outer function.
this is because the outer scope of the inner
function has already been saved to the inner
function’s environment internal slot when the inner
function was declared. In other words, even when the outer
function has been popped from the execution context stack and its lifetime has completed, its lexical environment is preserved because the inner
function refers to it as the outer lexical environment reference.
Theoretically, all functions are closures. However, generally, we don’t call a function a closure if:
- a function does not refer to any variable in the outer scope.
- a function refers to variables in the outer scope, but its lifetime is shorter than the outer function.
Finally, what can we use a closure for?
We use a closure to safely save or change a state. We can achieve information hiding and allow a specific function to change states through a closure. Let’s take a look at simple examples.
Example 1
let num = 0;const increase = function () {
return ++num;
}console.log(increase()); //return 1
console.log(increase()); //return 2
console.log(increase()); //return 3
The example above will give us the desired result, but it is not a good code because the variable num
can be accessed anywhere throughout the code and its state can change. We want to make the variable modified by a specific function only.
Example 2
const increase = function () {
let num = 0;
return ++num;
}console.log(increase()); //return 1
console.log(increase()); //return 1
console.log(increase()); //return 1
In this example, we have made increase
function the only way to modify variable num
. However, the result is not what we want because the num
gets declared to 0 every time the function is called.
Example 3
const increase = (function () {
let num = 0; return function () {
return ++num;
}
}());console.log(increase()); //return 1
console.log(increase()); //return 2
console.log(increase()); //return 3
In this final example, the immediately-invoked function will return a closure and assigns to increase
which remembers its outer scope that contains variable x
. So, even if the the immediately-invoked function returned and its lifetime has completed, because the returned closure has been assigned to increase
, it can still access to the outer scope’s variable.
Also, because the immediately-invoked function gets called only once, x
will not be re-assigned to its initial value of 0, and because it is a private variable which cannot be referred from the outside, we can achieve safe programming.