Closures in JavaScript verstehen

Closures gehören zu den mächtigsten und am häufigsten missverstandenen Konzepten in JavaScript. Hier lernst du Schritt für Schritt, wie sie funktionieren und wofür du sie im Alltag brauchst.

Teilen

Wenn du dich schon eine Weile mit JavaScript beschäftigst, ist dir der Begriff Closure bestimmt schon begegnet – oft mit dem Zusatz, dass das ein „schwieriges" Thema sei. Tatsächlich nutzt du Closures wahrscheinlich längst, ohne es zu merken. In diesem Beitrag schauen wir uns an, was eine Closure genau ist, warum sie so nützlich ist und wie du sie gezielt in deinem Code einsetzt. Versprochen: Am Ende ist das Konzept kein Mysterium mehr.

Was ist eine Closure?

Eine Closure entsteht, wenn eine Funktion sich an die Variablen ihrer Umgebung „erinnert" – auch dann noch, wenn diese Umgebung eigentlich schon längst abgeschlossen ist. Anders gesagt: Eine innere Funktion hat Zugriff auf die Variablen der äußeren Funktion, in der sie definiert wurde, selbst nachdem die äußere Funktion zurückgekehrt ist.

Das klingt abstrakt, wird aber an einem Beispiel sofort klar:

function aussen() {
  const nachricht = "Hallo aus der äußeren Funktion";

  function innen() {
    console.log(nachricht);
  }

  return innen;
}

const meineFunktion = aussen();
meineFunktion(); // "Hallo aus der äußeren Funktion"

Obwohl aussen() längst durchgelaufen ist, kann innen immer noch auf die Variable nachricht zugreifen. Die innere Funktion hat die Umgebung quasi „eingefangen" – genau das ist eine Closure.

Warum funktioniert das überhaupt?

Der Schlüssel liegt darin, wie JavaScript mit dem sogenannten Scope (Gültigkeitsbereich) umgeht. Jede Funktion erzeugt beim Aufruf ihren eigenen Scope. Definierst du innerhalb dieser Funktion eine weitere Funktion, behält diese eine Referenz auf den umgebenden Scope.

Normalerweise würde der Speicher der äußeren Funktion nach ihrem Durchlauf wieder freigegeben. Solange aber eine innere Funktion noch eine Referenz darauf hält, bleibt dieser Bereich bestehen. JavaScript räumt erst auf, wenn niemand mehr darauf zugreift. Dadurch lebt der „eingefrorene" Zustand der Variablen weiter.

Ein praktisches Beispiel: Zähler

Closures glänzen, wenn du einen privaten Zustand brauchst, der von außen nicht direkt manipuliert werden kann. Ein klassisches Beispiel ist ein Zähler:

function erstelleZaehler() {
  let anzahl = 0;

  return {
    hoch() {
      anzahl++;
      return anzahl;
    },
    runter() {
      anzahl--;
      return anzahl;
    },
    stand() {
      return anzahl;
    }
  };
}

const zaehler = erstelleZaehler();
console.log(zaehler.hoch());  // 1
console.log(zaehler.hoch());  // 2
console.log(zaehler.runter()); // 1
console.log(zaehler.stand());  // 1

Die Variable anzahl ist von außen nicht direkt erreichbar. Du kannst sie nur über die zurückgegebenen Methoden verändern. Das ist eine elegante Form der Datenkapselung – ähnlich wie private-Eigenschaften in anderen Sprachen, nur ganz ohne Klassen.

Die berühmte Schleifen-Falle

Ein Klassiker, an dem viele Einsteiger stolpern, ist die Kombination aus Closures und Schleifen. Schau dir diesen Code an:

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

Warum dreimal die 3? Weil var einen funktionsweiten Scope hat. Alle drei setTimeout-Callbacks teilen sich dieselbe Variable i, und wenn sie nach 100 Millisekunden ausgeführt werden, ist die Schleife längst durch und i hat den Wert 3.

Die einfachste Lösung: Nutze let statt var. Mit let bekommt jede Iteration ihren eigenen Block-Scope und damit ihre eigene Closure:

// Funktioniert wie erwartet mit let
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Ausgabe: 0, 1, 2

Dieses Beispiel zeigt gut, dass Closures immer die Referenz auf eine Variable festhalten, nicht eine Kopie ihres Werts zum Zeitpunkt der Definition.

Closures im echten Alltag

Du begegnest Closures überall, sobald du etwas anspruchsvolleren JavaScript-Code schreibst. Ein paar typische Einsatzgebiete:

  • Event-Handler, die auf Variablen aus ihrem Umfeld zugreifen.
  • Callback-Funktionen bei asynchronen Operationen wie fetch oder setTimeout.
  • Factory-Funktionen, die vorkonfigurierte Funktionen zurückgeben.
  • Modul-Muster, um privaten Zustand zu kapseln.

Ein hübsches Beispiel ist eine Factory-Funktion, die spezialisierte Funktionen erzeugt:

function multiplizierer(faktor) {
  return (zahl) => zahl * faktor;
}

const verdoppeln = multiplizierer(2);
const verdreifachen = multiplizierer(3);

console.log(verdoppeln(5));     // 10
console.log(verdreifachen(5));  // 15

Jede zurückgegebene Funktion „merkt" sich ihren eigenen faktor. So baust du mit wenig Code flexible, wiederverwendbare Bausteine.

Fazit

Eine Closure ist nichts weiter als eine Funktion, die sich die Variablen ihrer Geburtsumgebung merkt. Dieses scheinbar kleine Detail ermöglicht mächtige Muster: private Zustände, Factory-Funktionen und sauber gekapselte Logik. Wenn du das nächste Mal eine Funktion aus einer anderen Funktion zurückgibst oder einen Callback schreibst, achte bewusst darauf, welche Variablen die innere Funktion festhält. Mit diesem Blick wirst du Closures nicht nur verstehen, sondern sie auch gezielt einsetzen – und damit deutlich eleganteren JavaScript-Code schreiben.