Oliver Schmidt

Pseudoklassische Vererbung in JavaScript - Teil 4

2012-01-28

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

Dies ist der letzte Teil der kleinen Reihe zum Thema “Pseudoklassische Vererbung in JavaScript”, die auf Stoyan Stefanovs sehr empfehlenswerten Buch [“JavaScript Patterns”](https:// books.google.de/books?id=WTZqecc9olUC&printsec=frontcover&dq=stefanov+javascript+patterns&hl=de&sa=X&ved=0ahUKEwj34oLMj8jpAhXdB50JHWp1Aj4Q6AEIKzAA#v=onepage&q=stefanov%20javascript%20patterns&f=false) basiert.

5. Ein temporärer Konstruktor

Beim zuletzt vorgestellten Muster wurde dem Kind-Prototyp der Eltern-Prototyp zugewiesen (Child.prototype = Parent.prototype;). Dies führt dazu, dass Änderungen am Prototyp sich immer über die Vererbungskette hinweg auswirken, und zwar auch von den Kindern in Richtung Eltern.

Das als “temporärer Konstruktor” bezeichnete fünfte Muster umgeht dieses Problem, indem es eine leere Funktion als Bindeglied (Stoyan spricht von einem “proxy”) zwischen Eltern und Kind verwendet:

function inherit(C, P) {
  var F = function () {};
  F.prototype = P.prototype;
  C.prototype = new F();
}

Wie man sieht, zeigt der Prototyp der leeren Funktion auf den Prototyp des Eltern-Konstruktors, und der Prototyp des Kind-Konstruktors auf eine neue Instanz der leeren Funktion. Durch diesen Aufbau werden nur Eigenschaften des Prototyps vererbt, da keine Instanz des Eltern-Konstruktors erzeugt wird, und das Kind somit keine Zugriff auf dessen Eigenschaften hat. Wie in den letzten Artikeln bereits erwähnt, ist genau dieses Verhalten erwünscht.

Hier nun eine (triviale) beispielhafte Anwendung:

function Parent(name) {
  this.name = name || 'Tom';
}

Parent.prototype.say = function () {
  return 'Ich heisse ' + this.name;
};

function Child(name) {
  this.name = name || 'Timo';
}

function inherit(C, P) {
  var F = function () {};
  F.prototype = P.prototype;
  C.prototype = new F();
}

inherit(Child, Parent);

var son = new Child('Peter');
son.say(); //"Ich heisse Peter"

Die Super-Klasse speichern

Zur Verfeinerung des Musters kann man noch eine Referenz zum Eltern-Prototyp bereitstellen, um den Zugriff auf Elterneigenschaften zu ermöglichen. Stoyan hat als Bezeichnung für die Eigenschaft “uber” gewählt (der deutsche Umlaut wird dabei verständlicherweise umgangen), da super ein reserviertes Wort in JavaScript ist:

function inherit(C, P) {
  var F = function () {};
  F.prototype = P.prototype;
  C.prototype = new F();
  C.uber = P.prototype;
}

Die constructor-Eigenschaft zurücksetzen

Als weitere Optimierung des Musters schlägt Stoyan das explizite setzen der constructor-Eigenschaft vor. Diese gibt Auskunft darüber, durch welche Konstruktor-Funktion ein Objekt erzeugt wurde. Allerdings würde sie im Falle des temporären Konstruktors immer zurückgeben, dass Parent der Konstruktor war, was nicht sehr hilfreich ist.

Der angepasste Code sieht folgendermaßen aus:

function inherit(C, P) {
  var F = function () {};
  F.prototype = P.prototype;
  C.prototype = new F();
  C.uber = P.prototype;
  C.prototype.constructor = C;
}

Feinschliff

Um die Performance noch etwas zu verbessern, empfiehlt Stoyan es zu vermeiden den temporären Konstruktor bei jedem Vererbungsfall zu erzeugen. Um dies zu erreichen kommt eine Eigenschaft von JavaScript zum Einsatz, die mit zur Popularität der Sprache beigetragen hat: Closures.

var inherit = (function () {
  var F = function () {};
  return function (C, P) {
    F.prototype = P.prototype;
    C.prototype = new F();
    C.uber = P.prototype;
    C.prototype.constructor = C;
  };
})();

Hier wird der Variablen inherit ein anonymer bzw. unbenannter Funktionsausdruck (engl. function expression) zugewiesen, der sofort ausgeführt wird. Dies wird auch als “immediate function” bezeichnet. Rückgabewert ist dabei ein weiterer anonymer Funktionsausdruck, der bis auf ein Detail der bisherigen inherit-Funktion gleicht: der temporäre Konstruktor befindet sich ausserhalb der Funktion, eine Ebene höher als lokale Variable im äußeren Funktionsausdruck. Er wird daher nur einmal erzeugt, nämlich im Moment der Ausführung des äußeren Funktionsausdrucks. Trotzdem hat der zurückgegebene Funktionsausdruck bei jedem Aufruf von inherit Zugriff auf ihn (für ausführlichere Infos zur Funktionsweise von Closures siehe den Link oben).