New WeakMap Methods in JavaScript: From Latest Features to Practical Use Cases
WeakMap is for one thing: attaching data to an object such that the data disappears when the object does. It has exactly four methods—set, get, has, delete—and that has not changed.
This article was published in 2024 and completely rewritten in September 2026. The three “new methods” it described do not exist. Calling them throws a TypeError.
WeakMap.prototype.mergedoes not existWeakMap.prototype.hasKeydoes not exist (existence checks usehas())WeakMap.prototype.entriesdoes not exist. A WeakMap is fundamentally not enumerable—the reason is explained below- The memory-comparison table previously included has been removed, as the figures could not be sourced
The only genuine change in recent years is ES2023 allowing symbols as keys. This article covers the accurate specification and where WeakMap genuinely helps.
What a WeakMap is
A map that holds its keys weakly. Being a key in a WeakMap does not, on its own, keep an object alive.
const meta = new WeakMap();
let element = document.createElement('div');
meta.set(element, { clickCount: 0 });
meta.get(element); // { clickCount: 0 }
element = null;
// with no other references, both the div and the WeakMap entry
// become eligible for garbage collection
This is the decisive difference from Map. An object used as a key in a Map is never collected. In a long-running application, that is a memory leak.
There are four methods
| Method | Behaviour |
|---|---|
set(key, value) |
Stores an entry (returns the map, so it chains) |
get(key) |
Reads it; undefined when absent |
has(key) |
Whether an entry exists |
delete(key) |
Removes it; returns a boolean |
That is the entire API. There is no size, no keys() / values() / entries() / forEach(), and it cannot be iterated.
const wm = new WeakMap();
wm.size; // undefined
wm.entries(); // TypeError: wm.entries is not a function
for (const x of wm) {} // TypeError: wm is not iterable
Why it cannot be enumerated
This is not a limitation; it follows from the design.
An entry can be removed at any moment once its key is unreachable elsewhere. When that happens is implementation-dependent and unspecified.
If you could enumerate the contents, you could observe when garbage collection ran. The same code would produce different results depending on timing—a non-deterministic program.
So the count, the order and the contents are all deliberately unobservable. “I want to loop over it for debugging” is a use case WeakMap simply does not serve.
If you need to see inside, use a Map—which means you did not need weak references.
ES2023: symbols as keys
The one real change of recent years. Keys used to be objects only; now non-registered symbols work too.
const wm = new WeakMap();
const key = Symbol('token');
wm.set(key, 'value'); // allowed from ES2023
wm.get(key); // 'value'
// strings and numbers are still rejected
wm.set('str', 1); // TypeError
wm.set(1, 'x'); // TypeError
// symbols from Symbol.for() are excluded
wm.set(Symbol.for('app'), 1); // TypeError
Registered symbols are excluded because they live in a global registry, making them effectively string-like and never collectable.
Chrome and Safari support this; Firefox does not at the time of writing. Check before relying on it.
WeakMap or Map?
| WeakMap | Map | |
|---|---|---|
| Valid keys | Objects, non-registered symbols | Any value |
| Key references | Weak (does not prevent collection) | Strong (prevents collection) |
| Enumeration | Not possible | Possible |
size |
No | Yes |
clear() |
No | Yes |
| Best for | Metadata attached to objects | Ordinary key-value storage |
One question decides it: should this map get to determine how long the key object lives? If not, use a WeakMap.
Use case 1: metadata on DOM elements
The most practical application. When the element leaves the DOM, the metadata goes with it.
const state = new WeakMap();
function initCounter(el) {
state.set(el, { count: 0 });
el.addEventListener('click', () => {
const s = state.get(el);
s.count += 1;
el.textContent = `${s.count} clicks`;
});
}
document.querySelectorAll('.counter').forEach(initCounter);
After remove(), with no other references, the element and its { count: n } are collected together. Do the same with a Map and removed elements stay in memory forever.
One misconception to clear up
Using a WeakMap does not prevent event listener leaks.
In the code above, the listener is attached to el itself. If the element leaves the DOM and nothing references it, the element, its listener and the WeakMap entry all become collectable. Conversely, if something still holds a reference to the element, nothing is collected—WeakMap or not.
What a WeakMap guarantees is only that the map’s own existence does not block collection. When listeners genuinely need removing, call removeEventListener or detach them all with an AbortSignal.
const controller = new AbortController();
el.addEventListener('click', handler, { signal: controller.signal });
el.addEventListener('keydown', handler2, { signal: controller.signal });
// removes both
controller.abort();
AbortSignal usage is also covered in practical fetch().
Use case 2: caching computed results
Caching expensive work per object, where the cache should vanish with the original object, fits perfectly.
const cache = new WeakMap();
function getExpensiveResult(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = heavyCalculation(obj);
cache.set(obj, result);
return result;
}
Written with a Map, the cache grows without bound, and you end up implementing size limits or LRU eviction. A WeakMap delegates that to the engine.
The trade-off: hit rates are not guaranteed. Since collection is unobservable, this is unsuitable for data that must still be there.
Use case 3: private data (prefer #private now)
WeakMap was long used to keep class internals inaccessible from outside.
const privateData = new WeakMap();
class Account {
constructor(secret) {
privateData.set(this, { secret });
}
getSecret() {
return privateData.get(this).secret;
}
}
Today, private fields with # are the straightforward choice. Standardised in ES2022 and available everywhere.
class Account {
#secret;
constructor(secret) {
this.#secret = secret;
}
getSecret() {
return this.#secret;
}
}
const a = new Account('xxx');
a.secret; // undefined
a.#secret; // SyntaxError — not accessible outside the class
# states the intent clearly and avoids carrying an external WeakMap around. The WeakMap version still has a place when you are not using class syntax, or when several classes share metadata.
WeakRef and FinalizationRegistry
ES2021 added two related primitives.
const ref = new WeakRef(someObject);
// retrieve it; undefined if already collected
const obj = ref.deref();
if (obj) {
obj.doSomething();
}
You should generally avoid both. MDN and the specification both counsel caution.
- Collection timing is implementation-dependent and unpredictable, varying by browser and version
FinalizationRegistrycallbacks may never run—closing a tab, for instance. Design assuming they will not- They are not a reliable cleanup mechanism. An explicit
close()ordispose()is far more dependable
When you think you need a weak reference, check whether a WeakMap suffices first. Usually it does.
Before you reach for it
- Is the key an object? For strings or numbers, use
Map - Do you need to count or list the contents? Then use
Map—a WeakMap cannot - Is the key object’s lifetime managed elsewhere? That is exactly when WeakMap fits
- Would losing the data be a problem? Then WeakMap is wrong—you cannot know when it goes
Summary
merge,hasKeyandentriesare not WeakMap methods- There are four:
set,get,has,delete. Nosize, no iteration - Non-enumerability follows from the design—otherwise GC timing would be observable
- The only recent change is ES2023 allowing non-registered symbols as keys (not yet in Firefox)
- It shines for DOM element metadata and per-object caches
- A WeakMap does not remove event listeners. Use
AbortSignalfor that - Prefer class
#fields for private data WeakRefandFinalizationRegistrybehave unpredictably—do not reach for them casually
“Memory efficient” is the phrase that circulates, but the real property is that you can delegate lifetime to something else. For array methods, see grouping and non-mutating array methods.