Legitimate, memory-efficient privacy with ES6 WeakMaps

By on March 19, 2015 8:06 am

When writing object-oriented source code, you generally only want to expose a very specific API to whomever is using it. In many languages you would control this by marking methods and properties you do not want other developers to use as private. However, if you have been writing JavaScript for any amount of time, you know that there is no private keyword for object properties and methods. The best option we have prior to EcmaScript6 (ES6) to protect data is to hide it inside a closure. But this comes with problems of its own, notably that data must be shared across instances, deliberately removed in order to prevent memory leaks, or new privileged methods must be allocated each time the object is instantiated. Fortunately, ES6 brings us WeakMaps, which we can use to achieve legitimate privacy without the risk of accidentally blocking garbage collection.

WeakMaps look similar to a plain object in that they are collections of key/value pairs, but have some important differences. First, they accept only objects (as opposed to strings) as keys. Second, they cannot be iterated. And most importantly, they do not interfere with garbage collection (hence the name WeakMap), so forgetting to remove objects from WeakMaps will not result in memory leaks.

While the MDN link above describes the WeakMap API in detail, we will still discuss its role in object data privacy. To start, we instantiate a WeakMap and store it in a local variable. Next, we create a new class that stores a reference to itself in the map (with WeakMap#set). Finally, as each instance adds a new object to the map, the other methods on the class’ prototype chain can access the private data with WeakMap#get(this):

(function () {
	var data = new WeakMap();

	function Constructor() {
		// Add a new object for our private data store to the weak map
		data.set(this, Object.create(null));
	}

	Constructor.prototype.privilegedMethod = function () {
		// do something with the object returned by `data.get(this)`
	};
})();

Don’t we already have a solution for real privacy?

In his book JavaScript: The Good Parts, Douglas Crockford details an approach for fully encapsulating private data. In short, a factory method returns an object of privileged methods that have access to private data via a closure:

var factory = function () {
	var privateData = {};

	return {
		privilegedMethod: function () {
			// do something with `privateData`
		}
	};
};

So why would we need a different approach? Since the privileged methods in the above factory are created on the fly, the runtime environment has to allocate memory for them each time a new object is instantiated (even though the functions are identical). WeakMaps allow you to both reuse the same allocated methods and truly encapsulate private data.

Note: The current WeakMap implementation uses a lot of memory (Google Chrome’s Heap Snapshot profiler shows the retained memory size of new WeakMap() as ten times that of Object.create(null)). However, once this is optimized so that the WeakMap keys are stored as values on a hidden storage object, memory usage should be very comparable to that of regular JavaScript objects. Even with this caveat, data-intensive applications will consume less memory with WeakMap storage than with the pre-ES6 method.

Which problems are not solved?

As nice as WeakMaps are, we still do not have a way for subclasses to inherit private data used by parent classes (in other words, we have private data but still not protected data), unless you are able to store all of your subclasses in the same closure as the original map. The same is true of private methods. If we need to refactor our public methods into a collection of private methods, the only way to truly hide them is to do so via a closure. Once again, this would make them unusable by child classes.

When can WeakMaps be used?

As you can see from kangax’s combatability table, currently WeakMaps are not natively supported across many browsers. There are a handful of shims available, such as weak-map which was developed by the Google Caja team that comes close to being genuinely weak (i.e., it leaks very little memory), as well as a work in progress Dojo 2 weak map implementation. Since the existing polyfills inevitably will leak memory, if you decide to use them, it would be smart to deliberately call WeakMap#delete when you no longer need objects. That said, this might be one of the features to look forward to, rather than try to use now (unless, of course, you only have to support the environments that are already packaged with ES6 features).

Conclusion

While perhaps not as appealing or satisfying as actual private or protected keywords for methods and properties, with WeakMaps, JavaScript developers finally have a means of fully encapsulating private data without needing to repeatedly allocate memory for the same methods. If you are developing a small site, then you might not see a reason to be concerned with this additional memory consumption. But once you begin building real-time applications that consume significant amounts of data, then you might find WeakMap to be a very useful addition to your toolkit.

Learning more

If you need assistance with modern JavaScript development best practices, we are able to help via our JavaScript support and web app development services. Contact us for a free consultation to discuss how we can help you and your organization be more productive with JavaScript development.

Comments