Javascript ist Toll!

Bibliotheken > Javascript

8. Februar 2010 - 11:23

OOP in Javascript – Teil 1

Mein erster Versuch hier einen Artikel über Vererbung in JS zu schreiben, scheiterte an einem Punkt den ich übersehen hatte. Daher hier eine neue zweite Version. Ich werde den Artikel aber in drei Teile aufsplitten.

  1. Teil 1. - OOP in Javascript
  2. Teil 2. - MOC my Object Creator
  3. Teil 3. - Vergleiche und Benchmarks

OOP in Javascript

In Javascript gibt es an sich keine Klassen, keine Vererbung, keine privaten Eigenschaften. All die Dinge, die eine Objektorientierte Programmierung ausmachen. Trotzdem wird Javascript als OO Sprache bezeichnet. Warum?

Der Konstruktor

Der Grund dafür ist, dass Objekte existieren und diese um Eigenschaften und Methoden erweitert werden können. Entscheidend ist aber das es möglich ist neue Objekte zu erzeugen. Das Schlüsselwort new KonstrukorFunktion() erzeugt ein neues (Funktions)Objekt. Das ist trivial und funktioniert so:

javascript
function A() {
this.prop = 'hallo';
this.func = function() {alert(this.prop);};
}
var obj = new A();
obj.func();

Eine Funktion, die ein Objekt erzeugt heißt Konstruktorfunktion. Die aber im Grunde keinen Unterschied mit einer herkömmlichen Funktion hat. Lediglich die Bedeutung des Schlüsselwort this innerhalb der Funktion ist eine andere. In Javascript ist this immer der Kontext aus dem eine Funktion aufgerufen wurde. Ein Aufruf einer globalen func() entspricht genau genommen window.func(). Das bedeutet, in der Funktion ist window der Kontext und entspricht dem Wert von this.

Bei einer Konstruktorfunktion, die mit new func() aufgerufen wird, ist der Kontext im Prinzip new. Es wird also ein Objekt erzeugt und this zugewiesen. Die Funktion A() in dem Beispiel, wird dann in diesem Kontext des neu erzeugten Objektes aufgerufen. Dabei werden dem Objekt über this, zwei Eigenschaften hinzugefügt. Eine Zeichenkette und eine Funktion. Jedes neu erzeugte Objekt A() erhält diese Eigenschaften. Da die Zuweisung jedesmal auf's neue zur Laufzeit erfolgt, sollte diese Methode um dem Objekt Eigenschaften zu zuweisen, sparsam verwendet werden - sie kostet viel Zeit. Stattdessen ist es sinnvoller, wenn möglich, Funktionen und Eigenschaften über das prototype Objekt zu deklarieren. Doch das läßt sich nicht immer realisieren, wie im nachfolgenden Beispiel.

private Eigenschaften

Das führt uns zum ersten Trick. Um in Javascript private Eigenschaften zu simulieren, werden diese in der Konstruktorfunktion mit var deklariert. Dadurch sind diese nur lokal sichtbar und können auch nur von den in der Konstruktorfunktion definierten Funktionen benutzt werden. Diese Funktionen sind im OOP Sprachgebrauch privilegierte Funktionen, da sie auf private Eigenschaften zugreifen dürfen.

Ein Beispiel für private Eigenschaften
javascript
function A() {
var privat = 'x';
this.privilegiert = function() {return privat;};
}
var obj = new A();
alert(obj.privilegiert()); // 'x'

Nach dem gleichen Prinzip lasen sich auch private Funktionen erzeugen, wenn eine Funktion lokal in der Funktion erzeugt wird, ist diese nach aussen nicht sichtbar.

Vererbung - der klassische Weg

Der klassiche Weg um in Javascript eine Vererbungshierachie umzusetzen funktioniert aber in so einem Fall nicht mehr und muss vermieden werden. Der Weg sähe in etwa so aus:

javascript
function A() {}
function B() {}
B.prototype = new A(); // B erbt von A

prototype ist eine Art Vorlage oder Template, auf die alle Instanzen des Objektes zugreifen können. Das ist eine prototypische Vererbung.

Das Problem

Das Problem mit dieser Art der Vererbung ist, dass der Konstruktor von A() bei der Zuweisung aufgerufen wird.

Dadurch wird ein Objekt A() zu einem Zeitpunkt erzeugt, wo es noch nicht gebraucht wird und manchmal auch stört. Z.b. wenn die Anzahl der Objekte die erzeugt gezählt werden sollen.

Ausserdem funktioniert der Trick mit den privaten Variabeln nicht mehr, da mit dieser Methode alle Objekte die gleichen privaten Variabeln haben. Ganz besonders aufgepaßt werden muss hier, dass das Objekt A in allen abgeleiteten Instanzen identisch ist, was, wenn man es nicht weiß, zu seltsamen Verhalten und schwer zu findenen Fehlern führen kann.

Ein Beispiel für die klassische Vererbung

Um das Problem zu zeigen hat das Objekt A() ein privates Attribut und ein Paar setter/getter Funktionen. Bei der Zuweisung des Prototypobjekts wird der Konstruktor aufgerufen, nun hat privat in allen Instanzen von B() den gleichen Wert.

javascript
function A() {
	var privat = 'x';
	this.set = function(p) {if(p) privat = p};
	this.get = function() {return privat};
}
function B() {}
B.prototype = new A(); // B erbt von A
 
var a = new B();
var b = new B();
a.set(1);
b.set(2);
 
alert(a.get()); // => 2
alert(b.get()); // => 2 !

Wegen diesem Problemen enstand der Wunsch, eine eigene Möglichkeit der Vererbung in JS einzuführen, wie es auch in vielen sehr interessanten Artikel im Internet beschrieben wird.

Der falsche Weg

Mein erster Versuch einer Lösung scheiterte an einem besonderen Verhalten des Objektes prototype, dass einerseits vermutlich ziemlich genial ist, wie vieles an JS, aber anderseits diese einfache Lösung verhindert.

Meine Idee war es, nur die prototype Objekte zu zuweisen und im Konstruktor dann mit .apply(this) den Kontext für die privaten Eigenschaften herzustellen. Diese Art der Vererbung klappt eigentlich relativ gut.

Ein Beispiel

Das Objekt A() hat die private Eigenschaft und die Methoden von dem Beispiel oben und noch eine prototypische Methode (diese dient später dazu das Problem bei dieser Art der Vererbung zu erkläutern).

javascript
function A() {
	var privat = 'x';
	this.set = function(p) {if(p) privat = p};
	this.get = function() {return privat};
}
A.prototype.a = function() { return 'A.prototype.a'; }
 
function B() {
	A.apply(this, arguments)
}
B.prototype = A.prototype; // B erbt von A
 
// und eine prototypische Funktion, s.u.
B.prototype.b = function() {return 'B.prototype.b';}
 
var a = new B();
var b = new B();
a.set(1);
b.set(2);
alert( a.get());
alert( b.get());

Der Konstruktor A() wird hier tatsächlich nur so oft aufgerufen, wie er soll, nämlich insgesamt zweimal. Die private Eigenschaften bleiben bei der Instanz erhalten und auch die prototype Eigenschaften werden vererbt. Eigentlich alles wunderbar, doch eine besondere Eigenschaft von prototype habe ich übersehen.

Im Grunde steckt hinter den ganzen Techniken von JS keinerlei besondere Magie, wir haben es hier mit Objekten und Funktionsreferenzen zu tun. Techniken, die unter JS weit verbreitet sind und die man auch beherschen sollte. Das einzige Objekt, das eine gewisse Magie enthält, ist das prototype Objekt. Normalerweise dient es zur Erzeugung der prototype-chain, der Prototypkette, an der sich entlang gehangelt wird, um Funktionen in der Vererbungshierachie zu finden. Eine besondere Eigenschaft ist, dass jede Eigenschaft/Funktion die dem prototype Objekt zugewiesen wird, sofort allen Instanzen zu Verfügung steht (das ist logisch, wenn man sich klar macht, dass prototype einfach nur eine Eigenschaft ist, in der Javascript nachschaut, ob eine Funktion existiert - wie gesagt, keine Magie). Das ist die geniale Eigenschaft, aber dummerweise gilt diese Regel auch umgekehrt, A erbt auch von B!

Beispiel für A.prototype = B.prototype und umgekehrt
javascript
function A() {
	var privat = 'x';
	this.set = function(p) {if(p) privat = p};
	this.get = function() {return privat};
}
A.prototype.a = function() { return 'A.prototype.a'; }
 
function B() {
	A.apply(this, arguments)
}
B.prototype = A.prototype; // B erbt von A
 
B.prototype.b = function() {return 'B.prototype.b';}
 
var a = new A();
 
alert( a.b());

Der Aufruf a.b(), der eigentlich einen Fehler erzeugen müßte, gibt ein B.prototype.b aus. Aus diesem Grund war der erste Entwurf dieses Artikels fehlerhaft. So schön diese simple Art der Vererbung auch ist, sie führt nicht zum richtigen Ziel.

Zum Ziel führt hier nur eine Erweiterung des Function.prototype Objekt bzw. eine Helperfunktion. Denn um alle Bedingungen, die die Vererbung erfüllen soll, umzusetzen, sind ein paar Verrenkungen nötig.

Bedingungen

Folgende Bedingungen sollte eine Vererbung erfüllen.

Die Umsetzung des Zieles erläutere ich in der 2. Folge. Und jetzt abschließend noch ein paar wirklich weiterführende Links, mit interessanten Artikeln zum Thema.

Links

Object Hierarchy and Inheritance in JavaScript
hier wird beschrieben, wie die klassische Vererbung in JS funktioniert.
The Prototype Chain
ausführliche Erläuterungen über die technischen Hintergründe der Prototypenkette.
Javascript object prototype
Eine sehr gute deutschsprachige Erklärung, des prototype Objekts. Das ist ein Artikel im Rahmen einer Serie, die Objekte in Javascript und deren Funktionsweisen, sehr gut!
Objektorientierte Programmierung in JavaScript
Eine verbesserte Variante der klassischen Vererbung (auch in Deutsch). Sie erfüllt fast alle Bedingungen, lediglich der Aufruf der Konstruktorfunktion erfolgt einmal zuviel. Wer eine einfache und effektive Vererbung nutzen möchte und mit diesen Nachteil Leben kann, sollte diese Art in's Auge fassen.
Object Oriented Programming in JavaScript
Der Artikel zeigt wie die Objekthierachien in Javascript abgebildet werden.
Object Oriented Super Class Method Calling with JavaScript
Hier wird der Weg beschrieben, der eine Lösung des Problems zeigt. Die dort beschriebene Methode nutzen auch einige Frameworks, vor allem MooTools setzt auf eine Funktion, die der dort beschriebenen sehr ähnlich ist. Die einzige Kritik, die ich daran habe, ist der dass keine Konstruktorfunktionen und prototypen verwendet werden können. Es muss immer ein Objekt dem Konstruktor Class() übergeben werden, aus dem dann das entsprechende Funktionsobjekt hergestellt wird, mit dem dann die Instanzen erzeugt werden können.
Comments (2)
1439 mal gelesen.

2 Kommentare Einen Kommentar hinterlassen »

Kommentare

1. Kommentar von RedTuesday
Freitag 2.Juli 2010: 13:16

Schöner Artikel. Freue mich schon auf die anderen beiden, sofern Du dafür Zeit und Lust findest. :)

2. Kommentar von Struppi
Montag 5.Juli 2010: 13:17

Danke.

Lust schon, der Artikel ist auch schon im groben Entwurf fertig, aber das Thema ist so komplex, dass mir in vielen Dingen schwer fällt es zu formulieren.

Einen Kommentar hinterlassen

Name (erforderlich)
Mail (wird nicht angezeigt) (erforderlich)
Website


Powered by WordPress Stop Spam Harvesters, Join Project Honey Pot
rats-wonderful
rats-wonderful
rats-wonderful
rats-wonderful Browser-Statistiken