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:
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".
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:
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:
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:
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:
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.
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:
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.