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 it’s 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 methods 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 WeakMap 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.

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 our WeakMap . This map is used for storing private variables for each instance of the Hedgehog class. Each value inside of the map is an object that we call namespace . In essence, namespace is a private object with key-value pairs that is only available to the particular instance of the Hedgehog class that holds a reference to it.

class, we store our . This map is used for storing private variables for each instance of the class. Each value inside of the map is an object that we call . In essence, is a private object with key-value pairs that is only available to the particular instance of the 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.

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, Object.keys() 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.