Kodokon kodokon.com

Speicher und Performance: GC, WeakMap/WeakRef, Debounce/Throttle

Verstehe, wie der Garbage Collector funktioniert, nutze schwache Referenzen, um Lecks zu vermeiden, und glätte teure Arbeit mit Debounce und Throttle.

11 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Der Garbage Collector von V8 basiert auf Erreichbarkeit: Ein Objekt wird einsammelbar, sobald keine Referenzkette es mehr mit den Wurzeln (dem Stack, den aktiven Scopes, den Globals) verbindet. Der GC ist generationsbasiert: Junge Objekte leben in der *Nursery*, werden oft und schnell eingesammelt (Scavenge); Überlebende werden in die alte Generation befördert, die von einem inkrementellen Mark-and-Sweep verwaltet wird. Klassische Lecks kommen nicht vom GC, sondern von Referenzen, die du vergessen hast: Map-basierte Caches, Listener, die nie abgehängt werden, Closures, die große Objekte einfangen.

JAVASCRIPT
const strongCache = new Map();
const weakCache = new WeakMap();

let session = { user: "ada" };
strongCache.set(session, "data");
weakCache.set(session, "data");

session = null;
// Map: the key retains the object -> memory leak
// WeakMap: the entry becomes collectable
Derselbe Code, zwei entgegengesetzte Speicherschicksale.

Eine WeakMap behält ihre Schlüssel nicht: Wird ein Schlüssel von überall sonst unerreichbar, verschwindet der gesamte Eintrag. Folgen für das Design: Schlüssel müssen Objekte sein (oder nicht registrierte Symbole), und die Struktur ist weder iterierbar noch messbar - ihre Größe preiszugeben würde das Verhalten des GC offenbaren, das nicht-deterministisch ist. WeakRef geht weiter: Es gibt dir eine direkte schwache Referenz, die mit deref() dereferenziert wird.

JAVASCRIPT
let config = { theme: "dark" };
const ref = new WeakRef(config);

function readTheme() {
  const target = ref.deref();
  return target ? target.theme : "default";
}

console.log(readTheme()); // "dark"
config = null;
// After a GC pass, deref() may
// return undefined: plan a fallback.
deref() kann das Objekt zurückgeben... oder undefined.

Was die gefühlte Performance angeht, können hochfrequente Events (scroll, resize, input, mousemove) Hunderte Aufrufe pro Sekunde auslösen. Zwei sich ergänzende Strategien: Debounce führt die Funktion erst nach einer Ruhephase aus (ideal für die Suche während der Eingabe), während Throttle höchstens eine Ausführung pro Intervall garantiert (ideal, um das Scrollen zu verfolgen).

JAVASCRIPT
function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(
      () => fn.apply(this, args),
      delay
    );
  };
}
Debounce: Nur der letzte Aufruf einer Serie zählt.
JAVASCRIPT
function throttle(fn, interval) {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn.apply(this, args);
    }
  };
}
Throttle: höchstens eine Ausführung pro Intervall.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Warum müssen WeakMap-Schlüssel Objekte sein und keine Primitiven wie Strings?
    • Aus Performance-Gründen beim Hashing
    • Weil nur eine Referenz auf ein Objekt unerreichbar werden und das Entfernen des Eintrags auslösen kann
    • Es ist eine historische Einschränkung, die seit ES2021 aufgehoben ist
    • Weil Primitiven in JavaScript nicht als Schlüssel verwendet werden können
  2. Was ist der grundlegende Unterschied zwischen Debounce und Throttle?
    • Debounce läuft nach einer Ruhephase, Throttle garantiert höchstens eine Ausführung pro Intervall
    • Debounce ist asynchron, Throttle ist synchron
    • Throttle bricht vorherige Aufrufe ab, Debounce reiht sie in eine Warteschlange
    • Es sind zwei Namen für dieselbe Technik
  3. Was kann ref.deref() bei einer WeakRef zurückgeben?
    • Immer das ursprüngliche Objekt, solange die WeakRef existiert
    • Das Objekt, wenn es noch lebt, oder undefined, wenn es eingesammelt wurde
    • Eine tiefe Kopie des Objekts
    • null, wenn das Objekt eingesammelt wurde