this
-KontextBei allen drei vorgestellten Techniken – Object
-Literalen, Revealing Module Pattern und Konstruktoren/Prototypen – haben wir mit this
gearbeitet, um auf das Modul bzw. die Instanz zuzugreifen. Nun wollen wir uns näher der Funktionsweise von this
zuwenden.
this
ist ein Schlüsselwort, das zu einem Wert aufgelöst wird und in der Regel auf ein Objekt zeigt. Worauf es zeigt, hängt ganz vom Kontext ab.
Notieren wir this
im globalen Scope, so zeigt es bloß auf window
, das globale Objekt:
alert(this); // Ergibt [object Window] oder ähnliches
Dasselbe gilt, wenn this
in einer Funktion verwendet wird, die ganz normal über funktion()
aufgerufen wird:
function zeigeThis() { alert(this); // ergibt ebenfalls [object Window] } zeigeThis();
In diesen beiden Fällen bietet this
wenig Nutzen, denn wir könnten genauso window
schreiben.
this
zeigt standardmäßig auf das globale Objekt window
. Innerhalb einer Methode, die über objekt.funktion()
aufgerufen wird, zeigt es jedoch auf objekt
.
this
wird in folgendem Fall interessant: Eine Funktion hängt an einem Objekt, ist also eine Methode dessen. Wenn wir die Funktion nun über das Schema objekt.funktion()
aufrufen, dann zeigt this
innerhalb der Funktion auf objekt
.
Ein einfaches Modul mit einem Object
-Literal:
var Modul = { eigenschaft : "wert", methode : function () { alert("methode wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); } }; Modul.methode();
this
zeigt innerhalb der Methode auf das Objekt Modul
. Wir könnten alternativ Modul.eigenschaft
schreiben, was auf dasselbe herauskäme. this
hat jedoch den Vorteil, dass es unabhängig vom aktuellen Modulnamen ist. Wenn dieser später geändert wird und die Funktion verschoben wir, so müssen nicht alle Verweise angepasst werden.
Bei Konstruktoren und Prototypen ist this
unersetzlich:
function Katze(name) { this.name = name; } Katze.prototype = { pfoten : 4, zeigePfoten : function () { alert("Die Katze zeigt ihre " + this.pfoten + " Pfoten."); } }; var maunzi = new Katze('Maunzi'); maunzi.zeigePfoten(); var schnucki = new Katze('Schnucki'); schnucki.zeigePfoten();
this
zeigt innerhalb des Konstruktors und der zeigePfoten
-Methode auf die Instanz. Dies ist einmal maunzi
und einmal schnucki
, deshalb müssen wir hier this
verwenden.
this
ist in der OOP äußerst praktisch. Allerdings verweist this
nur bei der Aufrufweise objekt.funktion()
auf das gewünschte Objekt. Der Verweis geht bei anderen Aufrufweisen, die in der funktionalen Natur von JavaScript liegen, verloren.
Der Zugriff auf das Modul bzw. auf die Instanz über this
ist zum Teil unerlässlich. Damit this
auf das gewünschte Objekt zeigt, müssen beide Kriterien erfüllt sein: Die Funktion hängt an dem Objekt als Unterobjekt und sie wird über das Schema objekt.funktion()
aufgerufen. Alleine durch diese Aufrufweise wird die this
-Verbindung hergestellt.
Dieser Bezug kann jedoch verloren gehen, wenn die Funktion außerhalb dieses Objektkontextes ausgeführt wird. Dies passiert vor allem in folgenden Fällen:
this
zeigt in Handler-Funktionen auf das Elementobjekt, bei dem das Ereignis verarbeitet wird – siehe this beim Event-Handling. Dadurch werden die Methoden außerhalb des Modul- bzw. Instanzkontextes ausgeführt. setTimeout
oder setInterval
. Die verzögert bzw. wiederholt ausgeführte Funktion verliert den Bezug zum Ursprungsobjekt, denn this
verweist darin auf das globale Objekt window
. In vielen Fällen ist der Zugriff auf das Modul bzw. die Instanz notwendig.this
als Fallback auf window
zeigt.this
-Problem bei einfachen ModulenDas folgende Beispiel demonstriert das Problem im Falle eines einfachen Moduls mit dem Object
-Literal:
var Modul = { eigenschaft : "Eigenschaftswert", start : function () { // Funktioniert: alert("start wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); setTimeout(this.verzögert, 100); document.getElementById("button").onclick = this.handler; }, verzögert : function () { // Fehler: this verweist auf window alert("verzögert wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); }, handler : function (e) { // Fehler: this verweist auf das Element, dem der Event-Handler anhängt alert("handler wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); } }; Modul.start();
Das zugehörige HTML:
<button id="button">Button, der auf Klick reagiert</button>
this
-Problem bei Prototypen und InstanzmethodenDasselbe mit einem Konstruktor, einem Prototyp und einer Instanz:
function Konstruktor() {} Konstruktor.prototype = { eigenschaft : "Eigenschaftswert", start : function () { // Funktioniert: alert("start wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); setTimeout(this.verzögert, 100); document.getElementById("button").onclick = this.handler; }, verzögert : function () { // Fehler: this verweist auf window alert("verzögert wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); }, handler : function (e) { // Fehler: this verweist auf das Element, dem der Event-Handler anhängt alert("handler wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); } }; var instanz = new Konstruktor(); instanz.start();
In beiden Fällen werden Objektmethoden als Event-Handler verwendet (handler
) sowie mit setTimeout
aufgerufen (verzögert
). In den start
-Methoden gelingt der Zugriff über this
noch. In der verzögert
-Methode zeigt this
jedoch nicht mehr auf das richtige Objekt, sondern auf window
. In der handler
-Methode, welche beim Klicken auf den Button ausgeführt wird, enthält this
zwar eine wertvolle Information, aber auch hier geht der Bezug zum Modul bzw. zur Instanz verloren.
Die Lösung dieses Problems ist kompliziert und führt uns auf eine zentral wichtige, aber auch schwer zu meisternde Eigenheit der JavaScript-Programmierung, die im Folgenden vorgestellt werden soll.
Verschachtelte Funktionen haben Zugriff auf die lokalen Variablen der äußere Funktion – und zwar auch nach der Abarbeitung der äußeren Funktion. Sie konservieren die Variablen.
Eine Closure ist allgemein gesagt eine Funktion, die in einer anderen Funktion notiert wird. Diese verschachtelte, innere Funktion hat Zugriff auf die Variablen des Geltungsbereiches (Scopes) der äußeren Funktion – und zwar über die Ausführung der äußeren Funktion hinaus.
Durch dieses Einschließen der Variablen kann man bestimmte Objekte in Funktionen verfügbar machen, die darin sonst nicht oder nur über Umwege verfügbar wären. Closures werden damit zu einem Allround-Werkzeug in der fortgeschrittenen JavaScript-Programmierung. Wir haben Closures bereits verwendet, um private Objekte zu erreichen.
Dieses Beispiel demonstriert die Variablen-Verfügbarkeit bei verschachtelten Funktionen:
function äußereFunktion() { // Definiere eine lokale Variable var variable = "wert"; // Lege eine verschachtelte Funktion an function innereFunktion() { // Obwohl diese Funktion einen eigenen Scope mit sich bringt, // ist die Variable aus dem umgebenden Scope hier verfügbar: alert("Wert der Variablen aus der äußeren Funktion: " + variable); } // Führe die eben definierte Funktion aus innerfunktion(); } äußereFunktion();
Das Beispiel zeigt, dass die innere Funktion Zugriff auf die Variablen der äußeren Funktion hat. Der entscheidende Punkt bei einer Closure ist jedoch ein anderer:
Normalerweise werden alle lokalen Variablen einer Funktion aus dem Speicher gelöscht, nachdem die Funktion beendet wurde. Eine Closure aber führt dazu, dass die Variablen der äußeren Funktion nach deren Ausführung nicht gelöscht werden, sondern im Speicher erhalten bleiben. Die Variablen stehen der inneren Funktion weiterhin über deren ursprüngliche Namen zur Verfügung. Die Variablen werden also eingeschlossen und konserviert – daher der Name »Closure«.
Auch lange nach dem Ablauf der äußeren Funktion hat die Closure immer noch Zugriff auf deren Variablen. Vorausgesetzt ist, dass die Closure woanders gespeichert wird und dadurch zu einem späteren Zeitpunkt ausgeführt werden kann. Im obigen Beispiel ist die innere Funktion nur eine lokale Variable, die zwar Zugriff auf die Variablen der äußeren Funktion hat, aber bei deren Beendigung selbst verfällt.
Eine Möglichkeit, die innere Funktion zu speichern, ist das Registrieren als Event-Handler. Dabei wird das Funktionsobjekt in einer Eigenschaft (hier onclick
) eines Elementobjekts gespeichert und bleibt damit über die Ausführung der äußeren Funktion hinweg erhalten:
function äußereFunktion () { var variable = "wert"; // Lege eine verschachtelte Funktion an function closure { alert("Wert der Variablen aus der äußeren Funktion: " + variable); } // Speichere die Closure-Funktion als Event-Handler document.getElementById("button").onclick = closure; } äußereFunktion();
Der zugehörige Button im HTML:
<button id="button">Button, der auf Klick reagiert</button>
Bei einem Klick auf den Button wird die Closure als Event-Handler ausgeführt. äußereFunktion
wird schon längst nicht mehr ausgeführt, aber variable
wurde in die Closure eingeschlossen.
Zusammengefasst haben wir folgendes Schema zur Erzeugung einer Closure:
Wie helfen uns Closures nun beim this
-Problem weiter?
this
zugunsten des Revealing Module PatternsBeim Revealing Module Pattern ist this
unnötig, denn alle Funktionen sind Closures und schließen die benötigten Variablen ein.
Gegenüber dem einfachen Object
-Literal bietet das Revealing Module Pattern bereits die nötige Infrastruktur, um das Problem zu lösen. Um private Objekte zu erreichen, benutzt das Revealing Module Pattern eine Kapselfunktion, in der weitere Funktionen notiert sind. Die inneren Funktionen sind bereits Closures. Daher liegt eine mögliche Lösung darin, vom einfachen Object
-Literal auf das Revealing Module Pattern umzusteigen.
Das Revealing Module Pattern trennt zwischen privaten Objekten und der öffentliche Schnittstelle (API). Letztere ist ein Object
-Literal, der aus der Kapselfunktion zurückgegeben wird. Dieses Object
enthält verschachtelte Methoden, die als Closures die privaten Objekte einschließen. Zur Wiederholung:
var Modul = (function () { // Private Objekte var privateVariable = "privat"; // Öffentliche API return { öffentlicheMethode : function () { alert(privateVariable); } }; })(); Modul.öffentlicheMethode();
Die Funktionen haben in jedem Fall Zugriff auf die privaten Objekte, auch wenn sie z.B. durch Event-Handling oder setTimeout
aus dem Kontext gerissen werden. Eine kleine Änderung ist jedoch nötig, damit öffentliche Methoden sich gegenseitig sowie private Funktionen öffentliche Methoden aufrufen können. Anstatt die öffentliche API-Objekt direkt hinter return
zu notieren, speichern wir es zuvor in einer Variable. Diese wird von allen verschachtelten Funktionen eingeschlossen.
var Modul = (function () { // Private Objekte var privateVariable = "privat"; function privateFunktion() { alert("privateFunktion wurde verzögert aufgerufen\n" + "privateVariable: " + privateVariable); // Rufe öffentliche Methode auf: api.ende(); } // Öffentliche API, gespeichert in einer Variable var api = { start: function () { alert("Test startet"); // Hier würde this noch funktionieren, wir nutzen trotzdem api setTimeout(api.öffentlicheMethode, 100); }, öffentlicheMethode: function () { alert("öffentlicheMethode wurde verzögert aufgerufen"); setTimeout(privateFunktion, 100); }, ende: function () { alert("Öffentliche ende-Methode wurde aufgerufen. Test beendet"); } }; return api; })(); Modul.start();
Der Code gibt folgende Meldungen aus:
Test startet öffentlicheMethode wurde verzögert aufgerufen privateFunktion wurde verzögert aufgerufen privateVariable: privat Öffentliche ende-Methode wurde aufgerufen. Test beendet
Dieses Beispiel zeigt, wie öffentliche und private Methoden einander aufrufen können. Auf this
kann verzichtet werden, denn alle benötigten Variablen werden durch Closures eingeschlossen. Infolgedessen stellt die Nutzung von setTimeout
kein Problem dar.
Die privaten Objekte sind direkt über ihre Variablennamen verfügbar, die Eigenschaften der öffentlichen API indirekt über die Variable api
.
Die Methoden werden im Konstuktor verschachtelt. Sie schließen eine Variable ein, die auf die Instanz verweist und die wir als Ersatz zu this
verwenden..
Wir haben zwei Möglichkeit kennengelernt, dem Instanzobjekt Eigenschaften zuzuweisen. Zum einen, indem wir sie im Konstruktor über this
anlegen. Zum anderen, indem wir einen Prototypen definieren und die Instanz davon erbt. Der Weg über den Prototyp hat Performance-Vorteile, der Weg über den Konstruktor erlaubt private Objekte.
Private Objekte funktionieren letztlich über Closures und bieten die Möglichkeit, das this
-Problem zu umgehen: Im Konstruktor wird eine lokale Variable als Referenz auf das Instanzobjekt this
angelegt. Diese heißt üblicherweise thisObject
, that
oder instance
. Alle Methoden, die der Instanz im Konstruktor hinzugefügt werden, schließen diese Variable ein. Sie ist darin auch dann verfügbar, wenn sie als Event-Handler oder mit Verzögerung in einem anderen Kontext ausgeführt werden. Folgendes Beispiel demonstriert beide Fälle:
function Konstruktor() { // Referenz auf das Instanzobjekt anlegen var thisObject = this; // Weitere private Objekte var privateVariable = "privat"; // Öffentliche Eigenschaften this.eigenschaft = "wert"; this.start = function () { alert("start() wurde aufgerufen\n" + "Instanz-Eigenschaft: " + thisObject.eigenschaft); setTimeout(thisObject.verzögert, 500); }; this.verzögert = function () { alert("verzögert() wurde aufgerufen\n" + "Instanz-Eigenschaft: " + thisObject.eigenschaft); }; this.handler = function () { alert("handler wurde aufgerufen\n" + "Element, das den Event behandelt: " + this + "\n" + "Instanz-Eigenschaft: " + thisObject.eigenschaft); }; // Hier im Konstruktor kann this noch verwendet werden document.getElementById("button").onclick = this.handler; } var instanz = new Konstruktor(); instanz.start();
Der zugehörige Button-Code lautet wieder:
<button id="button">Button, der auf Klick reagiert</button>
Wichtig ist hier die Unterscheidung zwischen this
und thisObject
. this
zeigt in den drei Methoden start
, verzögert
und handler
auf drei unterschiedliche Objekte. In start
zeigt es auf das Instanzobjekt instanz
, in verzögert
auf window
und in handler
auf das Button-Element. thisObject
hingegen ist die eingeschlossene Variable, die auf das Instanzobjekt zeigt – und zwar in allen drei Methoden.
Dank Closures können wir zum Zugriff auf die Instanz auf das uneindeutige this
verzichten. Stattdessen nutzen wir die eigene Variable thisObject
.
Mit Function Binding lassen sich bei Bedarf Closures erzeugen, die den this
-Kontext einer Funktion festlegen. Heraus kommt eine Wrapper-Funktion, die die ursprüngliche Funktion im gewünschten Kontext aufruft.
Die gezeigte Verschachtelung ist eine effektiver, aber folgenschwerer Trick, um die Verfügbarkeit von Objekten zu gewährleisten. Sie liegt nahe, wenn sowieso mit privaten Objekten gearbeitet wird und deshalb alle Modul- bzw. Instanzmethoden verschachtelt werden. Sie funktioniert nicht bei einfachen Object
-Literalen und bei der Nutzung von Prototypen. Glücklicherweise erlaubt die funktionale Natur von JavaScript, Funktionen und damit Closures zur Laufzeit anzulegen und den this
-Kontext einer Funktion bei ihrem Aufruf festzulegen.
this
-Kontext erzwingen mit call
und apply
Die Methoden call
und apply
von Funktionsobjekten erlauben das Erzwingen des Kontextes beim Funktionsaufruf.
Da Funktionen in JavaScript Objekte erster Klasse sind, können sie selbst Methoden besitzen. Zwei der vordefinierten Methoden von Funktionsobjekten sind call und apply. Diese rufen die zugehörige Funktion auf und erlauben es zusätzlich, den Kontext beim Aufruf einer Funktion explizit anzugeben. Das Objekt, auf das this
innerhalb der Funktion zeigt, wird nicht mehr nach den üblichen Regeln bestimmt. Stattdessen zeigt this
auf das Objekt, das call
bzw. apply
übergeben wird.
Auf diese Weise können wir jede beliebige Funktion im Kontext eines beliebigen Objekts ausführen, auch ohne dass die Funktion am angegebenen Objekt hängt:
var objekt = { eigenschaft: "Objekteigenschaft" }; function beispielFunktion() { // this zeigt nun auf objekt alert(this.eigenschaft); } // Erzwinge Kontext mit apply, setze objekt als Kontext beispielFunktion.call(objekt);
Der Unterschied zwischen call
bzw. apply
ist der dritte Parameter. Über diesen können der aufgerufenen Funktion Parameter durchgereicht werden. Während call
die Parameter für den Aufruf einzeln erwartet, also als zweiter, dritter, vierter und so weiter, erwartet apply
alle Parameter in einem Array.
var objekt = { eigenschaft: 0 }; function summe(a, b, c) { this.eigenschaft = a + b + c; alert("Ergebnis: " + this.eigenschaft); } // call: Übergebe drei einzelne Parameter summe.call(objekt, 1, 2, 3); // apply: Übergebe drei Parameter in einem Array summe.apply(objekt, [1, 2, 3]);
Im Beispiel werden call
und apply
genutzt, um die Funktion summe
im Kontext von objekt
auszuführen. Die Funktion nimmt drei Parameter entgegen, summiert diese und speichert sie in einer Objekteigenschaft. Der Effekt der beiden call
- und apply
-Aufrufe ist derselbe, summe
bekommt drei Parameter.
call
und apply
alleine helfen uns zur Lösung der Kontextproblematik noch nicht weiter. Sie stellen allerdings das Kernstück der Technik dar, die im Folgenden beschrieben wird.
bind
und bindAsEventListener
bind
/bindAsEventListener
vereinen funktionale Programmierung, Closures und call
/apply
, um eine gewünschte Funktion im angegebenen Kontext aufzurufen.
bind
und bindAsEventListener
sind zwei verbreitete Helferfunktionen, die durch das JavaScript-Framework Prototype bekannt wurden. Sie werden dem Prototyp von Funktionsobjekten (Function.prototype
) hinzugefügt. Daraufhin besitzt eine beliebige Funktion die Methoden funktion.bind(…)
und funktion.bindAsEventListener(…)
.
bind
und bindAsEventListener
erzeugen dynamisch eine neue Funktion, die die ursprüngliche Funktion umhüllt und an dessen Stelle verwendet wird. Man spricht von Wrapper-Funktionen.
Der Sinn dieser Kapselung ist in erster Linie die Korrektur des this
-Kontextes. Dazu werden die besagten call
und apply
verwendet. bind
und bindAsEventListener
nehmen – genauso wie call
/apply
– als ersten Parameter das Objekt an, das als Kontext verwendet wird.
bind
ermöglicht es zudem, der ursprünglichen Funktion Parameter zu übergeben, sodass darin nicht nur ein Objekt über this
, sondern viele weitere Objekte verfügbar sind. Das Erzeugen einer neuen Wrapper-Funktion, die eine andere mit vordefinierten Parametern aufruft, nennt sich Currying.
Die Funktionen bind
und bindAsEventListener
sehen kommentiert so aus:
Function.prototype.bind = function () { |
Erweitere alle Funktionsobjekte um die Methode bind über den Prototyp aller Funktionsobjekte. |
var originalFunction = this; |
Speichere die gegenwärtige Funktion in einer Variablen, damit in der Closure ein Zugriff darauf möglich ist. |
var args = Array.prototype.slice.call(arguments); |
bind nimmt eine beliebige Anzahl von Parametern entgegen. Sie sind nicht in der Parameterliste aufgeführt, denn es wird arguments zum Zugriff darauf verwendet. Diese Liste wird zunächst in einen echten Array umgewandelt, indem eine Array-Methode darauf angewendet wird. |
var contextObject = args.shift(); |
Entnehme dem Array den ersten Parameter. Das ist das Objekt, in dessen Kontext die Funktion ausgeführt werden soll. args enthält nun die restlichen Parameter. |
var wrapperFunction = function () { |
Erzeuge eine verschachtelte Funktion, die als Closure wirkt. Die Closure schließt originalFunction , args und contextObject ein. |
return originalFunction.apply(contextObject, args); |
Innerhalb der erzeugten Funktion: Rufe die ursprüngliche Funktion im Kontext des Objekts auf, reiche dabei die restlichen Parameter durch und gib den Rückgabewert der Funktion zurück. |
}; |
|
return wrapperFunction; |
Gib die soeben erzeugte Wrapper-Funktion zurück. |
}; |
Function.prototype.bindAsEventListener = function (contextObject) { |
Erweitere alle Funktionsobjekte um die Methode bindAsEventListener über den Prototyp aller Funktionsobjekte. Die Funktion nimmt nur einen Parameter entgegen: Das Objekt, in dessen Kontext die Funktion ausgeführt werden soll. |
var originalFunction = this; |
Speichere die gegenwärtige Funktion in einer Variablen, damit in der Closure ein Zugriff darauf möglich ist. |
var wrapperFunction = function (event) { |
Erzeuge eine verschachtelte Funktion, die als Closure wirkt. Die Closure schließt contextObject und originalFunction ein. |
var event = event || window.event; |
Vereinheitliche den Zugriff auf das Event-Objekt. Dieses wird der Handler-Funktion entweder als Parameter übergeben (hier event ) oder steht im Internet Explorer unter window.event zur Verfügung. |
return originalFunction.call(contextObject, event); |
Rufe die ursprüngliche Funktion im Kontext des Objekts auf, reiche dabei das Event-Objekt durch und gib den Rückgabewert der Funktion zurück. |
}; |
|
return wrapperFunction; |
Gib die soeben erzeugte Wrapper-Funktion zurück. |
}; |
Ohne Kommentare und Variablen, die bloß der Lesbarkeit dienen, sehen die beiden Funktionen wie folgt aus:
Function.prototype.bind = function () { var method = this, args = Array.prototype.slice.call(arguments), object = args.shift(); return function () { return method.apply(object, args); }; }; Function.prototype.bindAsEventListener = function (object) { var method = this; return function (event) { return method.call(object, event || window.event); } };
bind
ist die allgemeinere Funktion, die z.B. bei Timeouts, Intervallen und Callbacks Verwendung findet. bindAsEventListener
ist die Nutzung einer Funktion als Event-Handler zugeschnitten.
Der folgenden Code zeigt, wie sich die obigen Beispiele mithilfe von bind
und bindAsEventListener
lösen lassen, sodass this
immer auf das richtige Objekt zeigt.
var Modul = { eigenschaft: "Eigenschaftswert", start: function () { alert("start wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); setTimeout(this.verzögert.bind(this), 100); document.getElementById("button").onclick = this.handler.bindAsEventListener(this); }, verzögert: function () { alert("verzögert wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); }, handler: function (e) { alert("handler wurde aufgerufen\n" + "Event-Objekt: " + e + "\n", "this.eigenschaft: " + this.eigenschaft); } }; Modul.start();
function Konstruktor() { } Konstruktor.prototype = { eigenschaft: "Eigenschaftswert", start: function () { alert("start wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); setTimeout(this.verzögert.bind(this), 100); document.getElementById("button").onclick = this.handler.bindAsEventListener(this); }, verzögert: function () { alert("verzögert wurde aufgerufen\n" + "this.eigenschaft: " + this.eigenschaft); }, handler: function (e) { alert("handler wurde aufgerufen\n" + "Event-Objekt: " + e + "\n", "this.eigenschaft: " + this.eigenschaft); } }; var instanz = new Konstruktor(); instanz.start();
Hier mag zunächst die Schreibweise this.verzögert.bind(this)
und this.handler.bindAsEventHandler(this)
irritieren. Diese Aufrufe hüllen verzögert
und handler
in Closures, welche die beiden Methoden im Kontext der Instanz ausführen.
Function Binding und Currying mit solchen Helferfunktionen erlaubt das präzise Anlegen von Closures und ist ein wichtiges Werkzeug, um Verfügbarkeitsprobleme zu lösen. Im Gegensatz zu einer großen Kapselfunktion, in der alle weiteren Funktionen als Closures angelegt werden, ist Binding punktgenauer und vielseitiger – es funktioniert auch bei einfachen Object
-Literalen und Methoden, die über den Prototyp hinzugefügt werden.
Nutzen Sie die ausgereiften Binding- und Currying-Funktionen aus bekannten JavaScript-Bibliotheken.
Die hier vorgestellten bind
und bindAsEventListener
sind zwei sehr einfache Umsetzungen. Es gibt viele weitere, die mehr Komfort bieten und auf Performance optimiert sind. Die tatsächlichen Funktionen im Prototype-Framework ermöglichen es beispielsweise, dass Parameter der Wrapper-Funktion an die ursprüngliche Funktion weitergegeben werden. Die oben beschriebene bind
-Funktion gibt lediglich die Parameter weiter, die beim bind
-Aufruf angegeben wurden. bindAsEventListener
aus Prototype erlaubt ebenfalls beides, während die obige Funktion nur das Event-Objekt weitergeben kann.
Neben Prototype bieten auch andere Frameworks und funktionale Bibliotheken Function Binding und Currying. Das Konzept ist dasselbe, die Umsetzungen und Benennungen unterscheiden sich im Detail. Das Framework Mootools bietet etwa bind und bindWithEvent.