| [ Web Proxy ] |
| Viewing: https://developer.mozilla.org/de/docs/Web/JavaScript/Guide/Using_classes#extends_and_inheritance | [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 ist die Erstellung von Objekthierarchien und die Vererbung von Eigenschaften und deren Werten jedoch viel strker an andere objektorientierte Sprachen wie Java angelehnt. In diesem Abschnitt werden wir demonstrieren, wie Objekte aus Klassen erstellt werden knnen.
In vielen anderen Sprachen werden Klassen oder Konstruktoren klar von Objekten oder Instanzen unterschieden. In JavaScript sind Klassen hauptschlich eine Abstraktion ber den bestehenden mechanismus der prototypischen Vererbung alle Muster knnen in prototypbasierte Vererbung umgewandelt werden. Klassen selbst sind auch normale JavaScript-Werte und haben ihre eigenen Prototypenketten. Tatschlich knnen die meisten einfachen JavaScript-Funktionen als Konstruktoren verwendet werden Sie verwenden den new-Operator mit einer Konstruktorfunktion, um ein neues Objekt zu erstellen.
Wir werden in diesem Tutorial mit dem gut abstrahierten Klassenmodell arbeiten und diskutieren, welche Semantik Klassen bieten. Wenn Sie tiefer in das zugrunde liegende Prototypsystem eintauchen mchten, knnen Sie den Leitfaden Vererbung und die Prototypen-Kette lesen.
In diesem Kapitel wird vorausgesetzt, dass Sie bereits ein gewisses Ma an Vertrautheit mit JavaScript haben und dass Sie gewhnliche Objekte verwendet haben.
Wenn Sie bereits praktische Erfahrung mit JavaScript haben oder dem Leitfaden gefolgt sind, haben Sie wahrscheinlich schon Klassen verwendet, auch wenn Sie noch keine erstellt haben. Zum Beispiel drfte dies Ihnen 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 eine Methode toLocaleDateString() auf der bigDay-Instanz aufgerufen, die einen String zurckgibt. Dann haben wir zwei Zahlen verglichen: eine, die von der getTime()-Methode zurckgegeben wurde, die andere direkt von der Date-Klasse selbst aufgerufen, als Date.now().
Date ist eine eingebaute Klasse von JavaScript. Aus diesem Beispiel knnen wir einige grundlegende Ideen davon ableiten, was Klassen tun:
new-Operator.Diese entsprechen den drei Hauptmerkmalen von Klassen:
Klassen werden normalerweise mit Klassendeklarationen erstellt.
class MyClass {
// class body...
}
Innerhalb eines Klassenblocks stehen Ihnen eine Reihe von 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 aus einer Umgebung vor ES6 stammen, sind Sie mglicherweise besser damit vertraut, Funktionen als Konstruktoren zu verwenden. Das oben genannte Muster wrde sich ungefhr mit Funktionskonstruktoren in Folgendes bersetzen:
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 Funktionen in Klassen, die keine triviale Entsprechung in Funktionskonstruktoren haben.
Nachdem eine Klasse deklariert wurde, knnen Sie Instanzen davon mit dem new-Operator erstellen.
const myInstance = new MyClass();
console.log(myInstance.myField); // 'foo'
myInstance.myMethod();
Typische Funktionskonstruktoren knnen sowohl mit new konstruiert als auch ohne new aufgerufen werden. Der Versuch, eine Klasse ohne new zu "rufen", fhrt jedoch zu einem Fehler.
const myInstance = MyClass(); // TypeError: Class constructor MyClass cannot be invoked without 'new'
Im Gegensatz zu Funktionsdeklarationen werden Klassendeklarationen nicht gehoistet (oder in einigen Interpretationen gehoistet, aber mit der Einschrnkung der temporalen Todeszone), was bedeutet, dass Sie eine Klasse nicht verwenden knnen, bevor sie deklariert ist.
new MyClass(); // ReferenceError: Cannot access 'MyClass' before initialization
class MyClass {}
Dieses Verhalten ist hnlich wie bei Variablen, die mit let und const deklariert sind.
hnlich wie Funktionen haben auch Klassendeklarationen ihre Ausdrucksgegenstcke.
const MyClass = class {
// Class body...
};
Klassenausdrcke knnen ebenfalls Namen haben. Der Name des Ausdrucks ist nur fr den Klassenkrper sichtbar.
const MyClass = class MyClassLongerName {
// Class body. Here MyClass and MyClassLongerName point to the same class.
};
new MyClassLongerName(); // ReferenceError: MyClassLongerName is not defined
Vielleicht die wichtigste Aufgabe einer Klasse ist es, als "Fabrik" fr Objekte zu fungieren. Zum Beispiel, wenn wir den Date-Konstruktor verwenden, erwarten wir, dass er ein neues Objekt gibt, das die Datumsinformationen darstellt, die wir bergeben haben die wir dann mit anderen Methoden, die die Instanz bietet, manipulieren knnen. In Klassen erfolgt die Instanzerstellung durch den constructor.
Als Beispiel wrden wir eine Klasse namens Color erstellen, die eine bestimmte Farbe reprsentiert. Benutzer erstellen Farben durch das bergeben eines RGB-Triplets.
class Color {
constructor(r, g, b) {
// Assign the RGB values as a property of `this`.
this.values = [r, g, b];
}
}
ffnen Sie die Entwicklerwerkzeuge 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 eine Ausgabe wie diese sehen:
Object { values: (3) [] }
values: Array(3) [ 255, 0, 0 ]
Sie haben erfolgreich eine Color-Instanz erstellt, und die Instanz hat eine values-Eigenschaft, die ein Array der RGB-Werte ist, die Sie bergeben haben. Das ist ziemlich gleichbedeutend mit dem Folgenden:
function createColor(r, g, b) {
return {
values: [r, g, b],
};
}
Die Syntax eines Konstruktors ist genau die gleiche wie eine normale Funktion was bedeutet, dass Sie andere Syntaxe wie Restparameter verwenden knnen:
class Color {
constructor(...values) {
this.values = values;
}
}
const red = new Color(255, 0, 0);
// Creates an instance with the same shape as above.
Jedes Mal, wenn Sie new aufrufen, 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 zeigt der Wert von this auf die neu erstellte Instanz. Sie knnen ihr Eigenschaften zuweisen oder bestehende Eigenschaften lesen (insbesondere Methoden die wir als nchstes behandeln werden).
Der this-Wert wird automatisch als Ergebnis von new zurckgegeben. Es wird empfohlen, keinen Wert aus dem Konstruktor zurckzugeben denn wenn Sie einen nicht-primitiven Wert zurckgeben, wird er zum Wert des new-Ausdrucks, und der Wert von this wird verworfen. (Sie knnen mehr darber, was new tut, in seiner Beschreibung lesen.)
class MyClass {
constructor() {
this.myField = "foo";
return {};
}
}
console.log(new MyClass().myField); // undefined
Wenn eine Klasse nur einen Konstruktor hat, unterscheidet sie sich nicht wesentlich von einer createX-Fabrikfunktion, die nur einfache Objekte erstellt. Der Vorteil von Klassen besteht jedoch darin, dass sie als "Vorlagen" verwendet werden knnen, die automatisch Methoden an Instanzen zuweisen.
Zum Beispiel knnen Sie fr Date-Instanzen eine Reihe von Methoden verwenden, um unterschiedliche Informationen aus einem einzigen Datumswert zu erhalten, wie das Jahr, den Monat, den Wochentag usw. Sie knnen diese Werte auch ber die setX-Gegenstcke wie setFullYear setzen.
Fr unsere eigene Color-Klasse 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 auch. Ein Problem besteht jedoch darin, dass bei jeder Erstellung einer Color-Instanz eine neue Funktion erstellt wird, auch wenn sie alle das gleiche tun!
console.log(new Color().getRed === new Color().getRed); // false
Im Gegensatz dazu wird eine Methode, wenn Sie sie verwenden, zwischen allen Instanzen geteilt. Eine Funktion kann zwischen allen Instanzen geteilt werden, aber ihr Verhalten kann sich ndern, wenn verschiedene Instanzen sie aufrufen, da der Wert von this unterschiedlich ist. Wenn Sie neugierig sind, wo diese Methode gespeichert wird sie ist auf dem Prototyp aller Instanzen definiert, oder Color.prototype, was im Abschnitt Vererbung und die Prototypen-Kette ausfhrlicher erklrt wird.
hnlich 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!
Sie fragen sich vielleicht: Warum sollten wir uns die Mhe machen, getRed- und setRed-Methoden zu verwenden, wenn wir direkt auf das values-Array auf 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
Es gibt eine Philosophie in der objektorientierten Programmierung namens "Kapselung". Das bedeutet, dass Sie nicht auf die zugrunde liegende Implementierung eines Objekts zugreifen sollten, sondern stattdessen gut abstrahierte Methoden verwenden sollten, um mit ihm zu interagieren. Zum Beispiel, wenn wir pltzlich entscheiden, Farben 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 des Benutzers, dass values den RGB-Wert bedeutet, bricht pltzlich zusammen und es kann dazu fhren, dass ihre Logik fehlerhaft ist. Wenn Sie also ein Implementierer einer Klasse sind, wrden Sie die interne Datenstruktur Ihrer Instanz von Ihrem Benutzer verbergen wollen, sowohl um die API sauber zu halten als auch um zu verhindern, dass der Code des Benutzers bricht, wenn Sie einige "harmlose Refaktorisierungen" vornehmen. In Klassen wird dies durch private Felder erreicht.
Ein privates Feld ist ein Bezeichner, der mit # (dem Rautezeichen) vorangestellt wird. Das Rautezeichen ist ein integraler Bestandteil des Namens des Feldes, was bedeutet, dass ein privates Feld niemals einen Namenskonflikt mit einem ffentlichen Feld oder einer Methode haben kann. Um auf ein privates Feld irgendwo in der Klasse zu verweisen, mssen Sie es im Klassenkrper deklarieren (Sie knnen kein privates Element im Handumdrehen erstellen). Abgesehen davon ist ein privates Feld so ziemlich ein normales Attribut.
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 ist ein frher Syntaxfehler. Die Sprache kann dies verhindern, da #privateField eine spezielle Syntax ist, so dass sie eine statische Analyse durchfhren und alle Verwendungen von privaten Feldern finden kann, bevor der Code berhaupt ausgewertet 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 private Elemente auerhalb der Klasse zugreifen. Dies ist eine nur fr DevTools vorkommende Lockerung der JavaScript-Syntax-Einschrnkung.
Private Felder in JavaScript sind hart privat: Wenn die Klasse keine Methoden implementiert, die diese privaten Felder entblen, gibt es absolut keinen Mechanismus, um sie von auerhalb der Klasse abzurufen. Das bedeutet, dass Sie sicher sind, jede Refaktorisierung an den privaten Feldern Ihrer Klasse vorzunehmen, solange das Verhalten der sichtbaren Methoden gleich bleibt.
Nachdem wir das values-Feld privat gemacht haben, knnen wir mehr Logik in den getRed und setRed-Methoden hinzufgen, anstatt sie zu einfachen Durchreichmethoden zu machen. Zum Beispiel knnen wir eine berprfung in setRed hinzufgen, um zu sehen, ob es sich um einen gltigen R-Wert handelt:
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 wir die values-Eigenschaft offen lassen, knnen unsere Benutzer diese berprfung leicht umgehen, indem sie direkt values[0] zuweisen und ungltige Farben erstellen. Aber mit einer gut gekapselten API knnen wir unseren Code robuster machen und Logikfehler in weiteren Code verhindern.
Eine Klassenmethode kann die privaten Felder anderer Instanzen lesen, solange sie 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. (Auch wenn eine andere Klasse ein identisch benanntes privates Feld #values hat, bezieht es sich nicht auf das gleiche Ding und kann hier nicht zugegriffen werden.) Der Zugriff auf ein nicht vorhandenes privates Element wirft einen Fehler anstelle von undefined wie normale Eigenschaften. Wenn Sie nicht wissen, ob ein privates Feld auf einem Objekt existiert und Sie darauf zugreifen mchten, ohne try/catch zu verwenden, um den Fehler zu behandeln, knnen Sie den in-Operator 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 das # eine spezielle Bezeichner-Syntax ist und Sie den Feldnamen nicht verwenden knnen, als ob er eine Zeichenfolge wre. "#values" in anotherColor wrde nach einer Eigenschaft namens "#values" suchen, anstelle eines privaten Feldes.
Es gibt einige Einschrnkungen bei der Verwendung von privaten Elementen: derselbe Name kann nicht zweimal in einer einzigen Klasse deklariert werden und sie knnen nicht gelscht werden. Beides fhrt zu frhen Syntaxfehlern.
class BadIdeas {
#firstName;
#firstName; // syntax error occurs here
#lastName;
constructor() {
delete this.#lastName; // also a syntax error
}
}
Methoden, Getter und Setter knnen ebenfalls privat sein. Sie sind ntzlich, wenn Sie etwas Komplexes haben, das intern von der Klasse erledigt werden muss, aber kein anderer Teil des Codes darf darauf zugreifen darf.
Stellen Sie sich zum Beispiel vor, Sie erstellen HTML-Custom-Elemente, die etwas recht Komplexes tun sollten, wenn sie geklickt/angeklickt/aktiviert werden. Auerdem sollten die recht komplexen Dinge, die geschehen, wenn das Element geklickt wird, auf diese Klasse beschrnkt sein, da kein anderer Teil des JavaScript jemals 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 ist praktisch jedes Feld und jede Methode fr die Klasse privat. Dies stellt somit eine Schnittstelle fr den Rest des Codes dar, die im Wesentlichen genau wie ein eingebautes HTML-Element ist. Kein anderer Teil des Programms hat die Mglichkeit, die internen Struktur von Counter zu beeinflussen.
color.getRed() und color.setRed() ermglichen es uns, den Rotwert einer Farbe zu lesen und zu schreiben. Wenn Sie aus Sprachen wie Java kommen, werden Sie mit diesem Muster sehr vertraut sein. Es ist jedoch in JavaScript immer noch etwas unergonomisch, Methoden zu verwenden, um einfach auf eine Eigenschaft zuzugreifen. Zugriffsfelder ermglichen es uns, mit etwas zu arbeiten, als wre es eine "tatschliche 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 ob das Objekt eine Eigenschaft namens red hat - aber in Wirklichkeit gibt es keine solche Eigenschaft auf der Instanz! Es gibt nur zwei Methoden, aber sie sind mit get und set versehen, die es erlauben, sie zu manipulieren, als wren sie Eigenschaften.
Wenn ein Feld nur einen Getter hat, aber keinen Setter, wird es effektiv 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 wirft die Zeile red.red = 0 einen Typfehler: "Kann Eigenschaft red von #<Color> nicht setzen, welche nur einen Getter hat". Im nicht-strengen Modus wird die Zuweisung stillschweigend ignoriert.
Private Felder haben auch ihre ffentlichen Gegenstcke, die es jeder Instanz ermglichen, eine Eigenschaft zu haben. Felder sind normalerweise unabhngig von den Parametern des Konstruktors konzipiert.
class MyClass {
luckyNumber = Math.random();
}
console.log(new MyClass().luckyNumber); // 0.5
console.log(new MyClass().luckyNumber); // 0.3
ffentliche Felder sind fast gleichbedeutend mit dem Zuweisen einer Eigenschaft zu this. Zum Beispiel kann das obige Beispiel auch in Folgendes umgewandelt werden:
class MyClass {
constructor() {
this.luckyNumber = Math.random();
}
}
Mit dem Date-Beispiel haben wir auch die Date.now()-Methode getroffen, die das aktuelle Datum zurckgibt. Diese Methode gehrt zu keiner bestimmten Instanz sie gehrt zur Klasse selbst. Sie wird jedoch auf der Date-Klasse anstelle als einer globalen DateNow() Funktion belassen, weil sie hauptschlich ntzlich ist, wenn es sich um Datumsinstanzen handelt.
Hinweis:
Das Prfixieren von Dienstprogrammmethoden mit dem, womit sie zu tun haben, wird als "Namensraumbildung" bezeichnet und gilt als eine gute Praxis. Zum Beispiel neben der alten, unprfixierten parseInt() Methode, fgte JavaScript spter zustzlich die prfixierte Number.parseInt() Methode hinzu, um anzuzeigen, dass es um Zahlen geht.
Statische Eigenschaften sind eine Gruppe von Klassenfunktionen, die auf der Klasse selbst definiert sind, anstatt auf individuellen Instanzen der Klasse. Diese Funktionen umfassen:
Alles hat auch private Gegenstcke. Zum Beispiel knnen wir fr unsere Color-Klasse eine statische Methode erstellen, die berprft, ob ein bestimmtes Triplet ein gltiger RGB-Wert ist:
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 sind sehr hnlich wie ihre Instanzgegenstcke, mit der Ausnahme, dass:
static versehen sind undconsole.log(new Color(0, 0, 0).isValid); // undefined
Es gibt auch ein spezielles Konstrukt, das als statischer Initialisierungsblock bezeichnet wird, bei dem es sich um einen Codeblock handelt, der ausgefhrt wird, wenn die Klasse das erste Mal geladen wird.
class MyClass {
static {
MyClass.myStaticProperty = "foo";
}
}
console.log(MyClass.myStaticProperty); // 'foo'
Statische Initialisierungsblcke sind fast gleichbedeutend damit, dass unmittelbar nach der Deklaration einer Klasse irgendein Code ausgefhrt wird. Der einzige Unterschied besteht darin, dass sie Zugang zu statischen privaten Elementen haben.
Ein Hauptmerkmal, das Klassen mit sich bringen (neben der ergonomischen Kapselung mit privaten Feldern), ist die Vererbung, was bedeutet, dass ein Objekt einen groen Teil des Verhaltens eines anderen Objekts "ausleihen" kann, whrend es bestimmte Teile mit seiner eigenen Logik berschreibt oder verbessert.
Nehmen wir an, unsere Color-Klasse muss nun Transparenz untersttzen. Wir knnten versucht sein, ein neues Feld hinzuzufgen, das seine Transparenz angibt:
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;
}
}
Dies bedeutet jedoch, dass jede Instanz - auch die berwiegende Mehrheit, die nicht transparent ist (mit einem Alphawert von 1) - den zustzlichen Alphawert haben muss, was nicht sehr elegant ist. Auerdem, wenn die Funktionen weiter wachsen, wird unsere Color-Klasse sehr aufgeblasen und schwer zu warten.
Stattdessen wrden wir in der objektorientierten Programmierung eine abgeleitete Klasse erstellen. Die abgeleitete Klasse hat Zugriff auf alle ffentlichen Eigenschaften der Elternklasse. In JavaScript werden abgeleitete Klassen mit einer extends-Klausel deklariert, die die Klasse angibt, von der sie abgeleitet wird.
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;
}
}
Es gibt ein paar Dinge, die sofort Aufmerksamkeit erregt haben. Zuallererst ist, dass wir im Konstruktor super(r, g, b) aufrufen. Es ist eine Sprachvorgabe, super() aufzurufen, bevor Sie auf this zugreifen. Der Aufruf von super() ruft den Konstruktor der Elternklasse auf, um this zu initialisieren hier ist es ungefhr gleichbedeutend mit this = new Color(r, g, b). Sie knnen vor super() Code haben, aber Sie drfen nicht auf this zugreifen, bevor super() aufgerufen wurde die Sprache verhindert, dass Sie auf das nicht initialisierte this zugreifen.
Nachdem die Elternklasse ihre Manahmen gegenber this abgeschlossen hat, kann die abgeleitete Klasse ihre eigenen Logikmanahmen einfhren. Hier haben wir ein privates Feld namens #alpha hinzugefgt und auch ein Paar Getter/Setter bereitgestellt, um mit ihnen zu interagieren.
Eine abgeleitete Klasse erbt alle Methoden ihrer Eltern. Betrachten Sie zum Beispiel den get red()-Accessor, den wir der Color hinzugefgt haben, im Abschnitt Zugriffsfelder - obwohl wir in ColorWithAlpha keine deklariert haben, knnen wir red dennoch aufrufen, da dieses Verhalten von der Elternklasse spezifiziert wird:
const color = new ColorWithAlpha(255, 0, 0, 0.5);
console.log(color.red); // 255
Abgeleitete Klassen knnen auch Methoden der Elternklasse berschreiben. Zum Beispiel erben alle Klassen implizit die Object-Klasse, die einige grundlegende Methoden wie toString() definiert. Die Standard-toString()-Methode ist jedoch notorisch nutzlos, da sie in den meisten Fllen [object Object] ausgibt:
console.log(red.toString()); // [object Object]
Stattdessen kann unsere Klasse sie berschreiben, um die RGB-Werte der Farbe auszugeben:
class Color {
#values;
//
toString() {
return this.#values.join(", ");
}
}
console.log(new Color(255, 0, 0).toString()); // '255, 0, 0'
Innerhalb abgeleiteter Klassen knnen Sie die Methoden der Elternklasse mit super aufrufen. Dies ermglicht es Ihnen, Methoden zu erweitern und Code-Duplizierung zu vermeiden.
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, erben auch die statischen Methoden voneinander, sodass Sie diese ebenfalls berschreiben oder erweitern knnen.
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 der Elternklasse dies ist ein weiterer wesentlicher Aspekt der "harten Privatsphre" von JavaScript-privaten Feldern. Private Felder sind auf den Klassenkrper selbst beschrnkt und gewhren keinem externen Code 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 Klasse erben. Dies verhindert Probleme bei mehrfacher Vererbung wie das Diamantproblem. Aufgrund der dynamischen Natur von JavaScript ist es jedoch immer noch mglich, den Effekt der mehrfachen Vererbung durch Klassenkomposition und Mixins zu erzielen.
Instanzen abgeleiteter Klassen sind auch Instanzen von der Basisklasse.
const color = new ColorWithAlpha(255, 0, 0, 0.5);
console.log(color instanceof Color); // true
console.log(color instanceof ColorWithAlpha); // true
Der Leitfaden war bisher pragmatisch: Wir konzentrieren uns darauf, wie Klassen verwendet werden knnen, aber eine Frage bleibt unbeantwortet: Warum wrde man eine Klasse verwenden? Die Antwort lautet: Es kommt darauf an.
Klassen fhren ein Paradigma ein, oder eine Mglichkeit, Ihren Code zu organisieren. Klassen sind die Grundlagen der objektorientierten Programmierung, die auf Konzepten wie Vererbung und Polymorphismus (insbesondere Subtyp-Polymorphismus) aufgebaut ist. Viele Menschen sind jedoch philosophisch gegen bestimmte OOP-Praktiken eingestellt und verwenden Klassen daher nicht.
Ein Beispiel: Eines der Merkmale, das Date-Objekte berchtigt macht, ist, dass sie nderbar 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
nderbarkeit und interner Zustand sind wichtige Aspekte der objektorientierten Programmierung, machen jedoch oft den Code schwer nachvollziehbar - da jeder scheinbar harmlose Vorgang unerwartete Nebeneffekte haben und das Verhalten in anderen Teilen des Programms ndern kann.
Um Code wiederzuverwenden, greifen wir hufig darauf zurck, Klassen zu erweitern, was groe Hierarchien von Vererbungsmustern schaffen kann.
[Ein typischer OOP-Vererbung_tree mit fnf Klassen und drei Ebenen]
Es ist jedoch oft schwierig, Vererbung sauber zu beschreiben, wenn eine Klasse nur von einer anderen Klasse erben kann. Oft mchten wir das Verhalten mehrerer Klassen. In Java wird dies durch Schnittstellen erreicht; in JavaScript kann dies durch Mixins erreicht werden. Am Ende ist es jedoch immer noch nicht sehr bequem.
Auf der positiven Seite sind Klassen eine sehr mchtige Mglichkeit, unseren Code auf einer hheren Ebene zu organisieren. Ohne die Color-Klasse knnten wir zum Beispiel ein Dutzend Utility-Funktionen erstellen mssen:
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
);
}
//
Aber mit Klassen knnen wir sie alle unter dem Color-Namensraum versammeln, was die Lesbarkeit verbessert. Darber hinaus ermglicht die Einfhrung von privaten Feldern, bestimmte Daten vor nachgelagerten Benutzern zu verbergen, was eine saubere API schafft.
Im Allgemeinen sollten Sie berlegen, Klassen zu verwenden, wenn Sie Objekte erstellen mchten, die ihre eigenen internen Daten speichern und viel Verhalten freigeben. Nehmen Sie als Beispiel eingebaute JavaScript-Klassen:
Map- und Set-Klassen speichern eine Sammlung von Elementen und ermglichen den Zugriff darauf ber get(), set(), has() usw.Date-Klasse speichert ein Datum als Unix-Zeitstempel (eine Zahl) und ermglicht es Ihnen, individuelle Datumsbestandteile zu formatieren, zu aktualisieren und zu lesen.Error-Klasse speichert Informationen ber eine bestimmte Ausnahme, einschlielich der Fehlermeldung, des Stack-Traces, der Ursache usw. Sie ist eine der wenigen Klassen, die ber eine reichhaltige Vererbungsstruktur verfgt: Es gibt mehrere eingebaute Klassen wie TypeError und ReferenceError, die Error erweitern. Im Falle von Fehlern ermglicht diese Vererbung die Verfeinerung der Semantik von Fehlern: Jede Fehlerklasse reprsentiert eine bestimmte Art von Fehler, die leicht mit instanceof berprft werden kann.JavaScript bietet die Mglichkeit, Ihren Code auf eine kanonische objektorientierte Weise zu organisieren, aber ob und wie man es verwendet, liegt ganz im Ermessen des Programmierers.
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 |