Das Anwendungsfeld von JavaScript meist die Clientseite von Anwendungen. Die Anwendungen sind selten sehr groß. Dennoch ist es sinnvoll, objektorientiert zu entwerfen und zu realisieren.
JavaScript ist eine objektorientierte Sprache, die auf Prototypen basiert. Im Gegensatz dazu ist Java auf dem Konzept der Klassen aufgebaut.
In diesem Teil wird verdeutlicht, wie man in JavaScript mit Objekthierarchien und Vererbung arbeiten kann.
Dazu werden folgende Themen behandelt:
Im Folgenden werden Begriffe am Beispiel der Modellierung einer Angestelltenstruktur verdeutlicht:

Klassenbasierte objektorientierte Programmiersprachen, wie Java oder C++, sind auf zwei Grundelementen aufgebaut:
Eine prototypenbasierte objektorientierte Programmiersprache, wie JavaScript, kennt diese Unterscheidung nicht. Es existieren einfach nur Objekte. Diese Sprachen kennen Prototypen, dies sind selbst (reale) Objekte, die als Vorlage (template) zur Erzeugung weitere Objekte verwendet werden. Dabei beinhaltet dieser Prototyp alle Eigenschaften, die initial zum Objekt gehören. Jedes Objekt, ganz gleich aus welchem Prototyp es erzeugt wird, kann selbst Prototyp sein und mit zusätzlichen Eigenschaften ausgestattet werden (oder mit weniger).
In klassenbasierten Sprachen wird eine Klasse durch eine spezielle Klassendefinition erzeugt. In dieser Definition werden die Konstruktoren zum Bilden von Instanzen spezifiziert, Initialwerte für Eigenschaften festgelegt und Startroutinen definiert, die bei der Erzeugung einer Instanz ablaufen. Instanzen werden dann durch new und die Konstruktorfunktion gebildet.
In JavaScript geht das ähnlich, aber man braucht keine eigene Klassendefinition. Jede Funktion kann als Konstruktor Funktion verwendet werden.
In klassenbasierten Sprachen wird eine Hierarchie von Klassen gebildet, indem man innerhalb der Definition angibt, dass die Klasse eine Unterklasse einer bestehenden Klasse ist. Die Klasse erbt die Eigenschaften ihrer Oberklasse (oder verändert die Eigenschaften) und fügt u.U. neue Eigenschaften hinzu. Nehmen wir an, employee beinhaltet die Eigenschaften name (Name) und dept (Abteilung). manager ist ein Subklasse von employee mit der zusätzlichen Eigenschaft report. Dann erbt die Klasse manager die Eigenschaften dept und name.
In JavaScript ist Vererbung auch möglich, die Vorgehensweise ist aber unterschiedlich. Zuerst wird ein prototypisches Objekt mit einer Konstruktorfunktion erzeugt und zum Erzeugen eines neuen Objektes verwendet. Im Beispiel bedeutet das, dass zuerst eine Konstruktorfunktion employee angelegt wirt (mit name und dept). Dann wird eine manager Konstruktorfunktion mit Eigenschaft report angelegt. Zum Schluss wird dann ein neues Objekt employee als Prototyp für die manager Konstruktorfunktion angelegt. Wird nun ein neues manager Objekt erzeugt, so erbt es Eigenschaften von employee.
In klassenbasierten Sprachen werden Klassen zur Übersetzungszeit erzeugt und zur Laufzeit instanziiert. Man kann aber weder Anzahl noch Typ von Eigenschaften einer Klasse dynamisch verändern, nach dem die Klasse erzeugt ist.
In JavaScript kann man zur Laufzeit Eigenschaften von Objekte hinzufügen oder löschen. Eine Änderung von Eigenschaften eines Objektes, das als Prototyp verwendet wurde, bewirkt dass alle Objekte, die das Objekt als Prototyp verwendet haben, geändert werden.
Die u.a. Tabelle zeigt die Unterschiede zwischen JavaScript und Java. Die Punkte werden im Folgenden am Beispiel der Angestellten genauer erörtert.
| Java (klassenbasiert) | JavaScript (prototypenbasiert) |
| Objekt, Klasse und Instanz sind unterschiedliche Konzepte | alle Objekte sind auch Instanzen |
| Klassen werden durch Definitionen erzeugt, Instanzen werden durch Konstruktorfunktionen erzeugt | Objekte werden definiert und erzeugt über Konstruktorfunktionen |
| Erzeugung eines neuen Objektes mit dem Operator new | wie Java |
| Hierarchien werden als Unterklassen von Klassen bei der Klassendefinition gebildet | Hierarchien werden gebildet, indem ein Objekt als Prototyp in einer Konstruktorfunktion verwendet wird |
| Vererbung folgt der Kette von Unterklassen | Vererbung folgt der Kette der Prototypen |
| Klassendefinitionen beschreiben alle Eigenschaften aller Instanzen; keine dynamisches Hinzufügen oder Wegnehmen von Eigenschaften möglich | Konstruktorfunktionen und Prototypen beschreiben einen Initialbestand an Eigenschaften; dynamische Anpassung ist somit möglich. |
Die folgenden Objekte und Hierarchien werden verwendet.

Die Realisierung der Hierarchie ist nachfolgend demonstriert. Es ist eine einfache Lösung
Objektdefinition:

Objekterzeugung:

Nun sehen wir im Beispiel, wie die Vererbung funktioniert und wie sich das Hinzufügen von Eigenschaften auswirkt.
Durch
mark = new WorkerBee;
wird erreicht, dass
name = "" und dept="general" sind (geerbt von Employee)
projects = [] (durch Initialwert von WorkerBee)
JavaScript erreicht dies wie folgt:
Wenn ein new Operator erscheint, wird ein neues generisches Objekt erzeugt und an this in der korrespondierenden Konstruktorfunktion übergeben - im Beispiel die Funktion WorkerBee.Die Konstruktorfunktion setzt den Wert der Eigenschaft projects. Ebenfalls wird der Wert der (internen) Eigenschaft __proto__ auf WorkerBee.prototype (=Employee)gesetzt und somit die Prototyp-Kette bestimmt. Danach wird das so gebildete neue Objekt der Variablen mark zugewiesen.
Durch diesen Prozess werden keine Werte von gerbten Eigenschaften vorbesetzt. Wenn ein Wert einer ererbten Eigenschaft gebraucht wird, so wird er dynamische ermittelt, indem die Eigenschaft __proto__ verwendet wird und so der Prototy-Kette entlang nach der Eigenschaft gesucht wird. Wird eine solche Eigenschaft gefunden, wird ihr Wert zurück gegeben; ansonsten wird die Meldung "Objekt hat diese Eigenschaft nicht" erzeugt.
Nach der Objekterzeugung kann den Eigenschaften des Objektes mark neue Werte gegeben werden, z.B:
mark.name= "Hacker, Mark";
mark.dept= "admin";
mark.projects= ["Apache", "PHP4"];
Durch
mark.bonus = 3000;
wird erreicht, dass nur das Objekt mark die zusätzliche Eigenschaft bonus mit Wert 3000 erhält. D.h. kein anderes Objekt besitzt dadurch die Eigenschaft bonus.
Wenn eine Eigenschaft zu einem Objekt hinzugefügt wird, das als Prototyp in einer Konstruktor Funktion verwendet wird, so besitzt jedes Objekt der Prototyp Kette diese Eigenschaft.
Durch
Employee.prototype.speciality = "none";
wird im Beispiel folgendes bewirkt:

Spezifikation von Eigenschaften im Konstruktor
Bis jetzt waren Konstruktorfunktionen ohne Argumente. Mit Argumenten in Konstruktorfunktionen ist es möglich, den Eigenschaften (Initial-) Werte zuzuweisen.

Im o.a. Beispiel bekommt jim den Namen "Jones, Jim" und die Abteilung ""marketing" bei der Objektinstanziierung zugewiesen.
Die JavaScript Notation
this.name = name || "";
verwendet den OR Operator. Dabei wird der erste Operand ausgewertet; kann er zu true ausgewertet werden, so ist das Ergebnis der erste Operand, ansonsten der zweite.
Somit wird durch die obige Anweisung der Name, der als Argument mitgegeben wird, als Initialwert verwendet; ist kein Argument beim Aufruf der Konstruktorfunktion mitgegeben, so ist der Name vorbelegt mit dem leeren String.
Die Initialisierung der Eigenschaften bezieht sich auf die lokal definierten Properties:
jane = new Engineer("belau");
Jane’s Properties sind jetzt:
jane.name == "";
jane.dept == "general";
jane.projects == []; jane.machine == "belau"
Eine Initialisierung von ererbten Eigenschaften ist so einfach nicht möglich. Dies kann nur durch mehr Kode in der Konstruktorfunktion erreicht werden.
Spezifikation von Eigenschaften im Konstruktor (erweitert)
Bis jetzt hat eine Konstruktorfunktion immer ein generisches Objekt erzeugt und dann wurden lokale Eigenschaften hinzugefügt. Man kann auch innerhalb der Konstruktorfunktion Eigenschaften hinzufügen, indem man Konstruktorfunktionen der Prototyp-Kette aufruft:

Die Definition der Funktion Engineer:
function Engineer (name, projs, mach) {
this.base = WorkerBee;
this.base(name, "engineering", projs);
this.machine = mach || "";
}
mit der Deklaration
jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
wird wir folgt abgearbeitet:
Achtung:
Dadurch daß man innerhalb des Konstruktors Enginner schreibt
this.base = WorkerBee;
ist noch nicht automatisch eine Vererbung von Eigenschaften initiiert. Eigenschaften, die danach zugefügt werden, gelten nicht mehr für das Objekt.
Beispiel: betrachten wir folgende Anweisungen:
jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";
"jane" erbt die Eigenschaft "specialty" nicht.
Im Gegensatz dazu erbt "jane" diese Eigenschaft im nachfolgenden Kodestück:
Engineer.prototype = new WorkerBee;
jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";
Man muss den Prototyp explizite zuordnen, um die Vererbung dynamisch aufzusetzen.
Jetzt hat jane die Eigenschaft speciality ererbt und sie ist mit none vorbesetzt.
Bis jetzt haben wir gesehen, wie durch Konstruktoren und Prototypen Hierarchien aufgebaut werden können und der Vererbungsmechanismus grob funktioniert. Nun sehen wir uns Vererbung etwas genauer an.
Wird auf eine Eigenschaft eines Objektes zugegriffen, so führt JavaScript folgende Schritte durch:
Damit ist die Art und Weise, wie man Eigenschaften definiert entscheidend dafür, ob die Vererbung so funktioniert, wie man das will. Betrachten wir folgendes Kodefragment mit Definitionen:
function WorkerBee () {
this.projects = [];
}
WorkerBee.prototype = new Employee;
Wird nun eine Instanz von WorkerBee durch
amy = new WorkerBee;
erzeugt, so hat die Instanz folgende Werte ihrer Eigenschaften:
amy.name == ""
amy.dept == "general"
amy.projects == []
Wenn nun der Wert der Eigenschaft name im Prototyp verändert, etwa durch
Employee.prototype.name = "Unknown";
dann wird dieser Wert nicht auf alle Instanzen von Employee propagiert. Somit gilt weiterhin
amy.name == ""
Erklärung:
Wenn die Instanz von Employee erzeugt wird, so erhält die Instanz die lokalen Werte, also der leeren String für name, da durch WorkerBee.prototype = new Employee die Kette bestimmt ist und so name im Prototyp gefunden wird. Somit wird nicht mehr weiter gesucht und somit Employee.prototype nicht mehr untersucht.
Will man die Werte einer Objekteigenschaft zur Laufzeit ändern und dabei den neuen Wert an alle "Nachfahren" des Objektes vererben, so darf man die Eigenschaft nicht in der Konstruktorfunktion definieren, sondern in dem zugehörenden Prototyp.
Angewandt auf das obige Beispiel bedeutet dies:
Employee.prototype.name = "";
function WorkerBee () {
this.projects = [];
}
WorkerBee.prototype = new Employee;
amy = new WorkerBee;
Employee.prototype.name = "Unknown"
Jetzt gilt:
amy.name == "Unknown"
Schlussfolgerung:
Wenn Initialwerte für Objekteigenschaften zur Laufzeit verändert und vererbt werden sollen, muss man die Eigenschaft im Prototyp des Konstruktor definieren, nicht innerhalb der Konstruktorfunktion selbst.
In klassischen objektorientierten Programmiersprachen existiert meist ein Operator, etwa instanceof in Java, um abzufragen, ob ein Objekt in der Prototypkette enthalten ist. Solch ein Operator existiert in JavaScript nicht; man kann aber recht einfach eine Funktion entwickeln, die die gewünschte Funktionalität bereit stellt.
Wird der Operator new mit einer Konstruktorfunktion verwendet (WorkerBee.prototype = new Employee;), so setzt JavaScript die Eigenschaft __proto__ des neu erzeugten Objektes auf den Wert, der durch die prototyp Eigenschaft der Konstruktorfunktion bestimmt ist. Damit kann man dann die Kette testen.
In unserem Beispiel wird durch
chris = new Engineer("Test, Chris", ["Communicator"], "hp01");
ein Objekt erzeugt, für das gilt:
chris.__proto__ == Engineer.prototype
chris.__proto__.__proto__ == WorkerBee.prototype
chris.__proto__.__proto__.__proto__ == Employee.prototype
chris.__proto__.__proto__.__proto__.__proto__ ==
Object.prototype
chris.__proto__.__proto__.__proto__.__proto__.__proto__ ==
null
Damit kann man eine JavaScript Funktion instanceOf() schreiben:
function instanceOf(object,
constructor) {
while (object != null) {
if (object ==
constructor.prototype)
return true;
object =
object.__proto__;
}
return false;
}
Damit gilt für unser Beispiel:
instanceOf (chris, Engineer) == true
instanceOf (chris, WorkerBee) == true
instanceOf (chris, Employee) == true
instanceOf (chris, Object) == true
instanceOf (chris, SalesPerson) == false
Einige objektorientierte Programmiersprachen, wie C++, erlauben Mehrfachvererbung, d.h. ein Objekt kann Eigenschaften von mehreren unabhängigen Eltern erben.
JavaScript erlaubt Mehrfachvererbung nicht. Die Vererbung von Eigenschaften wird zur Laufzeit immer durch Suche in der Prototypkette umgesetzt. Da ein Objekt einen einzigen Prototypen zugeordnet hat, sind Mehrfachvererbungen nicht möglich.
Wenn globale Variable in Konstruktorfunktionen verwendet werden, können oft nicht erwünschte Effekte eintreten.
Nehmen wir an, in unserem Beispiel sollen alle Angestellten eine Personalnummer bekommen und diese Nummer soll fortlaufend und ohne Lücken sein.
Die erste Idee ist:
Auf den ersten Blick funktioniert dies.
Aber bei dem vollständigen Beispiel, also wenn Objekte über die Objekthierarchie (mit der base Eigenschaft) gebildet werden, entstehen Lücken, da das Inkrementieren bei jedem Aufruf des Employee Konstruktors ausgeführt wird.
var idCounter = 1;
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
this.id = idCounter++;
}
function Manager (...
Manager.prototype = new Employee; /* idCounter == 2 */
function WorkerBee (... this.base
= Employee; this.base(name,dept); ...
WorkerBee.prototype = new Employee; /* idCounter == 3
*/
function Engineer (...
Engineer.prototype = new WorkerBee; /* idCounter
== 4 wg. base */
function SalesPerson (...
SalesPerson .prototype = new Employee; /* idCounter == 5 */
mark = new Egnineer("Mark"); /* mark.id == 5, idCounter == 6 */
Je nach Aufgabenstellung muss eine Lösung gesucht werden. In unserem Beispiel könnte sie etwa wie folgt aussehen: nur wenn employee mit einem Argument (name ungleich leerer String) aufgerufen wird, wird idCounter inkrementiert.
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
if (name)
this.id =
idCounter++;
}
Verdeutlichen Sie sich den Vererbungsmechanismus, in dem Sie das Angestelltenbeispiel in ein lauffähiges Programm bringen. Dabei sollen von jedem Angestelltentyp mindestens eine Person mit den entsprechenden Daten eingegeben werden können. Die Objekthierarchie ist auszugeben.