Martin Puppe


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()
  1. 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.