Hallo
Ich bin aus return this nicht ganz schlau geworden. […] Ich nehme an das sich dieses this auf die Funktion die es umgibt bezieht.
Der Wert der Funktionsvariable this
hängt unter anderem davon ab, um was für eine Art von Funktion es sich handelt, und ganz besonders auch davon, auf welche Weise die Funktion aufgerufen wurde. Das bedeutet, die Frage nach dem Wert von this
kann nicht pauschal beantwortet werden, sondern nur bezogen auf konkrete Fälle.
Jedenfalls hast du in deiner Frage ein für das Verständnis von this
relevantes Stichwort bereits gegeben, nämlich das Wort Umgebung. Oder etwas anders ausgedrückt, das Wort Kontext. Allerdings geht es hier nicht wie du dachtest um die Umgebung, beziehungsweise den Kontext von this
, sondern um den Kontext der Funktion, der durch this
repräsentiert wird.
Das mag vielleicht kompliziert klingen, ist aber eigentlich nicht schwer zu verstehen. Insbesondere dann nicht, wenn man sich vergegenwärtigt, wie die Welt ohne this
aussieht. Nehmen wir als Beispiel also einmal eine gewöhnliche Funktion und fragen uns, welche Informationen innerhalb der Funktion zur Verfügung stehen, solange wir nicht auf this
zugreifen.
function add (a, b) {
return a + b;
}
console.log(add(2, 3)); // 5
Wenn wir diese Funktion betrachten, dann stellen wir fest, dass uns innerhalb der Funktion nur zwei Informationen über die Außenwelt zur Verfügung stehen, wobei es sich bei der ersten Information um die Werte handelt, die wir der Funktion bei ihrem Aufruf als Argumente übergeben, also die Werte, welche wir von außen in die Funktion hineinreichen.
Die zweite Information die wir haben ist die lexikalische Umgebung, in der die Funktion definiert wurde. Das heißt, unsere Funktion besitzt eine unsichtbare Verbindung zum umgebenden Quelltext, die es uns ermöglicht, auf Variablen, Konstanten oder andere Funktionen zuzugreifen, die in einem der umgebenden Gültigkeitsbereiche definiert wurden.
const value = 5;
function getValue ( ) {
return value;
}
console.log(getValue( )); // 5
Ein Beispiel hierfür wäre diese Funktion, die innerhalb ihres Körpers eine Konstante referenziert, die in der äußeren lexikalischen Umgebung der Funktion definiert wurde, und nicht in der Funktion selbst. Im Ergebnis bleibt also festzuhalten, dass eine Funktion abgesehen von diesen beiden Informationen nichts über das Programm weiß, dessen Bestandteil sie ist.
Im Sinne des funktionalen Programmierparadigmas wäre daran auch nichts auszusetzen, sondern aus diesem Blickwinkel betrachtet ist es sogar erwünscht, dass eine Funktion nur mit den Werten arbeitet, die ihr als Argumente übergeben wurden, ganz so, wie es die Funktion add
aus unserem ersten Beispiel tut. Für solche Funktionen spielt es keine Rolle, in welchem Zusammenhang zum restlichen Programm sie stehen, ein Bedarf an weiteren Informationen über den Kontext ist hier also nicht gegeben.
Allerdings stellt die funktionale Programmierung nur eine Möglichkeit dar, ein Programm zu strukturieren, und wenn wir uns der objektorientierten Programmierung zuwenden, dann stellen wir fest, dass Funktionen dort nicht nur isoliert betrachtet werden können, sondern dass sie dort Teil einer größeren logischen Einheit sind, nämlich von Objekten, deren Methoden sie sind.
Das heißt, aus Sicht einer Methode ist es nicht nur von Bedeutung, welche Werte ihr beim Aufruf übergeben wurden oder in welcher lexikalischen Umgebung sie definiert wurde, sondern es ist gegebenenfalls auch wichtig zu wissen, auf welchem Objekt sie operiert.
Diese zusätzlichen Informationen über den Kontext einer Funktion müssen also in irgendeiner Form zugänglich gemacht werden, und hier kommt nun die Funktionsvariable this
ins Spiel.
const object = {
print ( ) {
console.log(typeof this);
}
};
object.print( ) // object
Wenn wir wie in diesem Beispiel auf einem Objekt eine Methode definieren und diese Methode dann über einen Elementausdruck aufrufen, die Funktion also beim Aufruf über das Objekt referenzieren, dessen Methode sie ist, dann wird this
mit einer Referenz auf ebendieses Objekt initialisiert, sodass innerhalb der Methode direkt darauf zugegriffen werden kann.
Nun ist es natürlich so, dass wir – bezogen auf das Beispiel oben – das in der Konstante mit dem Bezeichner object
hinterlegte Objekt aus der Methode heraus nicht zwingend über this
hätten referenzieren müssen, sondern wir hätten dieses Objekt auch direkt ansprechen können.
Das heißt, wirklich interessant wird es eigentlich erst dann, wenn wir eine Methode auf einem Objekt aufrufen, ohne dass die Methode auch tatsächlich auf diesem Objekt definiert ist, also zum Beispiel in dem Fall, dass die Methode von einem Prototypen an dieses Objekt vererbt wurde.
const prototype = {
print ( ) {
console.log(this.number);
}
}
const a = Object.create(prototype, {
'number' : {value : 3}
});
a.print( ); // 3
const b = Object.create(prototype, {
'number' : {value : 5}
});
b.print( ); // 5
In diesem Beispiel haben wir zunächst ein Objekt erstellt und darauf eine Methode definiert, so wie schon im letzten Beispiel. Aber hier rufen wir die Methode nicht auf diesem Objekt auf, sondern wir erstellen mittels Object.create
zwei weitere Objekte, als deren Prototyp wir das zuerst erzeugte Objekt bestimmen. Wenn wir nun auf diesen beiden Objekten die geerbte Methode aufrufen, dann zeigt this
mal auf das eine und mal auf das andere Objekt. Das heißt, wir können Methoden mittels this
auf eine Weise definieren, sodass sie für eine Vielzahl an Objekten verwendet werden können, und nicht nur für ein spezifisches Objekt.
Daraus folgt aber natürlich, dass der Wert von this
nicht bereits zum Zeitpunkt der Definition einer Funktion festgelegt werden kann, sprich, die Bindung von this
an einen Wert erfolgt grundsätzlich erst beim Aufruf der jeweiligen Funktion, und dementsprechend hängt der Wert von this
wesentlich davon ab, nach welchem Muster die Funktion aufgerufen wird.
Bereits gesehen haben wir, dass bei einem Methodenaufruf this
mit einer Referenz auf das Objekt initialisiert wird, als dessen Methode die Funktion aufgerufen wurde. Aber es gibt natürlich noch einige andere Möglichkeiten, wie eine Funktion aufgerufen werden kann …
function a ( ) {
console.log(this === window);
}
a( ); // true
function b ( ) {
'use strict';
console.log(this === undefined);
}
b( ); // true
Bei gewöhnlichen Funktionsaufrufen wie in dem Beispiel oben zeigt this
im normalen Ausführungsmodus auf das globale Objekt, also wenn es sich bei der Ausführungsumgebung um einen Webbrowser handelt, auf das Objekt window
. Wird die Funktion hingegen im Strict Mode ausgeführt, dann wird this
mit dem primitiven Wert undefined initialisiert, was das in diesem Fall eigentlich wünschenswerte Verhalten darstellt, da hier keine spezifischen Kontextinformationen benötigt werden.
function Constructor ( ) {
'use strict';
console.log(typeof this);
}
const instance = new Constructor; // object
Wird eine Funktion etwa unter Verwendung des Operators new
als Konstruktor aufgerufen, dann zeigt this
innerhalb der Funktion auf das hierbei erzeugte Instanzobjekt, sodass auf diesem Objekt eigene Eigenschaften und Methoden definiert werden können.
Auch ist es möglich, eine Funktion explizit in einem bestimmten Kontext aufzurufen, zum Beispiel unter Verwendung der Funktionsmethoden call
und apply
, wie im folgenden Beispiel demonstriert.
function fn ( ) {
console.log(this.number);
}
fn.call({number : 3}); // 3
fn.apply({number : 5}); // 5
Hier wird die anfangs deklarierte Funktion zweimal aufgerufen, mal mit call
und mal mit apply
, wobei in beiden Fällen als erstes Argument ein Objekt mit einer Eigenschaft übergeben wird, dass dann an this
gebunden wird. Der Unterschied zwischen den beiden Methoden besteht übrigens darin, auf welche Art Argumente an die aufzurufende Funktion weitergeleitet werden. Die Methode call
nimmt die Argumente einzeln entgegen, während sie der Methode apply
in einem Array übergeben werden können.
Jedenfalls gibt es darüber hinaus noch ein paar andere Varianten des Aufrufs von Funktionen, auf die hier aber nicht näher eingegangen werden soll. Erwähnt werden soll an dieser Stelle nur noch, dass nicht alle Funktionen einen eigenen Kontext binden.
function outer ( ) {
const inner = ( ) => {
console.log(this.value);
};
inner( ); // 42
}
outer.call({value : 42});
Sogenannte Pfeilfunktionen, wie die Funktion, die in dem Beispiel oben der Konstante mit dem Bezeichner inner
zugewiesen wurde, binden grundsätzlich keinen eigenen Kontext, dass heißt, wenn innerhalb des Körpers einer solchen Funktion auf this
zugegriffen wird, dann wird der Kontext der äußeren lexikalischen Umgebung referenziert, welcher Wert das auch immer sein mag.
Also, soviel zu den Grundlagen. Nun wolltest du aber noch wissen, was es mit return this
auf sich hat, und diese Frage ist vor dem Hintergrund des bislang Gesagten relativ leicht zu beantworten.
const number = {
currentValue : 0,
add (value) {
this.currentValue += value;
return this;
},
subtract (value) {
this.currentValue -= value;
return this;
},
print ( ) {
console.log(this.currentValue);
}
};
number.add(3).subtract(2).add(4);
number.print( ); // 5
Wir hatten ja gesehen, dass bei einem Methodenaufruf die Kontextvariable this
mit einer Referenz auf das Objekt initialisiert wird, auf dem die Methode aufgerufen wurde.
Wenn die Methode nun this
zurückgibt, dann kann dieser Wert, also die Referenz auf das Objekt, für weitere Methodenaufrufe verwendet werden. Das heißt, die Aufrufe können wie in dem Beispiel oben verkettet werden, ohne dass das Objekt jedesmal über seinen Bezeichner referenziert werden muss.
Viele Grüße,
Orlok