Kodokon kodokon.com

Closures und lexikalischer Gültigkeitsbereich

Beherrsche, wie Closures Variablen einfangen, damit du Zustand kapseln und Fallstricke beim Gültigkeitsbereich vermeiden kannst.

9 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Eine Closure ist kein exotischer Trick: Sie ist der Mechanismus, durch den eine Funktion Zugriff auf den lexikalischen Gültigkeitsbereich behält, in dem sie definiert wurde, nicht auf den, in dem sie aufgerufen wird. Die Engine kopiert keine Werte: Sie behält eine lebendige Referenz auf die lexikalische Umgebung. Zwei Aufrufe derselben Factory erzeugen daher zwei unabhängige Umgebungen, was Closures zum leichtgewichtigsten Kapselungswerkzeug der Sprache macht.

JAVASCRIPT
function createCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    reset: () => { count = 0; },
  };
}
const counterA = createCounter();
const counterB = createCounter();
counterA.increment();
counterA.increment();
console.log(counterA.increment()); // 3
console.log(counterB.increment()); // 1
Privater Zustand: count ist nur über die zurückgegebenen Methoden erreichbar.

Die klassische Falle aus der Praxis: var ist an die umgebende Funktion gebunden, nicht an den Block. Innerhalb einer Schleife fängt dann jede Closure dieselbe Variable ein. Mit let erzeugt jede Iteration eine frische lexikalische Umgebung, sodass jede Erfassung eigenständig ist. Hinzu kommt die temporale tote Zone (TDZ): Eine let- oder const-Variable existiert, sobald der Block betreten wird, wirft aber einen ReferenceError, wenn du sie vor ihrer Deklaration liest.

JAVASCRIPT
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0); // 3, 3, 3
}
for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 0); // 0, 1, 2
}
var teilt sich eine einzige Bindung; let erzeugt eine pro Iteration.

Was Muster angeht, sind Closures die Grundlage von Memoization und dem Modul-Muster. Im Vergleich zu einer Klasse gibt dir eine Closure echte Privatsphäre ohne this oder Prototypen, um den Preis eines etwas höheren Speicherverbrauchs, wenn du Tausende Instanzen erzeugst: Jede Instanz trägt ihre eigenen Funktionen.

JAVASCRIPT
function memoize(fn) {
  const cache = new Map();
  return function (key) {
    if (cache.has(key)) return cache.get(key);
    const result = fn(key);
    cache.set(key, result);
    return result;
  };
}
const square = (n) => n * n;
const fastSquare = memoize(square);
console.log(fastSquare(9)); // computed
console.log(fastSquare(9)); // read from the cache
Der Cache lebt innerhalb der Closure, von außen unsichtbar.

Wissenscheck

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

  1. Was geben in einer for (var i = ...)-Schleife drei setTimeout-Callbacks aus, die im Schleifenkörper erzeugt werden?
    • 0, 1, 2, weil jeder Callback seinen eigenen Wert einfängt
    • 3, 3, 3, weil sie sich alle dieselbe Bindung von i teilen
    • dreimal undefined, weil i nach der Schleife zerstört wird
  2. Was genau behält eine Closure aus der Umgebung, in der sie definiert wurde?
    • Eine eingefrorene Kopie der Werte zum Zeitpunkt der Erzeugung
    • Eine lebendige Referenz auf die eingefangenen Variablen
    • Nur die Parameter der umgebenden Funktion
    • Nichts: Der Gültigkeitsbereich wird zerstört, wenn die Funktion zurückkehrt
  3. Warum garantiert createCounter, dass count privat bleibt?
    • Weil let die Variable unveränderlich macht
    • Weil count nur über die zurückgegebenen Funktionen erreichbar ist, die einzigen, die ihren Gültigkeitsbereich einfangen
    • Weil die Engine count löscht, nachdem die Factory zurückgekehrt ist