Back

Wie JavaScript-Closures funktionieren

Wie JavaScript-Closures funktionieren

Sie haben eine Funktion innerhalb einer anderen Funktion geschrieben, und irgendwie greift die innere Funktion immer noch auf Variablen der äußeren zu – selbst nachdem die äußere Funktion ihre Ausführung beendet hat. Dieses Verhalten verwirrt viele Entwickler, folgt aber einer einfachen Regel: Closures erfassen Bindings, nicht Werte.

Dieser Artikel erklärt, wie JavaScript-Closures funktionieren, was Lexical Scope tatsächlich bedeutet und wie Sie häufige Fehler vermeiden, die aus dem Missverständnis dieser Mechanismen resultieren.

Wichtigste Erkenntnisse

  • Ein Closure ist eine Funktion kombiniert mit ihrer lexikalischen Umgebung – den Variablen-Bindings, die zum Zeitpunkt der Funktionserstellung existierten.
  • Closures erfassen Bindings (Referenzen), nicht Werte, sodass Änderungen an geschlossenen Variablen sichtbar bleiben.
  • Verwenden Sie let oder const in Schleifen, um für jede Iteration neue Bindings zu erstellen und das klassische Schleifen-Problem zu vermeiden.
  • Speicherprobleme entstehen durch das Beibehalten unnötiger Referenzen, nicht durch Closures selbst.

Was ist ein Closure?

Ein Closure ist eine Funktion kombiniert mit ihrer lexikalischen Umgebung – der Menge von Variablen-Bindings, die zum Zeitpunkt der Funktionserstellung existierten. Jede Funktion in JavaScript bildet zum Erstellungszeitpunkt ein Closure.

function createGreeter(greeting) {
  return function(name) {
    return `${greeting}, ${name}`
  }
}

const sayHello = createGreeter('Hello')
sayHello('Alice') // "Hello, Alice"

Wenn createGreeter zurückkehrt, endet ihr Ausführungskontext. Dennoch greift die zurückgegebene Funktion weiterhin auf greeting zu. Die innere Funktion hat nicht den String “Hello” kopiert – sie hat eine Referenz auf das Binding selbst beibehalten.

Lexical Scope in JavaScript erklärt

Lexical Scope bedeutet, dass der Variablenzugriff durch die Position bestimmt wird, an der Funktionen im Quellcode definiert sind, nicht durch den Ort ihrer Ausführung. Die JavaScript-Engine löst Variablennamen auf, indem sie die Scope-Kette vom innersten Scope nach außen durchläuft.

const multiplier = 2

function outer() {
  const multiplier = 10
  
  function inner(value) {
    return value * multiplier
  }
  
  return inner
}

const calculate = outer()
calculate(5) // 50, nicht 10

Die inner-Funktion verwendet multiplier aus ihrer lexikalischen Umgebung – dem Scope, in dem sie definiert wurde – unabhängig von einer globalen Variable mit demselben Namen.

Closures erfassen Bindings, keine Snapshots

Ein häufiges Missverständnis ist, dass Closures Variablenwerte „einfrieren”. Das tun sie nicht. Closures halten Referenzen auf Bindings, sodass Änderungen sichtbar bleiben:

function createCounter() {
  let count = 0
  return {
    increment() { count++ },
    getValue() { return count }
  }
}

const counter = createCounter()
counter.increment()
counter.increment()
counter.getValue() // 2

Beide Methoden teilen sich dasselbe count-Binding. Änderungen durch increment sind für getValue sichtbar, weil sie auf dieselbe Variable referenzieren.

Das klassische Schleifen-Problem: var versus let

Diese Unterscheidung ist bei Schleifen besonders wichtig. Mit var teilen sich alle Iterationen ein Binding:

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100)
}
// Gibt aus: 3, 3, 3

Jeder Callback schließt über dasselbe i, das 3 ist, wenn die Callbacks ausgeführt werden.

Mit let erstellt jede Iteration ein neues Binding:

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100)
}
// Gibt aus: 0, 1, 2

Block-Scoping gibt jedem Closure sein eigenes i.

Praktische Muster: Factory-Funktionen und Event-Handler

Closures ermöglichen Factory-Funktionen, die spezialisiertes Verhalten erzeugen:

function createValidator(minLength) {
  return function(input) {
    return input.length >= minLength
  }
}

const validatePassword = createValidator(8)
validatePassword('secret') // false
validatePassword('longenough') // true

Event-Handler nutzen natürlicherweise Closures, um den Kontext beizubehalten:

function setupButton(buttonId, message) {
  document.getElementById(buttonId).addEventListener('click', () => {
    console.log(message)
  })
}

Der Callback behält den Zugriff auf message lange nach der Rückkehr von setupButton.

Speicher-Überlegungen: Was Closures tatsächlich beibehalten

Closures verursachen nicht von sich aus Memory Leaks. Probleme entstehen, wenn Funktionen unbeabsichtigt Referenzen auf große Objekte oder langlebige Datenstrukturen beibehalten.

function processData(largeDataset) {
  const summary = computeSummary(largeDataset)
  
  return function() {
    return summary // Behält nur summary, nicht largeDataset
  }
}

Wenn Ihr Closure nur einen kleinen Teil der Daten benötigt, extrahieren Sie ihn, bevor Sie die innere Funktion erstellen. Das ursprüngliche große Objekt wird dann für die Garbage Collection freigegeben.

Moderne JavaScript-Engines optimieren Closures aggressiv. Sie sind ein normales Sprachfeature, kein Performance-Problem bei typischer Verwendung. Das Problem sind nicht die Closures selbst – sondern das Beibehalten von Referenzen, die Sie nicht benötigen.

Ein zuverlässiges mentales Modell aufbauen

Denken Sie über Closures so nach: Wenn eine Funktion erstellt wird, erfasst sie eine Referenz auf ihren umgebenden Scope. Dieser Scope enthält Bindings – Verbindungen zwischen Namen und Werten – nicht die Werte selbst. Die Funktion kann diese Bindings während ihrer gesamten Lebensdauer lesen und ändern.

Dieses Modell erklärt, warum Änderungen sichtbar sind, warum let Schleifen-Probleme behebt und warum Closures über asynchrone Grenzen hinweg funktionieren. Die Funktion und ihre lexikalische Umgebung reisen zusammen.

Fazit

Closures sind Funktionen, die mit ihrer lexikalischen Umgebung gebündelt sind. Sie erfassen Bindings, nicht Werte, sodass Änderungen an geschlossenen Variablen sichtbar bleiben. Verwenden Sie let oder const in Schleifen, um für jede Iteration neue Bindings zu erstellen. Speicherprobleme entstehen durch das Beibehalten unnötiger Referenzen, nicht durch Closures selbst.

Das Verständnis von JavaScript-Scope und Closures gibt Ihnen eine Grundlage für das Nachdenken über Variablen-Lebensdauer, Datenkapselung und Callback-Verhalten – Muster, denen Sie täglich in der Frontend-Entwicklung begegnen werden.

FAQs

Jede Funktion in JavaScript ist technisch gesehen ein Closure, weil sie ihre lexikalische Umgebung zum Erstellungszeitpunkt erfasst. Der Begriff Closure bezieht sich typischerweise auf Funktionen, die auf Variablen aus einem äußeren Scope zugreifen, nachdem dieser Scope seine Ausführung beendet hat. Eine Funktion, die nur ihre eigenen lokalen Variablen oder globale Variablen verwendet, zeigt kein Closure-Verhalten in bedeutsamer Weise.

Closures verursachen nicht von sich aus Memory Leaks. Probleme treten auf, wenn Closures unbeabsichtigt Referenzen auf große Objekte oder Datenstrukturen beibehalten, die durch die Garbage Collection freigegeben werden sollten. Um dies zu vermeiden, extrahieren Sie nur die Daten, die Ihr Closure benötigt, bevor Sie die innere Funktion erstellen. Moderne JavaScript-Engines optimieren Closures aggressiv, sodass sie bei typischer Verwendung kein Performance-Problem darstellen.

Dies passiert, wenn Sie var in Schleifen verwenden, weil var funktions-gescoped ist, nicht block-gescoped. Alle Iterationen teilen sich dasselbe Binding, sodass Callbacks den finalen Wert sehen, wenn sie ausgeführt werden. Beheben Sie dies, indem Sie stattdessen let verwenden, das für jede Iteration ein neues Binding erstellt. Jedes Closure erfasst dann seine eigene Kopie der Schleifenvariablen.

Ja. Closures erfassen Bindings (Referenzen auf Variablen), keine Snapshots von Werten. Wenn sich die geschlossene Variable ändert, sieht das Closure den aktualisierten Wert. Deshalb können mehrere Funktionen, die sich dasselbe Closure teilen, über gemeinsame Variablen kommunizieren, wie im Counter-Pattern zu sehen, wo die Methoden increment und getValue sich dasselbe count-Binding teilen.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay