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.
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
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
Weak maps and namespaces
The memory penalty caused by using factory functions and closures can be avoided by using
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
Hedgehogclass 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
Hedgehogclass, we store our
WeakMap. This map is used for storing private variables for each instance of the
Hedgehogclass. Each value inside of the map is an object that we call
namespace. In essence,
namespaceis a private object with key-value pairs that is only available to the particular instance of the
Hedgehogclass 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
There’s a reason why we chose
WeakMap for storage instead of using a regular
- Memory leaks are avoided because
WeakMapholds 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
WeakMapdoes not have any other methods for accessing its items(looping,
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 using
Object.getOwnPropertySymbols. Although, most of the time the client should get the hint and won’t try to use those private properties.