Hallo MB
Ich hab mal unter anderem auf YouTube gesucht und gefunden. Das ist leider auf Englisch und beinhaltet viele neue Funktionen z.B. prototype oder call. Diese sind glaube ich in der aktuellen JS Version aufgelistet und gabs früher noch nicht.
Weder prototype
noch call
sind in irgendeiner Form neu, und bei prototype
handelt es sich zumindest auf Sprachebene auch nicht um eine Funktion, jedenfalls in der Regel nicht. Eine Ausnahme von der Regel ist allerdings Function.prototype
, dessen eigene Methode call
ist. Aber das ist in diesem Zusammenhang kaum relevant.
Die Eigenschaft prototype
von Funktionsobjekten dient der Implementierung prototypischer Vererbung und ihr Wert ist üblicherweise ein gewöhnliches Objekt; Wird eine Funktion als Konstruktor aufgerufen, also entweder durch den Operator new
oder unter Verwendung der Methode Reflect.construct
, dann wird von der Funktion automatisch ein neues gewöhnliches Objekt erzeugt und zurückgegeben.
Dieses erzeugte Objekt wiederum hat eine Verbindung zu der Eigenschaft prototype
des Konstruktors, das heißt, der Prototyp des erzeugten Objektes ist der Wert dieser Eigenschaft. Ist nun, wie dies standardmäßig der Fall ist, als Wert der Eigenschaft prototype
ein Objekt hinterlegt, dann können dessen Eigenschaften und Methoden auch über das neu erzeugte Instanzobjekt referenziert werden; Sie werden also an dieses Objekt vererbt.
Sehen wir uns das Ganze aber mal an einem Beispiel etwas genauer an, wobei wir zunächst eine Funktion definieren, die wir dann später als Konstruktor aufrufen werden; Weil abgesehen von Pfeilfunktionen prinzipiell alle selbstdefinierten Funktionen als Konstruktor verwendet werden können, spielt die Art der Definition dabei keine Rolle.
Das heißt, es ist unerheblich, ob die Funktion wie im folgenden Beispiel deklariert wird, oder ob sie als Ausdruck notiert und das Funktionsobjekt einer Variable, Konstante oder Objekteigenschaft als Wert zugewiesen wird. Zu beachten ist hier ersteinmal nur die allgemeine Konvention, den ersten Buchstaben des Funktionsbezeichners groß zu schreiben, wodurch die Absicht zum Ausdruck gebracht wird, diese Funktion als Konstruktor zu verwenden.
function Constructor ( ) {
'use strict';
this.property = 'value';
}
Wird eine Funktion als Konstruktor aufgerufen, dann wird wie gesehen ein neues Objekt erzeugt. Dieses Objekt kann innerhalb des Funktionskörpers über die Kontextvariable this
angesprochen werden, sprich, die Funktion wird beim Aufruf als Konstruktor im Kontext des Instanzobjektes ausgeführt.
Entsprechend können wie in dem Beispiel oben bereits bei der Ausführung des Konstruktors eigene Eigenschaften und Methoden auf dem erzeugten Objekt definiert werden. Hier also eine Eigenschaft mit dem Namen property
, welcher der der Wert 'value'
zugewiesen wird. Jede von diesem Konstruktor erzeugte Instanz besitzt demnach also selbst eine solche Eigenschaft.
Zu beachten ist hier darüber hinaus die Anweisung 'use strict'
, die dafür sorgt, dass die Funktion Constructor
im Strict Mode ausgeführt wird. Dies ist eine Vorsichtsmaßnahme um zu verhindern, dass versehentlich globale Variablen erzeugt werden, wenn die Funktion nicht als Konstruktor aufgerufen wird.
Denn im normalen Ausführungsmodus verweist this
bei einem gewöhnlichen Funktionsaufruf auf das globale Objekt, also üblicherweise window
, dessen Eigenschaften in der Regel gleichzeitig globale Variablen sind. Würde Constructor
also im normalen Ausführungsmodus aufgerufen und dabei das Schlüsselwort new
vergessen werden, dann würde eine neue globale Variable mit dem Bezeichner property
angelegt.
Im Strict Mode ist der Wert von this
bei einem normalen Funktionsaufruf hingegen nicht window
, sondern der primitive Wert undefined
. Das heißt, in dem Fall, dass beim Aufruf das Schlüsselwort new
vergessen wird, würde keine globale Variable angelegt, sondern die Zuweisung würde einen Fehler produzieren.
function Constructor ( ) {
if (!new.target) {
throw new TypeError('Constructor called without new');
}
this.property = 'value';
}
Zu diesem Zweck könnte man übrigens auch den Operator new.target
verwenden, der im Falle des Aufrufs als Konstruktor auf die aufgerufene Funktion verweist, hier also Constructor
, sonst jedoch den Wert undefined
zurückgibt. Hierbei handelt es sich allerdings in der Tat um ein relativ neues Feature, dass gegebenenfalls noch nicht von allen relevanten Ausführungsumgebungen unterstützt wird.
console.log(typeof Constructor.prototype); // object
Jedenfalls besitzt unser Konstruktor wie alle gewöhnlichen Funktionen eine Eigenschaft mit dem Namen prototype
, in der ein planes Objekt hinterlegt ist. Dieses Objekt besitzt standardmäßig nur eine eigene Eigenschaft, nämlich die Eigenschaft constructor
, welche eine Referenz auf das dazugehörige Funktionsobjekt enthält, hier also auf unsere Funktion Constructor
.
const value = Constructor.prototype.constructor;
console.log(value === Constructor); // true
Nun definieren wir auf dem Objekt, welches in der Eigenschaft Constructor.prototype
hinterlegt ist eine Methode, welche die Anweisung beinhaltet, den Wert der Eigenschaft property
des Objektes zurückzugeben, in dessen Kontext die Methode ausgeführt wird.
Constructor.prototype.method = function ( ) {
return this.property;
};
Dabei ist zu berücksichtigen, dass die Kontextvariable this
im Falle eines Methodenaufrufs immer auf das Objekt zeigt, auf dem die Funktion aufgerufen wurde. Das heißt, wenn der Aufruf über einen Elementausdruck nach dem Schema object.method()
erfolgt, dann ist der Wert von this
innerhalb der Funktion immer das Objekt, über das die Funktion beim Aufruf referenziert wurde.
const instance = new Constructor;
console.log(typeof instance); // object
Nachdem wir nun also eine Konstruktorfunktion erstellt haben und auf dem Objekt, welches in der Eigenschaft prototype
hinterlegt ist, eine Methode definiert haben, rufen wir die Funktion jetzt mittels des Operators new
als Konstruktor auf, sodass ein neues Objekt erzeugt und zurückgegeben wird, das in der Konstante mit dem Bezeichner instance
gespeichert wird.
Da der Aufruf von Constructor
durch den Operator new
erfolgt und wir hier keine Argumente an die Funktion übergeben, können wir uns die Notierung der runden Klammern nach dem Funktionsbezeichner sparen.
console.log(instance.hasOwnProperty('property')); // true
console.log(instance.property); // value
Unser auf diese Weise erzeugtes Instanzobjekt besitzt nun eine eigene Eigenschaft namens property
deren Wert der String 'value'
ist, da wir im Funktionskörper unseres Konstruktors die entsprechende Zuweisung vorgenommen haben. Aber das ist natürlich nicht alles.
console.log(instance.hasOwnProperty('method')); // false
const value = instance.method( );
console.log(value); // value
Denn wir haben auf dem Objekt Constructor.prototype
ja noch eine Methode mit dem Namen method
definiert. Diese Methode wird nun bei der Erzeugung des Instanzobjektes nicht auf dieses kopiert, weshalb die Überprüfung mittels hasOwnProperty
, ob es sich um eine eigene Eigenschaft handelt, entsprechend das Ergebnis false
ergibt; Aber sie kann dennoch auf der Instanz aufgerufen werden, da Constructor.prototype
der Prototyp dieses Objektes ist.
Das heißt, bei dem Versuch, die Methode method
auf instance
zu referenzieren wird intern zunächst geprüft, ob das Objekt instance
selbst über eine solche Methode verfügt. Was hier nicht der Fall ist. Da die Suche auf diesem Objekt also erfolglos bleibt, wird in einem zweiten Schritt nachgesehen, ob der Prototyp von instance
über eine entsprechende Methode verfügt.
const proto = Object.getPrototypeOf(instance);
console.log(proto === Constructor.prototype); // true
Da der Prototyp von instance
nun das Objekt Constructor.prototype
ist und weil auf diesem Objekt eine Methode mit dem Namen method
definiert ist, wird schließlich diese Methode referenziert und im Kontext des Objektes instance
aufgerufen, sodass im Ergebnis der Wert der Eigenschaft property
von instance
zurückgegeben wird. Die Methode method
wird also von Constructor.prototype
an instance
vererbt.
Nun haben wir in den Beispielen oben darüber hinaus zweimal die Methode hasOwnProperty
auf dem Instanzobjekt aufgerufen, bei der es sich ebenfalls nicht um eine eigene Methode dieses Objektes handelt, sondern um eine Methode, die eigentlich auf dem Objekt Object.prototype
definiert ist.
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')); // true
const proto = Object.getPrototypeOf(Constructor.prototype);
console.log(proto === Object.prototype); // true
Da Object.prototype
jedoch der Prototyp von Constructor.prototype
ist, und weil wie gesehen beim Zugriff auf eine Eigenschaft oder Methode auch die jeweiligen Prototypen durchsucht werden, wird die Methode hasOwnProperty
ebenso wie die Methode method
beim Aufruf auf instance
über die Prototypenkette referenziert.
Der Zweck der Eigenschaft prototype
von Funktionsobjekten ist also, allen von der Funktion erzeugten Objekten Methoden und Eigenschaften zur Verfügung zu stellen, ohne diese auf den Objekten selbst definieren zu müssen.
Darum spricht man hier auch von Differenzieller Vererbung, das heißt, ein Prototyp stellt die Funktionalität zur Verfügung, die bei allen von diesem Prototypen erbenden Objekten vorhanden sein soll, während auf den Instanzobjekten selbst nur die Unterschiede, also die Differenzen definiert werden müssen.
Eine etwas ausführlichere Erklärung zu dem Thema bietet der Artikel Vererbung im Wiki.
Es soll allerdings nicht verschwiegen werden, dass die Erzeugung von Objekten durch den Aufruf einer Funktion oder Klasse mittels new
nicht die einzige Möglichkeit darstellt, prototypische Vererbung zu implementieren.
const proto = {
method ( ) {
return this.property;
}
};
const instance = Object.create(proto);
instance.property = 'value';
console.log(instance.method( )); // value
Zu diesem Zweck kann nämlich auch die Methode Object.create
verwendet werden, welche ein neues planes Objekt erzeugt und als dessen Prototyp das Objekt installiert, welches der Methode als erstes Argument übergeben wurde.
Was nun die Methode call
angeht, ist zu erwähnen, dass diese auf Function.prototype
definiert ist, und da alle Funktionen von Function.prototype
erben, kann sie entsprechend auch auf allen Funktionen aufgerufen werden.
Die Methode call
ermöglicht es, eine Funktion in einem bestimmten Kontext aufzurufen. Das heißt, die Kontextvariable this
der auf diese Weise aufgerufenen Funktion wird mit dem Wert initialisiert, welcher der Methode call
als erstes Argument übergeben wurde.
function getThis (parameter) {
console.log(parameter);
return this;
}
const result = getThis.call('value', 'argument'); // argument
console.log(result); // value
Mit den weiteren an die Methode call
übergebenen Argumenten wird dann die Funktion aufgerufen, auf der die Methode ausgeführt wird, weshalb der Parameter in dem Beispiel oben mit dem String 'argument'
initialisiert wird.
Soll eine Funktion also explizit in einem bestimmten Kontext aufgerufen werden kann dies mit call
bewerkstelligt werden. Oder mit der Methode Function.prototype.apply, welche die Argumente für den Aufruf in Form eines Arrays entgegennimmt.
Gruß,
Orlok