WeakSet and WeakMap in JavaScript

The JavaScript engine stores a value in memory while it is “reachable” and possibly usable.

As an example:

let codehubs= { name: "Codehubs" };

// the object can be accessed, john is the reference to it
// overwrite the reference
codehubs= null;

// the object will be removed from memory

Properties of an object or elements of an array or another data structure are usually regarded as readable and maintained in memory while the data structure is in memory.

For example, if we place an object into an array, the object will stay alive as long as the array is alive, even if there are no other references to it.

such as:

let john = { name: "John" };

let array = [ john ];

john = null; // overwrite the reference

// the object previously referenced by john is stored inside the array
// therefore it won't be garbage-collected
// we can get it as array[0]

Similarly to that, if we use an object as the key in a regular map, the object will also remain as long as the map does. It takes up memory and may not be garbage collected.

such as:

let john = { name: "John" };

let map = new Map();
map.set(john, "...");

john = null; // overwrite the reference

// john is stored inside the map,
// we can get it by using map.keys()

In this perspective, WeakMap is fundamentally different. Key object trash collection is not stopped by it.

Let’s look at examples to see what it signifies.

WeakMap

The first distinction between Map and WeakMap is that keys in Map must be objects rather than raw values:

let weakMap = new WeakMap();

let obj = {};

weakMap.set(obj, "ok"); // works fine (object key)

// can't use a string as the key
weakMap.set("test", "Whoops"); // Error, because "test" is not an object

Now, if we use an object as the key in it and there are no other references to that object, it will be immediately deleted from memory (and the map).

let john = { name: "John" };

let weakMap = new WeakMap();
weakMap.set(john, "...");

john = null; // overwrite the reference

// john is removed from memory!

Compare it to the previous Map example. If john only exists as the key of WeakMap, the map will be automatically removed (and memory).

WeakMap does not allow iteration or the methods keys(), values(), and entries(), therefore it cannot return all keys or values.

Only the following techniques are available in WeakMap:

  • weakMap.get(key)
  • weakMap.set(key, value)
  • weakMap.delete(key)
  • weakMap.has(key)

Why such a limitation? That is due to a technical issue. The object john in the code above will automatically be garbage-collected if it has no other references. But in fact, it’s not exactly clear when the cleaning takes place.

That decision is made by the JavaScript engine. It can decide whether to clear the memory right away or delay cleaning until after there have been more deletions. The number of elements in a WeakMap at the moment is therefore technically unknown. It might have been partially or completely cleaned up by the engine. The use of methods that access all keys and values is thus not allowed.

Use case: additional data

WeakMap is mostly used as an extra data storage system.

WeakMap is exactly what is required if we are working with an object that “belongs” to another code, maybe even a third-party library, and would like to keep some related data that should only exist while the object is alive.

We use the object as the key when adding the data to a WeakMap, and when the object is garbage collected, the data will also instantly disappear.

When the object is trash collected, the data is automatically removed since it was added to a WeakMap with the object as the key.

weakMap.set(john, "secret documents");
// if john dies, secret documents will be destroyed automatically

For Example:

For instance, our code keeps track of user visits. The data is stored in a map; a key is a user object, and the value is the number of visitors. We no longer want to record a user’s visit count after they leave (and their object is retrieved for trash).

This is an example of a counting function using Map:

let visitsCountMap = new Map(); // map: user => visits count

// increase the visits count
function countUser(user) {
  let count = visitsCountMap.get(user) || 0;
  visitsCountMap.set(user, count + 1);
}

And here’s another part of the code, that may be used in another file:

let john = { name: "John" };

countUser(john); // count his visits

// later john leaves us
john = null;

Now that it is a key in visitsCountMap, the john object should be garbage collected but instead remains in memory.

When we delete users, we must clear visitsCountMap; else, it will continue to grow in memory. In elaborate architectural designs, this cleaning can become a lengthy process.

By switching to WeakMap instead, we may avoid it:

let visitsCountMap = new WeakMap(); // weakmap: user => visits count

// increase the visits count
function countUser(user) {
  let count = visitsCountMap.get(user) || 0;
  visitsCountMap.set(user, count + 1);
}

We are no longer required to clean visitsCountMap. John object is removed from memory with the data referred by that key from WeakMap once it can no longer be retrieved by any other way than as a key of WeakMap.

Use case: Caching

Caching is another common example. Results from a function can be saved (“cached”) so that all around the same object can reuse them.

We can use Map to achieve it :

// cache.js
let cache = new Map();

// calculate and remember the result
function process(obj) {
  if (!cache.has(obj)) {
    let result = /* calculations of the result for */ obj;

    cache.set(obj, result);
  }

  return cache.get(obj);
}

// Now we use process() in another file:

// main.js
let obj = {/* let's say we have an object */};

let result1 = process(obj); // calculated

// ...later, from another place of the code...
let result2 = process(obj); // remembered result taken from cache

// ...later, when the object is not needed anymore:
obj = null;

alert(cache.size); // 1 (The object is still in cache, taking memory!)

Process(obj) only calculates the result once for many calls with the same object, after which it simply gets it from the cache. The disadvantage is that when an object is no longer required, we must clear the cache.

When WeakMap is used in place of Map, the issue is solved. The cached result will be automatically deleted from memory when the object is garbage collected.

// cache.js
let cache = new WeakMap();

// calculate and remember the result
function process(obj) {
  if (!cache.has(obj)) {
    let result = /* calculate the result for */ obj;

    cache.set(obj, result);
  }

  return cache.get(obj);
}

// main.js
let obj = {/* some object */};

let result1 = process(obj);
let result2 = process(obj);

// ...later, when the object is not needed anymore:
obj = null;

// Can't get cache.size, as it's a WeakMap,
// but it's 0 or soon be 0
// When obj gets garbage collected, cached data will be removed as well

WeakSet

WeakSet acts similarly to these:

  • It is similar to Set, however, WeakSet just allows us to add items
  • When an object can be reached from another location, it is said to exist in the set.
  • Similar to Set, it provides add, has, and delete but not size or keys(), and iterations are not supported.

It also acts as additional storage due to its “weakness”. But for “yes/no” facts, not for random data. WeakSet membership may provide information about the item.

To keep track of visitors who have visited our website, for instance, we can add users to WeakSet:

let visitedSet = new WeakSet();

let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };

visitedSet.add(john); // John visited us
visitedSet.add(pete); // Then Pete
visitedSet.add(john); // John again

// visitedSet has 2 users now

// check if John visited?
alert(visitedSet.has(john)); // true

// check if Mary visited?
alert(visitedSet.has(mary)); // false

john = null;

// visitedSet will be cleaned automatically

The inability to get all current material and the limitation of iterations are WeakMap and WeakSet’s two most obvious disadvantages. Although it might seem inconvenient, this does not stop WeakMap/WeakSet from performing their primary function, which is to serve as “extra” storage for objects that are stored or managed elsewhere.

Submit a Comment

Your email address will not be published. Required fields are marked *

Subscribe

Select Categories