WeakSet
Collects objects for membership tests without preventing them from being garbage collected.
new WeakSet()
new WeakSet(iterable)- Added in JavaScript ES2015
Parameters
| Parameter | Type | Description |
|---|---|---|
$iterableoptional | Iterable<object | symbol>= undefined | An iterable whose elements are added to the new WeakSet. Every element must be an object or a non-registered symbol. null or undefined is treated as no argument. |
Return value
WeakSetA new WeakSet object holding weakly-referenced values. It can only be constructed with new; calling it as a function throws.
Errors
add() throws a TypeError if the value is not an object or a non-registered symbol; has() and delete() return false instead of throwing. Calling WeakSet without new throws a TypeError.
Changelog
- PHP ES2015Introduced. Baseline widely available since September 2015.
- PHP Later revisionNon-registered symbols became legal values alongside objects; symbols from the global registry, i.e. Symbol.for("x"), remain rejected.
A WeakSet answers exactly one question — "have I seen this object?" — and answers it without keeping the object alive. It is the membership-only sibling of WeakMap: add, has, delete, nothing else. No size, no iteration, no clear(), because exposing the contents would make collection timing observable.
When to reach for it
Use it to tag objects you do not own. Marking nodes as visited while walking a graph that may contain cycles, recording which DOM-like objects have already had a listener attached, remembering which instances passed an expensive validation, or brand-checking that an object really came from your factory rather than being a look-alike. The alternative — setting a _visited property — mutates objects that may not be yours and shows up in serialization and key enumeration.
What trips people up
Membership is by reference. Two structurally identical objects are two different values, so a fresh literal never matches an added one. Primitives are rejected: add(1) or add("x") throws a TypeError. And because you cannot enumerate it, a WeakSet is useless for reporting what is in it — reach for a Set when you need a list, and accept that the list retains its members.
Example
When to use it
- Mark nodes as visited while traversing a possibly cyclic object graph
- Ensure an expensive initialization runs only once per object
- Brand-check that an instance really came from your own constructor
- Track which objects have already been sent over the wire
Frequently asked questions
What is the difference between WeakSet and Set?
A WeakSet holds its members weakly, so it never keeps them alive, and in exchange it cannot be iterated, counted, or cleared and only accepts objects and non-registered symbols. A Set accepts any value and is fully enumerable, but retains everything you put in it.
How do I get the size of a WeakSet?
You cannot — there is no size property and no iteration, because that would expose garbage collection timing. If you need a count, maintain a parallel Set or counter and accept the strong references that come with it.
Why does has() return false for an identical object?
Membership is by reference, using SameValueZero, so two object literals with the same properties are two distinct values. Keep and reuse the original reference, or key on a primitive id in a regular Set instead.
More examples
Walking a cyclic graph safely
Without the WeakSet the loop would run forever, since a and b point at each other. Each node is recorded on first sight, so the traversal stops after two steps and nothing is written onto the nodes themselves.
const a = { name: "a" };
const b = { name: "b", next: a };
a.next = b; // cycle
const visited = new WeakSet();
const order = [];
for (let n = a; n && !visited.has(n); n = n.next) {
visited.add(n);
order.push(n.name);
}
console.log(order); // ["a", "b"]Run-once initialization
The second call short-circuits because the widget is already in the set. The counter confirms the body ran once, and no flag property was added to the widget to achieve it.
const initialized = new WeakSet();
let runs = 0;
function setup(target) {
if (initialized.has(target)) return "skipped";
initialized.add(target);
runs++;
return "initialized";
}
const widget = {};
console.log(setup(widget), setup(widget)); // initialized skipped
console.log(runs); // 1Brand-checking instances
Only objects that actually passed through the constructor are in the set, so a hand-made look-alike with the same shape fails the check. has() never throws on a primitive, but the typeof guard keeps the intent explicit.
const real = new WeakSet();
class Token {
constructor(value) {
this.value = value;
real.add(this);
}
static isToken(x) {
return typeof x === "object" && x !== null && real.has(x);
}
}
console.log(Token.isToken(new Token("abc"))); // true
console.log(Token.isToken({ value: "abc" })); // false
Discussion