JavaScript/Objekte/Map/species

Aus SELFHTML-Wiki
< JavaScript‎ | Objekte‎ | Map
Wechseln zu: Navigation, Suche

Die Eigenschaft @@species des eingebauten Konstruktors Map ist als Getter implementiert und gibt den Wert zurück, mit dem die Kontextvariable this beim Aufruf initialisiert wurde, auf Map angesprochen also eine Referenz auf den Konstruktor selbst.


Syntax

Map[Symbol.species]

Beschreibung[Bearbeiten]

Eigenschaften von Konstruktoren wie Map, deren Schlüssel die eingebaute Konstante @@species ist, dienen dazu zu bestimmen, welcher Konstruktor verwendet werden soll, wenn durch eine vererbte Methode innerhalb einer abgeleiteten Klasse ein neues Instanzobjekt erzeugt wird.


Beispiel
class Parent {
  clone ( ) {
    return Object.assign(new Parent, this);
  }
}

class Child extends Parent { }

console.log(new Child( ).clone( ) instanceof Child); // false


Dieser Zweck wird beim Blick auf das Beispiel oben deutlich. Wird nämlich wie hier innerhalb einer Basisklasse eine Methode definiert, die eine neue Instanz erzeugt und dabei den Konstruktor explizit referenziert, dann wird natürlich auch in dem Fall, dass die Methode auf einer Instanz einer abgeleiteten Klasse aufgerufen wird, der Konstruktor der Basisklasse aufgerufen. Das heißt, im Ergebnis wird hier also von der vererbten Methode clone keine Instanz von Child erzeugt, sondern lediglich von Parent, obwohl die Methode auf einer Instanz von Child aufgerufen wurde.


Beispiel
class Parent {
  clone ( ) {
    return Object.assign(new this.constructor, this);
  }
}

class Child extends Parent { }

console.log(new Child( ).clone( ) instanceof Child); // true


Eine alternative Vorgehensweise, welche das im letzten Beispiel dokumentierte Problem behebt, könnte natürlich darin liegen, den Konstruktor über die Eigenschaft constructor zu referenzieren, die über die Prototypenkette an alle Instanzen vererbt wird. Hier würde nämlich die auf dem prototypischen Objekt der Basisklasse Parent definierte Eigenschaft constructor durch die gleichnamige Eigenschaft von Child.prototype überschrieben werden, sodass im Ergebnis der Konstruktor Child referenziert wird, wenn die Methode auf einer seiner Instanzen aufgerufen wird. Eine solche Methodendefinition hätte allerdings den Nachteil, dass immer und in jedem Fall der Konstruktor der abgeleiteten Klasse aufgerufen wird, auch wenn dies im Einzelfall gar nicht gewünscht ist.


Beispiel
class Parent {
  static get [Symbol.species] ( ) {
    return this;
  }
  clone ( ) {
    return Object.assign(new this.constructor[Symbol.species], this);
  }
}

class Child extends Parent { }

console.log(new Child( ).clone( ) instanceof Child); // true


Dies ist nun der Punkt, an dem die Eigenschaft @@species ins Spiel kommt, denn durch sie wird einerseits als Standardverhalten implementiert, dass bei der Erzeugung einer Instanz der Konstruktor der abgeleiteten Klasse aufgerufen wird, und andererseits besteht die Möglichkeit, diese Eigenschaft zu überschreiben, um für eine abgeleitete Klasse ein anderes Verhalten zu definieren, wenn dies gewünscht sein sollte. Eine Methode, die wie in dem Beispiel oben die Eigenschaft @@species implementiert, ist also wiederverwendbar und nicht auf die Klasse, beziehungsweise den Konstruktor beschränkt, auf dessen prototypischem Objekt sie ursprünglich definiert wurde.


Beispiel
class CustomArray extends Array { }

const array = new CustomArray(1, 2).map(num => num + 1);

console.log(array instanceof CustomArray); // true


Da die Standardbibliothek für Maps bis dato keine Methode enthält, die eine neue Mapinstanz erzeugen würde, ist es momentan nicht möglich, das zuvor beschriebene Verhalten anhand von eingebauten Methoden zu demonstrieren. Allerdings besitzt der Konstruktor Array ebenfalls eine Eigenschaft mit dem Schlüssel @@species und es existieren verschiedene Methoden, die bei ihrem Aufruf eine neue Arrayinstanz erzeugen, wie zum Beispiel die Methode map. Dabei wird nun intern nicht der Konstruktor Array direkt referenziert, sondern der aufzurufende Konstruktor wird über die Eigenschaft @@species ermittelt. Dies führt im Ergebnis dazu, dass beim Aufruf der Methode map auf einer Instanz einer von Array abgeleiteten Klasse eine neue Instanz dieser Klasse erzeugt wird.


Beispiel
Object.defineProperty(Map.prototype, 'filter', {
  configurable : true,
  value : function filter (callback, thisArg) {
    const map = new this.constructor[Symbol.species];
    this.forEach((value, key) => {
      const result = callback.call(thisArg, value, key, this);
      if (result) {
        map.set(key, value);
      }
    });
    return map;
  }
});

class CustomMap extends Map { }

const map = new CustomMap([
  [false, 0], [true,  1]
]);

console.log(map.filter(value => value) instanceof CustomMap); // true


Jedenfalls könnte eine entsprechende Methode für Maps, welche auf die Eigenschaft @@species zugreift um festzustellen, welcher Konstruktor für die Erzeugung einer neuen Instanz aufgerufen werden soll, wie in dem Beispiel oben implementiert sein. Die hier definierte Methode mit dem Namen filter erzeugt eine neue Map mit den Einträgen der Map, auf der die Methode aufgerufen wurde, und die den in der Rückruffunktion durchgeführten Test bestanden haben. Dabei wird der Konstruktor Map jedoch nicht direkt referenziert, sondern der zu verwendende Konstruktor wird über die Eigenschaft @@species bestimmt. Entsprechend wird in dem Fall, dass die Methode auf einer Instanz einer von Map abgeleiteten Klasse aufgerufen wird, eine Instanz dieser Klasse erzeugt, da sie von der von Map vererbten Eigenschaft @@species referenziert wurde.

Spezifikation[Bearbeiten]

ECMAScript 2016: get Map[@@species]