Klassen in JavaScript

0 Aktien
0
0
0
0

Einführung

Klassen dienen als Vorlage für die Erstellung von Objekten. Sie kapseln Daten mit Code zur Verarbeitung dieser Daten. Klassen in JavaScript basieren auf Prototypen, verfügen aber auch über eine für Klassen einzigartige Syntax und Semantik.

Klassen sind im Wesentlichen “spezielle Funktionen”, und genau wie man Funktionsausdrücke und Funktionsdeklarationen definieren kann, kann eine Klasse auf zwei Arten definiert werden: als Klassenausdruck oder als Klassendeklaration.

// Declaration
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
// Expression; the class is anonymous but assigned to a variable
const Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
// Expression; the class has its own name
const Rectangle = class Rectangle2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};

Wie Funktionsdeklarationen können auch Klassendeklarationen anonym sein oder einen anderen Namen als die Variable haben, der sie zugewiesen werden. Im Gegensatz zu Funktionsdeklarationen unterliegen Klassendeklarationen jedoch denselben zeitlichen Beschränkungen wie `let` oder `const` und verhalten sich so, als wären diese nicht aufgehoben.

Klassenkörper

Der Klassenrumpf ist der Teil, der von geschweiften Klammern {} umschlossen wird. Hier werden die Klassenmember, wie z. B. Methoden oder Konstruktoren, definiert.

Der Code einer Klasse wird im strikten Modus ausgeführt, auch ohne die Direktive "use strict".

Ein Klassenelement kann durch drei Aspekte charakterisiert werden:

  • Typ: Empfänger, Regler, Methode oder Feld
  • Standort: Statisch oder Probe
  • Sichtbarkeit: Öffentlich oder Privat

Konstruktor

Eine Konstruktormethode ist eine spezielle Methode zum Erstellen und Initialisieren eines Objekts einer Klasse. Jede Klasse darf nur eine Methode namens “Konstruktor” enthalten. Enthält die Klasse mehrere Instanzen einer Konstruktormethode, wird ein Syntaxfehler ausgelöst.

Ein Konstruktor kann das Schlüsselwort super verwenden, um den Konstruktor der Oberklasse aufzurufen.

Sie können Instanzeigenschaften innerhalb des Konstruktors erstellen:

class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}

Alternativ können Sie, falls die Werte Ihrer Instanzeigenschaften nicht von den Konstruktorargumenten abhängen, diese als Klassenfelder definieren.

Statische Initialisierungsblöcke

Statische Blöcke ermöglichen die flexible Initialisierung statischer Eigenschaften, einschließlich der Auswertung von Ausdrücken während der Initialisierung, und erlauben gleichzeitig den Zugriff auf den privaten Gültigkeitsbereich.

Es können mehrere statische Blöcke deklariert werden, und diese Blöcke können mit Deklarationen von statischen Feldern und Methoden durchsetzt werden (alle statischen Elemente werden in der Reihenfolge ihrer Deklaration ausgewertet).

Methoden

Methoden werden im Prototyp jeder Klasseninstanz definiert und von allen Instanzen gemeinsam genutzt. Methoden können einfache Funktionen, asynchrone Funktionen, Generatorfunktionen oder asynchrone Generatorfunktionen sein. Weitere Informationen finden Sie unter Methodendefinitionen.

class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea() {
return this.height * this.width;
}
*getSides() {
yield this.height;
yield this.width;
yield this.height;
yield this.width;
}
}
const square = new Rectangle(10, 10);
console.log(square.area); // 100
console.log([...square.getSides()]); // [10, 10, 10, 10]

Statische Methoden und Felder

Das Schlüsselwort `static` definiert eine statische Methode oder ein statisches Feld einer Klasse. Statische Eigenschaften (Felder und Methoden) werden in der Klasse selbst definiert, nicht in jeder einzelnen Instanz. Statische Methoden werden häufig verwendet, um Hilfsfunktionen für eine Anwendung zu erstellen, während statische Felder für Caching, feste Konfigurationen oder andere Daten nützlich sind, die nicht in allen Instanzen wiederholt werden müssen.

class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static displayName = "Point";
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.displayName; // undefined
p1.distance; // undefined
p2.displayName; // undefined
p2.distance; // undefined
console.log(Point.displayName); // "Point"
console.log(Point.distance(p1, p2)); // 7.0710678118654755

Felddurchsagen

Mit der Syntax zur Deklaration von Klassenfeldern kann das Konstruktorbeispiel wie folgt geschrieben werden:

class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}

Klassenfelder sind wie Objekteigenschaften, keine Variablen, daher verwenden wir keine Schlüsselwörter wie `const`, um sie zu deklarieren. In JavaScript verwenden private Eigenschaften eine spezielle Bezeichnersyntax, weshalb auch Modifikatoren wie `public` und `private` nicht verwendet werden sollten.

Wie oben dargestellt, können Felder mit oder ohne Standardwerte deklariert werden. Felder ohne Standardwerte sind standardmäßig nicht definiert. Durch die vorherige Deklaration von Feldern werden Klassendefinitionen besser dokumentiert und die Felder sind stets vorhanden, was die Optimierung erleichtert.

Weitere Informationen finden Sie unter Öffentliche Klassenfelder.

Privatgrundstücke

Mithilfe privater Felder kann die Definition wie folgt modifiziert werden.

class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}

Es ist ein Fehler, von außerhalb der Klasse auf private Felder zuzugreifen. Diese können nur innerhalb des Klassenrumpfs gelesen und beschrieben werden. Indem Sie Dinge definieren, die außerhalb der Klasse nicht sichtbar sind, stellen Sie sicher, dass Benutzer Ihrer Klasse nicht von internen Details abhängig sein können, die sich von Version zu Version ändern können.

Private Felder können nur im Voraus in einer Felddeklaration festgelegt werden. Sie können nicht nachträglich durch Zuweisung erstellt werden, wie es bei regulären Eigenschaften möglich ist.

Weitere Informationen finden Sie unter Privatgrundstück.

Nachlass

Das Schlüsselwort extends wird in Klassendeklarationen oder Klassenausdrücken verwendet, um eine Klasse als Kind eines anderen Konstruktors (Klasse oder Funktion) zu erstellen.

class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // call the super class constructor and pass in the name parameter
}
speak() {
console.log(`${this.name} barks.`);
}
}
const d = new Dog("Mitzie");
d.speak(); // Mitzie barks.

Falls die Unterklasse einen Konstruktor besitzt, muss dieser vor seiner Verwendung die Methode `super()` aufrufen. Das Schlüsselwort `super` kann auch verwendet werden, um Methoden der Oberklasse aufzurufen.

class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(`${this.name} roars.`);
}
}
const l = new Lion("Fuzzy");
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.

Bewertungsreihenfolge

Bei der Auswertung einer Klassendeklaration oder eines Klassenausdrucks werden die verschiedenen Komponenten in der folgenden Reihenfolge ausgewertet:

  1. Die erweiterte Klausel wird, falls vorhanden, zuerst ausgewertet. Sie muss zu einer gültigen Konstruktorfunktion oder zu null ausgewertet werden, andernfalls wird ein TypeError ausgelöst.
  2. Die Konstruktormethode wird extrahiert, und falls kein Konstruktor vorhanden ist, wird dieser durch die Standardimplementierung ersetzt. Da die Konstruktordefinition jedoch lediglich eine Methodendefinition ist, ist dieser Schritt nicht sichtbar.
  3. Die Eigenschaftsschlüssel von Klassenelementen werden in der Reihenfolge ihrer Deklaration ausgewertet. Wird ein Eigenschaftsschlüssel ausgewertet, wird der mit diesem Wert ausgewertete Ausdruck auf den Wert des die Klasse umschließenden Objekts (nicht der Klasse selbst) gesetzt. Bisher wurden noch keine Eigenschaftswerte ausgewertet.
  4. Methoden und Zugriffsmethoden werden in der Reihenfolge ihrer Deklaration installiert. Instanzmethoden und -zugriffsmethoden werden der Prototyp-Eigenschaft der aktuellen Klasse zugewiesen, statische Methoden und Zugriffsmethoden hingegen der Klasse selbst. Private Instanzmethoden und -zugriffsmethoden werden reserviert und später direkt der Instanz zugewiesen. Dieser Schritt ist nicht sichtbar.
  5. Die Klasse wird nun mit dem durch Erweiterungen spezifizierten Prototyp und der durch den Konstruktor spezifizierten Implementierung initialisiert. Bei allen oben genannten Schritten wird ein ReferenceError ausgelöst, wenn ein ausgewerteter Ausdruck versucht, auf den Klassennamen zuzugreifen, da die Klasse noch nicht initialisiert wurde.
  6. Die Werte der Klassenelemente werden in der Reihenfolge ihrer Deklaration ausgewertet:
  • Für jedes Instanzfeld (öffentlich oder privat) wird sein Initialisierungsausdruck gespeichert. Der Initialisierer wird bei der Erstellung der Instanz, zu Beginn des Konstruktors (bei Basisklassen) oder unmittelbar vor der Rückgabe des super()-Aufrufs (bei abgeleiteten Klassen) ausgewertet.
  • Für jedes konstante Feld (öffentlich oder privat) wird seine Initialisierung mit diesem Satz auf die Klasse selbst ausgewertet und die Eigenschaft wird in der Klasse erstellt.
  • Statische Initialisierungsblöcke werden mit diesem Set in der Klasse selbst ausgewertet.

Beispiele

Dies wird mit Instanz- und statischen Methoden verknüpft.

Wird eine statische oder Instanzmethode ohne Wert aufgerufen, beispielsweise durch Zuweisung des Methodenwerts an eine Variable, ist der Wert innerhalb der Methode undefiniert. Dieses Verhalten ist auch ohne die Direktive “using strict” identisch, da Code innerhalb des Klassenrumpfs immer im strikten Modus ausgeführt wird.

class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
const obj = new Animal();
obj.speak(); // the Animal object
const speak = obj.speak;
speak(); // undefined
Animal.eat(); // class Animal
const eat = Animal.eat;
eat(); // undefined

Wenn wir den obigen Code im nicht-strikten Fall mit der traditionellen funktionsbasierten Syntax umschreiben, werden Aufrufe dieser Methode automatisch auf `globalThis` beschränkt. Im strikten Fall bleibt der Wert von `this` undefiniert.

function Animal() {}
Animal.prototype.speak = function () {
return this;
};
Animal.eat = function () {
return this;
};
const obj = new Animal();
const speak = obj.speak;
speak(); // global object (in non–strict mode)
const eat = Animal.eat;
eat(); // global object (in non-strict mode)
Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Das könnte Ihnen auch gefallen