Signatur
Beschreibung
Ein WeakMap ist eine Sammlung von Schlüssel/Wert-Paaren, deren Schlüssel Objekte oder nicht-registrierte Symbols sein müssen und deren Werte einen beliebigen JavaScript-Typ haben können. Sie erzeugt keine starken Referenzen auf ihre Schlüssel. Das heißt, das Vorhandensein eines Objekts als Schlüssel in einer WeakMap verhindert nicht, dass das Objekt vom Garbage Collector eingesammelt wird. Sobald ein als Schlüssel verwendetes Objekt eingesammelt wurde, werden die zugehörigen Werte in jeder WeakMap ebenfalls Kandidaten für die Garbage Collection – sofern sie nicht anderweitig stark referenziert werden. Der einzige primitive Typ, der als WeakMap-Schlüssel verwendet werden kann, ist Symbol – genauer gesagt nicht-registrierte Symbols – da nicht-registrierte Symbols garantiert eindeutig sind und nicht neu erzeugt werden können.
WeakMap erlaubt es, Daten mit Objekten zu verknüpfen, ohne zu verhindern, dass die Schlüsselobjekte eingesammelt werden – auch wenn die Werte auf die Schlüssel verweisen. Allerdings erlaubt eine WeakMap nicht, die Lebendigkeit ihrer Schlüssel zu beobachten, weshalb sie auch keine Aufzählung erlaubt; würde eine WeakMap eine Methode bereitstellen, um eine Liste ihrer Schlüssel zu erhalten, so würde diese Liste vom Zustand der Garbage Collection abhängen und Nichtdeterminismus einführen. Wenn Sie eine Liste von Schlüsseln benötigen, sollten Sie eine Map statt einer WeakMap verwenden.
Schlüsselgleichheit
Wie bei einer regulären Map basiert die Wertgleichheit auf dem SameValueZero-Algorithmus, der dem ===-Operator entspricht, da eine WeakMap nur Objekt- und Symbol-Schlüssel enthalten kann. Das bedeutet, dass die Gleichheit für Objektschlüssel auf der Objektidentität basiert. Sie werden per Referenz verglichen, nicht per Wert.
Warum WeakMap?
Eine Map-API könnte in JavaScript mit zwei Arrays (eines für Schlüssel, eines für Werte) implementiert werden, die von den vier API-Methoden gemeinsam genutzt werden. Das Setzen von Elementen würde bedeuten, gleichzeitig einen Schlüssel und einen Wert an das Ende jedes dieser Arrays zu pushen. Dadurch würden die Indizes von Schlüssel und Wert in beiden Arrays übereinstimmen. Um Werte zu erhalten, müsste man alle Schlüssel durchlaufen, um eine Übereinstimmung zu finden, und dann über den Index den entsprechenden Wert aus dem Werte-Array abrufen.
Eine solche Implementierung hätte zwei Hauptnachteile:
- Der erste ist ein
O(n)-Setzen und -Suchen (n ist die Anzahl der Schlüssel in der Map), da beide Operationen die Liste der Schlüssel durchlaufen müssen, um einen passenden Wert zu finden. - Der zweite Nachteil ist ein Memory-Leak, da die Arrays sicherstellen, dass Referenzen auf jeden Schlüssel und jeden Wert unbegrenzt gehalten werden. Diese Referenzen verhindern, dass die Schlüssel vom Garbage Collector eingesammelt werden, selbst wenn keine anderen Referenzen auf das Objekt existieren. Dies würde auch verhindern, dass die entsprechenden Werte eingesammelt werden.
Im Gegensatz dazu verweist in einer WeakMap ein Schlüsselobjekt stark auf seine Inhalte, solange der Schlüssel nicht eingesammelt wurde, danach jedoch nur noch schwach. Somit gilt für eine WeakMap:
- Sie verhindert nicht die Garbage Collection, die schließlich Referenzen auf das Schlüsselobjekt entfernt.
- Sie erlaubt die Garbage Collection beliebiger Werte, sofern deren Schlüsselobjekte nicht von anderer Stelle als der
WeakMapreferenziert werden.
Eine WeakMap kann besonders nützlich sein, wenn Schlüssel auf Informationen über den Schlüssel abgebildet werden, die nur dann wertvoll sind, wenn der Schlüssel nicht eingesammelt wurde.
Da eine WeakMap aber nicht erlaubt, die Lebendigkeit ihrer Schlüssel zu beobachten, sind ihre Schlüssel nicht aufzählbar. Es gibt keine Methode, um eine Liste der Schlüssel zu erhalten. Gäbe es sie, würde die Liste vom Zustand der Garbage Collection abhängen und Nichtdeterminismus einführen. Wenn Sie eine Liste von Schlüsseln benötigen, sollten Sie eine Map verwenden.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $iterable | Iterable<[object, any]>|null|undefined | undefined | Ein optionales iterierbares Objekt (z. B. ein Array von [schlüssel, wert]-Paaren), mit dem die WeakMap initialisiert wird. Alle Schlüssel müssen Objekte oder nicht registrierte Symbole sein. |
Rückgabewert
WeakMap-Instanz, optional vorbelegt mit den Einträgen aus dem übergebenen Iterable.Beispiele
WeakMap verwenden
const wm1 = new WeakMap();
const wm2 = new WeakMap();
const wm3 = new WeakMap();
const o1 = {};
const o2 = () => {};
const o3 = window;
wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2); // a value can be anything, including an object or a function
wm2.set(o2, undefined);
wm2.set(wm1, wm2); // keys and values can be any objects. Even WeakMaps!
wm1.get(o2); // "azerty"
wm2.get(o2); // undefined, because that is the set value
wm2.get(o3); // undefined, because there is no key for o3 on wm2
wm1.has(o2); // true
wm2.has(o2); // true (even if the value itself is 'undefined')
wm2.has(o3); // false
wm3.set(o1, 37);
wm3.get(o1); // 37
wm1.has(o1); // true
wm1.delete(o1);
wm1.has(o1); // false
Eine WeakMap-ähnliche Klasse mit einer .clear()-Methode implementieren
class ClearableWeakMap {
#wm;
constructor(init) {
this.#wm = new WeakMap(init);
}
clear() {
this.#wm = new WeakMap();
}
delete(k) {
return this.#wm.delete(k);
}
get(k) {
return this.#wm.get(k);
}
has(k) {
return this.#wm.has(k);
}
set(k, v) {
this.#wm.set(k, v);
return this;
}
}
Private Member emulieren
let Thing;
{
const privateScope = new WeakMap();
let counter = 0;
Thing = function () {
this.someProperty = "foo";
privateScope.set(this, {
hidden: ++counter,
});
};
Thing.prototype.showPublic = function () {
return this.someProperty;
};
Thing.prototype.showPrivate = function () {
return privateScope.get(this).hidden;
};
}
console.log(typeof privateScope);
// "undefined"
const thing = new Thing();
console.log(thing);
// Thing {someProperty: "foo"}
thing.showPublic();
// "foo"
thing.showPrivate();
// 1
Äquivalent mit privaten Feldern
class Thing {
static #counter = 0;
#hidden;
constructor() {
this.someProperty = "foo";
this.#hidden = ++Thing.#counter;
}
showPublic() {
return this.someProperty;
}
showPrivate() {
return this.#hidden;
}
}
const thing = new Thing();
console.log(thing);
// Thing {someProperty: "foo"}
thing.showPublic();
// "foo"
thing.showPrivate();
// 1
Metadaten zuordnen (klassischer Ansatz mit Property)
const buttons = document.querySelectorAll(".button");
buttons.forEach((button) => {
button.clicked = false;
button.addEventListener("click", () => {
button.clicked = true;
const currentButtons = [...document.querySelectorAll(".button")];
if (currentButtons.every((button) => button.clicked)) {
console.log("All buttons have been clicked!");
}
});
});
Metadaten zuordnen mit WeakMap
const buttons = document.querySelectorAll(".button");
const clicked = new WeakMap();
buttons.forEach((button) => {
clicked.set(button, false);
button.addEventListener("click", () => {
clicked.set(button, true);
const currentButtons = [...document.querySelectorAll(".button")];
if (currentButtons.every((button) => clicked.get(button))) {
console.log("All buttons have been clicked!");
}
});
});
Caching
const cache = new WeakMap();
function handleObjectValues(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = Object.values(obj).map(heavyComputation);
cache.set(obj, result);
return result;
}
// Wichtig · Fallstricke
Keine Iteration: WeakMap bietet weder keys(), values(), entries() noch forEach() – und auch keine size-Eigenschaft. Ist eine Iteration über alle Einträge erforderlich, sollte stattdessen eine reguläre Map verwendet werden.
Schlüsseltypen: Nur Objekte und nicht registrierte Symbole (erstellt mit Symbol(), nicht mit Symbol.for()) sind als Schlüssel zulässig. Der Versuch, einen primitiven Wert als Schlüssel zu verwenden, wirft einen TypeError.
Nicht deterministischer Garbage Collection: Der Zeitpunkt, zu dem ein Eintrag nach dem Wegfall aller anderen Referenzen auf den Schlüssel tatsächlich entfernt wird, ist nicht garantiert und engine-abhängig. Man sollte sich nicht auf eine sofortige Bereinigung verlassen.
Browser-Kompatibilität: WeakMap wird seit ES2015 von allen modernen Browsern und Node.js unterstützt. In sehr alten Umgebungen (IE10 und älter) ist kein natives WeakMap verfügbar; Polyfills können die schwachen Referenzen jedoch nicht wirklich nachbilden.