Klassen
Klassendefinition
Klassen sind eigentlich Funktionen. Analog zu Funktionsausdrücken und Funktionsdeklarationen hat die Klassensyntax zwei Komponenten:
- Klassenausdrücke und
- Klassendeklarationen.
Klassendeklaration
Eine Möglichkeit, Klassen zu definieren ist eine Klassendeklaration. Diese wird eingeleitet durch das Schlüsselwort class
, gefolgt vom Namen der Klasse (hier: "Rectangle").
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
Hoisting
Ein wichtiger Unterschied zwischen Klassen- und Funktionsdeklarationen besteht im sogenannten Hoisting. Funktionsdeklarationen werden an den Anfang des Definitionsbereichs "gehoben", für Klassen gilt dies nicht. Das heißt, um auf eine Klasse zuzugreifen, muss sie zuvor definiert worden sein, sonst führt dies zu einem ReferenceError
:
var p = new Polygon(); // ReferenceError
class Polygon {}
Klassenausdruck
Ein Klassenausdruck ist eine weitere Möglichkeit eine Klasse zu definieren. Dabei ist es optional, hinter dem Schlüsselwort class
einen Namen anzugeben. Sollte ein Name angegeben werden, so gilt dieser nur innerhalb des Klassenkörpers.
// unnamed
var Polygon = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
// named
var Polygon = class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
Klassenkörper und Methodendefinitionen
Der Körper der Klasse ist innerhalb der beiden geschweiften Klammern {}
. Hier werden die Eigenschaften der Klasse definiert, wie Konstruktoren oder Methoden.
"Strict mode"
Der Inhalt der Klassendeklaration und des Klassenausdrucks werden im "strikten Modus" ausgeführt.
Konstruktor
Die Konstruktor-Methode ist eine spezielle Methode, um Objekte zu erzeugen und zu initialisieren. Eine Klasse kann nur eine spezielle Methode mit dem Namen "constructor" haben. Sollte es in einer Klasse mehrere "constructor"-Methoden geben, wird ein SyntaxError
geworfen.
In der Konstruktor-Methode kann man mit dem Schlüsselwort "super", den Konstruktor der Elternklasse aufrufen.
Prototype Methoden
Siehe auch Methodendefinitionen.
class Polygon {
constructor(hoehe, breite) {
this.hoehe = hoehe;
this.breite = breite;
}
get flaeche() {
return this.berechneFlaeche();
}
berechneFlaeche() {
return this.hoehe * this.breite;
}
}
const quadrat = new Polygon(10, 10);
console.log(quadrat.flaeche);
Statische Methoden
Das Schlüsselwort static
definiert statische Methoden. Statische Methoden werden ohne Instanzierung einer Klasse aufgerufen und sind über eine erzeugte Instanz nicht aufrufbar. Oft werden in Applikationen statische Methoden für Hilfsfunktionen verwendet.
class Punkt {
constructor(x, y) {
this.x = x;
this.y = y;
}
static laenge(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx*dx + dy*dy);
}
}
const p1 = new Punkt(5, 5);
const p2 = new Punkt(10, 10);
console.log(Punkt.laenge(p1, p2));
Boxing with prototype and static methods
Wird eine statische oder eine prototype-Methode aufgerufen, ohne dass ein Objekt in der Variable "this" liegt (oder mit "this" als Wahrheitswert, Zeichenkette, Nummer, undefiniert oder null), dann wird die Variable "this" im Funktionskörper undefined sein. Autoboxing wird nicht passieren. Das Verhalten wird das gleiche sein, sollte der Code nicht im "strict mode" geschrieben worden sein.
class Tier {
sprich() {
return this;
}
static iss() {
return this;
}
}
let obj = new Tier();
let sprich = obj.sprich;
sprich(); // undefined
let iss = Tier.iss;
iss(); // undefined
Falls wir den vorherigen Quelltext mit klassischen funktionsbasierten Klassen schreiben, wird Autoboxing stattfinden. Dies wird auf dem Wert passieren, der für "this" an die Funktion übergeben wurde.
function Tier() { }
Tier.prototype.sprich = function() {
return this;
}
Tier.iss = function() {
return this;
}
let obj = new Tier();
let sprich = obj.sprich;
sprich(); // Globales Objekt
let iss = Tier.iss;
iss(); // Globales Objekt
Vererbung mittels extends
Das Schlüsselwort extends
wird dafür verwendet, Klassendeklarationen und Klassenausdrücke zu erzeugen, die von einer anderen Klasse ableiten.
class Tier{
constructor(name) {
this.name = name;
}
sprich() {
console.log(this.name + ' macht ein Geräusch.');
}
}
class Hund extends Tier{
sprich() {
console.log(this.name + ' bellt.');
}
}
var h = new Hund('Wolfi');
h.sprich();
Existiert in der Unterklasse ein Konstruktor, muss dieser zuerst super() aufrufen, bevor "this" verwendet werden kann.
Man kann auch traditionelle funktionsbasierte Klassen erweitern:
function Tier(name) {
this.name = name;
}
Tier.prototype.sprich = function () {
console.log(this.name + ' macht ein Geräusch.');
}
class Hund extends Tier {
sprich() {
super.sprich();
console.log(this.name + ' bellt.');
}
}
var h = new Hund('Wolfi');
h.sprich();
Klassen können nicht von regulären (nicht konstruierbaren) Objekten erben. Falls von einem regulärem Objekt geerbt werden soll, kann Object.setPrototypeOf()
(en-US) verwendet werden:
var Tier = {
sprich() {
console.log(this.name + ' macht ein Geräusch.');
}
};
class Hund {
constructor(name) {
this.name = name;
}
sprich() {
console.log(this.name + ' bellt.');
}
}
Object.setPrototypeOf(Hund.prototype, Tier);
var h = new Hund('Wolfi');
h.sprich();
Species
Falls man zum Beispiel in einer selbst erzeugten Klasse MyArray
den Konstruktor mit dem Konstruktor der Array
Klasse überschreiben will, kann man dies mittels des species Musters erreichen.
Zum Beispiel, wenn man die map()
Methode aufruft, wird der Default-Konstruktor der Klasse aufgerufen. Will man, dass stattdessen der Konstruktor der Elternklasse benutzt wird, kann man das Symbol.species
(en-US)-Symbol dafür verwenden:
class MyArray extends Array {
// Überschreibt species mit dem Konstruktor der Array-Klasses
static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
Elternklasse Methoden mit super
aufrufen
Das Schlüsselwort super
kann verwendet werden, um Methoden der Elternklassen aufzurufen
class Katze{
constructor(name) {
this.name = name;
}
sprich() {
console.log(this.name + ' macht ein Geräusch.');
}
}
class Loewe extends Katze {
sprich() {
super.sprich();
console.log(this.name + ' brüllt.');
}
}
Mix-ins
Abstrakte Subklassen oder mix-ins sind Vorlagen für Klassen. Eine ECMAScript-Klasse kann nur von einer einzigen Klasse ableiten, damit ist beispielsweise mehrfache Vererbung von Helferklassen nicht möglich. Die gewünschte Funktionalität muss von der Elternklasse bereitgestellt werden.
Eine Funktion die als Input eine Elternklasse nimmt und als Output eine davon abgeleitete Subklasse ausgibt, kann verwendet werden, um mix-ins in ECMAScript zu erzeugen:
var RechnerMixin = Base => class extends Base {
rechne() { }
};
var ZufallsGeneratorMixin = Base => class extends Base {
generiere() { }
};
Eine Klasse die ein solches mix-in verwendet kann so erzeugt werden:
class Foo { }
class Bar extends RechnerMixin(ZufallsGeneratorMixin(Foo)) { }
Spezifikationen
Spezifikation | Status | Kommentar |
---|---|---|
ECMAScript 2015 (6th Edition, ECMA-262) Die Definition von 'Class definitions' in dieser Spezifikation. |
Standard | Ursprüngliche Definition. |
ECMAScript (ECMA-262) Die Definition von 'Class definitions' in dieser Spezifikation. |
Lebender Standard |
Browserkompatibilität
Feature | Chrome | Firefox (Gecko) | Edge | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|---|
Basic support | 42.0[1] 49.0 |
45 (45) | 13 | Nicht unterstützt | Nicht unterstützt | 9.0 |
Funktion | Android | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile | Chrome für Android |
---|---|---|---|---|---|---|
Basis Unterstützung | Nicht unterstützt | 45.0 (45) | ? | ? | 9 | 42.0[1] 49.0 |
[1] Benötigt den strikten Modus. Der Support im nicht-strikte Modus verbirgt sich hinter der Flag "Experimentelle JavaScript-Funktionen", welche standardmäßig deaktiviert ist.