JavaScript/Tutorials/OOP/Objektverfügbarkeit und this
- 15min
- mittel
- Kenntnisse in
● Objekte und ihre Eigenschaften
Schon im ersten Kapitel wurde gezeigt, wie man mit dem Schlüsselwort this einen internen Zeiger auf das aktuelle Objekt setzen kann.
Dieser Artikel soll zeigen, welche Probleme im globalen Scope auftreten können.
Inhaltsverzeichnis
this
Das Schlüsselwort this wird zu einem Wert aufgelöst und zeigt in der Regel auf ein Objekt. Worauf es zeigt, hängt ganz vom Kontext ab.
this
standardmäßig undefined
. Sie müssen deshalb neue Eigenschaften und Methoden immer mit new
einleiten.
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();
Information
this
zeigt standardmäßig auf das globale Objekt window
. Innerhalb einer Methode, die über objekt.funktion()
aufgerufen wird, zeigt es jedoch auf objekt
.In diesen beiden Fällen bietet this
wenig Nutzen, denn wir könnten genauso window
schreiben.
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:
let 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 und die Funktion verschoben wird, 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.");
}
};
let maunzi = new Katze('Maunzi');
maunzi.zeigePfoten();
let 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.
Methoden in anderen Kontexten ausführen
Information
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:
- Beim Event-Handling, wenn die Funktion als Event-Handler registriert wird. Beim Unobtrusive JavaScript ist es üblich, dass Methoden eines Moduls oder einer Instanz als Event-Handler dienen (siehe Grundlagen zur Ereignisverarbeitung).
this
zeigt in Handler-Funktionen auf das Elementobjekt, bei dem das Ereignis verarbeitet wird – siehethis
beim Event-Handling. Dadurch werden die Methoden außerhalb des Modul- bzw. Instanzkontextes ausgeführt. - Beim Aufrufen der Funktion mit
setTimeout
odersetInterval
. Die verzögert bzw. wiederholt ausgeführte Funktion verliert den Bezug zum Ursprungsobjekt, dennthis
verweist darin auf das globale Objektwindow
. In vielen Fällen ist der Zugriff auf das Modul bzw. die Instanz notwendig. - Bei der Übergabe einer Funktion als Parameter (z. B. als Callback-Funktion), beim Speichern in einer Variablen und dergleichen. In diesen Fällen zeigt gibt es oftmals keinen spezifischen Kontext, sodass
this
den Wertnull
enthält oder in altem Code, der noch nicht im strict mode läuft, auf daswindow
-Objekt verweist.
this-Problem bei einfachen Modulen
Das folgende Beispiel demonstriert das Problem im Falle eines einfachen Moduls mit dem Object-Literal:
let Modul = {
eigenschaft : "Eigenschaftswert",
start : function () {
// Funktioniert:
console.log("start wurde aufgerufen: this.eigenschaft = ", this.eigenschaft);
setTimeout(this.verzögert, 100);
document.getElementById("button").onclick = this.handler;
},
verzögert : function () {
// Fehler: this verweist auf window oder ist null
console.log("verzögert wurde aufgerufen: this.eigenschaft = ", this.eigenschaft);
},
handler : function (e) {
// Fehler: this verweist auf das Element, dem der Event-Handler anhängt
console.log("handler wurde aufgerufen: this.eigenschaft = ", this.eigenschaft);
}
};
Modul.start();
Das zugehörige HTML:
<button id="button">Button, der auf Klick reagiert</button>
this-Problem bei Prototypen und Instanzmethoden
Dasselbe mit einem Konstruktor, einem Prototyp und einer Instanz:
function Konstruktor () {}
Konstruktor.prototype = {
eigenschaft : "Eigenschaftswert",
start : function () {
// Funktioniert:
console.log("start wurde aufgerufen: this.eigenschaft = ", this.eigenschaft);
setTimeout(this.verzögert, 100);
document.getElementById("button").onclick = this.handler;
},
verzögert : function () {
// Fehler: this verweist auf window oder ist null
console.log("verzögert wurde aufgerufen: this.eigenschaft = ", this.eigenschaft);
},
handler : function (e) {
// Fehler: this verweist auf das Element, dem der Event-Handler anhängt
console.log("handler wurde aufgerufen: this.eigenschaft = ", this.eigenschaft);
}
};
let 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.
Wenn Sie die class
-Syntax verwenden, um eine Klasse zu definieren, wird die verzögert
-Methode definitiv mit einem Zugriff auf null
abbrechen, weil Code innerhalb einer Klasse grundsätzlich im strikten Modus ausgeführt wird.
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.
Closures
Information
Funktionen sind in JavaScript echte Objekte. Sie haben Eigenschaften, sie haben einen Prototypen, und sie können als Werte verwendet werden. Eine wichtige Eigenschaft von Funktionen ist auch, dass ein Funktionsobjekt jederzeit erzeugt werden kann, auch innerhalb von anderen Funktionen.
Beim Erzeugen eines solchen Funktionsobjekt passiert etwas sehr wichtiges. Im Moment der Erzeugung ist ein bestimmter Geltungsbereich (Scope) von Variablen aktiv. Sie können sich diesen Geltungsbereich als ein internes Objekt vorstellen, dessen Eigenschaften die lokalen Variablen dieser Funktion sind. Dieses interne Objekt wird – ebenfalls intern und für Sie als Programmierer nicht sichtbar – als Eigenschaft des erzeugten Funktionsobjekts gespeichert. Wenn eine Funktion auf Variablen zugreift, die nicht in ihr deklariert wurden, nutzt JavaScript diesen internen Verweis, um auf den Geltungsbereich der äußeren Funktion zuzugreifen.
Ein solches Paar aus Funktion und Geltungsbereich nennt man eine Closure. Der Name kommt von der Vorstellung, dass die innere Funktion den Geltungsbereich der äußeren Funktion mit „einschließt“. Da jede JavaScript-Funktion einen Verweis auf den Geltungsbereich enthält, in dem sie entstanden ist – im Zweifelsfalle der globale Geltungsbereich – handelt es sich genau genommen bei jeder JavaScript-Funktion um eine Closure.
Durch dieses Einschließen der Variablen kann man Werte in Funktionen verfügbar machen, die darin sonst nicht oder nur über Umwege zugänglich wären. Closures sind ein 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 aeussereFunktion () {
// Definiere eine lokale Variable
let 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
innereFunktion();
}
aeussereFunktion();
Das Beispiel zeigt, dass die innere Funktion Zugriff auf die Variablen der äußeren Funktion hat. Die entscheidenden Punkte bei Closures ist jedoch andere.
Funktionen sind – wie gesagt – Objekte und damit Werte, die man speichern kann. Man kann sie auch anderen Funktionen als Parameter übergeben, oder aus einer Funktion als Wert zurückgeben. Die Closure, die von jeder Funktion gebildet wird, bleibt dabei unverändert enthalten. Ein Beispiel:
// Eine Funktion, die eine andere Funktion als Parameter erwartet
function funktion1(helfer) {
let wert = 1;
let hilfe = helfer(); // Aufruf der Helfer-Funktion
console.log(hilfe); // Ausgabe: 2
}
function funktion2() {
let wert = 2;
function helfer() { // Die Helfer-Funktion greift auf die wert-Variable von funktion2 zu!
return wert;
}
funktion1(helfer); // funktion2 ruft funktion1 auf!
}
funktion2();
Das Funktionsobjekt, das die Funktion helfer
repräsentiert, wird als Parameter verwendet. Wenn funktion2
die Funktion funktion1
aufruft und helfer
als Parameter übergibt, dann bleibt der Elternscope, der in der Closure von helfer
liegt, erhalten. Wenn helfer
nun innerhalb von funktion1
aufgerufen wird, dann sieht helfer
nach wie vor die wert
Variable aus funktion2
, gibt also nicht 1, sondern 2 zurück.
Ein weiterer Aspekt von Closures ist, dass sie die Lebensdauer von Geltungsbereichen verändern können. Im vorigen Beispiel hat funktion2 funktion1 aufgerufen, d. h. war noch aktiv, während funktion1 die helfer-Funktion verwendet hat. Ändern wir das Beispiel etwas ab:
// Eine Funktion, die eine andere Funktion als Parameter erwartet
function funktion1(helfer) {
let wert = 1;
let hilfe = helfer(); // Aufruf der Helfer-Funktion
console.log(hilfe); // Ausgabe: 2
}
function funktion2() {
let wert = 2;
function helfer() { // Die Helfer-Funktion greift auf die wert-Variable von funktion2 zu!
return wert;
}
return helfer; // helfer wird als Wert zurückgegeben!
}
let help = funktion2();
funktion1(help);
JavaScript löscht alle Objekte aus dem Speicher, auf die keine Verweise mehr existieren. Das geschieht automatisch, ohne Ihr Zutun. Auch das Objekt, das den Geltungsbereich einer Funktion darstellt, verschwindet so, wenn der Programmablauf eine Funktion verlässt.
Es wäre also normalerweise so, dass der Geltungsbereich von funktion2
verschwinden würde, sobald die Funktion zurückkehrt. Aber nun gibt sie helfer
zurück, eine Closure. Diese Closure enthält eine interne Referenz auf den Geltungsbereich von funktion2
, und sorgt so dafür, dass dieser Geltungsbereich nicht elternlos wird, wenn funktion2 endet. Die Variablen von funktion2
bleiben eingeschlossen und konserviert, in der »Closure«. Für die helfer
Funktion ist nicht erkennbar, ob ihre Elternfunktion noch läuft oder nicht.
Deshalb hat die Closure auch lange nach dem Ablauf der äußeren Funktion immer noch Zugriff auf deren Variablen. Vorausgesetzt ist, dass die Closure irgendwo gespeichert wird, sodass die Speicherverwaltung von JavaScript sie nicht löscht und sie zu einem späteren Zeitpunkt ausgeführt werden kann. Im ersten Beispiel war die innere Funktion nichts anderes wie eine lokale Variable, die zwar Zugriff auf die Variablen der äußeren Funktion hatte, aber bei deren Beendigung selbst verfallen ist.
Eine weitere Möglichkeit, die innere Funktion zu speichern, ist das Registrieren als Event-Handler. Dabei wird das Funktionsobjekt mittels addEventListener
im DOM abgelegt und bleibt damit über die Ausführung der äußeren Funktion hinweg solange erhalten, bis die Registrierung gelöscht oder das Button-Objekt aus dem DOM entfernt wird.
function registerClickHandler() {
let variable = "wert";
// Lege eine verschachtelte Funktion an
function closure () {
alert("Wert der Variablen aus der äußeren Funktion: " + variable);
};
// Registriere die Closure-Funktion als Event-Handler
document.getElementById("button").addEventListener("click", closure);
}
registerClickHandler();
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. registerClickHandler
wird schon längst nicht mehr ausgeführt, aber variable
wurde in die Closure eingeschlossen.
Zusammengefasst haben wir folgendes Schema zur Erzeugung einer Closure:
- Beginn der Ausführung der äußeren Funktion
- Lokale Variablen werden definiert
- Innere Funktion wird definiert
- Innere Funktion wird außerhalb gespeichert, sodass sie erhalten bleibt
- Ende der Ausführung der äußeren Funktion
- Unbestimmte Zeit später: Innere Funktion (Closure-Funktion) wird ausgeführt
Siehe auch: SELF-Forum: JavaScript bietet mit Closures einige Möglichkeiten, Daten zwischen Funktionen zu teilen, ohne globale Variablen zu benötigen. von Rolf B. vom 09.11.2020
Anwendung von Closures: Zugriff auf das Modul bzw. die Instanz ermöglichen
Wie helfen uns Closures nun beim this-Problem weiter? Indem wir die Referenz auf this
in einer Variablen konservieren und innerhalb einer anonymen Funktion verwenden:
function Konstruktor () {}
Konstruktor.prototype = {
eigenschaft : "Eigenschaftswert",
start : function () {
// Funktioniert:
alert("start wurde aufgerufen\n" +
"this.eigenschaft: " + this.eigenschaft);
let t = this; // Variable in diesem Scope definieren
setTimeout(function () { t.verzögert(); }, 100); // t ist innerhalb der anonymen Funktion bekannt
document.getElementById("button").onclick = this.handler;
},
verzögert : function () {
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);
}
};
let instanz = new Konstruktor();
instanz.start();
Module: Verzicht auf this zugunsten des Revealing Module Patterns
Information
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:
let Modul = (function () {
// Private Objekte
let 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.
let Modul = (function () {
// Private Objekte
let 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
let 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
.
Konstruktoren/Instanzen: Methoden im Konstruktor verschachteln
Information
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
let thisObject = this;
// Weitere private Objekte
let 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;
}
let 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
.
Function Binding: Closures automatisiert erzeugen
Information
Die gezeigte Verschachtelung ist ein 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
Information
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 Objektes ausführen, auch ohne dass die Funktion am angegebenen Objekt hängt:
let 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.
let 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
Information
bind
vereint funktionale Programmierung, Closures und call/apply, um eine gewünschte Funktion im angegebenen Kontext aufzurufen.Die Function.prototype.bind()-Methode erzeugt 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
nimmt – 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.
Function.prototype.bind = function () {
let originalFunction = this;
let args = Array.prototype.slice.call(arguments);
let contextObject = args.shift();
let wrapperFunction = function () {
return originalFunction.apply(contextObject, args);
};
return wrapperFunction;
};
bind
über den Prototyp aller Funktionsobjekte.Speichere die gegenwärtige Funktion in einer Variablen, damit in der Closure ein Zugriff darauf möglich ist.
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.
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.
Erzeuge eine verschachtelte Funktion, die als Closure wirkt. Die Closure schließt originalFunction
, args
und contextObject
ein.
Innerhalb der erzeugten Funktion: Rufe die ursprüngliche Funktion im Kontext des Objektes auf, reiche dabei die restlichen Parameter durch und gib den Rückgabewert der Funktion zurück.
Quellen
Information
- ↑ Mathias Schäfer: Organisation von JavaScripten: Module und Kapselung
setTimeout
steht ein Funktionsobjekt, welches in seinem Scope die zuvor im äußeren Scope definierte Variablet
kennt. Dadurch kann die Methodeverzögert
mit dem richtigenthis
-Kontext aufgerufen werden.