Skip to content

Pseudoklassische Vererbung in JavaScript – Teil 2

Posted on:

Note: This post is from my old blog and thus written in German and potentially obsolete.

Nach dem ersten Teil der kleinen Reihe zum Thema pseudoklassische Vererbung in JavaScript, enthält dieser Teil die Muster 2 und 3 aus Stoyan Stefanovs Buch “JavaScript Patterns”.

2. Einen Konstruktor leihen

Das letztes Mal hier vorgestellte Standardmuster hatte zum einen den Nachteil, dass sowohl Eigenschaften von this als auch Eigenschaften des Prototyps an Kind-Objekte vererbt werden. Dies wird durch das zweite Muster behoben. Dabei wird der Eltern-Konstruktor “geliehen” und diesem das Kind-Objekt sowie alle beim Aufruf des Kind-Konstruktors verwendeten Argumente übergeben. “Leihen” weißt darauf hin, dass der Eltern-Konstruktor nicht direkt mit new sondern mit Hilfe von apply aufgerufen wird:

function Child(a, b, c, d) {
  Parent.apply(this, arguments);
}

Hier gibt es also keine direkte Verbindung zwischen Parent und Child, da für die Vererbung nicht mehr Child.prototype zur Anwendung kommt. Daher vererbt man nur Eigenschaften welche this im Eltern-Konstruktor hinzugefügt wurden. Außerdem erhalten die Kind-Objekte Kopien der vererbten Eigenschaften. Beim Standardmuster hingegen bekommen sie lediglich Referenzen, weshalb es Kindern möglich ist Eltern-Eigenschaften zu überschreiben.

Hier das vom Standardmuster bekannte Beispiel mit angepasstem Eltern-Konstruktor:

// Eltern-Konstruktor
function Parent(name) {
  this.name = name || "Adam";
}

// Funktionalität wird dem Prototyp hinzugefügt (aber nicht nicht mehr vererbt)
Parent.prototype.say = function () {
  return this.name;
};

// Kind-Konstruktor
function Child(name) {
  Parent.apply(this, arguments);
}

var kid = new Child("Patrick");
kid.name; // "Patrick"
typeof kid.say; // "undefined"

Mehrfachvererbung mit Hilfe von geliehenen Konstruktoren

Durch Leihen von mehrere Konstruktoren kann man eine Mehrfachvererbung erreichen. Dies sollte in der Praxis jedoch vermieden werden, da es zu Problemen führen kann.

function Cat() {
  this.legs = 4;
  this.say = function () {
    return "meaowww";
  };
}

function Bird() {
  this.wings = 2;
  this.fly = true;
}

function CatWings() {
  Cat.apply(this);
  Bird.apply(this);
}

var jane = new CatWings();

console.log(jane.legs); // 4
console.log(jane.wings); // 2

Vor- und Nachteile des geliehenen Konstruktors

Ein Vorteil dieses Musters ist, dass man echte Kopien der Eigenschaften des Eltern-Konstrukors bekommt anstatt nur Referenzen. Ein unbeabsichtigtes Überschreiben ist somit nicht mehr möglich.

Der Nachteil ist offensichtlich, dass keine Vererbung der Eigenschaften die dem Prototyp hinzugefügt werden mehr stattfindet. Wiederverwendebare Eigenschaften sollen jedoch dem Prototyp hinzugefügt und auch vererbt werden. Das folgende Muster behebt diesen Nachteil daher.

3. Einen Konstruktor leihen und den Protoyp festlegen

Dieses Muster kombiniert einfach die zwei vorhergehenden. Dies geschieht in dem zusätzlich zur Verwendung des geliehenen Konstuktors dem Protoyp von Child eine neue Instanz von Parent zugewiesen wird:

function Child(a, b, c, d) {
  Parent.apply(this, arguments);
}

Child.prototype = new Parent();

Das Kind kann nun auch Aurgumente an den Eltern-Konstruktor übergeben. Allerdings wir dieser nun zweimal aufgerufen, wodurch seine Eigenschaften sozusagen doppelt vererbt werden.

// Eltern-Konstruktor
function Parent(name) {
  this.name = name || "Adam";
}
// Funktionalitat wird dem Prototyp hinzugefugt (und jetzt wieder vererbt)
Parent.prototype.say = function () {
  return this.name;
};

// Kind-Konstuktor und setzen des Prototyps
function Child(name) {
  Parent.apply(this, arguments);
}
Child.prototype = new Parent();

var kid = new Child("Patrick");
kid.name; // "Patrick"
kid.say(); // "Patrick"

Wie man sieht, wird say jetzt korrekt vererbt. Durch die doppelte Vererbung kann man zudem den an den Kind-Konstruktor übergebenen Namen löschen und erhält dann bei Aufruf von say wieder den im Eltern-Konstruktor zugewiesenen Wert:

delete kid.name;
kid.say(); // "Adam"