[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Classes/extends [Back]  [Original]

extends - JavaScript | MDN

Dieser Inhalt wurde automatisch aus dem Englischen bersetzt, und kann Fehler enthalten. Erfahre mehr ber dieses Experiment.

View in English Always switch to English

extends

Baseline Weitgehend verfgbar

Diese Funktion ist gut etabliert und funktioniert auf vielen Gerten und in vielen Browserversionen. Sie ist seit Mrz 2016 browserbergreifend verfgbar.

Das extends Schlsselwort wird in Klassendeklarationen oder Klassenexpressionen verwendet, um eine Klasse zu erstellen, die eine Unterklasse einer anderen Klasse ist.

In diesem Artikel

Probieren Sie es aus

class DateFormatter extends Date {
  getFormattedDate() {
    const months = [
      "Jan",
      "Feb",
      "Mar",
      "Apr",
      "May",
      "Jun",
      "Jul",
      "Aug",
      "Sep",
      "Oct",
      "Nov",
      "Dec",
    ];
    return `${this.getDate()}-${months[this.getMonth()]}-${this.getFullYear()}`;
  }
}

console.log(new DateFormatter("August 19, 1975 23:15:30").getFormattedDate());
// Expected output: "19-Aug-1975"

Syntax

js
class ChildClass extends ParentClass { /*  */ }
ParentClass

Ein Ausdruck, der zu einer Konstruktorfunktion (einschlielich einer Klasse) oder null auswertet.

Beschreibung

Das extends Schlsselwort kann zum Erstellen von Unterklassen fr benutzerdefinierte Klassen sowie fr eingebaute Objekte verwendet werden.

Jeder Konstruktor, der mit new aufgerufen werden kann und die prototype Eigenschaft hat, kann Kandidat fr die Elternklasse sein. Beide Bedingungen mssen erfllt sein zum Beispiel knnen gebundene Funktionen und Proxy konstruiert werden, aber sie haben keine prototype Eigenschaft, also knnen sie nicht unterklassifiziert werden.

js
function OldStyleClass() {
  this.someProperty = 1;
}
OldStyleClass.prototype.someMethod = function () {};

class ChildClass extends OldStyleClass {}

class ModernClass {
  someProperty = 1;
  someMethod() {}
}

class AnotherChildClass extends ModernClass {}

Die prototype Eigenschaft der ParentClass muss ein Object oder null sein, aber in der Praxis werden Sie sich selten darum kmmern, da ein nicht-objekt-orientiertes prototype sich ohnehin nicht wie erwartet verhlt. (Es wird vom new Operator ignoriert.)

js
function ParentClass() {}
ParentClass.prototype = 3;

class ChildClass extends ParentClass {}
// Uncaught TypeError: Class extends value does not have valid prototype property 3

console.log(Object.getPrototypeOf(new ParentClass()));
// [Object: null prototype] {}
// Not actually a number!

extends legt das Prototyp fr sowohl ChildClass als auch ChildClass.prototype fest.

Prototyp von ChildClass Prototyp von ChildClass.prototype
extends Klausel fehlt Function.prototype Object.prototype
extends null Function.prototype null
extends ParentClass ParentClass ParentClass.prototype
js
class ParentClass {}
class ChildClass extends ParentClass {}

// Allows inheritance of static properties
Object.getPrototypeOf(ChildClass) === ParentClass;
// Allows inheritance of instance properties
Object.getPrototypeOf(ChildClass.prototype) === ParentClass.prototype;

Die rechte Seite von extends muss kein Bezeichner sein. Sie knnen jeden Ausdruck verwenden, der zu einem Konstruktor auswertet. Dies ist oft ntzlich, um Mixins zu erstellen. Der this Wert im extends Ausdruck ist das this, das die Klassendefinition umgibt, und das Verweisen auf den Klassennamen fhrt zu einem ReferenceError, da die Klasse noch nicht initialisiert ist. await und yield funktionieren erwartungsgem in diesem Ausdruck.

js
class SomeClass extends class {
  constructor() {
    console.log("Base class");
  }
} {
  constructor() {
    super();
    console.log("Derived class");
  }
}

new SomeClass();
// Base class
// Derived class

Whrend die Basisklasse alles von ihrem Konstruktor zurckgeben kann, muss die abgeleitete Klasse ein Objekt oder undefined zurckgeben, sonst wird ein TypeError ausgelst.

js
class ParentClass {
  constructor() {
    return 1;
  }
}

console.log(new ParentClass()); // ParentClass {}
// The return value is ignored because it's not an object
// This is consistent with function constructors

class ChildClass extends ParentClass {
  constructor() {
    super();
    return 1;
  }
}

console.log(new ChildClass()); // TypeError: Derived constructors may only return object or undefined

Wenn der Elternklassenkonstruktor ein Objekt zurckgibt, wird dieses Objekt als this Wert fr die abgeleitete Klasse verwendet, wenn diese weiter Klassenfelder initialisiert. Dieser Trick wird als "Rckgabevernderung" bezeichnet und ermglicht es, dass die Felder einer abgeleiteten Klasse (einschlielich der privaten) auf nicht verwandten Objekten definiert werden.

Unterklassenbildung eingebauter Klassen

Warnung: Das Standardkomitee ist mittlerweile der Ansicht, dass der eingebaute Unterklassifizierungsmechanismus in frheren Spezifikationsversionen berentwickelt ist und nicht unerhebliche Leistungs- und Sicherheitsprobleme verursacht. Neue eingebaute Methoden bercksichtigen Unterklassen weniger, und Implementierer von Engines untersuchen, ob bestimmte Mechanismen der Unterklassifizierung entfernt werden sollen. Ziehen Sie in Betracht, Zusammensetzung anstelle von Vererbung zu verwenden, wenn Sie eingebaute Klassen erweitern.

Hier sind einige Dinge, die Sie erwarten knnen, wenn Sie eine Klasse erweitern:

  • Beim Aufruf einer statischen Fabrikmethode (wie Promise.resolve() oder Array.from()) auf einer Unterklasse ist die zurckgegebene Instanz immer eine Instanz der Unterklasse.
  • Beim Aufruf einer Instanzmethode, die eine neue Instanz zurckgibt (wie Promise.prototype.then() oder Array.prototype.map()) auf einer Unterklasse, ist die zurckgegebene Instanz immer eine Instanz der Unterklasse.
  • Instanzmethoden versuchen, soweit mglich, an eine minimalen Satz von primitiven Methoden zu delegieren. Zum Beispiel fhrt das berschreiben von then() in einer Unterklasse von Promise automatisch dazu, dass sich das Verhalten von catch() ndert; oder beim berschreiben von set() in einer Unterklasse von Map ndert sich das Verhalten des Map() Konstruktors automatisch.

Diese Erwartungen richtig zu implementieren erfordert jedoch nicht-triviale Anstrengungen.

  • Erstens muss die statische Methode den Wert von this lesen, um den Konstruktor fr die Konstruktion der zurckgegebenen Instanz zu erhalten. Dies bedeutet, dass [p1, p2, p3].map(Promise.resolve) einen Fehler auslst, da this innerhalb von Promise.resolve undefined ist. Eine Mglichkeit, dies zu beheben, besteht darin, auf die Basisklasse zurckzufallen, wenn this kein Konstruktor ist, wie Array.from() es tut, wobei die Basisklasse dennoch eine Sonderbehandlung erfhrt.
  • Zweitens muss die Instanzmethode this.constructor lesen, um die Konstruktorfunktion zu erhalten. Aber new this.constructor() kann alten Code brechen, weil die constructor Eigenschaft sowohl beschreibbar als auch konfigurierbar ist und in keiner Weise geschtzt ist. Daher verwenden viele eingebaute Kopiermethoden stattdessen die [Symbol.species] Eigenschaft des Konstruktors (die standardmig einfach this, also den Konstruktor selbst, zurckgibt). Aber [Symbol.species] erlaubt es, beliebigen Code auszufhren und Instanzen eines beliebigen Typs zu erstellen, was ein Sicherheitsproblem darstellt und die Semantik der Unterklassenerstellung erheblich verkompliziert.
  • Drittens fhrt es zu sichtbaren Aufrufen von benutzerdefiniertem Code, was viele Optimierungen erschwert. Zum Beispiel, wenn der Map() Konstruktor mit einem iterierbaren von x Elementen aufgerufen wird, muss er die set() Methode x-mal sichtbar aufrufen, anstatt die Elemente einfach in den internen Speicher zu kopieren.

Diese Probleme sind nicht einzigartig fr eingebaute Klassen. Bei Ihren eigenen Klassen mssen Sie wahrscheinlich die gleichen Entscheidungen treffen. Bei eingebauten Klassen sind jedoch Optimierbarkeit und Sicherheit ein viel greres Anliegen. Neue eingebaute Methoden konstruieren immer die Basisklasse und rufen so wenige benutzerdefinierte Methoden wie mglich auf. Wenn Sie eingebaute Klassen unterklassen wollen und trotzdem die oben genannten Erwartungen erfllen mchten, mssen Sie alle Methoden berschreiben, bei denen das Standardverhalten fest integriert ist. Jede Hinzufgung neuer Methoden in die Basisklasse kann auch die Semantik Ihrer Unterklasse brechen, da sie standardmig vererbt werden. Daher ist eine bessere Art, eingebaute Klassen zu erweitern, die Zusammensetzung zu verwenden.

Null erweitern

extends null wurde entworfen, um die einfache Erstellung von Objekten, die nicht von Object.prototype erben zu ermglichen. Aufgrund ungelster Entscheidungen darber, ob super() im Konstruktor aufgerufen werden sollte, ist es jedoch in der Praxis nicht mglich, eine solche Klasse unter Verwendung einer beliebigen Konstruktorimplementierung zu konstruieren, die kein Objekt zurckgibt. Das TC39 Komitee arbeitet daran, dieses Feature wieder zu aktivieren.

js
new (class extends null {})();
// TypeError: Super constructor null of anonymous class is not a constructor

new (class extends null {
  constructor() {}
})();
// ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

new (class extends null {
  constructor() {
    super();
  }
})();
// TypeError: Super constructor null of anonymous class is not a constructor

Stattdessen mssen Sie explizit eine Instanz aus dem Konstruktor zurckgeben.

js
class NullClass extends null {
  constructor() {
    // Using new.target allows derived classes to
    // have the correct prototype chain
    return Object.create(new.target.prototype);
  }
}

const proto = Object.getPrototypeOf;
console.log(proto(proto(new NullClass()))); // null

Beispiele

Verwendung von extends

Das erste Beispiel erstellt eine Klasse namens Square von einer Klasse namens Polygon. Dieses Beispiel stammt aus diesem Live-Demo (Quelle).

js
class Square extends Polygon {
  constructor(length) {
    // Here, it calls the parent class' constructor with lengths
    // provided for the Polygon's width and height
    super(length, length);
    // Note: In derived classes, super() must be called before you
    // can use 'this'. Leaving this out will cause a reference error.
    this.name = "Square";
  }

  get area() {
    return this.height * this.width;
  }
}

Normale Objekte erweitern

Klassen knnen regulre (nicht konstruierbare) Objekte nicht erweitern. Wenn Sie von einem regulren Objekt erben mchten, indem Sie alle Eigenschaften dieses Objekts in geerbten Instanzen verfgbar machen, knnen Sie stattdessen Object.setPrototypeOf() verwenden:

js
const Animal = {
  speak() {
    console.log(`${this.name} makes a noise.`);
  },
};

class Dog {
  constructor(name) {
    this.name = name;
  }
}

Object.setPrototypeOf(Dog.prototype, Animal);

const d = new Dog("Mitzie");
d.speak(); // Mitzie makes a noise.

Eingebaute Objekte erweitern

Dieses Beispiel erweitert das eingebaute Date Objekt. Dieses Beispiel stammt aus diesem Live-Demo (Quelle).

js
class MyDate extends Date {
  getFormattedDate() {
    const months = [
      "Jan", "Feb", "Mar", "Apr", "May", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
    ];
    return `${this.getDate()}-${months[this.getMonth()]}-${this.getFullYear()}`;
  }
}

Object erweitern

Alle JavaScript-Objekte erben standardmig von Object.prototype, daher scheint das Schreiben von extends Object auf den ersten Blick redundant zu sein. Der einzige Unterschied, extends nicht zu schreiben, besteht darin, dass der Konstruktor selbst statische Methoden von Object erbt, wie zum Beispiel Object.keys(). Da jedoch keine statische Methode von Object den this Wert verwendet, gibt es keinen Nutzen darin, diese statischen Methoden zu erben.

Der Object() Konstruktor behandelt die Unterklassification besonders. Wenn er implizit ber super() aufgerufen wird, initialisiert er immer ein neues Objekt mit new.target.prototype als sein Prototyp. Jeder an super() bergebene Wert wird ignoriert.

js
class C extends Object {
  constructor(v) {
    super(v);
  }
}

console.log(new C(1) instanceof Number); // false
console.log(C.keys({ a: 1, b: 2 })); // [ 'a', 'b' ]

Vergleichen Sie dieses Verhalten mit einem benutzerdefinierten Wrapper, der die Unterklassification nicht besonders behandelt:

js
function MyObject(v) {
  return new Object(v);
}
class D extends MyObject {
  constructor(v) {
    super(v);
  }
}
console.log(new D(1) instanceof Number); // true

Species

Es kann sein, dass Sie Array Objekte in Ihrer abgeleiteten Array-Klasse MyArray zurckgeben mchten. Das Species-Muster ermglicht es Ihnen, Standardkonstruktoren zu berschreiben.

Zum Beispiel mchten Sie bei der Verwendung von Methoden wie Array.prototype.map(), die den Standardkonstruktor zurckgeben, dass diese Methoden ein bergeordnetes Array-Objekt zurckgeben, anstelle des MyArray-Objekts. Das Symbol.species Symbol lsst Sie dies tun:

js
class MyArray extends Array {
  // Overwrite species to the parent Array constructor
  static get [Symbol.species]() {
    return Array;
  }
}

const a = new MyArray(1, 2, 3);
const mapped = a.map((x) => x * x);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true

Dieses Verhalten wird von vielen eingebauten Kopiermethoden implementiert. Fr Vorbehalte zu diesem Feature siehe die Diskussion zur Unterklassenbildung eingebauter Klassen.

Mix-ins

Abstrakte Unterklassen oder Mix-ins sind Vorlagen fr Klassen. Eine Klasse kann nur eine einzige Oberklasse haben, daher ist Mehrfachvererbung von Werkzeugklassen beispielsweise nicht mglich. Die Funktionalitt muss von der Oberklasse bereitgestellt werden.

Eine Funktion mit einer Oberklasse als Eingabe und einer Unterklasse, die diese Oberklasse erweitert, als Ausgabe kann verwendet werden, um Mix-ins zu implementieren:

js
const calculatorMixin = (Base) =>
  class extends Base {
    calc() {}
  };

const randomizerMixin = (Base) =>
  class extends Base {
    randomize() {}
  };

Eine Klasse, die diese Mix-ins verwendet, kann dann so geschrieben werden:

js
class Foo {}
class Bar extends calculatorMixin(randomizerMixin(Foo)) {}

Vermeidung von Vererbung

Vererbung ist eine sehr starke Kopplungsbeziehung in der objektorientierten Programmierung. Sie bedeutet, dass alle Verhaltensweisen der Basisklasse standardmig von der Unterklasse geerbt werden, was nicht immer gewnscht sein knnte. Betrachten Sie zum Beispiel die Implementierung einer ReadOnlyMap:

js
class ReadOnlyMap extends Map {
  set() {
    throw new TypeError("A read-only map must be set at construction time.");
  }
}

Es stellt sich heraus, dass ReadOnlyMap nicht konstruierbar ist, da der Map() Konstruktor die set() Methode der Instanz aufruft.

js
const m = new ReadOnlyMap([["a", 1]]); // TypeError: A read-only map must be set at construction time.

Wir knnen dies umgehen, indem wir einen privaten Indikator verwenden, um anzuzeigen, ob die Instanz konstruiert wird. Ein wesentliches Problem bei diesem Design ist jedoch, dass es das Liskov'sche Substitutionsprinzip bricht, welches besagt, dass eine Unterklasse fr ihre Oberklasse austauschbar sein sollte. Wenn eine Funktion ein Map-Objekt erwartet, sollte sie auch ein ReadOnlyMap-Objekt verwenden knnen, was hier jedoch zu einem Bruch fhrt.

Vererbung fhrt oft zu dem Kreis-Ellipse-Problem, da kein Typ perfekt das Verhalten des anderen beinhaltet, obwohl sie viele gemeinsame Merkmale teilen. Im Allgemeinen ist es, es sei denn, es gibt einen sehr guten Grund, Vererbung zu verwenden, besser, Zusammensetzung einzusetzen. Zusammensetzung bedeutet, dass eine Klasse eine Referenz zu einem Objekt einer anderen Klasse hat und dieses Objekt nur als Implementierungsdetail verwendet.

js
class ReadOnlyMap {
  #data;
  constructor(values) {
    this.#data = new Map(values);
  }
  get(key) {
    return this.#data.get(key);
  }
  has(key) {
    return this.#data.has(key);
  }
  get size() {
    return this.#data.size;
  }
  *keys() {
    yield* this.#data.keys();
  }
  *values() {
    yield* this.#data.values();
  }
  *entries() {
    yield* this.#data.entries();
  }
  *[Symbol.iterator]() {
    yield* this.#data[Symbol.iterator]();
  }
}

In diesem Fall ist die ReadOnlyMap Klasse keine Unterklasse von Map, aber sie implementiert dennoch die meisten der gleichen Methoden. Dies bedeutet mehr Code-Duplikation, aber es bedeutet auch, dass die ReadOnlyMap Klasse nicht stark mit der Map Klasse gekoppelt ist und nicht leicht bricht, wenn sich die Map Klasse ndert. Es vermeidet die semantischen Probleme der eingebauten Unterklassifizierung. Zum Beispiel, wenn die Map Klasse eine neue Utility-Methode hinzufgt (wie getOrInsert()), die set() nicht aufruft, wrde dies bedeuten, dass die ReadOnlyMap Klasse nicht mehr schreibgeschtzt ist, es sei denn, letztere wird entsprechend aktualisiert, um getOrInsert() ebenfalls zu berschreiben. Auerdem haben ReadOnlyMap Objekte die set Methode berhaupt nicht, was genauer ist, als zur Laufzeit einen Fehler auszulsen.

Spezifikationen

Spezifikation
ECMAScript 2027 LanguageSpecification
# sec-class-definitions

Browser-Kompatibilitt

Siehe auch


Web Proxy Viewer  |  New URL  |  Original Page