Über die prototype Eigenschaft lässt sich eine Objekt - Hierarchie
aufbauen, soll beispielsweise eine KlasseB alle Eigenschaften von KlasseA erben
so geschieht dies mittels KlasseB.prototype = new KlasseA(); Klasse
B ist dann eine Subklasse von KlasseA.
Nehmen wir das Beispiel einer Fahrzeug Klasse diese besitzt eine Eigenschaft
Geschwindigkeit und Methoden um zu beschleunigen bzw. abzubremsen. Eine Subklasse
davon ist in unserem Fall ein Auto, es könnte aber genau so gut ein Zug, Schiff,
oder Flugzeug sein. Im Weiteren haben wir noch eine Cabriolet Klasse welche
von Auto abgeleitet wird. Dabei spricht man auch von einem "ist ein" - Verhältnis,
denn ein Cabriolet ist ein Auto und ein Auto ist ein Fahrzeug. Bei der Vererbung
wird die constructor Eigenschaft überschrieben, deshalb wird diese anschliessend
wieder richtig gesetzt, andernfalls würde beispielsweise der constructor
einer Cabriolet Instanz auf Car zeigen...
Vehicle = function(){
}
Vehicle.prototype.speed = 0;
Vehicle.prototype.speedup = function(){
this.speed += 10;
}
Vehicle.prototype.slowdown = function(){
if(this.speed > 0){
this.speed -= 10;
}
}
Car = function(){
}
Car.prototype = new Vehicle();
Car.prototype.constructor = Car;
Car.prototype.direction = "strait";
Car.prototype.turnleft = function(){
this.direction = "left";
}
Car.prototype.turnright = function(){
this.direction = "right";
}
Cabriolet = function(){
}
Cabriolet.prototype = new Car();
Cabriolet.prototype.constructor = Cabriolet;
Cabriolet.prototype.cover = "closed";
Cabriolet.prototype.open = function(){
this.cover = "opened";
}
Cabriolet.prototype.close = function(){
this.cover = "closed";
}
myCar = new Car();
/*************************************************
now myCar includes the following propertys
> speed
> speedup()
> slowdown()
> direction
> turnleft()
> turnright()
*************************************************/
myCabriolet = new Cabriolet();
/*************************************************
now myCabriolet includes the following propertys
> speed
> speedup()
> slowdown()
> direction
> turnleft()
> turnright()
> cover
> open()
> close()
*************************************************/
Beim Vererbungsvorgang wird eine Instanz der Elternklasse im Prototypen der Kindklasse erstellt. Dies geschieht jedoch nur dieses eine Mal und nicht jedes Mal wenn eine neue Instanz erstellt wird. Das kann beim Arbeiten mit Komplexen Datentypen (Arrays, Objekte) zu gewollten bzw. ungewollten Nebeneffekten führen:
Car = function(){
}
Car.prototype.trunk = [];
Cabriolet = function(){
}
Cabriolet.prototype = new Car();
Cabriolet.prototype.constructor = Cabriolet;
Cabriolet.prototype.addItemToTrunk = function(name){
this.trunk[this.trunk.length] = name;
}
myCabriolet = new Cabriolet();
yourCabriolet = new Cabriolet();
alert(myCabriolet.trunk == yourCabriolet.trunk) // true // same reference
myCabriolet.addItemToTrunk("Shopping bag");
alert(myCabriolet.trunk) // ["Shopping bag"]
alert(yourCabriolet.trunk) // ["Shopping bag"]
yourCabriolet.addItemToTrunk("Another bag")
alert(myCabriolet.trunk) // ["Shopping bag","Another bag"]
alert(yourCabriolet.trunk) // ["Shopping bag","Another bag"]
Der Car - Konstruktor wird nur ein einziges Mal ausgeführt, deshalb referenziert jede Instanz auf das selbe Array. Allfällige Änderungen haben somit Auswirkungen auf alle Instanzen. Was wir hier haben ist also gewissermassen eine statische Eigenschaft. Ist dieses Verhalten nicht erwünscht so sollte man sich eine Initalisierungs - Methode schreiben und diese jeweils am Anfang des Konstruktors aufrufen. Dadurch wird das Array in jeder Instanz hinterlegt.
Car = function(){
this.init();
}
Car.prototype.init = function(){
this.trunk = [];
}
Cabriolet = function(){
this.init(); // always before any other operation
}
Cabriolet.prototype = new Car();
Cabriolet.prototype.constructor = Cabriolet;
Cabriolet.prototype.addItemToTrunk = function(name){
this.trunk[this.trunk.length] = name;
}
myCabriolet = new Cabriolet();
yourCabriolet = new Cabriolet();
alert(myCabriolet.trunk == yourCabriolet.trunk) // false
myCabriolet.addItemToTrunk("Shopping bag");
alert(myCabriolet.trunk) // ["Shopping bag"]
alert(yourCabriolet.trunk) // []
yourCabriolet.addItemToTrunk("Another bag")
alert(myCabriolet.trunk) // ["Shopping bag"]
alert(yourCabriolet.trunk) // ["Another bag"]
Soweit so gut, doch was geschieht wenn wir eine weitere Klasse von Cabriolet
ableiteten wollen? Die Methode init() würde dann bereits im Prototypen
gefunden und das resultiert in einer Endlosschleife, die init()
Methode der Elternklasse würde nie ausgeführt werden. Was wir also brauchen,
ist eine Eigenschaft die auf die jeweilige Elternklasse (super in JAVA) zeigt,
mit ihr könnten wir dann die richtige init() Methode aufrufen.
Solch eine Eigenschaft gibt es in JavaScript noch nicht, super
ist zwar ein reserviertes Schlüsselwort doch es wird bislang noch nicht verwendet.
Wir werden uns diese Eigenschaft welche auf den Prototypen
der Elternklasse zeigt also selber anlegen müssen.
Jede Funktion in JavaScript ist ebenfalls ein Objekt
und besitzt Methoden wie call()
und apply() mit Ihrer
Hilfe werden die Parameter an die Elternklasse weitergegeben, dabei wird this
auf das aktuelle Objekt gesetzt. Dass wiederum hat zur Folge, dass wir keine
Instanz - Eigenschaft verwenden können, den dies würde wieder zu einer Endlosschleife
führen, deshalb weichen wir auf eine Klassen
- Eigenschaft aus.
Vehicle = function(maxspeed){
this.init(maxspeed);
}
Vehicle.prototype.speed = 0;
Vehicle.prototype.init = function(maxspeed){
this.maxspeed = maxspeed;
}
Vehicle.prototype.speedup = function(){
this.speed += 10;
}
Vehicle.prototype.slowdown = function(){
if(this.speed > 0){
this.speed -= 10;
}
}
Car = function(model,maxspeed){
this.init(model,maxspeed);
}
Car.prototype = new Vehicle();
Car.prototype.constructor = Car;
Car.parent = Vehicle.prototype; // our "super" property
Car.prototype.direction = "strait";
Car.prototype.init = function(model,maxspeed){
this.model = model;
Car.parent.init.call(this,maxspeed);
}
Car.prototype.turnleft = function(){
this.direction = "left";
}
Car.prototype.turnright = function(){
this.direction = "right";
}
Cabriolet = function(cover,model,maxspeed){
this.init(cover,model,maxspeed);
}
Cabriolet.prototype = new Car();
Cabriolet.prototype.constructor = Cabriolet;
Cabriolet.parent = Car.prototype; // our "super" property
Cabriolet.prototype.init = function(cover,model,maxspeed){
this.cover = cover;
Cabriolet.parent.init.call(this,model,maxspeed);
}
Cabriolet.prototype.open = function(){
this.cover = "opened";
}
Cabriolet.prototype.close = function(){
this.cover = "closed";
}
myCabriolet = new Cabriolet("closed","BMW",250);
alert(myCabriolet.maxspeed); // 250
alert(myCabriolet.model); // "BMW"
alert(myCabriolet.cover); // "closed"
myCabriolet.speedup();
myCabriolet.turnleft();
myCabriolet.open();
alert(myCabriolet.speed); // 10
alert(myCabriolet.direction); // "left"
alert(myCabriolet.cover); // "opened"
Da jede Konstruktor Funktion nur eine Prototype - Eigenschaft besitzt, ist wirkliche mehrfach Vererbung nicht möglich. Man kann sehr wohl Eigenschaften von einem Prototypen in einen anderen kopieren, fügt man jedoch später der Basisklasse eine neue Eigenschaft hinzu so steht diese der abgeleiteten Klasse nicht zur Verfügung.
Function.prototype.inheritFrom = function(object){
for(var property in object.prototype){
this.prototype[property] = object.prototype[property];
}
}
ClassA = function(){
}
ClassA.prototype.MethodA = function(){
}
ClassB = function(){
}
ClassB.prototype.MethodB = function(){
}
ClassC = function(){
}
ClassC.inheritFrom(ClassA);
ClassC.inheritFrom(ClassB);
ClassC.prototype.MethodC = function(){
}
myInstance = new ClassC();
alert(myInstance.MethodA != undefined) // true
alert(myInstance.MethodB != undefined) // true
alert(myInstance.MethodC != undefined) // true
ClassA.prototype.MethodD = function(){
}
alert(myInstance.MethodD) // undefined