Working with WeakMap in Modern JavaScript

Updated: May 14, 2023 By: Khue Post a comment

Overview

What is a WeakMap?

A WeakMap is a collection of key/value pairs whose keys must be objects, with values of any arbitrary JavaScript type, and which does not create strong references to its keys. That means an object’s presence as a key in a WeakMap does not prevent the object from being garbage collected. Once an object used as a key has been collected, its corresponding values in any WeakMap become candidates for garbage collection as well.

Just imagine you have a WeakMap that remembers the names of your friends. You can use your friends as keys and their names as values in the WeakMap. But if you lose contact with a friend and you don’t have any other way to reach them, then the WeakMap will forget their name too. That way, you don’t waste memory on things that you don’t need anymore.

Why do we need WeakMap?

A WeakMap can help us reduce memory leaks. It is very useful when you want to keep some extra information for objects that may not last long. For example, if you have a website that shows some images, and you want to keep track of how many times each image was clicked, you can use a WeakMap to store that information. But if an image is removed from the website, the WeakMap will also remove the information about it. That way, you don’t have to worry about cleaning up the data yourself.

WeakMap vs regular Map: The difference

A WeakMap is different from a regular Map because a regular Map will remember everything you put in it, even if you don’t need it anymore. A regular Map will also let you use any kind of value as a key, not just objects.

A WeakMap does not support iteration or methods like keys(), values(), entries(), and it does not have a size property.

Creating a WeakMap

There are two main ways to create a WeakMap in modern JavaScript.

Using the WeakMap() constructor

You can create a non-empty WeakMap by passing an iterable object that contains key-value pairs as an argument to the WeakMap() constructor.

Example:

// create an iterable object that contains key-value pairs
let iterable = [
  [{}, 'foo'],
  [{}, 'bar'],
];

// create a WeakMap with the iterable object
let weakMap = new WeakMap(iterable);

// get the values for the keys
console.log(weakMap.get(iterable[0][0])); // "foo"
console.log(weakMap.get(iterable[1][0])); // "bar"

Creating an empty WeakMap then adding key-value pairs later

You’re free to initialize an empty WeakMap and then add new key-value pairs later as needed.

Example:

// create an empty WeakMap
let weakMap = new WeakMap();

// create some objects to use as keys
let obj1 = {};
let obj2 = {};

// set some values for the keys
weakMap.set(obj1, 'hello');
weakMap.set(obj2, 'world');

// get the values for the keys
console.log(weakMap.get(obj1)); // "hello"
console.log(weakMap.get(obj2)); // "world"

WeakMap methods

There are only four methods that can be called on a WeakMap:

  • set(key, value): Sets the value for the key in the WeakMap object. Returns the WeakMap object.
  • get(key): Returns the value associated with the key, or undefined if there is none.
  • delete(key): Removes any value associated with the key. Returns true if an element in the WeakMap object existed and has been removed, or false if the element does not exist.
  • has(key): Returns a boolean asserting whether a value has been associated with the key in the WeakMap object or not.

A WeakMap does not have any built-in, inherited properties that can be accessed from it. It does not have a size property like a regular Map, because it does not keep track of how many elements are in it1. It also does not have any properties that allow iteration or enumeration of its keys or values, such as keys(), values(), entries(), or forEach(). This is because a WeakMap does not allow observing the liveness of its keys, which may be garbage collected at any time.

Real-world example

At this point, you might get bored with words (most developers think words are less interesting than code, and so do I). Let’s finish this article with a real-world example that demonstrates how useful a WeakMap can be.

In this example, we will keep track of how many times a button on a web page has been clicked by a user. We will use a WeakMap to store the button object as the key, and the click count as the value. By doing so, we can avoid memory leaks in case the button object is removed from the DOM and no longer needed.

Example screenshot:

The complete code:

<html>

<head>
  <title>Sling Academy</title>
</head>

<body>
  <button id="my-button">I'm a button</button>

  <script>
    // get a reference to the button
    let myButton = document.getElementById("my-button");

    // initialize the WeakMap and store the click count as 0
    let weakMap = new WeakMap();
    weakMap.set(myButton, 0);

    // on every click, increment the click count in the WeakMap
    myButton.addEventListener("click", function () {
      let value = weakMap.get(myButton);
      value++;
      weakMap.set(myButton, value);
      console.log(`Button clicked ${value} times`);
    });

  </script>
</body>

</html>

That’s it. Happy coding!