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 WeakSet and WeakMap exist.

Every Object in 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 Object as 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 value they 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 Object actually is.

WeakSet

A set is 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 Request object is created it's being added to the requests set. If somebody calls the makeRequest() method it will be able to tell if that invocation came from an instance of a Request (since this can only be in requests if 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 Request which may introduce security or inconsistent state issues.

Couldn't we just do the same thing if requests was 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 WeakMap is similar to an object where the keys in that object are actually a WeakSet. Another way to look at it is a WeakMap is a WeakSet where 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 WeakMap that uses all values of true would operate just like a WeakSet.

You could use this to attach a legitimate private object 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.