JavaScript is a powerful language full of different paradigms that let you write interesting and flexible code. However, at the same time it lacks some of the basic structural features the other languages have. By far one of the biggest anomalies of JavaScript is its inability to natively support encapsulation.
The scoping system was introduced with TypeScript, which is a superset of JavaScript. But unfortunately its not a clear victory yet since, while it does give you a warning, TypeScript code still compiles and runs even when you access the private variables.
Nevertheless, people came up with ways to achieve encapsulation using other features of the language. And in this post I’m going over the most widely used ones.
Easy way
The easiest way to achieve pseudo-encapsulation would be to prefix your private member with a special symbol that indicates the private scope to the client. It is a common convention to use the _
symbol as a prefix. Of course this won’t actually prevent anyone from accessing your private variables so we won’t go in too much detail here.
Factory functions and closures
Simply put, factory functions are functions used to create new instances of the object. Factory functions are often a preferred choice over the direct object creation using new
keyword. The reason is because using factory function gives you the freedom and flexibility to change the object’s instantiation process without client ever being aware of the change.
Factory functions used with closures are often a go-to method for achieving encapsulation because of it’s simplicity. Let’s take a look at the example:
|
|
The code above is a typical example of attaining encapsulation via factory function and closure. While the implementation is pretty straightforward, it does come with a memory usage penalty. The reason is because method zoom
will be recreated for every new instance of the Hedgehog
function. Here’s an identical implementation that uses ES6 syntax:
|
|
As you can see in order for zoom
method to access both speed
and name
we had to put it in the constructor. In some cases this penalty is acceptable and you might be okay using it. Also, to reduce the overheat you can define classes that don’t use private variables outside of the constructor, like we did with jump
method.
Weak maps and namespaces
The memory penalty caused by using factory functions and closures can be avoided by using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap">WeakMap</a>
objects to store the private members. The penalty is avoided since one WeakMap
can be used to store private members of multiple instances of the class. Let’s look at the example to understand the concept better:
|
|
Here’s what’s happening here:
- We wrap the
Hedgehog
class inside of the self-invoking function and return it. This way we ensure that our private data is only created once and our class is instantly available to the client. - Within the self-invoking function but outside of the
Hedgehog
class, we store ourWeakMap
. This map is used for storing private variables for each instance of theHedgehog
class. Each value inside of the map is an object that we callnamespace
. In essence,namespace
is a private object with key-value pairs that is only available to the particular instance of theHedgehog
class that holds a reference to it. - As mentioned before, the overheat is greatly reduced due to the fact that we’re only storing one map for multiple instances of
Hedgehog
class.
There’s a reason why we chose WeakMap
for storage instead of using a regular Map
or a plain JavaScript object:
- Weak maps allow you to use JavaScript objects as keys. Other kinds of dictionary collections will only let you use primitives.
- Memory leaks are avoided because
WeakMap
holds weak references to its items. This allows those items to be garbage collected when they loose all other references. - Weak maps guarantee that objects are only accessible using
get
method.WeakMap
does not have any other methods for accessing its items(looping,<em>Object.keys()</em>
and etc).
Using Symbols (kind of encapsulated)
The last approach is using new ES6 feature – symbols. Symbol is a type of primitive that is guaranteed to be unique. One of its primary purposes is to be used as key for dictionary collections.
The trick here is to use closure to define a private symbol. Let’s look at the example:
|
|
Using this method each instance of the Hedgehog
class has its own instance of the this[speed]
variable which is still accessible to other methods in the class thanks to the speed
symbol defined at the top. Symbols are not accessible when using dot notation and iterating over the collection of objects or using Object.keys()
. And so it does provide some level of encapsulation.
While symbols do provide a sufficient level of encapsulation for the most cases, it can still be breached usingObject.getOwnPropertySymbols
. Although, most of the time the client should get the hint and won’t try to use those private properties.
And that’s it for this post. Hopefully, you learned some new ways to achieve encapsulation in JavaScript.
If you’d like to get more web development, React and TypeScript tips consider
following me on Twitter,
where I share things as I learn them.
Happy coding!