Don’t Forget About WeakMap and WeakSet

When it comes to data collections in JavaScript, most developers immediately think of arrays, objects, Map
, or Set
. However, there are other, less well-known data structures that can be called "tools for special cases" — WeakMap
and WeakSet
.
WeakMap
and WeakSet
are structures designed to work with objects. Their main feature is weak references, which help avoid memory leaks. These structures clean up after themselves automatically when an object used in them becomes unreachable.
However, their use cases are not limited to memory management alone. WeakMap
and WeakSet
enable:
- Storing data that disappears automatically without explicit deletion.
- Creating hidden storage for private data without altering the object itself.
- Automatic removal of temporary data associated with objects, such as caches.
WeakMap
WeakMap
is a collection of key-value pairs where:
- Keys can only be objects.
- Values can be any type of data, from strings to functions.
- Keys are weakly referenced, allowing the garbage collector to remove them if they are no longer used anywhere else.
The main difference from a regular Map
is that WeakMap
takes care of memory management automatically. There’s no need to manually delete data when an object becomes unnecessary — it happens automatically.
WeakMap
has only four methods:
set(key, value)
— adds a key-value pair.get(key)
— returns the value associated with a key.has(key)
— checks for the presence of a key.delete(key)
— removes a key-value pair.
Example usage:
const weakMap = new WeakMap();
let user = { name: "Alice" };
weakMap.set(user, "User's private data");
console.log(weakMap.get(user)); // "User's private data"
user = null;
// After this, the object is automatically removed from WeakMap by the garbage collector.
Application Examples
WeakMap
is ideal for storing private data inside objects without altering their structure.
const privateData = new WeakMap();
class User {
constructor(name) {
this.name = name;
privateData.set(this, { permissions: [] });
}
getPermissions() {
return privateData.get(this).permissions;
}
addPermission(permission) {
privateData.get(this).permissions.push(permission);
}
}
const user = new User("Bob");
user.addPermission("admin");
console.log(user.getPermissions()); // ["admin"]
// If the user object is deleted, its data in privateData will also disappear.
If your code processes objects and you need to store the processing results, WeakMap
allows you to do this without risking memory leaks.
const cache = new WeakMap();
function processData(obj) {
if (cache.has(obj)) {
return cache.get(obj); // Use cached data
}
// Complex calculations
const result = { processed: true };
cache.set(obj, result);
return result;
}
let data = { id: 1 };
console.log(processData(data)); // { processed: true }
console.log(processData(data)); // { processed: true }
data = null; // The cache is automatically cleared.
WeakSet
WeakSet
is a collection of objects that are stored using weak references. As with WeakMap
, if an object becomes unreachable, it is automatically removed from the WeakSet
.
The main purpose of WeakSet
is to track unique objects without the risk of them "sticking" in memory.
WeakSet
has even fewer methods than WeakMap
:
add(value)
— adds an object.has(value)
— checks for the presence of an object.delete(value)
— removes an object.
Example usage:
const weakSet = new WeakSet();
let obj = { id: 1 };
weakSet.add(obj);
console.log(weakSet.has(obj)); // true
obj = null;
// The object is automatically removed from WeakSet.
Application Examples
For instance, you may need to ensure that each object is processed only once:
const processed = new WeakSet();
function process(obj) {
if (processed.has(obj)) {
console.log("Object has already been processed!");
return;
}
processed.add(obj);
console.log("Processing object:", obj);
}
let task = { id: 1 };
process(task); // "Processing object"
process(task); // "Object has already been processed!"
task = null; // WeakSet is automatically cleared.
When working with the DOM, you can use WeakSet
to track elements that have already been assigned event handlers:
const elementsWithHandlers = new WeakSet();
function addClickHandler(element) {
if (!elementsWithHandlers.has(element)) {
elementsWithHandlers.add(element);
element.addEventListener("click", () => console.log("Click!"));
}
}
let div = document.createElement("div");
addClickHandler(div); // Attach handler
addClickHandler(div); // Do nothing, handler is already attached
div = null; // Handlers are automatically removed.
Let me know if you’d like me to elaborate on anything!