Mit Objekten arbeiten

Die Sprache JavaScript wurde nach einem einfachen objektbasierten Paradigma gestaltet. Ein Objekt besteht aus einer Zusammenstellung von Eigenschaften. Eine Eigenschaft ist eine Verknüfung eines Namens mit einem Wert und der Wert einer Eigenschaft kann auch eine Funktion sein. Eine Funktion, die einem Objekt zugeordnet ist, nennt man Methode. Zusätzlich zu den Objekten, die im Browser vordefiniert sind, können auch eigene Objekte erstellt werden.

Dieses Kapitel beschreibt, wie Objekte, Eigenschaften, Funktionen und Methoden benutzt werden und wie man eigene Objekte erstellt.

Übersicht zu Objekten

Objekte in JavaScript können, wie auch bei anderen Programmiersprachen, mit Objekten in der realen Welt verglichen werden. Daher können normale Gegenstände zur Veranschaulichung des Konzepts dienen.

Bei JavaScript ist ein Objekt eine eigenständige Einheit mit Eigenschaften und einem Typ. Ein Objekt könnte z. B. eine Tasse sein, denn eine Tasse ist ein Objekt mit bestimmten Eigenschaften. Sie hat eine Farbe, ein Design, ein Gewicht, ist aus einem bestimmten Material gefertigt usw. Auf dieselbe Art und Weise besitzen JavaScript-Objekte Eigenschaften, womit deren Merkmale definiert werden.

Objekte und Eigenschaften

Ein JavaScript-Objekt hat Eigenschaften, die mit dem Objekt verknüpft sind. Objekteigenschaften sind im Prinzip dasselbe, wie normale Variablen, außer dass sie zusätzlich einem Objekt zugeordnet sind. Der Zugriff auf eine Eigenschaft kann mit Hilfe der Punkt-Notation erfolgen:

objectName.propertyName

Wie auch bei normalen Variablen gilt es sowohl bei Objektnamen als auch bei deren Eigenschaften zwischen Groß- und Kleinschreibung zu unterscheiden. Eine Eigenschaft wird definiert, indem man dieser Eigenschaft einen Wert zuweist. Wir erstellen zum Beispiel ein Objekt mit dem Namen myCar and geben diesem Objekt drei verschiedene Eigenschaften make, model und year:

var myCar = new Object();
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;

Eigenschaften von JavaScript-Objekten können auch mit Hilfe der Klammer-Notation definiert und angesprochen werden. Objekte werden manchmal assoziative Arrays genannt, da jede Eigenschaft mit einem String-Wert verknüpft ist, über den auf die Eigenschaft zugegriffen werden kann. Zum Beispiel können die Eigenschaften des Objekts myCar wie folgt definiert werden:

myCar["make"] = "Ford";
myCar["model"] = "Mustang";
myCar["year"] = 1969;

Der Name einer Objekteigenschaft kann jeder beliebige JavaScript-String und auch alles sein, was zu einem String konvertiert werden kann, einschließlich der leere String. Jedoch lassen sich Eigenschaftsnamen, die keine gültigen JavaScript-Bezeichner sind (z. B. Eigenschaftsnamen, die Leerzeichen oder Bindestriche enthalten oder mit einer Ziffer beginnen) nur über die Klammer-Notation ansprechen. Diese Notation ist außerdem sehr nützlich, wenn Eigenschaftsnamen dynamisch festgelegt werden, also wenn der Name der Eigenschaft erst während der Laufzeit festgelegt wird.

Ein paar Beispiele:

var myObj = new Object(),
    str = "myString",
    rand = Math.random(),
    obj = new Object();

myObj.type              = "Dot syntax";
myObj["date created"]   = "String with space";
myObj[str]              = "String value";
myObj[rand]             = "Random Number";
myObj[obj]              = "Object";
myObj[""]               = "Even an empty string";

console.log(myObj);

Der Eigenschaftsname kann auch in einer Variablen gespeichert und die Eigenschaft dann über diese Variable angesprochen werden:

var propertyName = "make";
myCar[propertyName] = "Ford";

propertyName = "model";
myCar[propertyName] = "Mustang";

Mit Hilfe der Klammer-Notation in Kombination mit for...in ist es möglich, über alle zählbaren Eigenschaften eines Objekts zu iterieren. Die folgende Funktion gibt alle Eigenschaften eines Objekts aus. Das Objekt und dessen Name werden als Argumente übergeben.

function showProps(obj, objName) {
  var result = "";
  for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
        result += objName + "." + i + " = " + obj[i] + "\n";
    }
  }
  return result;
}

Der Funktionsaufruf showProps(myCar, "myCar") würde folgendes zurückgeben:

myCar.make = Ford
myCar.model = Mustang
myCar.year = 1969

Fast alles ist ein Objekt

Bei JavaScript ist fast alles ein Objekt. Alle einfachen Typen, mit Ausnahme von null und undefined, werden wie Objekte behandelt. Sie können zugewiesene Eigenschaften sein (zugewiesene Eigenschaften mancher Typen sind nicht beständig) und haben alle Merkmale von Objekten.

Durchlaufen von Objekteigenschaften

Ab ECMAScript 5 gibt es drei verschiedene Möglichkeiten, um Objekteigenschaften aufzulisten oder zu durchlaufen:

  • for...in-Schleifen
    Diese Methode durchläuft alle aufzählbaren Eigenschaften eines Objekts und seines Prototypes.
  • Object.keys(o)
    Diese Methode gibt ein Array mit allen eigenen aufzählbaren Eigenschaftsnamen (Schlüssel) eines Objekts o zurück.
  • Object.getOwnPropertyNames(o)
    Diese Methode gibt ein Array mit allen Eigenschaftsnamen (aufzählbar oder nicht) eines Objekts o zurück.

Bei ECMAScript 5 gibt es keine vordefinierte Funktion, um alle Eigenschaften eines Objekts zu durchlaufen. Hier kann die folgende Funktion aushelfen:

function listAllProperties(o){     
	var objectToInspect;     
	var result = [];
	
	for(objectToInspect = o; objectToInspect !== null; objectToInspect = Object.getPrototypeOf(objectToInspect)){  
		result = result.concat(Object.getOwnPropertyNames(objectToInspect));  
	}
	
	return result; 
}

Das Durchlaufen der Eigenschaften kann nützlich sein, um "versteckte" Eigenschaften aufzudecken (Eigenschaften des Prototype, die nicht über das Objekt ansprechbar sind, weil eine andere frühere Eigenschaft des Prototype denselben Namen hat). Es können auch nur die ansprechbaren Eigenschaften aufgelistet werden, indem einfach doppelte Namen aus dem Array entfernt werden.

Erstellung von neuen Objekten

Bei JavaScript sind eine Reihe von Objekten bereits vordefiniert. Zusätzlich können eigene Objekte erstellt werden. Bei JavaScript 1.2 und neuer können Objekte mit Hilfe eines Objekt-Literals erstellt werden. Alternativ kann auch zuerst eine Konstruktorfunktion erstellt werden und dann Instanzen von Objekten mit Hilfe dieser Funktion und dem new-Operator erstellt werden.

Benutzung von Objekt-Literalen

Auch mit Hilfe von Objekt-Literalen (auch Objekt-Initialisierer genannt) können Objekte erstellt werden. Die Benutzung von Objekt-Literalen wird als die "buchstäbliche" Notation zum Erstellen von Objekten bezeichnet. Die Bezeichnung Objekt-Initialisierung ist gleichbedeutend mit der Terminologie bei der Programmiersprache C++.

Hier die Syntax für ein Objekt, das über einen Objekt-Literal erstellt wird:

var obj = { property_1:   value_1,   // property_# may be an identifier...
            2:            value_2,   // or a number...
            // ...,
            "property n": value_n }; // or a string

wobei obj der Name des Objekts ist, jedes property_i eine Eigenschaft bzw. ein Bezeichner (Name, Zahl, oder String) und jedes value_i ein Ausdruck, dessen Wert der jeweiligen Eigenschaft zugewiesen ist. Die Variable obj und die Zuweisung ist optional; wenn nicht an anderer Stelle auf das Objekt verwiesen werden soll, muss die Variable nicht erstellt werden. (Achtung: Wenn der Code an einer Stelle steht, wo eine Anweisung erwartet wird, muss er in Klammern eingeschlossen werden, damit keine Verwechslung mit einer Blockanweisung stattfindet).

Wenn ein Objekt mit Hilfe eines Objekt-Literals in einem Top-Level-Script erstellt wird, interpretiert JavaScript das Objekt jedesmal, wenn ein Ausdruck ausgewertet wird, der das Objekt-Literal enthält. Befindet sich der Code für das Objekt-Literal innerhalb einer Funktion, wird es bei jedem Funktionsaufruf erstellt.

Die folgende Anweisung erstellt ein Objekt und weist es der Variablen x zu, falls (und nur dann) die Bedingung cond zu true evaluiert.

if (cond) var x = {hi: "there"};

Das folgende Beispiel zeigt die Erstellung das Objekts myHonda mit drei Eigenschaften. Die Eigenschaft engine ist ebenfalls ein Objekt mit eigenen Eigenschaften.

var myHonda = {color: "red", wheels: 4, engine: {cylinders: 4, size: 2.2}};

Auch mit Objekt Literalen können Arrays erstellt werden, siehe Array Literale.

Bei JavaScript 1.1 und früher können Objekt-Literale nicht eingesetzt werden. Objekte können nur mit Hilfe der Konstruktorfunktionen oder einer Funktion, die durch ein anderes Objekt bereitgestellt wird und diese Aufgabe erledigt, erstellt werden (mehr dazu im nächster Abschnitt).

Benutzung einer Konstruktor-Funktion

Alternativ kann ein Objekt über die beiden folgenden Schritte erstellt werden:

Definition des Objekttyps durch Schreiben einer Konstruktorfunktion. Es gibt eine Übereinkunft, dass man für diese Funktion einen großen Anfangsbuchstaben verwendet. Erstellen einer Instanz des Objekts mit Hilfe von new.

Zur Definition des Objekttyps wird eine Funktion für diesen Typ erstellt, welche den Namen, die Eigenschaften und Methoden enthält. Angenommen es soll ein Objekttyp für Automobile erstellt werden. Das Objekt soll car heißen und die drei Eigenschaften make, model und year besitzen. Dann definiert man eine Funktion Car, welche die Werte für die Eigenschaften als Parameter entgegennimmt:

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

Bei dieser Funktion wird das Schlüsselwort this eingesetzt, um den Eigenschaften des Objekts die Werte der Argumente zuzuweisen.

Anschließend kann mit Hilfe dieser Funktion ein Objekt mycar erstellt werden:

var mycar = new Car("Eagle", "Talon TSi", 1993);

Mit dieser Zuweisung wird das Objekt mycar erstellt und die Argumentwerte werden den Eigenschaften zugewiesen. Die Eigenschaft mycar.make bekommt den Wert "Eagle", mycar.year den Wert 1993 usw.

Über den Aufruf von new können beliebig viele Objekte erstellt werden. Zum Beispiel:

var kenscar = new Car("Nissan", "300ZX", 1992);
var vpgscar = new Car("Mazda", "Miata", 1990);

Ein Objekt kann eine Eigenschaft haben, die selbst wieder ein Objekt ist. Angenommen man erstellt ein Objekt Person wie folgt:

function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}

Und erzeugen dann zwei Instanzen dieses Objekts:

var rand = new Person("Rand McKinnon", 33, "M");
var ken = new Person("Ken Jones", 39, "M");

Dann kann man die Funktion Car erweitern, damit das Objekt eine weitere Eigenschaft owner besitzt, der ein Person-Objekt zugewiesen werden kann:

function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
}

Nun können Instanzen des Objekts wie folgt erzeugt werden:

var car1 = new Car("Eagle", "Talon TSi", 1993, rand);
var car2 = new Car("Nissan", "300ZX", 1992, ken);

Hierbei wern nun nicht nur String- und Integer-Werte, sondern auch jeweils ein Besitzer-Objekt (rand und ken) an die Funktion übergeben. Den Besitzer von car1 kann man nun z. B. über folgende Eigenschaft herausfinden:

car2.owner.name

Eigenschaften können jederzeit zu einem bereits vordefinierten Objekt hinzugefügt werden. Diese Zuweisung:

car1.color = "black";

fügt dem Objekt car1 die Eigenschaft color mit dem Wert "black" hinzu. Dies betrifft allerdings keine der anderen Instanzen. Um allen Instanzen die neue Eigenschaft hinzuzufügen, muss die Definition des car Objekttyps erweitert werden.

Benutzung der Methode Objekt.create

Objekte können auch mit Hilfe der Methode Object.create erstellt werden. Dieser Methode kann sehr nützlich sein, da sie erlaubt, das entsprechende Prototype-Objekt ohne die Definition einer Konstruktorfunktion auszuwählen. Weitere Informationen findet man in der Referenz.

Vererbung

Alle Objekte bei JavaScript erben von wenigstens einem anderen Objekt. Das Objekt, von dem geerbt wird, nennt man Prototype. Die vererbten Eigenschaften können im Prototyp-Objekt des Konstruktors gefunden werden.

Indexieren von Objekt-Eigenschaften

Bei JavaScript 1.0 kann über den Namen oder den Index auf die Eigenschaft eines Objekt zugegriffen werden. Bei JavaScript 1.1 und neuer muss der Zugriff auf eine Eigenschaft wieder über den Namen erfolgen, wenn sie über den Namen definiert wurde und über den Index, wenn sie über den Index definiert wurde.

Diese Beschränkung gilt sowohl für Objekte und ihre Eigenschaften, wenn sie über die Konstruktorfunktion erstellt wurden (wie im vorherigen Abschnitt beim car-Objekt) als auch wenn verschiedene Eigenschaften explizit definiert werden (zum Beispiel myCar.color = "red"). Wenn man anfänglich eine Objekt-Eigenschaft über ihren Index definiert, z. B. myCar[5] = "25 mpg", so lässt sich diese Eigenschaft später nur über myCar[5] ansprechen.

Eine Ausnahme für diese Regel gibt es bei Objekten, die von HTML stammen, wie z. B. dem forms-Array. Auf diese Objekte kann entweder über ihre Nummer (basierend darauf, wo sie im Dokument vorkommen) oder ihren Namen (falls definiert) zugegriffen werden. Wenn im Dokument z. B. das zweite Formular-Tag <FORM> ein NAME-Attribute mit dem Wert "myForm" hat, kann dieses Formular über document.forms[1], document.forms["myForm"] oder document.myForm angesprochen werden.

Definition von Eigenschaften für einen Objekttyp

Einem zuvor definierten Objekt können mit Hilfe des Prototyp neue Eigenschaften zugeordnet werden. Auf diese Weise wird die Eigenschaften von allen Instanzen geteilt. Der folgende Code fügt allen Objekten vom Typ car eine Farbeigenschaft hinzu und weist der Eigenschaft von cor1 einen Wert zu.

Car.prototype.color = null;
car1.color = "black";

Im Abschnitt zu den Eigenschaften des Function-Objekts in der JavaScript Referenz findet man weiter Informationen.

Defintion von Methoden

Eine Methode ist eine Funktion die mit einem Objekt verknüpft ist. Anders ausgedrückt: Eine Methode ist eine Eigenschaft eines Objekts, die eine Funktion ist. Methoden werden genauso wie normale Funktionen definiert, außer dass sie einem Objekt zugeordnet werden müssen.

Beispiele:

objectName.methodname = function_name;

var myObj = {
  myMethod: function(params) {
    // ...do something
  }
};

Bei diesem Beispiel ist objectName ein existierendes Objekt, methodname ist der Name der Methode und function_name, der Name einer Funktion die sich später über methodname wie folgt aufrufen lässt:

object.methodname(params);

Methoden können für einen Objekttyp definiert werden, indem sie innerhalb einer Konstruktorfunktion definiert werden. Zum Beispiel knnte man eine Funktion definieren, welche die Eigenschaften des zuvor definierten car-Objekts formatiert und auflistet:

function displayCar() {
  var result = "A Beautiful " + this.year + " " + this.make
    + " " + this.model;
  pretty_print(result);
}

wobei pretty_print eine Funktion ist, welche eine horizontale Linie und einen String ausgibt. Beachten Sie die Verwendung des Schlüsselworts this, womit auf das Objekt verwiesen wird.

Diese Funktion kann man zu einer Methode des Objekts car machen, indem man folgende Zuweisung der Objektdefinition hinzufügt.

this.displayCar = displayCar;

Die ganze Definition des Objekts car ist dann wie folgt:

function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
  this.displayCar = displayCar;
}

Anschließend kann man die Methode displayCar wie folgt aufrufen:

car1.displayCar();
car2.displayCar();
 
Dies produziert eine Ausgabe wie im folgenden Bild:
 

Image:obja.gif

Figure 7.1: Ausgabe der Methode.

Benutzung von this zur Objektreferenzierung

Javascript besitzt das spezielle Schlüsselwort this, das man innerhalb von Methoden benutzen kann, um auf das aktuelle Objekt zu verweisen. Wenn man zum Beispiel eine Funktion mit Namen validate hat, die einen Wert einer Objekteigenschaft validiert und der zwei Grenzwerte als Parameter übergeben werden:

function validate(obj, lowval, hival) {
  if ((obj.value < lowval) || (obj.value > hival))
    alert("Invalid Value!");
}

Dann kann man die Funktion für jedes Formular-Element über den onchange-event-Handler aufrufen und mit Hilfe von this das Element übergeben:

<input type="text" name="age" size="3" onChange="validate(this, 18, 99)">

Im Allgemeinen verweist this auf das aufrufende Objekt.

In Kombination mit der Eigenschaft form kann this auf das Elternobjekt eines Elements verweisen. Beim folgenden Beispiel enthält das Formular myForm ein Textobjekt und einen Button. Klickt der Benutzer den Button an, wird der Name des Formulars als Wert für des Textobjekt gesetzt. Beim onclick-Event-Handler des Buttons wird mit this.form auf das Elternobjekt myForm verwiesen.

Definition von gettern und settern

Ein getter ist eine Methode, die einen Wert einer spezifischen Eigenschaft bekommt. Ein setter ist eine Methode, die den Wert einer spezifischen Eigenschaft setzt. Man kann getter und setter für jedes vordefinierte Kernobjekt oder benutzerdefinierte Objekt verwenden, welches das Hinzufügen von neuen Eigenschaften erlaubt. Als Syntax für die Definition von gettern und settern kommt die literale Syntax zum Einsatz.

 

Hinweis: JavaScript 1.8.1

Ab JavaScript 1.8.1 werden setter nicht weiter aufgerufen, wenn Eigenschaften in Objekten und Array-Initialisierern gesetzt werden.

Die nachfolgende Sitzung mit JS-Shell veranschaulicht wie getter und setter für ein benutzerdefiniertes Objekt o funktionieren. Die JS-Shell ist ein Programm, das Entwicklern ermöglicht, Javascript-Code im Batch-Modus oder interaktiv zu testen. Bei Firefox kann man die Shell über die Tastenkombination Strg+Shift+K öffnen.

Die Eigenschaften des Objekts o sind:

  • o.a — Eine Zahl
  • o.b — Ein getter, der o.a plus 1 zurückgibt
  • o.c — Ein setter, der den Wert von o.a auf den halben Wert von o.c setzt.

Achtung: Funktionsnamen von gettern und settern, die in einem Objekt-Literal mit Hilfe von "[gs]et property()" (anstatt mit __define[GS]etter__ wie unten) definiert werden, sind nicht die Namen des getters/setters selbst, obgleich die [gs]et propertyName(){ }-Syntax zu dieser Annahme verleitet. Um eine Funktion in einem getter oder setter mit Hilfe der "[gs]et property()"-Syntax zu benennen, definiert man eine explizit benannte Funktion mit Hilfe von Object.defineProperty (oder dem Object.prototype.__defineGetter__ Fallback).

Die folgende JS-Shell-Sitzung demonstriert, wie getter und setter den Date-Prototype erweitern, um allen Instanzen des vordefinierten Date-Objekts eine weitere Eigenschaft year hinzuzufügen. Die bereits existierenden Methoden getFullYear und setFullYear werden benutzt, um der year-Eigenschaften getter und setter hinzuzufügen.

Mit diesen Anweisungen wird ein getter und setter für die year-Eigenschaft definiert:

js> var d = Date.prototype;
js> d.__defineGetter__("year", function() { return this.getFullYear(); });
js> d.__defineSetter__("year", function(y) { this.setFullYear(y); });

Und so wird auf getter und setter des Date-Objekts zugegriffen:

js> var now = new Date;
js> print(now.year);
2000
js> now.year = 2001;
987617605170
js> print(now);
Wed Apr 18 11:13:25 GMT-0700 (Pacific Daylight Time) 2001

Veraltete Syntax

Bei früheren Versionen unterstütze Javascript einige unterschiedliche Syntaxen für getter und setter, die nicht von anderen Engines unterstützt wurden. Bei neueren Versionen werden diese nicht mehr unterstützt. Weitere Informationen darüber, was genau entfernt und geändert wurde, findet man in diesem Abschnitt.

Zusammenfassung

Im Prinzip können getter und setter wie folgt definiert werden:

  • über Objekt-Initializer, oder
  • durch späteres hinzufügen zu beliebiger Zeit und einem beliebigen Objekt mit Hilfe von gettern oder settern.

Wenn getter und setter über Objekt-Initialisierer definiert werden, ist alles was man tun muss, dem Methodennamen ein get oder set voranzustellen. Die getter-Methode darf dabei keinen Parameter entgegennehmen, während der setter-Methode exakt ein Paremeter übergeben wird - der neuen Wert, der gesetzt werden soll.

Zum Beispiel:

var o = {
  a: 7,
  get b() { return this.a + 1; },
  set c(x) { this.a = x / 2; }
};

Getter und setter können auch jederzeit mit Hilfe der zwei speziellen Methoden __defineGetter__ und __defineSetter__ hinzugefügt werden. Beide Methoden erwarten den Namen des getter oder setter als ersten Parameter in Form eines Strings. Der zweite Parameter ist die Funktion, die als getter oder setter aufgerufen werden soll. Dazu folgendes Beispiel (anknüpfend an das letzte Beispiel):

o.__defineGetter__("b", function() { return this.a + 1; });
o.__defineSetter__("c", function(x) { this.a = x / 2; });

Für welche der beiden Möglichkeiten man sich entschedet, hängt vom Programmierstil und der zu bewältigenden Aufgabe ab. Wenn man sowieso einen Objekt-Initializer benutzt, um einen Prototype zu definieren, wird man sich eher für die erstgenannte Form entscheiden. Diese ist auch kompakter und einfacher nachvollziehbar. Sofern man getter und setter zu einem späteren Zeitpunkt definiert, etwa weil man den Prototype oder das entsprechende Objekt nicht selbst definiert hat, bleibt nichts anderes übrig, als die zweite Form zu benutzen. Die zweite Form bringt die Dynamik von JavaScript wunderbar zum Ausdruck, kann aber dazu führen, dass der Code schwer lesbar und verständlich ist.

Bei Firefox vor Version 3.0, werden getter und setter nicht für DOM-Elemente unterstützt. Bei ältere Versionen von Firefox scheitert die Ausführung ohne eine Fehlermeldung. Wenn Exceptions für diese Browser benötigt werden, kann man als Workaround den Prototype von HTMLElement(HTMLElement.prototype.__define[SG]etter__) verändern und eine Ausnahme auslösen.

Mit Firefox 3.0 löst das Definieren eines getters oder setters für eine bereits definierte Eigenschaft eine Ausnahme aus. Die Eigenschaft muss dann vorher gelöscht werden, was bei älteren Versionen nicht der Fall ist.

Weitere Informationen

Löschen von Eigenschaften

Mit Hilfe des delete-Operators können Eigenschaften gelöscht werden:

//Neues Objekt "myobj" mit zwei Eigenschaften, "a" und "b".
var myobj = new Object;
myobj.a = 5;
myobj.b = 12;

//Löschen der Eigenschaft "a". Das Objekt mit der Eigenschaft "b" bleibt bestehen.
delete myobj.a;

Mit delete kann man auch globale Variablen löschen, wenn das Schlüsselwort var bei der Deklaration nicht verwendet wurde:

g = 17;
delete g;

Siehe delete-Operator.

Siehe auch