Closure and Encapsulation in JavaScript | Simplified
What is Closure and when can we really use it?
To understand Closure, we need to know about 2 simple concepts in JavaScript: nested functions and returning a function.
Nested Functions
In JavaScript, we can have nested functions. The inner function has access to the outer function’s variables and parameters.
Let’s check the following example:
function greetPerson(name) {
const message = 'Hello';
function displayMessage() {
console.log(`${message} ${name}!`);
}
displayMessage();
}
greetPerson('Alex'); // Hello Alex!
In this example, the inner function displayMessage
can access the variable message
declared in the outer function and the parameter name
of the outer function.
Returning a Function
Besides declaring and invoking an inner function inside the outer function, we can also return the inner function so that it can be invoked from outside.
Let’s see an example of this:
function greetPerson(name) {
const message = 'Hello';
function displayMessage() {
console.log(`${message} ${name}!`);
}
return displayMessage;
}
const displayMessageFunction = greetPerson('Alex');
console.log(typeof(displayMessageFunction)); // function
displayMessageFunction(); // Hello Alex!
In this example, the inner function displayMessage
is returned by the outer function, so the type of displayMessageFunction
variable is function. When we invoke that function, the displayMessage
is being called and so the Hello Alex! message will be displayed.
Closure
After we understand this 2 concepts, the “definition” of closure is pretty simple.
Closure means that an inner function has access to the variables and the parameters of it’s outer function even after the outer function has returned.
Let’s see another example where we can see exactly how closure can be used:
function counterFunction() {
let counter = 0;
function increaseCounter() {
return counter += 1;
}
return increaseCounter;
}
// counter reffers to the increaseCounter returned by the counterFunction
const counter = counterFunction();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
console.log(counter()); // 4
As the definition suggests, the inner function increaseCounter
has access to the counter
variable declared in the outer function counterFunction
.
An inner function does not keep a separate copy of an outer variable, but it reference the outer variable. In other words, the value of the counter
will be changed if you change it using the inner function.
The outer function counterFunction
also returns a reference to the increaseCounter
inner function, so when we call it, the actual value of the counter
variable will be changed. In this way, we can see that the counter
variable has a new incremented value after every counter()
call.
When to use Closure?
We can use closure to achieve encapsulation in JavaScript. This can be accomplished by hiding detail implementation. In this way, we can create private variables and functions.
Let’s check the following example, where we want to encapsulate some data using only functions and objects:
const counter = (function () {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
function increaseCounter() {
changeBy(1);
}
function decreaseCounter() {
changeBy(-1);
}
function decreaseCounter() {
return privateCounter;
}
return { increaseCounter, decreaseCounter, getCounterValue };
})();
console.log(counter.getCounterValue()); // 0
counter.increaseCounter();
counter.increaseCounter();
console.log(counter.getCounterValue()); // 2
counter.decreaseCounter();
console.log(counter.getCounterValue()); // 1
In this example, we only expose increaseCounter
, decreaseCounter
and getCounterByValue
because they are included in the returned object. The privateCounter
variable as well as the changeBy
function cannot be accessed from outside, since they are not part of the returned object.
We can control the counter’s value from outside, only by calling increaseCounter
and decreaseCounter
, and we can get the value by calling the getCounterValue
function, which behaves like a getter.
In this way, we are able to simulate the public
and private
access specifiers and achieve encapsulation.
This is a very powerful concept in software development since it prevents unintended modifications to variables and functions that should remain private.
That’s all!
Thank you for taking the time to read this article on Closure in JavaScript. I hope it has helped you understand better this important concept and how it can be used to achieve encapsulation and create private variables and functions!