OOP in JavaScript

Vererbung

Ü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()
*************************************************/

Statische Eigenschaften

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"]

Zugriff auf Elternklasse

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"

Mehrfach Vererbung

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