Speicherlecks in JavaScript-Closures vermeiden
JavaScript-Closures halten Objekte im Speicher am Leben, solange eine Referenz auf die Closure-Funktion existiert. Das führt zu Lecks, wenn ein Objekt nicht mehr benötigt wird, der Garbage Collector es aber nicht freigeben kann, weil es in der Closure gefangen ist.
Betrachten Sie einen typischen Fall mit einem großen Objekt:
let bigData = {
id: 42,
data: new Array(1000000).fill('Some data')
}
function handler(data) {
return function() {
console.log(data.id);
debugger;
}
}
const runHandler = handler(bigData);
bigData = null;
runHandler();
Die innere Funktion nutzt nur data.id, aber die Closure erfasst das gesamte bigData-Objekt. In den DevTools am Breakpoint sehen Sie, dass das Objekt noch im Speicher hängt.
Probleme mit eval() in Closures
Die eval()-Funktion verschärft Lecks, da der Engine alle äußeren Variablen zur Closure hinzufügt, unabhängig davon, ob sie genutzt werden:
let hello = 'Hello';
let userName = 'Vasya';
setTimeout(function () {
debugger;
return eval('console.log("Hello from Vasya!")');
}, 1000);
In den DevTools enthält die Closure hello und userName, obwohl eval() sie nicht referenziert. Das liegt an der Ungewissheit des Codes in eval(). Vermeiden Sie eval() in der Produktion.
Methoden zur Vermeidung von Lecks
Gefasste Daten erzwingen zurücksetzen
Setzen Sie erfasste Daten nach der Nutzung zurück:
function runCalc(buttonId) {
let bigData = new Array(1000000).fill('Something');
const button = document.getElementById(buttonId);
button.addEventListener('click', function onClick() {
console.log('Processed items:', bigData.length);
bigData = null;
});
}
Das Zuweisen von null gibt die Referenz in der Closure frei, sodass der Garbage Collector das Objekt entfernen kann.
Event-Handler korrekt entfernen
Event-Handler sind eine häufige Leak-Quelle beim Entfernen von DOM-Elementen:
function clickSetup(btn, message) {
const bigData = new Array(1000000).fill(message);
function handler() {
console.log(message, bigData.length);
btn.remove();
btn.removeEventListener('click', handler);
}
btn.addEventListener('click', handler);
}
Eine benannte Funktion handler kann zusammen mit dem Element entfernt werden. Anonyme Arrow-Funktionen lassen sich so nicht entfernen.
WeakMap für schwache Referenzen
WeakMap löst das Problem des Erfassens großer Objekte durch schwache Referenzen auf Objektschlüssel.
Schlüssel müssen Objekte sein
const weak = new WeakMap();
const key = {};
weak.set(key, 'Vasya war hier!');
console.log(weak.get(key));
const anotherKey = {};
console.log(weak.get(anotherKey)); // undefined
Schlüssel müssen Objekte oder Nicht-Primitives sein. Zugriff via get(key) per Referenz.
Schwache Referenzen und Garbage Collection
Referenzen in WeakMap verhindern keine Garbage Collection:
const weak = new WeakMap();
let userKey1 = {id: 1};
weak.set(userKey1, 'Vasya');
userKey1 = null;
Nach dem Setzen von userKey1 auf null werden Objekt und Wert garbage-collected.
Keine Iteratoren
Kein size, forEach, keys(). Nur Basis-Methoden:
set(key, value)— setzenget(key)— abrufenhas(key)— prüfendelete(key)— löschen
Keine Iteratoren, um Unvorhersehbarkeit bei der Garbage Collection zu vermeiden.
WeakMap in Closures einsetzen
Eine Closure mit WeakMap hält Objekte nicht stark fest:
function createProcessor() {
const weakMap = new WeakMap();
return function process(obj) {
weakMap.set(obj, obj.value);
debugger;
return weakMap.get(obj);
}
}
const processor = createProcessor();
let bigData = {
value: 21,
hugeArray: new Array(1000000).fill('*')
};
console.log(processor(bigData));
bigData = null;
let bigData2 = {
value: 42,
hugeArray: new Array(1000000).fill('1')
};
console.log(processor(bigData2));
In den DevTools nach der Garbage Collection bleibt nur bigData2 übrig. WeakMap ermöglicht automatische Speicherbereinigung.
Zur Überprüfung:
- Setzen Sie einen Breakpoint in
process(). - Gehen Sie Schritt für Schritt durch den Code (F10).
- Nach
bigData = nullwechseln Sie zum Memory-Tab. - Lösen Sie die Garbage Collection mit dem Button aus.
- Fahren Sie fort — nur aktuelle Schlüssel bleiben übrig.
Wichtige Erkenntnisse
- Closures erfassen ganze Objekte, auch wenn nur eine Eigenschaft genutzt wird.
eval()fügt alle äußeren Variablen zur Closure hinzu.- Benannte Event-Handler können via
removeEventListenerentfernt werden. WeakMapnutzt schwache Referenzen auf Objektschlüssel.- Keine Iteratoren in
WeakMapfür vorhersehbare Garbage Collection.
— Editorial Team
Noch keine Kommentare.