TypeScript: Microsofts neues typisiertes JavaScript

Seite 2: Klassen & Module

Inhaltsverzeichnis

Das statische Typsystem von TypeScript bildet die Basis für die zweite Erweiterung, die TypeScript gegenüber JavaScript vornimmt: Die Sprache enthält als Alternative zur Prototypenvererbung von Objekten eine "richtige", auf Klassen basierende Objektorientierung. Eine Klasse wird auf ähnliche Weise definiert wie ein interface. Der wesentliche Unterschied ist, dass sich von ihr neue Instanzen erzeugen lassen, weshalb man den Konstruktor in dem Fall nicht mit dem Schlüsselwort new, sondern mit constructor kennzeichnet:

class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
var point = new Point(23, 5);

Als Kurzform kann der Entwickler die Eigenschaften der Klasse direkt im Konstruktor definieren, was einige Zeilen einspart. Zum einen kann nämlich die eigentliche Definition der Eigenschaften entfallen, zum anderen auch die Zuweisungen innerhalb des Konstruktors:

class Point {
constructor(public x: number, public y: number) {
}
}

Neben dem Zugriffsmodifizierer public kennt TypeScript auch die beiden Schlüsselwörter private und static, mit denen man nicht öffentliche beziehungsweise klassenbezogene Elemente definiert.

TypeScript unterstützt jedoch nicht nur einfache Klassen, sondern auch Vererbung. Hierzu dient das Schlüsselwort extends, das bei der Definition einer Klasse zum Einsatz kommen kann, um deren Basisklasse anzugeben:

class Point3D extends Point {
// [...]
}

Intern wandelt TypeScript vererbte Klassen in Konstruktorfunktionen um, deren Prototypen die Sprache korrekt verkettet: Es wird also das für natives JavaScript übliche Vorgehen gewählt. Um aus einer abgeleiteten Klasse Zugriff auf die Basisklasse zu erhalten, dient das Schlüsselwort super. Kommt es innerhalb eines Konstruktors zum Einsatz, wird der Konstruktor der Basisklasse aufgerufen; kommt es hingegen innerhalb einer normalen Funktion zum Einsatz, stellt es eine Referenz auf die Basisklasse dar.

Daher kann der Entwickler mit super Funktionen in abgeleiteten Klassen überschreiben und von dort dennoch die ursprüngliche Implementierung aufrufen.

Einen häufigen Stolperstein von JavaScript stellt das Schlüsselwort this dar, das je nach Aufrufart einer Funktion auf ein anderes Objekt verweist. Soll es fixiert werden, um beispielsweise innerhalb eines Callbacks Zugriff auf den ursprüngliche Kontext zu haben, wird üblicherweise eine zweite Referenz verwendet, die dann innerhalb des Callbacks zur Verfügung steht:

var that = this;

TypeScript kennt als Abhilfe hierfür Funktionen, die den ursprünglichen Kontext von this erhalten. Man definiert sie mit dem Operator =>, in TypeScript als "Fat Arrow Operator" bezeichnet. Wird beispielsweise ein Objekt obj definiert als

var obj = {
baz: 'baz',

foo: function () {
this.bar(() => {
console.log)(this.baz);
});
},

bar: function (callback: () => void) {
callback();
}
};

lässt sich die Funktion foo wie folgt aufrufen:

obj.foo();

Als Text wird – wie gewünscht – die Zeichenkette baz ausgegeben. Das bedeutet, dass der Callback im Kontext des ursprünglichen Objekts läuft und this somit erhalten geblieben ist. Intern verfährt TypeScript dabei so, wie man es in JavaScript von Hand machen müsste: Der Wert von this wird in eine eigene Variable gespeichert, die anschließend innerhalb des Callbacks den Zugriff auf den ursprünglichen Kontext ermöglicht. Intern wird diese Variable mit dem Bezeichner _this versehen.

Als dritte Erweiterung zu JavaScript enthält TypeScript die Möglichkeit, Module zu definieren, zu exportieren und an anderer Stelle wieder zu importieren. Dazu dienen die Schlüsselwörter module, export und import. Um beispielsweise einen Namensraum mit einer Klasse zu exportieren, genügen die folgenden Zeilen:

module Foo {
export class Bar {
// ...
}
}

Wird der Namensraum nun mit der import-Anweisung an anderer Stelle wieder eingefügt und in einer Variablen für den weiteren Zugriff hinterlegt, lässt sich auf die Klasse Bar entsprechend zugreifen:

import test = module(Foo);
var bar = new test.Bar();

Nach dem Entfernen des Schlüsselworts export bei der Klassendefinition ist diese Klasse nicht mehr nach außen sichtbar und lässt sich dementsprechend nicht mehr instanziieren.

Module können sich in TypeScript auch in anderen als der aktuellen Datei befinden, sodass Entwickler eine Anwendung auf einfache Weise sauber strukturieren können. An Stelle des Modulnamens ist dann der Dateiname in Anführungszeichen anzugeben, wobei man ihm zumindest den Pfad des aktuellen Verzeichnisses voranstellen muss:

import test = module('./Foo');

Zusätzlich wird beim Modul noch deklariert, wozu die Anweisung declare dient:

declare module './Foo' {
class Bar {
};
}

Die Deklaration wird üblicherweise in einer gesonderten Datei abgelegt, die die Dateiendung .d.ts trägt, sie kann aber auch inline erfolgen. Für JavaScript-Frameworks und Bibliotheken wie Node.js und jQuery bringt TypeScript die Deklarationsdateien von Haus aus mit, sodass sich deren Module ohne weiteren Aufwand importieren und verwenden lassen. Beispielsweise genügt die folgende Zeile, um das http-Modul von Node.js zu laden:

import http = module('http');

Unter der Haube wandelt TypeScript den Aufruf in einen der Node.js-Funktion require um.