Objektorientierung in JavaScript

Flexibilität von JavaScript

Hinzufügen und Entfernen von Eigenschaften zur Laufzeit

Eine Besonderheit, die die klassenbasierte Objektorientierung nicht zuließe, ist das dynamische Hinzufügen und Entfernen von Objekteigenschaft zur Laufzeit. Einer Instanz von Rechteck kann man beispielsweise folgendermaßen eine Methode zur Flächenberechnung hinzufügen:

//Methode zu bestehendem Objekt hinzufuegen
r.flaeche = function() { return this.breite * this.hoehe };

Diese Methode ist dann jedoch nur in diesem einen Objekt verfügbar und nicht etwa in allen Instanzen von Rechteck.

Auf dieselbe Art und Weise können auch Attribute hinzugefügt werden.

Das Entfernen von Eigenschaften wird mit dem delete-Operator realisiert. Die Anwendung dieses Operators wurde bereits im Kapitel "Vererbung realisieren" gezeigt.

Bei erfolgreichem Löschvorgang wird true zurückgeliefert, ansonsten false. Nicht alle Eigenschaften von Objekten können gelöscht werden. Neben den in den Sprachkern implementierten Eigenschaften können auch mit dem Schlüsselwort var angelegte Eigenschaften nicht gelöscht werden. Das Löschen von nicht existierenden Eigenschaften gibt ebenfalls den Wert true zurück. Ein einfaches Beispiel ist folgendes:

var o = {x:1, y:2};

Die Eigenschaft x aus o löschen funktioniert und liefert true. Das Objekt selbst zu löschen funktioniert nicht, da es mit dem Schlüsselwort var angelegt wurde. Es wird also false zurückgegeben.

Mehrfachvererbung vortäuschen

Einige objektorientierte Programmiersprachen besitzen die Fähigkeit der Mehrfachvererbung. Dadurch, dass Vererbung in JavaScript über die Abarbeitung der Prototypenkette realisiert wird und über die Prototyp-Eigenschaft immer nur ein Objekt referenziert wird, kann JavaScript keine Mehrfachvererbung.

Allerdings ist es möglich, mehr als eine Konstruktorfunktion innerhalb eines Konstruktors aufzurufen, wodurch die Illusion einer Mehrfachvererbung erzeugt werden kann. Das folgende Beispiel zeigt dieses Verfahren:

//Eine Funktion die nur ein Hobby-Attribut setzt
function Hobby (hobby) {
  this.hobby = hobby;
}

//Konstruktor fuer eine Instanz von Arbeiter
//Dem Konstruktor werden nun Initialisierungswerte uebergeben
function Arbeiter (name, dept, projs) {
  this.name = name;
  this.dept = dept;
  this.projects = projs;
}

//Konstruktor
function Mechaniker (name, projs, mach, hobby) {
  //Speicherung einer Funktionsreferenz
  this.base1 = Arbeiter;
  //Initialisierungswerte werden dem Konstruktor uebergeben
  //Dann wird der Konstruktor aufgerufen
  this.base1(name, "Technik", projs);
  //Speicherung einer weiteren Funktionsreferenz
  this.base2 = Hobby;
  this.base2(hobby);
  this.machine = mach;
}
Mechaniker.prototype = new Arbeiter;
jim = new Mechaniker("Jim", ["Schweissen"], "Schweissgeraet", "Musik");

Die Bezeichner base1 und base2 können frei gewählt werden und sind keineswegs reservierte Bezeichner.

Im Konstruktor Mechaniker werden zwei Funktionsreferenzen auf die Funktionen Arbeiter und Hobby in den Variablen base1 und base2 gespeichert. Hierdurch werden die Funktionen wie objekteigene Methoden behandelt. Ihnen wird folglich als Wert des Schlüsselwortes this das umgebende Objekt Mechaniker übergeben. Dadurch werden den Instanzen von Mechaniker in den einzelnen Funktionen die jeweiligen Eigenschaften hinzugefügt, wenn der Konstruktor aufgerufen wird.

Das Objekt jim hat in diesem Fall die Eigenschaften von Arbeiter, von Hobby, sowie die in Mechaniker angelegte Eigenschaft machine:

Nun wird gezeigt, warum nicht von Mehrfachvererbung gesprochen werden kann. Im folgenden Code-Schnipsel wird sowohl dem Arbeiter-Prototyp als auch dem Hobby-Prototyp ein weiteres Attribut hinzugefügt. Anschließend werden wieder alle Attribute von Jim ausgegeben:

//Dem Arbeiter-Protoyp wird ein Vorgesetzter hinzugefuegt
Arbeiter.prototype.supervisor = "John";
//Dem Hobby-Prototyp wird ein zweites Hobby zugeordnet
Hobby.prototype.hobby2 = "Singen";
with (jim) {
  alert("Name: " + name);
  alert("Abteilung: " + dept);
  alert("Vorgesetzter: " + supervisor);
  alert("Projekt: " + projects);
  alert("Maschine: " + machine);
  alert("erstes Hobby: " + hobby);
  alert("zweites Hobby: " + hobby2);
}

Das zweite Hobby wird nicht ausgegeben, da eine Instanz von Mechaniker nicht vom Prototyp von Hobby erbt und somit nicht über dieses Attribut verfügt. Durch die Zeile Mechaniker.prototype = new Arbeiter; wird Arbeiter in der Prototypenkette explizit als Elternelement gesetzt und somit kann auch nur von dessen Prototyp geerbt werden. Man sieht, dass Mehrfachvererbung nur zum Zeitpunkt der Objekterstellung erzeugt werden kann, da in JavaScript immer nur von einem bestimmten Prototyp, der im Prototyp-Attribut referenziert wurde, dauerhaft geerbt werden kann.

Erweitern, ohne zu erben

Die Flexibilität von JavaScript bietet über die Vererbung hinaus eine weitere Möglichkeit um Prototypen bestehender Objekte um Methoden anderer Prototypen zu erweitern. Dabei werden die Methoden eines Prototyp-Objekts in den zu erweiternden Prototypen kopiert. Sie können dann von entsprechenden Instanzen (die von diesem Prototypen erben) als objekteigene Methoden verwendet werden. Dies wird im JavaScript-Jargon auch "Ausleihen" genannt. Wenn man bedenkt, dass Funktionen nichts anderes als Datenwerte, also Objekte sind, kann man dafür die folgende Funktion benutzen:

function borrowMethods(ausleihenVon, hinzufuegenZu) {
  //Prototyp-Objekt, aus dem ausgeliehen wird;
  var from = ausleihenVon.prototype;
    //Prototyp-Objekt, das erweitert werden soll
    var to = hinzufuegenZu.prototype;
    // Alle Eigenschaften des Prototyps durchlaufen
    for(m in from) {;
      //Nur Funktionen betrachten
      if (typeof from[m] != "function") continue;
      //Die Methoden ausleihen
      to[m] = from[m];
    };
  }

Diese Funktion nutzt die interne Darstellung von Objekten als assoziative Arrays und speichert unter dem gleichnamigen Eintrag im Prototyp-Objekt(-Array) eine Referenz auf eine Funktion eines anderen Prototyps.

Bei dieser Art der Erweiterung ist zu beachten, dass die Methoden, die ausgeliehen werden, so allgemein geschrieben sind, dass sie von beliebigen Objekten verwendet werden können. Objekte, die solche Methoden enthalten werden als "Mixins" bezeichnet. Methoden, die eng an bestimmte Objekt-Typen gebunden sind, sind für diese Art der Erweiterung ungeeignet.

Das folgende Beispiel zeigt eine allgemeine generische toString-Methode:

//Erzeugen eines leeren Funktionsobjekts
function GenericToString() {}
  GenericToString.prototype.toString = function() {
    //leeres Array anlegen
    var eigenschaften = [];
    for (var name in this) {
      if (!this.hasOwnProperty(name)) continue;
      var wert = this[name];
      var s = name + ':'
      switch (typeof wert) {
        case 'function':
          s += "function";
          break;
        case 'object':
          if (wert instanceof Array) s += "array"
          else s += wert.toString();
          break;
        default:
          s += String(wert);
          break;
      }
      eigenschaften.push(s);
    }
    return "{" + eigenschaften.join(", ") + "}";
  }

Nun wird ein Konstruktor Rechteck definiert, der danach um die Methode GenericToString erweitert wird:

function Rechteck(x,y,b,h) {
  this.x = x;
  this.y = y;
  this.breite = b;
  this.hoehe = h;
}
Rechteck.prototype.flaeche = function() { return this.breite * this.hoehe; }
//Methode ausleihen
borrowMethods(GenericToString, Rechteck);

Nun wird die toString-Methode wie eine objekteigene Methode behandelt.

//Objekt erstellen
r = new Rechteck(1,2,3,4);
for (var name in r) {
  alert(name);
}

[top]