Tutorials für JavaScript Patterns gesucht
bearbeitet von
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`{: .language-javascript} 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.
~~~ javascript
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`{: .language-javascript} 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'`{: .language-javascript} 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'`{: .language-javascript}, 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`{: .language-javascript} 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`{: .language-javascript} vergessen werden, dann würde eine neue globale Variable mit dem Bezeichner `property` angelegt.
Im *Strict Mode* ist der Wert von `this`{: .language-javascript} bei einem normalen Funktionsaufruf hingegen nicht `window`, sondern der primitive Wert `undefined`{: .language-javascript}. Das heißt, in dem Fall, dass beim Aufruf das Schlüsselwort `new`{: .language-javascript} vergessen wird, würde keine globale Variable angelegt, sondern die Zuweisung würde einen Fehler produzieren.
~~~ javascript
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`{: .language-javascript} 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.
~~~ javascript
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`.
~~~ javascript
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.
~~~ javascript
Constructor.prototype.method = function ( ) {
return this.property;
};
~~~
Dabei ist zu berücksichtigen, dass die Kontextvariable `this`{: .language-javascript} 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`{: .language-javascript} innerhalb der Funktion immer das Objekt, über das die Funktion beim Aufruf referenziert wurde.
~~~ javascript
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`{: .language-javascript} 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`{: .language-javascript} erfolgt und wir hier keine Argumente an die Funktion übergeben, können wir uns die Notierung der runden Klammern nach dem Funktionsbezeichner sparen.
~~~ javascript
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'`{: .language-javascript} ist, da wir im Funktionskörper unseres Konstruktors die entsprechende Zuweisung vorgenommen haben. Aber das ist natürlich nicht alles.
~~~ javascript
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`{: .language-javascript} 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.
~~~ javascript
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.
~~~ javascript
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](https://wiki.selfhtml.org/wiki/JavaScript/Vererbung) im Wiki.
Es soll allerdings nicht verschwiegen werden, dass die Erzeugung von Objekten durch den Aufruf einer Funktion oder Klasse mittels `new`{: .language-javascript} nicht die einzige Möglichkeite darstellt, prototypische Vererbung zu implementieren.
~~~ javascript
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`{: .language-javascript} der auf diese Weise aufgerufenen Funktion wird mit dem Wert initialisiert, welcher der Methode `call` als erstes Argument übergeben wurde.
~~~ javascript
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'`{: .language-javascript} 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](https://wiki.selfhtml.org/wiki/JavaScript/Objekte/Function/apply), welche die Argumente für den Aufruf in Form eines Arrays entgegennimmt.
Gruß,
Orlok