A WeakMap is to a Map what a WeakSet is to a Set. Its keys must be objects, and it holds them weakly: once nothing else in the program uses a key object, that object can be removed from memory, and its entry disappears from the WeakMap with it.
Say you want to attach extra data to objects you did not create, like page elements or objects from a library. A regular Map works, but it has a hidden cost:
const clickCounts = new Map();
let button = document.querySelector("#save");
clickCounts.set(button, 0);
button.remove(); // gone from the page...
button = null;
// ...but the Map still holds it, so it can never be freed from memory
(document.querySelector("#save") finds the element on the page whose id is save, and button.remove() takes that element off the page.)
That is a memory leak. In a long-running page that keeps creating and removing elements, the Map grows forever. Swap in a WeakMap and the leak disappears.
const clickCounts = new WeakMap();
const btnA = { id: "save" };
const btnB = { id: "cancel" };
clickCounts.set(btnA, 0);
clickCounts.set(btnA, clickCounts.get(btnA) + 1);
console.log(clickCounts.get(btnA)); // 1
console.log(clickCounts.has(btnB)); // false
clickCounts.delete(btnA);
The whole toolkit is set, get, has, and delete.
| Map | WeakMap | |
|---|---|---|
| Keys | Any value | Objects only |
| Values | Any value | Any value |
| Keeps keys in memory | Yes | No |
| size / loops / clear | Yes | No |
const wm = new WeakMap();
wm.set("name", "Ava"); // TypeError: Invalid value used as weak map key
Keys must be objects. (There is one exception: a plain Symbol("id") also works as a key. Symbols are covered in JS Symbols.)
size or loop, because garbage collection could change the answer at any moment.
A WeakMap declared inside a module or function can hold data that only that code can reach:
const secrets = new WeakMap();
class Account {
constructor(owner, pin) {
this.owner = owner;
secrets.set(this, { pin }); // not a property of the object
}
checkPin(guess) {
return secrets.get(this).pin === guess;
}
}
const acct = new Account("Jordan", 4821);
console.log(acct.pin); // undefined (not reachable)
console.log(acct.checkPin(4821)); // true
When an Account is discarded, its secret entry goes with it automatically. (Newer JavaScript also has #private class fields for this job. This course does not cover them, but WeakMaps were the standard way to do it before they existed.)
Remember an expensive calculation for each object, without keeping the objects alive:
const cache = new WeakMap();
function getStats(report) {
if (cache.has(report)) return cache.get(report);
const total = report.values.reduce((a, b) => a + b, 0);
const stats = { total, avg: total / report.values.length };
cache.set(report, stats);
return stats;
}
const r = { values: [4, 8, 15, 16] };
console.log(getStats(r)); // calculated
console.log(getStats(r)); // served from the cache
A WeakSet can only record that someone visited. A WeakMap can store a count with each visitor:
const visits = new WeakMap();
function track(person) {
visits.set(person, (visits.get(person) ?? 0) + 1);
}
let paul = { name: "Paul" };
track(paul);
track(paul);
console.log(visits.get(paul));
paul = null; // the object and its count can now be cleaned up
W3Schools' version of this example declares the visitors with const and then sets one to null, which throws TypeError: Assignment to constant variable. Use let for any variable you plan to clear.
| A list of unique values | Set |
| A lookup table of key to value | Map |
| Tag objects yes/no, without leaks | WeakSet |
| Attach data to objects, without leaks | WeakMap |
Which one can be a WeakMap key?
{ id: 7 }"id"7trueTypeError: Invalid value used as weak map key.In a WeakMap, what is held weakly?
What does this log?
const wm = new WeakMap();
const k = {};
wm.set(k, 1);
wm.set(k, wm.get(k) + 1);
console.log(wm.get(k), wm.size);
2 undefined. Setting the same key again replaces its value. WeakMaps have no size.Account example with its Try it Yourself button and confirm the PIN cannot be read directlyconsole.log("calculating...") inside getStats, and confirm it only prints once per report