ECMAScript 6 collections, Part 3: WeakMaps

Weakmaps are similar to regular maps in that they map a value to a unique key. That key can later be used to retrieve the value it identifies. Weakmaps are different because the key must be an object and cannot be a primitive value. This may seem like a strange constraint but it’s actually the core of what makes weakmaps different and useful.

A weakmap holds only a weak reference to a key, which means the reference inside of the weakmap doesn’t prevent garbage collection of that object. When the object is destroyed by the garbage collector, the weakmap automatically removes the key-value pair identified by that object. The canonical example for using weakmaps is to create an object related to a particular DOM element. For example, jQuery maintains a cache of objects internally, one for each DOM element that has been referenced. Using a weakmap would allow jQuery to automatically free up memory associated with a DOM element when it is removed from the document.

The ECMAScript 6 WeakMap type is an unordered list of key-value pairs where the key must be a non-null object and the value can be of any type. The interface for WeakMap is very similar to that of Map in that set() and get() are used to add data and retrieve data, respectively:

In this example, one key-value pair is stored. The key is a DOM element used to store a corresponding string value. That value was later retrieved by passing in the DOM element to get(). If the DOM element is then removed from the document and the variable referencing it is set to null, then the data is also removed from the weakmap and the next attempt to retrieve data associated with the DOM element fails.

This example is a little bit misleading because the second call to map.get(element) is using the value of null (which element was set to) rather than a reference to the DOM element. You can’t use null as a key in weakmaps, so this code isn’t really doing a valid lookup. Unfortunately, there is no part of the interface that allows you to query whether or not a reference has been cleared (because the reference no longer exists).

Note: The weakmap set() method will throw an error if you try to use a primitive value as a key. If you want to use a primitive value as a key, then it’s best to use Map instead.

Weakmaps also have has() for determining if a key exists in the map and delete() for removing a key-value pair.

Here, a DOM element is once again used as the key in a weakmap. The has() method is useful for checking to see if a reference is currently being used as a key in the weakmap. Keep in mind that this only works when you have a non-null reference to a key. The key is forcibly removed from the weakmap by using delete(), at which point has() returns false and get() returned undefined.

Browser Support

Both Firefox and Chrome have implemented WeakMap, however, in Chrome you need to manually enable ECMAScript 6 features: go to chrome://flags and enable "Experimental JavaScript Features". Both implementations are complete per the current strawman[1] specification (though the current ECMAScript 6 spec also defines a clear() method).

Uses and Limitations

Weakmaps have a very specific use case in mind, and that is mapping values to objects that might disappear in the future. The ability to free up memory related to these objects is useful for JavaScript libraries that wrap DOM elements with custom objects such as jQuery and YUI. There’ll likely be more use cases discovered once implementations are complete and widespread, but in the short term, don’t feel bad if you can’t figure out a good spot for using weakmaps.

In many cases, a regular map is probably what you want to use. Weakmaps are limited in that they aren’t enumerable and you can’t keep track of how many items are contained within. There also isn’t a way to retrieve a list of all keys. If you need this type of functionality, then you’ll need to use a regular map. If you don’t, and you only intend to use objects as keys, then a weakmap may be the right choice.

References

Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.

Further reading

5 Comments

Correction: WeakMaps *are* in the current draft; see section 15.15. The notable difference from the strawman is that they have a `clear` method.

I don’t like the private data example very much (especially for DOM objects, which are never non-extensible) because symbols are a better fit for that. I tried to give a better use case in my ES6 is Nigh slides (http://es6isnigh.com, slide 46). Namely, a cache of checksums calculated for HTTP responses. But even then this could be accomplished with symbols.

In general it’s hard coming up with a use case for WeakMaps that isn’t covered by symbols; I’m curious if anyone has a good one?

If the DOM element is then removed from the document and the variable referencing it is set to null, then the data is also removed from the weakmap and the next attempt to retrieve data associated with the DOM element fails.

element = null;

value = map.get(element);
console.log(value); // undefined

You seem to imply that data retrieval failed because the element was removed from the weakmap, but in this case isn’t it caused by trying to retrieve a null element?

SaulOfTheJungle on November 7th, 2012 at 1:25 pm

@Saul – yes, which is why I mentioned that right in the post (second paragraph after that example).

1. Every symbol must be initialized, so when a constructor initializes a new object (that defines the use of a symbol) it must also initialize a new symbol. A single WeakMap could theoretically be used by an entire program.