Objektorientierung in JavaScript

Objekthandling

Prototyping – Das Sprachkonzept von JavaScript

In der prototypbasierten Programmierung, die oft auch klassenlose Objektorientierung genannt wird, wird auf das Sprachelement der Klasse verzichtet.

In prototypbasierten Sprachen (wie JavaScript) existieren nur Objekte, von denen wiederum spezielle, bereits vorhandene Objekte als Vorlage für andere Objekte dienen. Damit stellen diese so genannten Prototyp-Objekte ihre Eigenschaften und Methoden zur Wiederverwendung zur Verfügung, wobei die neu erzeugten Objekte nur die Unterschiede zu ihren Prototyp-Objekten definieren müssen.

Es existieren in JavaScript also keine Klassen, stattdessen wird mit Funktionsobjekten und prototypischen Objekten gearbeitet. Auch Funktionen sind eine spezielle Form von Objekten, daher werden sie auch Funktionsobjekte genannt. Jeder Prototyp gehört genau zu einem Funktionsobjekt, der Konstruktorfunktion, die ihm als Vorlage dient. Dabei kann jedes Objekt in JavaScript als Prototyp für neue Objekte dienen, woraus eine Vererbungshierarchie, die als Prototypenkette bezeichnet wird, entsteht.

Objekte

In JavaScript existieren nur Objekte. Diese bestehen aus Eigenschaften und Methoden, wobei Methoden wiederum selbst ebenfalls spezielle Formen von Objekten sind. Man unterscheidet zwischen Host-Objekten, fest implementierten Objekten und benutzerdefinierten Objekten. Host-Objekte sind Browser-Objekte wie beispielsweise window oder document, die mit JavaScript angesprochen werden können. Fest implementierte Objekte sind vordefinierte Objekte wie etwa String, Number oder Array. Benutzerdefinierte Objekte werden, wie der Name sagt, vom Benutzer definiert. Durch sie können eigene Datentypen definiert werden, daher wird das Hauptaugenmerk im Folgenden auf diese Art von Objekten gelegt.

Objekterstellung

Die einfachste Möglichkeit in JavaScript Objekte zu erstellen, ist die Nutzung von vordefinierten Datentypen:

//Objekterstellung mit vordefiniertem Datentyp
var meinObjekt = new Object();

Object ist ein vordefiniertes Objekt und vergleichbar mit der Klasse Object in Java, der Basisklasse, von der automatisch alle Klassen erben.

Eine andere, aber unflexible Art um Objekte zu erstellen bietet die JSON-Notation (JavaScript Object Notation). Hierbei werden die Objekt-Eigenschaften ähnlich wie ein struct (in C) in geschwungenen Klammern deklariert. Da der Aufruf von new Object() ein leeres Objekt erzeugt, sieht die äquivalente Erzeugung durch die JSON-Notation wie folgt aus:

//Objekterzeugung mit der JSON-Notation
meinObjekt = {};

Auch wenn meinObjekt ein leeres Objekt darstellt, enthält es bereits einige Eigenschaften, die jedes Objekt in JavaScript von Object erbt. Die für die Ausarbeitung relevanten Eigenschaften sind diese:

Dem neu erzeugten, leeren Objekt können nun beliebige Eigenschaften und Methoden hinzugefügt werden.

Ein Rechteck-Objekt, mit den Attributen breite und hoehe ließe sich beispielsweise so deklarieren:

rechteck = new Object();
rechteck.breite = 2;
rechteck.hoehe = 4;

In JSON-Notation:

Rechteck = {
    breite : 2,
    hoehe : 4
  };

Diese Notation eignet sich lediglich zur Datenspeicherung, da sie keine Wiederverwendung zulässt. Warum das so ist, wird in den nachfolgenden Kapiteln deutlich.

Interne Repräsentation von Objekten

Um einige weiterführende Ausführungen zu verstehen, ist es notwendig zu wissen, wie JavaScript Objekte intern repräsentiert.

JavaScript-Objekte sind assoziative Arrays. Das bedeutet, dass Attribute und Methoden eines Objektes Elemente eines Arrays sind, deren Werte über den (eindeutigen) Objektnamen angesprochen werden können.

Da auch Funktionen Objekte sind, ist im Array unter dem Funktionsnamen eine Referenz auf ein namenloses Funktionsobjekt gespeichert.

Wie bereits gesehen, werden Objekt-Eigenschaften mit dem bekannten Punktoperator angesprochen. Das folgende Beispiel zeigt, dass der Punktoperator für diesen Zugriff nicht zwingend benutzt werden muss. Der []-Operator ist nahezu äquivalent zum Punktoperator:

myObject = new Object();
myObject.property = "Eine Eigenschaft";

Betrachtet man das Argument des []-Operator wird klar, warum die beiden Operatoren nicht gleich sind. Das Argument für den []-Operator ist ein String (" "), während der Punktoperator immer einen Ausdruck als Argument verlangt. Das bedeutet, dass die Argumente des []-Operators als String-Repräsentation nicht den Regeln für Bezeichner unterliegen. Somit können über den []-Operator Objekt-Eigenschaften angelegt werden, die niemals über den Punktoperator angesprochen werden können:

myObject["Attributname mit Leerzeichen"] = "abc";

Objektreferenzen

In Hinsicht auf Objektreferenzen verhält sich JavaScript genauso wie Java. Mehrere Referenzen können auf ein Objekt zeigen, dabei handelt es sich nicht etwa um eine Kopie, sondern um dasselbe Objekt:

a = new Object();
a.first = "1st";
b = a;
b.second = "2nd";

Funktionen und Funktionsobjekte

Funktionen spielen in JavaScript eine zentrale Rolle. Einerseits dienen sie als Konstruktoren, andererseits sind sie, wie bereits angesprochen, selbst Objekte. Darüber hinaus macht JavaScript keinen Unterschied, ob Funktionen als Konstruktoren oder Methoden genutzt werden.

alt-text

Grafik 02: JavaScript unterscheidet nicht zwischen Funktionen als Methoden oder Konstruktoren

Da diese Notation an mehreren Stellen genutzt wird, zeigt die folgende Code-Passage wie anonyme Funktionen deklariert werden:

alert(function(a,b){ return a * b; }(2,2));

Die Funktion hat keinen Namen. Es handelt sich um ein namenloses Funktionsobjekt, das erstellt und sofort aufgerufen wird.

Wichtiger im Zusammenhang mit der Objekterstellung ist die Deklaration von nicht-anonymen Funktionen. Die Deklaration solcher Methoden erfolgt folgendermaßen:

function Rechteck() {
  this.breite = 2;
  this.hoehe = 4;
}

Eine äquivalente Schreibweise ist diese:

Rechteck = function() {
  this.breite = 2;
  this.hoehe = 4;
}

Der Unterschied zwischen den beiden obigen Notationen liegt darin, dass durch die Zeile function Rechteck() dem Funktionsobjekt ein Name zugeordnet wird, und durch die Zeile Rechteck = function() eine Referenz eines namenlosen Funktionsobjektes in einer Variable gespeichert wird. Daher sind so deklarierte Funktionen nicht anonym und in beiden Fällen über den Bezeichner Rechteck aufrufbar.

Wichtig ist die Zuweisung von Eigenschaften über das Schlüsselwort this, welches stets das umgebende Objekt (hier Rechteck) referenziert. Eigenschaften, die nicht über this im Objekt angelegt werden, verfügen über einen nicht öffentlichen Sichtberkeitsstatus.

Konstruktoren

JavaScript unterstützt keine echtes Klassenkonzept, wie es beispielsweise in Java der Fall ist. Dennoch ist es möglich mittels Konstruktorfunktionen Objekte nach einem bestimmten Muster mit gewissen Eigenschaften und Methoden zu instanzieren.

Gleichartige Objekte auf manuelle Art und Weise zu erstellen, wie es bisher gezeigt wurde, ist umständlich. Konstruktorfunktionen bieten eine flexible Möglichkeit gleichartige Objekte mit verschiedenen Eigenschaftswerten zu erstellen, sie ersetzen das Gebilde der Klasse.

Obwohl die Funktion Rechteck aus dem vorangegangenen Beispiel bereits als Konstruktor zu verwenden wäre, wird die Signatur nun um formale Parameter erweitert, um die Möglichkeit einer Initialisierung der Eigenschaften zu erreichen:

Rechteck = function(b,h) {
  this.breite = b;
  this.hoehe = h;
}

Die eigentliche Objekterstellung erfolgt analog zu Java durch das bereits bekannte Schlüsselwort new:

//Erzeugung zweier Instanzen von Rechteck
r1 = new Rechteck(2,4);
r2 = new Rechteck(3,2);

Wie mehrfach erwähnt, existieren in JavaScript keine Klassen. Daher ist Rechteck hier ein Funktionsobjekt, das die Variablen r1 und r2 initialisiert.

Der Aufruf des new-Ausdrucks erzeugt ein neues Objekt ohne Eigenschaften, ruft anschließend die Konstruktofunktion auf und übergibt ihr das neue Objekt als Wert des Schlüsselworts this. Die Konstruktorfunktion legt die Eigenschaften des neu erzeugten Objekts an und initialisiert diese.

Konstruktorfunktionen haben üblicherweise keinen Rückgabewert. Dennoch darf ein Konstruktor ein Objekt zurückliefern, in diesem Fall referenziert der new-Ausdruck das neue Objekt. Nach verlassen der Konstruktorfunktion wird der Wert von this in der Konstruktorfunktion verworfen.

[top]