Coner Murphy Web developer, content creator, and tech entrepreneur building phytype.com. I post about web dev, tech entrepreneurship, and financial freedom on my Twitter and blog.

WeakMap and WeakSet: Understanding JavaScript weak references

5 min read 1431

weakmap And weakset: Understanding JavaScript Weak References

Weak references are not often used in JavaScript due to how the language is designed. However, in certain circumstances, they can prove vital, such as when developers must store additional data and need to automatically manage the memory usage.

In this post, we’ll learn about these weak references in JavaScript and how we can use them by utilizing two objects within the language: WeakMap and WeakSet.

Understanding strong references

Let’s start by taking a look at what a normal, or strong, reference is in JavaScript. In its simplest definition, a strong reference is a reference that keeps an object in memory. Let’s take a look at this in practice to understand what we are talking about:

let dog = { name: "badger" };

const pets = [dog];

dog = null;

console.log(pets); // [{ name: "badger" }]

By creating a variable as an object, we can place the object into an array and remove the reference to the original object from the variable we created by setting its value to null.

Although we can’t access the object via the dog variable anymore because there is a strong reference between the pets array and the object, the object is kept in memory and can be accessed via pets[0].

In other words, the strong reference prevents removing the object from memory via garbage collection.

Understanding weak references

Simply put a weak reference is a reference to an object that doesn’t prevent garbage collection if it is the only reference to the object in the memory.

A normal reference (considered strong), would prevent the garbage collection of an object even if it is the only object referencing it; this isn’t the case for a weak reference.

Let’s take this theory and put it into practice with the previous example of a strong reference and putting it into the context of a weak reference. Ignore the use of WeakMap right now; we will explain this in more depth later in the article. For now, let’s see weak reference behavior:

let pets = new WeakMap();
let dog = { name: "badger" };

pets.set(dog, "okay");
console.log(pets); // WeakMap{ {...} -> "Okay" } <= dog set to the WeakMap

dog = null; // Overwrite the reference to the object
console.log(pets); // WeakMap(0) <= dog has been garbage collected.

By utilizing WeakMap and the weak references that come with it, we can see the differences between the two types of references in action. While the strong (normal) reference to the original dog object still exists, the dog object persists in the WeakMap, and we can access it with no issues.

We made a custom demo for .
No really. Click here to check it out.

But, when we overwrite the reference to the original dog object by reassigning the variable to null, the only reference to the original object in memory is the weak reference coming from the WeakMap we created.

Because it’s a weak reference, it won’t prevent garbage collection from occurring. This means when the JavaScript engine runs a garbage collection process again, the dog object will be removed from memory and from the WeakMap we assigned it to.

The key difference to note is that a strong reference prevents an object from garbage collection while a weak one will not.

By default, JavaScript uses strong references for all of its references and the only way to use weak references is to use either a WeakMap or a WeakSet.

What is garbage collection?

While garbage collection is a detailed and complicated subject, it is important to understand when discussing references.

Garage collection is an automated process controlled by the JavaScript engine. When a value is reachable, it is guaranteed to be stored in memory and not garbage collected, and there are two ways a value is considered reachable.

This first is that they are part of the base set of reachable values like global variables, the current executing function and its local variables/parameters, and more internal values.

The other is reaching any value from the root by reference or a chain of references. For instance, imagine we create an object in a global variable; this is reachable by the global space, thus considered reachable.

Now, if we create another object and reference it off the global object we created, it is also reachable because it’s referenced via the global object.

However, if we remove the global object by setting it to null, suddenly the one we could reach by reference isn’t reachable, so it would be garbage collected.

This is specifically referencing strong references because they are the default in JavaScript. But, the same does apply to weak references, the only exception being if the only reference to an object is weak, it does not prevent garbage collection, and the object is removed.

That is a high-level overview of how garbage collection works; essentially, if something isn’t reachable, it is removed from memory so the memory can be used in other locations.

Sets vs. WeakSets

Per MDN, “Set objects are collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Set‘s collection.”

Simply put, a Set is like an array that can only contain unique values but we can still iterate through it like an array using methods like for loops and .forEach.

Similar to a Set, WeakSet is a collection of objects that are unique from each other but differs because WeakSet can only store objects and cannot contain arbitrary values of any type like strings or numbers.

Ultimately, as the name suggests, WeakSets are indeed weak, meaning they use weak references.

It is also worth noting an interesting side effect of using weak references is that WeakSet is not enumerable. This means there is no way to loop over the items contained within it because there is no list of current objects stored in the collection; they are weakly referenced and may be removed at any point.

Here is an example of WeakSet in use and the methods we can call on it:

const pets = new WeakSet();
const cat = {name: "fluffy"};
const dog = {name: "badger"};

pets.add(cat);
pets.add(dog);

pets.has(cat);    // true
pets.has(dog);    // true

pets.delete(cat); // removes cat from the set
pets.has(cat);    // false, cat has been removed
pets.has(dog);    // true, dog is retained

Maps vs. WeakMap

According to MDN, “The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.”

This means a Map is like an object where we can store key-value pairs and access the values contained within the Map through the key. Unlike a standard object in JavaScript, however, we must use the .get() method to access the values.

In comparison to a Map, a WeakMap is very much the same but the references it holds are weak references, meaning it won’t prevent garbage collection from removing values it references if they are not strongly referenced elsewhere.

Also, WeakMap has the same side effect of not being enumerable due to the weak references.

Finally, we must use objects as the keys, but the values can be any arbitrary value like a string or number. Here is an example of WeakMaps used and the methods we can use on it:

const wm1 = new WeakMap();
const wm2 = new WeakMap();

const obj1 = {};
const obj2 = window;

wm1.set(obj1, 100);
wm1.set(obj2, 'Hello');
wm2.set(obj1, obj2); // You can set the value to be anything including an object or function
wm2.set(obj2, undefined); // Or, undefined
wm1.set(wm2, wm1); // Or, even a WeakMap itself

wm1.get(obj1); // 100

wm1.has(obj1); // true
wm1.delete(obj1);
wm1.has(obj1); // false

Conclusion

Before closing out, let’s consider a potential use case for weak references and the two objects we covered in this article.

If you need to store additional data temporarily and don’t want to worry about cleaning up the memory or how the objects are removed, then using weak references is an absolute lifesaver.

But, it’s not likely that you will regularly need to use WeakMaps, WeakSets, or even weak references regularly in JavaScript.

They are handy to know for the occasional situation and great to have base knowledge about, but in the majority of situations, use normal (strong) references.

I hope you found this article on weak references in JavaScript helpful, if you did, please consider following me over on Twitter, where I post helpful and actionable tips and content on the JavaScript ecosystem.

: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

.
Coner Murphy Web developer, content creator, and tech entrepreneur building phytype.com. I post about web dev, tech entrepreneurship, and financial freedom on my Twitter and blog.

Leave a Reply