WeakMap and WeakSet

In chapter Garbage Collection, it was stated that the JavaScript engine can store a value in memory once it is reachable.

Here is an example:

let js = {
  name: "Javascript"
}; 
// object can be accessed, js is the reference to it
// rewrite the reference
js = null;
// the object will be deleted from memory
console.log(js);

As a rule, properties or elements of data structures such as an object or an array are reachable and kept in memory once that data structure is in memory. For example, after putting an object into an array, it will exist as long as the array exists.

Here is an example:

let js = {
  name: "Javascript"
};
let array = [js];
js = null; // rewrite the reference
// js is stored in an array, so it will not garbage-collected
// it can be received as an array[0]
console.log(js);

Correspondingly, applying an object like the key in a regular Map, it will exist as long as the map exists.

An example will look as follows:

let js = {
  name: "Javascript"
};
let map = new Map();
map.set(js, "...");
js = null; // rewriting the reference
// js is on the map,
// it can be received by using map.keys()
console.log(js);

Further, we are going to cover WeakMap, which is completely different and doesn’t restrain from garbage-collection of the key objects.

WeakMap

The primary difference between Map and WeakMap is that the WeakMap key can’t be primitive values. They must be objects, like in the example below:

let weakMap = new WeakMap();
let obj = {};
 
key1 = weakMap.set(obj, "ok"); // works fine, object key
console.log(key1);
// can't use string as key
key2 = weakMap.set("test", "Oops"); // Error because the “test” is not an object
console.log(key2);

Iteration and keys(), values(), entries() methods are not supported by WeakMap.

The methods, supported by WeakMap are the following: weakMap.get(key), weakMap.set(key, value), weakMap.has(key), and weakMap.delete(key).

weakMap.delete(key)

Additional data storage is the primary area application for WeakMap. WeakMap is especially useful for storing data associated with a third-party library. For example, consider putting the data into a WeakMap, with an object as the key. Once the object is garbage collected, the data will also automatically vanish as shown below:

let weakMap = new WeakMap();
let obj = {
  name: "test"
};
key = weakMap.set(obj, "test docs");
// if obj disappears, test docs will be automatically destroyed

Now, imagine having a code that maintains a visit count for the users. The information is kept inside a map, with a user object as a key and the visit count as the value.

After a user leaves, you intend to stop storing their visit count.

First, let’s see an example of counting function with Map. It will look as follows:

// visitsCount.js
let visitsCountMap = new Map(); // map: book => visits count
// increase the visits count
function countBook(book) {
  let count = visitsCountMap.get(book) || 0;
  visitsCountMap.set(book, count + 1);
  console.log(count);
}
 
// main.js
let js = {
  name: "Javascript"
};
countBook(js); // count his visits
countBook(js); // count his visits
// later js leaves us
js = null;
console.log(js);

So, while removing the users, it is needed to clean visitsCountMap. In another way, it will be stored in memory indefinitely.

However, cleaning like this might become an annoying task sometimes. If you want to avoid it, you can apply to WeakMap. Here is an example of using WeakMap instead of that kind of cleaning:

// visitsCount.js
let visitsCountMap = new WeakMap(); // weakmap: book => visits count
// increase the visits count
function countBook(book) {
  let count = visitsCountMap.get(book) || 0;
  visitsCountMap.set(book, count + 1);
  console.log(count);
}
 
// main.js
let js = {
  name: "Javascript"
};
countBook(js); // count his visits
countBook(js); // count his visits
// later js leaves us
js = null;
console.log(js);

Now, there is no necessity to clean visitsCountMap.

About Caching

Caching happens when a function result must be remembered (cached) to reuse it later while calling on the same object.

Map can be used for storing results as follows:

// cache.js
let cache = new Map();
// calculate and remember the result
function process(myObj) {
  if (!cache.has(myObj)) {
    let res = /* result calculations for */ myObj;
    cache.set(myObj, res);
  }
  return cache.get(myObj);
}
 
// Now we use process() in another file:
// main.js
let myObj = { /* let's say we have an object */ };
let res1 = process(myObj); // calculated
// later from another place in the code
 
let res2 = process(myObj); // the result taken from the cache is remembered
  // later when an object is no longer needed:
myObj = null;
console.log(cache.size); // 1

In case of calling process(obj) with the same object multiple times, it will calculate the result only the first time. Afterward, it will take the information from the cache.

The only disadvantage of caching is that you need to clean the cache once you don’t need the object anymore.

Replacing Map with WeakMap will resolve the problem. The cached information will be deleted from memory automatically once the object has garbage collected.

To be more precise, let’s consider the example below:

// cache.js
let cache = new WeakMap();
// calculate and remember the result
function process(obj) {
  if (!cache.has(obj)) {
    let res = /* calculate the result for */ obj;
    cache.set(obj, res);
  }
  return cache.get(obj);
}
 
// main.js
let obj = { /* some object */ };
let res1 = process(obj);
let res2 = process(obj);
// later, when object is no longer needed:
obj = null;
// Can not get cache.size because it is  WeakMap,
// but it is 0 or soon will be 0
//Once object gets garbage collected, the cached data will also be cleaned

WeakSet

WeakSet is considered equivalent to Set. Yet, only objects and not primitives may be added to WeakSet.

An object is located in the set while it is reachable in another place.

WeakSet also supports has, add, and delete. But no iterations or size and keys()are supported.

Also, it can serve as additional storage for data, but not for arbitrary data. Here is an example of adding languages to WeakSet for keeping track of the ones that created the site:

let createdSet = new WeakSet();
 
let html = {
  name: "Html"
};
let php = {
  name: "php"
};
let js = {
  name: "Javascript"
};
 
createdSet.add(html); // Html created us
createdSet.add(js); // Then Javascript
createdSet.add(html); // Html again
 
// createdSet has 2 languages now
// check if html created?
console.log(createdSet.has(html)); // true
// check if php created?
console.log(createdSet.has(php)); // false
html = null;
// createdSet will be cleaned automatically

There is a significant limitation in WeakMap and WeakSet: there are no iterations. Neither there is an ability to receive all the current content.

Summary

In this chapter, we covered WeakMap and WeakSet.

To sum up, we can state that WeakMap is considered a Map-like collection, allowing merely objects to be keys. It deletes them along with associated value when they become inaccessible.

WeakSet is considered a Set-like collection, storing merely objects and deleting them when they become inaccessible.

Both collections don’t support properties and methods not referring to all the keys or the count of them. They allow merely individual operations.




Do you find this helpful?

Related articles