Closures in Javascript (und Scala)
Closures1 sind meiner Meinung nach eines der interessantesten Sprachkonstrukte von Javascript. Sie erlauben sehr elegante Lösungen, die sonst nicht möglich wären. Was das konkret bedeutet, werde ich an einem Beispiel erläutern. Zunächst möchte ich aber erklären, was Closures sind.
Was ist eine Closure?
Eine Closure beinhaltet eine Funktion und (auch nicht-lokale) Variablen, die innerhalb der Funktion referenziert werden. Closures verändern die Lebensdauer von Variablen. Schauen wir uns dazu ein Beispiel an:
var x = 0;
function foo() {
var y = 42;
return x + y;
}
x = x + 1;
Wir sehen hier zwei Variablen, x
und y
. Im Hinblick auf die
Sichtbarkeit unterscheiden sich die Variablen darin, dass y
nur
innerhalb der Funktion foo
sichtbar ist. Was die Lebensdauer
betrifft, so existiert y
nur während foo
ausgeführt wird.
Sichtbarkeit und Lebensdauer für sich genommen sind keine schwierigen Konzepte. Javascript ist allerdings eine funktionale Sprache und da wird die Sache interessant. Javascript erlaubt es nämlich, Funktionen als Rückgabewert anderer Funktionen zu verwenden. Dazu erweitern wir das obige Beispiel.
function makeFoo() {
var x = 0;
function foo() {
var y = 41;
x = x + 1;
return x + y;
}
return foo;
}
var bar = makeFoo();
console.log(bar());
console.log(bar());
console.log(bar());
In der Konsole erhält man folgende Ausgabe:
42
43
44
Das ist auf den ersten Blick überraschend, wenn wir davon ausgehen, dass
lokale Variablen nur existieren, solange die Funktion, in der sie
definiert wurden, ausgeführt wird. Die Variable x
hingegen bleibt
weiterhin innerhalb der Funktion foo
bzw. bar
sichtbar und behält
zudem ihren Zustand, obwohl makeFoo
vollständig abgearbeitet wurde.
foo
wurde zusammen mit x
in eine Closure gepackt.
Praktische Anwendung
Das obige Beispiel ist konstruiert und in der Praxis kaum nützlich.
Closures haben aber durchaus sinnvolle Anwendungen. Stellen wir uns
einmal vor, wir wollten verschiedene Objekte mit einer eindeutigen
Nummer (einer ID) versehen. Dazu wollen wir eine Funktion generateId
definieren, die stets eine andere Zahl zurückliefert. Der Einfachheit
halber zählen wir von 0 aufwärts. Wir haben mehrere Möglichkeiten dieses
Problem zu lösen.
Möglichkeit 1: Globaler Zähler
var counter = 0;
function generateId() {
var id = counter;
counter = counter + 1;
return id;
}
var obj = {
id: generateId()
}
Ich halte diese Lösung für nicht sehr sinnvoll. counter
ist eine
globale Variable. D.h. wir verschmutzen hier den globalen Namensraum mit
einer Variable, die nur für eine einzige Funktion benötigt wird.
Außerdem kann die Variable praktisch an jeder Stelle im Programm
versehentlich geändert werden. Und es wird überhaupt nicht klar, dass
counter und
generateId` zusammengehören.
Möglichkeit 2: Zähler und Funktion in einem Objekt
var idGenerator = {
counter: 0,
generateId: function() {
var id = this.counter;
this.counter = this.counter + 1;
return id;
}
};
var obj = {
id: idGenerator.generateId()
}
Diese Lösung ist schon etwas besser, in der Hinsicht, dass wir auf eine
globale Variable counter
verzichten. Auch wird der Zusammenhang von
counter
und generateId
deutlicher. Allerdings ist diese Variante
unhandlich in der Verwendung (idGenerator.generateId()
). Außerdem kann
auch hier counter
an anderer Stelle im Programm geändert werden,
obwohl die Variable einzig und allein von der Funktion generateId
manipuliert werden sollte.
Möglichkeit 3: Closure
var generateId = (function() {
var counter = 0;
return function() {
var id = counter;
counter = counter + 1;
return id;
};
})();
var obj = {
id: generateId()
}
Ich halte diese Lösung für die eleganteste. Der Zusammenhang von
counter
und generateId
wird deutlich und der Zähler ist außerhalb
der Funktion überhaupt nicht sichtbar.
Anhang: Das Beispiel in Scala
Closures gibt es nicht nur in Javascript, sondern in wohl jeder funktionalen Programmiersprache. Ich habe mich kürzlich im Rahmen einer Vorlesung mit Scala beschäftigen dürfen, daher hier das obige Beispiel in Scala:
val generateId = (() => {
var counter = 0
() => {
var id = counter;
counter = counter + 1;
id
}
}).apply()
-
Kurz zur Terminologie: Das deutsche Wort für „function closure“ ist wohl „Funktionsabschluss“. Allerdings hat man wenig Glück, wenn man danach googlet. Mir scheint der Begriff nicht sehr gebräuchlich zu sein, also bleibe ich bei der englischen Bezeichnung. ↩