Mit ES6 (ECMAScript 2015) wurde die Klassensyntax in JavaScript eingeführt. Diese Syntax vereinfacht die Implementierung objektorientierter Konzepte erheblich, ohne jedoch die grundlegende prototypenbasierte Natur von JavaScript zu verändern. Klassen in JavaScript sind im Wesentlichen “syntaktischer Zucker” über dem bestehenden Prototypen-Mechanismus, den wir im vorherigen Kapitel betrachtet haben.
Eine Klasse in JavaScript wird mit dem Schlüsselwort
class definiert und kann einen Konstruktor, Methoden und
Getter/Setter enthalten:
class Fahrzeug {
constructor(marke, modell, baujahr) {
this.marke = marke;
this.modell = modell;
this.baujahr = baujahr;
this._kilometerstand = 0;
}
// Methode
fahren(kilometer) {
this._kilometerstand += kilometer;
return `${this.marke} ${this.modell} fährt ${kilometer} km`;
}
// Getter
get kilometerstand() {
return this._kilometerstand;
}
// Setter
set kilometerstand(wert) {
if (wert >= 0) {
this._kilometerstand = wert;
} else {
throw new Error('Kilometerstand kann nicht negativ sein');
}
}
// Statische Methode
static istFahrzeug(obj) {
return obj instanceof Fahrzeug;
}
}
// Instanziierung
const meinAuto = new Fahrzeug('VW', 'Golf', 2020);
console.log(meinAuto.fahren(100)); // "VW Golf fährt 100 km"
console.log(meinAuto.kilometerstand); // 100
// Statische Methode aufrufen
console.log(Fahrzeug.istFahrzeug(meinAuto)); // trueDie obige Klassendefinition erzeugt im Wesentlichen dasselbe Ergebnis wie die folgende Verwendung des Prototypen-Mechanismus:
function Fahrzeug(marke, modell, baujahr) {
this.marke = marke;
this.modell = modell;
this.baujahr = baujahr;
this._kilometerstand = 0;
}
Fahrzeug.prototype.fahren = function(kilometer) {
this._kilometerstand += kilometer;
return `${this.marke} ${this.modell} fährt ${kilometer} km`;
};
Object.defineProperty(Fahrzeug.prototype, 'kilometerstand', {
get: function() {
return this._kilometerstand;
},
set: function(wert) {
if (wert >= 0) {
this._kilometerstand = wert;
} else {
throw new Error('Kilometerstand kann nicht negativ sein');
}
}
});
Fahrzeug.istFahrzeug = function(obj) {
return obj instanceof Fahrzeug;
};Eine der größten Stärken der ES6-Klassensyntax ist die vereinfachte
Vererbung mit dem Schlüsselwort extends:
class Auto extends Fahrzeug {
constructor(marke, modell, baujahr, türen) {
// Ruft den Konstruktor der Elternklasse auf
super(marke, modell, baujahr);
this.türen = türen;
this.typ = 'Auto';
}
// Überschreibt die Methode der Elternklasse
fahren(kilometer) {
const nachricht = super.fahren(kilometer); // Elternmethode aufrufen
return `${nachricht} als ${this.typ}`;
}
// Zusätzliche Methode
parken() {
return `${this.marke} ${this.modell} parkt`;
}
}
const meinAuto = new Auto('BMW', '3er', 2021, 4);
console.log(meinAuto.fahren(200)); // "BMW 3er fährt 200 km als Auto"
console.log(meinAuto.parken()); // "BMW 3er parkt"
console.log(meinAuto.kilometerstand); // 200 (geerbt)super hat in JavaScript-Klassen zwei wichtige
Verwendungen:
super()) im Konstruktor: Ruft den
Konstruktor der Elternklasse aufsuper.methode()) in Methoden:
Greift auf Methoden der Elternklasse zuclass ElektroAuto extends Auto {
constructor(marke, modell, baujahr, türen, batteriekapazität) {
super(marke, modell, baujahr, türen); // Muss vor this aufgerufen werden!
this.batteriekapazität = batteriekapazität;
this.typ = 'Elektroauto';
}
laden() {
return `${this.marke} ${this.modell} lädt seine ${this.batteriekapazität}kWh-Batterie`;
}
}
const teslaModel3 = new ElektroAuto('Tesla', 'Model 3', 2022, 4, 75);
console.log(teslaModel3.laden()); // "Tesla Model 3 lädt seine 75kWh-Batterie"
console.log(teslaModel3.fahren(150)); // "Tesla Model 3 fährt 150 km als Elektroauto"Klassen sind keine Hoisting-Objekte: Im Gegensatz zu Funktionen werden Klassen nicht “gehoisted”, d.h. sie müssen definiert werden, bevor sie verwendet werden können.
Klassenrumpf wird im strict mode ausgeführt: Der Code innerhalb einer Klassendeklaration unterliegt automatisch dem “strict mode”.
Klassenmethoden sind nicht aufzählbar: Methoden,
die mit der Klassensyntax erstellt wurden, haben das Flag
enumerable: false.
Konstruktor-Aufruf ohne new: Konstruktoren von
Klassen müssen mit dem new-Operator aufgerufen
werden.
// Fehler: Klasse vor Definition verwenden
try {
const k = new KlasseNochNichtDefiniert();
} catch (e) {
console.log("Fehler: " + e.message);
}
class KlasseNochNichtDefiniert {}
// Fehler: Konstruktor ohne new
try {
Fahrzeug("Mercedes", "C-Klasse", 2019);
} catch (e) {
console.log("Fehler: " + e.message); // TypeError: Class constructor Fahrzeug cannot be invoked without 'new'
}Ab ECMAScript 2022 können private Klassenelemente mit dem Präfix
# definiert werden:
class BankKonto {
#kontostand = 0; // Privates Feld
#pin; // Privates Feld ohne Initialisierung
constructor(kontoinhaber, startguthaben, pin) {
this.kontoinhaber = kontoinhaber; // Öffentliches Feld
this.#kontostand = startguthaben;
this.#pin = pin;
}
// Private Methode
#prüfePin(pin) {
return pin === this.#pin;
}
abheben(betrag, pin) {
if (!this.#prüfePin(pin)) {
throw new Error('Falsche PIN');
}
if (betrag > this.#kontostand) {
throw new Error('Nicht genügend Guthaben');
}
this.#kontostand -= betrag;
return `${betrag}€ abgehoben. Neuer Kontostand: ${this.#kontostand}€`;
}
einzahlen(betrag) {
this.#kontostand += betrag;
return `${betrag}€ eingezahlt. Neuer Kontostand: ${this.#kontostand}€`;
}
get kontostand() {
return `Aktueller Kontostand: ${this.#kontostand}€`;
}
}
const meinKonto = new BankKonto('Max Mustermann', 1000, 1234);
console.log(meinKonto.einzahlen(500)); // "500€ eingezahlt. Neuer Kontostand: 1500€"
console.log(meinKonto.kontostand); // "Aktueller Kontostand: 1500€"
try {
console.log(meinKonto.#kontostand); // SyntaxError: Private field '#kontostand' cannot be accessed
} catch (e) {
console.log("Zugriffsfehler auf privates Feld");
}Private Felder:
_property)Ab ECMAScript 2022 können statische Initialisierungsblöcke verwendet werden, um komplexe statische Initialisierungen durchzuführen:
class Konfiguration {
static defaultConfig;
static validSettings;
static {
// Komplexe Initialisierung für statische Eigenschaften
this.defaultConfig = {
darkMode: true,
fontSize: 16,
language: 'de'
};
this.validSettings = new Set(['darkMode', 'fontSize', 'language']);
// Weitere Initialisierungslogik...
console.log('Statische Initialisierung abgeschlossen');
}
static isValidSetting(key) {
return this.validSettings.has(key);
}
}
console.log(Konfiguration.defaultConfig.darkMode); // true
console.log(Konfiguration.isValidSetting('fontSize')); // trueÄhnlich wie bei Funktionsausdrücken können Klassen auch als Ausdrücke definiert werden:
// Anonymer Klassenausdruck
const Person = class {
constructor(name) {
this.name = name;
}
grüßen() {
return `Hallo, ich bin ${this.name}`;
}
};
// Benannter Klassenausdruck
const Mitarbeiter = class MitarbeiterKlasse {
constructor(name, abteilung) {
this.name = name;
this.abteilung = abteilung;
}
vorstellen() {
return `${this.name} aus der Abteilung ${this.abteilung}`;
}
// Der Name MitarbeiterKlasse ist nur innerhalb der Klasse sichtbar
getClassName() {
return MitarbeiterKlasse.name;
}
};
const max = new Person('Max');
console.log(max.grüßen()); // "Hallo, ich bin Max"
const anna = new Mitarbeiter('Anna', 'IT');
console.log(anna.vorstellen()); // "Anna aus der Abteilung IT"
console.log(anna.getClassName()); // "MitarbeiterKlasse"Getter und Setter erlauben es, Eigenschaftszugriffe zu kontrollieren:
class Temperatur {
constructor(celsius) {
this._celsius = celsius;
}
get celsius() {
return this._celsius;
}
set celsius(wert) {
if (wert < -273.15) {
throw new Error('Temperatur kann nicht unter dem absoluten Nullpunkt liegen');
}
this._celsius = wert;
}
get fahrenheit() {
return this._celsius * 9/5 + 32;
}
set fahrenheit(wert) {
this.celsius = (wert - 32) * 5/9;
}
get kelvin() {
return this._celsius + 273.15;
}
set kelvin(wert) {
this.celsius = wert - 273.15;
}
}
const temp = new Temperatur(25);
console.log(temp.fahrenheit); // 77
temp.fahrenheit = 86;
console.log(temp.celsius); // 30
console.log(temp.kelvin); // 303.15Die Vererbung in JavaScript ermöglicht das Erstellen komplexer Hierarchien und polymorphisches Verhalten:
// Basisklasse
class Shape {
constructor(color) {
this.color = color;
}
draw() {
return `Zeichne eine Form in ${this.color}`;
}
area() {
throw new Error('Muss in Unterklasse implementiert werden');
}
}
// Unterklassen
class Circle extends Shape {
constructor(color, radius) {
super(color);
this.radius = radius;
}
draw() {
return `${super.draw()} als Kreis mit Radius ${this.radius}`;
}
area() {
return Math.PI * this.radius * this.radius;
}
}
class Rectangle extends Shape {
constructor(color, width, height) {
super(color);
this.width = width;
this.height = height;
}
draw() {
return `${super.draw()} als Rechteck ${this.width}x${this.height}`;
}
area() {
return this.width * this.height;
}
}
// Polymorphismus demonstrieren
function renderShape(shape) {
console.log(shape.draw());
console.log(`Fläche: ${shape.area().toFixed(2)}`);
}
const circle = new Circle('rot', 5);
const rectangle = new Rectangle('blau', 4, 6);
renderShape(circle); // "Zeichne eine Form in rot als Kreis mit Radius 5"
// "Fläche: 78.54"
renderShape(rectangle); // "Zeichne eine Form in blau als Rechteck 4x6"
// "Fläche: 24.00"JavaScript unterstützt keine Mehrfachvererbung, aber mit Mixins können Methoden von mehreren Quellen kombiniert werden:
// Mixin-Funktion
const SwimmableMixin = (superclass) => class extends superclass {
swim() {
return `${this.name || 'Objekt'} schwimmt`;
}
dive() {
return `${this.name || 'Objekt'} taucht`;
}
};
const FlyableMixin = (superclass) => class extends superclass {
fly() {
return `${this.name || 'Objekt'} fliegt`;
}
land() {
return `${this.name || 'Objekt'} landet`;
}
};
// Basisklasse
class Animal {
constructor(name) {
this.name = name;
}
eat() {
return `${this.name} frisst`;
}
}
// Klassen mit Mixins
class Duck extends SwimmableMixin(FlyableMixin(Animal)) {
quack() {
return `${this.name} quakt`;
}
}
class Fish extends SwimmableMixin(Animal) {
// Keine weiteren Methoden nötig
}
const donald = new Duck('Donald');
console.log(donald.eat()); // "Donald frisst"
console.log(donald.swim()); // "Donald schwimmt"
console.log(donald.fly()); // "Donald fliegt"
console.log(donald.quack()); // "Donald quakt"
const nemo = new Fish('Nemo');
console.log(nemo.eat()); // "Nemo frisst"
console.log(nemo.swim()); // "Nemo schwimmt"
try {
console.log(nemo.fly()); // TypeError: nemo.fly is not a function
} catch (e) {
console.log("Fische können nicht fliegen");
}