Menu

TypeScript Klassen: Felder, Methoden, Getter und implements

TypeScript-Klassen sind JavaScript-Klassen mit typisierten Feldern, Methoden und Konstruktoren. Wie Felddeklarationen und strictPropertyInitialization funktionieren, wie du this typisierst, Getter und Setter, statische Member, implements und wie eine Klasse zugleich ein Typ ist.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Eine TypeScript-Klasse ist eine JavaScript-Klasse mit Typen: Jedes Feld wird mit seinem Typ im Klassenrumpf deklariert, und Methoden und Konstruktor bekommen typisierte Parameter und Rückgabewerte.

Die kompilierte Ausgabe ist eine gewöhnliche JavaScript-Klasse ohne die Typen, daher gilt alles aus JavaScript-Klassen weiterhin. Was TypeScript hinzufügt, passiert beim Kompilieren: Der letzte Aufruf wird abgelehnt, bevor das Programm läuft.

Felder deklarieren

Ein Feld braucht eine Deklaration im Klassenrumpf, bevor der Konstruktor es zuweisen kann. this.owner zuzuweisen, ohne owner zu deklarieren, ist der Fehler TS2339 (Eigenschaft existiert nicht). Ein Feld mit Initialisierer bekommt seinen Typ aus dem Wert abgeleitet, genau wie eine Variable.

DeklarationBedeutung
name: string;Muss im Konstruktor zugewiesen werden
count = 0;Initialisiert, Typ als number abgeleitet
label?: string;Optional, Typ ist string | undefined
readonly id: number;Einmal zugewiesen, danach nur lesbar
data!: string[];Definite Assignment: Vertrau mir, es wird gesetzt
static total = 0;Gehört zur Klasse, nicht zu den Instanzen

Mit aktivem strict prüft strictPropertyInitialization, dass jedes nicht optionale Feld einen Wert bekommt. Diese Klasse vergisst zwei:

Der Compiler meldet beide:

index.ts(3,5): error TS2564: Property 'name' has no initializer and is not definitely assigned in the constructor.
index.ts(4,5): error TS2564: Property 'age' has no initializer and is not definitely assigned in the constructor.

Die Prüfung gibt es, weil sonst new Profile().name.toUpperCase() die Typprüfung bestehen und dann an undefined abstürzen würde. Behebe jedes Feld auf eine von vier Arten: Gib ihm einen Initialisierer, weise es im Konstruktor zu, markiere es mit ? als optional oder schreibe name!: string, wenn ein Framework oder eine Methode init() es später setzt. Die letzte Variante schaltet die Prüfung für dieses Feld ab, setze sie also sparsam ein.

Methoden und this

Methoden werden wie Funktionen typisiert. In einer Methode hat this den Typ der Instanz. Eine Methode kann this zurückgeben, um Verkettung zu ermöglichen, und der Rückgabetyp folgt Unterklassen automatisch.

class QueryBuilder {
    private parts: string[] = [];

    where(condition: string): this {
        this.parts.push(condition);
        return this;
    }

    build(): string {
        return this.parts.join(" AND ");
    }
}

const sql = new QueryBuilder().where("age > 18").where("active = 1").build();
console.log(sql); // age > 18 AND active = 1

Eine Methode verliert ihr this, wenn du sie vom Objekt abziehst, ein klassischer JavaScript-Bug. TypeScript findet das standardmäßig nicht, wohl aber, wenn die Methode einen this-Parameter deklariert. Dieser Parameter wird aus der Ausgabe entfernt und schützt nur die Aufrufstelle:

Das Arrow-Feld funktioniert, weil jede Instanz ihre eigene Funktion bekommt, die this einschließt. Der Preis ist eine Funktion pro Instanz statt einer gemeinsamen Methode auf dem Prototyp.

Getter und Setter

Accessoren mit get und set sehen von außen wie Eigenschaften aus. Ein Getter ohne Setter ist nur lesbar: Ihm etwas zuzuweisen ist ein Compilerfehler (TS2540).

Der Compiler lehnt die Zuweisung ab, und wird die Prüfung umgangen, wirft auch die Laufzeit einen TypeError, weil die kompilierte Datei im Strict Mode läuft (der Compiler setzt "use strict" an den Anfang). Der RangeError im Setter ist eine Prüfung zur Laufzeit, die du selbst geschrieben hast. Der Typ number sagt nur, dass der Wert eine Zahl ist; über Wertebereiche weiß er nichts.

Statische Member

static-Felder und Methoden gehören zur Klasse selbst. Du erreichst sie über den Klassennamen, nicht über eine Instanz. Ein Block static { } läuft einmal, wenn die Klasse definiert wird.

Statische Member können die Typparameter der Klasse nicht verwenden: In class Box<T> ist ein static empty: T der Fehler TS2302, weil es nur ein statisches Feld gibt, das sich jedes Box<string>, Box<number> und so weiter teilt.

Ein Interface implementieren

implements bittet den Compiler zu prüfen, dass die Klasse alles hat, was ein Interface verlangt. Ein fehlender Member ist der Fehler TS2420: Class 'X' incorrectly implements interface 'Y'.

Eine Klasse kann mehrere Interfaces implementieren: class Doc implements Printable, Serializable. Zwei Dinge tut implements nicht. Es typisiert die Methodenparameter der Klasse nicht für dich: check(s) { ... } in einer Klasse, die check(s: string): boolean implementiert, ist weiterhin der Fehler TS7006 (Parameter 's' implicitly has an 'any' type), annotiere sie also. Und es hinterlässt zur Laufzeit keine Spur, obj instanceof Shape ist also nicht möglich.

Das private radius im Konstruktor ist eine Parameter Property: Sie deklariert und weist das Feld in einem Schritt zu. Siehe Konstruktoren.

Eine Klasse ist auch ein Typ

Eine Klassendeklaration erzeugt zwei Dinge mit einem Namen: einen Wert (die Konstruktorfunktion, die du mit new aufrufst) und einen Typ (die Form einer Instanz). Weil TypeScript Typen nach Struktur vergleicht, passt jedes Objekt mit denselben öffentlichen Membern zum Typ, auch eines, das nicht von der Klasse erzeugt wurde.

Das einfache Objekt besteht die Typprüfung, ist zur Laufzeit aber kein Point: ({ x: 6, y: 8 }) instanceof Point ist false. Eine Klasse mit einem Member private oder #private verhindert das: Nur Instanzen dieser Klasse (oder ihrer Unterklassen) sind ihr zuweisbar.

Häufige Fehler

  • Ein Feld deklarieren und nie zuweisen. TS2564 sagt dir, dass das Feld undefined wäre. Initialisiere es, statt den Fehler mit ! zum Schweigen zu bringen.
  • Eine Methode als Callback übergeben. button.onclick = obj.handle verliert this. Nimm ein Arrow-Feld oder obj.handle.bind(obj).
  • Erwarten, dass implements Code oder Typen hinzufügt. Es prüft nur. Parametertypen brauchen weiterhin Annotationen.
  • instanceof mit einem Interface verwenden. Interfaces existieren zur Laufzeit nicht; prüfe eine Klasse oder nimm einen Type Guard.
  • Annehmen, dass Typen Daten validieren. Ein Feld vom Typ number akzeptiert beim Kompilieren jede Zahl und zur Laufzeit aus untypisierten Daten überhaupt alles.

Häufig gestellte Fragen

Wie erstellt man eine Klasse in TypeScript?

Deklariere jedes Feld mit seinem Typ im Klassenrumpf und schreibe dann Konstruktor und Methoden wie in JavaScript: class User { name: string; constructor(name: string) { this.name = name; } greet(): string { return "Hi, " + this.name; } }. Eine Instanz erzeugst du mit new User("Ada").

Was bedeutet "has no initializer and is not definitely assigned in the constructor"?

Das ist der Fehler TS2564 aus strictPropertyInitialization (Teil von strict). Ein als string typisiertes Feld würde mit undefined beginnen, weil nichts es setzt. Behebe das mit einem Initialisierer (name = ""), einer Zuweisung im Konstruktor, einem optionalen Feld (name?: string) oder, wenn etwas außerhalb des Konstruktors es setzt, einer Definite Assignment Assertion (name!: string).

Was ist der Unterschied zwischen implements und extends in TypeScript?

extends erbt Code von einer Elternklasse: Ihre Felder und Methoden existieren zur Laufzeit auf dem Kind. implements bittet den Compiler nur zu prüfen, dass die Klasse die Form eines Interfaces hat. Es fügt der Klasse nichts hinzu und verschwindet aus dem kompilierten JavaScript.

Kann eine Klasse in TypeScript als Typ verwendet werden?

Ja. Ein Klassenname ist zugleich ein Wert (der Konstruktor) und ein Typ (die Form seiner Instanzen). let u: User akzeptiert jedes Objekt mit denselben öffentlichen Membern, weil TypeScript Typen strukturell vergleicht. typeof User ist der Typ des Konstruktors selbst.

Unterscheiden sich TypeScript-Klassen zur Laufzeit von JavaScript-Klassen?

Nein. TypeScript kompiliert eine Klasse zu einer normalen JavaScript-Klasse. Typannotationen, implements-Klauseln und Modifikatoren wie private werden entfernt; nur JavaScript-Features wie #private-Felder, static-Blöcke und Getter existieren zur Laufzeit.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S