Nachfrage zu prototypischen Manipulationen
bearbeitet von
Hallo Felix
Irgendwie habe ich heute echt Probleme zu verstehen, was manche Leute mir sagen wollen. ;-)
> Nehmen wir an, ich hätte für ein älteres Objekt selbst eine `forEach`{: .language-javascript}-Methode gebastelt und sie über `Array.prototype`{: .language-javascript} an meine Arrays geflanscht.
Du meinst, für ein älteres *Projekt*?
> Nun, da der Standard eine solche Methode vorsieht, wäre dieses Vorgehen insofern unproblematisch, als dass ich auf diese Manipulation von `Array.prototype`{: .language-javascript} zugunsten der nun nativ vorhandenen Methode schlicht verzichten könnte.
Wenn die Frage lautet, ob man die native `forEach`-Methode ohne Netz und doppelten Boden benutzen kann, dann lautet die Antwort: Ja. Die wird von allen relevanten Ausführungsumgebungen [unterstützt](http://kangax.github.io/compat-table/es5/#test-Array_methods_Array.prototype.forEach).
> Die Reihenfolge der Parameter für die Callback-Funktion einmal außen vor.
>
> Nun ist es in der Tat aber so, dass ich diese Methode nicht `forEach`{: .language-javascript}, sondern `each` genannt habe. Momentan stört sich das vielleicht nicht, weil andere Entwickler sich vielleicht mit dem Entwurf für ECMA-Script 6
ECMAScript 5
> beschäftigt haben, und ein eventuell vorausgreifendes Polyfill schon mit dem richtigen Bezeichner `forEach`{: .language-javascript} erstellt haben - und mein `each` niemanden (mehr) juckt oder jucken wird.
Inwiefern deine Methode `each` *tatsächlich* jemanden juckt oder jucken wird, vermag ich natürlich nicht zu sagen, aber *prinzipiell* wäre das Vorgehen schon problematisch, denn wie du ja selbst sagst, handelt es sich bei deiner Methode nicht um ein *standardkonformes* Polyfill.
> Für die Zukunft
>
> Wie baue ich mir ein eigenes Array (nennen wir es MyArray), das ebenso prototypisch von Array erbt, Änderungen an `MyArray.prototype`{: .language-javascript} aber nur dort, nicht aber an `Array.prototype`{: .language-javascript} vorgenommen werden?
Sorry, irgendwie stehe ich etwas auf dem Schlauch, aber wenn ich dich richtig verstehe, dann willst du in etwa sowas wie das hier …
~~~ javascript
function MyArray ( ) {
// create array
}
MyArray.prototype.method = function ( ) {
// do something
};
const array = new MyArray;
array.method( );
~~~
… wobei der Prototyp von `MyArray.prototype` das Objekt `Array.prototype`{: .language-javascript} sein soll, damit die erzeugten Instanzen sowohl die selbstdefinierten Methoden als auch die eingebauten Methoden erben, ohne dass dabei `Array.prototype`{: .language-javascript} angefasst werden muss. Richtig?
Wenn das deine Frage sein sollte, dann habe ich dazu im Abschnitt [Selbstdefinierte Methoden](https://wiki.selfhtml.org/wiki/JavaScript/Objekte/Array/prototype#Selbstdefinierte_Methoden) meines Artikels ja schon einiges geschrieben. Aber ich kann gerne noch einige Punkte ergänzen, die dort vielleicht etwas zu kurz gekommen sind.
Zum Beispiel könnte man erklären, *warum* die oben gezeigte Variante mit dem Konstruktor unter den genannten Voraussetzungen nicht funktionieren kann.
~~~ javascript
MyArray.prototype = [ ];
~~~
Dabei wäre zunächst zu erwähnen, dass es natürlich möglich ist, ein Array als prototypisches Objekt des Konstruktors anzulegen, sodass die von dieser Funktion erzeugten Instanzen sowohl die auf diesem Objekt definierten eigenen Methoden, als auch die Methoden von `Array.prototype`{: .language-javascript} erben.
~~~ javascript
const instance = new MyArray;
console.log(Array.prototype.isPrototypeOf(instance)); // true
console.log(Array.isArray(instance)); // false
~~~
Aber das Objekt, das bei einem Aufruf via [[Construct]] erzeugt wird, in dessen Kontext die Funktion ausgeführt wird und das standardmäßig deren Rückgabewert ist, ist eben *kein* Array, sondern ein gewöhnliches Objekt.
Das heißt, einem auf diese Weise erzeugten Objekt fehlt die besondere Semantik von Arrays, es besitzt also weder eine Eigenschaft `length`, noch das besondere Verhalten beim schreibenden Zugriff auf Objekteigenschaften, wie es in der internen Methode [[DefineOwnProperty]] bestimmt ist.
~~~ javascript
function MyArray ( ) {
return Array.from(arguments);
}
const instance = new MyArray;
console.log(MyArray.prototype.isPrototypeOf(instance)); // false
~~~
Wird in der Konstruktorfunktion hingegen ein *anderes Objekt* als Rückgabewert bestimmt, als das Objekt in dessen Kontext die Funktion ausgeführt wird, dann wird zwar dieses Objekt zurückgegeben, aber es wird dabei nicht der Prototyp verändert. Das heißt, man bekäme hierbei nur ein normales Array, ohne dass dessen Prototyp auf `MyArray.prototype` gesetzt würde.
Es ist also egal wie man es dreht und wendet, man bekommt hier entweder nur ein ganz gewöhnliches Array, dessen interne Eigenschaft [[Prototype]] eine Referenz auf das Objekt `Array.prototype`{: .language-javascript} enthält, oder aber ein Objekt, das zwar über die gewünschte Prototypenkette verfügt, das aber selbst kein exotisches Arrayobjekt ist.
Wenn wir nun aus guten Gründen die Möglichkeit ignorieren, *nach* der Erzeugung eines Arrays ein Objekt in dessen Prototypenkette einzufügen, dann kann die gewünschte Funktionalität tatsächlich nur auf eine Weise implementiert werden, nämlich durch Erzeugung einer von Array abgeleiteten Klasse.
~~~ javascript
class MyArray extends Array {
method ( ) {
// do something
}
}
const instance = new MyArray;
console.log(Array.isArray(instance)); // true
~~~
Hier haben wir also eine Klasse `MyArray`, die vom Konstruktor `Array`{: .language-javascript} abgeleitet ist. Das heißt, die von dieser Klasse erzeugten Instanzen sind richtige Arrays, die sowohl die Methoden von `MyArray.prototype` als auch die Methoden von `Array.prototype`{: .language-javascript} erben.
~~~ javascript
console.log(MyArray.prototype.hasOwnProperty('method')); // true
console.log(MyArray.prototype.propertyIsEnumerable('method')); // false
~~~
Die im Körper der Klasse definierten Methoden werden dabei automatisch auf dem prototypischen Objekt der Funktion angelegt. Diese Form der Methodendefinition hat darüber hinaus den Vorteil, dass das interne Attribut [[Enumerable]] der Methode standardmäßig auf `false`{: .language-javascript} gesetzt wird.
~~~ javascript
class MyArray extends Array {
constructor ( ) {
super(...arguments);
console.log(this.length);
}
method ( ) {
// do something
}
}
const array = new MyArray(2, 4, 8); // 3
~~~
Optional kann innerhalb der Klasse die Pseudomethode `constructor` notiert werden, die im Prinzip wie ein gewöhnlicher Konstruktor funktioniert. Damit die Methode bei einer abgeleiteten Klasse aber auch im Kontext des Objektes ausgeführt wird, welches von der *Superklasse* erzeugt wird, hier also durch den Konstruktor `Array`{: .language-javascript}, muss diese innerhalb der Methode mit dem Keyword `super`{: .language-javascript} aufgerufen werden.
Im Ergebnis ist hier also `Array.prototype`{: .language-javascript} der Prototyp von `MyArray.prototype` und die erzeugten Instanzen sind native Arrays. Das heißt es können, vorzugsweise aber nicht zwingend innerhalb des Körpers der Klasse, beliebige Arraymethoden definiert werden, ohne dabei `Array.prototype`{: .language-javascript} zu manipulieren.
Für die Zunkunft ist das also das Mittel der Wahl, wenn es darum geht, eigene Methoden für Arrays zu definieren.
Viele Grüße,
Orlok
Nachfrage zu prototypischen Manipulationen
bearbeitet von
Hallo Felix
Irgendwie habe ich heute echt Probleme zu verstehen, was manche Leute mir sagen wollen. ;-)
> Nehmen wir an, ich hätte für ein älteres Objekt selbst eine `forEach`{: .language-javascript}-Methode gebastelt und sie über `Array.prototype`{: .language-javascript} an meine Arrays geflanscht.
Du meinst, für ein älteres *Projekt*?
> Nun, da der Standard eine solche Methode vorsieht, wäre dieses Vorgehen insofern unproblematisch, als dass ich auf diese Manipulation von `Array.prototype`{: .language-javascript} zugunsten der nun nativ vorhandenen Methode schlicht verzichten könnte.
Wenn die Frage lautet, ob man die native `forEach`-Methode ohne Netz und doppelten Boden benutzen kann, dann lautet die Antwort: Ja. Die wird von allen relevanten Ausführungsumgebungen [unterstützt](http://kangax.github.io/compat-table/es5/#test-Array_methods_Array.prototype.forEach).
> Die Reihenfolge der Parameter für die Callback-Funktion einmal außen vor.
>
> Nun ist es in der Tat aber so, dass ich diese Methode nicht `forEach`{: .language-javascript}, sondern `each` genannt habe. Momentan stört sich das vielleicht nicht, weil andere Entwickler sich vielleicht mit dem Entwurf für ECMA-Script 6
ECMAScript 5
> beschäftigt haben, und ein eventuell vorausgreifendes Polyfill schon mit dem richtigen Bezeichner `forEach`{: .language-javascript} erstellt haben - und mein `each` niemanden (mehr) juckt oder jucken wird.
Inwiefern deine Methode `each` *tatsächlich* jemanden juckt oder jucken wird, vermag ich natürlich nicht zu sagen, aber *prinzipiell* wäre das Vorgehen schon problematisch, denn wie du ja selbst sagst, handelt es sich bei deiner Methode nicht um ein *standardkonformes* Polyfill.
> Für die Zukunft
>
> Wie baue ich mir ein eigenes Array (nennen wir es MyArray), das ebenso prototypisch von Array erbt, Änderungen an `MyArray.prototype`{: .language-javascript} aber nur dort, nicht aber an `Array.prototype`{: .language-javascript} vorgenommen werden?
Sorry, irgendwie stehe ich etwas auf dem Schlauch, aber wenn ich dich richtig verstehe, dann willst du in etwa sowas wie das hier …
~~~ javascript
function MyArray ( ) {
// create array
}
MyArray.prototype.method = function ( ) {
// do something
};
const array = new MyArray;
array.method( );
~~~
… wobei der Prototyp von `MyArray.prototype` das Objekt `Array.prototype`{: .language-javascript} sein soll, damit die erzeugten Instanzen sowohl die selbstdefinierten Methoden als auch die eingebauten Methoden erben, ohne dass dabei `Array.prototype`{: .language-javascript} angefasst werden muss. Richtig?
Wenn das deine Frage sein sollte, dann habe ich dazu im Abschnitt [Selbstdefinierte Methoden](https://wiki.selfhtml.org/wiki/JavaScript/Objekte/Array/prototype#Selbstdefinierte_Methoden) meines Artikels ja schon einiges geschrieben. Aber ich kann gerne noch einige Punkte ergänzen, die dort vielleicht etwas zu kurz gekommen sind.
Zum Beispiel könnte man erklären, *warum* die oben gezeigte Variante mit dem Konstruktor unter den genannten Voraussetzungen nicht funktionieren kann.
~~~ javascript
MyArray.prototype = [ ];
~~~
Dabei wäre zunächst zu erwähnen, dass es natürlich möglich ist, ein Array als prototypisches Objekt des Konstruktors anzulegen, sodass die von dieser Funktion erzeugten Instanzen sowohl die auf diesem Objekt definierten eigenen Methoden, als auch die Methoden von `Array.prototype`{: .language-javascript} erben.
~~~ javascript
const instance = new MyArray;
console.log(Array.prototype.isPrototypeOf(instance)); // true
console.log(Array.isArray(instance)); // false
~~~
Aber das Objekt, das bei einem Aufruf via [[Construct]] erzeugt wird, in dessen Kontext die Funktion ausgeführt wird und das standardmäßig deren Rückgabewert ist, ist eben *kein* Array, sondern ein gewöhnliches Objekt.
Das heißt, einem auf diese Weise erzeugten Objekt fehlt die besondere Semantik von Arrays, es besitzt also weder eine Eigenschaft `length`, noch das besondere Verhalten beim schreibenden Zugriff auf Objekteigenschaften, wie es in der internen Methode [[DefineOwnProperty]] bestimmt ist.
~~~ javascript
function MyArray ( ) {
return Array.from(arguments);
}
const instance = new MyArray;
console.log(MyArray.prototype.isPrototypeOf(instance)); // false
~~~
Wird in der Konstruktorfunktion hingegen ein *anderes Objekt* als Rückgabewert bestimmt, als das Objekt in dessen Kontext die Funktion ausgeführt wird, dann wird zwar dieses Objekt zurückgegeben, aber es wird dabei nicht der Prototyp verändert. Das heißt, man bekäme hierbei nur ein normales Array, ohne dass dessen Prototyp auf `MyArray.prototype` gesetzt würde.
Es ist also egal wie man es dreht und wendet, man bekommt hier entweder nur ein ganz gewöhnliches Array, dessen interne Eigenschaft [[Prototype]] eine Referenz auf das Objekt `Array.prototype`{: .language-javascript} enthält, oder aber ein Objekt, das zwar über die gewünschte Prototypenkette verfügt, das aber selbst kein exotisches Arrayobjekt ist.
Wenn wir nun aus guten Gründen die Möglichkeit ignorieren, *nach* der Erzeugung eines Arrays ein Objekt in dessen Prototypenkette einzufügen, dann kann die gewünschte Funktionalität tatsächlich nur auf eine Weise implementiert werden, nämlich durch erzeugung einer von Array abgeleiteten Klasse.
~~~ javascript
class MyArray extends Array {
method ( ) {
// do something
}
}
const instance = new MyArray;
console.log(Array.isArray(instance)); // true
~~~
Hier haben wir also eine Klasse `MyArray`, die vom Konstruktor `Array`{: .language-javascript} abgeleitet ist. Das heißt, die von dieser Klasse erzeugten Instanzen sind richtige Arrays, die sowohl die Methoden von `MyArray.prototype` als auch die Methoden von `Array.prototype`{: .language-javascript} erben.
~~~ javascript
console.log(MyArray.prototype.hasOwnProperty('method')); // true
console.log(MyArray.prototype.propertyIsEnumerable('method')); // false
~~~
Die im Körper der Klasse definierten Methoden werden dabei automatisch auf dem prototypischen Objekt der Funktion angelegt. Diese Form der Methodendefinition hat darüber hinaus den Vorteil, dass das interne Attribut [[Enumerable]] der Methode standardmäßig auf `false`{: .language-javascript} gesetzt wird.
~~~ javascript
class MyArray extends Array {
constructor ( ) {
super(...arguments);
console.log(this.length);
}
method ( ) {
// do something
}
}
const array = new MyArray(2, 4, 8); // 3
~~~
Optional kann innerhalb der Klasse die Pseudomethode `constructor` notiert werden, die im Prinzip wie ein gewöhnlicher Konstruktor funktioniert. Damit die Methode bei einer abgeleiteten Klasse aber auch im Kontext des Objektes ausgeführt wird, welches von der *Superklasse* erzeugt wird, hier also durch den Konstruktor `Array`{: .language-javascript}, muss diese innerhalb der Methode mit dem Keyword `super`{: .language-javascript} aufgerufen werden.
Im Ergebnis ist hier also `Array.prototype`{: .language-javascript} der Prototyp von `MyArray.prototype` und die erzeugten Instanzen sind native Arrays. Das heißt es können, vorzugsweise aber nicht zwingend innerhalb des Körpers der Klasse, beliebige Arraymethoden definiert werden, ohne dabei `Array.prototype`{: .language-javascript} zu manipulieren.
Für die Zunkunft ist das also das Mittel der Wahl, wenn es darum geht, eigene Methoden für Arrays zu definieren.
Viele Grüße,
Orlok