WeakMap, WeakSet & WeakRef: Advanced Memory Management
In JavaScript, standard Map and Set objects hold strong references to their keys and values. As long as a key is present in a Map, the object representing that key cannot be garbage-collected, even if there are no other references to it in the application. This can lead to memory leaks in certain patterns.
To solve this, ES6 introduced WeakMap and WeakSet, and later ES2021 introduced WeakRef and FinalizationRegistry.
WeakMap
A WeakMap is a collection of key/value pairs where the keys must be objects (primitives are not allowed) and the references to those key objects are weak.
If there are no other references to a key object outside of the WeakMap, the garbage collector is free to destroy the object and automatically remove the corresponding entry from the WeakMap.
Characteristics of WeakMap:
- Keys must be objects.
- It is not iterable (
keys(),values(),entries(),forEachdo not exist). Since the garbage collector can remove items at any time unpredictably, the size and content of aWeakMapcannot be observed. - It does not have a
sizeproperty.
Use Case 1: Associating Data with DOM Elements
If you want to keep track of click counts or state for DOM elements, using a Map will prevent the element from being garbage collected if it gets removed from the DOM. A WeakMap perfectly solves this.
const elementData = new WeakMap();
const button = document.getElementById('my-button');
elementData.set(button, { clicks: 0 });
// Later, if the button is removed from the DOM and all references to it are lost:
// button.remove();
// button = null;
// The entry in the WeakMap will automatically be garbage collected!
Use Case 2: Private Class Data
Before private class fields (#privateField), WeakMap was the standard way to implement true privacy in JavaScript classes without causing memory leaks.
const privateData = new WeakMap();
class User {
constructor(name, age) {
privateData.set(this, { age });
this.name = name;
}
getAge() {
return privateData.get(this).age;
}
}
WeakSet
A WeakSet is similar to a Set, but it only stores objects, and the references to those objects are weak. Like WeakMap, it is not iterable and has no size property.
Use Case: Tracking if an object has been processed or belongs to a certain group without preventing garbage collection.
const visitedObjects = new WeakSet();
function processObject(obj) {
if (visitedObjects.has(obj)) return; // Already processed
// Process...
visitedObjects.add(obj);
}
WeakRef & FinalizationRegistry (ES2021)
Sometimes you want to hold a weak reference to an object to read it if it's still alive, but without preventing garbage collection. This is where WeakRef comes in.
let target = { data: "large object" };
const weakRef = new WeakRef(target);
target = null; // Remove strong reference
// Sometime later...
const dereferencedTarget = weakRef.deref();
if (dereferencedTarget) {
console.log(dereferencedTarget.data); // Still in memory
} else {
console.log("Object was garbage collected");
}
FinalizationRegistry allows you to register a callback to be invoked after an object has been garbage collected.
const registry = new FinalizationRegistry((heldValue) => {
console.log(`${heldValue} was garbage collected!`);
});
let user = { name: "John" };
registry.register(user, "User Object");
user = null; // When GC runs, the callback will log: "User Object was garbage collected!"
Warning: The TC39 committee advises against relying heavily on WeakRef and FinalizationRegistry for core application logic, as garbage collection timing is highly unpredictable across different JavaScript engines.