DanLevy.net

Hör auf, Speicher zu verschwenden – mit WeakMap

Schwache Referenzen gegen undichte Stellen!

Du kennst das Gefühl, wenn eine einzige geänderte Zeile Code den Speicherverbrauch um 50 % senkt? Ich hatte diesen Moment, als ich im Chrome DevTools Performance Monitor zusah, wie eine Dashboard-App aufhörte, stündlich 100 MB zu verlieren, und stattdessen einen ganzen Nachmittag lang sauber lief.

Die eine geänderte Zeile: new Map() wurde zu new WeakMap().

Das war’s. Gleiche API-Oberfläche, gleiches Nutzungsmuster, völlig anderes Verhalten unter der Haube. Aber zu verstehen, warum das funktioniert, bedeutet zu verstehen, woran die meisten JavaScript-Entwickler nie denken: Was passiert, wenn niemand mehr auf deine Daten schaut?

Wenn Referenzen zu Ankern werden

Eine normale Map in JavaScript behandelt ihre Schlüssel wie wertvolle Fracht. Sobald du etwas hineingesteckt hast, hält die Map eisern daran fest. Der Garbage Collector sieht diese Beziehung und denkt: „Offensichtlich brauchen sie dieses Objekt noch, besser nicht anfassen.“

Dieser Schutzinstinkt wird zum Problem, wenn du Metadaten über temporäre Dinge speicherst. DOM-Knoten, die entfernt werden. Benutzersitzungen, die ablaufen. Komponenteninstanzen, die unmounten. Die Map weiß nicht, dass diese Objekte nicht mehr gebraucht werden. Sie weiß nur, dass sie eine Referenz hat – also hält sie sie am Leben.

const cache = new Map();
function trackClick(element) {
cache.set(element, { clicks: 0 });
}
document.body.removeChild(element);
// Das Element ist aus dem DOM verschwunden, aber cache hält es im Speicher

Der Garbage Collector kann element nicht aufräumen, weil cache immer noch darauf zeigt. Das nennt man eine „starke Referenz“, und in langlebigen Single-Page-Apps wird daraus ein Leck, das irgendwann den Browser zum Absturz bringt.

WeakMap ändert die Regeln

Eine WeakMap funktioniert anders. Sie behandelt ihre Schlüssel als temporäre Gäste statt als Dauermieter. Wenn du etwas in einer WeakMap speicherst, sagst du im Grunde: „Ich möchte diese Daten mit diesem Objekt verknüpfen, aber ich will nicht der Grund sein, warum es am Leben bleibt.“

Wenn das Einzige, was ein Objekt im Speicher hält, eine WeakMap ist, darf der Garbage Collector es entfernen. Sobald das Objekt verschwindet, verschwindet auch der WeakMap-Eintrag. Keine manuelle Bereinigung nötig.

const cache = new WeakMap();
function trackClick(element) {
cache.set(element, { clicks: 0 });
}
document.body.removeChild(element);
// Das Element wird vom Garbage Collector eingesammelt
// Der Cache-Eintrag verschwindet automatisch

Ich habe einen Benchmark laufen lassen: 100.000 DOM-Knoten erstellt, Metadaten zu jedem gespeichert und dann alle entfernt. Mit einer Map hielt der Browser 150–200 MB. Mit einer WeakMap fiel es auf 70–80 MB. Gleicher Code, gleiche Funktionalität, halber Speicherverbrauch.

Worauf du verzichten musst

WeakMap hat Einschränkungen, die sich wie Limitierungen anfühlen – bis du merkst, dass sie genau das sind, was die Magie möglich macht.

Du kannst nicht über eine WeakMap iterieren. Kein forEach, keine keys(), keine values(). Das ergibt Sinn, wenn du darüber nachdenkst: Der Garbage Collector könnte mitten in deiner Schleife einen Eintrag löschen. Willst du das wirklich handhaben?

Du kannst die Größe nicht abfragen. Keine .size-Eigenschaft, keine .length. Wieder ein bewegliches Ziel. Die Anzahl könnte sich zwischen Frage und Antwort ändern.

Schlüssel müssen Objekte sein. Keine Strings, keine Zahlen, keine Primitive. Das ist fundamental für schwache Referenzen: Primitive Werte haben keine Identität, die unabhängig von ihrem Wert verfolgt werden kann.

Das sind keine Bugs. Das ist das Design. WeakMap ist für eine einzige Aufgabe gebaut: Metadaten an Objekte zu heften, ohne zu verhindern, dass diese Objekte aufgeräumt werden. Wenn du Iteration, primitive Schlüssel oder eine Zählung der Einträge brauchst, löst du wahrscheinlich ein anderes Problem und solltest eine normale Map verwenden.

Wo das wirklich hilft

Das „Private Daten“-Muster war der ursprüngliche Anwendungsfall für WeakMaps, bevor JavaScript #private-Felder hatte. Bibliotheken erstellten eine WeakMap außerhalb der Klasse und nutzten sie, um Daten zu speichern, die nicht auf der Instanz zugänglich sein sollten.

const privateData = new WeakMap();
class User {
constructor(name) {
privateData.set(this, { name });
}
getName() {
return privateData.get(this).name;
}
}

Wenn eine User-Instanz vom Garbage Collector eingesammelt wird, verschwinden die privaten Daten mit ihr. Kein Aufräumcode nötig.

Memoization ist ein weiterer natürlicher Anwendungsfall, besonders wenn du Ergebnisse basierend auf Objekt-Eingaben statt primitiven Werten zwischenspeicherst. Wenn deine teure Berechnung ein Konfigurationsobjekt als Eingabe nimmt, sorgt eine WeakMap dafür, dass du dir keine Sorgen machen musst, dass der Cache die Konfigurationen überlebt.

const cache = new WeakMap();
function expensiveCalc(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = heavyMath(obj);
cache.set(obj, result);
return result;
}

Der Cache lebt nur so lange wie die Objekte, die gecacht werden. Sobald obj nirgendwo mehr referenziert wird, verschwinden das zwischengespeicherte Ergebnis und der Cache-Eintrag gemeinsam.

Wann du danach greifen solltest

Speicherlecks in modernen Web-Apps entstehen meist durch alte Referenzen auf Dinge, die längst bereinigt sein sollten. Wenn du etwas Langlebiges baust – ein Dashboard, das den ganzen Tag offen bleibt, eine Chat-App, die stundenlang läuft, ein Admin-Panel, das nie neu lädt – musst du darüber nachdenken, was mit alten Daten passiert.

WeakMap ist besonders nützlich, wenn du Daten mit DOM-Knoten, Komponenteninstanzen oder anderen Objekten verknüpfügst, deren Lebensdauer du nicht kontrollierst. Wenn du etwas basierend auf einer Referenz speicherst und diese Referenz könnte verschwinden, macht WeakMap die Bereinigung wesentlich einfacher.

Eine normale Map ist immer noch die richtige Wahl, wenn du einen echten Cache mit Verdrängungsstrategien baust, wenn du über Einträge iterieren musst, wenn du primitive Schlüssel verwendest oder wenn die Daten selbst wichtiger sind als ihre Verknüpfung mit einem Objekt.

Das Schöne an WeakMap ist, dass meist offensichtlich ist, wann du sie brauchst. Wenn du dich dabei ertappst, Bereinigungscode zu schreiben, der Map-Einträge entfernt, sobald Objekte zerstört werden, ist das ein Zeichen. Wenn du dir Sorgen machst, dass der Speicher unbegrenzt wächst, weil du nicht weißt, wann du löschen sollst, ist das ein weiteres Zeichen.

Manchmal ist die beste Funktion eine, die einfach funktioniert, ohne dass du darüber nachdenken musst.

Ressourcen