WeakSet and WeakMap in JavaScript ES6

Image for post
Image for post

If you have been playing around with JavaScript ES6 you may have seen one of the new features is the ability to have weak references used in the form of a WeakSet and WeakMap.

If you’re very familiar with JavaScript but haven’t dabbled much with languages where you need to manage your own memory or deal with the shortcomings of a garbage collector these may seem alien to you. Alternatively, if you have worked in a language where you know what these are it might seem even more alien that such a feature would turn up in a scripting language like JavaScript. Hopefully I can put these both of these to rest with this article.

Some Fundamentals

First you must understand the importance of weak references, because they are the reason WeakSetand WeakMapexist.

Every Objectin JavaScript (this includes arrays) is allocated somewhere in memory. This somewhere is a memory address, often called a pointer, which is an integer. When you pass an Objectas a parameter you are not moving the values contained in that Object, but rather you are simply passing the pointer. This is why different parts of your code that have received the same object can affect each other, because they are changing the same physical memory for that object.

Two objects are only equal if they are actually the same physical object (same pointer), it has nothing to do with the internal value of the object. For example:

var a = {};
var b = {};
var c = a;
a == a; // true
a == b; // false
a == c; // true

Even though both objects look like they would be equal in valuethey are not equal in address so the comparison is false. This comparison is extremely cheap because it’s only comparing a pair of integers, no matter how complex either Objectactually is.

WeakSet

A setis a collection where all of the members are unique. The important part here is how it identifies an object to make the set unique. You guessed it… it’s the pointer for the object. Just like we saw above, a set could contain many objects that have the same value but not the same pointer.

A WeakSet works just like a set. The only difference is that it keeps a weak connection to all the members. This means the garbage collector can remove any object in the set when it’s no longer used without the programmer needing to explicitly remove it from the set.

I’m not sure who first wrote it but I’ve seen this example used in many tutorials, so I’m going to steal the idea.

const requests = new WeakSet();class Request {
constructor() {
requests.add(this);
}
makeRequest() {
if(!request.has(this)) {
throw new Error("Invalid access");
}
// Do work...
}
}

It’s not immediately obvious what this code does. Basically every time an Requestobject is created it’s being added to the requestsset. If somebody calls the makeRequest() method it will be able to tell if that invocation came from an instance of a Request (since thiscan only be in requestsif that same object was registered through the constructor) and not somebody trying to get to that method any other way (such as Request.prototype.makeRequest). This could be by mistake or they are trying to masquerade the bound context as something other than a Requestwhich may introduce security or inconsistent state issues.

Couldn’t we just do the same thing if requestswas a normal array and we removed the instance in some clean up method (like close())? The short answer is: Yes. The longer answer is “You shouldn’t” because if they forget to call your close()method the object will never be able to be released and your app will slowly consume memory forever.. Not good if your library is used by someone running a NodeJS server indefinitely. Also, by removing the need for somebody to make that mistake you have one less thing to worry about. :)

WeakMap

A WeakMapis similar to an object where the keys in that object are actually a WeakSet. Another way to look at it is a WeakMapis a WeakSetwhere each item has an attached object that is the value for that key, this value does not need to be unique. A third way to look at it is a WeakMapthat uses all values of true would operate just like a WeakSet.

You could use this to attach a legitimate privateobject against an externally managed object that may disappear, such as privately managed metadata. Extending from the previous example something like:

const requests = new WeakSet();class Request {
constructor() {
requests.set(this, {
created: new Date()
});
}
makeRequest() {
if (requestIsTooOld(this)) {
throw new Error("Try again?");
}
// Do work...
}
}

I realise this is not a good solution to this particular problem but it does illustrate that many libraries may attach and maintain their own metadata onto real external objects without bringing memory leaks into question. At least, in theory.

There is another use for WeakMap which is a side effect of how it allows objects as keys. The ECMAScript standard only permits strings as keys on objects (even though other types of keys may work). WeakMap allows you to use an object, or even a function as a legitimate key in an array. I’ll leave it unto you to come up with a creative and practical reason why you would do this.

Thanks to @bulankou for suggesting I do this article.

Originally published at http://elliot.land on April 16, 2016.

Written by

I’m a data nerd and TDD enthusiast originally from Sydney. Currently working for Uber in New York. My thoughts here are my own. 🤓 elliotchance@gmail.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store