Vererbung und die Prototype-chain – Programmieren lernen mit JavaScript – Thytos
Nächstes Video startet in 3 Sekunden.
Programmieren lernen mit JavaScript

Vererbung und die Prototype-chain

In der Ob­jekt­o­ri­en­tie­rung gibt es das Kon­zept von Ver­er­bung. Ei­ne Klas­se erbt Ei­gen­schaf­ten und Me­tho­den von einer an­de­ren und er­wei­tert die­se um ei­ge­ne. In Ja­va­Script gibt es kei­ne Klas­sen. Ver­er­bung funk­tio­niert hier über die Pro­to­typ­kette.

Im folgenden soll eine CastDatabase erstellt werden, eine Datenbank spezifisch für Filmbesetzungen. Es wäre praktisch, die Datenbank aus einem der letzten Artikeln dafür wiederverwenden zu können.
Ein Aspekt der Objektorientierung ist, Objekte erweitern zu können. In einer klassenbasierten Sprache wie Java geht das mit dem extends Schlüsselwort.

// Beispiel aus Java
public class MountainBike extends Bicycle {
  …
}

Es wäre also praktisch, die Database erweitern zu können, um darauf aufbauend die CastDatabase zu kreieren.
Aber JavaScript ist eine klassenlose Sprache. Wie funktioniert das hier?

Prototypkette

Der Prototyp eines Konstruktors kann auf ein eigenes Objekt gesetzt werden

function Beispiel () {}
// Der Prototyp wird auf ein Objekt gesetzt
Beispiel.prototype = {};

Die Schreibweise mit öffnender und schließender geschweifter Klammer ist allerdings nur eine Abkürzung für new Object.

function Beispiel () {}
// Statt {} kann auch new Object() geschrieben werden
Beispiel.prototype = new Object();

Was bedeutet das? Wenn an dieser Stelle Object instanziiert wird, kann auch ein Objekt eines beliebigen anderen Konstruktors instanziiert werden.
Methoden und Eigenschaften aus dem Prototyp stehen bei der Instanz zur Verfügung. Bringt die Instanz, die dem Prototyp zugewiesen wird, eigene Eigenschaften und Methoden mit, stehen auch diese an der Instanz zur Verfügung.

function Beispiel () {}
// Zum Test: Prototyp auf ein neues Array setzen
Beispiel.prototype = new Array();

var bsp = new Beispiel();

// Array-Properties gibt es nun auch bei Instanz von Beispiel
bsp.push("Hello");

bsp.length;
// => 1

bsp
// => ["Hello"]

So funktioniert Vererbung in JavaScript. Die Properties eines Objekts werden geerbt und stehen dadurch bei einem anderen Objekt zur Verfügung.

Mit dem Wissen kann die CastDatabase nun Database erweitern:

function CastDatabase() {
  // Auch jede CastDatabase-Instanz braucht
  // ihren eigenen Datensatz
  this.datensatz = [];
}

// Erbe nun Methoden und Eigenschaften von Database
CastDatabase.prototype = new Database();

// Erweitere / Verändere CastDatabase nach Belieben
CastDatabase.prototype.insert = function (name, gender, birthday) {
  this.datensatz.push({
    name: name,
    gender: gender,
    birthday: birthday
  });
};

Es gibt also nun nicht mehr nur ein Prototype-Objekt – das Prototyp-Objekt hat nun ebenfalls ein eigenes Prototyp-Objekt.
Das muss an der Stelle noch nicht aufhören: Auch das Prototyp-Objekt des Prototyp-Objekts kann ein eigenes Prototyp-Objekt haben und so weiter.
Das ganze heißt: Die Prototypkette, auf Englisch prototype chain.

In der Konsole von Chrome kann die Prototypkette betrachtet werden, indem die __proto__ Properties geöffnet werden

var cast = new CastDatabase();

cast.insert("Anthony Hopkins", "male", new Date(1937, 11, 31));

cast.where();
// => [{ name: "Anthony Hopkins", gender: "male", birthday: … }]

Wird ein Property an einem Objekt aufgerufen, wird zuerst beim Objekt selbst geschaut, ob es das Property besitzt. Ist das nicht der Fall, wird im Prototyp nachgesehen. Ist es auch dort nicht zu finden, wird in dessen Prototyp gesucht.
Dieser Vorgang heißt Property lookup.

Im Fall der CastDatabase gibt es im Prototyp die Methode insert, die aber ebenfalls durch Database im Prototyp vom Prototyp existiert. Wie kann es sein, dass es bei zwei gleichnamigen Properties in der Prototypkette nicht zu Verwechslungen oder anderen Konflikten kommt?
Dadurch, dass der Property lookup immer beim Objekt selbst beginnt und sich dann schrittweise durch die Prototypkette durcharbeitet, wird die erste insert-Methode genommen, also die aus dem CastDatabase-Prototyp.
Dieser Fall, wenn ein gleichnamiges Property in der Prototypkette früher kommt als ein anderes, wird als Property shadowing bezeichnet. Es ist das Äquivalent zum Überschreiben von Methoden und Eigenschaften in einer klassenbasierten Sprache.

Vererbung

Vererbung ist ein essentieller Aspekt in der Objektorientierung. Sie hilft dabei von einer Basis auszugehen und nur durch Anpassungen und Erweiterungen dieser die gewünschte Funktionalität zu implementieren.
Die Richtung ist dabei immer von abstrakt zu spezifisch.

Die folgende verschachtelte Liste ist ein Beispiel für einen Vererbungsbaum aus der klassenbasierten Programmiersprache Java.

  • java.lang.Object
    • java.awt.Component
      • java.awt.Container
        • javax.swing.JComponent
          • javax.swing.text.JTextComponent
            • javax.swing.JTextField
              • javax.swing.JPasswordField

Ganz zu Beginn ist die Klasse Object, die die Basis für alle Objekte in Java darstellt. Im Abstract Window Toolkit wird daraus die Klasse Component. Diese Component-Klasse beinhaltet die Funktionalität, um Elemente auf dem Bildschirm auszugeben. Welches Element genau, ist hierbei nicht festgelegt, es enthält bloß diese noch recht abstrakte Fähigkeit.
Über weitere Vererbungsschritte wird aufbauend auf dieser Component eine JTextComponent, eine Komponente, um Text auf dem Bildschirm darzustellen. Aufbauend darauf gibt es das JTextField, eine Komponente, die Tastatureingaben annimmt und als Text auf dem Bildschirm ausgibt – ein Eingabefeld also.
Und schließlich erweitert das JPasswordField die JTextField-Klasse, um ein Eingabefeld zu erzeugen, bei dem jedes Zeichen nur als ein Punkt dargestellt wird, damit nicht zu sehen ist, was der Nutzer eingibt. Das ist nützlich für Passwörter.

Dies ist eine Hierarchie mit sieben Stufen. Wenn die Entwickler für das JPasswordField bei Null angefangen hätten zu programmieren, hätten sie sehr viel Code schreiben und sehr viel durchdenken müssen. So dagegen brauchten sie das JTextField nur an einigen Stellen zu erweitern und an anderen Stellen zu überschreiben, um daraus ein Passwortfeld zu machen.
Die Überlegungen, wie etwas auf dem Bildschirm ausgegeben werden kann, wie Text gerendert und in Pixel umgewandelt wird, wie Tastatureingaben angenommen und bestimmten Buchstaben und Zeichen zugeordnet werden, ist alles ausgelagert — das passiert bereits vorher in der Erb-Hierarchie und muss nicht mehr bedacht werden, um das Passwortfeld zu programmieren.
Vererbung spart also nicht bloß Schreibarbeit und erleichtert zukünftige Veränderungen – es lagert vor allem auch Komplexität aus, sodass der Fokus auf die wesentliche Funktionalität gerichtet werden kann.