Closures, Encapsulation, and Information Hiding
2016. 6. 27.

Closures and Objects
I remember struggling to understand closures when I first encountered them. Properly grasping closures requires a solid understanding of JavaScript's core concepts. Personally, I once summarized closures as entities that delay the termination of their scope until they disappear themselves—essentially, they terminate their scope when they vanish. This is why they are aptly named Closure. While this explanation is simplistic, it isn't entirely incorrect. For a deeper dive into closures, you can refer to this article, which provides an excellent explanation. For a quick and concise understanding, I recommend reading the introductory section of MDN's closure documentation.
Closures are functions that refer to independent (free) variables (variables that are used locally but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.
In essence, closures are functions that remember the environment in which they were created. This environment refers to the scope chain itself, meaning variables or functions accessible through the scope chain persist even when the scope should be released. Such scopes can be used to implement encapsulation and information hiding—properties typically associated with objects. When a closure is created, it bundles state and behavior, making it another way to create objects (depending on how you define objects). Unlike objects that access context through this
, closures access context through their scope.
This article explores several ways to leverage closures.
Implementing a Counter
Counters are often used as examples to explain closures because they are straightforward yet effective for this purpose. Let's use a counter implementation to illustrate the concept.
Counter Using Regular Objects
A counter can be implemented using regular objects as shown below:
var counter = {
_count: 0,
count: function() {
return this._count += 1;
}
};
console.log(counter.count()); // 1
console.log(counter.count()); // 2
Here, an object literal is used to create an object with an _count
attribute for counting numbers. While this approach is simple, it only allows for one counter. To create multiple counters, we can use a constructor function:
function Counter() {
this._count = 0;
}
Counter.prototype.count = function() {
return this._count += 1;
};
var counter = new Counter();
var counter2 = new Counter();
console.log(counter.count()); // 1
console.log(counter.count()); // 2
console.log(counter2.count()); // 1
This approach uses this
to access context within regular objects. The key components here are:
_count
: A member variable for storing numbers.count()
: A member function for incrementing_count
.
Counter Using Closures
Now let's implement the same functionality using closures:
var counter = (function() {
var _count = 0;
return function() {
return _count += 1;
};
})();
console.log(counter());
console.log(counter());
Although the code differs slightly, the resulting object behaves similarly. Instead of accessing _count
via this
, it uses scope. The components remain the same—a variable _count
for storing numbers and a function for incrementing them. This approach aligns with encapsulation and information hiding principles.
To create multiple counters, we can use a factory function:
function counterFactory() {
var _count = 0;
return function() {
_count += 1;
return _count;
};
}
var counter = counterFactory();
var counter2 = counterFactory();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter2()); // 1
While there may be concerns about performance due to the number of generated functions, the focus here is on implementation rather than optimization.
Counters created using closures do not require this
for context access. They can be attached to any object or used as event listeners while maintaining consistent context:
var counter = counterFactory();
var app = { counter: counter };
var app2 = { counter: counter };
console.log(app.counter()); // 1
console.log(app.counter()); // 2
console.log(app2.counter()); // 3
From an information-hiding perspective, closures excel because variables like _count
cannot be accessed externally.
Counter Using Currying
Let's modify our factory to allow custom increment values using currying:
function counterFactoryMaker(incValue) {
return function factory(initValue) {
var _count = initValue;
return function counter() {
return _count += incValue;
};
};
}
var counterFactory = counterFactoryMaker(2);
var counter = counterFactory(0);
var counter2 = counterFactory(1);
console.log(counter()); // 2
console.log(counter()); // 4
console.log(counter2()); // 3
console.log(counter2()); // 5
Here, currying enables us to create counters with different increment values.
Combining Objects and Closures
In JavaScript development, combining objects and closures is often necessary. For example, event listeners benefit from closures while objects provide structure. By combining both approaches, we can mimic access modifiers not natively supported by JavaScript:
function counterFactory2() {
var _count = 0;
function count(value) {
_count = value || _count;
return _count;
}
return {
count: count,
inc: function() { return count(count() + 1); },
dec: function() { return count(count() - 1); }
};
}
var counter = counterFactory2();
console.log(counter.inc());
console.log(counter.inc());
console.log(counter.dec());
This implementation follows the Module Pattern, exposing only necessary methods while keeping _count
private.
We can extend such counters by overriding methods:
function counterFactory2Ext() {
var counter = counterFactory2();
var count = counter.count;
counter.inc = function() { return count(count() + 2); };
return counter;
}
var counterExt = counterFactory2Ext();
console.log(counterExt.inc()); // 2
console.log(counterExt.inc()); // 4
console.log(counterExt.inc()); // 6
This combines a private closure variable with methods in an object.
Conclusion
While calling closures "objects" might be an oversimplification, understanding them as tools for encapsulation and information hiding makes them more approachable. Combining closures with objects allows developers to leverage JavaScript's strengths in implementing object-oriented programming concepts. In future articles, we will explore closures from a memory performance perspective.
References
with kakaopay
Recommend Post
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.