| [ Web Proxy ] |
| Viewing: https://developer.mozilla.org/de/docs/Web/JavaScript/Guide/Using_classes | [Back] [Original] |
Get to know MDN better
Dieser Inhalt wurde automatisch aus dem Englischen bersetzt, und kann Fehler enthalten. Erfahre mehr ber dieses Experiment.
JavaScript ist eine prototypbasierte Sprache: Das Verhalten eines Objekts wird durch seine eigenen Eigenschaften und die Eigenschaften seines Prototyps bestimmt. Mit der Einfhrung von Klassen hnelt das Erstellen von Objekthierarchien und das Vererben von Eigenschaften und deren Werten jedoch strker dem Vorgehen in anderen objektorientierten Sprachen wie Java. In diesem Abschnitt zeigen wir, wie Sie Objekte aus Klassen erstellen.
In vielen anderen Sprachen wird klar zwischen Klassen beziehungsweise Konstruktoren und Objekten beziehungsweise Instanzen unterschieden. In JavaScript sind Klassen hauptschlich eine Abstraktion ber dem bestehenden Mechanismus der prototypbasierten Vererbung alle entsprechenden Muster lassen sich auf prototypbasierte Vererbung zurckfhren. Klassen sind selbst ebenfalls gewhnliche JavaScript-Werte und haben eigene Prototypketten. Tatschlich lassen sich die meisten gewhnlichen JavaScript-Funktionen als Konstruktoren verwenden: Mit dem Operator new und einer Konstruktorfunktion erstellen Sie ein neues Objekt.
In diesem Tutorial arbeiten wir mit dem abstrahierten Klassenmodell und besprechen, welche Semantik Klassen bieten. Wenn Sie das zugrunde liegende Prototypsystem genauer kennenlernen mchten, lesen Sie den Leitfaden Vererbung und die Prototypkette.
Dieses Kapitel setzt voraus, dass Sie bereits etwas mit JavaScript vertraut sind und gewhnliche Objekte verwendet haben.
extends und Vererbung
Wenn Sie praktische Erfahrung mit JavaScript haben oder dem Leitfaden bisher gefolgt sind, haben Sie wahrscheinlich schon Klassen verwendet, auch wenn Sie selbst noch keine erstellt haben. Beispielsweise drfte Ihnen Folgendes bekannt vorkommen:
const bigDay = new Date(2019, 6, 19);
console.log(bigDay.toLocaleDateString());
if (bigDay.getTime() < Date.now()) {
console.log("Once upon a time...");
}
In der ersten Zeile haben wir eine Instanz der Klasse Date erstellt und sie bigDay genannt. In der zweiten Zeile haben wir die Methode toLocaleDateString() auf der Instanz bigDay aufgerufen. Sie gibt einen String zurck. Anschlieend haben wir zwei Zahlen verglichen: Die eine stammt aus der Methode getTime(), die andere wurde ber Date.now() direkt von der Klasse Date selbst abgerufen.
Date ist eine integrierte JavaScript-Klasse. Dieses Beispiel vermittelt einige grundlegende Vorstellungen davon, was Klassen tun:
new.Das entspricht den drei zentralen Merkmalen von Klassen:
Klassen werden blicherweise mit Klassendeklarationen erstellt.
class MyClass {
// class body...
}
Innerhalb eines Klassenkrpers stehen verschiedene Funktionen zur Verfgung.
class MyClass {
// Constructor
constructor() {
// Constructor body
}
// Instance field
myField = "foo";
// Instance method
myMethod() {
// myMethod body
}
// Static field
static myStaticField = "bar";
// Static method
static myStaticMethod() {
// myStaticMethod body
}
// Static block
static {
// Static initialization code
}
// Fields, methods, static fields, and static methods all have
// "private" forms
#myPrivateField = "bar";
}
Wenn Sie JavaScript schon vor ES6 verwendet haben, sind Sie mglicherweise eher damit vertraut, Funktionen als Konstruktoren zu verwenden. Das obige Muster liee sich ungefhr wie folgt mit Funktionskonstruktoren umsetzen:
function MyClass() {
this.myField = "foo";
// Constructor body
}
MyClass.myStaticField = "bar";
MyClass.myStaticMethod = function () {
// myStaticMethod body
};
MyClass.prototype.myMethod = function () {
// myMethod body
};
(function () {
// Static initialization code
})();
Hinweis: Private Felder und Methoden sind neue Klassenfunktionen, fr die es in Funktionskonstruktoren keine einfache Entsprechung gibt.
Nachdem eine Klasse deklariert wurde, knnen Sie mit dem Operator new Instanzen davon erstellen.
const myInstance = new MyClass();
console.log(myInstance.myField); // 'foo'
myInstance.myMethod();
Typische Funktionskonstruktoren knnen sowohl mit new als auch ohne new aufgerufen werden. Der Versuch, eine Klasse ohne new aufzurufen, fhrt dagegen zu einem Fehler.
const myInstance = MyClass(); // TypeError: Class constructor MyClass cannot be invoked without 'new'
Anders als Funktionsdeklarationen werden Klassendeklarationen nicht durch Hoisting vorgezogen oder nach manchen Auslegungen zwar vorgezogen, unterliegen aber der Einschrnkung der Temporal Dead Zone. Das bedeutet, dass Sie eine Klasse nicht verwenden knnen, bevor sie deklariert wurde.
new MyClass(); // ReferenceError: Cannot access 'MyClass' before initialization
class MyClass {}
Dieses Verhalten hnelt dem von Variablen, die mit let und const deklariert werden.
Wie bei Funktionen gibt es auch fr Klassendeklarationen entsprechende Ausdrcke.
const MyClass = class {
// Class body...
};
Klassenausdrcke knnen ebenfalls einen Namen haben. Der Name des Ausdrucks ist nur innerhalb des Klassenkrpers sichtbar.
const MyClass = class MyClassLongerName {
// Class body. Here MyClass and MyClassLongerName point to the same class.
};
new MyClassLongerName(); // ReferenceError: MyClassLongerName is not defined
Die vielleicht wichtigste Aufgabe einer Klasse besteht darin, als Fabrik fr Objekte zu dienen. Wenn wir beispielsweise den Konstruktor Date verwenden, erwarten wir ein neues Objekt, das die bergebenen Datumsdaten reprsentiert. Dieses Objekt knnen wir anschlieend mit weiteren Methoden bearbeiten, die die Instanz bereitstellt. In Klassen bernimmt der Konstruktor die Erstellung der Instanz.
Als Beispiel erstellen wir eine Klasse namens Color, die eine bestimmte Farbe reprsentiert. Benutzer erstellen Farben, indem sie ein RGB-Tripel bergeben.
class Color {
constructor(r, g, b) {
// Assign the RGB values as a property of `this`.
this.values = [r, g, b];
}
}
ffnen Sie die Entwicklertools Ihres Browsers, fgen Sie den obigen Code in die Konsole ein und erstellen Sie dann eine Instanz:
const red = new Color(255, 0, 0);
console.log(red);
Sie sollten ungefhr die folgende Ausgabe sehen:
Object { values: (3) [] }
values: Array(3) [ 255, 0, 0 ]
Sie haben erfolgreich eine Color-Instanz erstellt. Die Instanz hat eine Eigenschaft values, die ein Array mit den bergebenen RGB-Werten enthlt. Das entspricht weitgehend Folgendem:
function createColor(r, g, b) {
return {
values: [r, g, b],
};
}
Die Syntax des Konstruktors entspricht genau der einer normalen Funktion. Sie knnen daher auch andere Syntaxformen wie Rest-Parameter verwenden:
class Color {
constructor(...values) {
this.values = values;
}
}
const red = new Color(255, 0, 0);
// Creates an instance with the same shape as above.
Bei jedem Aufruf von new wird eine andere Instanz erstellt.
const red = new Color(255, 0, 0);
const anotherRed = new Color(255, 0, 0);
console.log(red === anotherRed); // false
Innerhalb eines Klassenkonstruktors verweist this auf die neu erstellte Instanz. Sie knnen ihr Eigenschaften zuweisen oder vorhandene Eigenschaften auslesen insbesondere Methoden, auf die wir als Nchstes eingehen.
Der Wert von this wird automatisch als Ergebnis von new zurckgegeben. Sie sollten daher keinen Wert aus dem Konstruktor zurckgeben: Wenn Sie einen nicht-primitiven Wert zurckgeben, wird dieser zum Wert des new-Ausdrucks und der Wert von this wird verworfen. (Mehr darber, was new bewirkt, erfahren Sie in seiner Beschreibung.)
class MyClass {
constructor() {
this.myField = "foo";
return {};
}
}
console.log(new MyClass().myField); // undefined
Wenn eine Klasse nur einen Konstruktor hat, unterscheidet sie sich kaum von einer Fabrikfunktion createX, die lediglich gewhnliche Objekte erstellt. Die Strke von Klassen liegt jedoch darin, dass sie als Vorlagen dienen knnen, die Instanzen automatisch Methoden zur Verfgung stellen.
Bei Date-Instanzen knnen Sie beispielsweise mit verschiedenen Methoden Informationen aus einem einzelnen Datumswert abrufen, etwa das Jahr, den Monat oder den Wochentag. Mit den entsprechenden setX-Methoden wie setFullYear knnen Sie diese Werte auch setzen.
Unserer Klasse Color knnen wir eine Methode namens getRed hinzufgen, die den Rotwert der Farbe zurckgibt.
class Color {
constructor(r, g, b) {
this.values = [r, g, b];
}
getRed() {
return this.values[0];
}
}
const red = new Color(255, 0, 0);
console.log(red.getRed()); // 255
Ohne Methoden knnten Sie versucht sein, die Funktion innerhalb des Konstruktors zu definieren:
class Color {
constructor(r, g, b) {
this.values = [r, g, b];
this.getRed = function () {
return this.values[0];
};
}
}
Das funktioniert ebenfalls. Allerdings wird dabei jedes Mal eine neue Funktion erstellt, wenn eine Color-Instanz erzeugt wird obwohl alle diese Funktionen dasselbe tun!
console.log(new Color().getRed === new Color().getRed); // false
Wenn Sie stattdessen eine Methode verwenden, wird sie von allen Instanzen gemeinsam genutzt. Eine Funktion kann von allen Instanzen gemeinsam genutzt werden und sich beim Aufruf durch verschiedene Instanzen dennoch unterschiedlich verhalten, weil der Wert von this jeweils ein anderer ist. Falls Sie wissen mchten, wo diese Methode gespeichert ist: Sie wird auf dem Prototyp aller Instanzen definiert, also auf Color.prototype. Nheres dazu finden Sie unter Vererbung und die Prototypkette.
Ebenso knnen wir eine neue Methode namens setRed erstellen, die den Rotwert der Farbe setzt.
class Color {
constructor(r, g, b) {
this.values = [r, g, b];
}
getRed() {
return this.values[0];
}
setRed(value) {
this.values[0] = value;
}
}
const red = new Color(255, 0, 0);
red.setRed(0);
console.log(red.getRed()); // 0; of course, it should be called "black" at this stage!
Vielleicht fragen Sie sich: Warum sollten wir uns die Mhe machen, getRed und setRed zu verwenden, wenn wir direkt auf das Array values der Instanz zugreifen knnen?
class Color {
constructor(r, g, b) {
this.values = [r, g, b];
}
}
const red = new Color(255, 0, 0);
red.values[0] = 0;
console.log(red.values[0]); // 0
In der objektorientierten Programmierung gibt es ein Prinzip namens Kapselung. Es besagt, dass Sie nicht direkt auf die zugrunde liegende Implementierung eines Objekts zugreifen sollten, sondern ber abstrahierte Methoden mit ihm interagieren. Angenommen, wir beschlieen pltzlich, Farben stattdessen als HSL darzustellen:
class Color {
constructor(r, g, b) {
// values is now an HSL array!
this.values = rgbToHSL([r, g, b]);
}
getRed() {
return hslToRGB(this.values)[0];
}
setRed(value) {
const rgb = hslToRGB(this.values);
rgb[0] = value;
this.values = rgbToHSL(rgb);
}
}
const red = new Color(255, 0, 0);
console.log(red.values[0]); // 0; It's not 255 anymore, because the H value for pure red is 0
Die Annahme der Benutzer, values enthalte RGB-Werte, trifft dann nicht mehr zu, wodurch ihre Programmlogik fehlschlagen kann. Wenn Sie eine Klasse implementieren, mchten Sie deshalb die interne Datenstruktur ihrer Instanzen vor Benutzern verbergen. So bleibt die API bersichtlich, und der Code der Benutzer geht nicht durch eine vermeintlich harmlose Umstrukturierung kaputt. In Klassen erreichen Sie das mit privaten Feldern.
Ein privates Feld ist ein Bezeichner mit vorangestelltem # (dem Rautezeichen). Die Raute ist fester Bestandteil des Feldnamens. Deshalb kann der Name eines privaten Feldes niemals mit dem eines ffentlichen Feldes oder einer Methode kollidieren. Damit Sie innerhalb der Klasse auf ein privates Feld zugreifen knnen, mssen Sie es im Klassenkrper deklarieren ein privates Element lsst sich nicht spontan erstellen. Abgesehen davon entspricht ein privates Feld weitgehend einer normalen Eigenschaft.
class Color {
// Declare: every Color instance has a private field called #values.
#values;
constructor(r, g, b) {
this.#values = [r, g, b];
}
getRed() {
return this.#values[0];
}
setRed(value) {
this.#values[0] = value;
}
}
const red = new Color(255, 0, 0);
console.log(red.getRed()); // 255
Der Zugriff auf private Felder auerhalb der Klasse fhrt bereits beim Parsen zu einem Syntaxfehler. Die Sprache kann dies verhindern, weil #privateField eine besondere Syntax ist. Dadurch kann sie den Code statisch analysieren und alle Verwendungen privater Felder finden, noch bevor der Code ausgefhrt wird.
console.log(red.#values); // SyntaxError: Private field '#values' must be declared in an enclosing class
Hinweis: Code, der in der Chrome-Konsole ausgefhrt wird, kann auch auerhalb der Klasse auf private Elemente zugreifen. Diese Lockerung der JavaScript-Syntaxbeschrnkung gilt nur fr die Entwicklertools.
Private Felder in JavaScript sind streng privat: Wenn die Klasse keine Methoden implementiert, die diese Felder zugnglich machen, gibt es keinerlei Mglichkeit, sie von auerhalb der Klasse abzurufen. Sie knnen die privaten Felder Ihrer Klasse daher bedenkenlos umstrukturieren, solange das Verhalten der ffentlich zugnglichen Methoden gleich bleibt.
Nachdem wir das Feld values privat gemacht haben, knnen wir die Methoden getRed und setRed um zustzliche Logik erweitern, statt sie nur Werte durchreichen zu lassen. Beispielsweise knnen wir in setRed prfen, ob der bergebene R-Wert gltig ist:
class Color {
#values;
constructor(r, g, b) {
this.#values = [r, g, b];
}
getRed() {
return this.#values[0];
}
setRed(value) {
if (value < 0 || value > 255) {
throw new RangeError("Invalid R value");
}
this.#values[0] = value;
}
}
const red = new Color(255, 0, 0);
red.setRed(1000); // RangeError: Invalid R value
Wenn die Eigenschaft values ffentlich zugnglich bleibt, knnen Benutzer diese Prfung leicht umgehen, indem sie values[0] direkt einen Wert zuweisen, und so ungltige Farben erzeugen. Mit einer gut gekapselten API knnen wir unseren Code dagegen robuster machen und Folgefehler in der Programmlogik verhindern.
Eine Klassenmethode kann die privaten Felder anderer Instanzen lesen, sofern diese derselben Klasse angehren.
class Color {
#values;
constructor(r, g, b) {
this.#values = [r, g, b];
}
redDifference(anotherColor) {
// #values doesn't necessarily need to be accessed from this:
// you can access private fields of other instances belonging
// to the same class.
return this.#values[0] - anotherColor.#values[0];
}
}
const red = new Color(255, 0, 0);
const crimson = new Color(220, 20, 60);
red.redDifference(crimson); // 35
Wenn anotherColor jedoch keine Color-Instanz ist, existiert #values nicht. (Selbst wenn eine andere Klasse ebenfalls ein privates Feld namens #values hat, handelt es sich nicht um dasselbe Feld und es kann hier nicht darauf zugegriffen werden.) Der Zugriff auf ein nicht vorhandenes privates Element lst einen Fehler aus, statt wie bei normalen Eigenschaften undefined zurckzugeben. Wenn Sie nicht wissen, ob ein privates Feld auf einem Objekt existiert, und ohne try/catch darauf zugreifen mchten, knnen Sie den Operator in verwenden.
class Color {
#values;
constructor(r, g, b) {
this.#values = [r, g, b];
}
redDifference(anotherColor) {
if (!(#values in anotherColor)) {
throw new TypeError("Color instance expected");
}
return this.#values[0] - anotherColor.#values[0];
}
}
Hinweis:
Beachten Sie, dass # Teil einer besonderen Bezeichnersyntax ist und Sie den Feldnamen nicht wie einen String verwenden knnen. "#values" in anotherColor wrde nach einer Eigenschaft suchen, die wrtlich "#values" heit, nicht nach einem privaten Feld.
Fr private Elemente gelten einige Einschrnkungen: Derselbe Name darf innerhalb einer Klasse nicht zweimal deklariert werden, und private Elemente knnen nicht gelscht werden. Beides fhrt bereits beim Parsen zu Syntaxfehlern.
class BadIdeas {
#firstName;
#firstName; // syntax error occurs here
#lastName;
constructor() {
delete this.#lastName; // also a syntax error
}
}
Auch Methoden, Getter und Setter knnen privat sein. Sie sind ntzlich, wenn eine Klasse intern eine komplexere Aufgabe ausfhren muss, die von keinem anderen Teil des Codes aufgerufen werden soll.
Stellen Sie sich beispielsweise vor, Sie erstellen benutzerdefinierte HTML-Elemente, die beim Anklicken, Antippen oder einer anderen Aktivierung eine etwas komplexere Aktion ausfhren sollen. Auerdem sollen die dabei ausgefhrten Vorgnge auf diese Klasse beschrnkt bleiben, weil kein anderer Teil des JavaScript-Codes darauf zugreifen wird oder sollte.
class Counter extends HTMLElement {
#xValue = 0;
constructor() {
super();
this.onclick = this.#clicked.bind(this);
}
get #x() {
return this.#xValue;
}
set #x(value) {
this.#xValue = value;
window.requestAnimationFrame(this.#render.bind(this));
}
#clicked() {
this.#x++;
}
#render() {
this.textContent = this.#x.toString();
}
connectedCallback() {
this.#render();
}
}
customElements.define("num-counter", Counter);
In diesem Fall sind nahezu alle Felder und Methoden fr die Klasse privat. Gegenber dem brigen Code bietet sie damit eine Schnittstelle, die im Wesentlichen der eines integrierten HTML-Elements entspricht. Kein anderer Teil des Programms kann den internen Zustand von Counter beeinflussen.
Mit color.getRed() und color.setRed() knnen wir den Rotwert einer Farbe lesen und schreiben. Wenn Sie aus einer Sprache wie Java kommen, ist Ihnen dieses Muster vermutlich sehr vertraut. In JavaScript ist es jedoch etwas umstndlich, Methoden nur fr den Zugriff auf eine Eigenschaft zu verwenden. Zugriffsfelder ermglichen es uns, einen Wert so zu verwenden, als wre er eine echte Eigenschaft.
class Color {
constructor(r, g, b) {
this.values = [r, g, b];
}
get red() {
return this.values[0];
}
set red(value) {
this.values[0] = value;
}
}
const red = new Color(255, 0, 0);
red.red = 0;
console.log(red.red); // 0
Es sieht so aus, als htte das Objekt eine Eigenschaft namens red tatschlich gibt es auf der Instanz jedoch keine solche Eigenschaft! Es gibt nur zwei Methoden. Da ihnen aber get und set vorangestellt sind, lassen sie sich wie Eigenschaften verwenden.
Wenn ein Feld nur einen Getter, aber keinen Setter hat, ist es praktisch schreibgeschtzt.
class Color {
constructor(r, g, b) {
this.values = [r, g, b];
}
get red() {
return this.values[0];
}
}
const red = new Color(255, 0, 0);
red.red = 0;
console.log(red.red); // 255
Im Strict Mode lst die Zeile red.red = 0 einen Typfehler aus: Cannot set property red of #<Color> which has only a getter. Auerhalb des Strict Mode wird die Zuweisung stillschweigend ignoriert.
Zu privaten Feldern gibt es ffentliche Gegenstcke, mit denen jede Instanz eine eigene Eigenschaft erhalten kann. Felder sind blicherweise so konzipiert, dass sie unabhngig von den Parametern des Konstruktors sind.
class MyClass {
luckyNumber = Math.random();
}
console.log(new MyClass().luckyNumber); // 0.5
console.log(new MyClass().luckyNumber); // 0.3
ffentliche Felder entsprechen nahezu einer Zuweisung einer Eigenschaft an this. Das obige Beispiel lsst sich beispielsweise auch so schreiben:
class MyClass {
constructor() {
this.luckyNumber = Math.random();
}
}
Im Beispiel mit Date haben wir auch die Methode Date.now() kennengelernt, die das aktuelle Datum zurckgibt. Diese Methode gehrt zu keiner einzelnen Datumsinstanz, sondern zur Klasse selbst. Sie ist in der Klasse Date definiert und wird nicht als globale Funktion DateNow() bereitgestellt, weil sie vor allem im Zusammenhang mit Datumsinstanzen ntzlich ist.
Hinweis:
Hilfsmethoden mit einem Prfix zu versehen, das ihren Zustndigkeitsbereich angibt, wird Namespacing genannt und gilt als gute Praxis. Beispielsweise hat JavaScript neben der lteren Methode parseInt() ohne Prfix spter auch die Methode Number.parseInt() mit Prfix eingefhrt, um zu verdeutlichen, dass sie fr die Verarbeitung von Zahlen gedacht ist.
Statische Eigenschaften umfassen Klassenfunktionen, die auf der Klasse selbst statt auf einzelnen Instanzen definiert sind. Dazu gehren:
Fr alle gibt es auch private Varianten. Fr unsere Klasse Color knnen wir beispielsweise eine statische Methode erstellen, die prft, ob ein bestimmtes Tripel einen gltigen RGB-Wert darstellt:
class Color {
static isValid(r, g, b) {
return r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255;
}
}
Color.isValid(255, 0, 0); // true
Color.isValid(1000, 0, 0); // false
Statische Eigenschaften hneln den entsprechenden Instanzeigenschaften sehr. Der Unterschied ist:
static vorangestellt.Auerdem gibt es ein besonderes Konstrukt namens statischer Initialisierungsblock. Dabei handelt es sich um einen Codeblock, der ausgefhrt wird, wenn die Klasse zum ersten Mal geladen wird.
class MyClass {
static {
MyClass.myStaticProperty = "foo";
}
}
console.log(MyClass.myStaticProperty); // 'foo'
Statische Initialisierungsblcke entsprechen nahezu Code, der unmittelbar nach der Deklaration einer Klasse ausgefhrt wird. Der einzige Unterschied besteht darin, dass sie auf statische private Elemente zugreifen knnen.
extends und VererbungEin wesentliches Merkmal von Klassen ist neben der einfachen Kapselung durch private Felder die Vererbung. Sie ermglicht es einem Objekt, einen groen Teil des Verhaltens eines anderen Objekts zu bernehmen und bestimmte Teile mit eigener Logik zu berschreiben oder zu erweitern.
Angenommen, unsere Klasse Color soll nun auch Transparenz untersttzen. Wir knnten versucht sein, ein neues Feld fr die Transparenz hinzuzufgen:
class Color {
#values;
constructor(r, g, b, a = 1) {
this.#values = [r, g, b, a];
}
get alpha() {
return this.#values[3];
}
set alpha(value) {
if (value < 0 || value > 1) {
throw new RangeError("Alpha value must be between 0 and 1");
}
this.#values[3] = value;
}
}
Dann msste jedoch jede Instanz einen zustzlichen Alphawert enthalten auch die groe Mehrheit der Instanzen, die nicht transparent sind und deren Alphawert 1 betrgt. Das ist wenig elegant. Wenn weitere Funktionen hinzukommen, wird unsere Klasse Color auerdem zunehmend aufgeblht und schwer zu pflegen.
Stattdessen wrden wir in der objektorientierten Programmierung eine abgeleitete Klasse erstellen. Die abgeleitete Klasse kann auf alle ffentlichen Eigenschaften der Elternklasse zugreifen. In JavaScript werden abgeleitete Klassen mit einer extends-Klausel deklariert, die angibt, von welcher Klasse sie erben.
class ColorWithAlpha extends Color {
#alpha;
constructor(r, g, b, a) {
super(r, g, b);
this.#alpha = a;
}
get alpha() {
return this.#alpha;
}
set alpha(value) {
if (value < 0 || value > 1) {
throw new RangeError("Alpha value must be between 0 and 1");
}
this.#alpha = value;
}
}
Dabei fallen sofort einige Dinge auf. Erstens rufen wir im Konstruktor super(r, g, b) auf. Die Sprache verlangt, dass super() aufgerufen wird, bevor auf this zugegriffen wird. Der Aufruf von super() ruft den Konstruktor der Elternklasse auf, um this zu initialisieren. Hier entspricht das ungefhr this = new Color(r, g, b). Vor super() darf Code stehen, aber Sie drfen vor dem Aufruf von super() nicht auf this zugreifen: Die Sprache verhindert den Zugriff auf das noch nicht initialisierte this.
Nachdem die Elternklasse this bearbeitet hat, kann die abgeleitete Klasse ihre eigene Logik ausfhren. Hier haben wir ein privates Feld namens #alpha hinzugefgt und ein Paar aus Getter und Setter bereitgestellt, um darauf zuzugreifen.
Eine abgeleitete Klasse erbt alle Methoden ihrer Elternklasse. Betrachten wir beispielsweise den Zugriff ber get red(), den wir im Abschnitt Zugriffsfelder zu Color hinzugefgt haben. Obwohl wir ihn nicht in ColorWithAlpha deklariert haben, knnen wir weiterhin auf red zugreifen, weil dieses Verhalten von der Elternklasse definiert wird:
const color = new ColorWithAlpha(255, 0, 0, 0.5);
console.log(color.red); // 255
Abgeleitete Klassen knnen Methoden der Elternklasse auch berschreiben. Beispielsweise erben alle Klassen implizit von der Klasse Object, die einige grundlegende Methoden wie toString() definiert. Die grundlegende Implementierung von toString() ist allerdings oft wenig hilfreich, da sie in den meisten Fllen [object Object] ausgibt:
console.log(red.toString()); // [object Object]
Unsere Klasse kann die Methode stattdessen berschreiben, damit sie die RGB-Werte der Farbe ausgibt:
class Color {
#values;
//
toString() {
return this.#values.join(", ");
}
}
console.log(new Color(255, 0, 0).toString()); // '255, 0, 0'
Innerhalb abgeleiteter Klassen knnen Sie mit super auf Methoden der Elternklasse zugreifen. So knnen Sie Methoden erweitern, ohne Code zu duplizieren.
class ColorWithAlpha extends Color {
#alpha;
//
toString() {
// Call the parent class's toString() and build on the return value
return `${super.toString()}, ${this.#alpha}`;
}
}
console.log(new ColorWithAlpha(255, 0, 0, 0.5).toString()); // '255, 0, 0, 0.5'
Wenn Sie extends verwenden, werden auch statische Methoden vererbt. Sie knnen sie daher ebenfalls berschreiben oder erweitern.
class ColorWithAlpha extends Color {
//
static isValid(r, g, b, a) {
// Call the parent class's isValid() and build on the return value
return super.isValid(r, g, b) && a >= 0 && a <= 1;
}
}
console.log(ColorWithAlpha.isValid(255, 0, 0, -1)); // false
Abgeleitete Klassen haben keinen Zugriff auf die privaten Felder ihrer Elternklasse. Das ist ein weiterer wichtiger Aspekt der strengen Privatheit von JavaScript-Feldern. Private Felder sind auf den jeweiligen Klassenkrper beschrnkt und gewhren keinem Code auerhalb davon Zugriff.
class ColorWithAlpha extends Color {
log() {
console.log(this.#values); // SyntaxError: Private field '#values' must be declared in an enclosing class
}
}
Eine Klasse kann nur von einer einzigen Klasse erben. Dadurch werden Probleme der Mehrfachvererbung wie das Diamond-Problem vermieden. Aufgrund der dynamischen Natur von JavaScript lsst sich der Effekt einer Mehrfachvererbung durch Klassenkomposition und Mixins dennoch erzielen.
Instanzen abgeleiteter Klassen sind zugleich Instanzen der Basisklasse.
const color = new ColorWithAlpha(255, 0, 0, 0.5);
console.log(color instanceof Color); // true
console.log(color instanceof ColorWithAlpha); // true
Bisher war dieser Leitfaden pragmatisch ausgerichtet: Wir haben uns darauf konzentriert, wie Klassen verwendet werden. Eine Frage ist jedoch noch offen: Warum sollte man eine Klasse verwenden? Die Antwort lautet: Es kommt darauf an.
Klassen fhren ein Paradigma ein, also eine Art, Code zu organisieren. Sie bilden die Grundlage der objektorientierten Programmierung, die auf Konzepten wie Vererbung und Polymorphismus beruht, insbesondere auf Subtyp-Polymorphismus. Viele Menschen lehnen jedoch bestimmte Praktiken der objektorientierten Programmierung grundstzlich ab und verwenden deshalb keine Klassen.
Ein Grund fr den schlechten Ruf von Date-Objekten ist beispielsweise, dass sie vernderbar sind.
function incrementDay(date) {
return new Date(date.setDate(date.getDate() + 1));
}
const date = new Date(); // 2019-06-19
const newDay = incrementDay(date);
console.log(newDay); // 2019-06-20
// The old date is modified as well!?
console.log(date); // 2019-06-20
Vernderbarkeit und interner Zustand sind wichtige Aspekte der objektorientierten Programmierung. Sie erschweren es jedoch oft, das Verhalten von Code nachzuvollziehen, weil selbst eine scheinbar harmlose Operation unerwartete Nebenwirkungen haben und das Verhalten anderer Programmteile verndern kann.
Um Code wiederzuverwenden, greifen wir hufig darauf zurck, Klassen zu erweitern. Dadurch knnen groe Vererbungshierarchien entstehen.
[Ein typischer OOP-Vererbungsbaum mit fnf Klassen auf drei Ebenen]
Vererbung lsst sich jedoch oft nur schwer klar abbilden, wenn eine Klasse nur von einer anderen Klasse erben kann. Hufig mchten wir das Verhalten mehrerer Klassen kombinieren. In Java geschieht das ber Interfaces, in JavaScript ber Mixins. Besonders bequem ist es letztlich trotzdem nicht.
Andererseits sind Klassen ein sehr leistungsfhiges Mittel, um Code auf einer hheren Ebene zu organisieren. Ohne die Klasse Color mssten wir beispielsweise mglicherweise ein Dutzend Hilfsfunktionen erstellen:
function isRed(color) {
return color.red === 255;
}
function isValidColor(color) {
return (
color.red >= 0 &&
color.red <= 255 &&
color.green >= 0 &&
color.green <= 255 &&
color.blue >= 0 &&
color.blue <= 255
);
}
//
Mit Klassen knnen wir sie jedoch alle unter dem Namespace Color zusammenfassen und so die Lesbarkeit verbessern. Darber hinaus knnen wir mit privaten Feldern bestimmte Daten vor Benutzern der API verbergen und eine klare Schnittstelle schaffen.
Grundstzlich sollten Sie Klassen in Betracht ziehen, wenn Sie Objekte erstellen mchten, die eigene interne Daten speichern und viele Funktionen bereitstellen. Beispiele dafr sind integrierte JavaScript-Klassen:
Map und Set speichern Sammlungen von Elementen und ermglichen unter anderem mit get(), set() und has() den Zugriff auf beziehungsweise die Arbeit mit ihnen.Date speichert ein Datum als Unix-Zeitstempel (eine Zahl). Sie ermglicht es, das Datum zu formatieren und zu aktualisieren sowie einzelne Datumsbestandteile auszulesen.Error speichert Informationen ber eine bestimmte Ausnahme, darunter die Fehlermeldung, den Stack-Trace und die Ursache. Sie gehrt zu den wenigen Klassen mit einer umfangreichen Vererbungsstruktur: Mehrere integrierte Klassen wie TypeError und ReferenceError erben von Error. Bei Fehlern ermglicht diese Vererbung eine genauere Unterscheidung: Jede Fehlerklasse reprsentiert einen bestimmten Fehlertyp, der sich leicht mit instanceof prfen lsst.JavaScript bietet die Mglichkeit, Code auf klassische objektorientierte Weise zu organisieren. Ob und wie Sie diese Mglichkeit nutzen, liegt ganz in Ihrem Ermessen.
Der Bauplan fr ein besseres Internet.
Teile dieses Inhalts sind 19982026 von einzelnen mozilla.org-Mitwirkenden. Inhalte sind verfgbar unter einer Creative-Commons-Lizenz.
| Web Proxy Viewer | New URL | Original Page |