A WeakSet is a special, stripped-down Set for tracking objects. Its one superpower: it does not keep objects alive. When nothing else in the program is using an object, JavaScript can delete it from memory, and it quietly disappears from the WeakSet too.
JavaScript automatically frees memory for objects that nothing refers to anymore. This is called garbage collection:
let user = { name: "Ava" };
user = null; // nothing points to that object now: it can be cleaned up
A regular Set counts as "something pointing to it." An object inside a Set is never cleaned up while the Set exists:
const seen = new Set();
let user = { name: "Ava" };
seen.add(user);
user = null; // the Set still holds it: it stays in memory
A WeakSet holds its objects weakly: it does not count. Once the rest of the program lets go, the object can be collected.
"Can be collected" does not mean "is collected right away". The engine cleans up whenever it decides to, which might be a moment later or much later. Your code never sees it happen.
const visited = new WeakSet();
const pageA = { url: "/home" };
const pageB = { url: "/about" };
visited.add(pageA);
console.log(visited.has(pageA)); // true
console.log(visited.has(pageB)); // false
visited.delete(pageA);
console.log(visited.has(pageA)); // false
That is the whole toolkit: add, has, and delete.
| Set | WeakSet | |
|---|---|---|
| Can hold | Any value | Objects only |
| Keeps items in memory | Yes | No |
| size | Yes | No |
| Loop over / list contents | Yes | No |
| clear() | Yes | No |
const ws = new WeakSet();
ws.add("hello"); // TypeError: Invalid value used in weak set
const ws = new WeakSet();
console.log(ws.size); // there is no size property
for (const x of ws) {} // TypeError: ws is not iterable
has(obj).
A WeakSet is perfect for marking objects without changing them and without causing memory leaks. For example, making sure each item is only processed once:
const processed = new WeakSet();
function processOrder(order) {
if (processed.has(order)) {
return `Order ${order.id} already done`;
}
processed.add(order);
return `Processing order ${order.id}`;
}
const o = { id: 7 };
console.log(processOrder(o));
console.log(processOrder(o));
When an order object is finished with and discarded, the WeakSet does not hold it back.
A WeakSet can record whether each visitor has been seen, with no extra data:
const visited = new WeakSet();
let john = { name: "John" };
const paul = { name: "Paul" };
function visit(person) {
if (visited.has(person)) return `Welcome back, ${person.name}`;
visited.add(person);
return `Hello, ${person.name}`;
}
console.log(visit(john));
console.log(visit(paul));
console.log(visit(john));
john = null; // John's object can now be cleaned up, WeakSet entry and all
const and then writes John = null. That line throws TypeError: Assignment to constant variable. A variable you plan to clear must be declared with let, as above. (Its comment also calls the WeakSet a WeakMap.)
To count visits instead of just recording them, you need to store a number with each object: that is a WeakMap (JS Map WeakMap).
Which value can be added to a WeakSet?
"hello"42{ id: 1 }nullTypeError: Invalid value used in weak set.What does this log?
const ws = new WeakSet();
ws.add({});
console.log(ws.size);
undefined. WeakSets have no size, because garbage collection could change the count at any moment.Nothing else refers to an object in a WeakSet anymore. When is it removed?
b and check againws.size and a for...of loop on a WeakSet. What happens?markRead(message) and isRead(message) using a WeakSet of message objects