Objektorientierung in JavaScript

Die Prototypenkette

Vererbung realisieren

Wie bereits gesehen, bilden die Grundlage für Vererbung in JavaScript die Prototypen. JavaScript versteht unter Vererbung ein anderes Konzept als beispielsweise Java. Vererbung wird in JavaScript durch das Hinzufügen von Eigenschaften und Methoden zu einem bestimmten Objekt realisiert, wobei diese dann für alle daraus erzeugten Objekte gelten.

Bei dem Vererbungsvorgang wird eine Instanz des Elternobjekts erstellt und als Prototyp des Kindobjekts definiert. Dies geschieht jedoch nur ein Mal und nicht jedes Mal wenn eine neue Instanz erstellt wird.

Im Folgenden wird anhand eines Beispiels gezeigt, wie man Vererbung mit Prototypen realisiert und dadurch eine Prototypenkette entsteht.

Folgende Hierarchie wird entwickelt:
Jeder Arbeitnehmer soll über die Attribute name und abteilung verfügen, die über den Konstruktor initialisiert werden. Arbeiter soll von Arbeitnehmer abgeleitet werden und zudem über die Eigenschaft projekte verfügen, welche, wie die folgenden Eigenschaften auch, über den Konstruktor initialisiert wird. Mechaniker soll hingegen von Arbeiter abgeleitet sein und eine Eigenschaft maschine haben. Eine Instanz von Mechaniker soll demzufolge über alle Attribute von Mechaniker, Arbeiter und Arbeitnehmer verfügen.

Die Realisierung in JavaScript erfolgt folgendermaßen:

function Arbeitnehmer(n,a) {
  this.name = n;
  this.abteilung = a;
}

function Arbeiter(n,a,p) {
  Arbeitnehmer.call(this,n,a);
  this.projekte = p;
}
Arbeiter.prototype = new Arbeitnehmer;
Arbeiter.prototype.construcor = Arbeiter;
delete Arbeiter.prototype.name;
delete Arbeiter.prototype.abteilung;

function Mechaniker(n,a,p,m) {
  Arbeiter.call(this,n,a,p);
  this.maschine = m;
}
Mechaniker.prototype = new Arbeiter;
Mechaniker.prototype.construcor = Mechaniker;
delete Mechaniker.prototype.projekte;

Im obigen Beispiel wurden zunächst einige Konstruktoren erstellt. Durch die jeweiligen nachfolgenden Zeilen wird die Vererbung realisiert. Was diese Code-Passagen im Einzelnen bewirken wird nun erklärt.

Der Konstruktor für Arbeitnehmer bleibt unverändert, von ihm sollen alle weiteren Objekte erben. Instanzen, die mit diesem Konstruktor erstellt werden, erben nur von Object, da dies der default-Prototyp ist.

Im Konstruktor Arbeiter wird der Arbeitnehmer-Konstruktor mit der call-Methode aufgerufen. Die call-Methode ermöglicht es, objektexterne Methoden wie objekteigene Methoden aufzurufen. Dazu wird dieser Methode das Objekt auf dem die Funktion (die Konstruktorfunktion Arbeitnehmer) ausgeführt werden soll, in diesem Fall this, sowie die nötigen Argumente übergeben:

Arbeitnehmer.call(this,n,a);

Dadurch werden die im Arbeitnehmer-Konstruktor definierten Eigenschaften zu lokalen Eigenschaften von Arbeiter-Objekten, da sie auf das umgbende Objekt über das Schlüsselwort this referenziert werden.

Zusätzlich zu den Eigenschaften von Arbeitnehmer, wird eine Eigenschaft projekte definiert und über den Parameter im Konstruktor initialisiert:

this.projekte = p;

Benutzt man das default-Prototyp-Objekt das Konstruktors Arbeiter erstellt man einen von Object erbenden Konstruktor. Um dauerhaft von Arbeitnehmer zu erben, das bedeutet, dass sich auch Änderungen am Prototyp-Objekt auf Instanzen von Arbeiter auswirken, muss das prototype-Attribut von Arbeiter auf eine neue Instanz von Arbeitnehmer verweisen:

Arbeiter.prototype = new Arbeitnehmer;

Diese Instanz enthält wiederum eine Referenz auf das eigentliche Prototyp-Objekt von Arbeitnehmer, über die die Prototyp-Eigenschaften angesprochen werden können.

Beim Setzen dieser Eigenschaft wird allerdings auch die constructor-Eigenschaft überschrieben, sodass sie auf den Arbeitnehmer-Konstruktor verweist. Damit eine Instanz von Arbeiter auch mit dem korrekten Konstruktor aufgerufen wird, muss dieser explizit neu gesetzt werden:

Arbeiter.prototype.constructor = Arbeiter;

Da die Eigenschaften von Arbeitnehmer (durch den Aufruf der call-Methode) bereits lokale Eigenschaften von Objekten, die mit dem Arbeiter-Konstruktor erstelllt wurden, sind, können diese Eigenschaften aus dem Prototyp-Objekt entfernt werden:

delete Arbeiter.prototype.name;
delete Arbeiter.prototype.abteilung;

Das Löschen dieser Eigenschaften ist nicht zwingend notwendig. Da das Prototyp-Objekt aber nur zu Vererbungszwecken von dem Prototyp hinzugefügten Eigenschaften dient und die Eigenschaften name und abteilung nie von erbenden Instanzen angesprochen werden, da sie in jeder Instanz als lokale Eigenschaften vorliegen, können sie gelöscht werden.

Die Instanzierung von Objekten ohne Angabe von Parametern, wie sie zur Erzeugung von Prototyp-Objekten angewandt wird, bewirkt die Belegung der Eigenschaften mit dem Wert undefined. Dies ist ein weiterer Grund, sie aus dem prototypischen Objekt zu entfernen.

Analog wird die Vererbung der Eigenschaften von Arbeiter an Mechaniker realisiert, sodass eine Instanz von Mechaniker über alle Eigenschaften von Mechaniker, Arbeiter und Arbeitnehmer verfügt.

//Jim erstellen
jim = new Mechaniker("Jim","Technik","Schweissen","Schweissgeraet");
//Eigenschaften anzeigen
alert("Jim's Name: " + jim.name);
alert("Seine Abteilung: " + jim.abteilung);
alert("Sein Projekt: " + jim.projekte);
alert("Seine Maschine: " + jim.maschine);

Im obigen Beispiel wurde also eine mehrstufige Vererbung realisiert. Die entstandene Vererbungshierarchie wird, wie bereits gesagt, in prototypbasierten Sprachen Prototypenkette genannt. Allerdings wird diese im obigen Beispiel nicht genutzt, da die Aufrufe der call-Methoden lokale Eigenschaften in den Objekten anlegen. Die Prototypenkette wird erst dann angesprochen, wenn einem der Prototypen Eigenschaften oder Methoden hinzugefügt werden, die in einer Instanz nicht vorhanden sind.

Fügt man nun den Prototypen jeweils Eigenschaften und Methoden hinzu, so werden diese bei lesendem Zugriff über die Prototypenkette angesprochen. Im Folgenden werden den Prototypen Methoden zur Ausgabe ihrer Eigenschaften hinzugefügt.

Arbeitnehmer.prototype.printName = function () { alert(this.name); }
Arbeitnehmer.prototype.printAbteilung = function () { alert(this.abteilung); }
Arbeiter.prototype.printProjekte = function () { alert(this.projekte); }
Mechaniker.prototype.printMaschine = function () { alert(this.maschine); }

Die folgende Grafik zeigt die bisherigen Durchführungen und veranschaulicht die Zusammenhänge zwischen den Prototypen und der Instanz jim von Mechaniker:

alt-text

Grafik 06: Visualisierung der Veränderung zur Realisierung von mehrstufiger Vererbung

instanceOf-Methode nachbilden

Eine Instanz von Mechaniker ist durch die Vererbung ein zu allen Elementen aus der Prototypenkette gehöriges Objekt.

Dies lässt sich auf folgende Weise durch eine instanceOf-Funktion, ähnlich wie sie in Java existiert, zeigen:

//Nachbildung einer instanceOf-Methode
function instanceOf(object, constructor) {
  while (object != null) {
   //constructor.prototype ist Referenz auf Prototypen
   if (object == constructor.prototype)
    return true;
   object = object.__proto__;
  }
  return false;
}

Als Parameter erwartet die Funktion ein Objekt, also den Mechaniker jim, und einen Konstruktor gegen dessen Prototyp-Referenz getestet wird. Der Operator == testet dabei auf Gleichheit und nicht auf Identität (===). Er ist also weniger restriktiv und testet, ob die Eigenschaften aus dem Prototypen auch im Objekt vorhanden sind und nicht ob die Referenz dieselbe ist.

Die Funktion testet rekursiv die Gleichheit der Objekt-Instanz mit dem Prototyp der entsprechenden Konstruktorfunktion.

Mit Hilfe dieser Funktion wird erkenntlich, dass eine Instanz von Mechaniker zu allen Objekten gehört, von denen sie Eigenschaften erbt:

//Ist Jim eine Instanz von Arbeitnehmer?
alert(instanceOf (jim, Arbeitnehmer));
//Ist Jim eine Insatnz von Arbeiter?
alert(instanceOf (jim, Arbeiter);
//Ist Jim eine Instanz von Mechaniker?
alert(instanceOf (jim, Mechaniker);
//Ein false_Beispiel: Testet ob Jim eine Number ist.
alert(instanceOf (jim, Number);

Die vorgefertigte instanceof-Funktion wird nicht von allen Interpretern unterstützt. Daher bietet es sich an, die oben aufgeführte Funktion zu benutzen.

Verifikation der Prototypenkette

Jede Instanz von Mechaniker enthält also eine lokale Eigenschaft aller in den drei Konstruktoren definierten Eigenschaften. Fügt man nun jedem Prototyp von Mechaniker, Arbeiter und Arbeitnehmer eine Eigenschaft hinzu, so erscheinen diese ebenfalls als Eigenschaften einer Instanz von Mechaniker.

Realisiert wird dies über die implizite __proto__-Eigenschaft, die jedes Objekt in JavaScript hat. Wie bereits erwähnt, enthält sie eine Referenz auf das Prototyp-Objekt

Über diese Eigenschaft wird das Lookup (die Suche) nach Eigenschaften eines Objekts durchgeführt, wie im Kapitel "Zugriff auf Objekt-Eigenschaften" gezeigt wurde.

Über die __proto__-Eigenschaft der Objekte kann man darüber hinaus (mit entsprechendem Vorwissen) die Prototypenkette zurückverfolgen und dadurch belegen. Im Folgenden wird über einen Vergleich der verketteten __proto__-Eigenschaften mit den verschiedenen Objekt-Prototypen die Prototypenkette aufgezeigt:

alert(jim.__proto__ == Mechaniker.prototype);
alert(jim.__proto__.__proto__ == Arbeiter.prototype);
alert(jim.__proto__.__proto__.__proto__ == Arbeitnehmer.prototype);
alert(jim.__proto__.__proto__.__proto__.__proto__ == Object.prototype);
alert(jim.__proto__.__proto__.__proto__.__proto__.__proto__ == null);

Das Verhalten eines Objektes kann also von mehreren Definitionen abhängen. Ein Objekt wird aber unmittelbar nur aus einem Prototyp abgeleitet. Aus diesem Grund unterstützt JavaScript keine Mehrfachvererbung.

[top]