WaBis

walter.bislins.ch

JavaScript: Closures

Closure ist (u.a.) ein Begriff aus der Informatik. Ein «Closure» ist ein Ausdruck (typischerweise eine Funktion), in dem Variablen vorkommen, welche an eine bestimmte Laufzeit-Umgebung gebunden sind. Closures sind besonders nützlich in JavaScripts.

JavaScript Beispiel

var globalVar = 10;

function OuterFunction( arg ) {
  var localVar = arg * globalVar;

  function InnerFunction( x ) {
    return localVar + arg + x;
  }

  return InnerFunction;
}

var f1 = OuterFunction( 2 );
var f2 = OuterFunction( 3 );

alert( f1(0) ); // -> 22 = 2 + 10*2 + 0
alert( f2(0) ); // -> 33 = 3 + 10*3 + 0

Im obigen Beispiel verwendet die Funktion InnerFunction die lokale Variable localVar und das Argument arg der äusseren Funktion OuterFunction, in welcher sie definiert ist. Nun wird aber InnerFunction nicht in OuterFunction aufgerufen, sondern als Returnwert zurückgegeben und an f1 bzw. f2 zugewiesen. Wird nun InnerFunction durch Aufruf von f1() bzw. f2() ausgeführt, stellt sich die Frage, welche Werte haben nun die Variablen arg und localVar in diesem Moment? OuterFunction ist ja in diesem Moment nicht aktiv, wird nicht ausgeführt und die Variablen arg und localVar sind eigentlich zu diesem Zeitpunkt nicht existent! Gibt es eine Fehlermeldung?

Erklärung von Closures

Closures erlauben inneren Funktionen (Funktions-Definitionen und Funktions-Ausdrücke innerhalb einer äusseren Funktion) auf alle lokalen Variablen, Argumente und deklarierten inneren Funktionen innerhalb der äusseren Funktion zuzugreifen. Ein Closure wird erzeugt, wenn eine dieser inneren Funktionen ausserhalb der äusseren Funktion referenzierbar gemacht wird, so dass die innere Funktion aufgerufen werden kann, nachdem die äussere Funktion beendet worden ist. Die so aufgerufene innere Funktion InnerFunction hat dann immer noch Zugriff auf all die lokalen Variablen, Argumente und inneren Funktionen von OuterFunction. Diese lokalen Variablen, Argumente und inneren Funktionen haben dann die Werte, die sie hatten, als die äussere Funktion ausgeführt worden ist, und diese Werte können von InnerFunction gelesen und verändert werden. Für jede Instanz von InnerFunction (f1 und f2 im Beispiel) wird ein eigener Satz der lokalen Variablen, Argumente usw. mit den Werten gespeichert, die beim Aufruf von OuterFunction gerade aktuell waren!

Wie wird das gemacht?

Zum Zeitpunkt, wo OuterFunction ausgeführt wird, wird auch die Deklaration von InnerFunction ausgeführt. Dabei wird InnerFunction kompiliert und es entstehen intern Referenzen auf die lokalen Variablen und Argumente von OuterFunction (arg, localVar). Wenn OuterFunction beendet ist, werden normalerweise alle Argumente und lokalen Variablen gelöscht. Da aber InnerFunction noch Referenzen auf diese Variablen hat, können diese erst gelöscht werden, wenn InnerFunction gelöscht wird. InnerFunction wird jedoch den globalen Variablen f1 bzw. f2 zugewiesen und deshalb nicht gelöscht. So bleiben auch die Variablen arg und localVar mit ihren aktuellen Werten erhalten und InnerFunction bzw. jetzt f1 oder f2 können darauf zugreifen.

Interessant ist, dass f1 und f2 zwei verschiedene Instanzen von InnerFunction enthalten. Jede dieser Instanzen hat ihren eigenen Satz von arg und localVar mit den Werten, die beim Erzeugen der Instanzen von InnerFunction aktuell waren!

Wozu kann man Closures brauchen?

Eine Hauptanwendung von Closures sind Callback-Funktionen oder Event-Handler. Oftmals vermisst man die Möglichkeit, einem Event-Handler ein Objekt mitzugeben, für welches er dann aufgerufen wird. Über Closures kann man dies elegant machen.

Zum Beispiel: Der Funktion setTimeout() kann man eine Callback-Funktion übergeben, welche nach Ablauf einer bestimmten Zeit vom System aufgerufen wird. Diese Callback-Funktion hat keine Argumente, wir möchten aber ein bestimmtes Objekt an den Aufruf binden können. Dies kann man mit Closures wiefolgt machen:

function MyClass( aObj ) {
  this.Obj = aObj;
  :
}

MyClass.prototype.StartTimer = function ( aVal ) {
  var me = this;
  function Callback() { me.OnTimer( aVal ); }
  this.Timer = setTimeout( Callback, 1000 );
}

MyClass.prototype.OnTimer = function( aVal ) {
  this.Timer = null;
  :
}

Die innere Funktion Callback wird an setTimeout übergeben, wird dadurch von setTimeout referenziert und bildet dadurch ein Closure. Da Callback die lokalen Variablen me und aVal referenziert, werden diese samt ihrem aktuellen Wert an die aktuelle Instanz von Callback gebunden. Wenn nun nach 1000 ms vom Timer die Funktion Callback aufgerufen wird, kann sie auf me und aVal zugreifen, obwohl me und aVal gar nicht mehr in StartTimer existent sind, da StartTimer zu diesem Zeitpunkt längst beendet ist!

So kann man viele Instanzen von MyClass erzeugen, welche alle gleichzeitig ihren eigenen Timer aktivieren können, ohne dass sie sich in die Quere kommen. Jede Callback Funktion ruft OnTimer für sein aktuelles MyClass-Objekt mit seinem aktuellen Wert aVal.

Trick: Da auf this ausserhalb von StartTimer nicht mehr zugegriffen werden kann, auch nicht von Callback, muss der Wert von this zunächst einer lokalen Variablen me zugewiesen werden. Diese kann dann in Callback referenziert werden.

Weitere Informationen

Weitere Infos zur Seite
Erzeugt Samstag, 27. Januar 2007
von wabis
Zum Seitenanfang
Geändert Mittwoch, 12. Oktober 2011
von wabis