JavaScript/Objekte/Array/prototype

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Die Methoden des in der Eigenschaft prototype des Konstruktors Array hinterlegten Objektes werden an dessen Instanzen vererbt und können entsprechend direkt auf allen Arrays aufgerufen werden. Viele der Methoden sind darüber hinaus generisch, in dem Sinne, dass sie nicht zwingend im Kontext eines Arrays aufgerufen werden müssen, sondern auch auf anderen Objekten ausgeführt werden können.


Syntax

Array.prototype


Attribute
Writable false
Enumerable false
Configurable false



Beschreibung

Jedes Objekt und damit auch jedes Array, verfügt über eine interne Eigenschaft mit dem Namen [[Prototype]], deren Wert entweder eine Referenz auf ein anderes Objekt, oder der primitive Wert null ist. Bei Arrayinstanzen enthält diese Eigenschaft standardmäßig eine Referenz auf das Objekt, das in der Eigenschaft prototype des Konstruktors Array hinterlegt ist, das heißt, alle Arrays besitzen eine Verbindung zu Array.prototype.


Beispiel
const array = [ ];

const prototype = Object.getPrototypeOf(array);

console.log(prototype === Array.prototype); // true


Zwar kann die interne Eigenschaft [[Prototype]] nicht direkt angesprochen werden, aber es ist dennoch möglich ihren Wert in Erfahrung zu bringen, wie das Beispiel oben zeigt, in dem zunächst in Literalschreibweise (Array Initializer) ein leeres Array erzeugt wird. Die danach mit einer Referenz auf das erzeugte Array als Argument aufgerufene Methode getPrototypeOf des Konstruktors Object gibt nun der Wert der internen Eigenschaft [[Prototype]] des Arrays zurück, also dessen Prototyp. Wie die Ausgabe des abschließend durchgeführten Vergleichs in der Konsole zeigt, ist das Objekt Array.prototype tatsächlich der Prototyp des zuvor erstellten Arrays.


Beispiel
const array = Array.of(128);

const prototype = Object.getPrototypeOf(array);

console.log(prototype === Array.prototype); // true


In diesem Beispiel wird nicht in Literalschreibweise, sondern unter Verwendung der Methode of des Konstruktors Array eine neue Arrayinstanz erzeugt und auch hier zeigt der Vergleich, dass der Prototyp dieses Arrays das Objekt ist, das in der Eigenschaft prototype des Konstruktors Array hinterlegt ist. Es ist also festzuhalten, dass es keine Rolle spielt, ob ein Array durch ein Literal erzeugt wird, durch den Konstruktor, oder durch eine eingebaute Methode, der Prototyp ist in jedem Fall Array.prototype.


Beispiel
const stack = [16, 32];

stack.push(64);

console.log(stack.pop( )); // 64


Über Array.prototype werden verschiedene Methoden an Arrayinstanzen vererbt, wie zum Beispiel die Methode push, die ein oder mehrere Elemente an das Ende eines Arrays anfügt, oder auch die Methode pop, die das letzte Element eines Arrays entfernt und zurückgibt. Mit diesen beiden Methoden kann ein Array also wie ein Stapelspeicher (Stack) verwendet werden. Wobei es in diesem Zusammenhang erwähnenswert scheint, dass es keine eingebaute Methode gibt, um explizit das letzte Element eines Arrays zu lesen, ohne es gleichzeitig aus dem Array zu entfernen. Eine solche Methode kann aber leicht selbst implementiert werden, wie das folgende Beispiel zeigt.


Beispiel
const stack = [2, 4, 8, 16];

stack.peek = function ( ) {
  return this[this.length - 1];
};

console.log(stack.peek( )); // 16


Hier wird zunächst ein Array erzeugt und gespeichert. Auf diesem Arrayobjekt wird dann eine Methode definiert, die das Element zurückgibt, dessen Index um Eins kleiner ist als die Länge des Arrays, welche die Anzahl der in einem Array vorhandenen Elemente repräsentiert. Da Arrays einen Nullindex haben und die Zahl der Elemente demnach immer um Eins größer ist als der Index des letzten Elementes, wird hier also dessen Wert zurückgegeben. Dabei wird das Array innerhalb der Methode über die Kontextvariable this angesprochen, die bei einem Methodenaufruf standardmäßig auf das Objekt zeigt, über das die Methode beim Aufruf referenziert wurde.


Beispiel
console.log(stack.hasOwnProperty('peek')); // true

console.log(stack.hasOwnProperty('push')); // false


Bei diesem Objekt muss es sich jedoch nicht zwingend auch um das Objekt handeln, auf dem die Methode definiert wurde. So handelt es sich bei den eingebauten Methoden wie der bereits kennengelernten Methode push, im Gegensatz zu der Methode peek, die im letzten Beispiel direkt auf einer Arrayinstanz definiert wurde, nämlich nicht um eigene Methoden der einzelnen Arrays. Das bedeutet, die auf Array.prototype definierten Methoden werden nicht auf die Instanzen kopiert, wie die Überprüfung mit der von Object.prototype vererbten Methode hasOwnProperty in dem Beispiel oben zeigt, sondern sie werden nur bei Bedarf über die Prototypenkette (Prototype Chain) referenziert, also gewissermaßen von Array.prototype ausgeliehen.



Wird also wie in den Beispielen oben auf einem Array die Methode push aufgerufen, dann wird intern zunächst nachgesehen, ob das Array selbst über eine Methode mit diesem Namen verfügt. Da dies nun standardmäßig nicht der Fall ist, wird weiter geprüft, ob als Wert der Eigenschaft [[Prototype]] des Arrays eine Referenz auf ein Objekt hinterlegt ist. Weil diese Eigenschaft bei Arrayinstanzen wie gesehen grundsätzlich auf das Objekt Array.prototype verweist, wird also bei diesem Objekt nachgesehen, ob dort eine entsprechende Methode definiert ist. Da Array.prototype eine Methode mit dem Namen push besitzt, wird diese schließlich referenziert und im Kontext des Arrays aufgerufen.


Beispiel
console.log(Array.isArray(Array.prototype)); // true

Array.prototype.push(2, 4, 8);

console.log(Array.prototype[1]); // 4


Bei dem in der Eigenschaft Array.prototype hinterlegten Objekt handelt es sich übrigens selbst auch um ein Array, wie die Überprüfung mit der Methode isArray des Konstruktors Array in dem Beispiel oben zeigt. Das heißt, die Methode push, ebenso wie alle anderen Arraymethoden, kann auch direkt auf diesem Objekt aufgerufen werden, auch wenn es in aller Regel nicht sinnvoll ist, Array.prototype auf diese Weise zu verwenden. Es gibt allerdings durchaus Anwendungsfälle, in denen eine direkte Referenzierung einer Arraymethode über Array.prototype sinnvoll sein kann, nämlich beim Aufruf einer solchen Methode im Kontext eines Objektes, das selbst kein Array ist.


Beispiel
function numbers ( ) {
  return Array.prototype.filter.call(arguments, value =>
    typeof value === 'number'
  );
}

const array = numbers('Alpha', 3, 5, 7, 'Omega');

console.log(array); // [3, 5, 7]


So kann wie in dem Beispiel oben die von Function.prototype vererbte Methode call verwendet werden, um eine eigene Methode von Array.prototype im Kontext des Objektes aufzurufen, das innerhalb einer Funktion über den Bezeichner arguments referenziert werden kann. Dieses Objekt, welches die an die Funktion übergebenen Argumente enthält, besitzt zwar ähnliche Eigenschaften wie ein Array, in dem Sinne, dass es über eine Eigenschaft length verfügt und seine Elemente über einen ganzzahligen Index angesprochen werden (Indexed Collection), aber es handelt sich dabei dennoch nicht um ein Objekt vom Typ Array.


Beispiel
void function ( ) {
  console.log(Array.isArray(arguments)); // false
}( );


Das bedeutet, Array.prototype ist nicht der Prototyp des Objektes arguments und die Methoden von Array.prototype werden entsprechend nicht an arguments vererbt. Da jedoch viele dieser Methoden generisch sind, sie also hinsichtlich des Objektes auf dem sie ausgeführt werden nicht auf Arrays beschränkt sind, ist es möglich, sie auch auf dem Objekt arguments aufzurufen, indem dieses Objekt explizit als Kontext der jeweiligen Methode bestimmt wird. Im vorangegangenen Beispiel wird also die generische Arraymethode filter mittels call im Kontext von arguments aufgerufen und auf diese Weise ein Array mit den Werten erzeugt, welche den in der als Argument übergebenen Rückruffunktion durchgeführten Test mit typeof bestanden haben.


Hinweis:
Anders als in dem Beispiel mit filter, muss die jeweilige Methode in so einem Fall jedoch nicht über Array.prototype direkt referenziert werden. Das heißt, es ist genauso gut möglich ein temporäres leeres Array zu erzeugen, vorzugsweise in Form eines Literals, um die entsprechende Methode dann über die Prototypenkette zu referenzieren. Diese Variante besitzt den Vorzug kürzer zu sein. Aus Gründen der besseren Lesbarkeit des Codes ist es in der Regel aber doch empfehlenswert, die jeweilige Methode direkt auf Array.prototype anzusprechen.


Bezogen auf den Umgang mit dem exotischen Objekt arguments ist darüber hinaus noch anzumerken, dass es meistens nicht sinnvoll ist, Arraymethoden im Kontext dieses Objektes auszuführen, also das Objekt arguments selbst für die Arbeit mit den an die Funktion übergebenen Argumenten zu verwenden. Das bedeutet, alle Operationen, die über das Lesen der Eigenschaft length von arguments und den Zugriff auf einzelne Elemente über ihren Index hinausgehen, sollten besser auf einem richtigen Array durchgeführt werden, in welches man den Inhalt von arguments zuvor kopiert hat.


Beispiel
function ToArray ( ) {
  return [ ].slice.call(arguments);
}

const list = ToArray( );

console.log(Array.isArray(list)); // true


Für das Erzeugen eines Arrays mit den Elementen von arguments gibt es eine Vielzahl an Möglichkeiten. So kann zu diesem Zweck beispielsweise eine Schleife verwendet werden, ebenso wie verschiedene Methoden von Array.prototype. Die bislang übliche Vorgehensweise ist aber jedenfalls, die Methode slice im Kontext von arguments aufzurufen, ohne dabei Argumente an die Methode zu übergeben. Dadurch wird ein neues Array erzeugt und mit allen Elementen von arguments in derselben Reihenfolge befüllt. Mit diesem Array können dann weitere Operationen auf den an die Funktion übergebenen Argumenten durchgeführt werden.


Beispiel
void function ( ) {
  console.log(Array.isArray([...arguments])); // true
}( );


Die empfohlene, weil deutlich elegantere Vorgehensweise bezüglich der Erzeugung eines Arrays mit den Elementen von arguments sieht allerdings anders aus, denn zu diesem Zweck sollte der aus drei Punkten bestehende sogenannte Spread Operator verwendet werden. Wird dieser wie in dem Beispiel oben in einem Arrayliteral notiert und ihm ein iterierbares Objekt nachgestellt, dann extrahiert er die Elemente dieses Objektes und fügt sie in das Array ein.


Beispiel
function ToArray ( ) {
  const list = [...arguments];
  if (list.length) {
    console.log(list[0]);
  }
}

ToArray(2, 4, 8); // 2


Es ist dabei aber zu berücksichtigen, dass es sich bei diesem Operator um eine Syntax handelt, die erst in der sechsten Edition der Sprache standardisiert wurde, weshalb es unter Umständen sinnvoll ist, vor dessen Verwendung zunächst den allgemeinen Stand der Unterstützung zu überprüfen, wobei etwa die Tabelle zur Kompatibilität von Kangax zurate gezogen werden kann.


Beispiel
function ToArray ( ) {
  return Array.from(arguments);
}
 
const list = ToArray( );

console.log(Array.isArray(list)); // true


Eine gleichwertige und unter Umständen sogar vorzugswürdige, weil besser lesbare Alternative zur Verwendung des Spread Operators besteht schließlich darin, das Objekt arguments an die Methode from des Konstruktors Array zu übergeben. Auch hier wird ein Array erzeugt, dessen Elemente den Einträgen von arguments entsprechen. Allerdings ist ebenso wie beim Spread Operator zu beachten, dass die Methode gegebenenfalls noch nicht von allen relevanten Ausführungsumgebungen unterstützt wird, da auch sie erst in der sechsten Edition der Sprache standardisiert wurde.

Selbstdefinierte Methoden

Es ist grundsätzlich möglich, Array.prototype um selbstdefinierte Methoden zu erweitern. So kann wie in dem folgenden Beispiel durch einfache Zuweisung (Assignment) eine eigene Methode auf dem Objekt definiert werden, welche dann an alle Arrayinstanzen vererbt wird und die somit für alle nach der Zuweisung erzeugten Arrays verfügbar ist.


Beispiel
Array.prototype.random = function ( ) {
  return this[
    Math.floor(Math.random( ) * this.length)
  ];
};

const array = ['Ares', 'Hermes', 'Athene'];

console.log(array.random( )); // maybe Hermes


Die auf Array.prototype definierte Methode mit dem Namen random erzeugt zunächst unter Verwendung der eingebauten Funktion random des Standardobjektes Math eine Zufallszahl im halboffenen Intervall [0, 1), also einen Wert, der größer oder gleich Null und kleiner als Eins ist. Dieser Wert wird dann mit dem Wert der Eigenschaft length multipliziert und mittels floor abgerundet. Im Ergebnis werden hier also mehr oder weniger zufällige Ganzzahlen erzeugt, die den Indizes der Elemente des jeweiligen Arrays entsprechen, auf dem die Methode aufgerufen wurde. Der Rückgabewert der selbstdefinierte Methode random ist demnach der Wert eines zufällig ausgewählten Elementes des Arrays.


Hinweis:
Auch wenn das Beispiel oben isoliert betrachtet funktioniert, sollte dennoch nicht auf diese Weise verfahren werden, denn die Veränderung eingebauter Objekte, in diesem Fall also die Manipulation von Array.prototype, gilt zurecht als schlechte Praxis. Es ist nämlich zu berücksichtigen, dass es für jede globale Umgebung nur ein solches Standardobjekt gibt, welches sich alle gegebenenfalls eingebundenen Programme teilen. Das heißt, es besteht hier immer das Risiko, dass Konflikte und mithin Fehler entstehen, weil mehrere in derselben Umgebung ausgeführte Programme unabhängig voneinander auf demselben Objekt eine gleichnamige Methode definieren. Das trifft analog natürlich auch auf einzelne Programme zu, an denen mehrere Entwickler beteiligt sind. Die einzige Ausnahme, bei der die Manipulation eingebauter Objekte unproblematisch und sogar geboten ist, ist die Bereitstellung von Polyfills für standardkonforme Methoden.


Eine alternative, aber nur unwesentlich bessere Variante der Methodendefinition wäre, die Definition von der Bedingung abhängig zu machen, dass auf dem Objekt noch keine Methode dieses Namens existiert. Allerdings würde dies nur dann funktionieren, wenn eine gegebenenfalls zuvor definierte Methode mit demselben Namen auch tatsächlich dieselbe Semantik besitzt, sie sich also im Vergleich mit der in diesem Fall nicht definierten Methode identisch verhält. Weil man sich darauf aber selbstverständlich nicht verlassen kann, sollte auch diese Möglichkeit verworfen werden.


Beispiel
function customize (array) {
  return Object.defineProperty(array, 'same', {
    value ( ) {
      const type = typeof this[0];
      return this.every(
        element => typeof element === type
      );
    }
  });
}


Eine Möglichkeit, eigene Methoden für Arrays zu definieren ohne dabei Array.prototype zu verändern, besteht natürlich darin, die entsprechenden Methoden direkt auf den einzelnen Instanzen anzulegen. Zu diesem Zweck kann eine Funktion wie in dem Beispiel oben geschrieben werden, welche auf einem bestimmten Array eine eigene Methode definiert. Dies kann entweder durch einfache Zuweisung geschehen, oder wie hier durch explizite Definition mittels defineProperty, wobei Letzteres den Vorteil hat, dass dabei das Attribut [[Enumerable]] der auf diese Weise definierten Methode standardmäßig auf false gesetzt wird, so wie dies auch bei den eingebauten Arraymethoden der Fall ist.


Hinweis:
Den Wert des internen Attributes [[Enumerable]], welches die Abzählbarkeit einer Objekteigenschaft bestimmt, auf false zu setzten, hat übrigens eine besondere Bedeutung. Geschieht dies nämlich nicht, dann würde die definierte Methode zum Beispiel bei der Iteration über das Array unter Verwendung einer Schleife mit for und in berücksichtigt werden. Zwar ist es als schlechte Praxis anzusehen, diese Schleifenvariante auf Arrays auszuführen, aber eine solche Verwendung kann in der Regel nicht ausgeschlossen werden. Würden selbstdefinierte Methoden hierbei berücksichtigt, dann kann dies zu Fehlern im Programm führen, die unter Umständen nur schwer zu finden sind. Es ist also zu empfehlen, den Wert dieses Attributes bei selbst erstellten Arraymethoden grundsätzlich auf false zu setzen.


Die Funktion in dem Beispiel oben definiert also für jedes Array das ihr als Argument übergeben wird eine eigene, nicht abzählbare Methode mit dem Namen same, welche zunächst mittels typeof den Datentyp des ersten Elementes des Arrays ermittelt und dann unter Verwendung der eingebauten Arraymethode every prüft, ob alle in dem Array enthaltenen Elemente denselben Datentyp besitzen. Ist dies der Fall wird der boolesche Wert true zurückgegeben, sonst false.


Beispiel
const array = [2, 3, 5, 7];

customize(array);
 
console.log(array.same( )); // true


Bei dieser Variante kommt man allerdings nicht in den Genuss der Vorteile differenzieller Vererbung, da in diesem Fall für jedes Array ein oder mehrere eigene Funktionsobjekte erzeugt werden, statt die gewünschte Funktionalität an einen gemeinsamen Prototypen zu delegieren. Eine solche Lösung wäre also potentiell weniger performant. Das heißt, das eigentliche Ziel sollte es demnach sein, in der Prototypenkette zwischen den Instanzen und Array.prototype ein weiteres Objekt zu platzieren, sodass die Instanzen sowohl die standardmäßig von Array.prototype bereitgestellten Methoden, also auch die selbstdefinierten Methoden des eingefügten Objektes erben, ohne dass dabei das eingebaute Objekt Array.prototype verändert wird.


Beispiel
const object = Object.create([ ]);

console.log(object instanceof Array); // true

console.log(Array.isArray(object)); // false


Tatsächlich war diese Art der Manipulation der Prototypenkette bei Arrays bis zur sechsten Edition der Sprache jedoch schlicht unmöglich, das heißt, es gab bis dahin keine standardkonforme Möglichkeit Arrays zu erzeugen, deren Prototyp nicht Array.prototype ist, denn unabhängig davon, ob man das prototypische Objekt unter Verwendung der Methode Object.create einfügt, oder ob man hierzu eine Konstruktorfunktion verwendet, werden im Ergebnis immer nur gewöhnliche Objekte erzeugt, denen die spezielle Semantik von exotischen Arrayobjekten fehlt.

Von Array abgeleitete Klassen

Will man also einerseits vermeiden, an Array.prototype Veränderungen vorzunehmen, andererseits jedoch eigene Arraymethoden nicht auf jeder einzelnen Instanz definieren, dann gibt es nur eine Möglichkeit wie dies umgesetzt werden kann, nämlich unter Verwendung der Klassensyntax, welche in der sechsten Edition der Sprache eingeführt wurde. Diese erlaubt es nämlich, eine vom Konstruktor Array abgeleitete Klasse zu erstellen, deren Instanzen einerseits echte Arrayobjekte sind, und die andererseits sowohl die Methoden der selbstdefinierten Klasse erben, als auch die Methoden von Array.prototype.


Beispiel
class CustomArray extends Array {
  peek ( ) {
    return this[this.length - 1];
  }
}

const stack = new CustomArray(2, 4);

stack.push(8);

console.log(stack.peek( )); // 8


In diesem Beispiel wird zunächst eine Klasse namens CustomArray deklariert, wobei unter Verwendung des Schlüsselwortes extends bestimmt wird, dass diese Klasse vom Konstruktor Array abgeleitet sein soll. Im Körper der Klasse wird nun eine Methode definiert, welche von CustomArray an die Instanzen vererbt werden soll. Bei dem folgenden Aufruf der Klasse wird nun ein Array erzeugt, dessen Prototyp das in der Eigenschaft prototype von CustomArray hinterlegte Objekt ist, auf dem die Methode peek implizit angelegt wurde. Dieses prototypische Objekt wiederum hat Array.prototype als Wert der internen Eigenschaft [[Prototype]], sodass im Ergebnis sowohl die Methoden von Array.prototype, als auch die mittels CustomArray erstellte Methode peek an die Instanzen vererbt werden.


Hinweis:
Wird eine Methode wie in dem Beispiel oben innerhalb des Körpers einer Klasse definiert, dann wird übrigens der Wert des Attributes [[Enumerable]] der Methode automatisch auf false gesetzt. Das heißt, auf diese Weise erstellte Methoden sind standardmäßig nicht abzählbar. Eine explizite Änderung dieses Wertes unter Verwendung der Methode defineProperty, wie im vorangegangenen Abschnitt gezeigt, ist hier also nicht notwendig. Dies gilt aber tatsächlich nur für solche Methoden, die wie hier bei der Definition der Klasse angelegt werden. Würde also bezogen auf das Beispiel durch bloße Zuweisung an CustomArray.prototype eine weitere Methode hinzugefügt, dann wäre diese auch hier abzählbar. Ob eine eigene Eigenschaft oder Methode abzählbar ist, kann im Übrigen mit der Methode propertyIsEnumerable in Erfahrung gebracht werden, welche von Object.prototype an alle Objekte vererbt wird.


Sollen darüber hinaus auf den Instanzen der abgeleiteten Klasse auch eigene Eigenschaften definiert werden, dann kann dies innerhalb der Pseudomethode constructor geschehen, welche wie das Beispiel oben gezeigt hat nicht zwingend innerhalb der Klasse notiert werden muss. Diese Methode funktioniert im Prinzip wie ein gewöhnlicher Konstruktor. Das heißt, beim Aufruf der Klasse wird automatisch diese Methode mit den Argumenten aufgerufen, welche an die Klasse übergeben wurden, und innerhalb der Methode kann das Instanzobjekt über die Kontextvariable this angesprochen werden. Bei abgeleiteten Klassen ist es allerdings erforderlich, vor der Verwendung von this zunächst mit dem Operator super die jeweilige Superklasse aufzurufen, da ansonsten ein Fehler geworfen wird. Wenn constructor nicht definiert ist, dann erfolgt dieser Aufruf implizit.


Beispiel
class CustomArray extends Array {
  constructor ( ) {
    super(...arguments);
    this.type = this.constructor.name;
  }
  random ( ) {
    return this[
      Math.floor(Math.random( ) * this.length)
    ];
  }
}

const array = new CustomArray(1, 2, 3);

console.log(array.type); // CustomArray

console.log(array.random( )); // maybe 3


In dem Beispiel oben wird also wieder eine von Array abgeleitete Klasse deklariert, wobei diesmal jedoch eine Methode mit dem Namen constructor definiert wird. Innerhalb dieser Methode wird nun zunächst mit dem Operator super der Konstruktor Array aufgerufen, mit dessen Rückgabewert, also einem Array, dann die Kontextvariable this initialisiert wird. Durch Zuweisung an this wird dann eine eigene Eigenschaft für die Instanzen definiert. Da der Aufruf der Superklasse, also des Konstruktors Array, hier manuell über den Operator super erfolgt, müssen dabei natürlich die an CustomArray übergebenen Argumente weitergereicht werden, weshalb hier beim Superaufruf die Elemente des Objektes arguments mit dem Spread Operator in Argumente für den Konstruktor Array umgewandelt werden.


Hinweis:
Zu beachten ist bei der Definition eigener Arraymethoden innerhalb einer abgeleiteten Klasse, dass die innerhalb des Körpers einer Klasse notierten Methoden nicht durch Komma getrennt werden. Dies ist ein häufiger Fehler den es zu vermeiden gilt. Darüber hinaus ist zu erwähnen, dass im Körper der Klasse auch eigene Methoden des Konstruktors, also der Klasse selbst definiert werden können. Dabei ist dem Bezeichner der Methode das Schlüsselwort static voranzustellen.


Wenn es also darum geht eigene Arraymethoden zu definieren, dann sollte dies wie in diesem Abschnitt beschrieben innerhalb einer von Array abgeleiteten Klasse erfolgen. Hierbei ist jedoch zu berücksichtigen, dass diese Syntax wie eingangs erwähnt erst in der sechsten Edition der Sprache standardisiert wurde und sie gegebenenfalls noch nicht von allen relevanten Browsern unterstützt wird. Es ist also empfehlenswert, vor dem Gebrauch zunächst den Stand der Unterstützung zu überprüfen, wozu die Tabelle zur Kompatibilität von kangax konsultiert werden kann.

Alternative Vorgehensweisen

Scheidet aus Gründen der Kompatibilität die Klassensyntax als Möglichkeit aus, eigene Arraymethoden zu definieren ohne Array.prototype zu manipulieren oder die Methoden auf jeder Instanz anzulegen, dann kommen noch einige andere Varianten in Betracht, die jedoch allesamt mit Nachteilen einhergehen. So kann ein prototypisches Objekt zum Beispiel nach der Erzeugung einer Arrayinstanz in dessen Prototypenkette eingefügt werden, entweder unter Verwendung der nicht standardkonformen, aber weitestgehend unterstützten Eigenschaft __proto__, oder durch die Methode setPrototypeOf des Konstruktors Object, welche jedoch ebenfalls erst in der sechsten Edition der Sprache standardisiert wurde und die entsprechend ebenso wie die Syntax für abgeleitete Klassen unter Umständen noch nicht zufriedenstellend unterstützt wird.


Beispiel
function CustomArray ( ) {
  let array = [ ].slice.call(arguments);
  if (Object.setPrototypeOf) {
    Object.setPrototypeOf(array, CustomArray.prototype);
  } else {
    array.__proto__ = CustomArray.prototype;
  }
  return array;
}
 
CustomArray.prototype = [ ];
 
Object.defineProperty(CustomArray.prototype, 'random', {
  value : function ( ) {
    return this[
      Math.floor(Math.random( ) * this.length)
    ];
  }
});
 
let array = CustomArray(2, 4, 8);
 
console.log(array.random( )); // maybe 4


Hierbei ist allerdings zu beachten, dass es vergleichsweise unperformant ist, die Prototypenkette auf diese Weise zu manipulieren, weshalb bei vielen Ausführungsumgebungen in der Konsole eine entsprechende Warnung ausgegeben wird. Es wäre darum, wenn es sich um gewöhnliche Objekte handeln würde, von dieser Praxis dringend abzuraten. Da Arrays aber wie gesehen keine gewöhnlichen Objekte sind und es kaum praktikable Alternativen gibt, die nicht selbst wiederum mit mehr oder minder gravierenden Nachteilen einhergehen, kann eine solche Vorgehensweise unter Umständen durchaus in Betracht gezogen werden, zumindest solange die Syntax für abgeleitete Klassen noch nicht in akzeptablem Maße unterstützt wird. Es ist allerdings empfehlenswert, bezogen auf ein konkretes Programm auf verschiedenen Plattformen zu testen, ob die Performance hierbei nicht zu sehr beeinträchtigt wird.


Beispiel
function CustomArray ( ) {
  'use strict';
  this.elements = [ ].slice.call(arguments);
}

CustomArray.prototype.peek = function ( ) {
  let array = this.elements,
      index = array.length - 1;
  return array[index];
};

let wrapper = new CustomArray(3, 6, 9);

wrapper.elements.push(12);

console.log(wrapper.peek( )); // 12


Eine weitere Möglichkeit eigene Arraymethoden zu definieren ohne dabei Array.prototype zu manipulieren besteht darin, die Arrayinstanzen als Eigenschaften von gewöhnlichen Objekten anzulegen, welche die selbstdefinierten Methoden erben. Dabei müssen die Methoden natürlich entsprechend angepasst werden, sodass sie das in dem Objekt gekapselte Array referenzieren. Bei der Verwendung der nativen Arraymethoden muss das eingeschlossene Array dann aber natürlich über einen Elementausdruck direkt referenziert werden, was allerdings der Lesbarkeit des Codes nicht unbedingt zuträglich ist.




Im Allgemeinen, die Kompatibilität der Syntax vorausgesetzt, sollten eigene Arraymethoden also in einer von Array abgeleiteten Klasse definiert werden. Solange die Unterstützung hierfür jedoch noch nicht gegeben ist, muss im konkreten Einzelfall abgewogen werden, was die beste Alternative darstellt, ob also das Risiko vertretbar ist, die Methoden direkt auf Array.prototype zu definieren, und falls nicht, ob es performanter ist die Methoden auf den Instanzen selbst anzulegen oder nach Erzeugung des Arrays die Prototypenkette zu manipulieren, oder ob die Lösung nicht vielleicht darin besteht, die Methoden über gewöhnliche Objekte zu vererben, in denen die Arrayinstanzen als Eigenschaften hinterlegt sind.

Weblinks