Menu

JavaScript Closures verstehen – mit Beispielen

Eine Closure ist eine Funktion, die sich ihre umliegenden Variablen merkt. So funktionieren Closures in JavaScript – inklusive lauffähiger Beispiele.

Eine Closure ist eine Funktion, die sich erinnert

Jedes Mal, wenn du in JavaScript eine Funktion definierst, behält sie im Hintergrund eine Verbindung zu den Variablen in ihrer Umgebung. Wird die Funktion später aufgerufen – egal an welcher Stelle im Code – hat sie weiterhin Zugriff auf genau diese Variablen. Genau das ist eine Closure in JavaScript.

Das kürzestmögliche Beispiel:

index.js
Output
Click Run to see the output here.

makeGreeter läuft durch, gibt eine innere Funktion zurück und ist damit fertig. Eigentlich würde man erwarten, dass die lokale Variable name verschwindet – die Funktion ist ja beendet. Die innere Funktion greift aber weiterhin auf name zu, und deshalb hält JavaScript die Variable am Leben. greetAda merkt sich "Ada", greetBoris merkt sich "Boris". Zwei Closures, zwei voneinander getrennte gespeicherte Werte.

Lexical Scope: der Ursprung von Closures in JavaScript

Hinter Closures steckt eine Regel namens Lexical Scope (lexikalischer Geltungsbereich): Eine Funktion sieht die Variablen von dort, wo sie im Code steht – nicht von dort, wo sie aufgerufen wird. „Lexikalisch" heißt dabei einfach: „abhängig von der Stelle im Quellcode".

index.js
Output
Click Run to see the output here.

show gibt "Ich bin draußen" aus, nicht "Ich bin in caller". Die Funktion wurde direkt neben dem outer auf oberster Ebene geschrieben – und genau das ist das outer, das sie sieht. Dass sie irgendwo aufgerufen wird, wo zufällig ein anderes outer existiert, spielt keine Rolle.

Closures sind im Grunde nichts anderes als lexical scope, der die äußere Funktion überlebt. Die Variable verschwindet nicht, solange noch jemand eine Referenz darauf hält.

Jeder Aufruf erzeugt seine eigene Closure

Jeder neue Aufruf der äußeren Funktion legt frische Variablen an, und jede innere Funktion, die dabei zurückgegeben wird, merkt sich genau diese Variablen. Deshalb sind sich greetAda und greetBoris oben auch nicht in die Quere gekommen.

Das klassische Beispiel für eine Closure in JavaScript ist ein Counter:

index.js
Output
Click Run to see the output here.

a und b haben jeweils ihre eigene count-Variable. Von außen kommt niemand an diese Variablen heran – count ist komplett privat. Das ist kein extra eingebautes Sprachfeature, sondern ergibt sich ganz natürlich aus der Funktionsweise von Closures.

Private Variablen in JavaScript ohne Klassen

Da die eingeschlossenen Variablen nur über die zurückgegebene Funktion erreichbar sind, lassen sich mit Closures kleine Objekte mit echtem privatem Zustand bauen:

index.js
Output
Click Run to see the output here.

balance ist keine Eigenschaft am zurückgegebenen Objekt – die Variable lebt in der Closure. Lesen oder verändern lässt sie sich ausschließlich über die Methoden, die du nach außen freigegeben hast. Das geht zwar auch mit Klassen und #private-Feldern, aber die Closure-Variante gibt's schon seit Jahrzehnten und taucht im Ökosystem bis heute überall auf.

Der klassische Stolperstein in Schleifen

Am häufigsten legen einen Closures in Schleifen aufs Kreuz. Schau dir an, was mit var passiert:

index.js
Output
Click Run to see the output here.

Man würde 0, 1, 2 erwarten. Stattdessen bekommt man 3, 3, 3. Der Grund: var ist function-scoped, es gibt also nur ein i für die gesamte Schleife. Alle drei Closures haben dieselbe Variable eingefangen, und wenn sie schließlich ausgeführt werden, ist die Schleife längst durch und i steht auf 3.

Einfach auf let umstellen:

index.js
Output
Click Run to see the output here.

Jetzt werden brav 0, 1, 2 ausgegeben. let ist block-scoped – bei jeder Iteration der Schleife entsteht ein eigenes Binding für i, sodass jede Closure ihren eigenen Wert einfängt. Genau das ist der wichtigste Grund, warum du let gegenüber var bevorzugen solltest.

Closures merken sich Variablen, keine Werte

Ein feiner, aber entscheidender Punkt: Eine Closure hält eine Referenz auf die Variable selbst – nicht auf einen Schnappschuss ihres Werts zum Zeitpunkt der Funktionsdefinition.

index.js
Output
Click Run to see the output here.

printMessage liest message erst beim Ausführen aus, nicht beim Erstellen. Wenn du einen Schnappschuss des Werts brauchst, kopiere ihn vorher in eine lokale Variable — genau das passiert im Grunde auch, wenn du let in einer for-Schleife verwendest.

Ein typisches Praxisbeispiel: Once

Hier ist ein kleines Utility, das per Closure dafür sorgt, dass eine Funktion wirklich nur ein einziges Mal ausgeführt wird:

index.js
Output
Click Run to see the output here.

called und result sind privater Zustand, der genauso lange existiert wie die zurückgegebene Funktion selbst. Kein globales Flag, kein zusätzliches Objekt. Dieses Muster – kleiner Helfer, private Variablen, Closure – gehört zu den nützlichsten Dingen, die JavaScript überhaupt kann.

Ein Wort zum Speicher

Eine Closure hält ihre eingefangenen Variablen so lange am Leben, wie irgendetwas noch auf die Closure verweist. Meistens ist das genau das, was man will. Hängst du die Closure aber an etwas Langlebiges (etwa einen DOM-Event-Listener oder einen globalen Cache) und sie fängt ein großes Objekt ein, kann dieses Objekt erst freigegeben werden, wenn auch die Closure verschwindet.

function attach() {
    const hugeData = new Array(1_000_000).fill("...");
    document.addEventListener("click", () => {
        console.log(hugeData.length);
    });
}

Solange der Listener registriert ist, bleibt hugeData im Speicher. Entfernst du den Listener (oder vermeidest von vornherein, unnötige Variablen einzufangen), wird die Referenz freigegeben. Du musst das nicht bis ins letzte Detail steuern – es reicht zu wissen, dass Closures und Speicher zusammenhängen.

Das Wichtigste auf einen Blick

  • Eine Closure ist eine Funktion plus die Variablen, die sie zum Zeitpunkt ihrer Definition gesehen hat.
  • Jeder Aufruf einer äußeren Funktion erzeugt einen frischen Satz Variablen für die inneren Closures.
  • Mit Closures bekommst du private Variablen in JavaScript, ganz ohne Klassen.
  • Verwende in Schleifen let, damit jede Iteration eine eigene Bindung bekommt.
  • Closures fangen die Variable selbst ein, nicht den Wert zum Zeitpunkt der Erzeugung.

Als Nächstes: Das Schlüsselwort this

Closures kümmern sich um die Variablen rund um eine Funktion. Der nächste Baustein ist die Frage, worauf eine Funktion aufgerufen wird – und das steuert in JavaScript das Schlüsselwort this. Es verhält sich ganz anders als die eingefangenen Variablen, die wir gerade besprochen haben.

Häufig gestellte Fragen

Was ist eine Closure in JavaScript?

Eine Closure ist eine Funktion, die sich an die Variablen aus dem Scope erinnert, in dem sie definiert wurde – auch dann noch, wenn dieser äußere Scope längst durchgelaufen ist. Streng genommen ist in JavaScript jede Funktion eine Closure. Der Begriff taucht aber meist erst dann auf, wenn eine Funktion zurückgegeben oder weitergereicht wird und trotzdem noch auf Variablen aus ihrem Ursprungs-Scope zugreift.

Wofür braucht man Closures überhaupt?

Mit Closures kann eine Funktion ihren eigenen privaten State mitnehmen. Du bekommst Daten, die fest an eine Funktion gebunden sind, ganz ohne Klasse oder globale Variable. Typische Einsätze sind Counter, einmalig ausführbare Callbacks, memoisierte Helfer oder das Verstecken von Implementierungsdetails hinter einer kleinen API.

Warum verhalten sich Closures in Schleifen mit var so seltsam?

var ist function-scoped – alle Iterationen teilen sich also dieselbe Variable. Die Closures aus der Schleife referenzieren genau diese eine Variable und sehen bei der späteren Ausführung nur noch den Endwert. Die Lösung: Nimm let. Das ist block-scoped, damit bekommt jede Iteration ihr eigenes Binding.

Lerne mit Coddy zu programmieren

LOS GEHT'S