JavaScript bietet die Möglichkeiten für objektorientierte Programmierung (OOP). In der OOP möchtest du Objekte (Instanzen) aus „Vorlagen" (Klassen) erzeugen, damit sie bestimmte Daten und Funktionalität enthalten. Die Daten-Eigenschaften heißen im OOP-Kontext Felder, die Funktions-Eigenschaften heißen Methoden.
JavaScript hatte überhaupt keine Klassen, bevor sie 2015 zur Sprachspezifikation hinzugefügt wurden, ermöglichte aber objektorientierte Programmierung mit prototypbasierter Vererbung.
Und obwohl heutzutage ein class-Schlüsselwort verfügbar ist, ist JavaScript immer noch eine prototypbasierte Sprache.
Um zu verstehen, was es bedeutet, eine prototypbasierte Sprache zu sein, und wie JavaScript tatsächlich funktioniert, gehen wir zurück in die Zeit, in der es noch keine Klassen gab.
In JavaScript wird die Vorlage (Klasse) durch eine gewöhnliche Funktion umgesetzt.
Wenn eine Funktion als solche Vorlage verwendet werden soll, nennt man sie Konstruktorfunktion, und die Konvention ist, dass der Funktionsname mit einem Großbuchstaben beginnen sollte.
Instanzen (Objekte) werden von der Vorlage abgeleitet, indem du die Konstruktorfunktion mit dem new-Schlüsselwort aufrufst.
function Car() {
// ...
}
const myCar = new Car();
const yourCar = new Car();
Es ist wichtig zu wissen, dass in JavaScript die Instanzen und die Konstruktorfunktion auch nach dem Erzeugen der Instanzen in Beziehung zueinander bleiben.
Jedes Instanzobjekt enthält eine versteckte, interne Eigenschaft, die in der Sprachspezifikation [[prototype]] genannt wird.
Sie enthält eine Referenz auf den Wert des prototype-Schlüssels der Konstruktorfunktion.
Ja, du hast richtig gelesen: Eine JavaScript-Funktion kann Schlüssel-Wert-Paare haben, weil sie hinter den Kulissen auch ein Objekt ist.
Zusammengefasst:
prototype-Eigenschaft des Konstruktors (der Funktion) wird zum Prototyp der Instanz.Häufig möchtest du, dass alle abgeleiteten Objekte (Instanzen) einige Felder enthalten, und beim Erzeugen des Objekts Anfangswerte für diese übergeben.
Das lässt sich über das this-Schlüsselwort umsetzen.
Innerhalb der Konstruktorfunktion repräsentiert this das neue Objekt, das mit new erzeugt wird.
this wird automatisch von der Konstruktorfunktion zurückgegeben, wenn sie mit new aufgerufen wird.
Das bedeutet, wir können der neuen Instanz Felder hinzufügen, indem wir sie in der Konstruktorfunktion zu this hinzufügen.
function Car(color, weight) {
this.color = color;
this.weight = weight;
this.engineRunning = false;
}
const myCar = new Car('red', '2mt');
myCar.color;
// => 'red'
myCar.engineRunning;
// => false
Methoden werden über die prototype-Eigenschaft der Konstruktorfunktion hinzugefügt.
Innerhalb einer Methode kannst du über this auf die Felder der Instanz zugreifen.
Das funktioniert wegen der folgenden allgemeinen Regel.
Wenn eine Funktion als Methode eines Objekts aufgerufen wird, wird ihr
thisauf das Objekt gesetzt, für das die Methode aufgerufen wird. 1
function Car() {
this.engineRunning = false;
// ...
}
Car.prototype.startEngine = function () {
this.engineRunning = true;
};
Car.prototype.addGas = function (litre) {
// ...
};
const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true
myCar im obigen Beispiel ist ein normales JavaScript-Objekt. Wenn wir es untersuchen würden (z. B. in der Browser-Konsole), würden wir keine Eigenschaft startEngine mit einer Funktion als Wert direkt im myCar-Objekt finden.
Wie funktioniert der obige Code dann überhaupt?
Das Geheimnis hier heißt Prototypkette.
Wenn du auf eine beliebige Eigenschaft (Feld oder Methode) eines Objekts zugreifen willst, prüft JavaScript zuerst, ob der jeweilige Schlüssel direkt im Objekt selbst zu finden ist.
Wenn nicht, sucht es weiter nach dem Schlüssel in dem Objekt, auf das die [[prototype]]-Eigenschaft des ursprünglichen Objekts verweist.
Wie bereits erwähnt, verweist [[prototype]] in unserem Beispiel auf die prototype-Eigenschaft der Konstruktorfunktion.
Dort würde JavaScript die Funktion startEngine finden, weil wir sie dort hinzugefügt haben.
function Car() {
// ...
}
Car.prototype.startEngine = function () {
// ...
};
Und die Kette endet dort nicht.
Die [[prototype]]-Eigenschaft von Car.prototype (myCar.[[prototype]].[[prototype]]) verweist auf Object.prototype (die prototype-Eigenschaft der Konstruktorfunktion Object).
Sie enthält allgemeine Methoden, die für alle JavaScript-Objekte verfügbar sind, z. B. toString().
Zusammenfassend kannst du myCar.toString() aufrufen, und diese Methode wird existieren, weil JavaScript die gesamte Prototypkette nach ihr durchsucht.
Beachte, dass die Prototypkette nur beim Abrufen eines Werts durchlaufen wird. Eine Eigenschaft direkt zu setzen oder eine Eigenschaft eines Instanzobjekts zu löschen, betrifft nur diese spezifische Instanz. Das entspricht vielleicht nicht deinen Erwartungen, wenn du an eine Sprache mit klassenbasierter Vererbung gewöhnt bist.
Heutzutage unterstützt JavaScript das Definieren von Klassen mit einem class-Schlüsselwort.
Das wurde 2015 zur Sprachspezifikation hinzugefügt.
Einerseits bietet das syntaktischen Zucker, der Klassen leichter lesbar und schreibbar macht.
Die neue Syntax ähnelt mehr der Art und Weise, wie Klassen in Sprachen wie C++ oder Java geschrieben werden.
Entwickler, die von diesen Sprachen umsteigen, tun sich mit der Umstellung leichter.
Andererseits ebnet die Klassensyntax den Weg für neue Sprachfunktionen, die in der Prototyp-Syntax nicht verfügbar sind.
Mit der neuen Syntax werden Klassen mit dem class-Schlüsselwort definiert, gefolgt vom Namen der Klasse und dem Klassenkörper in geschweiften Klammern.
Der Körper enthält die Definition der Konstruktorfunktion, d. h. eine spezielle Methode mit dem Namen constructor.
Diese Funktion funktioniert genauso wie die Konstruktorfunktion in der Prototyp-Syntax.
Der Klassenkörper enthält außerdem alle Methodendefinitionen.
Die Syntax für die Methoden ähnelt der Kurzschreibweise, die wir zum Hinzufügen von Funktionen als Werte innerhalb eines Objekts kennengelernt haben, siehe Konzept Objekte.
class Car {
constructor(color, weight) {
this.color = color;
this.weight = weight;
this.engineRunning = false;
}
startEngine() {
this.engineRunning = true;
}
addGas(litre) {
// ...
}
}
const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true
Denk daran, dass JavaScript hinter den Kulissen immer noch eine prototypbasierte Sprache ist. Alle Mechanismen, die wir im obigen Abschnitt „Prototyp-Syntax" kennengelernt haben, gelten weiterhin.
Standardmäßig sind alle Instanzfelder in JavaScript öffentlich. Auf sie kann direkt zugegriffen und ihnen kann direkt ein Wert zugewiesen werden.
Es gibt jedoch eine etablierte Konvention, dass Felder und Methoden, die mit einem Unterstrich beginnen, als privat behandelt werden sollten. Auf sie sollte niemals direkt von außerhalb der Klasse zugegriffen werden.
Private Felder werden manchmal von Gettern und Settern begleitet.
Mit den Schlüsselwörtern get und set kannst du Funktionen definieren, die ausgeführt werden, wenn auf eine Eigenschaft mit demselben Namen wie die Funktion zugegriffen oder ihr ein Wert zugewiesen wird.
class Car {
constructor() {
this._mileage = 0;
}
get mileage() {
return this._mileage;
}
set mileage(value) {
throw new Error(`Mileage cannot be manipulated, ${value} is ignored.`);
// Just an example, usually you would not provide a setter in this case.
}
}
const myCar = new Car();
myCar.mileage;
// => 0
myCar.mileage = 100;
// => Error: Mileage cannot be manipulated, 100 is ignored.
this-Beispiele – Als Objektmethode, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#as_an_object_method (abgerufen am 3. Dezember 2021) ↩
In dieser Übung simulierst du ein fensterbasiertes Computersystem. Du erstellst einige Fenster, die verschoben und in ihrer Größe geändert werden können. Das folgende Bild veranschaulicht die Werte, mit denen du unten arbeitest.
<--------------------- screenSize.width --------------------->
^ ┌────────────────────────────────────────────────────────────┐
| │ │
| │ position.x, _ │
| │ position.y \ │
| │ \<----- size.width -----> │
| │ ^ *──────────────────────┐ │
| │ | │ title │ │
| │ | ├──────────────────────┤ │
screenSize.height │ | │ │ │
| │ size.height │ │ │
| │ | │ contents │ │
| │ | │ │ │
| │ | │ │ │
| │ v └──────────────────────┘ │
| │ │
| │ │
v └────────────────────────────────────────────────────────────┘
📣 Um deine vielfältigen JavaScript-Kenntnisse zu üben, versuche, die Aufgaben 1 und 2 mit Prototype-Syntax und die restlichen Aufgaben mit Klassen-Syntax zu lösen.
Definiere eine Klasse (Konstruktorfunktion) namens Size.
Sie soll zwei Felder width und height haben, die die aktuellen Abmessungen des Fensters speichern.
Die Konstruktorfunktion soll Anfangswerte für diese Felder entgegennehmen.
Die Breite wird als erster Parameter übergeben, die Höhe als zweiter.
Die Standardbreite und -höhe sollen 80 bzw. 60 sein.
Definiere außerdem eine Methode resize(newWidth, newHeight), die eine neue Breite und Höhe als Parameter entgegennimmt und die Felder an die neue Größe anpasst.
const size = new Size(1080, 764);
size.width;
// => 1080
size.height;
// => 764
size.resize(1920, 1080);
size.width;
// => 1920
size.height;
// => 1080
Definiere eine Klasse (Konstruktorfunktion) namens Position mit zwei Feldern, x und y, die die aktuelle horizontale bzw. vertikale Position der linken oberen Ecke des Fensters speichern.
Die Konstruktorfunktion soll Anfangswerte für diese Felder entgegennehmen.
Der Wert für x wird als erster Parameter übergeben, der Wert für y als zweiter.
Der Standardwert soll für beide Felder 0 sein.
Die Position (0, 0) ist die linke obere Ecke des Bildschirms, wobei die x-Werte größer werden, wenn du dich nach rechts bewegst, und die y-Werte größer werden, wenn du dich nach unten bewegst.
Definiere außerdem eine Methode move(newX, newY), die neue x- und y-Parameter entgegennimmt und die Eigenschaften an die neue Position anpasst.
const point = new Position();
point.x;
// => 0
point.y;
// => 0
point.move(100, 200);
point.x;
// => 100
point.y;
// => 200
Definiere eine Klasse ProgramWindow mit den folgenden Feldern:
screenSize: enthält einen festen Wert vom Typ Size mit width 800 und height 600size : enthält einen Wert vom Typ Size; der Anfangswert ist der Standardwert der Size-Instanzposition : enthält einen Wert vom Typ Position; der Anfangswert ist der Standardwert der Position-InstanzWenn das Fenster geöffnet (erstellt) wird, hat es am Anfang immer die Standardgröße und -position.
const programWindow = new ProgramWindow();
programWindow.screenSize.width;
// => 800
// Similar for the other fields.
Randnotiz: Der Name ProgramWindow wird anstelle von Window verwendet, um die Klasse von der eingebauten Window-Klasse abzugrenzen, die es in Browser-Umgebungen gibt.
Die Klasse ProgramWindow soll eine Methode resize enthalten.
Sie soll einen Parameter vom Typ Size entgegennehmen und versuchen, die Größe des Fensters auf die angegebene Größe zu ändern.
Die neue Größe darf jedoch bestimmte Grenzen nicht überschreiten.
x = 400, y = 300 liegt und eine Größenänderung auf height = 400, width = 300 angefordert wird, dann würde das Fenster auf height = 300, width = 300 geändert, da der Bildschirm in y-Richtung nicht groß genug ist, um die Anforderung vollständig aufzunehmen.const programWindow = new ProgramWindow();
const newSize = new Size(600, 400);
programWindow.resize(newSize);
programWindow.size.width;
// => 600
programWindow.size.height;
// => 400
Neben der Größenänderung soll die Klasse ProgramWindow auch eine Methode move enthalten.
Sie soll einen Parameter vom Typ Position entgegennehmen.
Die Methode move ähnelt resize, allerdings passt diese Methode die Position des Fensters an den angeforderten Wert an, nicht die Größe.
Wie bei resize darf die neue Position bestimmte Grenzen nicht überschreiten.
x und y.x = 250, y = 100 liegt und eine Verschiebung auf x = 600, y = 200 angefordert wird, dann würde das Fenster auf x = 550, y = 200 verschoben, da der Bildschirm in x-Richtung nicht groß genug ist, um die Anforderung vollständig aufzunehmen.const programWindow = new ProgramWindow();
const newPosition = new Position(50, 100);
programWindow.move(newPosition);
programWindow.position.x;
// => 50
programWindow.position.y;
// => 100
Implementiere eine Funktion changeWindow, die eine ProgramWindow-Instanz entgegennimmt und das Fenster auf die angegebene Größe und Position ändert.
Die Funktion soll die übergebene ProgramWindow-Instanz zurückgeben, nachdem die Änderungen angewendet wurden.
Das Fenster soll eine Breite von 400 und eine Höhe von 300 erhalten und bei x = 100, y = 150 positioniert sein.
const programWindow = new ProgramWindow();
changeWindow(programWindow);
programWindow.size.width;
// => 400
// Similar for the other fields.
Melde dich bei Exercism an, um JavaScript mit 37 Konzepte159 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.