zurück zum Artikel

Fortgeschrittene OO-Programmierung in Javascript

Sprachen
Aufmacher

Javascript hat unter Softwareentwicklern keinen besonders guten Ruf. Die Sprache gilt als relativ unstrukturiert, fehleranfällig und ganz allgemein untauglich, große Anwendungen zu erstellen – und vor allem zu pflegen. Schuld an diesem Image dürfte vor allem das ungewohnte objektbasierte Prinzip der Sprache sein: Statt Klassen gibt es nur Objekte, statt Methoden nur Funktionen, und ein Compiler zur Typ- und Syntaxprüfung existiert gar nicht. Dieser Artikel soll zeigen, wie man dennoch mit bekannten (und weniger bekannten) Mitteln vollständig objektorientierte und wartbare Anwendungen erstellen kann.

In Javascript ist bis auf primitive Typen wie Zahlen alles ein Objekt. Dazu zählen sogar Funktionen (man könnte von Funktionsobjekten sprechen), die ausführbaren Code in sich kapseln. Die wichtigste Eigenschaft von Javascript-Objekten ist, dass sie als assoziative Arrays arbeiten: Jedem Objekt kann man per objekt.name = wert beliebige Attribute hinzufügen. Als Werte sind auch Funktionsobjekte zulässig, die man wie Methoden aufrufen kann.

Im Zusammenspiel mit dem new-Operator kommen Funktionen als Konstruktoren zum Einsatz (siehe Listing 1 [1]). new bewirkt, dass ein neues Objekt erzeugt wird – zunächst noch ohne Attribute -, auf das der Programmierer innerhalb des Konstruktors über das Schlüsselwort this zugreifen kann. Innerhalb von Funktionen, die über den Punkt-Operator auf einem bestimmten Objekt aufgerufen werden, im Listing testPerson.getName(), steht dieses Objekt ebenfalls als this zur Verfügung.

Obwohl es in Javascript nur Objekte und Funktionen gibt, sei im Folgenden von Klassen und Methoden die Rede. Gemeint sind damit Klassen als objektorientiertes Konzept, in Javascript durch Konstruktor-Funktionen abgebildet, sowie Funktionen, die als Attribute an einem Objekt hängen.


Prototypen sparen Speicher

Nachteilig an der direkten Definition von Methoden im Konstruktor ist, dass es pro Instanz von Person jeweils eine Instanz des Funktionsobjekts getName gibt. Bei vielen Instanzen bedeutet das einen unnötig hohen Speicherverbrauch, da gleiche Methoden mehrfach im Speicher gehalten werden. Um das zu vermeiden, kann man sich den sogenannten Prototypen zunutze machen, den jedes Javascript-Objekt besitzt. Er dient als Fallback: Wenn ein Objekt nach einem Attribut gefragt wird, das es nicht selbst besitzt, greift der Interpreter auf den Prototypen zurück. Ist es hier ebenfalls nicht zu finden, zieht er dessen Prototyp heran, was sich so lange wiederholt, bis er das Attribut gefunden hat oder das Ende der Prototyp-Kette erreicht ist.

Den Prototyp eines Objekts kann man nicht direkt setzen, sondern muss ihn indirekt über den Konstruktor festlegen: Bei jeder Erzeugung eines Objekts wird das prototype-Attribut seiner Konstruktor-Funktion abgefragt und sein Inhalt als Prototyp für das neue Objekt verwendet. Als Hilfestellung enthält das prototype-Attribut jeder Funktion schon per Default ein leeres Objekt, das man mit eigenen Werten (üblicherweise Funktionen) füllen kann. Listing 2 [2] zeigt das anhand einer getName-Methode für alle Person-Objekte: Durch die Verwendung des prototype-Attributs ist diese Methode nur einmal vorhanden, während Listing 1 [3] für jede Person-Instanz eine eigene getName-Instanz erzeugt.

Unter anderem lässt sich der Prototype-Ansatz dazu nutzen, eine Vererbungshierarchie aufzubauen. Eine weit verbreitete Technik hierzu ist die Verwendung einer Instanz der Superklasse als Prototyp für die Objekte der Subklasse (siehe Listing 3 [4] ). Durch die automatische Attributauflösung über die Prototyp-Kette sind damit alle Methoden von Person in Employee-Instanzen ebenfalls zugänglich. Gleichzeitig kann man an die als Prototyp verwendete Person-Instanz neue Methoden anhängen, die danach in allen Employee-Objekten zur Verfügung stehen. Wichtig dabei: Es wird nicht Person selbst (als Konstruktor beziehungsweise Klasse) verändert, sondern eine Instanz von Person, die als Prototyp für alle Employee-Objekte dient.

Eine Superklassen-Instanz als Prototyp zu verwenden ist in Javascript zwar üblich, für Neulinge allerdings recht gewöhnungsbedürftig. Außerdem führt das zu sinnlosen Aufrufen des Konstruktors der Superklasse, was eigentlich nur sinnvoll ist, wenn es um die Erstellung richtiger[(i] Instanzen geht und nicht um den Aufbau der Vererbungshierarchie. Dieser konzeptionelle Bruch zeigt sich auch daran, dass in Listing 3 [5] der Person-Konstruktor ohne Parameter aufgerufen wird, obwohl er einen Vor- und Nachnamen erwartet. Da man ja gar keine konkrete Person-Instanz erzeugen will, gäbe es hier auch keinen sinnvollen Wert, den man angeben könnte. Dass der Aufruf an sich dennoch nicht zu Fehlern führt, liegt daran, dass Javascript für alle nicht angegebenen Parameter automatisch [i]undefined einsetzt.

Bessere Vererbung

Um derlei zu vermeiden, behelfen sich viele Entwickler auf die eine oder andere Weise. Eine der elegantesten Lösungen ist, was man statt der Zeile Employee.prototype = new Person(); in Listing 3 [6] verwenden kann:

var Temp = function() {};

Temp.prototype = Person.prototype;
Employee.prototype = new Temp();

Ein leerer temporärer Konstruktor dient der Erzeugung des Employee-Prototyps. Das vermeidet den Aufruf des Person-Konstruktors, während die Hierarchie durch das Setzen von Temp.prototype auf Person.prototype gewahrt bleibt.

Im Fall des Prototyp-Objekts ist es nützlich, den Aufruf des Superklassen-Konstruktors zu vermeiden. Ganz anders sieht es im Konstruktor der Unterklasse aus. Hier muss man sogar den Superklassen-Konstruktor aufrufen, um das Objekt korrekt zu initialisieren. In anderen Sprachen wie Java ist das recht einfach: super(); – Javascript kennt diese Syntax aber nicht. Man kann zwar die Konstruktor-Funktion der Superklasse direkt aufrufen (Person();), aber dann fehlt ihr der Kontext, das heißt this ist nicht korrekt gesetzt. Abhilfe schafft hier die sogenannte call-Methode, die jedes Funktionsobjekt in Javascript besitzt. Mit ihr kann man eine Funktion aufrufen und gleichzeitig bestimmen, auf welches Objekt das this-Schlüsselwort während des Aufrufs zeigen soll. Die Anwendung von call ist in Listing 3 [7] am Konstruktor und der überschriebenen getName-Methode zu sehen. In beiden Fällen wird die aktuelle Instanz an die aufgerufene Funktion durchgereicht, indem sie als erster Parameter an call übergeben wird.

Von welchem Typ ein Objekt ist

Ein Punkt, der bisher unberücksichtigt blieb, ist die Frage nach Typinformationen. Oft muss man herausfinden können, ob ein konkretes Objekt eine Instanz einer bestimmten Klasse ist (oder allgemeiner: von einem bestimmten Typ). Generelle Typinformationen erhält man in Javascript mit dem typeof-Operator. typeof 5 liefert beispielsweise den String number. Allerdings lässt sich typeof nicht für Klassenhierarchien verwenden, denn es liefert für Objekte immer nur object zurück – gleichgültig, welche Konstruktor-Funktion sie erzeugt hat.

Generell gibt es in Javascript leider keine Möglichkeit, einem Objekt seine Klasse anzusehen. Es gibt zwar Konstruktionen, die mit dem constructor-Attribut arbeiten, das beim Erzeugen eines Objekts automatisch gesetzt wird, aber die versagen spätestens, wenn Namensräume zum Einsatz kommen (siehe unten) oder der Konstruktor aus einem anderen Grund nicht mehr dem Schema function <Name>(<Parameter>) entspricht.

Was allerdings funktioniert, ist der instanceof-Operator. Mit ihm lässt sich feststellen, ob der Wert des prototype-Attributs einer Funktion in der Prototyp-Kette eines Objekts vorkommt. Klingt kompliziert, bedeutet aber in der Praxis nichts anderes, als dass man feststellen kann, ob ein Objekt zu einer Klasse oder einer ihrer Subklassen gehört (siehe Listing 4 [8]).


Kapselung mit Closures

Die bisher vorgestellten Konzepte erlauben schon ein weitgehend objektorientiertes Arbeiten. Allerdings ist ein wichtiges Kernprinzip noch nicht berücksichtigt: Die Kapselung (Information Hiding). Von Haus aus kann Javascript nicht die Sichtbarkeit auf bestimmte Attribute einschränken. Weist man einem Objekt ein Attribut zu, ist es für alle lesbar und veränderbar. Um bei komplexen Objekten ein definiertes Verhalten garantieren zu können, ist eine Kapselung bestimmter Daten jedoch wichtig. Man möchte eine API definieren können, über die das Objekt benutzbar ist, während jeder weitere Zugriff technisch verboten sein soll.

Auch wenn oft Gegenteiliges behauptet wird, lässt sich dieses Prinzip in Javascript umsetzen, und zwar mit den sogenannten Closures. Eine Closure ist eine Verbindung zwischen einer Funktion und dem Kontext, der sie erzeugt hat. Konkret bedeutet das: Wenn innerhalb einer Funktion eine weitere Funktion definiert wird, ist der lokale Kontext der äußeren Funktion in der inneren zugänglich, selbst wenn die äußere schon verlassen wurde. Listing 5 [9] zeigt dies: Die Funktion outer definiert eine lokale Variable x. Außerdem weist sie der globalen Variablen inner eine Funktion zu. Diese kann selbst dann noch auf x zugreifen, wenn outer schon lange verlassen wurde. Dieses Prinzip funktioniert auch mit noch weiter verschachtelten Funktionsdefinitionen: Eine innere Funktion kann auf alle außerhalb liegenden Kontexte zugreifen.

Mit den gerade vorgestellten Closures kann man eine echte Kapselung erreichen. Dazu werden Variablen, die privat sein sollen, als lokale Variablen im Konstruktor definiert. Alle Methoden, die auf diese Variablen zugreifen, werden ebenfalls im Konstruktor definiert. Durch das Closure-Prinzip können sie – und nur sie – auch nach Verlassen des Konstruktors auf die privaten Variablen zugreifen (weitere Details bietet David Crockford, siehe Onlineressourcen [10]).

Listing 6 [11] zeigt dieses Verfahren: Die Parameter und lokalen Variablen des Konstruktors sind im gleichen Kontext definiert, wie die Methoden, die darauf zugreifen. Damit sind sie für die Methoden sichtbar, nach außen hin jedoch sind sie gekapselt.

Closures machen auch das Überschreiben von Methoden eleganter. Statt umständlich den Prototyp der Superklasse zu bemühen, kann man sich nun die Supermethode in einer privaten Variablen merken und aufrufen (siehe Listing 7 [12]).

Analog zu privaten Membervariablen lassen sich private Methoden umsetzen: Dazu definiert man eine Funktion und weist sie einer lokalen Variable des Konstruktors zu. Durch die Closure können alle im Konstruktor definierten Methoden auf die private Methode zugreifen, wie Listing 8 [13] zeigt. Dabei ist jedoch eines zu beachten: Private Methoden werden direkt aufgerufen und nicht mit dem Punkt-Operator auf einem bestimmten Objekt. Dadurch ist die implizite Variable this nicht gesetzt (sie zeigt auf das globale window-Objekt).

Dieses Problem lässt sich jedoch leicht umgehen, indem man die Referenz auf die eigene Instanz in einer privaten Variablen hält. Damit ist die Instanz in allen Methoden – inklusive der privaten – zugänglich. In Anlehnung an Smalltalk bezeichnet man eine solche Variable üblicherweise mit self. Konsequenterweise sollten Programmierer überall statt this die neue Variable self nutzen. Einziger Nachteil: self ist in Javascript außerdem ein Alias für das aktuelle Fenster (window), den die lokale self-Variable versteckt. In den Methoden ist es daher nicht mehr möglich, auf das aktuelle Fenster mit self zuzugreifen – man muss window verwenden (was sowieso der allgemein übliche Weg ist).

Mit Closures lassen sich öffentliche und private Attribute realisieren. In vielen Fällen ist eine private Sichtbarkeit jedoch zu viel des Guten – oft möchte man zwar eine saubere Kapselung zu den Benutzern einer Klasse hin erreichen, abgeleiteten Klassen jedoch trotzdem Zugriff auf die internen Methoden und Variablen gewähren. Man wünscht sich eine Sichtbarkeit, die dem protected in Java oder in C++ entspricht.

Für ein protected-Äquivalent kommen einem wieder die Closures zugute. Die Grundidee ist, in jedem Konstruktor der Vererbungskette eine private Variable zu definieren, aber mit dem Kniff, dass alle diese Variablen auf das gleiche Objekt verweisen. Dies kann als Container dienen, um Variablen und Methoden aufzunehmen. Dadurch, dass die verweisenden Variablen privat sind, ist das protected-Objekt außerhalb der Hierarchie nicht zugänglich.

Bleibt noch die Frage, wie sich die Konstruktoren das gleiche Objekt teilen können. Dazu kann man sich die Eigenart zunutze machen, dass man im Konstruktor einer Unterklasse explizit den Konstruktor der Oberklasse aufrufen muss. Indem man nun die Konstruktor-Funktionen einen Wert zurückgeben lässt, hat man einen Kommunikationsweg geschaffen: Der oberste Konstruktor kann ein Objekt erzeugen und es als Rückgabewert liefern. Der nächste Konstruktor in der Kette merkt es sich in einer lokalen Variablen und gibt es seinerseits zurück, um es an die nächste Unterklasse weiterzureichen (siehe Listing 9 [14]).

Die einzige Schwierigkeit ist, dass der Konstruktor das protected-Objekt nur zurückgeben darf, wenn er von einer Subklasse aus aufgerufen wurde – sonst würde die Erzeugung neuer Instanzen mit dem new-Operator nicht mehr korrekt funktionieren. Diesem Zweck dient in Listing 9 [15] ein Zähler, der für jeden Aufruf in der Konstruktor-Kette erhöht wird. Nur wenn der Zähler auf 0 steht (wenn der Konstruktor-Aufruf von außerhalb der Hierarchie erfolgt) wird kein protected-Objekt zurückgegeben. Dieses Verfahren ist etwas umständlich und nicht ganz wasserdicht, das heißt es ist auf Umwegen möglich, von außen an das protected-Objekt zu gelangen. Eine saubere Lösung lässt sich nur erreichen, indem man die Klassendefinition in eine Hilfsfunktion auslagert, die dem Entwickler die Verwaltungsarbeit abnimmt und das protected-Objekt über eine weitere Closure zuverlässig schützt. Dieser oder ähnliche Ansätze dürften aber über kurz oder lang in den verbreiteten Javascript-Frameworks auftauchen, sodass man sich als Anwendungsentwickler nicht darum zu kümmern braucht.

Closures ermöglichen private- und protected-Sichtbarkeit in Javascript-Klassen. Sie vervollständigen damit die objektorientierten Eigenschaften der Sprache, und man ist nicht mehr auf halbgare Kompromisse wie Namenskonventionen angewiesen. Ein Nachteil sei allerdings nicht verschwiegen: Durch die Definition sämtlicher Methoden im Konstruktor existieren Kopien davon in jeder einzelnen Instanz. Die Methoden müssen aber im Konstruktor definiert sein, sonst erhält man keine Closure. In der Praxis ergeben sich zwar nur bei großen Klassen Schwierigkeiten, und das erst dann, wenn man es mit einer mindestens vierstelligen Anzahl an Instanzen zu tun hat. Dennoch empfiehlt es sich, für Massenobjekte die weiter oben beschriebenen Prototyp-Methoden zu verwenden, die nur einmal pro Klasse Speicher belegen.

Natürlich lassen sich der Closure- und der Prototyp-Ansatz in einer Hierarchie kombinieren. Man kann beispielsweise an der Spitze einer umfangreichen Vererbungskette mit dem Prototyp arbeiten und erst in Unterklassen, die nicht massenhaft instanziiert werden, die Kapselung mit Closures einführen. Die Unterklassen können dabei schlicht auf die (Prototyp-)Methoden der Oberklassen zugreifen.


Namensräume und Pakete

Eine weitere wichtige Eigenschaft objektorientierter Umgebungen sind Namensräume beziehungsweise Pakete, mit denen sich thematisch verwandte Klassen zusammenfassen lassen. Außerdem kann man damit Namenskollisionen vermeiden, da der Name einer Klasse (oder eines anderen Sprachkonstrukts) nur innerhalb eines Namensraums oder Pakets eindeutig sein muss.

In Javascript lässt sich das Konzept einfach durch Objekte abbilden (siehe Listing 10 [16]). Dazu dient eine Hierarchie von Objekten, die bei de beginnt und mit dem Punkt-Operator traversiert werden kann (beispielsweise de.heise.ix.test). Das Listing prüft für jede Hierarchieebene, ob sie schon existiert (etwa weil sie bei der Erzeugung einer anderen Klasse angelegt wurde) – wenn nicht, legt es sie an. Eine Ausnahme bildet die oberste Ebene: Hier sollte eine Abfrage der Form if (de) genügen, aber Javascript erzeugt einen Fehler, wenn de noch nicht definiert ist. Das lässt sich mit einem Zugriff auf window.de umgehen (statt direkt auf de). window ist in Javascript (zumindest im Browser) die Wurzel für alle Namensauflösungen, und de und window.de sind normalerweise äquivalent – bis auf den Fall, dass de noch nicht definiert ist.

Die länglichen vollqualifizierten Namen wie de.heise.ix.test.Person lassen sich abkürzen (ähnlich import in Java), indem man eine Klasse (oder genauer: einen Konstruktor) einer Variablen zuweist:

var Person = de.heise.ix.test.Person;

Auch Teilpakete lassen sich auf diese Weise abkürzen:

var testPackage = de.heise.ix.test;
var emp = new testPackage.Employee(...);

Ein weiterer wichtiger Punkt ist, gerade bei komplexen Anwendungen, die Beschreibung von Schnittstellen. Dazu gibt es mittlerweile auch für Javascript Generatoren für API-Dokumentation, die vergleichbar mit JavaDoc oder Doxygen arbeiten. Verbreitet ist hier JSDoc, das in Perl geschrieben ist und bei der Analyse des Quellcodes weitgehend der JavaDoc-Syntax folgt. Listing 11 [17] zeigt einen Dokumentationskommentar.

Ausblick: Framework-Unterstützung

Die beschriebenen Techniken ermöglichen in der Summe ein wartbares, objektorientiertes Arbeiten in Javascript. Allerdings ist es teilweise recht mühsam, sie alle konsequent umzusetzen. In Javascript 2.0 soll die Unterstützung für Objektorientierung zwar stark verbessert sein – es dürfte jedoch noch einige Zeit dauern, bis der neue Standard definiert ist, und es wird noch länger dauern, bis er in allen gängigen Browsern so weit verbreitet ist, dass man Webanwendungen darauf aufsetzen kann.

Schon in heutigen Anwendungen erhält man allerdings Unterstützung durch Frameworks und Hilfsklassen wie Dean Edwards Base (siehe Onlinequellen [18]), die beispielsweise den Code für eine Vererbungsbeziehung hinter simplen Aufrufen wie object.extend() verbergen. Mit Framework-Hilfe lässt sich aber noch mehr erreichen: Objekte kann man beispielsweise mit Metainformationen anreichern, sodass man sie nach ihrem Klassennamen fragen kann. Ebenfalls nützlich sind Properties, wie sie das qooxdoo-Framework bietet (siehe den genannten Kasten). Damit lassen sich Attribute mit automatisch generierten Getter- und Setter-Methoden anlegen, die außerdem weiteren Komfort wie automatische Validierung und ein Listener-Konzept für die Reaktion auf Wertänderungen anbieten.

Noch ist die Javascript-Framework-Landschaft in ständiger Veränderung begriffen – es haben sich weder ein Framework noch eine Bibliothek als Standard durchgesetzt, sodass man als Anwendungsentwickler die Qual der Wahl hat. Allerdings bekommt man fast immer Unterstützung bei der objektorientierten Entwicklung, ganz gleich, auf welcher Basis man aufsetzt. Und Techniken wie die oben beschriebene Kapselung funktionieren, ohne explizite Unterstützung, in fast jedem Framework (Ausnahme: protected-Attribute, denn hier muss das OO-Konzept des Frameworks mitspielen). Es sind daher nicht nur die Frameworks, die dem Entwickler den Alltag erleichtern; geeignete Code-Muster tragen dazu bei, dass komplexer Code auf Dauer wartbar bleibt.

Eines ist sicher: Javascript wird allmählich erwachsen, und es entstehen ernsthafte und umfangreiche Anwendungen damit. Die Sprache bietet die Chance, objektorientiert und sauber strukturiert zu arbeiten, aber man muss sie wahrnehmen. Frameworks und Bibliotheken geben hierbei wertvolle Hilfestellung. Ein späterer Artikel soll anhand des qooxdoo-Framework zeigen, wie man fortgeschrittene Objektorientierung mit einem konkreten Framework erreicht.

Andreas Junghans und Til Schneider
entwickeln beim STZ-IDA in Karlsruhe Ajax-basierte Web-Anwendungen im Auftrag der PTV AG.


[19]Listing 1: Einfache Objekte

function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getName = function() {
return this.firstName + " " + this.lastName;
};
}
var testPerson = new Person("Max", "Muster");
alert(testPerson.getName());

Listing 2: Methoden per Prototyp

function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.getName = function() {
return this.firstName + " " + this.lastName;
};
var testPerson = new Person("Max", "Muster");
alert(testPerson.getName());

Listing 3: Vererbung per Prototyp

// Definition von "Person" wie in Listing 2
function Employee(firstName, lastName, department) {
Person.call(this, firstName, lastName);
this.department = department;
};
Employee.prototype = new Person();
Employee.prototype.getName = function() {
return Person.prototype.getName.call(this) +
" (" + this.department + ")";
};
Employee.prototype.alertName = function() {
alert(this.getName());
};
var max = new Employee("Max", "Muster", "Sales");
max.alertName();

[20]Listing 4: Der instanceof-Operator

var toni = new Person("Toni", "Mustermann");
var max = new Employee("Max", "Muster", "Sales");
alert(toni instanceof Person); // true
alert(toni instanceof Employee); // false
alert(max instanceof Person); // true
alert(max instanceof Employee); // true

Listing 5: Closures

var inner;
function outer() {
var x = 5;
inner = function() {
x++;
alert(x);
}
}
// Erzeugen einer Closure, die x enthält
outer();
// Funktion aufrufen, die auf die Closure zugreift
inner(); // Ausgabe: "6"
inner(); // Ausgabe: "7"

Listing 6: Kapselung mit Closures

function Person(firstName, lastName) {
var title;
this.setTitle = function(t) {
title = t;
}
this.getName = function() {
return title + " " + firstName + " " + lastName;
};
}
var testPerson = new Person("Max", "Muster");
testPerson.setTitle("Dr.");
alert(testPerson.getName()); // Ausgabe: "Dr. Max Muster"

[21]Listing 7: Überschreiben mit Merker-Variable

// Definition von "Person" wie in Listing 6
function Employee(firstName, lastName, department) {
Person.call(this, firstName, lastName)
var superGetName = this.getName;
this.getName = function() {
return superGetName.call(this) + " (" + department + ")";
};
}
var Temp = function() {};
Temp.prototype = Person.prototype;
Employee.prototype = new Temp();

Listing 8: Private Methoden und die self-Variable

function Person(firstName, lastName) {
var self = this;
self.getName = function() {
return firstName + " " + lastName;
};
var alertName = function() {
alert(self.getName());
};
self.showDebugInfo = function() {
alertName();
};
}

[22]Listing 9: Protected

function Person(firstName, lastName) {
var prot = {};
var self = this;
prot.aProtectedMethod = function() {
alert("Protected method");
};
if (self._protCounter > 0) {
return prot;
}
}
function Employee(firstName, lastName, department) {
var self = this;
self._protCounter = (self._protCounter ? self._protCounter + 1 : 1);
var prot = Person.call(this, firstName, lastName);
self._protCounter--;
self.getName = function() {
prot.aProtectedMethod();
return firstName + " " + lastName;
};
if (self._protCounter > 0) {
return prot;
}
}
var Temp = function() {};
Temp.prototype = Person.prototype;
Employee.prototype = new Temp();

[23]Listing 10: Namensräume/Pakete

if (!window.de) window.de = {};
if (!de.heise) de.heise = {};
if (!de.heise.ix) de.heise.ix = {};
if (!de.heise.ix.test) de.heise.ix.test = {};
de.heise.ix.test.Person = function(firstName, lastName) {
// Implementierung siehe vorheriges Listing
}
de.heise.ix.test.Employee = function (firstName, lastName, department) {
// Implementierung siehe vorheriges Listing
}
var Temp = function() {};
Temp.prototype = de.heise.ix.test.Person.prototype;
de.heise.ix.test.Employee.prototype = new Temp();
var max = new de.heise.ix.test.Employee("Max", "Muster", "Sales");

[24]Listing 11: Dokumentations-Kommentar

/**
* Constructor for a Person object.
* @param {String} firstName the person's first name.
* @param {String} lastName the persons's last name.
*/
de.heise.ix.test.Person = function(firstName, lastName) {
// ...
}

[25]Onlinequellen

Members in JavaScript (David Crockford) [26]

Base Class for JavaScript Inheritance (Dean Edwards) [27]

Javascript-GUI-Framework qooxdoo [28]


URL dieses Artikels:
http://www.heise.de/-227076

Links in diesem Artikel:
[1] https://www.heise.de/developer/artikel/Listing-1-3-227498.html#L1
[2] https://www.heise.de/developer/artikel/Listing-1-3-227498.html#L1
[3] https://www.heise.de/developer/artikel/Listing-1-3-227498.html#L1
[4] https://www.heise.de/developer/artikel/Listing-1-3-227498.html#L1
[5] https://www.heise.de/developer/artikel/Listing-1-3-227498.html#L1
[6] https://www.heise.de/developer/artikel/Listing-1-3-227498.html#L1
[7] https://www.heise.de/developer/artikel/Listing-1-3-227498.html#L1
[8] https://www.heise.de/developer/artikel/Listing-4-6-227500.html#L4
[9] https://www.heise.de/developer/artikel/Listing-4-6-227500.html#L4
[10] https://www.heise.de/developer/artikel/Onlinequellen-227504.html#OQ
[11] https://www.heise.de/developer/artikel/Listing-4-6-227500.html#L4
[12] https://www.heise.de/developer/artikel/Listing-7-11-227502.html#L7
[13] https://www.heise.de/developer/artikel/Listing-7-11-227502.html#L7
[14] https://www.heise.de/developer/artikel/Listing-7-11-227502.html#L9
[15] https://www.heise.de/developer/artikel/Listing-7-11-227502.html#L9
[16] https://www.heise.de/developer/artikel/Listing-7-11-227502.html#L10
[17] https://www.heise.de/developer/artikel/Listing-7-11-227502.html#L11
[18] https://www.heise.de/developer/artikel/Onlinequellen-227504.html#OQ
[19] 
[20] 
[21] 
[22] 
[23] 
[24] 
[25] 
[26] http://www.crockford.com/javascript/private.htmlPrivate
[27] http://dean.edwards.name/weblog/2006/03/base/A
[28] http://qooxdoo.org/