The ld procedural code is dying out. Future of JavaScript is divided between object-oriented and functional paradigms. But… What’s finally the best approach to encapsulation?

JavaScript has come a long way from simple scripting language used for animating snow on personal webpages to recognized multiple paradigm language, used by the biggest enterprises to build complex applications.

Object oriented programming wasn’t eagerly embraced by community – prototype based programming, one of JavaScript paradigms, isn’t intuitive for skilled developers switching to it from classical object oriented languages, like Java or C++. As there was no way to declaratively define “class” nor syntax to conveniently call method or constructor from superclass, we can understand their confusion.

First approach to object oriented programming

In order to finalize this tutorial you will need:

This is how we had to define objects in dark ages before ecosystem of custom  framework-specific class helpers exploded. We can see three lines of boilerplate code here – we have to manually change Bar.prototype to correct object and manually apply Foo constructor as mixin.

This code isn’t defensive. What if Foo constructor has side effects? What should happen if constructor is called without “new”? What if we want bar.method to call its “super”?

We wrote 10 lines of boilerplate already and we don’t even check if this == global or other edge cases. Calling super from subclass requires us to explicitly refer to superclass, so changes in class hierarchy (eg. our class used to inherit directly from Component but now it’s going to inherit from Component through it’s subclass, AppletComponent) require careful inspection of all methods. Moreover, Foo.prototype.method.call is just awkward thing to write.

Next try – factory functions

These problems were addressed by a pattern called “factory function”, later popularized by Douglas Crockford. It focuses on avoiding the use of “this” and constructors and favoring composition over inheritance.

For a long time it was the most popular method of object-oriented programming in JS. Mostly declarative, methods are bound to instance, it’s impossible to accidentally share state among instances – and there comes the killer feature – we have private state.

But there is no free lunch, and it has major drawbacks – every function have to be allocated over and over with its enclosing scope, resulting in significantly higher memory footprint. Currently that’s not  such a  big deal, because we have more available RAM and modern JavaScript engines optimize functions with the same source code for lower memory usage. Other disadvantages include no inheritance (specific forms of composition can be used to emulate inheritance in many cases), no exposed methods to be .call-ed, forced duck typing, limited composition.

Many abstractions over object oriented programming

Popular alternative that have risen against factory function were special functions exposed by frameworks and libraries like Backbone’s Model.extend or Prototype.js’ Class.create. We will not go into further details, but these functions are basically bags of helpers to reduce boilerplate code from prototypical inheritance. There is no private state there, as there is no way to define such private state in classical JS .

Yes, JavaScript used to lack a method to define truly private state associated with instances of “class”. Closures exploited by factory function pattern can fully encapsulate private state, but they come with their own limitations, eliminating this pattern from modern web applications.

So how can we encapsulate data and methods? Right now, the most popular way is to use methods prefixed with underscore or two. Private by convention is a techniqueborrowed from Python – you aren’t expected to use such methods (“we’re all consenting adults”) and proper linting combined with code review would work almost as good as static compiler to ensure lack of access to private members.

However, with ECMAScript 2015 (previously known as ES6) we got two better ways to encapsulate our precious internal state.

“Symbol” means (almost) private

The easier of them is to use Symbols. Symbol is a new primitive type in JavaScript. Each symbol is unique (Symbol() !== Symbol()) and can be used as property key in addition to strings. Its main advantage over unguessable strings is invisibility for classic iteration methods like for-in, Object.keys, Object.getOwnPropertyNames.

That’s my method of choice – it’s easy to use, easy to define a way to prevent your code consumers from messing with your class guts[1]. But, what is the catch? Encapsulation isn’t perfect – properties can be retrieved by using new Object method, Object.getOwnPropertySymbols. Some people will say “wait, but these fields aren’t actually private, because you can use reflection to inspect them”. Yes, reflection can expose private fields, in the same way, that it can do in Java, PHP, C# using easily accessible reflection API. Even in C++, language without runtime reflection, it’s possible to access internal members by pointer voodoo.

Though, there is another method to encapsulate our state, that will be appreciated by purists[2].

Ideally private state achievable by weak maps

We can achieve our goal using WeakMap. The basic idea is to weakly link object to other object holding data. To access private field both weak map and class instance are required – but weak map is never leaked from module/closure.

That’s probably most defensive way to implement private state in modern JavaScript.

Private state in Javascript

Conclusions

Those are four ways to encapsulate private state. Each of them has it’s own advantages and disadvantages. For maximal compatibility I would use private by convention, because of it’s minimal dependency – it will work out-of-the-box in all possible browsers, including IE5. My method of choice, preferred for new code, would be Symbols – it’s easy and has only one minor drawback. WeakMap based encapsulation isn’t really fit for that, as it decreases code readability and maintainability.  On the other hand, I would not use function factory because it imposes too many limitations

Notes:
  1. Programmers shouldn’t mess with your guts too, it’s legal only for doctors.
  2. Overwriting WeakMap.prototype.get to leak “this” doesn’t count.

Node.js Developer

Node.js programmer and SQL specialist.