Jetzt tauchen wir tief in den Maschinenraum von JavaScript ein. Bevor es die moderne class-Syntax gab, musste man die Vererbung noch manuell „verdrahten“. Um zu verstehen, was JavaScript heute im Hintergrund automatisch für uns regelt, werfen wir einen Blick auf die Werkzeuge von damals: die Methoden new Object(), Object.create(), call() und apply() sowie die Eigenschaft constructor.
Wenn man das Prinzip der Prototypen-Kette im Hinterkopf hat, ergeben diese Werkzeuge plötzlich alle Sinn. Sie teilen sich auf in zwei Aufgaben: Objekt-Erzeugung und Konstruktor-Vererbung.
Beide Methoden erstellen Objekte, aber sie tun das auf eine völlig andere Art und Weise, wenn es um den Prototypen geht.
Das ist der absolute Standard. Wenn du new Object() aufrufst (was exakt dasselbe ist wie das literale {}), erstellst du ein leeres, frisches Objekt.
const user = new Object();// oder const user = {};
user.name = "Anna";
Was passiert mit dem Prototyp? Dieses Objekt bekommt automatisch den Standard-Ur-Prototyp von JavaScript (Object.prototype). Es erbt die Standard-Methoden wie .toString().
Diese Methode ist ein mächtiges Werkzeug. Sie erstellt ebenfalls ein neues Objekt, aber du darfst explizit bestimmen, welches Objekt als Prototyp (__proto__) dienen soll.
const tierKopf = {
augen: 2,
blinzeln: function() { console.log("Blinzelt..."); }
};
// Erstellt ein LEERES Objekt, dessen Prototyp direkt 'tierKopf' ist
const hund = Object.create(tierKopf);
console.log(hund.augen); // 2 (geerbt!)
hund.blinzeln(); // "Blinzelt..." (geerbt!)
Wenn du dir hund im Speicher anschaust, ist das Objekt komplett leer ({}). Es besitzt die Eigenschaft augen nicht selbst. Erst wenn du darauf zugreifst, wandert JavaScript die Prototypen-Kette hoch zu tierKopf.
Der Spezialeffekt: Mit Object.create(null) kannst du ein Objekt erschaffen, das absolut gar keinen Prototyp hat – es besitzt dann nicht einmal Standardfunktionen wie .toString().
Wenn wir eine "Vererbung" zwischen zwei Konstruktorfunktionen bauen wollen (z. B. ein Magier soll alles erben, was ein allgemeiner Spielfigur-Konstruktor hat), müssen wir den Eltern-Konstruktor im Kind-Konstruktor aufrufen.
Dafür nutzen wir call() oder apply(). Beide tun fast dasselbe: Sie führen eine Funktion aus, erlauben es dir aber, den Wert von this manuell zu bestimmen.
call(thisArg, arg1, arg2, ...) nimmt die Argumente einzeln.
apply(thisArg, [arg1, arg2, ...]) nimmt die Argumente als Array.
// 1. Der Eltern-Konstruktor
function Spielfigur(name, leben) {
this.name = name;
this.leben = leben;
}
// 2. Der Kind-Konstruktor
function Magier(name, leben, mana) {
// Wir rufen den Eltern-Konstruktor auf, zwingen ihn aber, SEINE
// Eigenschaften ('name', 'leben') an UNSER 'this' (den neuen Magier) zu hängen.
Spielfigur.call(this, name, leben);
// Danach fügen wir die magierspezifische Eigenschaft hinzu
this.mana = mana;
}
const gandalf = new Magier("Gandalf", 100, 150);
console.log(gandalf.name); // "Gandalf"
Hätten wir hier nur Spielfigur(name, leben) ohne .call(this...) aufgerufen, wäre this innerhalb der Spielfigur im globalen Kontext verpufft. Durch .call(this) sagen wir: "Hey Eltern-Konstruktor, nimm mein neues Magier-Objekt und statte es mit deinen Eigenschaften aus!"
Jetzt kommt der Knackpunkt. Wir haben oben zwar die Eigenschaften (name, leben) vererbt, aber der Magier erbt noch keine Methoden, die auf Spielfigur.prototype liegen!
Dafür müssen wir Object.create() und die Eigenschaft constructor kombinieren.
// Eine Methode auf dem Eltern-Prototyp
Spielfigur.prototype.schadenNehmen = function(menge) {
this.leben -= menge;
};
// JETZT VERBINDEN WIR DIE PROTOTYPEN:
// Magier.prototype soll ein neues Objekt sein, das von Spielfigur.prototype erbt.
Magier.prototype = Object.create(Spielfigur.prototype);
Das Problem dabei: Weil wir Magier.prototype komplett mit einem neuen Objekt überschrieben haben, hat JavaScript die eingebaute Eigenschaft .constructor verloren. Wenn man jetzt gandalf.constructor abfragt, würde JavaScript die Kette hochwandern und behaupten, Gandalf sei eine Spielfigur, kein Magier.
Deshalb müssen wir den Konstruktor nach dem Überschreiben manuell reparieren:
// Den Konstruktor-Zeiger wieder auf die richtige Funktion biegen Magier.prototype.constructor = Magier;
So sah sauberer, professioneller Vererbungscode in JavaScript vor dem Jahr 2015 aus. Hier kommt alles zusammen:
// 1. Basis-Konstruktor
function Spielfigur(name, leben) {
this.name = name;
this.leben = leben;
}
Spielfigur.prototype.heilen = function() {
this.leben += 10;
};
// 2. Sub-Konstruktor
function Magier(name, leben, mana) {
Spielfigur.call(this, name, leben); // <-- Eigenschaften erben via call()
this.mana = mana; }
// 3. Prototypen-Kette verknüpfen
Magier.prototype = Object.create(Spielfigur.prototype); // <-- Methoden-Vererbung via Object.create()
Magier.prototype.constructor = Magier; // <-- Constructor reparieren!
// 4. Eigene Methode für den Magier hinzufügen
Magier.prototype.zaubern = function() {
this.mana -= 10;
console.log(`${this.name} wirkt einen Zauber!`);
};
// Test
const gandalf = new Magier("Gandalf", 100, 50);
gandalf.heilen(); // Funktioniert! (Geerbt von Spielfigur)
gandalf.zaubern(); // Funktioniert! (Eigene Methode des Magiers)
Wenn du in modernem JavaScript das hier schreibst:
// 1. Basis-Klasse (Die Spielfigur)
class Spielfigur {
constructor(name, leben) {
this.name = name;
this.leben = leben;
}
// Diese Methode landet automatisch auf Spielfigur.prototype
heilen() {
this.leben += 10;
}
}
// 2. Sub-Klasse (Der Magier) erbt von Spielfigur
class Magier extends Spielfigur {
constructor(name, leben, mana) {
// Ruft den Eltern-Konstruktor auf (entspricht Spielfigur.call(this, name, leben))
super(name, leben);
this.mana = mana;
}
// Diese Methode landet automatisch auf Magier.prototype!
zaubern() {
this.mana -= 10;
console.log(`${this.name} wirkt einen Zauber!`);
}
}
// Test – funktioniert exakt wie der alte Code
const gandalf = new Magier("Gandalf", 100, 50);
gandalf.heilen(); // Geerbt von Spielfigur
gandalf.zaubern(); // Eigene Methode aus der Magier-Klasse
Dann passiert unter der Haube exakt das lange Skript von oben inklusive des Object.create() und dem Geradebiegen des constructors. Wenn man diesen alten Weg versteht, verliert das Wort super() in modernen Klassen sofort seine Magie – es ist einfach nur ein Wrapper für .call(this).