Über diese Einführung

Mozilla Developer Network Mozilla Developer Network
  • Read
    Docs
      • HTML
      • CSS
      • JavaScript
      • Graphics
      • APIs / DOM
      • Apps
      • Dev Tools
      • MathML
      • Tutorials
      • References
      • Developer Guides
      • Accessibility
      • Demos

      • ...more docs
  • Make
    Apps
  • Build & Use
    Firefox
    • Firefox OS
    • Firefox Desktop
    • Mobile
    • Add-ons
  • See & Submit
    Demos
  • Get
    Involved
    • Join the Community
    • Contribute to MDN
    • Events
    • Tweets, Blogs, and More

Über diese Einführung

JavaScript ist eine plattformübergreifende und objektorientierte Skriptsprache. Diese Einführung beschreibt alles, was man zur Benutzung von Javascript wissen muss.

Neue Features in JavaScript-Versionen

  • Neu in JavaScript 1.5
  • Neu in JavaScript 1.6
  • Neu in JavaScript 1.7
  • Neu in JavaScript 1.8
  • Neu in JavaScript 1.8.1
  • Neu in JavaScript 1.8.5

Was man schon wissen sollte

Bei dieser Einführung wird davon ausgegangen, dass die folgenden Grundkenntnisse vorhanden sind:

  • Ein allgemeines Verständnis über das Internet und das World Wide Web (WWW).
  • Praxiserfahrung mit HTML (HyperText Markup Language) und fundierte Kenntnisse.

Etwas Programmiererfahrung mit Sprachen wie C oder Visual Basic sind nützlich, jedoch keine Voraussetzung.

JavaScript-Versionen

Tabelle 1: JavaScript- und Navigator-Versionen
JavaScript-Version | Navigator-Version
JavaScript 1.0 | Navigator 2.0
JavaScript 1.1 | Navigator 3.0
JavaScript 1.2 | Navigator 4.0-4.05
JavaScript 1.3 | Navigator 4.06-4.7x
JavaScript 1.4 |
JavaScript 1.5 | Navigator 6.0 Mozilla (Open-Source Browser)
JavaScript 1.6 | Firefox 1.5, andere Mozilla 1.8-basierte Produkte
JavaScript 1.7 | Firefox 2, andere Mozilla 1.8.1-basierte Produkte
JavaScript 1.8 | Firefox 3, andere Gecko 1.9-basierte Produkte

Informationen über Javascript

Die JavaScript-Dokumentation schließt die folgenden Bücher ein:

  • Die JavaScript-Einführung (diese Einführung) liefert Informationen über die Sprache JavaScript und ihre Objekte.
  • Die JavaScript-Referenz stellt Referenzmaterial für die Sprache JavaScript bereit.

Beim Einstieg sollte mit der Einführung gestartet werden. Sobald man sich einen Überblick verschafft hat, kann man die Referenz benutzen, um weitere Details über die einzelnen Bestandteile der Sprache zu erfahren.

Tipps zum Lernen von Javascript

Mit JavaScript anzufangen ist einfach: Alles was man braucht ist ein moderner Webbrowser. Diese Einführung beschreibt einige JavaScript-Features, die ausschließlich in der aktuellen Version von Firefox (und anderen Gecko-Browsern) implementiert sind. Aus diesem Grund ist es empfehlenswert, die aktuelle Version von Firefox zu benutzen.

Ein interaktiver Interpreter

Ein interaktiver JavaScript-Interpreter ist eine unerlässliche Hilfe, um die Sprache zu lernen. Er hilft dabei mit dem Code zu experimentieren, ohne vorher eine Datei erstellen zu müssen und die Webseite nach Änderungen zu aktualisieren. Die Error-Konsole von Firefox, welche über das Firefox-Menü zugänglich ist, stellt eine einfache Methode bereit, um Javascript-Code interaktiv auszuprobieren: Einfach den Code eingeben und den Button "Evaluate" drücken.

Firebug

Eine umfangreiche Benutzereingabe steht mit der Erweiterung Firebug zur Verfügung. Hier werden Ausdrücke als Objekte interpretiert und mit anderen Teilen von Firebug verknüpft. Zum Beispiel kann man 5 und 5 addieren, die Groß- und Kleinschreibung einer Zeichenkette ändern, einen anklickbaren Link zum Dokument erzeugen, oder auf ein Element des Dokuments zugreifen:

Klickt man den Pfeil in der Ecke unten rechts an, erhält man einen Editor für die Eingabe von mehrzeiligem Code.

Firefox stellt außerdem einen fortgeschrittenen DOM-Inspektor, einen JavaScript-Debugger, ein Tool zum Untersuchen von Elementen und diverse andere Tools bereit. JavaScript-Code kann die Funktion console.log() ausführen. Diese Funktion interpretiert die Argumente und gibt das Ergebnis in der Firebug-Konsole aus.

Dokument-Konventionen

JavaScript-Applikationen laufen auf vielen verschiedenen Betriebssystemen. Die Informationen in diesem Buch gelten für alle Versionen. Datei- und Verzeichnispfade werden im Windows-Format dargestellt (mit umgekehrten Schrägstrichen zwischen Verzeichnisnamen). Für Unix-Versionen sind die Verzeichnisnamen ähnlich, nur werden hier normale Schrägstriche zur Trennung von Verzeichnisnamen verwendet.

Diese Einführung benutzt URIs mit der folgenden Darstellung:

http://server.domain/pfad/datei.html

Hierbei steht server für den Namen des Servers (Subdomain) auf dem die Applikation läuft, wie z.B. blog oder www; domain steht für den Domainnamen, z.B. mozilla.org oder uiuc.edu; pfad repräsentiert die Verzeichnisstruktur auf dem Server und datei.html steht für den Dateinamen der Datei, die aufgerufen werden soll. Im Allgemeinen sind kursiv geschriebene Teile der URI Platzhalter und Teile in normaler Monospace-Schrift sind Literale. Wenn bei dem verwendeten Server SSL (Secure Sockets Layer) aktivierte wäre, würde man statt http in der URI https verwenden.

Es gelten die folgenden Regelungen für die Schrift:

  • Die nichtproportionale Schrift wird für Beispielcode und Code-Auflistungen, API und Sprachelemente (z.B. Methoden- oder Eigenschaftsnamen), Dateinamen, Pfadnamen, Verzeichnisnamen, HTML-Tags und allen Text, der auf dem Bildschirm eingegeben werden muss, verwendet. (nichtproportinale kursive Schrift kennzeichnet Platzhalter, die in Code integriert sind.)
  • Kursivschrift wird für Buchtitel, Hervorhebungen, Variablen, Platzhalter und Wörter, die in konstantem Kontext stehen, verwendet.
  • Fettschrift wird für Erläuterungen verwendet.


Über JavaScript

Dieses Kapitel stellt JavaScript vor und führt in die grundlegenden Konzepte der Sprache ein.

Was ist JavaScript?

JavaScript ist eine plattformübergreifende und objektorientierte Skriptsprache. Sie ist schlank und leichtgewichtig, jedoch als alleinstehende Sprache nicht besonders nützlich. Sie ist dafür geschaffen in andere Produkte und Applikationen - wie z.B. Webbrowser - integriert zu werden. Innerhalb einer Hostumgebung kann JavaScript mit den Objekten dieser Umgebung verknüpft werden, um eine programmatische Kontrolle zu ermöglichen.

Der Kern von JavaScript besteht aus verschiedenen Objekten wie z.B. Array, Date und Math und einer Kernausstattung von Sprachelementen wie Operatoren, Kontrollstrukturen und Anweisungen. Die Kernelemente der Sprache können für unterschiedliche Zwecke durch weitere Objekte ergänzt werden:

  • Clientseitiges JavaScript erweitert den Kern der Sprache, indem Objekte zur Kontrolle des Browsers (Firefox oder ein anderer Webbrowser) und dessen Document Object Model (DOM) bereitgestellt werden. Zum Beispiel erlaubt clientseitiges JavaScript einer Applikation, Elemente auf ein HTML-Formular zu platzieren und auf Ereignisse, wie z.B. Mausklicks, Formulareingaben oder Seitennavigation zu reagieren.
  • Serverseitiges JavaScript erweitert den Kern der Sprache, indem Objekte bereitgestellt werden, die es ermöglichen, JavaScript auf einem Server laufen zu lassen. Zum Beispiel kann mit serverseitigen Erweiterungen die Kommunikation mit einer Datenbank, kontinuierliche Informationsbeschaffung bei Ausführung einer Applikation oder die Dateimanipulation auf dem Server ermöglicht werden.

Durch die Funktionalität von JavaScript's LiveConnect kann Java- und JavaScript-Code miteinander kommunizieren. Von JavaScript aus können Instanzen von Java-Objekten erzeugt und deren öffentliche Methoden und Felder angesprochen werden. Von Java aus kann auf die Eigenschaften und Methoden von JavaScript-Objekten zugegriffen werden.

JavaScript wurde von Netscape eingeführt und zuerst von Netscape verwendet. Parallel entwickelte Microsoft eine eigene Version der Sprache und nannte sie JScript. Dies führt dazu, dass insbesondere bei älteren Browsern eine spezielle Behandlung der Browser nötig wird, um das Funktionieren der Applikation sicherzustellen. Später wurde die Sprache von der Standardisierungsorganisation ECMA unter dem Namen ECMAScript standardisiert.

JavaScript und Java

JavaScript und Java sind zum Teil ähnlich, doch andererseits auch sehr verschieden. JavaScript hat Gemeinsamkeiten mit Java, aber hat keine so starke Typisierung und starke Typ-Überprüfung. Javascript ist eine dynamisch typisierte Sprache. Das bedeutet, dass sich der Typ einer Variablen aus dem zugewiesenen Wert ergibt und nicht vorher festgelegt werden muss. Javascript folgt weitgehend der Syntax von Java-Ausdrücken, Namenskonventionen und grundlegenden Konstruktionen zur Flusskontrolle, weshalb die Sprache von LiveScript in JavaScript umbenannt wurde.

Im Gegensatz zu Java's Kompilerzeit-System mit Klassen, die sich aus Deklarationen zusammensetzen, verwendet JavaScript ein Laufzeitsystem, das auf wenigen Datentypen basiert, die numerische Werte, boolsche Werte und Zeichenketten repräsentieren. JavaScript verwendet außerdem ein Prototypen-basiertes Objektmodell anstelle eines weiter verbreiteten klassenbasierten Objektmodells. Das Prototypen-basierte Modell ermöglicht dynamische Vererbung - d.h. was vererbt wird kann zwischen den einzelnen Objekten variieren. JavaScript unterstützt zudem Funktionen, ohne spezielle deklarative Anforderungen. Funktionen können z.B. Eigenschaften von Objekten sein, die als schwach typisierte Methoden ausgeführt werden.

JavaScript ist - verglichen mit Java - eine sehr frei geformte Sprache. Es müssen nicht alle Variablen, Klassen und Methoden deklariert werden. Auch braucht man sich nicht darum zu kümmern, ob Methoden als öffentlich, privat oder geschützt deklariert werden sollten. Es müssen keine Interfaces implementiert werden und Variablen, Parameter und Rückgabewerte von Funktionen werden nicht explizit typisiert.

Java ist eine klassenbasierte Programmiersprache, entworfen für schnelle Ausführung und Typsicherheit. Typsicherheit bedeutet, dass z.B. ein Java-Integer in eine Objektreferenz gecastet, oder auf privaten Speicher zugegriffen werden kann, indem man Java-Bytecode manipuliert. Bei Java's klassenbasiertem Modell setzen sich Programme exklusiv aus Klassen und ihren Methoden zusammen. Java's Vererbung und starke Typisierung erfordern dicht verschachtelte Objekthierarchien. Diese Anforderungen machen Java-Programmierung komplexer als JavaScript-Programmierung.

Auf der anderen Seite ist JavaScript mit einer Reihe von schmaleren, dynamisch typisierten Programmiersprachen verwandt, wie z.B. HyperTalk und dBASE. Diese Skriptsprachen offerieren Programmier-Werkzeuge für ein weit größeres Publikum, aufgrund ihrer einfacheren Syntax, speziellen eingebauten Funktionalität, und minimalen Anforderungen für die Erstellung von Objekten.

Tabelle 1.1 JavaScript verglichen mit Java
JavaScript | Java
Objektorientiert. Keine Unterscheidung zwischen Typen von Objekten. Vererbung geschieht durch den Prototype-Mechanismus. Eigenschaften und Methoden können dynamisch jedem Objekt hinzugefügt werden. | Klassenbasiert. Objekte werden in Klassen und Instanzen unterteilt. Vererbung geschieht durch die Festlegung der Klassenhierarchie. Klassen und Instanzen können keine Eigenschaften oder Methoden dynamisch hinzugefügt werden.
Variable Datentypen werden nicht deklariert (dynamische Typisierung). | Variable Datentypen müssen deklariert werden (statische Typisierung).
Es kann nicht automatisch auf die Festplatte geschrieben werden. | Es kann nicht automatisch auf die Festplatte geschrieben werden.

Mehr Informationen zu den Unterschieden zwischen JavaScript und Java findet man im Kapitel Feinheiten des Objektmodells.

Javascript und die ECMAScript-Spezifikation

Netscape arbeitete mit Ecma International zusammen - eine private, internationale Normungsorganisation zur Normung von Informations- und Kommunikationssystemen und Unterhaltungselektronik. Die Organisation war früher unter dem Namen ECMA (European Computer Manufacturers Association) bekannt und soll für die Standardisierung einer internationalen Programmiersprache basiernd auf dem Kern von JavaScript sorgen. Diese standardisierte Version von JavaScript, genannt ECMAScript, verhält sich in allen Applikationen equivalent, die den Standard unterstützen. Unternehmen können den offenen Standard bei der Entwicklung für ihre Implementierung von JavaScript benutzen. Der ECMAScript-Standard ist in der Spezifikation ECMA-262 dokumentiert.

Der Standard ECMA-262 wurde auch von der ISO (International Organization for Standardization) als ISO-16262 abgenommen. Es gibt eine PDF-Version des ECMA-262 (veraltete Version) auf der Website von Mozilla. Außerdem wird die Spezifikation auf der Ecma International Website bereitgestellt. Die ECMAScript-Spezifikation beschreibt nicht das Document Object Model (DOM), welches vom World Wide Web Consortium (W3C) standardisiert ist. Das DOM definiert die Art und Weise, in welcher Form ein HTML-Dokument für den Zugriff mit Skripten bereitgestellt wird.

Beziehungen zwischen JavaScript-Versionen und ECMAScript-Editionen

Netscape arbeitete eng mit Ecma International zusammen, um die ECMAScript-Spezifikation (ECMA-262) zu entwerfen. Die Folgende Tabelle beschreibt die Beziehung zwischen Javascript-Versionen und ECMAScript-Editionen.

Table 1.2 JavaScript-Versionen und ECMAScript-Editionen
JavaScript version | Relationship to ECMAScript edition
JavaScript 1.1 | ECMA-262, Edition 1 basiert auf JavaScript 1.1.
JavaScript 1.2 ECMA-262 war nicht fertig, als JavaScript 1.2 herausgegeben wurde. JavaScript 1.2 ist nicht vollständig kompatibel mit ECMA-262, Edition 1, wegen folgender Gründe:
  • Netscape entwickelte zusätzliche Features in JavaScript 1.2, die bei ECMA-262 noch nicht bedacht wurden.
  • ECMA-262 fügte zwei neue Features hinzu: Internationalisierung mit Hilfe von Unicode und einheitliches Verhalten über alle Plattformen hinweg. Diverse Features von JavaScript 1.2, wie z.B. das Date-Objekt waren plattformabhängig und arbeiteten mit plattformspezifischem Verhalten.

JavaScript 1.3

JavaScript 1.3 ist voll kompatibel mit ECMA-262, Edition 1.

JavaScript 1.3 löst die Inkonsistenz zwischen JavaScript 1.2 und ECMA-262 auf, während alle zusätzlichen Features von JavaScript 1.2 erhalten bleiben, abgesehen von den Operatoren == und !=, welche geändert wurden, um ECMA-262 zu entsprechen.

JavaScript 1.4

JavaScript 1.4 ist voll kompatibel mit ECMA-262, Edition 1.

Die dritte Version der ECMAScript-Spezifikation war nicht fertiggestellt, als JavaScript 1.4 herausgegeben wurde.
JavaScript 1.5 | JavaScript 1.5 ist voll kompatibel mit ECMA-262, Edition 3.
Anmerkung: ECMA-262, Edition 2 besteht aus kleineren redaktionellen Veränderungen und Fehlerbehebungen bei der Edition 1 Spezifikation. Die aktuelle Version der TC39-Arbeitsgruppe von Ecma International ist ECMAScript Edition 5.1.

Die JavaScript-Referenz beschreibt, welche Features der Sprache mit ECMAScript konform sind.

JavaScript wird immer Features beinhalten, die nicht Teil der ECMAScript-Spezifikation sind. JavaScript ist kompatibel mit ECMAScript, während noch weitere Features zur Verfügung stehen.

JavaScript-Dokumentation im Vergleich zur ECMAScript-Spezifikation

Die ECMAScript-Spezifikation ist ein Satz von Anforderungen für die Implementierung von ECMAScript. Sie ist nützlich, wenn man wissen möchte, ob ein JavaScript-Feature von ECMAScript-Implementationen unterstützt wird. Wenn man nur JavaScript-Code schreiben möchte, der nur Features, die von ECMAScript unterstützt werden, beinhaltet, dann lohnt es sich in die ECMAScript-Spezifikation zu schauen.

Das ECMAScript-Dokument ist nicht dafür vorgesehen, Skriptprogrammierern zu helfen. Diese sollten die JavaScript-Dokumentation benutzen, um sich über das Schreiben von Skripten zu informieren.

JavaScript und ECMAScript Terminologie

Die ECMAScript Spezifikation benutzt eine Terminologie und Syntax, mit denen reine Javascript-Programmierer nicht sehr vertraut sein dürften. Obgleich die Beschreibung der Sprache sich in ECMAScript unterscheidet, meint die Sprache selbst dasselbe. JavaScript unterstützt jede Funktionalität, die in der ECMAScript-Spezifikation beschrieben ist.

Die JavaScript-Dokumentation beschreibt Aspekte der Sprache, die für JavaScript-Programmierer geeignet sind. Zum Beispiel:

  • Auf das Globale Objekt wird in der JavaScript-Dokumentation nicht eingegangen, weil es nicht direkt benutzt wird. Die Methoden und Eigenschaften des Globalen Objekts, welche von Javascript-Programmierern benutzt werden, werden in der Javascript-Dokumentation diskutiert, jedoch dort Top-Level-Funktionen und Eigenschaften genannt.
  • Der parameterlose (zero-argument) Konstruktor mit Number- und String-Objekten wird in der JavaScript-Dokumentation nicht beschrieben, weil er von geringem Nutzen ist. Ein Number-Konstruktor ohne ein Argument gibt +0 zurück und ein String-Konstruktor ohne Argument "" (einen leeren String).


Werte, Variablen und Literale

Dieses Kapitel informiert über die Werte mit denen JavaScript arbeitet und beschreibt die grundlegenden Elemente von Ausdrücken: Variablen, Konstanten und Literale.

Werte

JavaScript kennt die folgenden verschiedenen Typen von Werten:

  • Numbers (numerische Werte), wie 42 or 3.14159
  • Boolean (boolesche Werte), true oder false
  • Strings (Zeichenketten), wie z.B. "Hallo!"
  • null, ein spezielles Schlüsselwort, das einen null-Wert beschreibt; null ist auch ein primitiver Wert. Da JavaScript zwischen Groß- und Kleinschreibung unterscheidet, ist null nicht dasselbe wie Null, NULL oder jede andere Variante.
  • undefined, eine Top-Level-Eigenschaft, deren Wert undefiniert ist; undefined ist auch ein primitiver Wert.

Dieser relativ kleine Satz von unterschiedlichen Typen für Werte, auch Datentypen genannt, ermöglicht die Erstellung nützlicher Funktionen in Applikationen. Es gibt keine echte Unterscheidung zwischen Ganzzahlen und Gleitkommazahlen, beide sind vom Typ Number. Bei Operationen mit Kalenderdaten, Zeitpunkten und Datumsformaten kann mit dem Date-Objekt gearbeitet werden.

Objekte und Funktionen sind weitere Kernelemente der Sprache. Objekte kann man sich wie benannte Container für Werte vorstellen und Funktionen wie Prozeduren, die in der Applikation ausgeführt werden können.

Konvertierung von Datentypen

Javascript ist eine dynamisch typisierte Programmiersprache. Das bedeutet, dass der Typ einer Variablen nicht festgelegt werden muss, bevor ihr ein Wert zugewiesen wird. Eine Variable muss also nicht erst durch die Spezifikation eines Datentyps deklariert werden, wie meist bei anderen Programmiersprachen. Außerdem werden Datentypen automatisch so konvertiert, wie sie bei der Ausführung des Skripts benötigt werden.

Eine Variable kann z.B. so definiert werden:

var antwort = 42;

Später kann man derselben Variablen z.B. einen String zuweisen:

antwort = "Danke für den Fisch...";

Da Javascript dynamisch typisiert ist, erzeugt diese Zuweisung keine Fehlermeldung.

Wenn man in Ausdrücken numerische Werte (Numbers) und Zeichenketten (Strings) mit dem Operator + (Pluszeichen) verbindet, werden die numerischen Werte von Javascript automatisch in Strings umgewandelt:

x = "Die Antwort ist " + 42  // ergibt: "Die Antwort ist 42"
y = 42 + " ist die Antwort"  // ergibt: "42 ist die Antwort"

Werden bei Ausdrücken jedoch andere Operatoren verwendet, konvertiert Javascript den Wert nicht automatisch zu einem String:

"37" - 7 // returns 30
"37" + 7 // returns "377"

Konvertieren von Strings zu Numbers

Falls sich ein numerischer Wert in Form eines Strings im Speicher befindet, kann er mit passenden Methoden konvertiert werden.

parseInt() und parseFloat()

Siehe: parseInt() und parseFloat().

Die Methode parseInt gibt ausschließlich ganze Zahlen zurück, daher ist ihr Nutzen auf Dezimalzahlen beschränkt. Bei parseInt sollte man immer den radix-Parameter mit angeben.

Plus-Operator

Eine alternative Methode, um einen numerischen Wert aus einem String zu erhalten, ist die Verwendung des Operators + (Pluszeichen):

"1.1" + "1.1" = "1.11.1"
(+"1.1") + (+"1.1") = 2.2   // Anführungszeichen dienen der Übersichtlichkeit, nicht unbedingt nötig.

Variablen

Variablen werden als symbolische Namen für Werte eingesetzt. Die Namen der Variablen werden Bezeichner (oder engl. Identifier) genannt und folgen gewissen Regeln.

Ein JavaScript-Bezeichner muss mit einem Buchstaben, Unterstrich (_) oder Dollar-Zeichen ($) beginnen. Die darauf folgenden Zeichen dürfen auch Ziffern sein (0-9). Da JavaScript zwischen Groß- und Kleinschreibung unterscheidet, sind mit Buchstaben sowohl "A" bis "Z" (Großbuchstaben) als auch "a" bist "z" (Kleinbuchstaben) gemeint.

Ab JavaScript 1.5 sind bei Bezeichnern auch Buchstaben aus den Zeichensätzen ISO 8859-1 oder Unicode wie å oder ü erlaubt. Außerdem dürfen auch die \uXXXX Unicode-Escape-Sequenzen bei Identifiern verwendet werden.

Ein paar Beispiele von zulässigen Bezeichnern: Number_hits, temp99, and _name

Deklarieren von Variablen

Variablen kann man auf zwei verschiedene Arten deklarieren:

  • Mit dem Schlüsselwort var, welches dem Bezeichner vorangestellt wird: var x = 42. Auf diese Weise können sowohl lokale als auch globale Variablen deklariert werden.
  • Indem einfach einer Variablen ein Wert zugewiesen wird: x = 42. Dies erzeugt immer eine globale Variable und eine strikte JavaScript-Warnung. Deshalb wird von dieser Vorgehensweise abgeraten.

Auswerten von Variablen

Eine Variable, die mit dem var-Schlüsselwort deklariert wurde, ohne dass ihr ein Wert zugewiesen wurde, besitzt den Wert undefined.

Der Versuch, auf eine undeklarierte Variable zuzugreifen, scheitert mit dem Ergebnis einer ReferenceError-Ausnahmebehandlung:

var a;
console.log("Der Wert von a ist " + a); // gibt aus: "Der Wert von a ist undefined"
console.log("Der Wert von b ist " + b); // erzeugt ReferenceError-Ausnahmebehandlung

Ersteres Verhalten kann man sich zunutze machen, um zu überprüfen, ob eine Variable einen Wert enthält. Im folgenden Code-Beispiel wurde der Variablen myVar kein Wert zugewiesen und die Prüfung mit einer if-Anweisung ergibt deshalb true.

var myVar;
if(myVar === undefined){
  alert("myVar ist kein Wert zugewiesen.");
} else {
  alert("myVar ist ein Wert zugewiesen.");
}

Je nachdem in welchem Kontext Variablen gebraucht und ausgewertet werden, werden die Werte unterschiedlich interpretiert.

Der Wert undefined evaluiert zu false, wenn er in einem booleschen Kontext verwendet wird. Zum Beispiel wird bei folgendem Code die Funktion myFunction ausgeführt, weil das Element myArray nicht definiert ist:

var myArray = new Array();
if (!myArray[0]) myFunction();

In numerischem Kontext wird undefined hingegen zu NaN konvertiert:

var a;
a + 2 = NaN

Eine Variable, die mit null deklariert ist, evaluiert in numerischem Kontext zu 0 und in booleschem Kontext zu false:

var n = null;
console.log(n * 32); // prints 0

Sichtbarkeitsbereich (Scope) von Variablen

Eine Variable, die außerhalb einer Funktion deklariert wird, ist eine globale Variable. Globale Variablen sind im ganzen Programm sichtbar und der Zugriff kann über den Code im Dokument von beliebiger Stelle aus erfolgen. Hingegen sind Variablen, die innerhalb von Funktionen definiert werden, lokale Variablen, die nur innerhalb der Funktion angesprochen werden können.

JavaScript grenzt die Sichtbarkeit von Block-Anweisungen nicht ein. Variablen innerhalb von Block-Anweisungen sind lokal zu dem Code, in dem sich der Anweisungsblock befindet. Zum Beispiel wird der folgende Code 5 in der Konsole ausgeben, weil die Sichtbarkeit von x auf die äußere Funktion (oder den globale Kontext) beschränkt ist und nicht auf den Block der if-Anweisung.

if (true) {
  var x = 5;
}
console.log(x);

Eine weitere Besonderheit bei Variablen in JavaScript ist, dass man sich auf eine Variable beziehen kann, die erst später deklariert wurde, ohne einen Ausnahmefehler zu erhalten. Dieses Konzept ist als "hoisting" (engl. Hochziehen) bekannt. Variablen in JavaScript werden gewissermaßen zum Anfang der Funktion hochgezogen oder angehoben. Jedoch liefern nicht initialisierte Variablen den Wert undefined.

// Beispiel 1

console.log(x === undefined); // loggt "true"
var x = 3;

// Beispiel 2

// gibt undefined zurück
var myVar = "mein Wert";

(function() {
  console.log(myVar); // undefined
  var myVar = "lokaler Wert";
})();

Das Beispiel 2 oben wird vom JavaScript-Interpreter so gedeutet:

var myVar = "mein Wert";

(function() {
  var myVar;  // hoisting
  console.log(myVar); // undefined
  myVar = "lokaler Wert";
})();

Aufgrund des "hoisting" sollten alle var-Anweisungen in einer Funktion so nahe wie möglich an den Anfang platziert werden. Diese Vorgehensweise verbessert die Übersichtlichkeit des Codes.

Globale Variablen

Globale Variablen sind tatsächlich Eigenschaften des globalen Objekts. Bei Webseiten ist das globale Objekt window, sodass man globale Variablen über die Syntax window.variable ansprechen kann.

Globale Variablen, die in einem Fenster oder Frame deklariert wurden, lassen sich von einem anderen Fenster oder Frame aus ansprechen, indem man den Namen des Fensters oder Frames spezifiziert. Wenn z.B. eine globale Variable Telefonnummer auf einer Webseite deklariert wurde, die ein FRAMESET enthält, lässt sich diese von den Frames aus ansprechen.

Konstanten

Mit dem Schlüsselwort const erzeugt man Konstanten. Die Syntax für Bezeichner von Konstanten ist dieselbe wie bei Variablen.

const prefix = '212';

Der Wert einer Konstanten kann während der Laufzeit des Skripts nicht geändert und nicht neu deklariert werden.

Auch die Sichtbarkeitsregeln für Konstanten sind dieselben wie bei Variablen, außer dass das Schlüsselwort const zwingend angegeben werden muss. Dies gilt auch für globale Konstanten. Wenn das Schlüsselwort nicht angegeben wird, wird der Bezeichner als Variable interpretiert.

Es ist nicht erlaubt, Konstanten und Variablen mit dem Namen einer Funktion oder Variablen innerhalb desselben Sichtbarkeitsbereichs zu deklarieren:

// LOEST EINEN ERROR AUS
function f() {};
const f = 5;

// EBENFALLS ERROR
function f() {
  const g = 5;
  var g;

  // Anweisungen
}

Literale

Literale repräsentieren feste Werte in JavaScript, die buchstäblich festgelegt werden.

  • Array-Literale
  • Boolean-Literale
  • Gleitkommazahlen-Literale
  • Ganzzahlen (Integer)
  • Objekt-Literale
  • String-Literale

Array-Literale

Ein Array-Literal ist eine Liste von Null oder mehreren Ausdrücken von denen jeder ein Array-Element repräsentiert. Arrays werden in eckige Klammern ([]) eingeschlossen. Wenn man ein Array über ein Array-Literal erstellt, wird es mit den angegebenen Werten als dessen Elemente initialisiert. Die Länge des Arrays entspricht dann der Anzahl der Elemente.

Im folgenden Beispiel wird das Array Kaffees mit drei Elementen und einer Länge von 3 erstellt:

var Kaffees = ["French Roast", "Colombian", "Kona"];

Merke: Ein Array ist eine Art Objekt-Initializer. Siehe Benutzung von Objekt-Literalen.

Wenn ein Array über einen Literal in einem Top-Level-Skript erstellt wird, interpretiert JavaScript das Array jedes Mal erneut, wenn die Anweisung evaluiert wird, welche den Array-Literal enthält. Ein Literal innerhalb einer Funktion wird jedes Mal neu erstellt, wenn die Funktion ausgeführt wird.

Array-Literale sind auch Objekte. Im Abschnitt zu Array-Objekten findet man weitere Informationen.

Extra Kommas in Array-Literalen

In einem Array-Literal müssen nicht alle Elemente angegeben werden. Wenn man zwei Kommas nacheinander schreibt, also die Stelle für ein Element frei lässt, bekommt das entsprechende Element den Wert undefined:

var fish = ["Lion", , "Angel"];   // fish[1] ist undefined

Dieses Array hat zwei Elemente mit String-Werten und ein leeres Element. Da man mit 0 zu zählen beginnt, ist das erste Element fish[0] und hat den Wert "Lion". Das zweite Element ist fish[1] und undefined. Das dritte Element fish[2] hat den Wert "Angel".

Wenn man ein zusätzliches Komma am Ende der Liste angibt, wird dieses Komma ignoriert. Beim folgenden Beispiel ist die Länge des Arrays drei. Demzufolge existiert kein Element myList[3], weil man ja mit 0 zu zählen beginnt. (Merke: Die Kommas am Ende können zu Fehlern bei älteren Browsern führen und es ist am besten, sie zu entfernen).

var myList = ['home', , 'school', ];

Beim folgenden Beispiel ist die Länge des Arrays vier. Die Elemente myList[0] und myList[2] fehlen und sind darum undefined.

var myList = [ , 'home', , 'school'];

Noch ein Beispiel: Die Länge des Arrays ist hier ebenfalls vier. Die Elmente myList[1] und myList[3] fehlen und nur das letzte Komma wird ignoriert.

var myList = ['home', , 'school', , ];

Es ist wichtig zu verstehen, was zusätzliche Kommas bewirken, um JavaScript als Sprache zu verstehen. Wenn man aber selbst Code schreibt, ist es besser, die fehlenden Elemente mit undefined zu deklarieren, um die Lesbarkeit, Übersichtlichkeit und Wartbarkeit zu verbessern.

Boolean-Literale

Der boolesche Datentyp kennt zwei literale Werte: true und false.

Die primitiven booleschen Werte sollten nicht mit den true- und false-Werten des booleschen Objekts verwechselt werden. Das boolesche Objekt ist eine Hülle für den primitiven booleschen Datentyp. Siehe Boolean-Objekt für mehr Informationen.

Integer (ganze Zahlen)

Integer können mit Dezimalzahlen (Basis: 10), Hexadezimalzahlen (Basis: 16) und Oktalzahlen (Basis: 8) ausgedrückt werden.

  • Literale mit dezimalen Ganzzahlen bestehen aus einer Reihe von Ziffern, ohne eine führende 0 (Null).
  • Eine 0 (Null) an erster Stelle bedeutet, dass es sich um eine Oktalzahl handelt. Oktalzahlen können außschließlich die Ziffern 0-7 beinhalten.
  • Eine Hexadezimalzahl kennzeichnet sich durch 0x (oder 0X) am Anfang. Hexadezimale Zahlen bestehen aus den Ziffern 0-9 und den Buchstaben A-F.

Oktalzahlen-Literale sind veraltet und wurden aus dem Standard ECMA-262, Edition 3 (im strict mode) entfernt. JavaScript 1.5 unterstützt sie aber weiterhin aus Gründen der Abwärtskompatibilität.

Ein paar Beispiele von Integer-Literalen sind:

0, 117 und -345 (dezimal, Basis 10)
015, 0001 and -077 (oktal, Basis 8)
0x1123, 0x00111 and -0xF1A7 (hexadezimal, "hex" oder Basis 16)

Gleitkommazahlen-Literale

Eine Gleitkommazahl kann folgende Bestandteile haben:

  • Eine Dezimalzahl mit Vorzeichen ( "+" oder "-"),
  • Einen Dezimalpunkt ("."),
  • Einen Bruchteil mit weiteren Dezimalzahlen,
  • Einen Exponenten.

Der Teil mit dem Exponent ist ein "e" oder "E" gefolgt von einer Ganzzahl, die ein Vorzeichen haben kann ("+" or "-"). Eine Gleitkommazahl muss mindestens eine Ziffer besitzen und einen Dezimalpunkt oder "e" (oder "E").

Ein paar Beispiele für Gleitkommazahlen-Literale sind 3.1415, -3.1E12, .1e12 und 2E-12.

Beschreibung der Syntax:

[digits][.digits][(E|e)[(+|-)]digits]

Weitere Beispiele:

3.14
2345.789
.3333333333333333333

Objekt-Literale

Ein Objekt-Literal ist eine Liste von Null oder mehreren Paaren von Eigenschaftsnamen und assoziierten Werten, die zu einem Objekt gehören, eingeschlossen in geschweifte Klammern ({}). Ein Objekt-Literal sollte nicht am Anfang einer Anweisung verwendet werden. Dies führt zu einem Fehler oder verhält sich anders als gewollt, weil die erste Klammer wie der Anfang eines Blocks interpretiert wird.

Das folgende Beispiel zeigt ein Objekt-Literal. Das erste Element des Objekts car definiert eine Eigenschaft myCar,.Das zweite Element, die Eigenschaft getCar, ruft eine Funktion CarTypes("Honda") auf. Das dritte Element, die Eigenschaft special, benutzt eine existierende Variable (Sales).

var Sales = "Toyota";

function CarTypes(name) {
  return (name == "Honda") ?
    name :
    "Sorry, we don't sell " + name + "." ;
}

var car = { myCar: "Saturn", getCar: CarTypes("Honda"), special: Sales };

console.log(car.myCar);   // Saturn
console.log(car.getCar);  // Honda
console.log(car.special); // Toyota

Des Weiteren kann man einen numerischen Literal oder String-Literal für den Namen der Eigenschaft benutzen oder Objekte ineinander verschachteln:

var car = { manyCars: {a: "Saab", "b": "Jeep"}, 7: "Mazda" };

console.log(car.manyCars.b); // Jeep
console.log(car[7]); // Mazda

Merke:

var foo = {a: "alpha", 2: "zwei"};
console.log(foo.a);    // alpha
console.log(foo[2]);   // zwei
//console.log(foo.2);  // Error: fehlende ) nach der Argumentliste
//console.log(foo[a]); // Error: a ist nicht definiert
console.log(foo["a"]); // alpha
console.log(foo["2"]); // zwei

String-Literale

String-Literale können aus Null oder mehr Zeichen bestehen. Sie werden in doppelten (") oder einfachen (') Anführungszeichen eingeschlossen. Dabei muss der Typ der beiden Anführungszeichen am Anfang und Ende stets derselbe sein, d.h. entweder sind beide Anführungszeichen einfach oder doppelt.

Hier ein paar Beispiele für String-Literale:

  • "foo"
  • 'bar'
  • "1234"
  • "Eine Zeile \n Noch eine Zeile"
  • "John's Katze

Jede der Methoden des String-Objekts kann man auf einen String anwendenkonvertiert den String-Literal automatisch zu einem temporären String-Objekt, ruft die Methode auf und verwirft das temporäre Objekt wieder. Auch die Eigenschaften String.length kann bei String-Literale benutzt werden:

"John's Katze".length

String-Literale sollte man verwenden, solange man kein String-Objekt benötigt. Siehe String-Objekt für mehr Informationen.

Escape-Sequenzen in Strings

Außer den normalen Zeichen kann man auch die sogenannten Escape-Sequenzen in Strings verwenden. Das sind eine Reihe von speziellen Zeichenkombinationen, die mit einem Backslash beginnen und eine Sonderbedeutung haben.

"Eine Zeile \n Noch eine Zeile"

Die folgende Tabelle listet die speziellen Zeichen (und deren Bedeutung) auf, die in JavaScript benutzt werden können.

Tabelle 2.1 Escape-Sequenzen bei JavaScript
Sequenz | Bedeutung
\b | Rückschritt (Backspace)
\f | Seitenvorschub (Form feed)
\n | Zeilenvorschub (New line)
\r | Wagenrücklauf (Carriage return)
\t | Horizontaler Tabulator
\v | Vertikaler Tabulator
\' | Apostroph oder einfaches Anführungszeichen
\" | Doppeltes Anführungszeichen
\\ | Umgekehrter Schrägstrich (\).
\XXX | Das Zeichen aus dem Zeichensatz Latin-1 mit Angabe der dreistelligen oktalen Zahl XXX zwischen 0 und 377. Zum Beispiel ist \251 die oktale Sequenz für das Copyright-Symbol.
\xXX | Das Zeichen aus dem Zeichensatz Latin-1 mit Angabe der zweistelligen hexadezimalen Zahl XX zwischen 00 und FF. Zum Beispiel ist \xA9 die hexadezimale Sequenz für das Copyright-Symbol.
\uXXXX | Das Unicode-Zeichen mit Angabe der vierstelligen hexadezimalen Zahl XXXX. Zum Beispiel ist \u00A9 die Unicode-Sequenz für das Copyright-Symbol. Siehe Unicode Escape-Sequenzen.

Escapen-Sequenzen

Bei Zeichen, die nicht in der Tabelle aufgelistet sind, wird ein Backslash ignoriert, sollte jedoch vermieden werden (veraltet).

Innerhalb eines Strings kann man Anführungszeichen verwenden, indem man einen Backslash voranstellt. Dies wird als escapen eines Anführungszeichens bezeichnet. Zum Beispiel:

var buchtitel = "Sie liest \"Per Anhalter durch die Galaxis\" von Douglas Adams.";
console.log(buchtitel);

Das Resultat wäre:

Sie liest "Per Anhalter durch die Galaxis" von Douglas Adams.

Um einen normalen Backslash in einen Strings einzufügen, muss man diesen escapen. Hier ein Beispiel für eine Zuweisung mit einem Dateipfad:

var home = "c:\\temp";

Darüber hinaus kann man auch Zeilenumbrüche markieren (escapen), indem man einen Backslash als letztes Zeichen (vor den Zeilenumbruch) schreibt. Beim Ergebnis wird sowohl der Backslash als auch der Zeilenumbruch aus dem String entfernt.

var str = "Dieser String \
ist über \
mehrere Zeilen \
verteilt."
console.log(str);   // Dieser String ist über mehrere Zeilen verteilt.

Obwohl JavaScript nicht die "heredoc"-Syntax unterstützt, kann man Ähnliches erreichen, indem man am Ende der Zeile einen Zeilenvorschub (new line) einfügt und anschließend den Zeilenumbruch markiert:

var poem =
"Rosen sind rot,\n\
Veilchen sind blau.\n\
Ich bin schizophren\n\
Und das bin ich auch."

Unicode

Unicode ist ein internationaler Standard, in dem langfristig für jedes sinntragende Schriftzeichen oder Textelement aller bekannten Schriftkulturen und Zeichensysteme ein digitaler Code festgelegt wird. Ziel ist es, die Verwendung unterschiedlicher und inkompatibler Kodierungen in verschiedenen Ländern oder Kulturkreisen zu beseitigen. Unicode wird ständig um Zeichen weiterer Schriftsysteme ergänzt.

Der Unicode-Zeichensatz kann für alle bekannten Kodierungen benutzt werden. Unicode ist nach dem ASCII-Zeichensatz entworfen. Jedes Zeichen hat einen numerischen Wert und einen Namen. Die Zeichenkodierung beschreibt die Identität des Zeichens und den numerischen Wert (Code-Position), als auch die Repräsentation des Werts in Bits. Der 16-Bit breite numerische Wert (digitaler Code) definiert sich durch eine hexadezimale Zahl und ein Prefix U. zum Beispiel steht U+0041 für den Buchstaben A. Der individuelle Name für diesen Buchstaben ist LATIN CAPITAL LETTER A.

Unicode wird nicht von JavaScript vor Version 1.3 unterstützt.

Unicode Kompatiblität zu ASCII und ISO

Unicode ist vollständig kompatibel mit dem internationalen Standard ISO/IEC 10646-1; 1993, welcher eine Untermenge von ISO 10646 ist. ISO 10646 ist die von ISO verwendete, praktisch bedeutungsgleiche Bezeichnung des Unicode-Zeichensatzes; er wird dort als Universal Character Set (UCS) bezeichnet.

Neben dem eigentlichen Zeichensatz sind auch eine Reihe von Zeichenkodierungen definiert, die den Unicode-Zeichensatz implementieren und die eingesetzt werden können, um den vollen Zugriff auf alle Unicode-Zeichen zu gewährleisten. Sie werden Unicode Transformation Format (kurz UTF) genannt.

Die Kodierung UTF-8 ist kompatibel mit ASCII und wird von vielen Programmen unterstützt. Die ersten 128 Unicode-Zeichen sind die gleichen wie beim ASCII-Zeichensatz und haben denselben Bytewert. Die Unicode-Zeichen U+0020 bis U+007E sind equivalent zu den ASCII-Zeichen 0x20 bis 0x7E. Anders als der ASCII-Zeichensatz, der das lateinische Alphabet in Groß- und Kleinschreibung, die zehn arabischen Ziffern und diverse nicht druckbare Zeichen enthält und jedem Zeichen ein Bitmuster aus 7-Bit zuordnet, sind UTF-8-Zeichen zwischen einem und vier Oktetts (Oktett bedeutet ein Byte oder 8 Bit) breit. Dies erlaubt die Darstellung von merhreren Millionen verschiedenen Zeichen.

Ein alternativer Kodierungsstandard namens UTF-16 verwendet zwei Oktett, um die Unicode-Zeichen darzustellen. Eine Escape-Sequenz erlaubt UTF-16 die ganze Spanne des Unicode-Zeichensatzes mit vier Oktett aufzunehmen. Der ISO UCS-2 (Universal Character Set) verwendet zwei Oktetts.

Die Unterstützung bei JavaScript und Navigator für UTF-8/Unicode erlaubt die Nutzung von nicht-lateinischen, internationalen und lokalisierten Zeichen, plus besondere technische Symbole in JavaScript-Programmen. Unicode definiert eine Standardmethode für die Kodierung von mehrsprachigem Text. Da die UTF-8-Kodierung kompatibel mit ASCII ist, können Programme auch ASCII-Zeichen verwenden. Zeichen, die nicht im ASCII enthalten sind, kann man in Kommentaren, String-Literalen, Bezeichner und regulären Ausdrücken verwenden.

Unicode Escape-Sequenzen

Die Unicode-Escape-Sequenzen dürfen in String-Literalen, regulären Ausdrücken und Bezeichner zum Einsatz kommen. Die Escape-Sequenzen bestehen aus sechs ASCII-Zeichen: den ersten beiden Zeichen "\u" und anschließend einer vierstelligen hexadezimale Zahl. Zum Beispiel steht \u00A9 für das Copyright-Symbol. Jede Unicode-Escape-Sequenz in JavaScript wird als ein Zeichen interpretiert.

Der folgende Code liefert das Copyright-Symbol und den String "Mozilla Firefox".

var x = "\u00A9 Mozilla Firefox";

In der nachstehenden Tabelle werden die oft gebrauchten Zeichen und ihr Unicode-Wert aufgelistet.

Tabelle 2.2 Unicode-Werte für Zeichenfolgen mit Sonderbedeutung
Kategorie | Unicode-Wert | Name | Formatname
Zwischenraumzeichen (Whitespace) | \u0009 | Horizontaler Tabulator (Tab) | <TAB>
 | \u000B | Vertikaler Tabulator | <VT>
 | \u000C | Seitenvorschub (Form Feed) | <FF>
 | \u0020 | Leerzeichen (Space) | <SP>
Zeilenumbruch | \u000A | Zeilenvorschub (Line Feed) | <LF>
 | \u000D | Wagenrücklauf (Carriage Return) | <CR>
Weitere Unicode-Escape-Sequenzen | \u0008 | Rückschritt (Backspace) | <BS>
 | \u0009 | Horizontal Tabulator (Tab) | <HT>
 | \u0022 | Doppelte Anführungszeichen | "
 | \u0027 | Einfache Anführungszeichen | '
 | \u005C | Umgekehrter Schrägstrich (Backslash) | \

Bei JavaScript läuft die Interpretation der Unicode-Escape-Sequenzen etwas anders ab als bei Java. JavaScript betrachtet Escape-Sequenzen nie zuerst als eine Zeichenfolge mit Sonderbedeutung. Ein String wird z.B. nicht durch eine Zeilenumbruch-Sequenz beendet, bevor die Sequenz von einer Funktion ausgewertet wurde. JavaScript ignoriert alle Escape-Sequenzen in Kommentaren. Bei Java wird eine Escape-Sequenz als Unicode-Zeichen interpretiert, sofern sie in einem einzeiligen Kommentar steht. Bei String-Literalen interpretiert der Java-Compiler zuerst die Escape-Sequenz. Wenn in Java z.B. ein Unicode-Zeilenvorschub (\u000A) benutzt wird, beendet dieser den String-Literal. Möchte man einen Zeilenvorschub in einem String erreichen, muss man stattdessen auf \n zurückgreifen. Bei JavaScript hingegen funktioniert ein Unicode-Zeilenvorschub genau wie bei Verwendung von \n.

Unicode-Zeichen in JavaScript-Dateien

Frühere Versionen von Gecko nahmen für Javascript-Dateien an, dass sie mit dem Latin-1-Zeichensatz kodiert sind. Seit Gecko 1.8 wird die Zeichenkodierung von der XUL-Datei übernommen. Unter International characters in XUL JavaScript findet man mehr Informationen.

Anzeigen von Zeichen mit Unicode

Mit Unicode kann man Zeichen für verschiedene Sprachen und technische Symbole anzeigen lassen. Damit die Zeichen richtig dargestellt werden, muss die Client-Software wie z.B. Firefox die Unicode-Zeichenkodierung unterstützen. Außerdem muss dem Client ein brauchbarer Unicode-Font zur Verfügung stehen und die Plattform muss Unicode ebenfalls unterstützen. Meistens können Unicode-Fonts nicht alle Zeichen darstellen. Manche Plattformen, wie z.B. Windows 95, unterstützen Unicode nur zum Teil.

Um nicht-ASCII-Zeicheneingaben zu erhalten, muss der Client die Eingabe als Unicode senden. Bei der Benutzung einer Standard-Tastatur kann der Client viele der Zeichen nicht so einfach eingeben, die von Unicode unterstützt werden. Deshalb ist manchmal die Verwendung von Unicode-Escape-Sequenzen die einzige Möglichkeit.

Mehr Informationen zu Unicode findet man auf der Unicode-Homepage und im Buch Unicode Standard, Version 2.0, erschienen im Verlag Addison-Wesley, 1996.


Ausdrücke und Operatoren

Dieses Kapitel beschreibt Ausdrücke und Operatoren in Javascript. Dazu gehören Zuweisungen, Vergleiche, Arithmetik sowie bitweise, logische, String- und spezielle Operatoren.

Ausdrücke

Ein Ausdruck ist jede gültige Einheit von Code, der ausgewertet werden kann und einen Wert liefert.

Konzeptuell gibt es zwei verschiedene Arten von Ausdrücken: Entweder wird einer Variablen ein Wert zugewiesen oder der Ausdruck selbst hat einen Wert und wird direkt ausgewertet. Zum Beispiel ist die Zuweisung x = 7 ein Ausdruck, der x den Wert 7 zuweist und gleichzeitig liefert dieser Ausdruck selbst den Wert 7 zurück. Diese Art von Ausdrücken benutzen Zuweisungsoperatoren. Beim Ausprobieren in der interaktiven Konsole lässt sich nachvollziehen, dass bei der Zuweisung der Wert 7 zurückgegeben wird. Andererseits liefert der Ausdruck 3 + 4 ebenfalls 7 als Ergebnis, ohne dass hier eine Zuweisung stattfindet. Die Operatoren, die in solchen Ausdrücken benutzt werden, haben keine spezielle Bezeichnung, sondern werden einfach Operatoren genannt.

JavaScript kennt die folgenden Arten von Ausdrücken:

  • Arithmetisch: liefern eine Zahl, z.B. 3.14159. (Grundsätzlich werden arithmetische Operatoren benutzt.)
  • String: liefern eine Zeichenkette, z.B. "Fred" oder "234". (Grundsätzlich werden String-Operatoren benutzt.)
  • Logisch: liefern true oder false. (Oft werden Logische Operatoren verwendet.)
  • Objekt: liefern ein Objekt. (Siehe Spezielle Operatoren für diverse Ausdrücke, die Objekte liefern.)

Operatoren

Dieser Abschnitt beschriebt die Operatoren und die Operatorenrangfolge, d.h. welcher Teil eines Ausdrucks zuerst ausgewertet wird und die Reihenfolge der darauf folgenden Auswertungen.

  • Zuweisungsoperatoren
  • Vergleichsoperatoren
  • Arithmetische Operatoren
  • Bitweise Operatoren
  • Logische Operatoren
  • Zeichenkettenoperatoren
  • Spezielle Operatoren

JavaScript arbeitet sowohl mit binären als auch mit unären Operatoren. Zudem kommt ein spezieller ternärer Operator, der Vergleichsoperator, zum Einsatz. Ein binärer Operator benötigt zwei Operanden - der eine steht vor und der andere hinter dem Operator.

Operand1 Operator Operand2

Zum Beispiel 3 + 4 oder x * y.

Ein unärer Operator benötigt nur einen einzelnen Operanden, der entweder vor oder hinter dem Operator steht.

Operator Operand

oder

Operand Operator

Zum Beispiel x++ oder ++x.

Zuweisungsoperatoren

Ein Zuweisungsoperator weist dem Operanden, der auf der linken Seite des Operators steht, den Wert des Operanden zu, der auf der rechten Seite des Operators steht. Der grundlegende Zuweisungsoperator ist das Gleichzeichen (=), womit dem linken Operanden der Wert des rechten Operanden zugewiesen wird. Das bedeutet x = y weist x den Wert von y zu.

Die anderen Zuweisungsoperatoren sind eine Kurzschreibweise für Standardoperationen. Die nachstehende Tabelle zeigt deren Bedeutung:

Tabelle 3.1 Zuweisungsoperatoren
Kurzschreibweise | Bedeutung
x += y | x = x + y
x -= y | x = x - y
x *= y | x = x * y
x /= y | x = x / y
x %= y | x = x % y
x <<= y | x = x << y
x >>= y | x = x >> y
x >>>= y | x = x >>> y
x &= y | x = x & y
x ^= y | x = x ^ y
x |= y | x = x | y

Vergleichsoperatoren

Ein Vergleichsoperator vergleicht den Wert seiner Operanden und liefert dann einen logischen Wert zurück. Die zum Vergleich herangezogenen Operanden können numerische oder logische Werte, aber auch Zeichenketten oder Objekte sein. Zeichenketten werden anhand ihrer lexographischen Ordnung (bezogen auf Unicode) verglichen. Sind die Operatoren nicht vom selben Typ, versucht Javascript in den meisten Fällen, die Operanden zu einem Typ umzuwandeln, der für den Vergleich für sinnvoll erachtet wird. Eine Ausnahme sind die Operatoren === und !==, welche "strikte" Vergleiche durchführen - Sie vergleichen nicht nur, ob der Wert der Operanden übereinstimmt sondern auch, ob es sich um denselben Datentyp handelt. Des Weiteren versuchen sie nicht, die Operanden vor dem Vergleich umzuwandeln. Dies führt in der Regel dazu, dass ein numerischer Vergleich durchgeführt wird. Die folgende Tabelle beschreibt die Vergleichsoperatoren bezogen auf den folgenden Code:

var var1 = 3, var2 = 4;
Tabelle 3.2 Vergleichsoperatoren
Operator | Beschreibung | Beispiele, die true liefern
Gleich (==) Liefert true, wenn die Operanden gleich sind. 3 == var1

"3" == var1

3 == '3'
Ungleich (!=) Liefert true, wenn die Operanden nicht gleich sind. var1 != 4
var2 != "3"
Strikt Gleich (===) | Liefert true, wenn die Operanden gleich und vom gleichen Datentyp sind. | 3 === var1
Strikt-Ungleich (!==) Liefert true, wenn die Operanden nicht gleich und/oder nicht vom selben Datentyp sind. var1 !== "3"
3 !== '3'
Größer als (>) Liefert true, wenn der Linke Operand größer als der rechte Operand ist. var2 > var1
"12" > 2
Größer oder Gleich (>=) Liefert true, wenn der linke Operand größer oder gleich ist als der rechte Operand. var2 >= var1
var1 >= 3
Kleiner als (<) Liefert true, wenn der linke Operand kleiner als der rechte Operand ist. var1 < var2
"12" < "2"
Kleiner oder gleich (<=) Liefert true, wenn der linke Operand kleiner oder gleich dem rechten Operanden ist. var1 <= var2
var2 <= 5

Arithmetische Operatoren

Arithmetische Operatoren (Rechenoperatoren) arbeiten mit numerischen Werten (entweder Literale oder Variablen) als Operanden und geben einzelne numerische Werte zurück. Die Standard-Operatoren für arithmetische Operationen sind die Operatoren für Addition (+), Subtraktion (-), Multiplikation (*) und Division (/). Diese Operatoren funktionieren wie bei den meisten anderen Programmiersprachen, wenn sie auf Gleitkommazahlen angewendet werden (Achtung: Eine Division durch Null ergibt NaN).

Zum Beispiel:

console.log(1 / 2); /* ergibt 0.5 */
console.log(1 / 2 == 1.0 / 2.0); /* liefert true */

Ferner stellt JavaScript die arithmetischen Operatoren zur Verfügung, die in der Tabelle aufgelistet sind:

Tabelle 3.3 Arithmetische Operatoren
Operator | Beschreibung | Beispiel
% (Modulo) | Binärer Operator. Liefert den ganzzahligen Restwert der Division der beiden Operanden. | 12 % 5 ergibt 2.
++ (Inkrement) | Unärer Operator. Addiert eins zum Operanden. Wenn er als Präfix-Operator verwendet wird, d.h. dem Operanden vorangestellt (++x), wird der Wert des Operanden ausgewertet bevor der Wert um 1 erhöht wurde; wenn er als Postfix-Operator benutzt wird, d.h. dem Operator hintangestellt (x++), wird der Operand ausgewertet, bevor eins hinzuaddiert wurde. | Wenn der Wert von x 3 ist, setzt ++x den Wert von x auf 4 und gibt 4 zurück, während x++ zuerst den Wert 3 zurückgibt und erst dann x auf 4 erhöht.
-- (Dekrement) | Unärer Operator. Subtrahiert 1 vom Operanden. Verhält sich wie der Inkrement-Operator, statt addiert wird jedoch subtrahiert. | Wenn der Wert von x 3 ist, dann setzt --x den Wert von x auf 2 und gibt 2 zurück, während x-- zuerst 3 zurückgibt und erst dann x auf 2 setzt.
- (Unäre Negation) | Unärer Operator. Liefert die Negation seines Operanden. | Wenn x 3 ist, dann gibt -x den Wert -3 zurück.

Bitweise Operatoren

Bitweise Operatoren behandeln ihre Operanden wie einen Satz von 32 Bits (Nullen und Einsen), anstatt wie dezimale, hexadezimale oder oktale Zahlen. Zum Beispiel wird die Dezimalzahl 9 durch den binären Wert 1001 repräsentiert. Bitweise Operatoren arbeiten mit solchen Binärwerten, geben jedoch normale numerische Werte zurück.

Die nachstehende Tabelle beschreibt bitweise Operatoren und ihre Eigenschaften.

Tabelle 3.4 Bitweise Operatoren
Operator | Verwendung | Beschreibung
Bitweises UND | a & b | Überprüft bei jeder Bitposition, ob der entsprechende Wert bei beiden Operanden (a UND b) eins ist und gibt dann eins zurück.
Bitweises ODER | a | b | Überprüft bei jeder Bitposition, ob der entsprechende Wert bei einem der beiden Operanden (a ODER b) oder beiden Operanden eins ist und gibt dann eins zurück.
Bitweises XOR | a ^ b | Überprüft bei jeder Bitposition, ob der entsprechende Wert bei einem der beiden Operanden aber nicht bei beiden Operanden eins ist und gibt dann eins zurück.
Bitweises NICHT | ~ a | Invertiert die Bits des Operanden.
Bitweise Linksverschiebeung | a << b | Verschiebt die binäre Repräsentation von a um b Stellen nach links, während von rechts Nullen nachgeschoben werden.
Rechtsverschiebung mit Beachtung des Vorzeichens | a >> b | Verschiebt die binäre Repräsentation von a um b Stellen nach rechts, während bei einem positiven Vorzeichen von links Nullen nachgeschoben werden und bei einem negativen Vorzeichen Einsen.
Rechtsverschiebung ohne Beachtung des Vorzeichens | a >>> b | Verschiebt die binäre Repräsentation von a um b Stellen nach rechts, ohne Beachtung des Vorzeichens.

Bitweise logische Operatoren

Generell arbeiten die bitweisen Operatoren folgendermaßen:

  • Die Operanden werden zu 32-Bit-Integern konvertiert und durch eine Reihe von Bits (Nullen und Einsen) dargestellt.
  • Jedes Bit des ersten Operanden wird dem Bit des zweiten Operanden, das an derselben Position steht, paarweise gegenüber gestellt: das erstes Bit des ersten Operanden dem ersten Bit des zweiten Operanden, das zweites Bit des ersten Operanden dem zweitem Bit des zweiten Operanden, usw.
  • Der Operator wird auf jedes der Paare angewendet und das Ergebnis bitweise konstruiert.

Zum Beispiel ist die bitweise Repräsentation von 9 die binäre Zahl 1001 und die binäre Repräsentation von 15 ist 1111. Wenn die bitweisen Operatoren auf diese Zahlen angewendet werden, ist das Ergebnis wie folgt:

Tabelle 3.5 Beispiele zu Bitweisen Operatoren
Ausdruck | Ergebnis | Binäre Darstellung
15 & 9 | 9 | 1111 & 1001 = 1001
15 | 9 | 15 | 1111 | 1001 = 1111
15 ^ 9 | 6 | 1111 ^ 1001 = 0110
~15 | 0 | ~1111 = 0000
~9 | 6 | ~1001 = 0110

Bitweise Verschiebungsoperatoren

Die Operatoren für die bitweise Verschiebung nehmen zwei Operanden entgegen: Der erste ist ein Wert, dessen Bits verschoben werden sollen und der zweite Operand legt die Anzahl der Bit-Positionen fest, um die das Bitmuster des ersten Operanden verschoben werden soll. Die Richtung der Verschiebung wird dadurch bestimmt, welcher Operator zum Einsatz kommt.

Verschiebungsoperatoren konvertieren ihre Operanden zu 32-Bit-Zahlen und geben das Ergebnis in Form des Datentyps des linken Operanden zurück.

Die Verschiebungsoperatoren und deren Bedeutung werden in der folgenden Tabelle aufgelistet:

Tabelle 3.6 Bitweise Verschiebungsoperatoren
Operator | Beschreibung | Beispiel
<< (Linksverschiebung) | Dieser Operator verschiebt den ersten Operanden um die Anzahl von Bitstellen nach links, die durch den zweiten Operanden spezifiziert wurden. Die nach links herausgeschobenen Bits werden verworfen und von rechts werden Nullen nachgeschoben. | 9<<2 ergibt 36, weil 1001 um zwei Bit-Stellen nach links verschoben 100100 ergibt (1*32 + 0*16 + 0*8 + 1*4 + 0*2 + 0*1 = 36).
>> (Rechtsverschiebung mit Vorzeichenbeachtung) | Dieser Operator verschiebt den ersten Operanden um die durch den zweiten Operanden spezifizierten Bit-Stellen nach rechts. Überschüssige Bits auf der rechten Seite werden verworfen. Kopien des Bits, das ganz links steht, werden von links eingeschoben. | 9>>2 ergibt 2, weil 1001 um 2 Bit-Stellen nach rechts verschoben zu 10 wird, also dezimal 2 (1*2 + 0*1 = 2). Ebenso ergibt -9>>2 -3, weil das Vorzeichen erhalten bleibt.
>>> (Rechtsverschiebung ohne Vorzeichenbeachtung) | Dieser Operator verschiebt den ersten Operanden um die durch den zweiten Operanden spezifizierten Stellen nach rechts. Überschüssige Bits auf der rechten Seite werden verworfen und von links werden Nullen nachgeschoben. | 19>>>2 ergibt 4, weil 10011 um 2 Bit-Stellen verschoeben nach rechts 100 wird, also dezimal 4 (1*4 + 0*2 + 0*1). Bei nicht-negative Zahlen liefern beide Rechtsverschiebungsoperatoren dasselbe Ergebnis.

Logische Operatoren

Logische Operatoren werden typischerweise mit booleschen (logischen) Werten verwendet und geben dann ebenfalls einen booleschen Werte zurück. Die Operatoren && und || geben hingegen den Wert einer ihrer Operanden zurück - werden sie mit nicht-booleschen Werten verwendet kann es sein, dass sie auch einen nicht-booleschen Wert zurückgeben. Die logischen Operatoren sind in der folgenden Tabelle beschrieben.

Tabelle 3.6 Logische Operatoren
Operator | Benutzung | Beschreibung
&& | Ausdruck1 && Ausdruck2 | (Logisches UND) Gibt Ausdruck1 zurück, wenn dieser zu false konvertiert werden kann. Andernfalls wird Ausdruck2 zurückgegeben. Demzufolge liefert der Operator && true, wenn boolesche Werte übergeben werden und beide Operanden true sind, ansonsten false.
|| | Ausdruck1 || Ausdruck2 | (Logisches ODER) Liefert Ausdruck1, wenn dieser zu true konvertiert werden kann, ansonsten wird expr2 zurückgegeben. Demzufolge ist das Ergebnis bei || true, wenn boolesche Werte übergeben werden und einer dieser Werte true ist oder beide true sind. Wenn beide Operanden false sind, ist das Ergebnis false.
! | !Ausdruck | (Logisches NICHT) Liefert false, wenn der (einzelne) Operand zu true umgewandelt werden kann, ansonsten wird true geliefert.

Beispiele von Ausdrücken, die zu false konvertiert werden können, sind null, 0, leere Strings ("") oder undefined.

Der nachstehende Code zeigt Beispiele zum &&-Operator (logisches UND).

var a1 =  true && true;     // t && t ergibt true
var a2 =  true && false;    // t && f ergibt false
var a3 = false && true;     // f && t ergibt false
var a4 = false && (3 == 4); // f && f ergibt false
var a5 = "Katze" && "Hund";    // t && t ergibt Hund
var a6 = false && "Cat";    // f && t ergibt false
var a7 = "Katze" && false;    // t && f ergibt false

Der folgende Code zeigt Beispiele zum ||-Operator (logisches ODER).

var o1 =  true || true;     // t || t ergibt true
var o2 = false || true;     // f || t ergibt true
var o3 =  true || false;    // t || f ergibt true
var o4 = false || (3 == 4); // f || f ergibt false
var o5 = "Katze" || "Hund";    // t || t ergibt Katze
var o6 = false || "Katze";    // f || t ergibt Katze
var o7 = "Katze" || false;    // t || f ergibt Katze

Der folgende Code zeigt Beispiele zum !-Operator (logisches NICHT).

var n1 = !true;  // !t ergibt false
var n2 = !false; // !f ergibt true
var n3 = !"Katze"; // !t ergibt false

Kurzschlussauswertung (Short-circuit evaluation)

Logische Ausdrücke werden von links nach rechts ausgewertet und dabei auf mögliche Kurzschlussauswertungen überprüft:

  • false && irgendetwas ist eine Kurzschlussauswertung zu false.
  • true || irgendetwas ist eine Kurzschlussauswertung zu true.

Wenn das Ergebnis bereits nach der Auswertung des ersten Operanden feststeht, wird der andere Teil des Ausdrucks erst gar nicht ausgewertet. Die Regeln der Logik garantieren, dass das Resultat auch dann immer korrekt ist. Da irgendetwas nie ausgewertet wird, kommen auch etwaige Seiteneffekte nie zur Anwendung.

Zeichenkettenoperatoren (String-Operatoren)

Zusätzlich zu den Vergleichsoperatoren, die auch auf String-Werte angewendet werden können, gibt es den Verbindungsoperator (+), der zwei Zeichenketten entgegen nimmt, deren Werte verbindet und als Ergebnis eine neue Zeichenkette liefert, die aus dem Verbund der beiden Zeichenketten besteht. Zum Beispiel ergibt "meine " + "Zeichenkette" den String"meine Zeichenkette".

Der Operator für die Additionszuweisung (+=) kann auch verwendet werden, um Zeichenketten zu verbinden. Wenn z.B. die Variable meinString den Wert "Alpha" enthält, ist das Ergebnis von m einString += "bet" die Zeichenkette "Alphabet" und diese Zeichenkette wird sogleich der Variablen m einString zugewiesen.

Spezielle Operatoren

JavaScript stellt die folgenden speziellen Operatoren zur Verfügung:

  • Konditionaler Operator
  • Komma-Operator
  • delete
  • in
  • instanceof
  • new
  • this
  • typeof
  • void

Konditionaler Operator

Der konditionale Operator ist der einzige JavaScript-Operator, der drei Operanden entgegennimmt. Der Operator liefert nach der Auswertung einer Bedingung einen von zwei Werten als Ergebnis.

Syntax:

Bedingung ? Wert1 : Wert2

Wenn die Auswertung der Bedingung den Wert true ergibt, hat der Operator den Wert von Wert1, andernfalls den Wert von Wert2. Der Operator kann überall anstatt eines Standardoperators benutzt werden. Auch anstatt von if-else Anweisungen kann der Operator eingesetzt werden.

Beispiel:

var status = (alter >= 18) ? "erwachsen" : "minderjährig";

Diese Anweisung weist der Variablen status den Wert "erwachsen" oder "minderjährig" zu, je nachdem ob der Wert der Variablen alter größer oder gleich 18 ist.

Komma-Operator

Der Kommaoperator (,) wertet einfach beide seiner Operanden (links und rechts) aus und liefert den Wert des zweiten Operanden zurück. Dieser Operator wird vorwiegend innerhalb von for-Schleifen benutzt, um mit mehreren Variablen innerhalb der Schleife zu arbeiten.

Wenn z.B. a ein zweidimensionales Array mit 10 Elementen auf jeder Seite ist, wird beim folgenden Code der Komma-Operator benutzt, um zwei Variablen jeweils bei einem Schleifendurchlauf zu verändern. Der Code gibt die Werte der Array-Elemente aus, die in der Diagonale stehen:

for (var i = 0, j = 9; i <= 9; i++, j--)
  document.writeln("a[" + i + "][" + j + "]= " + a[i][j]);

delete

Der delete-Operator löscht ein Objekt, eine Obejekteigenschaft oder ein Element eines spezifizierten Index in einem Array.

Syntax:

delete objektName;
delete objektName.Eigenschaft;
delete objektName[Index];
delete Eigenschaft; // nur zulässig innerhalb einer with-Anweisung

objektName ist der Name eines Objekts, Eigenschaft ist eine existierende Eigenschaft und Index eine Zahl, welche die Position eines Elements innerhalb eines Arrays angibt.

Die vierte Anweisung ist nur innerhalb einer with-Anweisung erlaubt, um eine Eigenschaft eines Objekts zu entfernen.

Mit dem delete-Operator kann man Variablen löschen, die implizit deklariert wurden, nicht jedoch solche, die mit var deklariert wurden.

Wenn der delete-Operator beim Löschen erfolgreich ist, setzt er die Eigenschaft oder das Element auf undefined. Der delete-Operator gibt true zurück, wenn die Ausführung möglich ist, ansonsten wird false zurückgegeben.

x = 42;
var y = 43;
meinobj = new Number();
meinobj.h = 4;    // erzeuge Eigenschaft h
delete x;       // ergibt true (löschen funktioniert, da implizit deklariert)
delete y;       // ergibt false (löschen funktioniert nicht, weil mit var deklariert)
delete Math.PI; // ergibt false (löschen funktioniert nicht, weil vordefinierte Eigenschaft)
delete meinobj.h; // ergibt true (löschen funktioniert bei benutzerdefinierten Eigenschaften)
delete meinobj;   // ergibt true (löschen funktioniert, da implizit deklariert)
Löschen von Array-Elementen

Wenn man ein Array-Element löscht, wird die Länge des Arrays dadruch nicht beeinflusst. Zum Beispiel ist nach dem Löschen von a[3] das Element a[4] immer noch a[4], jedoch ist a[3] nun undefined.

Wenn der delete-Operator ein Element entfernt, ist dieses Element nicht mehr im Array. Beim folgenden Beispiel wird trees[3] mit delete entfernt. Anschließend ist das Element trees[3] zwar noch immer addressierbar, liefert jedoch undefined.

var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
delete trees[3];
if (3 in trees) {
  // wird nicht ausgeführt
}

Wenn man möchte, dass ein Array-Element zwar exisitert, aber undefined ist, sollte man das undefined-Schlüsselwort statt des delete-Operators benutzen. Beim folgenden Beispiel wird trees[3] der Wert undefined zugewiesen und das Array-Element bleibt bestehen:

var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
trees[3] = undefined;
if (3 in trees) {
  // wird ausgeführt
}

in

Der in-Operator gibt true zurück, wenn die entsprechende Eigenschaft im angegebenen Objekt existiert.

Syntax:

EigenschNameOderNummer in objektName

wobei EigenschNameOderNummer ein String oder ein numerischer Ausdruck sein kann, der für einen Eigenschaftsnamen oder Array-Index steht und objektName der Name eines Objekts ist.

Das Beispiel demonstriert die Benutzung des in-Operators:

// Arrays
var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
0 in trees;        // ergibt true
3 in trees;        // ergibt true
6 in trees;        // ergibt false
"bay" in trees;    // ergibt false (es muss die Index-Nummer angegeben werden,
                   // nicht der Wert)
"length" in trees; // ergibt true (length ist eine Array-Eigenschaft)

// Vordefinierte Objekte
"PI" in Math;          // ergibt true
var myString = new String("coral");
"length" in myString;  // ergibt true

// Selbstdefinierte Objekte
var mycar = {make: "Honda", model: "Accord", year: 1998};
"make" in mycar;  // ergibt true
"model" in mycar; // ergibt true

instanceof

Der instanceof-Operator gibt true zurück, wenn das spezifizierte Objekt vom angegebenen Typ ist.

Syntax:

objektName instanceof objektTyp

wobei objektName der Name des Objekts ist, dessen Typ mit objektTyp verglichen werden soll; objektTyp is ein Objekttyp, wie z.B. Date oder Array.

Mit instanceof überprüft man während der Laufzeit, ob ein Objekt von einem bestimmten Typ ist. Zum Beispiel kann man bei der Fehlerbehandlung je nach Typ unterschiedlichen Code ausführen lassen.

Der folgende Code greift auf instanceof zurück, um festzustellen, ob es sich bei derTag um ein Date-Objekt handelt und weil dies zutrifft, wird der Code der if-Anweisung ausgeführt.

var theDay = new Date(1995, 12, 17);
if (theDay instanceof Date) {
  // statements to execute
}

new

Mit dem new-Operator erzeugt man Instanzen eines benutzerdefinierten Objekts oder Instanzen von vordefinierten Objekten wie Array, Boolean, Date, Function, Image, Number, Object, Option, RegExp, oder String. Serverseitig kann er auch mit DbPool, Lock, File, oder SendMail benutzt werden.

Syntax:

var objektName = new objektTyp([param1, param2, ..., paramN]);

Objekte kann man auch mit Objekt-Intializern erzeugen, wie unter Benutzung von Objekt-Literalen beschreiben.

Auf der Seite new-Operator der JavaScript-Referenz findet man mehr Informationen.

this

Mit dem Schlüsselwort this wird auf das aktuelle Objekt verwiesen. Im Allgemeinen steht this für das aufrufende Objekt in einer Methode.

Syntax:

this["propertyName"]
this.propertyName

Beispiel 1.
Angenommen eine Funktion validieren validiert die Eigenschaft value eines Objekts, wobei ihr das Objekt und zwei Werte übergeben werden:

function validieren(obj, lowval, hival){
  if ((obj.value < lowval) || (obj.value > hival))
    alert("Falscher Wert!");
}

Dann könnte validieren für jedes Formelement über den Event-Handler onChange aufgerufen werden, indem mit this auf dieses Formelement verwiesen wird:

<B>Enter a number between 18 and 99:</B>
<INPUT TYPE="text" NAME="age" SIZE=3
   onChange="validate(this, 18, 99);">

Beispiel 2.

Kombiniert mit den Eigenschaften von form kann this auf das Eltern-Formular des aktuellen Objekts verweisen. Beim folgenden Beispiel enthält das Formular myForm ein Text-Objekt und einen Button. Wenn der Benutzer den Button anklickt, wird der Wert des Objekts Text als Name für des Formulars festgelegt. Beim onClick-Eventhandler des Buttons wird this.form benutzt, um auf das Elternformular myForm zu verweisen.

<FORM NAME="myForm">
Form name:<INPUT TYPE="text" NAME="text1" VALUE="Beluga">
<P>
<INPUT NAME="button1" TYPE="button" VALUE="Show Form Name"
   onClick="this.form.text1.value = this.form.name;">
</FORM>

typeof

Der typeof-Operator kann auf zwei verschiedene Arten benutzt werden:

  1. typeof operand
  2. typeof (operand)

Der typeof-Operator gibt einen String mit der Bezeichung für den Typ des übergebenen Operanden zurück. Der Operand kann ein String, eine Variable, ein Schlüsselwort oder ein Objekt sein. Die Klammern sind optional.

Angenommen es werden die folgenden Variablen definiert:

var myFun = new Function("5 + 2");
var shape = "round";
var size = 1;
var today = new Date();

Dann liefert der typeof-Operator folgende Ergbnisse:

typeof myFun;     // liefert "function"
typeof shape;     // liefert "string"
typeof size;      // liefert "number"
typeof today;     // liefert "object"
typeof dontExist; // liefert "undefined"

Für die Schlüsselwörter true und null liefert der typeof-Operator die folgenden Ergebnisse:

typeof true; // liefert "boolean"
typeof null; // liefert "object"

Für eine Zahl oder einen String liefert typeof-Operator die folgenden Ergbnisse:

typeof 62;            // liefert "number"
typeof 'Hello world'; // liefert "string"

Für Eigenschaftswerte liefert der typeof-Operator den Typ des Wertes, welcher der Eigenschaft zugewiesen ist:

typeof document.lastModified; // liefert "string"
typeof window.length;         // liefert "number"
typeof Math.LN2;              // liefert "number"

Für Methoden und Funktionen liefert der typeof-Operator die folgenden Ergebnisse:

typeof blur;        // liefert "function"
typeof eval;        // liefert "function"
typeof parseInt;    // liefert "function"
typeof shape.split; // liefert "function"

Für vordefinierte Objekte liefert der typeof-Operator die folgenden Ergebnisse:

typeof Date;     // liefert "function"
typeof Function; // liefert "function"
typeof Math;     // liefert "object"
typeof Option;   // liefert "function"
typeof String;   // liefert "function"

void

Der void-Operator kann auf zwei verschiedene Arten benutzt werden:

  1. void (Ausdruck)
  2. void Ausdruck

Der void-Operator spezifiziert einen Ausdruck, ohne dass ein Wert zurückgegeben wird. Ausdruck ist ein JavaScript-Ausdruck. Die Klammern sind optional, aber es ist guter Stil diese einzusetzen.

Mit dem void-Operator kann ein Ausdruck als Hyperlink verwendet werden. Der Ausdruck wird dann ausgewertet, jedoch nicht an der Stelle des Dokuments geladen.

Mit folgendem Code wird ein ein Hyperlink erstellt, der nichts bewirkt, wenn der Benutzer ihn anklickt. Wenn der Benutzer den Link anklickt, evaluiert void(0) zu indefined, was bei Javascript keinen Effekt hat.

<A HREF="javascript:void(0)">Hier klicken damit nichts passiert</A>

Im nächsten Beispiel wird ein Formualr gesendet, wenn der Benutzer den Link anklickt.

<A HREF="javascript:void(document.form.submit())">
Click here to submit</A>

Operatorenreihenfolge

Die Reihenfolge der Operatoren bestimmt, welche Operatoren eines Ausdrucks zuerst ausgewertet werden. Die Reihenfolge der Auswertung kann mit Hilfe von Klammern beeinflusst werden.

Die folgende Tabelle beschreibt die Reihefolge der Operatoren.

In Übereinstimmung mit einer Diskussion, wurde diese Tabelle so verändert, dass der Operator mit der höchsten Priorität zuerst und der Operator mit der niedrigsten Priorität am Ende der Tabelle steht.

Table 3.7 Operatorenreihenfolge
Operator-Typ | zugehörige Operatoren
Mitglied | . []
Aufruf/Instanzierung | () new
Negation/Inkrementierung | ! ~ - + ++ -- typeof void delete
Multiplikation/Division/Modulo | * / %
Addition/Subtraktion | + -
Bitwise Verschiebung | << >> >>>
Relation | < <= > >= in instanceof
Gleichheit | == != === !==
Bitweises UND | &
Bitweises XODER | ^
Bitweises ODER | |
Logisches UND | &&
Logisches ODER | ||
Konditional | ?:
Zuweisung | = += -= *= /= %= <<= >>= >>>= &= ^= |=
Komma | ,

Eine detailliertere Version dieser Tabelle, komplett mit Links und detaillieter Beschreibung der einzelnen Operatoren, findet man in der JavaScript-Referenz.


Reguläre Ausdrücke

Reguläre Ausdrücke sind Muster, die eingesetzt werden, um Text auf bestimmte Zeichenkombinationen zu überprüfen. Auch reguläre Ausdrücke sind Objekte bei JavaScript. Die Muster werden mit den Methoden exec und test von RegExp und den Methoden match, replace, search und split von String verwendet. Dieses Kapitel beschreibt reguläre Ausdrücke in JavaScript.

Erstellen von regulären Ausdrücken

Ein regulärer Ausdruck kann auf zwei verschiedene Arten erstellt werden.

Zum einen kann ein Literal verwendet werden:

var re = /ab+c/;

Hierbei findet die Komplilierung des regulären Ausdrucks während der Auswertung des Skripts statt. Wenn der reguläre Ausdruck konstant bleibt, ist diese Vorgehensweise aufgrund der schnelleren Ausführungsgeschwindigkeit empfehlenswert.

Zum anderen kann wie folgt die Konstruktorfunktion des Objekts RegExp zum Einsatz kommen:

var re = new RegExp("ab+c");

Bei der Benutzung der Konstruktorfunktion wird der reguläre Ausdruck während der Laufzeit kompiliert. Sie sollte verwendet werden, wenn man weiß, dass sich das Muster während der Laufzeit ändert oder man das Muster noch nicht kennt und von einer anderen Quelle, wie z. B. der Benutzereingabe, bezieht.

Schreiben von Mustern für reguläre Ausdrücke

Ein Muster eines regulären Ausdrucks wird aus einfachen Zeichen, wie z. B. /abc/ oder einer Kombination von einfachen und speziellen Zeichen, wie z. B. /ab*c/ oder /Kapitel (\d+)\.\d*/ erstellt. Das letzte Beispiel enthält Klammern, die zur Speicherung verwendet werden. Die Übereinstimmung im Text, welche auf das eingeklammerte Muster passt, wird hierbei für die spätere Verwendung hinterlegt, wie unter Benutzung von geklammerten Mustern beschrieben.

Verwendung von einfachen Mustern

Einfache Muster werden aus Zeichen konstruiert, für die man eine direkte Übereinstimmung finden möchte. Zum Beispiel passt das Muster /abc/ nur dann für eine Zeichenkette, wenn exakt die Zeichen "abc" aufeinanderfolgend in dieser Reihenfolge vorkommen. Solch eine Übereinstimmung kommt z. B. in der Zeichenkette "Hallo, kennst du das abc?" und "Das müssen wir noch abchecken." vor. In beiden Fällen enthält die Zeichenkette die Zeichenfolge "abc". In der Zeichenkette "Stab" gibt es keine Übereinstimmung, weil die Zeichenfolge "abc" darin nich vorkommt.

Verwendung von speziellen Zeichen

Wenn bei der Suche nicht nur nach einer direkten Übereinstimmung, sondern z. B. nach einem oder mehreren aufeinanderfolgenden Vorkommen des Buchstabens "b" oder nach Leerräumen (sowohl Tabulator, Leerzeichen als auch Steuerzeichen) gesucht wird, kommen spezielle Zeichen zum Einsatz. Zum Beispiel stimmt das Muster /ab*c/ mit allen Zeichenkombinationen überein, die ein "a", dann sofort nachfolgend 0 oder mehrere "b" (Das Sternchen bedeutet: 0 oder mehr Vorkommen des vorherigen Zeichens) und anschließend sofort ein "c" enthalten. Demnach wird in der Zeichenfolge "cbbabbbbcdebc" eine Übereinstimmung gefunden, weil das Muster auf die Zeichenfolge "abbbbc" passt.

Die folgende Tabelle zeigt eine komplette Liste der speziellen Zeichen, die in regulären Ausdrücken verwendet werden, mit einer Beschreibung zu deren Bedeutung.

Tabelle 4.1 Spezielle Zeichen in regulären Ausdrücken
Character | Meaning
\ Eine der folgenden Bedeutungen:
  • Für Zeichen, die normalerweise buchstäblich interpretiert werden, wird mit der Voranstellung des Rückstrichs dafür gesorgt, dass das Zeichen als speziell und nicht als normales Zeichen interpretiert wird. Zum Beispiel stimmt /b/ mit dem Zeichen "b" überein. Stellt man dem "b" einen Rückstrich voran, also /\b/, wird das Zeichen als spezielles Zeichen - in diesem Fall als Wortgrenze - gedeutet.
  • Bei Zeichen, die normalerweise sofort als spezielle Zeichen interpretiert werden, bewirkt der Rückstrich genau das Gegenteil, d.h. dem Zeichen wird die spezielle Bedeutung genommen und es wird als einfaches Zeichen interpretiert. Zum Beispiel ist das Zeichen * (Sternchen) ein spezielles Zeichen mit der Bedeutung: 0 oder mehr Vorkommen des vorherigen Zeichens. Der Ausdruck /a*/ steht demnach für 0 oder mehr a's. Um dem Zeichen * (Sternchen) die spezielle Bedeutung zu nehmen, stellt man ihm einen Rückstrich voran, z. B. stimmt /a\*/ mit 'a*' überein - der Stern wird nun als einfaches Zeichen interpretiert.
  • Das Maskieren eines Zeichens durch die Voranstellung eines Rückstrichs wird auch als escapen bezeichnet. Außerdem sollte man sich merken, dass bei Verwendung der new RegExp("pattern")-Notation auch der Rüchstrich selbst eine spezielle Bedeutung hat und maskiert werden muss, wenn er als normales Zeichen interpretiert werden soll.
^

Das Zirkumflex-Zeichen steht für den Anfang der Eingabe. Wenn das Flag für mehrere Zeilen (multiline flag) gesetzt ist, wird auch eine Übereinstimmung direkt hinter dem Zeichen für einen Zeilenumbruch (\n) gefunden.

Der Ausdruck /^A/ passt zwar auf das "A" in "Apfel", nicht jedoch auf das "A" in "Ein Apfel", weil das "A" bei dieser Zeichenkette nicht am Anfang steht.

Achtung: Wenn das Zeichen innerhalb von eckigen Klammern (einer Zeichenauswahl) steht, hat es eine andere Bedeutung (siehe [^xyz] in dieser Tabelle).

Zum Beispiel stimmt /[^a-zA-Z\s]/ mit der "3" in "Ich habe 3 Schwestern" überein. Die eckigen Klammern kennzeichnen die Zeichenauswahl. Das Zirkumflex innerhalb dieser Klammern negiert die aufgezählten Zeichen: nicht a bis z, nicht A bis Z und keine Leerräume wie das Leerzeichen oder die Steuerzeichen; übrig bleibt allein die "3".
$

Das Dollarzeichen steht für das Ende der Eingabe. Wenn das Flag für mehrere Zeilen (multiline flag) gesetzt ist, wird auch eine Übereinstimmung direkt hinter dem Zeichen für einen Zeilenumbruch (\n) gefunden.

Zum Beispiel wird mit /t$/ bei "Butter" keine Übereinstimmung gefunden, jedoch bei "Brot", weil das "t" am Ende steht.
*

Das Sternchen steht für eine beliebige Anzahl des vorangestellten Zeichens, also 0-mal oder öfter. Das Zeichen muss also nicht unbedingt vorkommen, sondern darf auch einfach nicht vorhanden sein. Das Sternchen wird gerne in Kombination mit dem . (Punkt) als Platzhalter für "ein beliebiges Zeichen beliebig oft" verwendet.

Der Ausdruck /bu*/ passt auf "buuuuu" in "Ein Geist buuuuuht" als auch auf "b" in "Ein Blauwal blubbert", jedoch nicht auf "Eine Katze kratzt".
+

Steht für das vorangestellte Zeichen einmal oder öfter und ist equivalent zu {1,}. Das Zeichen muss für einen Treffer also mindestens einmal vorkommen.

Zum Beispiel passt /a+/ auf "a" in "Schokolade" und alle a's in "Schokolaaaade".
?

Steht für das vorangestellte Zeichen 0 oder einmal und ist equivalent zu {0,1}.

Zum Beispiel passt /e?le?/ sowohl auf "el" in "Engel", als auch auf "le" in "Kapelle" und auch "l" in "Oslo".

Wenn es sofort hinter einen der Quantoren *, +, ?, oder {} gestellt wird, deaktiviert es die standardmäßige "Gierigkeit" (eine möglichst hohe Anzahl von passende Zeichen einschließend) dieser Quantoren und macht sie "genügsam" (eine möglichst geringe Anzahl von passende Zeichen einschließend).

Zum Beispiel passt der Ausdruck /\d+/ auf die Zeichenkette "123abc" angewendet auf "123", weil der Quantor + (Pluszeichen) "gierig" ist. Der Ausdruck /\d+?/ steht hingegen nur für "1", weil der Quantor + (Pluszeichen) aufgrund der Beeinflussung durch das hintangestellte ? (Fragezeichen) "genügsam" ist.

Das Fragezeichen wird auch bei sogenannten lookahead assertions (vorausschauenden Annahmen/Behauptungen) eingesetzt, die in dieser Tabelle unter x(?=y) und x(?!y) beschrieben sind.
.

Der Punkt steht für ein einzelnes beliebiges Zeichen mit Ausnahme des Zeichens für den Zeilenumbruch.

Zum Beispiel passt /.n/ auf "in" und "än" in der Zeichenkette "nur ein Apfel hängt am Baum", jedoch nicht auf das "n" ganz am Anfang, weil vor dem "n" ein beliebiges Zeichen stehen muss.
(x)

Steht für die Zeichen "x", also die eingeklammerten Zeichen. Außerdem werden die Zeichen gespeichert, die mit dem einklammerten Muster übereinstimmen, damit sie an anderer Stelle wieder eingesetzt werden können. Die Klammern werden auch capturing parentheses (aufnehmende Klammern) genannt.

Zum Beispiel steht /(foo)/ für "foo" in "foobar" und gleichzeitig wird "foo" gespeichert. Die passenden Zeichen können durch Array-Elemente wieder eingesetzt werden. Dabei steht [1] für die Zeichen des ersten Klammerpaars, [2] für die Zeichen des zweiten Paars usw.
(?:x) | Steht für die Zeichen "x", aber die Übereinstimmung wird nicht gespeichert. Diese Klammern werden auch als non-capturing parentheses bezeichnet. Die übereinstimmenden Zeichen können nicht wieder eingesetzt werden und die Array-Elemente [1], ..., [n] finden keine Anwendung.
x(?=y)

Steht für die Zeichen "x", jedoch nur wenn "x" vor "y" steht. Dies wird als lookahead (Vorausschauen) bezeichnet.

Zum Beispiel passt /Mustermann(?=Max)/ auf "Mustermann" nur dann, wenn "Max" dahinter steht. Der Ausdruck /Mustermann(?=Max|Erika)/ passt auf "Mustermann" nur dann, wenn dahinter "Max" oder "Erika" steht. Doch weder "Max" noch "Erika" ist Teil des übereinstimmenden Ergebnisses.
x(?!y)

Passt nur dann auf die Zeichen "x", wenn "x" nicht vor den Zeichen "y" steht. Dies wird auch als negated lookahead (negierte vorausschauende Annahme) bezeichnet.

Zum Beispiel passt /\d+(?!\.)/ nur dann auf eine Zahl, wenn diese nicht vor einem Dezimalpunkt steht. Der reguläre Ausdruck /\d+(?!\.)/.exec("3.141") passt auf "141", jedoch nicht auf "3.141".
x|y

Passt auf entweder die Zeichen "x" oder die Zeichen "y".

Zum Beispiel passt der Ausdruck /grüner|roter/ auf "grüner" in "grüner Apfel" und "roter" in "roter Apfel".
{n}

Das n steht für eine postive ganze Zahl. Das Muster passt exakt auf die Anzahl n des vorangestellten Zeichens.

Zum Beispiel passt /a{2}/ nicht auf das "a" in "Schokolade", jedoch auf alle beide a's in "Schokolaade" und die ersten beiden a's in "Schokolaaade".
{n,m}

Wobei n und m positive ganze Zahlen sind. Passt auf mindests n und höchstens m Vorkommen des vorangestellten Zeichens. Wenn für n oder m eine 0 stehen soll, kann diese auch weggelassen werden.

Zum Beispiel passt /a{1,3}/ auf nichts in "cndy", jedoch auf das "a" in "candy", die beiden a's in "caandy," und die ersten drei a's in "caaaaaaandy". Merke: Wenn der Ausdruck auf "caaaaaaandy" angewendet wird, ist die Übereinstimmung "aaa", auch wenn in der Zeichenkette mehr a's enthalten sind.
[xyz]

Die eckigen Klammern kennzeichnen eine Zeichenauswahl. Der Ausdruck stimmt mit allen in den Klammern stehenden Zeichen überein. Mit Hilfe eines Bidestrichs kann ein Bereich (z. B. von a bis z) festgelegt werden. Spezielle Zeichen (wie der Punkt oder das Sternchen) haben keine spezielle Bedeutung innerhalb der eckigen Klammern. Sie müssen nicht "escaped" werden. Escape-Sequenzen funktionieren ebenfalls.

Zum Beispiel ist [abcd] dasselbe wie [a-d]. Beides passt auf das "b" in "biegen" und das "d" in "denken". Die Ausdrücke /[a-z.]+/ und /[\w.]+/ passen beide auf "test.e.n". Der Punkt ist in diesem Fall kein spezielles Zeichen, weil er innerhalb der eckigen Klammern (Zeichenauswahl) steht.
[^xyz]

Eine negierte oder komplementäre Zeichenauswahl. Das Zirkumflex-Zeichen an erster Stelle innerhalb der Klammern negiert die Zeichenauswahl. Der Ausdruck passt also auf alle Zeichen, die nicht in den Klammern stehen. Es kann mit Hilfe eines Bindestrichs auch ein Bereich von Zeichen (z. B. von a bis z) festgelegt werden. Alle Zeichen, die bei einer normalen Zeichenauswahl funktionieren, funktionieren hier ebenso.

Zum Beispiel ist [^abc] dasselbe wie [^a-c]. Beide Ausdrücke passen auf das "r" in "brechen" und das "m" in "campen".
[\b] | Passt auf das Steuerzeichen für die Backspace-Tastet (U+0008), welche den Cursor um eine Position nach links verschiebt und das dort stehende Zeichen entfernt. (Sollte nicht mit \b verwechselt werden.)
\b

Steht für eine Wortgrenze. Wortgrenzen sind an den Stellen, wo kein anderes Wortzeichen vor oder hinter einem Wortzeichen steht. Eine Wortgrenze wird nicht in die Übereinstimmung aufgenommen. Mit anderen Worten ist die Länge einer Wortgrenze gleich 0 - die Wortgrenze ist der Zwischenraum zwischen einem Wortzeichen und einem Zeichen, das kein Wortzeichen ist oder zwischen einem Wortzeichen und dem Anfang oder Ende der Eingabe. (Sollte nicht mit [\b] verwechselt werden.)

Beispiele:
/\bMon/ passt auf das "Mon" in "Mond" ;
/ll\b/ passt nicht auf "ll" in "Falle", weil "ll" nicht vor einer Wortgrenze steht, sondern vor dem Zeichen "e", wobei es sich um ein Wortzeichen handelt.
/ond\b/ passt auf das "ond" in "Mond", weil "ond" das Ende der Zeichenkette ist, also nicht vor einem Wortzeichen steht.
Bei /\w\b\w/ wird niemals eine Übereinstimmung gefunden, weil eine Wortgrenze niemals gleichzeitig vor einem Wortzeichen und keinem Wortzeichen stehen kann.
\B

Steht für alle Zeichen, die keine Wortgrenze sind. Dies trifft auf Positionen zu, wo zwei nebeneinanderstehende Zeichen vom selben Typ sind: Entweder sind beide Wortzeichen oder keine Wortzeichen. Der Anfang und das Ende einer Zeichenkette werden nicht als Wortzeichen angesehen, gelten also als Wortgrenzen.

For example, /\B../ matches 'oo' in "noonday" (, and /y\B./ matches 'ye' in "possibly yesterday."

Zum Beispiel passt /\B../ auf "at" in "Katze" und /e\B./ auf "ei" in "schlafe ein".
\cX

X ist ein Zeichenbereich von A bis Z. Passt auf ein Steuerzeichen in einer Zeichkette.

Zum Beispiel steht /\cM/ für das Steuerzeichen control-M (U+000D) in einer Zeichenkette.
\d

Steht für Ziffern und ist equivalent zu [0-9].

Zum Beispiel passt /\d/ oder /[0-9]/ auf "2" in "B2 ist die Zimmernummer."
\D

Steht für alle nicht-Ziffern und ist equivalent zu [^0-9].

Zum Beispiel passt /\D/ oder /[^0-9]/ auf "B" in "B2 ist die Zimmernummer."
\f | Steht für einen Seitenvorschub (engl. f orm feed (U+000C)).
\n | Steht für einen Zeilenbruch (engl. line feed bzw. new line U+000A).
\r | Steht für einen Waagenrücklauf (engl. carriage return (U+000D).
\s

Steht für ein einzelnes Leerraum-Zeichen, einschließlich Leertaste, Tabulator, Seitenvorschub und Zeilenumbruch und ist equivalent zu [ \f\n\r\t\v​\u00A0\u1680​\u180e\u2000​\u2001\u2002​\u2003\u2004​ \u2005\u2006​\u2007\u2008​\u2009\u200a​\u2028\u2029​\u2028\u2029​ \u202f\u205f​\u3000].

Zum Beispiel passt /\s\w*/ auf "bar" in "foo bar".
\S

Steht für ein einzelnes Zeichen, das kein Leerraum-Zeichen ist. Equivalent zu [^ \f\n\r\t\v​\u00A0\u1680​\u180e\u2000​\u2001\u2002​\u2003\u2004​ \u2005\u2006​\u2007\u2008​\u2009\u200a​\u2028\u2029​\u2028\u2029​ \u202f\u205f​\u3000].

Zum Beispiel passt /\S\w*/ auf "foo" in "foo bar".
\t | Steht für einen Tabulator (engl. horizontal tab U+0009).
\v | Steht für einen vertikalen Tabulator (engl. vertical tabulator (U+000B).
\w

Steht für ein alphanumerisches Zeichen (Wortzeichen), einschließlich Unterstrich. Equivalent zu [A-Za-z0-9_].

Zum Biespiel passt /\w/ auf "a" in "apple" als auch auf "5" in "$5.28" und "3" in "3D".
\W

Steht für ein nicht-alphanumerisches Zeichen (nicht-Wortzeichen). Equivalent zu [^A-Za-z0-9_].

Zum Beispiel passt /\W/ oder /[^A-Za-z0-9_]/ auf "%" in "50%".
\n

Stellt einen Rückgriff auf die passenden Zeichen eines eingeklammerten Teilausdrucks dar, wobei n eine positive ganze Zahl ist. Die Zahl steht hierbei für das jeweilge Teilmuster innerhalb eines Klammerpaars an entsprechender Stelle (öffnende Klammern werden gezählt).

Zum Beispiel passt /Apfel(,)\sOrange\1/ auf "Apfel, Orange," in "Apfel, Orange, Kirsche, Pfirsich".
\0 | Steht für ein NULL-Zeichen (U+0000). Es sollte kein Ziffer dahinter stehen, weil \0<Ziffer> eine Escape-Sequenz für oktale Zahlen ist.
\xhh | Steht für das Zeichen mit dem Code hh (zweistellige hexadezimale Zahl).
\uhhhh | Steht für das Zeichen mit dem Code hhhh (vierstellige hexadezimale Zahl).

Benutzung von Klammern

Klammern werden verwendet, um den eingeklammerten Teil des regulären Ausdrucks zu speichern. Einmal gespeichert kann dieser Teil an anderer Stelle wieder eingesetzt werden, wie unter Benutzung von geklammerten Mustern beschrieben.

Zum Beispiel beinhaltet der Ausdruck /Kapitel (\d+)\.\d*/ einige maskierte und spezielle Zeichen. Mit den Klammern wird bewirkt, dass die Zeichen, welche mit dem eingeklammerten Muster übereinstimmen, gespeicert werden. Der Ausdruck stimmt genau mit der Zeichenkette "Kapitel " gefolgt von einer oder mehreren Ziffern (\d steht für eine Ziffer und das + (Pluszeichen) bedeutet 1 oder mehr Vorkommen), einem Punkt (der Punkt selbst ist standardmäßig ein spezielles Zeichen, weshalb er maskiert werden muss) und nochmal beliebig vielen Ziffern (\d steht für eine Ziffer und der Stern bedeutet 0 oder mehr Vorkommen) überein. Zusätzlich werden die Klammern eingesetzt, um die erste Ziffer zu speichern.

Eine Übereinstimmung wird z. B. in "Offenes Kapitel 4.3, Absatz 6" gefunden und dabei wird "4" gespeichert. Auf die Zeichenkette "Kapitel 3 und 4" passt der Ausdruck nicht, weil hinter der ersten Ziffer "3" kein Punkt steht.

Möchte man einen zusammenhängenden Teil einer Zeichenkette suchen, jedoch nicht speichern, setzt man ?: an den Anfang innerhalb der Klammern. Zum Beispiel stimmt (?:\d+) mit einer oder mehr Ziffern überein, aber diese Ziffern werden nicht gespeichert.

Arbeiten mit regulären Ausdrücken

Reguläre Ausdrücke werden mit den Methoden exec und test von RegExp und den Methoden match, replace, search und split von String verwendet. Diese Methoden sind in der JavaScript Referenz beschrieben.

Table 4.2 Methoden die reguläre Ausdrücke verwenden
Method | Description
exec | Eine Methode von RegExp, die eine Suche nach einer Übereinstimmung in einer Zeichenkette durchführt. Sie gibt ein Array mit den Übereinstimmungen zurück.
test | Eine Methode von RegExp, die eine Zeichenkette auf eine Übereinstimmung überprüft. Sie gibt true oder false zurück.
match | Eine Methode von String, die eine Suche nach einer Übereinstimmung in einer Zeichenkette durchführt. Sie gibt ein Array zurück oder null bei keinem Treffer.
search | Eine Methode von String, die eine Zeichenkette auf eine Übereinstimmung überprüft. Sie gibt den Index der Übereinstimmung zurück oder -1 bei keinem Treffer.
replace | Eine Methode von String, die eine Suche nach einer Übereinstimmung in einer Zeichenkette durchführt und die Übereinstimmungen durch eine andere Zeichenkette ersetzt.
split | Eine Methode von String, die anhand eines regulären Ausdrucks oder einer festen Zeichkette eine Zeichenkette trennt und die Teile als Array zurückgibt.

Wenn man nur wissen möchte, ob ein regulärer Ausdruck auf eine Zeichenkette passt, sollten die Methoden test oder search zum Einsatz kommen. Möchte man mehr erfahren, sollte man auf die Methoden exec oder match zurückgreifen (langsamere Ausführsgeschwindigkeit). Die Methoden exec und match geben ein Array zurück falls Übereinstimmungen gefunden werden und updaten die Eigenschaften des zugehörigen RegExp-Objekts und des vordefinierten RegExp-Objekts. Wenn keine Übereinstimmungen gefunden werden, geben die Methoden null (konvertiert: false) zurück.

Bei fogendem Beispiel kommt die Methode exec zum Einsatz, um eine Übereinstimmung in einer Zeichnkette zu finden.

var myRe = /d(b+)d/g;
var myArray = myRe.exec("cdbbdbsbz");

Wenn man auf die Eingenschaften des Objekts für den regulären Ausdruck nicht zugreifen möchte, kann man myArray auch direkt wie folgt erzeugen:

var myArray = /d(b+)d/g.exec("cdbbdbsbz");

Möchte man den regulären Ausdruck aus einem String erzeugen, besteht folgende Möglichkeit:

var myRe = new RegExp("d(b+)d", "g");
var myArray = myRe.exec("cdbbdbsbz");

Bei diesen Skripten werden Übereinstimmungen gefunden. Es wird ein Array zurückgegeben und die Eigenschaften geupdatet, welche in der nachstehenden Tabelle aufgelistet sind.

Tabelle 4.3 Ergebnisse von regulären Ausdrücken
Objekt | Eigenschaft oder Index | Beschreibung | Bei diesem Beispiel
myArray |  | Die übereinstimmende Zeichenfolge und alle gespeicherten Teile. | ["dbbd", "bb"]
 | index | Der mit 0 beginnende Index der Übereinstimmung in der Eingabe-Zeichenkette. | 1
 | input | Die ursprüngliche Eingabe-Zeichenkette. | "cdbbdbsbz"
 | [0] | Die zuletzt übereinstimmenden Zeichen. | "dbbd"
myRe | lastIndex | Der Index bei dem die nächste Suche nach einer Übereinstimmung gestartet wird. (Diese Eigenschaft wird nur gesetzt, wenn im regulären Ausdruck die Option "g" verwendet wird, wie unter Erweiterte Suche mit Optionen beschrieben). | 5
 | source | Der Text des Musters - wird geupdatet wenn der reguläre Ausdruck erstellt wird, nicht bei der Ausführung. | "d(b+)d"

Wie das zweite Beispiel zeigt, kann man einen regulären Ausdruck einsetzen, der über einen Objekt-intializer erstellt wurde, ohne die Zuweisung einer Variablen. Bei dieser Vorgehensweise ist jede Übereinstimmung ein neuer Ausdruck. Aus diesem Grund kann nicht auf die Eigenschaften dieses regulären Ausdrucks zugegriffen werden. Angenommen man hat z. B. dieses Skript:

var myRe = /d(b+)d/g;
var myArray = myRe.exec("cdbbdbsbz");
console.log("Der Wert von lastIndex ist " + myRe.lastIndex);

Dann ist die Ausgabe:

Der Wert von lastIndex ist 5

Hat man nun aber dieses Skript:

var myArray = /d(b+)d/g.exec("cdbbdbsbz");
console.log("Der Wert von lastIndex ist " + /d(b+)d/g.lastIndex);

Dann ist die Ausgabe:

Der Wert von lastIndex ist 0

Die einzelnen Vorkommen der Zeichenfolgen, die mit /d(b+)d/g übereinstimmen sind bei den beiden Anweisungen verschiedene Objekte und haben dementstrechend auch verschiedene Werte bei der jeweiligen Eigenschaft lastIndex. Falls man auf die Eigenschaften eines Objekts eines regulärenAusdrucks zugreifen möchte, sollte man diesen vorher einer Variablen zuweisen.

Benutzung von geklammerten Mustern

Klammern werden verwendet, um einen Teil der Zeichen, die mit dem regulären Ausdruck übereinstimmen, zu speichern. Zum Beispiel stimmt /a(b)c/ mit der Zeichenfolge "abc" überein und "b" wird gespeichert. Um die gespiecherten Zeichen wieder abzurufen, verwendet man die Array-Elemente [1], ..., [n].

Die Anzahl der möglichen speicherbaren Teile ist nicht limitiert. Das Array, welches zurückgegeben wird, enthält alle gespeicherten Übereinstimmungen. Die folgenden Beispiele zeigen auf, wie man geklammerte Muster einsetzt.

Beispiel 1

Das folgende Skript benutzt die Methode replace(), um Wörter in einer Zeichenkette auszutauschen. Die beiden Muster für die Wörter (\w+) wurden geklammert, damit die Wörter gespeichert werden. Anschließend werden im Ersetzungstext diese gespeicherten Wörter mit $1 und $2 wieder eingesetzt.

var re = /(\w+)\s(\w+)/;
var str = "Jonas Schmidt";
var newstr = str.replace(re, "$2, $1");
console.log(newstr);

Die Ausgabe ist "Schmidt, Jonas".

Erweiterte Suche mit Optionen (Flags)

Reguläre Ausdrücke kennen vier verschiedene Optionen (Flags), die es z. B. erlauben global nach Übereinstimmungen zu suchen oder die Berücksichtigung der Groß- und Kleinschreibweise zu deaktivieren. Zur Aktivierung der globalen Suche wird das Flag g eingesetzt. Die Berücksichtigung der Groß- und Kleinschreibweise ( engl. case-insensitive ) wird mit dem Flag i abgeschaltet. Die mehrzeilige Suche (engl. multiline) wird mit dem Flag m aktiviert und eine "sticky" Suche, wobei die Übereinstimmung an der aktuellen Postion im Eingabe-Zeichenkette zutreffen muss, wird mit dem Flag y aktiviert. Diese Optionen können getrennt oder gemeinsam verwendet und in beliebiger Reihenfolge angegeben werden. Die Buchsaben werden einfach an den regulären Ausdruck angehängt.

Hinweis zu Firefox 3

Die Unterstützung für das Flag y wurde bei Firefox 3 hinzugefügt. Wenn y aktiviert ist, schlägt die Suche fehl, wenn es keine Übereinstimmung an der aktuellen Position innerhalb der Eingabe-Zeichenkette gibt.

Syntax für die Verwendung von Optionen:

var re = /Muster/Optionen;

oder

var re = new RegExp("Muster", "Optionen");

Die Optionen sind ein integraler Bestandteil des regulären Ausdrucks. Sie können nicht im Nachhinein entfernt oder hinzugefügt werden.

Zum Beispiel wird mit re = /\w+\s/g ein regulärer Ausdruck erstellt, der auf ein oder mehr Zeichen, gefolgt von einem Leerraum, passt. Nach diesem Muster wird über die ganze Zeichenkette hinweg gesucht.

var re = /\w+\s/g;
var str = "fee fi fo fum";
var myArray = str.match(re);
console.log(myArray);

Hier ist die Ausgabe: ["fee ", "fi ", "fo "]. Bei diesem Beispiel könnte man die Zeile:

var re = /\w+\s/g;

mit folger austauschen:

var re = new RegExp("\\w+\\s", "g");

und würde dasselbe Ergebnis erhalten.

Die Option m sorgt dafür, dass eine mehrzeilige Eingabe auch als solche interpretiert wird. Wird die Option m angegeben, passen die speziellen Zeichen ^ und $ auf den Anfang oder das Ende jeder Zeile der Eingabe, anstatt nur auf den Anfang und das Ende der gesamten Eingabe.

Beispiele

Das folgende Beispiel zeigt einige Einsatzmöglichkeiten von regulären Ausdrücken.

Ändern der Reihenfolge in einer Zeichenkette

Das folgende Beispiel veranschaulicht die Anordnung von regulären Ausdrücken und die Verwendung der Methoden string.split() und string.replace(). Im Beispiel wird eine schlecht formatierte Eingabe mit Personennamen (Vorname zuerst), die durch Leerzeichen, Tabulatoren und exakt ein Semikolon getrennt sind, bereinigt. Außerdem wird die Reihenfolge des jeweiligen Namens herumgedreht (Nachname zuerst) und die Liste nach Nachnamen sortiert.

// Die Zeichenkette beinhaltet mehrere Leerzeichen und Tabulatoren
// und könnte mehrere Leerzeichen zwischen vor und Nachnamen enthalten.

var names = "Lieschen Müller ;Erika Mustermann; Michel Deutscher ; Otto Normalverbraucher ; ";

var output = ["---------- Original String\n", names + "\n"];

// Erstellen von zwei regulären Ausdrücken und eines Arrays.
// Die Eingabe-Zeichenkette wird in Array-Elemente zerlegt.

// Muster: Möglicherweise ein Leerraum, dann ein Semikolon
// und möglicherweise nochmal ein Leerraum (0 oder mehr).
var pattern = /\s*;\s*/;

// Splitten der Zeichnkette in einzelne Teile anhand des Musters und
// speichern der Teile in einem Array mit Namen nameList.
var nameList = names.split(pattern);

// Neuer regulärer Ausdruck: ein oder mehr alphanumerische Zeichen,
// dann mindestens ein Leerraum und nochmal mindestens ein Wortzeichen.
// Klammern zum Speichern; Rückgriff auf gespeicherte Teile später.
pattern = /(\w+)\s+(\w+)/;

// Neues Array zum Speichern der bearbeiteten Namen.
var bySurnameList = [];

// Anzeigen des Arrays und befüllen des neuen Arrays
// mit durch Komma getrennten Namen (Nachname zuerst).
// Die Methode replace() entfernt alles, was mit dem Muster übereinstimmt
// und ersetzt es durch den an zweiter Stelle angegebenen String,
// bestehend aus den gespeichrten Teilen.
// Zuerst wird der zweite Teil ($2) eingesetzt und anschließend durch Komma
// und Leerzeichen getrennt der erste Teil ($1).

output.push("---------- Nach Teilung durch regulären Ausdruck");

var i, len;
for (i = 0, len = nameList.length; i < len; i++){
  output.push(nameList[i]);
  bySurnameList[i] = nameList[i].replace(pattern, "$2, $1");
}

// Anzeigen des neuen Arrays.
output.push("---------- Namen reserviert");
for (i = 0, len = bySurnameList.length; i < len; i++){
  output.push(bySurnameList[i]);
}

// Sortieren anhand des Nachnamens und anzeigen des neuen Arrays.
bySurnameList.sort();
output.push("---------- Sortiert");
for (i = 0, len = bySurnameList.length; i < len; i++){
  output.push(bySurnameList[i]);
}

output.push("---------- End");

console.log(output.join("\n"));

Benutzung von speziellen Zeichen zur Verifizierung von Eingaben

Beim nachstehenden Beispiel wird der Benutzer aufgefordert, eine Telefonnumer einzugeben. Wenn der Benutzer den "Prüfen"-Button drückt, überprüft das Skript die Validität der Nummer. Sofern dann sichergestellt ist, dass es sich um eine Telefonnummer handelt (die Eingabe stimmt mit dem Muster des regulären Ausdrucks überein), zeigt das Skript eine Nachricht, um dem Benutzer zu danken und die Nummer zu bestätigen. Wenn die Nummer nicht valide ist (nicht mit dem Muster des regulären Ausdrucks übereinstimmt), wird der Benutzer ebenfalls mit einer Nachricht darüber informiert.

Das Muster des reguläre Ausdruck setzt sich folgendermaßen zusammen: Keine oder eine öffnende Klammer, \(?, gefolgt von drei Ziffern \d{3}, gefolgt von keiner oder einer schließenden Klammer \)?, gefolgt von einem Bindestrich, Schrägstrich, oder Punkt (falls vorhanden wird das Zeichen gespeichert) ([-\/\.]), gefolgt von drei Ziffern \d{3}, gefolgt (falls vorhanden) von dem gespeicherten Zeichen (Bindestrich, Schrägstrich oder Punkt) \1, gefolgt von vier Ziffern \d{4}.

Das Event Change wird ausgelöst, wenn der Benutzer die Enter-Taste drückt, um die Eingabe (den Wert für RegExp.input) zu bestätigen.

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <meta http-equiv="Content-Script-Type" content="text/javascript">
    <script type="text/javascript">
      var re = /\(?\d{3}\)?([-\/\.])\d{3}\1\d{4}/;
      function testInfo(phoneInput){
        var OK = re.exec(phoneInput.value);
        if (!OK)
          window.alert(RegExp.input + " ist keine Telefonnummer mit Vorwahl!");
        else
          window.alert("Danke! Ihre Telefonnummer ist " + OK[0]);
      }
    </script>
  </head>
  <body>
    <p>Bitte ihre Telefonnumer (mit Vorwahl) eingeben und den "Prüfen"-Button anklicken.
        <br>Das Format ist z.B. ###-###-####.</p>
    <form action="#">
      <input id="phone"><button onclick="testInfo(document.getElementById('phone'));">Prüfen</button>
    </form>
  </body>
</html>


Anweisungen (Kontrollfluss)

JavaScript unterstützt einen kompakten Satz von Anweisungen zur Flusskontrolle. Diese können eingesetzt werden, um Websites interaktiv zu gestalten. Dieses Kapitel bietet eine Übersicht zu diesen Anweisungen.

Jeder Ausdruck ist auch eine Anweisung. Das Kapitel Ausdrücke und Operatoren stellt hierzu mehr Informationen bereit.

Zur Trennung von Anweisungen wird das Semikolon (;) verwendet. Die JavaScript-Referenz informiert darüber genauer.

Blockstruktur

Anweisungen werden durch Anweisungsblöcke gruppiert und zusammengefasst. Ein Anweisungsblock wird durch geschweifte Klammern festgelegt:

{
   Anweisung_1;
   Anweisung_2;
   .
   .
   .
   Anweisung_n;
}

Beispiel
Anweisungsblöcke werden vorwiegend in Verbindung mit Anweisungen zum Kontrollfluss eingesetzt (z. B. if, for, while).

while (x < 10){
  x++;
}

Bei diesem Beispiel ist { x++; } der Anweisungsblock.

Wichtig: JavaScript grenzt die Sichtbarkeit (Scope) von Blöcken nicht ab. Variablen, die innerhalb eines Blocks deklariert werden, sind innerhalb der Funktion oder des Skripts sichtbar und bleiben auch innerhalb dieses Bereichs erhalten. Mit anderen Worten: Anweisungsblöcke erzeugen keinen seperaten Sichtbarkeitsbereich. Obwohl es auch eigenständige Blockanweisungen (standalone) gibt und diese zur Syntax gehören, sollte man auf deren Einsatz besser verzichten, da sie nicht auf dieselbe Weise funktionieren wie z. B. bei C oder Java.

Beispiel:

var x = 1;
{
  var x = 2;
}
alert(x); // Ausgabe: 2

Die Ausgabe ist hier 2, weil sich die Anweisung var x innerhalb des Anweisungsblocks im selben Sichtbarkeitsbereich befindet, wie die andere var x-Anweisung außerhalb des Anweisungsblocks. Bei C oder Java wäre die Ausgabe 1.

Bedingte Anweisungen

Bei einer bedingten Anweisung wird ein Satz von Befehlen ausgeführt, wenn eine bestimmte festgelegte Bedingung erfüllt ist. JavaScript kennt hierfür zwei verschiedene Anweisungen: if...else und switch.

if...else-Anweisung

Die if Anweisung sollte eingesetzt werden, wenn bestimmte Anweisungen ausgeführt werden sollen, falls eine logische Bedingungen erfüllt ist. Zusätzlich kann ein optionaler else-Abschnitt festgelegt werden, dessen Anweisungen ausgeführt werden, falls die Bedingung nicht erfüllt ist. Die Syntax einer if Anweisung ist wie folgt:

if (Bedingung)
  Anweisung_1
[else
  Anweisung_2]

Die Bedingung kann jeder Ausdruck sein, der zu true oder false evaluiert. Wenn die Bedingung true ist, wird Anweisung_1 ausgeführt, andernfalls wird Anweisung_2 ausgeführt. Anweisung_1 und Anweisung_2 können für beliebige Anweisungen stehen; auch weitere verschachtelte if-Anweisungen könnten an dieser Stelle eingesetzt werden.

Darüber hinaus ist es mit Hilfe von else if möglich, mehrere Bedingungen nacheinander zu überprüfen (Verzweigung):

if (Bedingung)
  Anweisung_1
[else if (Bedingung_2)
  Anweisung_2]
...
[else if (Bedingung_n_1)
  Anweisung_n_1]
[else
  Anweisung_n]

Sollen mehrere Anweisungen ausgeführt werden, müssen sie innerhalb eines Anweisungsblocks ({ ... }) stehen. Es ist gute Praxis, die Blöcke immer anzugeben, auch wenn sie für einzelne Anweisungen nicht zwingend notwendig sind. Insbesondere bei verschachtelten if-Anweisungen wird hierdurch die Lesbarkeit verbessert.

if (Bedingung) {
  Anweisungen_1
} else {
  Anweisungen_2
}

Auch ist es empfehlenswert keine Zuweisungen an der Stelle für die Bedingung vorzunehmen, weil diese aufgrund des Gleichzeichens leicht mit Vergleichen verwechselt werden können. Der folgende Code sollte also nicht verwendet werden:

if (x = y) {
  /* do the right thing */
}

Wenn man eine Zuweisung in einem bedingten Ausdruck vornehmen möchte, sollte diese in zusätzliche Klammern eingeschlossen werden. Zum Beispiel:

if ((x = y)) {
  /* mach das Richtige */
}

Die folgenden Werte evaluieren zu false:

  • false
  • undefined
  • null
  • 0
  • NaN
  • die leere Zeichenkette ("")

Alle anderen Werte, einschließlich aller Objekte, evaluieren zu true, wenn sie als Bedingung angegeben werden.

Vorsicht: Die primitiven booleschen Werte wie true und false sollten nicht mit den Werten true und false des booleschen Objekts (Boolean) verwechselt werden.

Zum Beispiel:

var b = new Boolean(false);
if (b) // diese Bedingung evaluiert zu true

Beispiel

Beim folgenden Beispiel liefert die Funktion checkData den Wert true, wenn die Anzahl der Zeichen eines Text-Objekts dem Wert 3 entspricht. Andernfalls wird eine Alarm-Nachricht ausgegeben und false zurückgegeben.

function checkData() {
  if (document.form1.threeChar.value.length == 3) {
    return true;
  } else {
    alert("Bitte genau 3 Zeichen eingeben. " +
      document.form1.threeChar.value + " is not valid.");
    return false;
  }
}

switch-Anweisung

Bei der switch-Anweisung wird ein Ausdruck ausgewertet und das Ergebnis mit den unterschiedlichen Ausdrücken der case-Labels verglichen (Fallunterscheidung). Stimmen die Werte überein, werden die Anweisungen des entsprechenden case-Abschnitts ausgeführt.

Beispiel:

switch (Ausdruck) {
   case Label_1:
      Anweisung_1
      [break;]
   case Label_2:
      Anweisung_2
      [break;]
   ...
   default:
      Anweisung_falls_kein_case_Ausdruck_passt
      [break;]
}

Bei diesem Skript wird zuerst nach einem case-Abschnitt gesucht dessen Wert mit dem Wert des in Klammern spezifizierten Ausdrucks übereinstimmt. Wird ein solcher Wert gefunden, werden die Anweisungen des case-Abschnitts ausgeführt. Sollte keiner der Werte mit dem Wert des Ausdrucks übereinstimmen, wird nach einem default-Abschnitt gesucht und falls vorhanden, die Kontrolle an diesen übergeben und die entsprechenden Anweisungen ausgeführt. Falls kein default-Abschnitt vorhanden ist, wird die Ausführung hinter der switch-Anweisung fortgesetzt. Der Konvention nach sollte der default-Abschnitt an letzter Stelle stehen, jedoch ist er keine Pflicht.

Die optionale break-Anweisung innerhalb jedes case-Abschnitts bewirkt den Abbruch der switch-Anweisung. Die Ausführung wird dann hinter der switch-Anweisung fortgesetzt. Wenn break nicht angegeben ist, findet kein Abbruch statt und die Anweisungen folgender case-Abschnitte werden ebenfalls ausgeführt.

Beispiel
Beim folgenden Beispielcode evaluiert fruittype zu "Bananen". Daraufhin wird eine Übereinstimmung beim case-Abschnitt "Bananen" gefunden und die Anweisungen dieses Abschnitts werden ausgeführt. Anschließend wird die switch-Anweisung an der Stelle des break verlassen und die Ausführung hinter dem switch-Block fortgesetzt. Wenn kein break innerhalb des Bananen-Abschnitts angegeben wäre, würde der Abschnitt "Kirschen" ebenfalls (bis zu break) ausgeführt werden.

switch (fruittype) {
   case "Orangen":
      document.write("Orangen kosten 0.59 EUR das Pfund.<br>");
      break;
   case "Birnen":
      document.write("Birnen kosten 0.32 EUR das Pfund.<br>");
      break;
   case "Bananen":
      document.write("Bananen kosten  0.48 EUR das Pfund.<br>");
      break;
   case "Kirschen":
      document.write("Kirschen kosten 3.00 EUR das Pfund.<br>");
      break;
   case "Mangos":
   case "Papayas":
      document.write("Mangos und Papayas kosten 2.79 EUR das Pfund.<br>");
      break;
   default:
      document.write("Entschuldigung, " + fruittype + "sind ausverkauft.<br>");
}
document.write("Darf es sonst noch etwas sein?<br>");

Schleifen-Anweisungen

Eine Schleife ist ein Satz von Befehlen, die solange wiederholt ausgeführt werden, bis eine bestimmte Bedingung erfüllt ist. Javascript unterstützt die Anweisungen for, do while und while, sowie Labels (Labels sind nicht direkt Schleifenanweisungen, werden aber sehr oft mit diesen zusammen verwendet). Zusätzlich können die beiden Anweisungen break und continue zum Einsatz kommen.

Eine weitere Anweisung ist for...in, welche ebenfalls wiederholt Anweisungen ausführt, jedoch zur Manipulation von Objekten verwendet wird. Siehe Anweisungen zur Objekt-Manipulation.

Die Schleifen-Anweisungen sind:

  • for-Anweisung
  • do...while-Anweisung
  • while-Anweisung
  • label-Anweisung
  • break-Anweisung
  • continue-Anweisung

for-Anweisung

Eine for-Schleife führt wiederholt Anweisungen aus, bis eine festgelegte Bedingung zu false evaluiert. Die Syntax der for-Schleife ist ähnlich wie bei Java und C:

for ([Initialisierungsausdruck]; [Bedingung]; [Inkrementierungsausdruck])
   Anweisung

Wenn eine for-Schleife ausgeführt wird, passiert folgendes:

  1. Der Initialisierungsausdruck wird (falls angegeben) ausgeführt. Der Ausdruck initialisiert für gewöhnlich eine oder mehrere einfache Variablen, die zur Schleifenzählung verwendet werden. Die Syntax erlaubt jedoch Anweisungen beliebiger Komplexität. Auch Variablen-Zuweisungen sind erlaubt.
  2. Der Ausdruck Bedingung wird ausgewertet. Wenn der Wert true ist, wird die Anweisung ausgeführt. Wenn der Wert der Bedingung false ist, wird die Schleife abgebrochen. Fehlt der Ausdruck Bedingung komplett, wird angenommen, dass der Wert der Bedingung true ist.
  3. Die Anweisung wird ausgeführt. Bei mehreren Anweisungen muss eine Blockanweisung ({ ... }) zur Gruppierung verwendet werden.
  4. Der Inkrementierungsausdruck (üblicherweise wird die Zählvariable inkrementiert), falls angegeben, wird ausgeführt und der nächste Schleifendurchlauf gestartet (Schritt 2). Dies wird solange wiederholt, bis die Bedingung nicht mehr erfüllt ist.

Beispiel

Die Funktion im folgenden Code enthält eine for-Schleife, welche die Anzahl der ausgewählten Optionen in einer Liste zählt. Die for-Schleife deklariert die Variable i und initialisiert sie mit dem Wert 0. Die Schleife überprüft, ob i kleiner ist als die Anzahl der verfügbaren Optionen des select-Objekts, welches die Auswahl mehrerer Optionen erlaubt. Solange dies zutrifft wird wiederholt der Anweisungsblock mit der if-Anweisung ausgeführt und der Wert der Variablen i nach jedem Schleifendurchlauf um 1 erhöht.

<script type="text/javascript">

function howMany(selectObject) {
   var numberSelected = 0;
   for (var i = 0; i < selectObject.options.length; i++) {
      if (selectObject.options[i].selected)
         numberSelected++;
   }
   return numberSelected;
}

</script>
<form name="selectForm">
   <p>
      <strong>Choose some music types, then click the button below:</strong>
      <br/>
      <select name="musicTypes" multiple="multiple">
         <option selected="selected">R&B</option>
         <option>Jazz</option>
         <option>Blues</option>
         <option>New Age</option>
         <option>Classical</option>
         <option>Opera</option>
      </select>
   </p>
   <p>
      <input type="button" value="How many are selected?"
         onclick="alert ('Number of options selected: ' + howMany(document.selectForm.musicTypes))"/>
   </p>
</form>

do...while Anweisung

Bei der do...while-Anweisung wird die Ausführung solange wiederholt bis eine bestimmte Bedingung zu false evaluiert. Die Syntax einer do...while-Anweisung ist wie folgt:

do
   Anweisung
while (Bedingung);

Die Anweisung wird einmal ausgeführt bevor die Bedingung überprüft wird. Um mehrere Anweisungen auszuführen, muss eine Blockanweisung zur Gruppierung eingesetzt werden. Wenn der Wert der Bedingung true ist, wird die Anweisung erneut ausgeführt. Die Bedingung wird nach jedem Durchlauf überprüft und die Schleife abgebrochen, wenn die Bedingung nicht mehr erfüllt ist.

Beispiel

Beim folgenden Beispiel wird zuerst ein Schleifendurchlauf durchgeführt. Anschließend wird die Bedingung überprüft und solange weitere Schleifendurchläufe durchgeführt, bis i nicht mehr kleiner als 5 ist.

do {
   i += 1;
   document.write(i);
} while (i < 5);

while-Anweisung

Eine while-Anweisung führt Anweisungen solange wiederholt aus wie eine bestimmte Bedingung zu true evaluiert. Eine while Anweisung sieht wie folgt aus:

while (Bedingung)
   Anweisung

Wenn die Bedingung nicht mehr erfüllt ist, stoppt die Ausführung der Anweisung und die Ausführung wird hinter der Schleife fortgesetzt.

Die Bedingung wird gleich zu Beginn vor der Ausführung der Anweisung überprüft. Wenn die Bedingung erfüllt ist, wird die Anweisung ausgeführt und die Bedingung erneut überprüft. Wenn die Bedingung nicht erfüllt ist, wird die Schleife abgebrochen und die Ausführung hinter der Schleife fortgesetzt.

Auch bei der while-Schleife müssen mehrere Anweisungen durch einen Anweisungsblock ({ ... }) gruppiert werden.

Beispiel 1

Die folgende while-Schleife wird ausgeführt, solange n kleiner als 3 ist:

n = 0;
x = 0;
while (n < 3) {
   n++;
   x += n;
}

Mit jeder Iteration wird die Variable n inkrementiert und der Wert zu x hinzuaddiert. Daher nehmen x und n im Verlauf die folgenden Werte an:

  1. Nach dem ersten Durchlauf: n = 1 und x = 1
  2. Nach dem zweiten Durchlauf: n = 2 und x = 3
  3. Nach dem dritten Durchlauf: n = 3 und x = 6

Nachdem der dritte Durchlauf abgeschlossen ist, ist die Bedingung n < 3 nicht mehr erfüllt und die Schleife wird abgebrochen.

Beispiel 2

Man sollte unbedingt darauf achten, keine Endlos-Schleifen zu programmieren, also sicherstellen, dass die Bedingung früher oder später nicht mehr erfüllt ist, sodass die Schleife abbricht. Die Anweisungen des folgenden Codes werden unendlich wiederholt ausgeführt, weil die Bedingung niemals zu false evaluiert:

while (true) {
   alert("Hello, world");
}

Label-Anweisungen

Ein Label ist ein Bezeichner, der eine Stelle markiert, die als Sprungziel dient. Zum Beispiel kann mit einem Label eine Schleife markiert und anschließend mit Hilfe von break oder continue die Schleife unterbrochen oder beim nächsten Durchlauf fortgesetzt werden.

Syntax:

Label :
   Anweisung

Der Wert von Label kann ein JavaScript-Bezeichner sein, der jedoch keines der reservierten Wörter von JavaScript sein darf. Anweisung kann für beliebige Anweisungen stehen.

Beispiel
Bei diesem Beispiel markiert das Label markLoop eine while-Schleife:

markLoop:
while (theMark == true) {
   doSomething();
}

break-Anweisung

Die break-Anweisung wird in Verbindung mit Schleifen, switch-Anweisungen oder mit Labels zum Abbruch eingesetzt.

  • Wenn break ohne ein Label verwendet wird, terminiert es das innerste while, do-while, for, oder switch und gibt die Kontrolle an die dahinterstehende Anweisung weiter.
  • Wenn break mit einem Label eingesetzt wird, terminiert es die angegebende Label-Anweisung.

Die Syntax von break-Anweisungen ist wie folgt:

  1. break;
  2. break Label;

Die erste Form terminiert die innerste Schleife oder switch-Anweisung. Die zweite Form terminiert die angegebene Label-Anweisung.

Beispiel 1:
Beim folgenden Beispiel wird solange über die Elemente eines Arrays iteriert, bis der index eines Elements gefunden wird, dessen Wert derWert entspricht:

for (i = 0; i < a.length; i++) {
   if (a[i] == derWert)
      break;
}

Beispiel 2: Abbruch und Sprung zu einem Label

var x = 0;
var z = 0
labelCancelLoops: while (true) {
    console.log("Outer loops: " + x);
    x += 1;
    z = 1;
    while (true) {
        console.log("Inner loops: " + z);
        z += 1;
        if (z === 10 && x === 10) {
            break labelCancelLoops;
        } else if (z === 10) {
            break;
        }
    }
}

continue-Anweisung

Mit der continue-Anweisung wird ein Sprung zum nächsten Schleifendurchlauf veranlasst.

  • Wenn continue ohne ein Label eingesetzt wird, bricht es den aktuellen Durchlauf der innersten while-, do-while- oder for-Anweisung ab und die Ausführung wird bei der nächsten Iteration fortgesetzt. Im Gegensatz zu break wird bei continue die Schleife nicht komplett abgebrochen, sondern nur der aktuelle Durchlauf der Schleife. Bei einer while-Schleife wird ein Sprung zurück zur Bedingung durchgeführt. Bei einer for-Schleife bewirkt continue einen Sprung zum Inkrementierungsausdruck.
  • Steht nach continue ein Label, bewirkt es einen Sprung zu diesem Label.

Die Syntax der continue-Anweisung ist wie folgt:

  1. continue
  2. continue Label

Beispiel 1
Das folgende Beispiel zeigt eine while-Schleife mit einer continue-Anweisung, die ausgeführt wird, wenn i den Wert 3 besitzt. Im Verlauf der Ausführung nimmt n die Werte 1, 3, 7 und 12 an.

i = 0;
n = 0;
while (i < 5) {
   i++;
   if (i == 3)
      continue;
   n += i;
}

Beispiel 2
Eine Anweisung mit dem Label checkiandj enthält eine weitere Anweisung mit dem Label checkj. Wenn continue erreicht ist, bricht das Skript die aktuelle Iteration von checkj ab und startet die nächste Iteration. Jedes Mal, wenn continue erreicht ist, wird checkj erneut durchlaufen, solange bis die Bedingung der if-Anweisung zu false evaluiert. Wenn false zurückgegeben wird, wird der Rest des Codes von checkiandj bis zum Ende ausgeführt und checkiandj wiederholt, bis die zugehörige Bedingung zu false evaluiert. Wenn false zurückgegeben wird, wird die Ausführung bei der Anweisung hinter checkiandj fortgesetzt.

Wenn bei continue das Label checkiandj angegeben wäre, würde das Skript am Anfang der checkiandj-Anweisung fortgesetzt.

checkiandj :
   while (i < 4) {
      document.write(i + "<br/>");
      i += 1;
      checkj :
         while (j > 4) {
            document.write(j + "<br/>");
            j -= 1;
            if ((j % 2) == 0)
               continue checkj;
            document.write(j + " is odd.<br/>");
         }
      document.write("i = " + i + "<br/>");
      document.write("j = " + j + "<br/>");
   }

Anweisungen zur Objekt-Manipulation

JavaScript benutzt die Anweisungen for...in, for each...in, und with zur Manipulation von Objekten.

for...in-Anweisung

Die Anweisung for...in iteriert eine bestimmte Variable über alle Eigenschaften eines Objekts. Bei jedem Schleifendurchlauf wird Variable der Name der Eigenschaft zugewiesen. Die Anweisungen werden für jede vorhandene Eigenschaft einmal ausgeführt.

Syntax:

for (Variable in Objekt) {
   Anweisungen
}

Beispiel
Die folgende Funktion nimmt ein Objekt als Argument und den Namen des Objekts entgegen. Anschließend wird mit Hilfe von for...in über alle Objekteigenschaften iteriert und ein String mit dem Namen und dem Wert der Eigenschaft zurückgegeben.

function dump_props(obj, obj_name) {
   var result = "";
   for (var i in obj) {
      result += obj_name + "." + i + " = " + obj[i] + "<br>";
   }
   result += "<hr>";
   return result;
}

Bei einem Objekt automobil mit den Eigenschaften hersteller und modell wäre das Ergebnis:

automobil.hersteller = Ford
automobil.modell = Mustang

Arrays

Obwohl es sinnvoll erscheinen mag, eine for...in-Anweisung zu benutzen, um über die Elemente eines Arrays zu iterieren, weil die for...in-Anweisung zusätzlich zu den Array-Elementen auch benutzerdefinierte Eigenschaften und Methoden erfasst, sollte besser eine normale for-Schleife benutzt werden, um über Array-Elemente zu interieren, da bei benutzerdefinierten Methoden nicht nur ein numerischer Index, sondern auch der Name der benutzerdefinierten Eigenschaften oder Methoden zurückgegeben wird.

for each...in Anweisung

for each...in ist eine Schleifenanweisung, die mit JavaScript 1.6 eingeführt wurde. Sie ist ähnlich wie for...in, aber iteriert über die Werte von Objekt-Eigenschaften und nicht über die Namen.

var sum = 0;
var obj = {prop1: 5, prop2: 13, prop3: 8};
for each (var item in obj) {
  sum += item;
}
print(sum); // gibt 26 aus, also die Summe von 5+13+8

Kommentare

Kommentare sind Bemerkungen der Autorin/des Autors, welche die Funktion von Code beschreiben. Kommentare werden vom Interpreter ignoriert. JavaScript unterstützt die Syntax für Kommentare im Stil von Java und C++:

  • Einzeilige Kommentare werden durch doppelte Schrägstriche (//) eingeleitet.
  • Kommentare über mehrere Zeilen werden in /* und */ eingeschlossen.

Beispiel
Der folgende Code demonstriert die beiden Varianten zur Kennzeichnung von Kommentaren:

// Dies ist ein einzeiliger Kommentar.

/* Dies ist ein mehrzeiliger Kommentar.
Er kann sich über beliebig viele Zeilen erstrecken
und beliebige Zeichen enthalten. */

Anweisungen zur Ausnahmebehandlung (Exceptions)

Mit der Anweisung throw wird eine Ausnahmenbehandlung eingeleitet und Werte von Ausdrücken ausgeworfen. Außerdem lässt sich mit Hilfe der try...catch-Anweisung auf Ausnahmen reagieren.

Mit try...catch können auch Java-Ausnahmen behandelt werden (Vorsicht: bug 391642 ). Unter Handling Java Exceptions in JavaScript und JavaScript to Java Communication wird genauer auf das Thema eingegangen.

  • throw-Anweisung
  • try...catch-Anweisung

Typen von Ausnahmen

Nahezu jedes Objekt kann in JavaScript ausgeworfen werden, jedoch werden nicht alle Objekte auf dieselbe Weise erstellt. Obwohl es üblich ist, dass Ziffern oder Zeichenketten als Error ausgeworfen werden, ist es weit mehr effektiver, einen der Ausnametypen zu benutzen, die speziell für diesen Zweck entwickelt wurden.

  • ECMAScript exceptions
  • DOMException
  • nsIXPCException (XPConnect)

throw-Anweisung

Die throw-Anweisung wird eingesetzt, um eine Ausnahmebehandlung einzuleiten und einen Ausdruck auszuwerfen. Der Ausdruck, dessen Wert ausgeworfen werden soll, wird hinter throw angegeben:

throw Ausdruck;

Der Ausdruck muss nicht von einem speziellen Typ sein, sondern es kann jede Art von Ausdruck benutzt werden. Der folgende Code wirft verschiedene Ausnahmen mit unterschiedlichen Typen aus:

throw "Error2";
throw 42;
throw true;
throw {toString: function() { return "Ich bin ein Objekt!"; } };
Note: Man kann beim Auswerfen von Ausnahmen auch Objekte angeben. Im Anschluss kann im catch-Block auf die Eigenschaften dieses Objekts zugegriffen werden. Beim folgenden Beispiel wird ein Objekt myUserException mit dem Typ UserException erstellt und dieses in einer throw-Anweisung benutzt.
// Erstellen eines Objekt-Typs UserException
function UserException (message){
  this.message=message;
  this.name="UserException";
}

// Konvertieren der Ausnahme zu einem String,
// wenn sie als String benutzt wird (z .B. von der Error-Konsole)
UserException.prototype.toString = function (){
  return this.name + ': "' + this.message + '"';
}

// Erstellen einr Instanz des Objekt-Typs und Auswurf
throw new UserException("Value too high");

try...catch-Anweisung

Bei der try...catch-Anweisung wird ein Block von Anweisungen "ausprobiert" und auf gegebenenfalls auftretende Ausnahmen reagiert. Wenn eine Ausnahme ausgeworfen wird, wird sie von der try...catch-Anweisung abgefangen.

Die try...catch-Anweisung besteht aus einem try-Block, welcher eine oder mehrere Anweisungen enthält und einem oder mehreren catch-Blöcken, die Anweisungen enthalten und jeweils bestimmen, was beim Auftreten einer bestimmten Ausnahme getan werden soll. Man möchte in der Regel, dass der try-Block erfolgreich ausgeführt wird, doch falls Fehler auftreten, entsprechend mit den catch-Anweisungen darauf reagieren. Sobald eine Ausnahme durch den Code im try-Block (oder eine Funktion, die im try-Block ausgeführt wird) ausgelöst wurde, wird die Kontrolle an den catch-Block übergeben. Wenn keine Ausnahme im try-Block ausgelöst wird, wird der catch-Block nicht ausgeführt. Der finally-Block wird nach dem try- und catch-Block ausgeführt, jedoch bevor der Code nach dem try...catch ausgeführt wird. Mit dem finally-Block werden in der Regel Aufräumarbeiten durchgeführt.

Beim folgenden Beispiel kommt eine try...catch-Anweisung zur Anwendung. Es wird eine Funktion aufgerufen, die eine Zahl entgegen nimmt und anhand dieser einen Monatsnamen aus einem Array auswählt. Wenn der Wert keiner Monatszahl (1-12) entspricht, wird eine Ausnahme ausgelöst, welche den Wert "InvalidMonthNo" auswirft und die Anweisungen im catch-Block setzen die monthName-Variable auf unknown.

function getMonthName (mo) {
    mo=mo-1; // Adjust month number for array index (1=Jan, 12=Dec)
    var months=new Array("Jan","Feb","Mar","Apr","May","Jun","Jul",
          "Aug","Sep","Oct","Nov","Dec");
    if (months[mo] != null) {
       return months[mo]
    } else {
       throw "InvalidMonthNo"
    }
}

try {
// statements to try
    monthName=getMonthName(myMonth) // function could throw exception
}
catch (e) {
    monthName="unknown"
    logMyErrors(e) // pass exception object to error handler
}

Der catch-Block

Mit dem catch-Block kann jede Ausnahme, die im try-Block ausgelöst wird, abgefangen werden.

catch (catchID) {
  Anweisungen
}

Bei diesem catch-Block wurde ein Bezeichner catchID angegeben, welcher den Wert enthält, der von der throw-Anweisung ausgeworfen wird. Diesen Bezeichner kann man benutzen, um Informationen zu der ausgelösten Ausnahme zu erhalten. JavaScript erstellt diesen Bezeichner, wenn der catch-Block zur Ausführung kommt. Der Bezeichner bleibt nur für den catch-Block erhalten. Nachdem die Ausführung des catch-Blocks beendet ist, kann auf den Bezeichner nicht mehr zugegriffen werden.

Der folgende Code löst eine Ausnahme aus. Sobald die Ausnahme ausgelöst wird, wird der catch-Block ausgeführt.

try {
   throw "myException" // löst Ausnahme aus
}
catch (e) {
// Anweisungen um auf die Ausnahme zu reagieren
   logMyErrors(e) // Das Ausnahme-Objekt wird an eine Funktion übergeben
}

Der finally-Block

Der finally-Block enthält Anweisungen, die nach den try- und catch-Blöcken ausgeführt werden, jedoch bevor der Code hinter der try...catch-Anweisung ausgeführt wird. Der Block wird unabhängig davon ausgeführt, ob eine Ausnahme ausgelöst wurde oder nicht. Auch wenn gar kein catch-Block vorhanden ist, womit auf eine Ausnahme reagiert wird, wird der finally-Block ausgeführt.

Mit Hilfe des finally-Blocks kann man elegant auf Fehler in einem Skript reagieren. Zum Beispiel möchte man vielleicht Ressourcen wieder freigeben, die das Skript in Anspruch genommen hat, bevor ein Abbruch stattfindet. Beim folgenden Beispiel wird eine Datei geöffnet und Anweisungen ausgeführt, die mit der Datei arbeiten (serverseitiges Javascript erlaubt den Zugriff auf Dateien). Wenn eine Ausnahme ausgelöst wird während die Datei geöffnet ist, schließt der Code des finally-Blocks die Datei, bevor die Ausführung des Skripts abbricht.

openMyFile();
try {
    writeMyFile(theData); // Hier könnte ein Fehler passieren
}catch(e){
    handleError(e); // Fehlerbehandlung falls Fehler auftritt
}finally {
    closeMyFile(); // In jedem Fall Datei schließen
}

Wenn der finally-Block einen Wert zurückgibt, ist dieser Wert der return-Wert des kompletten try-catch-finally-Blocks, unabhängig davon welche return-Werte die Funktionen in den try- und catch-Blöcken zurückgeben.

function f() {
    try {
        alert(0);
        throw "bogus";
    } catch(e) {
        alert(1);
        return true; // Dieser return-Wert wird zurückgehalten bis der finally-Block ausgeführt wurde
        alert(2); // nicht erreichbar
    } finally {
        alert(3);
        return false; // überschreibt das vorherige "return"
        alert(4); // nicht erreichbar
    }
    // "return false" wird nun ausgeführt

    alert(5); // nicht erreichbar
}
f(); // alert-Meldungen: 0, 1, 3 und "return false" wird ausgeführt

Verschachteln von try...catch-Anweisungen

Man kann eine oder mehrere try...catch-Anweisungen verschachteln. Wenn eine innere try...catch-Anweisungen keinen catch-Block hat, wird die einschließende try-catch-Anweisung auf eine Übereinstimmung überprüft.

Einsatz von Error-Objekten

Je nach Art des Fehlers kann es möglich sein, dass auf die Eigenschaften "name" und "message" zugegriffen werden kann, um detaillierte Informationen zu erhalten. Die Eigenschaft "name" enthält die Fehlerklasse (z. B. "DOMException" oder "Error") und die Eigenschaft "message" in der Regel genauere Informationen, als wenn man nur das Error-Objekt zu einem String konvertiert.

Wenn man selbst die Meldungen für Ausnahmen definieren möchte, um sich diese Eigenschaften zunutze zu machen (z. B. falls der catch-Block nicht zwischen System-Ausnahmen und selbst definierten Ausnahmen unterscheidet) kann man den Error-Konstruktor benutzen.

Zum Beispiel:

function doSomethingErrorProne () {
  if (ourCodeMakesAMistake()) {
    throw (new Error('Die Nachricht'));
  } else {
    doSomethingToGetAJavascriptError();
  }
}
....
try {
  doSomethingErrorProne();
}
catch (e) {
  alert(e.name); // Alarm-Meldung: 'Error'
  alert(e.message); // Alarm-Meldung: 'Eine Nachricht' oder eine JavaScript-Fehlermeldung)
}


Funktionen

Funktionen sind einer der fundamentalen Aufbaublöcke von JavaScript. Eine Funktion ist eine Prozedur - ein Satz von Anweisungen die eine Aufgabe erledigen oder einen Wert berechnen. Damit eine Funktion benutzt werden kann, muss diese im selben Sichtbarkeitsbereich definiert werden, von dem aus sie aufgerufen werden soll.

Definieren von Funktionen

Eine Funktionsdefinition (auch Funktionsdeklaration genannt) setzt sich zusammen aus dem Schlüsselwort function, gefolgt von:

  • Dem Namen der Funktion.
  • Einer Liste von Argumenten, die durch ein Komma getrennt und in Klammern eingeschlossen werden.
  • Den JavaScript Anweisungen, welche definieren, was die Funktion tun soll, eingeschlossen in geschweifte Klammern.

Der folgende Code definiert zum Beispiel eine einfache Funktion square:

function square(number) {
  return number * number;
}

Diese Funktion nimmt ein Argument number entgegen. Die Funktion besteht aus einer Anweisung, die festlegt, dass number mit sich selbst multipliziert und das Ergebnis zurückgeliefert werden soll. Mit dem Schlüsselwort return wird also der Rückgabewert der Funktion festgelegt.

return number * number;

Einfache Parameter (wie z. B. Numbers) werden als Wert (by value) an die Funktion übergeben. Das bedeutet, der Wert wird zwar an die Funktion übergeben, jedoch gelten Änderungen des Werts, die von der Funktion vorgenommen werden, nur innerhalb der Funktion selbst und nicht etwa global oder in der aufrufenden Funktion.

Wird hingegen ein Objekt (z. B. ein nicht-primitiver Wert, wie ein Array oder ein benutzerdefiniertes Objekt) als Parameter an eine Funktion übergeben und Eigenschaften dieses Objekts von der Funktion verändert, so gelten diese Änderungen auch außerhalb der Funktion.

Zum Beispiel:

function myFunc(theObject) {
  theObject.make = "Toyota";
}

var mycar = {make: "Honda", model: "Accord", year: 1998},
    x,
    y;

x = mycar.make;     // x bekommt den Wert "Honda"

myFunc(mycar);
y = mycar.make;     // y bekommt den Wert "Toyota"
                    // (Die make-Eigenschaft wurde von der Funktion geändert)

Es sei angemerkt, dass die Zuweisung eines neuen Objekts an den Parameter innerhalb der Funktion das übergebene Objekt nicht verändert, da hierdurch nur der Wert des Parameters selbst geändert wird:

function myFunc(theObject) {
  theObject = {make: "Ford", model: "Focus", year: 2006};
}

var mycar = {make: "Honda", model: "Accord", year: 1998},
    x,
    y;

x = mycar.make;     // x bekommt den Wert "Honda"

myFunc(mycar);
y = mycar.make;     // y bekommt noch einmal den Wert "Honda"

Beim ersten Beispiel wurde das Objekt mycar an die Funktion myFunc übergeben, welche das Objekt veränderte. Beim zweiten Beispiel wurde das übergebene Objekt von der Funktion nicht verändert; stattdessen wurde eine neue lokale Variable mit dem selben Namen erzeugt, was keinen Effekt auf das übergebene Objekt selbst hatte.

Bei JavaScript kann eine Funktion abhängig von einer Bedingung definiert werden:

if (num == 0){
  function myFunc(theObject) {
    theObject.make = "Toyota"
  }
}

Sollte num nicht dem Wert 0 entsprechen, wird die Funktion nicht definiert und jeder Versuch eines Aufrufs scheitert.

Achtung: ECMAScript erlaubt die Definition von Funktionen in solchem Kontext nicht, sondern nur direkt innerhalb anderer Funktionen beim Top Level eines Skripts. Aus diesem Grund ist das Beispiel nicht korrekt für ECMAScript.

Vorsicht: Die verschiedenen Implemetierungen von JavaScript behandeln dieses Nicht-Standard-Konstrukt unterschiedlich. Beim Schreiben von portablem Code sollte also besser darauf verzichtet werden. Andernfalls kann es passieren, dass der Code nicht in allen Browsern funktioniert.

Neben den hier beschriebenen Möglichkeiten zur Definition von Funktionen kann auch der Function-Konstruktor eingesetzt werden, um Funktionen aus einem String während der Laufzeit zu erzeugen, ähnlich wie bei eval().

Eine Methode ist eine Funktion, die eine Eigenschaft eines Objekts ist. Genauere Informationen findet man im Kapitel Mit Objekten arbeiten.

Während die oben beschriebenen Funktionsdeklarationen alle syntaktische Anweisungen sind, können Funktionen auch mit einem Funktionsausdruck erstellt werden. Solch eine Funktion kann anonym sein; sie muss keinen Namen besitzen. Zum Beispiel hätte die Funktion square auch so definiert werden können:

var square = function(number) {return number * number};

Es kann auch ein Name für die Funktion angegeben und die Funktion selbst mit Hilfe dieses Namens innerhalb der Funktion angesprochen werden. Außerdem kann die Festlegung eines Funktionsnamens dabei helfen, in Stack Traces die Funktion identifizieren zu können:

var factorial = function fac(n) {return n<2 ? 1 : n*fac(n-1)};

print(factorial(3));

Funktionsausdrücke sind praktisch wenn eine Funktion als Argument an eine andere Funktion übergeben werden soll. Das folgende Beispiel zeigt eine Funktion map, die zuerst definiert und dann aufgerufen wird, mit einer anonymen Funktion als Parameter.

function map(f,a) {
  var result = [], // Neues Array
      i;
  for (i = 0; i != a.length; i++)
    result[i] = f(a[i]);
  return result;
}

Der folgende Code gibt [0, 1, 8, 125, 1000] zurück. Für jedes Element des Arrays wird die anonyme Funktion einmal aufgerufen.

map(function(x) {return x * x * x}, [0, 1, 2, 5, 10]);

Aufrufen von Funktionen

Bei der Definition einer Funktion wird diese nicht ausgeführt, sondern nur ein Name zugewiesen und der Code festgelegt, der beim Aufruf der Funktion ausgeführt wird. Erst beim Aufruf einer Funktion wird der Code ausgeführt. Zum Beispiel kann die Funktion square wie folgt aufgerufen werden:

square(5);

Die voranstehende Anweisung ruft die Funktion mit einem Argument 5 auf. Daraufhin werden die Anweisungen der Funktion ausgeführt und der Wert 25 zurückgegeben.

Funktionen müssen beim Aufruf innerhalb desselben Sichtbarkeitsbereichs liegen, jedoch kann die Deklaration auch erst unterhalb eines Funktionsaufrufs stattfinden:

print(square(5));       // Aufruf
/* ... */
function square(n){return n*n}   // Deklaration

Die Argumente einer Funktion sind nicht auf bestimmte Datentypen wie Strings oder Numbers beschränkt. Es können auch ganze Objekte an eine Funktion übergeben werden. Die Funktion show_props (aus dem Abschnitt Mit Objekten arbeiten) ist ein Beispiel für eine Funktion, die ein Objekt als Argument entgegennimmt.

Eine Funktion kann rekursiv aufgebaut sein. Das bedeutet, dass die Funktion sich selbst aufruft. Das nachfolgende Beispiel stellt eine rekursive Funktion für die Berechnung von Fakultäten dar.

function factorial(n){
  if ((n == 0) || (n == 1))
    return 1;
  else
    return (n * factorial(n - 1));
}

Mit dieser Funktion lassen sich z. B. die Fakultäten von eins bis fünf berechnen:

var a, b, c, d, e;
a = factorial(1); // a gets the value 1
b = factorial(2); // b gets the value 2
c = factorial(3); // c gets the value 6
d = factorial(4); // d gets the value 24
e = factorial(5); // e gets the value 120

Es gibt noch weitere Möglichkeiten, wie Funktionen aufgerufen werden können. Des Öfteren kommt es vor, dass eine Funktion dynamisch aufgerufen werden soll, die Anzahl der Argumente variiert oder der Kontext beim Funktionsaufruf einem bestimmten Objekt während der Laufzeit zugeordnet werden soll. Daher sollte man wissen, dass Funktionen ebenfalls Objekte sind, welche entsprechende durch die Sprache festgelegte Standardmethoden besitzen (siehe Funktions-Objekte). Mit einer dieser Methoden, der Methode apply(), kann das Ziel der Zuordnung einer Funktion zu einem Objekt erreicht werden.

Sichtbarkeit von Funktionen

Auf Variablen, die innerhalb von Funktionen definiert werden, kann nicht von außerhalb der Funktion zugegriffen werden, da die Sichtbarkeit auf den Bereich der Funktion beschränkt ist. Jedoch kann eine Funktion auf alle Variablen und Funktionen zugreifen, die innerhalb des Sichtbarkeitsbereichs definiert wurden. Mit anderen Worten: Eine Funktion, die im globalen Sichtbarkeitsbereich definiert wurde, hat Zugriff auf alle Variablen, die im globalen Sichtbarkeitsbereich definiert wurden. Eine Funktion, die innerhalb einer anderen Funktion definiert wurde, hat auch Zugriff auf alle Variablen der äußeren Funktion und Variablen auf die die äußere Funktion Zugriff hat.

// Definition im globalen Sichtbarkeitsbereich
var num1 = 20,
    num2 = 3,
    name = "Chamahk";

// Funktion ebenfalls im globalen Sichtbarkeitsbereich definiert
function multiply() {
  return num1 * num2;
}

multiply(); // Gibt 60 zurück

// add() ist eine eingebettete Funktion (engl. nested function)
function getScore () {
  var num1 = 2,
      num2 = 3;

  function add() {
    return name + " scored " + (num1 + num2);
  }

  // Aufruf der Funktion und Ergebnis ist Rückgabewert
  return add();
}

getScore(); // Gibt zurück: "Chamahk scored 5"

Closures

Closures sind eines der mächtigsten Features von JavaScript. JavaScript erlaubt die Einbettung von Funktionen und erlaubt der inneren Funktion vollen Zugriff auf alle Variablen und Funktionen, die innerhalb der äußeren Funktion definiert wurden (und allen anderen Variablen und Funktionen, die von der Funktion aus ansprechbar sind). Jedoch hat die äußere Funktion keinen Zugriff auf die Variablen und Funktionen der inneren Funktion. Dieser eingeschränkte Zugriff stellt einen Sicherheitsmechanismus für die Variablen der inneren Funktion dar. Falls die innere Funktion länger bestehen bleibt als die äußere Funktion, bleiben die Variablen und Funktionen der äußeren Funktion länger erhalten als die Funktion selbst, da die innere Funktion Zugang zum Sichtbarkeitsbereich der äußeren Funktion hat. Eine Closure wird erstellt, wenn die innere Funktion für den Sichtbarkeitsbereich außerhalb der äußeren Funktion zugänglich gemacht wird.

var pet = function(name) {          // Die äußere Funktion definiert eine Variable "name"
      var getName = function() {
        return name;                // Die innere Funktion hat Zugriff auf die Variable "name" der äußeren Funktion
      }

      return getName;               // Rückgabe der inneren Funktion; damit wird sie für die äußeren Sichtbarkeitsbereiche zugänglich gemacht
    },
    myPet = pet("Vivie");

myPet();                            // Gibt "Vivie" zurück

Der Aufbau kann sehr viel komplexer sein, als bei diesem Beispiel, z.B. kann auch ein Objekt, das Methoden für die Manipulation der inneren Variablen der äußeren Funktion besitzt, zurückgegeben werden.

var createPet = function(name) {
  var sex;

  return {
    setName: function(newName) {
      name = newName;
    },

    getName: function() {
      return name;
    },

    getSex: function() {
      return sex;
    },

    setSex: function(newSex) {
      if(typeof newSex == "string" && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
        sex = newSex;
      }
    }
  }
}

var pet = createPet("Vivie");
pet.getName();                  // Vivie

pet.setName("Oliver");
pet.setSex("male");
pet.getSex();                   // male
pet.getName();                  // Oliver

Beim Code aus dem Beispiel ist die Variable name der äußere Funktion für die inneren Funktionen zugänglich und es gibt keine andere Möglichkeit auf die inneren Variablen zuzugreifen, außer durch die inneren Funktionen. Die inneren Variablen der inneren Funktion arbeiten wie ein abgesicherter Speicherbereich der inneren Funktionen. Sie halten "beständige" abgesicherte Daten, womit die inneren Funktionen arbeiten können.

Es ist nicht zwingend erforderlich die Funktionen einer Variablen zuzuordnen oder ihnen Namen zu geben:

var getCode = (function(){
  var secureCode = "0]Eal(eh&2";    // A code we do not want outsiders to be able to modify...

  return function () {
    return secureCode;
  };
})();

getCode();    // Returns the secret code

Leider gibt es ein paar Fallstricke, die beim Einsatz von Closures vermieden werden sollten. Falls in einer eingeschlossene Funktion eine Variable mit demselben Namen wie der Name einer Variablen der äußeren Funktion definiert wird, gibt es keine Möglichkeit mehr, die Variable der äußeren Funktion anzusprechen.

var createPet = function(name) {  // Outer function defines a variable called "name"
  return {
    setName: function(name) {    // Enclosed function also defines a variable called "name"
      name = name;               // ??? How do we access the "name" defined by the outer function ???
    }
  }
}

Der Umgang mit der magischen Variablen this kann sehr trickreich sein bei der Verwendung in Closures und es ist ein vorsichtiger Umgang erforderlich. Der Sichtbarkeitsbereich auf den sich das Schlüsselwort this bezieht, hängt beim Einsatz mit Closures davon ab, an welcher Stelle die Funktion aufgerufen wird und nicht wie üblich, wo sie definiert wurde. Einen exzellenten Artikel über Closures findet man hier.

Benutzung des Argument-Objekts

Die Argumente einer Funktion werden in einem Array-artigen Objekt gespeichert und können innerhalb der Funktion wie folgt angesprochen werden:

arguments[i]

wobei i die Nummer des Arguments ist, angefangen bei 0. Das erste Argument wird demnach mit arguments[0] angesprochen. Die Anzahl der Argumente ist arguments.length.

Mit Hilfe des Argument-Objekts kann eine Funktion mit mehr Argumenten aufgerufen werden, als sie eigentlich formal entgegennimmt. Dies ist sehr nützlich, falls man nicht im Klaren darüber ist, wievielen Argumenten an eine Funktion übergeben werden sollen. Über arguments.length lässt sich die Anzahl der Argumente herausfinden und diese können dann über das Objekt arguments angesprochen werden.

Hat man z.B. eine Funktion für das Verbinden mehrerer Strings und das einzige formal definierte Argument ist das Zeichen, welches zwischen den Strings stehen soll,

function myConcat(separator) {
   var result = "", // initialize list
       i;
   // iterate through arguments
   for (i = 1; i < arguments.length; i++) {
      result += arguments[i] + separator;
   }
   return result;
}

so kann man eine beliebige Anzahl von Argumenten an diese Funktion übergeben und die Funktion verbindet alle Strings miteinander zu einer Art Liste, die als String gespeichert wird.

// returns "red, orange, blue, "
myConcat(", ", "red", "orange", "blue");

// returns "elephant; giraffe; lion; cheetah; "
myConcat("; ", "elephant", "giraffe", "lion", "cheetah");

// returns "sage. basil. oregano. pepper. parsley. "
myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");

Vorsicht: Das Argument Objekt ist zwar Array-artig insofern dass es auch einen nummerierten Index und eine Eigenschaft length hat, jedoch ist es kein Array, hat also nicht alle Methoden, die normalerweise Arrays besitzen.

In der Referenz findet man im Abschnitt zum Funktions-Objekt weitere Informationen.

Vordefinierte Funktionen

  • eval
  • isFinite
  • isNaN
  • parseInt und parseFloat
  • Number und String
  • encodeURI, decodeURI, encodeURIComponent, und decodeURIComponent (alle ab Javascript 1.5 verfügbar).

Der folgende Abschnitt stellen diese Funktionen vor. Weitere Informationen findet man in der Javascript-Referenz.

eval-Funktion

Die eval Funktion führt JavaScript-Code aus, ohne eine Referenz zu einem Objekt.

Die Syntax von eval ist wie folgt:

eval(Ausdruck);

wobei Ausdruck ein String ist, der JavaScript-Code enthält.

Wenn der String ein Ausdruck ist, wird dieser von eval ausgewertet. Wenn das Argument einen oder mehrere JavaScript-Ausdrücke enthält, werden diese von eval ausgeführt. Der Sichtbarkeitsbereich (Scope) von eval ist identisch mit dem des aufrufeneden Codes. Achtung: eval sollte nicht zur Auswertung von arithmetischen Ausdrücken verwendet werden, da Javascript solche Ausdrücke automatisch auswertet.

isFinite-Funktion

Die isFinite-Funktion evaluiert ein Argument und stellt fest, ob es sich um eine endliche Zahl handelt.

Syntax:

isFinite(Number);

wobei Number die betreffende Zahl ist.

Falls die Zahl NaN, positiv unendlich oder negativ unenedlich ist, gibt die Funktion false zurück, andernfalls wird true zurückgegeben. Der folgende Code überprüft die Client-Eingabe und stellt fest, ob es sich um eine endliche Zahl handelt.

if(isFinite(ClientInput)){
/* take specific steps */
}

isNaN-Funktion

Die isNaN-Funktion wertet eine Zahl aus und prüft, ob sie keine Zahl ist (NaN = engl. not a number).

Syntax:

isNaN(testWert);

wobei testWert der Wert ist, der überprüft wird. Die Funktionen parseFloat und parseInt geben "NaN" zurück, wenn sie feststellen, dass es sich nicht um eine Zahl handelt. Die Funktion isNaN gibt true zurück, wenn der Wert keine Zahl ist (NaN) und andernfalls false.

Beim folgenden Code wird floatValue ausgewertet, um festzustellen, ob es sich um eine Zahl handelt und anschließend je nach Ergbnis eine andere Funktion aufgerufen.

var floatValue = parseFloat(toFloat);

if (isNaN(floatValue)) {
   notFloat();
} else {
   isFloat();
}

Die Funktionen parseInt und parseFloat

Die zwei "parse" Funktionen, parseInt und parseFloat, liefern einen numerischen Wert, wenn ihnen eine Zeichenkette als Argument übergeben wird.

Syntax von parseFloat:

parseFloat(str);

wobei parseFloat das Argument str verarbeitet und versucht, es in eine Gleitkommazahl umzuwandeln. Falls das Argument ein anderes Zeichen als ein Vorzeichen (+ oder -), eine Ziffer (0-9), einen Dezimalpunkt oder einen Exponenten enthält, liefert tdie Funktion den Wert bis zu dieser Stelle und ignoriert dieses und alle darauffolgenden Zeichen. Kann das erste Zeichen nicht zu einer Zahl konvertiert werden, gibt die Funktion NaN zurück.

Syntax von parseInt:

parseInt(str [, radix]);

Die Funktion parseInt verarbeitet die Zeichenkette str und versucht sie in eine Ganzzahl der Basis, die nach dem Argument durch Komma getrennt angegeben wird, umzuwandeln. Zum Beispiel bewirkt eine Basis von 10, dass zu einer Dezimalzahl konvertiert wird; bei einer Basis von acht zu einer Oktalzahl und bei 16 zu einer Hexadezimalzahl. Für Basiszahlen größer zehn, werden Buchstaben des Alphabets eingesetzt, z.B. werden bei hexadezimalen Zahlen die Buchstaben A-F verwendet.

Falls in der Zeichenkette ein anderes Zeichen als ein numerischer Wert der spezifizierten Basis vorkommt, wird dieses Zeichen und alle darauffolgenden Zeichen ignoriert. Wenn das erste Zeichen nicht zu einer Zahl der spezifizierten Basis konvertiert werden kann, gibt die Funktion NaN zurück.

Number- und String-Funktionen

Mit den Number- und String-Funktionen kann ein Objekt zu einer Zahl oder einer Zeichenkette umgewandelt werden.

Die Syntax ist wie folgt:

var objRef;

objRef = Number(objRef);

objRef = String(objRef);

wobei objRef eine Objekt-Referenz ist.

Die Funktion Number benutzt die Methode valueOf() des Objekts; die Funktion String greift auf die Methode toString() des Objekts zurück.

Das folgende Beispiel konvertiert das Date-Objekt zu einer lesbaren Zeichenkette.

var D = new Date(430054663215),
x;
x = String(D); // x equals "Thu Aug 18 04:37:43 GMT-0700 (Pacific Daylight Time) 1983"

Das folgende Beispiel konvertit ein String-Objekt zu eienem Number-Objekt.

var str = "12",
num;
num = Number(str);

Mit Hilfe des typeof-Operator kann das Ergebnis überprüft werden:

var str = "12",
    num;
document.write(typeof str);
document.write("");
num = Number(str);
document.write(typeof num);

Escape- und Unescape-Funktionen

Die Escape- und Unescape-Funtionen funktionieren nicht ordnungsgemäß mit Zeichen, die nicht im ASCII-Zeichensatz enthalten sind und gelten als veraltet. Ab JavaScript 1.5 sollte man stattdessen die Funktionen encodeURI, decodeURI, encodeURIComponent und decodeURIComponent benutzen.

Die Funktionen escape und unescape lassen sich Zeichenketten de- und enkodieren. Die Escape-Funktion gibt eine hexadezimal kodierte Zahl des Arguments aus dem Latin-1 Zeichensatz zurück. Die Unescape-Funktion liefert einen ASCII-String eines als hexadezimal kodierten Werts, der als Argument übergeben wird.

Syntax:

escape(string);
unescape(string);

Diese Funktionen werden primär bei serverseitigem JavaScript eingesetzt, um Name/Wert-Paare zu URLs zu de- oder encodieren.


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:

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

  • __defineGetter__
  • __defineSetter__
  • get
  • set

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

  • ECMAScript 5.1 spec: Language Overview
  • JavaScript. The core. (Dmitry A. Soshnikov ECMA-262 article series)


Vordefinierte Kernobjekte

Dieses Kapitel beschreibt die vordefinierten Objekte im Kern von Javascript: Array, Boolean, Date, Function, Math, Number, RegExp, und String.

Array-Objekt

JavaScript selbst besitzt keinen eigenständigen Array-Datentyp, jedoch kann das vordefinierte Array-Objekt und seine Methoden eingesetzt werden, um in eigenen Applikationen mit Arrays zu arbeiten. Das Array-Objekt besitzt verschiedene Methoden, womit Arrays manipuliert werden können. Mit Hilfe dieser Methoden lassen sich Arrays z.B. zusammenfügen, invertieren und sortieren. Des Weiteren besitzt das Array-Objekt eine Eigenschaft für die Länge des Arrays und Methoden zum Arbeiten mit regulären Ausdrücken.

Ein Array ist ein geordneter Satz von Werten, auf die mit einem Namen und einem Index verwiesen werden kann. Zum Beispiel kann man ein Array emp erstellen, welches die Namen von Mitarbeitern enthält, und die Mitarbeiter-Nummer als Index verwenden. Dann wäre emp[1] der erste Mitarbeiter, emp[2] der zweite Miitarbeiter und so weiter.

Erstellen eines Arrays

Die drei folgenden Anweisungen zur Erstellung von Arrays sind gleichbedeutend:

var arr = new Array(element0, element1, ..., elementN);
var arr = Array(element0, element1, ..., elementN);
var arr = [element0, element1, ..., elementN];

element0, element1, ..., elementN ist eine Liste von Werten für das entsprechende Element des Arrays. Wenn diese Form verwendet wird, wird das Array mit den angegebenen Werten als Elemente initalisiert und die length-Eigenschaft des Arrays auf die Anzahl der Argumente gesetzt.

Die Klammer-Syntax wird Array-Literal oder Array-Initialisierer genannt. Sie ist kürzer und wird im Allgemeinen bevorzugt; siehe Array Literale.

Zur Erstellung eines Arrays mit einer Länge ungleich 0, das jedoch keine Elemente enthält, kann eine der folgenden Methoden zum Einsatz kommen:

var arr = new Array(arrayLength);
var arr = Array(arrayLength);

// Dasselbe in literaler Notation:
var arr = [];
arr.length = arrayLength;

Merke: Beim obenstehenden Code muss arrayLength vom Typ Number sein. Andernfalls wird ein Array mit nur einem Element (den übergebenen Wert) enthält. Der Aufruf von arr.length gibt dann zwar den Wert von arrayLength zurück, aber das Array enthält leere Elemente (undefined). Bei der Anwendung einer for...in-Schleife auf das Array wird keines der Array-Elemente angesprochen.

Anstatt ein Array wie oben einer neu definierten Variablen zuzuweisen, kann es auch einer Eigenschaft eines bereits bestehenden oder neuen Objekts zugewiesen werden:

var obj = {};
// ...
obj.prop = [element0, element1, ..., elementN];

// OR
var obj = {prop: [element0, element1, ...., elementN]}

Falls man ein Array mit einem einzelnen Element initialisieren möchte und das Element ist vom Typ Number, so muss auf die Klammer-Notation zurückgegriffen werden. Denn wird ein einzelner Wert vom Typ Number an eine Array()-Konstruktorfunktion als Argument übergeben, so wird dieser Wert als Länge des Arrays interpretiert und nicht als Wert für ein einzelnes Array-Element.

var arr = [42]; var arr = Array(42); // Erstellt ein Array ohne Elemente; arr.length wird auf 42 gesetzt

// Der obenstehende Code ist äquivalent zu:
var arr = [];
arr.length = 42;

Möchte man ein Array mit einem einzelnen Element über die Konstruktorfunktion erstellen und dieses Element ist keine Ganzzahl (sondern eine Gleitkommazahl mit nicht zu vernachlässigenden Kommastellen), wird ein RangeError ausgeworfen. Darum sollte man zur Sicherheit Array-Literale verwenden, wenn anzunehmen ist, dass der Code Arrays mit einzelnen Elementen erzeugt, oder zuerst leere Arrays erstellen und diese anschließend befüllen.

var arr = Array(9.3); // RangeError: Invalid array length

Befüllen von Arrays

Ein Array kann mit Elementen befüllt werden, indem man den Positionen über den Index Werte zuweist:

var emp = [];
emp[0] = "Casey Jones";
emp[1] = "Phil Lesh";
emp[2] = "August West";

Achtung: Wenn man bei obenstehendem Code keine Ganzzahl (Integer) als Index sondern z. B. eine Gleitkommazahl oder einen String angibt, wird anstatt eines Array-Elements eine Eigenschaft erstellt, die auf das Array verweist.

var arr = [];
arr[3.4] = "Oranges";
console.log(arr.length);                // 0
console.log(arr.hasOwnProperty[3.4]);   // true

Man kann ein Array auch direkt bei der Erstellung befüllen:

var myArray = new Array("Hello", myVar, 3.14159);
var myArray = ["Mango", "Apple", "Orange"]

Verweise auf Array-Elemente

Auf die Elemente eines Arrays verweist man über die entsprechende Positionsnummer (Index). Wenn z. B. folgendes Array gegeben ist:

var myArray = ["Wind", "Rain", "Fire"];

Dann verweist man mit myArray[0] auf das erste Element des Arrays und mit myArray[1] auf das zweite Element. Der Index der Elemente startet bei 0.

Achtung: Der Array-Operator [] (eckige Klammern) wird auch für den Zugriff auf Array-Eigenschaften verwendet (Arrays sind auch Objekte bei JavaScript).

Zum Beispiel:

var arr = ["one", "two", "three"];
arr[2];  // three
arr["length"];  // 3

Die Eigenschaft length

Auf der Implementierungsseite werden die Elemente von JavaScript-Arrays wie normale Eigenschaften gespeichert - mit dem Array-Index als Eigenschaftsnamen. Die Eigenschaft length ist speziell; sie gibt immer den Index des letzten Elements zurück. Noch einmal zur Erinnerung: JavaScript-Arrays sind 0-basiert: Der Index startet mit 0 und nicht mit 1. Das bedeutet, dass der Wert von length eins mehr ist als der höchste Index der Array-Elemente:

var cats = [];
cats[30] = ['Dusty'];
print(cats.length); // 31

Man kann der length-Eigenschaft auch direkt einen Wert zuweisen. Wird ein Wert zugewiesen, der kleiner ist als die Anzahl der gespeicherten Elemente, wird das Array auf diese Länge gekürzt. Bei der Zuweisung von 0 werden alle Elemente gelöscht:

var cats = ['Dusty', 'Misty', 'Twiggy'];
console.log(cats.length); // 3

cats.length = 2;
console.log(cats); // gibt "Dusty,Misty" aus - Twiggy wurde entfernt

cats.length = 0;
console.log(cats); // keine Ausgabe; Das cats-Array ist leer

cats.length = 3;
console.log(cats);  // [undefined, undefined, undefined]

Iterieren über Arrays

Eine beliebte Vorgehensweise ist das Iterieren über die Werte eines Arrays, wobei jedes Elemente beliebig verarbeitet wird:

var colors = ['red', 'green', 'blue'];
for (var i = 0; i < colors.length; i++) {
  console.log(colors[i]);
}

Wenn man sicher ist, dass keines der Elemente im booleschen Kontext zu false evaluiert - z. B. wenn das Array nur DOM-Nodes enthält - gibt es eine effizientere Lösung:

var divs = document.getElementsByTagName('div');
for (var i = 0, div; div = divs[i]; i++) {
  /* Verarbeitung von div */
}

Hierbei wird auf die Überprüfung der Länge des Arrays verzichtet und die Variable div bei jedem Schleifendurchlauf dem aktuellen Element zugewiesen.

Introduced in JavaScript 1.6

Mit der Methode forEach(), die ab JavaScript 1.6 zur Verfügung steht, gibt es eine weitere Möglichkeit:

var colors = ['red', 'green', 'blue'];
colors.forEach(function(color) {
  console.log(color);
});

Die Funktion, die an forEach übergeben wird, wird einmal für jedes Array-Element ausgeführt, wobei das Array-Element an die Funktion als Argument übergeben wird. Elemente denen kein Wert zugewiesen ist, werden bei der forEach-Schleife nicht berücksichtigt.

Elemente, die bei der Definition des Arrays ausgelassen wurden, werden beim Iterieren mit forEach nicht aufgelistet. Weist man den Elemente jedoch manuell undefined zu, werden sie aufgelistet:

var array = ['first', 'second', , 'fourth'];

// returns ['first', 'second', 'fourth'];
array.forEach(function(element) {
  console.log(element);
}

if(array[2] === undefined) { console.log('array[2] is undefined'); } // true

var array = ['first', 'second', undefined, 'fourth'];

// returns ['first', 'second', undefined, 'fourth'];
array.forEach(function(element) {
  console.log(element);
}

Da JavaScript-Elemente wie Standard-Objekt-Eigenschaften gespeichert werden, ist es nicht empfehlenswert mit einer for...in-Schleife über Arrays zu iterieren, weil dabei auch normale Elemente und alle aufzählbaren Eigenschaften einbezogen werden.

Array-Methoden

Das Array-Objekt besitzt die folgenden Methoden:

  • concat() fügt zwei Arrays zusammen und gibt das Ergebnis als neues Array zurück:
    var myArray = new Array("1", "2", "3");
myArray = myArray.concat("a", "b", "c"); // myArray ist nun ["1", "2", "3", "a", "b", "c"]
  • join(deliminator = ",") fügt alle Elemente zu einem String zusammen:
    var myArray = new Array("Wind", "Rain", "Fire");
var list = myArray.join(" - "); // list is "Wind - Rain - Fire"
  • push() fügt eines oder mehrere Elemente am Ende des Arrays hinzu und gibt die Länge des neuen Arrays zurück:
    var myArray = new Array("1", "2");
myArray.push("3"); // myArray is now ["1", "2", "3"]
  • pop() entfernt das letzte Element des Arrays und gibt den Wert dieses Elements zurück:
    var myArray = new Array("1", "2", "3");
var last = myArray.pop(); // myArray is now ["1", "2"], last = "3"
  • shift() entfernt das erste Element aus dem Array und gibt dessen Wert zurück:
    var myArray = new Array ("1", "2", "3");
var first = myArray.shift(); // myArray is now ["2", "3"], first is "1"
  • unshift() fügt eines oder mehrere Elemente am Anfang des Arrays hinzu und gibt die Länge des neuen Arrays zurück:
    var myArray = new Array ("1", "2", "3");
myArray.unshift("4", "5"); // myArray becomes ["4", "5", "1", "2", "3"]
  • slice(start_index, upto_index) extrahiert einen Bereich eines Arrays und liefert ein neues Array zurück:
    var myArray = new Array ("a", "b", "c", "d", "e");
myArray = myArray.slice(1, 4); /* starts at index 1 and extracts all elements
  until index 3, returning [ "b", "c", "d"] */
  • splice(index, count_to_remove, addelement1, addelement2, ...) entfernt Elemente aus dem Array und ersetzt diese (optional).
    var myArray = new Array ("1", "2", "3", "4", "5");
myArray.splice(1, 3, "a", "b", "c", "d"); // myArray ist jetzt ["1", "a", "b", "c", "d", "5"]
  // This code started at index one (or where the "2" was), removed 3 elements there,
  // and then inserted all consecutive elements in its place.
  • reverse() dreht die Anordnung der Elemente herum: das erste Element ist dann das letzte Elemente das letzte Element das erste.
    var myArray = new Array ("1", "2", "3");
myArray.reverse(); // tauscht die Reihenfolge, sodass myArray = [ "3", "2", "1" ]
  • sort() sortiert die Elemente eines Arrays.
    var myArray = new Array("Wind", "Rain", "Fire");
myArray.sort(); // sorts the array so that myArrray = [ "Fire", "Rain", "Wind" ]

    sort() kann auch eine Callback-Funktion übergeben werden, womit festgelegt wird, wie die Elemente verglichen werden. Die Funktion vergleicht zwei Werte und gibt einen der folgenden drei Werte zurück:

    • Wenn a kleiner als b ist, nach dem Sortierverfahren, gib -1 (oder eine beliebige negative Zahl) zurück.
    • Wenn a größer ist als b, nach dem Sortierverfahren, gib 1 (oder eine beliebige positive Zahl) zurück.
    • Wenn a und b gleich sind, gib 0 zurück.

    Beim folgenden Code wird z. B. anhand des letzten Buchstabens der Elemente sortiert:

    var sortFn = function(a, b){
  if (a[a.length - 1] < b[b.length - 1]) return -1;
  if (a[a.length - 1] > b[b.length - 1]) return 1;
  if (a[a.length - 1] == b[b.length - 1]) return 0;
}
myArray.sort(sortFn); // sorts the array so that myArray = ["Wind","Fire","Rain"]

Introduced in JavaScript 1.6

Kompatibler Code für ältere Browser kann für jede dieser Funktionen auf verschiedenen Seiten gefunden werden. Informationen über die native Browserunterstützung der verschiedene Features in unterschiedlichen Browsern findet man hier.

indexOf(searchElement[, fromIndex]) sucht im Array nach searchElement und gibt den Index des ersten Treffers zurück.

  • var a = ['a', 'b', 'a', 'b', 'a'];
alert(a.indexOf('b')); // Alert-Meldung: 1
// Nun noch einmal, beginnend nach dem ersten Treffer
alert(a.indexOf('b', 2)); // Alert-Meldung: 3
alert(a.indexOf('z')); // Alert-Meldung: -1, weil 'z' nicht gefunden wurde
  • lastIndexOf(searchElement[, fromIndex]) funktioniert wie indexOf, aber es wird am Ende begonnen und rückwärts gesucht.
    var a = ['a', 'b', 'c', 'd', 'a', 'b'];
alert(a.lastIndexOf('b')); // Alert-Meldung: 5
// Nun noch einmal, beginnend vor dem letzten Treffer
alert(a.lastIndexOf('b', 4)); // Alert-Meldung: 1
alert(a.lastIndexOf('z')); // Alert-Meldung: -1
  • forEach(callback[, thisObject]) führt callback für jedes Array-Element aus.
    var a = ['a', 'b', 'c'];
a.forEach(alert); // Alert-Meldung für jedes Element nacheinander
  • map(callback[, thisObject]) gibt ein neues Array mit den Rückgabewerten von jedem Aufruf von callback mit den einzelnen Elementen zurück.
    var a1 = ['a', 'b', 'c'];
var a2 = a1.map(function(item) { return item.toUpperCase(); });
alert(a2); // Alert: A,B,C
  • filter(callback[, thisObject]) gibt ein neues Array mit den Werten zurück, für die callback den Wert true liefert.
    var a1 = ['a', 10, 'b', 20, 'c', 30];
var a2 = a1.filter(function(item) { return typeof item == 'number'; });
alert(a2); // Alerts 10,20,30
  • every(callback[, thisObject]) gibt true zurück, wenn callback für jedes Element true liefert.
    function isNumber(value){
  return typeof value == 'number';
}
var a1 = [1, 2, 3];
alert(a1.every(isNumber)); // Alert: true
var a2 = [1, '2', 3];
alert(a2.every(isNumber)); // Alert: false
  • some(callback[, thisObject]) gibt true zurück, wenn callback für mindestens eines der Elemente true liefert.
    function isNumber(value){
  return typeof value == 'number';
}
var a1 = [1, 2, 3];
alert(a1.some(isNumber)); // Alert: true
var a2 = [1, '2', 3];
alert(a2.some(isNumber)); // Alert: true
var a3 = ['1', '2', '3'];
alert(a3.some(isNumber)); // Alert: false

Die oben beschriebenen Methoden, welche eine callback-Funktion entgegennehmen, sind als iterative Methoden bekannt, weil sie über das ganze Array iterieren. Jeder dieser Funktionen kann optional ein zweites Argument thisObject übergeben werden. Wird ein Objekt angegeben, dann steht das this-Schlüsselwort, wenn es innerhalb des Funktionsblocks der callback-Funktion verwendet wird, für das angegebene Objekt. Wenn kein Objekt angegeben wird, verweist this wie bei allen Funktionen, die außerhalb eines eigenen Objekt-Kontext definiert werden, auf das globale Objekt (window).

Die callback-Funktion wird üblicherweise mit drei Argumenten aufgerufen. Das erste Argument ist der Wert des aktuellen Elements, das zweite der Array-Index des Elements und das dritte ein Verweis auf das Objekt selbst. JavaScript-Funktionen ignorieren alle Argumente, die nicht in der Parameterliste angegeben werden. Daher ist es sicher, nur ein einzelnes Argument wie alert anzugeben.

Introduced in JavaScript 1.8

  • reduce(callback[, initialValue]) führt callback(ersterWert, zweiterWert) aus, um die Liste auf einen einzelnen Wert zu reduzieren.
    var a = [10, 20, 30];
var total = a.reduce(function(first, second) { return first + second; }, 0);
alert(total) // Alerts 60
  • reduceRight(callback[, initialValue]) funktioniert wie reduce(), aber es wird von hinten angefangen.

reduce und reduceRight sind von den iterativen Array-Methoden am schwierigsten zu durchschauen. Sie sollten für Algorithmen verwendet werden, die zwei Werte rekursiv miteinander kombinieren, um eine Sequenz von Werten auf einen Einzelwert zu reduzieren.

Mehrdimensionale Arrays

Arrays können verschachtelt werden. Das bedeutet, dass ein Array ein weiteres Array als Element enthalten kann. Somit können mehrdimensionale Arrays mit JavaScript erstellt werden.

Mit folgendem Code wird ein zweidimensionaes Array erstellt:

var a = new Array(4);
for (i = 0; i < 4; i++) {
  a[i] = new Array(4);
  for (j = 0; j < 4; j++) {
    a[i][j] = "[" + i + "," + j + "]";
  }
}

Dieses Beispiel erstellt ein Array mit den folgenden Reihen:

Reihe 0: [0,0] [0,1] [0,2] [0,3]
Reihe 1: [1,0] [1,1] [1,2] [1,3]
Reihe 2: [2,0] [2,1] [2,2] [2,3]
Reihe 3: [3,0] [3,1] [3,2] [3,3]

Arrays und reguläre Ausdrücke

Wenn ein Array das Ergebnis einer Übereinstimmung eines regulären Ausdrucks und einer Zeichenkette ist, gibt das Array Eigenschaften und Elemente zurück, die Informationen zu dieser Überenstimmung liefern. Die Rückgabewerte von RegExp.exec(), String.match(), und String.split() sind Arrays. Weitere Informationen zur Benutzung von Arrays mit regulären Ausdrücken findet man im Kapitel Reguläre Ausdrücke.

Arbeiten mit Array-ähnlichen Objekten

Introduced in JavaScript 1.6

Manche JavaScript-Objekte, wie z. B. die NodeList, die von document.getElementsByTagName() zurückgegeben wird, oder das arguments-Objekt, das innerhalb eines Funktionskörpers verfügbar gemacht wird, verhalten sich oberflächlich wie Arrays, besitzen jedoch nicht alle für Arrays üblichen Methoden. So besitzt das arguments-Objekt zwar eine length-Eigenschaft, die forEach()-Methode ist aber z. B. nicht vorhanden.

Generische Array-Methoden, die mit JavaScript 1.6 eingeführt wurden, stellen eine Möglichkeit dar, um Array-ähnliche Methoden für andere Array-ähnliche Objekte zu verwenden. Für jede Standard-Arraymethode existiert eine korrespondierende Methode für das Array-Objekt selbst.

Zum Beispiel:

function alertArguments() {
   Array.forEach(arguments, function(item) {
     alert(item);
   });
 }

Diese generischen Methoden können für älteren Versionen von JavaScript mit etwas zusätzlichem Code und mit Hilfe der call-Methode emuliert werden:

Array.prototype.forEach.call(arguments, function(item) {
   alert(item);
 });

Generische Array-Methoden können auch für Strings eingesetzt werden, da auf die einzelnen Zeichen von Strings ebenfalls sequenziell zugegriffen werden kann:

Array.forEach("a string", function(chr) {
   alert(chr);
});

Ein paar weitere Beispiele für die Anwendung von Array-Methoden auf Strings, mit Rückgriff auf JavaScript 1.8 Expression Closures:

var str = 'abcdef';
var consonantsOnlyStr = Array.filter(str, function (c) !(/[aeiou]/i).test(c)).join(''); // 'bcdf'
var vowelsPresent = Array.some(str, function (c) (/[aeiou]/i).test(c)); // true
var allVowels = Array.every(str, function (c) (/[aeiou]/i).test(c)); // false
var interpolatedZeros = Array.map(str, function (c) c+'0').join(''); // 'a0b0c0d0e0f0'
var numerologicalValue = Array.reduce(str, function (c, c2) c+c2.toLowerCase().charCodeAt()-96, 0);
// 21 (reduce() since JS v1.8)

Achtung: Die Methoden filter und map geben nicht automatisch die Buchstaben als String zusammengefügt zurück, sondern als Array. Zur Umwandlung in einen String kommt die Methode join zum Einsatz.

Array-Comprehensions

Introduced in JavaScript 1.7

Die mit JavaScript 1.7 eingeführten Array Comprehensions stellen eine praktische Abkürzung für die Konstruktion eines neuen Arrays, basierend auf dem Inhalt eines anderen Arrays, bereit. Die Comprehensions können oft anstelle von call() und filter() Funktionsaufrufen oder zur Kombination dieser beiden Methoden eingesetzt werden.

Die folgende comprehension nimmt ein Array numbers mit Elementen vom Typ Number entgegen und erstellt ein neues Array mit dem doppelten Wert jeder Zahl:

var numbers = [1, 2, 3, 4];
var doubled = [i * 2 for (i of numbers)];
alert(doubled); // Alert-Meldung: 2,4,6,8

Die folgende Anweisung mit map() ist equivalent:

var doubled = numbers.map(function(i){return i * 2;});

Comprehensions können ebenfalls zur Selektion von Elementen, die eine bestimmte Bedingung erfüllen, verwendet werden.

Hier ein Beispiel bei dem nur die gerade Zahlen aus dem Array numbers im Array evens gespeichert werden:

var numbers = [1, 2, 3, 21, 22, 30];
var evens = [i for (i of numbers) if (i % 2 === 0)];
alert(evens); // Alerts 2,22,30

Die Methode filter() erfüllt denselben Zweck:

var evens = numbers.filter(function(i){return i % 2 === 0;});

Comprehensions, die jeweils wie map() und filter() agieren, können zu einer einzelnen Comprehension kombiniert werden. Bei folgender Comprehension werden zuerst die gerade Zahlen gefiltert und dann ein Array mit den doppelten Werten dieser Zahlen erstellt:

var numbers = [1, 2, 3, 21, 22, 30];
var doubledEvens = [i * 2 for (i of numbers) if (i % 2 === 0)];
alert(doubledEvens); // Alerts 4,44,60

Die eckigen Klammern einer Array Comprehension leiten einen implizierten Block zum Zweck der Sichtbarkeitsabgrenzung ein. Neue Variablen (wie z. B. i im Code-Beispiel) werden so behandelt, als wären sie mit let deklariert worden. Sie sind also außerhalb der Comprehension nicht verfügbar.

Der Eingabewert einer Array-Comprehension muss nicht unbedingt ein Array sein; es können auch Iteratoren und Generatoren benutzt werden.

Sogar Strings können mit Array-Comprehensions verarbeitet werden; um dasselbe wie oben (we bei Array-ähnlichen Objekten) zu erreichen:

var str = 'abcdef';
var consonantsOnlyStr = [c for (c of str) if (!(/[aeiouAEIOU]/).test(c))  ].join(''); // 'bcdf'
var interpolatedZeros = [c+'0' for (c of str) ].join(''); // 'a0b0c0d0e0f0'

Noch einmal zur Erinnerung: Der Typ des Eingabewerts bleibt nicht erhalten. Das Array wird daher mit der Methode join() zu einem String zusammengefügt.

Boolesches Objekt

Das Boolesche Objekt ist eine Hülle um den primitiven booleschen Datentyp. Mit folgender Syntax wird ein boolesches Objekt erstellt:

var booleanObjectName = new Boolean(value);

Achtung: Die primitiven booleschen Werte true und false können leicht mit den Werten true und false des booleschen Objekts verwechselt werden. Jedes Objekt dessen Wert nicht undefined , null, 0, NaN, oder ein leerer String ist, inklusive eines booleschen Objekts mit dem Wert false, evaluieren bei Vergleichsanweisungen zu true. Siehe if...else-Anweisung für weitere Informationen.

Das Date-Objekt

Bei JavaScript gibt es keinen speziellen Datentyp für Datums- und Zeitangaben. Man kann jedoch das Date-Objekt für diesen Zweck verwenden. Dieses Objekt hat viele Methoden zum setzen, abfragen und manipulieren von Datumsangaben., besitzt jedoch keinerlei Eigenschaften.

JavaScript geht mit Datumsangaben genauso um wie Java. Beide Programmiersprachen haben viele gleiche Methoden und speichern Daten als die Anzahl der Millisekunden seit dem 1. Januar 1970, 00:00:00.

Die Zeitspanne des Date-Objekts reicht von -100,000,000 Tagen bis 100,000,000 Tagen relativ zum 1. Januar 1970 UTC.

Erstellung eines Date-Objekts:

var dateObjectName = new Date([parameter]);

wobei dateObjectName der Name des zu erstellenden Date-Objekts ist; es kann ein neues Objekt oder eine Eigenschaft eines bereits existierenden Objekts sein.

Wenn Date ohne das Schlüsselwort new aufgerufen wird, wird das bereitgestellte Date-Objekt zu einem String konvertiert.

Die Parameter in der oben beschriebenen Syntax können wie folgt sein:

  • Nichts: Es wird ein Objekt mit heutigem Datum und Zeit erstellt. Zum Beispiel: today = new Date();.
  • Eine Datumsangabe als String (Zeichenkette) in Form von: "Monat Tag, Jahr Stunden:Minutes:Sekunden". Zum Beispiel: var Xmas95 = new Date("December 25, 1995 13:30:00"). Wird die Uhrzeit nicht angegeben, wird sie auf 00:00:00 gesetzt.
  • Ein Satz von Integer-Werten, die für Jahr, Monat und Tag stehen. Zum Beispiel: var Xmas95 = new Date(1995, 11, 25).
  • Ein Satz von Integer-Werten, die für Jahr, Monat, Tag, Stunde, Minute und Sekunde stehen. Zum Beispiel: var Xmas95 = new Date(1995, 11, 25, 9, 30, 0);.

JavaScript 1.2 und früher
Das Date-Objekt verhält sich wie folgt:

  • Datumsangaben, die vor 1970 liegen sind nicht erlaubt.
  • Das Verhalten von JavaScript hängt vom plattformspezifischen Datumsverhalten ab; Das Verhalten des Date-Objekts variiert von Plattform zu Plattform.

Methoden des Date-Objekts

Die Methoden des Date-Objekts zur Behandlung von Datums- und Zeitangaben lassen sich grob kategorisieren:

  • "set"-Methoden, für das setzen von Datums- und Zeitwerten in Date Objekten.
  • "get"-Methoden, für das Abfragen von Datums- und Zeitwerten von Date Objekten.
  • "to"-Methoden, für die Rückgabe von Datums- und Zeitwerten von Date-Objekten in Form von String-Werten.
  • parse- und UTC-Methoden, für das parsen von Date-Strings.

Mit den "get"- und "set"-Methoden kann man Sekunden, Minuten, Stunden, den Tag des Monats, den Wochentag, den Monat und das Jahr seperat abfragen und setzen. Es exisitert außerdem eine Methode getDay, welche den Wochentag zurückliefert, jedoch keine korresponierende Methode setDay für das Setzen, da der Wochentag automatisch gesetzt wird. Diese Methoden benutzen Integer, welche die Werte wie folgt repräsentieren:

  • Sekunden und Minuten: 0 bis 59
  • Stunden: 0 bis 23
  • Tag: 0 (Sonntag) to 6 (Samstag)
  • Datum: 1 bis 31 (Monatstag)
  • Monate: 0 (Januar) bis 11 (Dezember)
  • Jahr: Jahre seit 1900

Angenommen man definiert das folgende Date-Objekt:

var Xmas95 = new Date("December 25, 1995");

Dann gibt Xmas95.getMonth() den Wert 11 und Xmas95.getFullYear() den Wert 1995 zurück.

Die Methoden getTime und setTime sind nützlich für das Vergleichen von Datumsangaben. Die Methode getTime gibt eine Zahl - die Millisekunden seit 1. Januar 1970, 00:00:00 - für das Date-Objekt zurück.

Zum Beispiel liefert der folgende Code die Anzahl der noch verbleibenden Tage im Jahr:

var today = new Date();
var endYear = new Date(1995, 11, 31, 23, 59, 59, 999); // Setze Tag und Monat
endYear.setFullYear(today.getFullYear()); // Set year to this year
var msPerDay = 24 * 60 * 60 * 1000; // Number of milliseconds per day
var daysLeft = (endYear.getTime() - today.getTime()) / msPerDay;
var daysLeft = Math.round(daysLeft); //returns days left in the year
Bei diesem Beispiel wird ein Date-Objekt mit dem Namen today erstellt, welches das heutige Datum enthält. Danach wird ein zweites Date-Objekt mit dem Namen EndYear erstellt und das Jahr auf das aktuelle Jahr gesetzt. Anschließend werden anhand der Millisekunden pro Tag die Tage zwischen today und EndYear berechnet, wobei die Methode getTime zum Einsatz kommt und zu ganzen Tagen aufgerundet wird.

Die Methode parse ist nützlich für die Verarbeitung von Strings, welche Datumsangaben enthalten, die einem Date-Objekt zugewiesen werden sollen. Zum Beispiel kommen bei folgendem Code die Methoden parse und setTime zum Einsatz, um dem IPOdate-Objekt einen Datumswert zuzuweisen:

var IPOdate = new Date();
IPOdate.setTime(Date.parse("Aug 9, 1995"));

Beispiel für die Benutzung des Date-Objekts

Beim folgenden Beispiel gibt die Funktion JSClock() die aktuelle Uhrzeit im Format einer digitalen Uhr zurück.
function JSClock() {
  var time = new Date();
  var hour = time.getHours();
  var minute = time.getMinutes();
  var second = time.getSeconds();
  var temp = "" + ((hour > 12) ? hour - 12 : hour);
  if (hour == 0)
    temp = "12";
  temp += ((minute < 10) ? ":0" : ":") + minute;
  temp += ((second < 10) ? ":0" : ":") + second;
  temp += (hour >= 12) ? " P.M." : " A.M.";
  return temp;
}
Die JSClock-Funktion erstellt zunächst ein neues Date-Objekt mit dem Namen time. Da keine Argumente angegeben sind, wird es mit dem aktuellen Datum und der aktuellen Zeit erstellt. Anschließend werden die Werte der Methoden-Aufrufe von getHours, getMinutes und getSeconds den Variablen hour, minute, und second zugewiesen.
Die nächsten vier Anweisungen erzeugen einen String mit der aktuellen Zeit. Die erste Anweisung erstellt eine Variable temp, der über einen bedingten Ausdruck ein Wert zugewiesen wird; wenn der Wert von hour größer als 12 ist, wird der Variablender Wert hour - 12 zugewiesen, ansonsten der Wert von hour. Falls hour gleich 0 ist, wird temp der Wert 12 zugewiesen.
Die nächsten beiden Anweisungen fügen an den String von temp weiteren Text an. Wenn der Wert von minute bzw. second kleiner als 10 ist, wird ein Doppelpunkt und eine 0 angefügt, andernfalls nur ein Doppelpunkt. Anschließend wird noch der Wert von minute bzw. second angefügt.

Zum Schluss wird mit einem bedingten Ausdruck überprüft, ob der Wert von hour 12 oder größer ist und entsprechend "P.M." oder "A.M." an den String temp angefügt.

Das Function-Objekt

Das vordefinierte Function-Objekt spezifiziert einen String mit JavaScript-Code, der als Funktion kompiliert wird.

Erstellung eines Function-Objekts:

var functionObjectName = new Function ([arg1, arg2, ... argn], functionBody);

functionObjectName ist der Name einer Variablen oder Eigenschaft eines existierenden Objekts. Es kann auch ein Objekt sein, an das ein Event-Handler (kleingeschrieben), wie z. B. window.onerror, angehängt ist.

arg1, arg2, ... argn sind Argumente, die von der Funktion als formale Argumentnamen benutzt werden. Jedes dieser Argumente muss ein String sein und konform mit den Regeln für valide JavaScript-Bezeichner; valide Bezeichner sind z.B. "x" oder "theForm".

functionBody ist ein String mit dem JavaScript-Code der als Funktionskörper der Funktion kompiliert wird.

Function-Objekte werden bei jeder Benutzung evaluiert. Das ist weniger effizient als eine Funktion zu deklarieren und diese im Code aufzurufen, weil deklarierte Funktionen kompiliert werden.

Zusätzlich zur Definition von Funktionen, wie an dieser Stelle beschrieben, kann auch die Funktionsanweisung und der Funktionsausdruck zum Einsatz kommen. In der JavaScript-Referenz findet man dazu mehr Informationen.

Der folgende Code weist der Variablen setBGColor eine Funktion zu. Diese Funktion legt die Hintergrundfarbe für die aktuelle Seite fest.

var setBGColor = new Function("document.bgColor = 'antiquewhite'");

Der Aufruf des Function-Objekts erfolgt, indem der Variablenname so angegeben wird, als würde es sich um eine Funktion handeln; mit dem folgenden Code wird die Funktion der setBGColor-Variablen ausgeführt:

var colorChoice="antiquewhite";
if (colorChoice=="antiquewhite") {setBGColor()}

Die Funktion kann mit einer der folgenden Vorgensweisen einem Event-Handler zugewiesen werden:

  1. document.form1.colorButton.onclick = setBGColor;
  2. <INPUT NAME="colorButton" TYPE="button"
  VALUE="Change background color"
  onClick="setBGColor()">

Die Erstellung der Variablen setBGColor wie oben ist gleichbedeutend mit der Deklaration der folgenden Funktion:

function setBGColor() {
  document.bgColor = 'antiquewhite';
}

Die Zuweisung einer Funktion an eine Variable ist ungefähr genauso wie die Deklaration einer Funktion, allerdings gibt es diverse Unterscheide:

  • Wenn einer Variablen eine Funktion zugewiesen wird, ist var setBGColor = new Function("..."), setBGColor eine Variable für die der aktuelle Wert eine Referenz zur Funktion ist, die durch new Function() erstellt wurde.
  • Wenn eine Funktion mit function setBGColor() {...}, setBGColor erstellt wurde, handelt es sich bei setBGColor um keine Variable, sondern den Namen der Funktion.

Funktionen können ineinander verschachtelt werden. Die innere Funktion ist dann durch den Sichtbarkeitsbereich der äußeren Funktion abgegrenzt.

  • Die innere Funktion kann nur mit Anweisungen von der äußeren Funktion aus angesprochen werden.
  • Die innere Funktion kann auf die Argumente und Variablen der äußeren Funktion zugreifen. Die äußere Funktion kann jedoch nicht auf die Argumente und Variablen der inneren Funktion zugreifen.

Math-Objekt

Das vordefinierte Math-Objekt besitzt Eigenschaften und Methoden für mathematische Konstanten und Funktionen. Die Eigenschaft PI des Math-Objekts hat z.B. den Wert von Pi (3.141...) und kann wie folgt in einer Applikation verwendet werden:
Math.PI

Ebenso besitzt das Math-Objekt Methoden für mathematische Standardfunktionen der Trigonometrie, Logarithmen, Exponentialfunktion und andere. Die trigonometrische Funktion sinus kann z. B. so benutzt werden:

Math.sin(1.56)

Achtung: Alle Argumente der trigonometrischen Funktionen werden als Radiant angegeben.

Die folgende Tabelle stellt eine Übersicht über die Methoden des Math-Objekts dar.

Tabelle 7.1 Methoden von Math
Methode | Beschreibung
abs | Absoluter Wert
sin, cos, tan | Trigonometrische Standardfunktionen; Das Argument ist ein Radiant
acos, asin, atan, atan2 | Inverse trigonometrische Funktionen; geben den Wert als Radiant zurück
exp, log | Exponentialfunktion e und natürlicher Logarithmus
ceil | Gibt die kleinste ganze Zahl zurück, die größer oder gleich dem Argument ist
floor | Gibt die größte ganze Zahl zurück, die kleiner oder gleich dem Argument ist
min, max | Gibt von zwei Argumenten, die kleinere oder größere Zahl zurück
pow | Exponentialfunktion; das erste Argumente ist die Basis, das zweite der Exponent
random | Zufälliger Wert zwischen 0 und 1
round | Rundet das Argument auf die nächstliegende ganze Zahl
sqrt | Quadratwurzel

Anders als bei vielen anderen Objekten erstellt man niemals selbst ein Math-Objekt, sondern benutzt immer das vordefinierte.

Number-Objekt

Das Number-Objekt verfügt über Eigenschaften für numerische Konstanten, z. B. Maximalwert, not-a-number und Unendlichkeit. Die Werte dieser Eigenschaften können nicht verändert werden. Sie werden wie folgt verwendet:
var biggestNum = Number.MAX_VALUE;
var smallestNum = Number.MIN_VALUE;
var infiniteNum = Number.POSITIVE_INFINITY;
var negInfiniteNum = Number.NEGATIVE_INFINITY;
var notANum = Number.NaN;

Man bezieht sich immer auf eine Eigenschaft des vordefinierten Number-Objekts, wie oben gezeigt, und nicht auf Eigenschaften eines Number-Objekts, das selbst erstellt wurde.

In der folgenden Tabelle sind die Eigenschaften des Number-Objekts zusammengefasst.
Tabelle 7.2 Eigenschaften von Number
Eigenschaften | Beschreibung
MAX_VALUE | Die größte darstellbare Zahl
MIN_VALUE | Die kleinste darstellbare Zahl
NaN | Spezieller "not a number"-Wert
NEGATIVE_INFINITY | Spezieller negativer Unendlichkeitswert; wird bei Überschreitung zurückgegeben
POSITIVE_INFINITY | Spezieller positiver Unendlichkeitswert; wird bei Überschreitung zurückgegeben

Der Prototype von Number stellt Methoden zum Abrufen von Informationen aus Number-Objekten in verschiedenen Formaten bereit. Die folgende Tabelle fasst die Methoden von Number.prototype zusammen.

Tabelle 7.3 Methoden von Number.prototype
Methode | Beschreibung
toExponential | Gibt einen String mit dem numerischen Wert in exponentieller Notation zurück
toFixed | Gibt einen String mit dem numerischen Wert als Gleitkommazahl zurück
toPrecision | Gibt einen String mit dem numerischen Wert als Gleitkommazahl und mit beliebig vielen Nachkommastellen zurück
toSource | Gibt ein Objekt-Literal zurück, welches das spezifizierte Number-Objekt repräsentiert; der Wert kann zur Erstellung eines neuen Objekts benutzt werden; überschreibt die Methode Object.toSource.
toString | Gibt einen String zurück, welcher das angegebene Objekt repräsentiert; überschreibt die Methode Object.toString.
valueOf | Gibt den primitiven Wert des spezifizierten Objekts zurück; überschreibt die Methode Object.valueOf.

RegExp-Objekt

Das RegExp-Objekt ermöglicht das Arbeiten mit regulären Ausdrücken. Es ist im Kapitel Reguläre Ausdrücke beschrieben.

String-Objekt

Das String-Objekt ist eine Hülle um den primitiven String-Datentyp. Es sollte nicht mit einem String-Literal verwechselt werden. Der folgende Code erstellt z.B. einen String-Literal s1 und ein String-Object s2:

var s1 = "foo"; // erstellt einen String-Literal
var s2 = new String("foo"); // erstellt ein String-Objekt

Alle Methoden des String-Objekts können für String-Literale aufgerufen werden. JavaScript konvertiert den String-Literal automatisch zu einem temporären String-Objekt, ruft die Methode auf und verwirft das temporäre Objekt wieder. Auch die Methode String.length kann auf einen String-Literal angewendet werden.

String-Literale sollten verwendet werden, solange nicht unbedingt ein String-Objekt gebraucht wird, da String-Objekte ein nicht intuitives Verhalten aufweisen können. Zum Beispiel:

var s1 = "2 + 2"; // erstellt einen String-Literal
var s2 = new String("2 + 2"); // erstellt ein String-Objekt
eval(s1); // gibt 4 zurück
eval(s2); // gibt den String "2 + 2" zurück

Ein String-Object besitzt eine Eigenschaft, length, welche den Wert für die Anzahl der Zeichen des Strings angibt. Der folgende Code weist x den Wert 13 zu, weil der String "Hello, World!" aus 13 Zeichen besteht:

var mystring = "Hello, World!";
var x = mystring.length;
Ein String-Objekt hat zwei Arten von Methoden: solche, die eine Variation der Zeichenfolge selbst zurückliefern, wie z. B. substring und toUpperCase; und solche, die eine HTML-formatierte Version der Zeichenfolge zurückgeben, wie bold und link.

Beim vorigen Beispiel geben z. B. beide Methoden, mystring.toUpperCase() und "hello, world!".toUpperCase(),die Zeichenfolge "HELLO, WORLD!" zurück.

Die substring-Methode nimmt zwei Argumente entgegen und gibt die Zeichen des Strings zurück, welche zwischen den beiden Positionen liegen. Bezogen auf das vorige Beispiel gibt mystring.substring(4, 9) die Zeichenfolge "o, Wo" zurück. In der JavaScript-Referenz findet man genauere Informationen zur Methode substring .

Das String-Objekt besitzt auch eine Reihe von Methoden für die automatische HTML-Formatierung, wie bold für fettgedruckten Text und link zur Erstellung eines Hyperlinks. Einen Hyperlink zu einer hypothetischen URL kann man mit der Methode link z. B. wie folgt erstellen:
mystring.link("http://www.helloworld.com")

Die folgende Tabelle fasst die Methoden des String-Objekts zusammen.

Tabelle 7.4 Methoden von String-Instanzen
Methode | Beschreibung
anchor | Erstellt einen HTML-Anker
big, blink, bold, fixed, italics, small, strike, sub, sup | Erstellt einen HTML-formatierten String
charAt, charCodeAt | Gibt ein Zeichen oder einen Zeichencode an einer bestimmten Position eines Strings zurück
indexOf, lastIndexOf | Gibt die Position eines spezifizierten Substring in einem String bzw. die Position des letzten Vorkommen des Substrings zurück
link | Erstellt einen HTML-Hyperlink
concat | Kombiniert den Text zweier Strings und gibt einen neuen String zurück
fromCharCode | Konstruiert einen String aus den übergebenen Unicode-Sequenzen; dies ist eine Methode der String-Klasse und keine Instanz.
split | Teilt ein String-Objekt in ein Array mit Strings anhand eines Trennzeichens auf
slice | Extrahiert einen Teil eines Strings und gibt einen neuen String zurück
substring, substr | Gibt einen Teil des String zurück; entweder wird der Index für Anfang und Ende oder der Start und eine Länge angegeben
match, replace, search | Methoden zum Arbeiten mit regulären Ausdrücken
toLowerCase, toUpperCase | Gibt den String umgewandelt in Klein- oder Großschreibweise zurück


Feinheiten des Objektmodells

JavaScript ist eine objektbasierte Sprache, die auf Prototypen aufbaut anstatt wie andere Sprachen auf Klassen. Aufgrund dieser unterschiedlichen Aufbauweise ist vielleicht nicht sofort ersichtlich, wie mit JavaScript Objekthierarchien zusammengestellt werden können und die Vererbung von Objekteigenschaften und Werten funktioniert. Bei diesem Kapitel wird versucht darüber Klarheit zu schaffen.

Für dieses Kapitel wird angenommen, dass der Leser bereits ein wenig Erfahrung mit JavaScript gesammelt und JavaScript-Funktionen benutzt hat, um einfache Objekte zu erstellen.

Unterschiede von klassenbasierten und objektbasierten Sprachen

Klassenbasierte objektorientierte Sprachen (wie Java oder C++) setzen auf ein Konzept mit zwei verschiedene Einheiten: Klassen und Instanzen.

  • Eine Klasse definiert alle Eigenschaften (Methoden und Felder bei Java oder Members bei C++), die einen bestimmten Satz von Objekten charakterisieren. Eine Klasse ist ein abstraktes Ding, eine Art Bauplan für ein Objekt, und keines der Objekte selbst. Zum Beispiel repräsentiert die Klasse Employee den ganzen Satz von Mitarbeitern.
  • Eine Instanz hingegen ist eine Instanzierung der Klasse, also ein Member der Klasse. Zum Beispiel könnte Viktoria eine Instanz der Klasse Employee sein und eine Mitarbeiterin eines Unternehmens repräsentieren. Eine Instanz hat genau die Eigenschaften der Elternklasse (nicht mehr und nicht weniger).

Eine prototypenbasierte Sprache wie JavaScript macht diese Unterscheidung nicht: Es gibt einfach nur Objekte. Bei einer prototypenbasierten Sprache gibt es prototypische Objekte, die vordefinierte Eigenschaften besitzen und als Vorlage für neue Objekte dienen. Darüber hinaus kann jedes Objekt eigene Eigenschaften besitzen, die entweder bei der Erstellung oder später während der Laufzeit hinzugefügt werden. Jedes beliebige Objekt kann ein Prototype für ein anderes Objekt sein, sodass sich die Objekte die Eigenschaften teilen.

Definition einer Klasse

Bei klassenbasierten Sprachen definiert man eine Klasse in einer separaten Klassendefinition. In dieser Definition können spezielle Methoden, sogenannte Konstruktoren, zur Erstellung von Klasseninstanzen definiert werden. Mit einer Konstruktormethode lassen sich Anfangswerte für die Eigenschaften der Instanzen festlegen und andere Operationen zum Zeitpunkt der Erstellung durchführen. Der Operator new kommt in Verbindung mit der Konstruktormethode für die Erstellung von Klasseninstanzen zum Einsatz.

JavaScript folgt einem ähnlichen Modell, jedoch gibt es hier keine seperate Klassendefintion unabhängig vom Konstruktor eines Objekts. Stattdessen definiert man eine Konstruktorfunktion, womit beim Aufruf Objekte mit bestimmten in der Definition festgelegten Eigenschaften und Werten erstellt werden. Jede beliebige JavaScript-Funktion kann als Konstruktor verwendet werden. Der Operator new kommt in Verbindung mit der Konstruktorfunktion für die Erstellung von Objekten zum Einsatz.

Kindklassen und Vererbung

Bei einer klassenbasierten Sprache kommt die Hierarchie von Klassen durch die Definition von Klassen zustande. Bei der Definition einer Klasse legt man fest, ob die neue Klasse eine Kindklasse von einer bereits bestehenden Klasse sein soll. Die Kindklasse erbt sodann alle Eigenschaften der Elternklasse. Zusätzlich können der Kindklasse neue Eigenschaften hinzugefügt, oder die von der Elternklasse geerbten Eigenschaften verändert werden. Nehmen wir z. B. an, die Klasse Employee würde ausschließlich Eigenschaften für den Namen und die Abteilung der jeweiligen Mitarbeiter besitzen. Dann kann man eine Klasse Manager als Kindklasse der Klasse Employee erstellen und dieser eine Eigenschaft für Berichte hinzufügen. Eine Instanz der Managerklasse würde dann alle drei Eigenschaften besitzen: Name, Abteilung und Berichte.

Hinzufügen und Entfernen von Eigenschaften

Bei klassenbasierten Sprachen erstellt man in der Regel eine Klasse während der Kompilierung und erzeugt dann Klasseninstanzen entweder bereits bei der Kompilierung oder zur Laufzeit. Nach Erstellung der Klasse können keine Eigenschaften hinzugefügt, entfernt oder die Art von Eigenschaften geändert werden. JavaScript erlaubt hingegen das Hinzufügen oder Entfernen von Eigenschaften oder Objekten auch während der Laufzeit; wird einem Objekt eine neue Eigenschaft hinzugefügt, das als Prototype für ein anderes Objekt fungiert, so erhält auch das erbende Objekt diese neue Eigenschaft.

Zusammenfassung der Unterschiede

Die folgende Tabelle gibt einen schnellen Überblick über einige der Unterschiede zwischen JavaScript und Java. Der Rest dieses Kapitels beschreibt JavaScript-Konstruktoren und Prototypen für die Erstellung von Objekthierarchien genauer und vergleicht das Konzept von JavaScript mit dem von Java.

Tabelle 8.1 Vergleich von klassenbasierten (Java) und prototypbasierten (JavaScript) objektbasierten Systemen
Klassenbasiert (Java) | Prototypbasiert (JavaScript)
Klassen und Instanzen sind verschiedene Einheiten. | Alle Objekte sind Instanzen.
Definition einer Klasse über eine Klassendefinition; Instanzierung von Klassen mit Hilfe von Konstruktormethoden; | Definition und Erstellung von Sätzen eines Objekts mit Hilfe von Konstruktorfunktionen;
Erzeugung eines einzelnen Objekts mit dem Operator new. | Genauso.
Konstruktion einer Objekthierarchie mit Klassendefinitionen und der Definition von Kindklassen; | Konstruktion einer Objekthierarchie durch die Zuweisung eines Objekts als Prototype für eine Konstruktorfunktion;
Vererbung von Eigenschaften durch Klassen-Verkettung; | Vererbung von Eigenschaften durch Prototype-Verkettung;
Die Klassendefinition legt alle Eigenschaften einer Klasseninstanz fest. Eigenschaften können nicht während der Laufzeit hinzugefügt werden. | Die Konstruktorfunktion oder der Prototype legt einen anfänglichen Satz von Eigenschaften fest; neue Eigenschaften können dynamisch für einzelne Instanzen oder den kompletten Satz von Instanzen hinzugefügt oder entfernt werden;

Das Employee-Beispiel

Die nachfolgenden Erklärungen dieses Kapitels beziehen sich zum Teil auf das Employee-Beispiel, dessen Hierarchie wie in folgendem Bild aufgebaut ist:

Bild 8.1: Eine einfache Objekthierarchie

Diese Hierarchie setzt sich aus folgenden Objekte zusammen:

  • Employee hat die Eigenschaften name (mit einem leeren String als Anfangswert) und dept (mit dem Anfangswert "general").
  • Manager basiert auf dem Employee-Objekt und besitzt die zusätzliche Eigenschaft reports. Der Anfangswert ist ein leeres Array, das für die Speicherung von Employee-Objekten vorgesehen ist.
  • WorkerBee basiert ebenfalls auf dem Employee-Objekt und besitzt die zusätzliche Eigenschaft projects (mit einem leeren Array als Anfangswert, das für die Speicherung von Stringwerten vorgesehen ist).
  • SalesPerson basiert auf dem Objekt WorkerBee und besitzt die zusätzliche Eigenschaft quota (mit 100 als Anfangswert). Außerdem wird der Wert der Eigenschaft dept mit dem Wert "sales" überschrieben, wodurch ersichtlich werden soll, dass Verkaufsleute in derselben Abteilung beschäftigt sind.
  • Engineer basiert auf dem Objekt WorkerBee und besitzt die zusätzliche Eigenschaft machine (mit einem leeren String als Anfangswert). Der Wert der Eigenschaft dept wird mit dem Wert "engineering" überschrieben.

Erstellung der Hierarchie

Es gibt verschiedene Möglichkeiten, die entsprechenden Konstruktiorfunktionen zu definieren, um die Hierarchie zu implementieren. Welches Verfahren sich am besten eignet, hängt vom Einsatzzweck der Applikation ab.

In diesem Abschnitt wird beschrieben, wie mit sehr einfachen (jedoch relativ unflexiblen) Definitionen eine Vererbungshierarchie aufgebaut werden kann. Bei diesen Definitionen können den Eigenschaften eines Objekts bei der Erstellung keine eigenen Werte zugewiesen werden. Den neu erstellten Objekten werden Anfangswerte zugewiesen, die erst später geändert werden können. Das Bild 8.2 stellt die Hierarchie mit diesen simplen Definitionen dar.

Bei einer echten Applikation würde man evtl. Konstruktoren definieren, womit den Objekteigenschaften bei der Erzeugung selbstdefinierte Anfangswerte zugewiesen werden können; (siehe More Flexible Constructors für weitere Informationen). Zu Anfang begnügen wir uns jedoch mit einfachen Definitionen, um rein die Grundstruktur der Vererbungshierarchie zu veranschaulichen.

Bild 8.2: Die Employee-Objektdefinitionen

Die Java- und Javascript-Definitionen von Employee in der folgenden Tabelle sind gleichbedeutend. Der einzige Unterschied besteht darin, dass bei Java der Typ für jede Eigenschaft spezifiziert werden und explizit eine Konstruktormethode erstellt werden muss.

JavaScript Java
function Employee () {
  this.name = "";
  this.dept = "general";
}

public class Employee {
   public String name;
   public String dept;
   public Employee () {
      this.name = "";
      this.dept = "general";
   }
}

Die Definitionen der Objekte Manager und WorkerBee (Bild 8.2) zeigen die Unterschiede bei der Spezifikation des übergeordneten Objekts in der Vererbungskette. Bei JavaScript weist man der Eigenschaft prototype der Konstruktorfunktion eine Prototype-Instanz als Wert zu. Dies kann jederzeit nach der Definition der Konstruktorfunktion geschehen. Bei Java gibt man die Elternklasse in der Definition an. Eine Änderung der Elternklasse ist außerhalb der Klassendefintion nicht möglich.

JavaScript Java
function Manager () {
  this.reports = [];
}
Manager.prototype = new Employee;

function WorkerBee () {
  this.projects = [];
}
WorkerBee.prototype = new Employee;

public class Manager extends Employee {
   public Employee[] reports;
   public Manager () {
      this.reports = new Employee[0];
   }
}

public class WorkerBee extends Employee {
   public String[] projects;
   public WorkerBee () {
      this.projects = new String[0];
   }
}

Mit den Definitionen Engineer und SalesPerson werden Objekte erstellt, die von WorkerBee abstammen und deshalb auch von Employee, da WorkerBee von Employee erbt. Ein Objekt dieser Gruppe besitzt die Eigenschaften von allen Objekten, die in der Hierarchie übergeordnet sind. Der geerbte Wert der Eigenschaft dept wird mit einem neuen Wert überschrieben.

JavaScript Java
function SalesPerson () {
   this.dept = "sales";
   this.quota = 100;
}
SalesPerson.prototype = new WorkerBee;

function Engineer () {
   this.dept = "engineering";
   this.machine = "";
}
Engineer.prototype = new WorkerBee;

public class SalesPerson extends WorkerBee {
   public double quota;
   public SalesPerson () {
      this.dept = "sales";
      this.quota = 100.0;
   }
}

public class Engineer extends WorkerBee {
   public String machine;
   public Engineer () {
      this.dept = "engineering";
      this.machine = "";
   }
}

Durch den Gebrauch dieser Definitionen können Instanzen der entsprechenden Objekte erzeugt werden, welche dann die festgelegten Anfangswerte für die Eigenschaften erhalten. Das Bild 8.3 führt die Benutzung der Definitionen für die Erstellung neuer Objekte vor und zeigt die Werte der Eigenschaften von neuen Objekten.

Hinweis: Der Begriff Instanz hat bei klassenbasierten Sprachen eine spezielle technische Bedeutung. Bei diesen Sprachen ist eine Instanz eine eigenständige Instanzierung einer Klasse und unterscheidet sich grundlegend von der Klasse selbst. Bei JavaScript gibt es diese spezielle Bedeutung nicht, da es keinen Unterschied zwischen Instanzen und Klassen gibt. Oft spricht man jedoch von Instanzen, wenn Objekte gemeint sind, die mit einer bestimmten Konstruktorfunktion erstellt wurden. Somit darf man für das Beispiel behaupten, dass jane eine Instanz von Engineer ist. Auf dieselbe Weise haben auch die Begriffe Eltern, Kind, Vorfahre und Nachkomme keine formelle Bedeutung bei JavaScript und können für Objekte benutzt werden, die anderen Objekten in der Prototypekette über- oder untergeordnet sind.

Bild 8.3: Erstellung von Objekten mit einfachen Definitionen

Objekt-Eigenschaften

Dieser Abschnitt erklärt, wie Objekte die Eigenschaften von anderen Objekten erben, die in der Prototypekette hierarchisch übergeordnet sind und was passiert, wenn neue Eigenschaften während der Laufzeit hinzugefügt werden.

Vererbung von Eigenschaften

Angenommen man erstellt das Objekt mark als WorkerBee (wie in Bild 8.3) mit der folgenden Anweisung:

var mark = new WorkerBee;

Wenn die Ausführung beim new-Operator angelangt ist, wird ein neues WorkerBee-Objekt erstellt und dieses Objekt dem this-Schlüsselwort der WorkerBee-Konstruktorfunktion zugewiesen. Die Konstruktorfunktion setzt dann explizit den Wert für die Eigenschaft projects (wie in der Konstruktorfunktion definiert) und implizit den Wert der internen Eigenschaft __proto__ auf den Wert von WorkerBee.prototype (__proto__ hat jeweils zwei Unterstriche am Anfang und Ende). Die Eigenschaft __proto__ bestimmt die Prototype-Verkettung für die Rückgabe von Eigenschaftswerten. Sobald diese Eigenschaften gesetzt sind, wird das neue Objekt zurückgegeben und der Variablen mark zugewiesen.

Bei diesem Vorgang werden dem Objekt für die Eigenschaften, die mark von der Prototypekette erbt, nicht explizit (lokale) Werte für die Eigenschaften zugewiesen. Wenn der Wert einer Eigenschaft abgefragt wird, überprüft JavaScript zunächst, ob dieser Wert lokal existiert; trifft dies zu, wird der Wert zurückgegeben. Falls die Eigenschaft lokal nicht existiert, überprüft JavaScript die Prototypekette (über die Eigenschaft __proto__) und besitzt ein Objekt in der Prototypekette einen Wert für die Eigenschaft, wird dieser zurückgegeben. Falls gar keine solche Eigenschaft gefunden wird, meldet JavaScript, dass das Objekt keine solche Eigenschaft besitzt. Demzufolge hat das Objekt mark die folgenden Eigenschaften und Werte:

mark.name = "";
mark.dept = "general";
mark.projects = [];

Das mark-Objekt erbt die Werte für die Eigenschaften name und dept vom prototypischen Objekt in mark.__proto__. Diesem wurde ein lokaler Wert für die Eigenschaft projects über die WorkerBee-Konstruktorfunktion zugewiesen. Durch dieses Gefüge wird bei JavaScript Vererbung konstruiert. Einige weitere Details zu diesem Vorgang sind im Kapitel Property Inheritance Revisited beschrieben.

Mit den Konstruktoren können keine Werte für einzelne Instanzen definiert, sondern nur allgemeine Werte für alle Objekte eines Typs festgelegt werden. Die Eigenschaftswerte sind also Default-Werte, die allen neuen WorkerBee-Objekten bei ihrer Erzeugung zugewiesen werden. Natürlich kann man die Werte der Eigenschaften jederzeit ändern und z. B. mark wie folgt einen speziellen Wert zuweisen:

mark.name = "Doe, Mark";
mark.dept = "admin";
mark.projects = ["navigator"];

Hinzufügen von Eigenschaften

Bei JavaScript kann man jedem beliebigen Objekt während der Laufzeit neue Eigenschaften hinzufügen. Dabei muss die Eigenschaft nicht zwingend durch einen Konstruktor erstellt worden sein, auch komplett neue Eigenschaften lassen sich jederzeit während der Laufzeit definieren. Um eine neue Eigenschaft spezielle für ein einzelnes Objekt zu definieren, weißt man dem Objekt wie folgt einen neuen Wert zu:

mark.bonus = 3000;

Hiernach besitzt das mark-Objekt eine Eigenschaft bonus. Die anderen WorkerBee-Objekte bleiben unverändert.

Wenn man einem Objekt, das als Prototype für eine Konstruktorfunktion dient, eine neue Eigenschaft zuweist, wird diese Eigenschaft allen Objekten hinzugefügt, die von dem Prototype erben. Zum Beispiel kann man mit der folgenden Anweisung allen Employee-Objekten die Eigenschaft specialty hinzufügen:

Employee.prototype.specialty = "none";

Sobald diese Anweisung ausgeführt wird, besitzt das Objekt mark ebenfalls die Eigenschaft speciality mit dem Wert "none". Im nächsten Bild ist dargestellt, wie dem Prototype des Employee-Objekts die neue Eigenschaft hinzugefügt und anschließend dem Engineer-Prototype ein anderer Wert zugewiesen wird.

Bild 8.4: Hinzufügen von Eigenschaften

Flexiblere Konstruktoren

Mit den bisher beschriebenen Konstruktoren lassen sich bei der Erzeugung von Instanzen keine variablen Eigenschaftswerte definieren. Wie bei Java kann man Konstruktoren jedoch auch Argumente übergeben, um mit den Argumentwerten Instanzen zu initialisieren:

Bild 8.5: Festlegen von Eigenschaften über den Konstruktor, Teil 1

Die folgende Tabelle zeigt die Java- und JavaScript-Definitionen für diese Objekte:

JavaScript Java
function Employee (name, dept) {
  this.name = name || "";
  this.dept = dept || "general";
}

public class Employee {
   public String name;
   public String dept;
   public Employee () {
      this("", "general");
   }
   public Employee (String name) {
      this(name, "general");
   }
   public Employee (String name, String dept) {
      this.name = name;
      this.dept = dept;
   }
}

function WorkerBee (projs) {
  this.projects = projs || [];
}
WorkerBee.prototype = new Employee;

public class WorkerBee extends Employee {
   public String[] projects;
   public WorkerBee () {
      this(new String[0]);
   }
   public WorkerBee (String[] projs) {
      projects = projs;
   }
}

function Engineer (mach) {
   this.dept = "engineering";
   this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;

public class Engineer extends WorkerBee {
   public String machine;
   public Engineer () {
      dept = "engineering";
      machine = "";
   }
   public Engineer (String mach) {
      dept = "engineering";
      machine = mach;
   }
}

Diese JavaScript-Definitionen setzen eine spezielle logische Verknüpfung für Default-Werte ein:

this.name = name || "";

Der JavaScript-Operator für das logische ODER (||) wertet das ihm vorangestellte Argument aus. Wenn dieses zu true evaluiert, wird der Wert zurückgegeben. Andernfalls wird der Wert des zweiten Arguments hinter dem Operator zurückgegeben. Mit dem Ausdruck wird also überprüft, ob name einen brauchbaren Wert besitzt, welcher der Eigenschaft this.name zugewiesen werden kann. Falls ja, bekommt this.name diesen Wert. Andernfalls wird this.name ein leerer String zugewiesen. In diesem Kapitel wird auf dieses Konstrukt zurückgegriffen, um die Code-Beispiele knapp und übersichtlich zu halten.

Hinweis: Es kann passieren, dass dieses Konstrukt nicht wie vorgesehen funktioniert, wenn die Konstruktorfunktion mit Argumenten aufgerufen wird, die zu false evaluieren (wie z. B. 0 (Null) und der leere String (""). In diesem Fall wird der Default-Wert gewählt.

Mit diesen Definitionen kann man bei der Erstellung von Instanzen eines Objekts Werte für die lokalen Eigenschaften festlegen. Wie im Bild 8.5 bereits gezeigt, kann man die folgende Anweisung einsetzen, um eine neue Engineer-Instanz zu erzeugen:

var jane = new Engineer("belau");

Die Eigenschaften von Jane sind sodann:

jane.name == "";
jane.dept == "engineering";
jane.projects == [];
jane.machine == "belau"

Mit diesen Definitionen kann den vererbten Eigenschaften wie z. B. name kein Anfangswert zugewiesen werden; möchte man dies tun, so muss der Konstruktorfunktion weiterer Code hinzugefügt werden.

Bisher haben wir mit der Konstruktorfunktion ein generisches Objekt erstellt und dann lokale Eigenschaften und Werte zugewiesen. Ebenso kann man auch die Konstruktorfunktion eines in der Prototypkette höherliegenden Objekts aufrufen, um dem Objekt die entsprechenden Eigenschaften hinzuzufügen. Das folgende Bild zeigt diese neuen Definitionen.

Bild 8.6: Festlegen von Eigenschaften über den Konstruktor, Teil 2

Hier die neue Definition für die Engineer-Konstruktorfunktion:

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}

Angenommen man erstellt nun ein Engineer-Objekt wie folgt:

var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");

Dann führt JavaScript diese Schritte aus:

  1. Der new Operator erstellt ein generisches Objekt und setzt die __proto__-Eigenschaft auf Engineer.prototype.
  2. Der new-Operator übergibt das neue Obejekt an den Engineer-Konstruktor als Wert für this.
  3. Der Konstruktor erstellt eine neue Eigenschaft base für dieses Objekt und weist den Wert des WorkerBee-Konstruktors der Eigenschaft base zu. Dieser Vorgang macht den WorkerBee-Konstruktor zu einer Methode des Engineer-Objekts. Der Name der Eigenschaft base ist nicht speziell. Es kann jeder beliebige Bezeichner verwendet werden.

  4. Der Konstruktor ruft die Methode base auf und übergibt als Argumente zwei der Argumente, die an die Konstruktorfunktion übergeben wurden ("Doe, Jane" und ["navigator", "javascript"]) und außerdem noch den String "engineering". Da explizit der Wert "engineering" im Konstruktor angegeben wurde, erhalten alle Engineer-Objekte denselben Wert für die vererbte Eigenschaft dept. Mit diesem Wert wird der von Employee geerbten Wert überschrieben.

  5. Da base eine Methode von Engineer ist, bindet JavaScript das this-Schlüsselwort innerhalb des Aufrufs von base an das im ersten Schritt erstellte Objekt. Darum übergibt die WorkerBee-Funktion im Gegenzug die Argumente "Doe, Jane" und "engineering" an die Employee-Konstruktorfunktion. Bei der Rückgabe der Employee Konstruktorfunktion, benutzt die WorkerBee-Function das verbleibende Argument für das Setzen der projects Eigenschaft.
  6. Vor dem Ende der base Methode initialisiert der Engineer-Konstruktor die Eigenschaft machine zu "belau".
  7. Zum Schluss wird das neue Engineer-Objekt der Variablen jane zugewiesen.

Es scheint so, als hätte man mit dem Aufruf des WorkerBee-Konstruktors aus dem Engineer-Konstruktor heraus die Vererbung für das Engineering-Objekte initiiert. Das ist jedoch nicht der Fall. Der Aufruf des WorkerBee-Konstuktors stellt sicher, dass ein Engineering-Objekt mit allen Eigenschaften erstellt wird, welche durch die aufgerufene Konstruktorfunktionen hinzugefügt wurden. Fügt man den Prototypen von Employee oder WorkerBee später Eigenschaften hinzu, so werden diese nicht vom Engineer-Objekt geerbt. Hat man z. B. die folgenden Anweisungen:

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}
var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";

Dann erbt das Objekt jane die Eigenschaft specialty nicht. Der Prototype muss explizit zugewiesen werden, um dynamische Vererbung umzusetzen. Verändert man den Code wie folgt:

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
var jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";

Dann bekommt die Eigenschaft specialty des jane-Objekts den Wert "none".

Eine weitere Möglichkeit zur Umsetzung von Vererbung ist der Einsatz der Methoden call() / apply(). Der folgende Code ist äquivalent:

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}

function Engineer (name, projs, mach) {
  WorkerBee.call(this, name, "engineering", projs);
  this.machine = mach || "";
}

Die Verwendung der Methode call() ermöglicht saubereren Code, weil das base-Objekt dabei nicht mehr gebraucht wird.

Vererbung von Eigenschaften (Fortsetzung)

Die vorhergehenden Abschnitte beschreiben, wie durch JavaScript-Konstruktoren und Prototypen eine Vererbungshierarchie aufgebaut werden kann. Dieser Abschnitt beschreibt die Feinheiten des Vererbungskonzepts.

Lokale und vererbte Werte

Beim Zugriff auf die Eigenschaft eines Objekts führt JavaScript nacheinander diese Schritte aus, wie bereits weiter oben beschreiben:

  1. Überprüfung, ob der Wert lokal existiert und wenn ja, Rückgabe des Werts.
  2. Wenn kein lokaler Wert existiert, Überprüfung der Prototypekette (über die Eigenschaft __proto__).
  3. Wenn ein Objekt in der Prototypekette einen Wert für die Eigenschaft besitzt, Rückgabe dieses Werts.
  4. Wurde die Eigenschaft nicht gefunden, besitzt das Objekt keine solche Eigenschaft.

Das Ergebnis dieses Ablaufs hängt von den Definitionen ab. Kommen wir noch einmal auf das Beispiel vom Anfang des Kapitels zurück:

function Employee () {
  this.name = "";
  this.dept = "general";
}

function WorkerBee () {
  this.projects = [];
}
WorkerBee.prototype = new Employee;

Erstellt man nach diesen Definitionen z. B. das Objekt amy als Instanz von WorkerBee mit der folgenden Anweisung:

var amy = new WorkerBee;

Dann besitzt amy nur die lokale Eigenschaft projects. Die Werte der Eigenschaften name und dept sind nicht lokal zu amy und werden daher über die Prototypekette bzw. die Eigenschaft __proto__ abgefragt. Also besitzt amy diese Eigenschaften und Werte:

amy.name == "";
amy.dept == "general";
amy.projects == [];

Ändert man nun die Eigenschaft name des Prototype, der mit Employee verknüpft ist:

Employee.prototype.name = "Unknown"

Dann würde man vielleicht zunächst annehmen, dass der neue Wert bis nach unten für alle Instanzen von Employee übernommen wird. Das ist jedoch nicht der Fall.

Bei jeder Erzeugung einer Instanz des Employee-Objekts bekommt diese Instanz einen lokalen Wert (einen leeren String) für die Eigenschaft name zugewiesen. Wenn man also den WorkerBee-Prototype festlegt, indem man ein neues Employee-Objekt erstellt, besitzt Workerbee.prototype eine Eigenschaft name mit einem lokalen Wert. Beim Zugriff auf die Eigenschaft findet JavaScript dann den lokalen Wert des Prototype WorkerBee.prototype und sucht die Prototypekette nicht weiter bis zu Employee.prototype ab.

Möchte man den Wert einer Objekt-Eigenschaft während der Laufzeit ändern und der neue Wert soll auch für alle erbenden Objekte gesetzt werden, so darf man diesen nicht in der Konstruktorfunktion zuweisen. Stattdessen weist man dem mit dem Objekt verknüpften Prototype den Wert zu.

Ändert man vorherigen Code z. B. wie folgt:

function Employee () {
  this.dept = "general";
}
Employee.prototype.name = "";

function WorkerBee () {
  this.projects = [];
}
WorkerBee.prototype = new Employee;

var amy = new WorkerBee;

Employee.prototype.name = "Unknown";

Dann besitzt die Eigenschaft name von amy den Wert "Unknown".

Wie diese Beispiele zeigen, sollte man also Werte dem Prototype des Konstruktors zuweisen und nicht in der Konstruktorfunktion, falls man Default-Werte für Objekteigenschaften setzen und diese während der Laufzeit ändern möchte.

Festlegen von Instanzen-Beziehungen

Beim Zugriff auf Eigenschaften wird zuerst überprüft, ob das Objekt selbst eine entsprechende Eigenschaft besitzt. Wird die Eigenschaft lokal nicht gefunden, sucht JavaScript in der speziellen Objekteigenschaft __proto__. Dieser Ablauf wird rekursiv fortgeführt, solange bis eine Eigenschaft gefunden oder festgestellt wurde, dass keines der Objekte der Prototypekette eine solche Eigenschaft besitzt.

Die spezielle Eigenschaft __proto__ wird bei der Erzeugung eines Objekts gesetzt. Der Wert dieser Eigenschaft ist dann die prototype-Eigenschaft des Konstruktors. Demzufolge wird mit dem Ausdruck new Foo() ein Objekt mit __proto__ == Foo.prototype erstellt. Änderungen der Eigenschaften von Foo.prototype werden für alle Objekte übernommen, die mit new Foo() erzeugt wurden.

Jedes Objekt besitzt eine __proto__-Eigenschaft (mit Ausnahme von Object) und jede Funktion hat eine prototype-Objekteigenschaft. Objekte können also durch die Prototype-Vererbung mit anderen Objekten verwandt sein. Diese Verwandtschaft kann man überprüfen, indem man die __proto__-Eigenschaften mit dem prototype-Objekt einer Funktion vergleicht. Der Operator instanceof vergleicht ein Objekt mit einer Funktion und liefert true zurück, wenn das Objekt vom Prototype der Funktion erbt.

Hier ein Beispiel:

var f = new Foo();
var isTrue = (f instanceof Foo);

Hier noch ein weiteres Beispiel: Angenommen man hat dieselben Definitionen wie unter Vererbung von Eigenschaften und erstellt ein Engineer-Objekt wie folgt:

var chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");

Dann evaluieren die folgenen Anweisungen alle zu true:

chris.__proto__ == Engineer.prototype;
chris.__proto__.__proto__ == WorkerBee.prototype;
chris.__proto__.__proto__.__proto__ == Employee.prototype;
chris.__proto__.__proto__.__proto__.__proto__ == Object.prototype;
chris.__proto__.__proto__.__proto__.__proto__.__proto__ == null;

Und man könnte z. B. eine Funktion instanceOf wie folgt schreiben:

function instanceOf(object, constructor) {
   while (object != null) {
      if (object == constructor.prototype)
         return true;
      if (typeof object == 'xml') {
        return constructor.prototype == XML.prototype;
      }
      object = object.__proto__;
   }
   return false;
}
Achtung: Die Implementierung dieser Funktion überprüft den Typ des Objekts gegen "xml", um eine Eigenheit von neueren JavaScript-Versionen bei der Repräsentation von XML-Objekten zu beachten. Siehe bug 634150 für weitere Informationen.

Bei Verwendung der instanceOf-Funktion von oben evaluieren diese Ausdrücke zu true:

instanceOf (chris, Engineer)
instanceOf (chris, WorkerBee)
instanceOf (chris, Employee)
instanceOf (chris, Object)

Der folgende Ausdruck evaluiert indessen zu false:

instanceOf (chris, SalesPerson)

Globale Informationen in Konstruktoren

Bei der Erstellung von Konstruktoren ist Vorsicht geboten, wenn man globale Werte im Konstruktor verändert. Möchte man z. B. für jedes Empoyee-Objekt eine eindeutige ID generieren und automatisch dem Objekt zuweisen, so könnte man Employee wie folgt definieren:

var idCounter = 1;

function Employee (name, dept) {
   this.name = name || "";
   this.dept = dept || "general";
   this.id = idCounter++;
}

Mit dieser Definition weist der Konstruktor bei der Erstellung eines Empoyee-Objekts diesem eine ID zu und inkrementiert anschließend die globale Zählervariable idCounter. Wenn die nächsten Anweisungen wie folgt gemacht werden, bekommt victoria.id den Wert 1 und harry.id den Wert 2:

var victoria = new Employee("Pigbert, Victoria", "pubs");
var harry = new Employee("Tschopik, Harry", "sales");

Auf den ersten Blick mag diese Vorgensweise unproblematisch erscheinen. Man sollte jedoch bedenken, dass idCounter bei jeder Erstellung eines Employee-Objekts inkrementiert wird. Hätte man z. B. unter Verwendung dieser Definitionen die komplette in diesem Kaptitel beschriebene Vererbungshierarchie erstellt, dann würde der Employee-Konstuktor bei jeder Zuweisung eines Prototype aufgerufen.

Angenommen man hat den folgenden Code:

var idCounter = 1;

function Employee (name, dept) {
   this.name = name || "";
   this.dept = dept || "general";
   this.id = idCounter++;
}

function Manager (name, dept, reports) {...}
Manager.prototype = new Employee;

function WorkerBee (name, dept, projs) {...}
WorkerBee.prototype = new Employee;

function Engineer (name, projs, mach) {...}
Engineer.prototype = new WorkerBee;

function SalesPerson (name, projs, quota) {...}
SalesPerson.prototype = new WorkerBee;

var mac = new Engineer("Wood, Mac");

Weiter angenommen, die Funktionen der übrigen Objekte, deren Definitionen hier nicht gezeigt wurden, würden die base-Eigenschaft besitzen und den Konstruktor des darüberliegenden Objekts in der Prototypekette aufrufen. Dann bekäme mac.id bei der Erstellung von mac den Wert 5.

Ob die zusätzliche Inkrementierung erforderlich ist oder nicht, hängt von der Applikation ab. Damit die Zählervariable einen exakten Wert bekommt, könnte man die Definition wie folgt ändern:

function Employee (name, dept) {
   this.name = name || "";
   this.dept = dept || "general";
   if (name)
      this.id = idCounter++;
}

Bei der Erstellung einer Employee-Instanz, die als Prototype dient, übergibt man einfach keine Argumente an den Konstruktor. Beim Aufruf des Konstruktors wird dann id kein Wert zugewiesen und die Zählervariable nicht erhöht. Damit eine Employee-Instanz eine ID zugewiesen bekommt, muss name als Argument übergeben werden. Bei diesem Beispiel hätte dann mac.id den Wert 1.

Keine mehrfache Vererbung

Manche objektorientierte Programmiersprachen erlauben mehrfache Vererbung. Das bedeutet, dass Objekte die Eigenschaften und Werte von nicht verwandten Elternobjekten erben können. JavaScript unterstützt diese mehrfache Vererbung nicht.

Die Vererbung von Eigenschaftswerten erfolgt zur Laufzeit, indem JavaScript die Prototypekette nach dem Wert absucht. Da ein Objekt nur mit einem einzelnen Prototypen verknüpft werden kann, ist es nicht möglich, dass ein Objekt dynamisch von mehreren Prototypeketten erbt.

Bei JavaScript kann eine Konstruktorfunktion mehrere andere Konstruktorfunktionen aufrufen. Damit lässt sich etwas ähnliches wie mehrfache Vererbung nachbilden. Dies ließe sich z. B. wie in folgendem Beispiel umsetzen:

function Hobbyist (hobby) {
   this.hobby = hobby || "scuba";
}

function Engineer (name, projs, mach, hobby) {
   this.base1 = WorkerBee;
   this.base1(name, "engineering", projs);
   this.base2 = Hobbyist;
   this.base2(hobby);
   this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;

var dennis = new Engineer("Doe, Dennis", ["collabra"], "hugo")

Wird diese Definition so benutzt, wie im vorherigen Abschnitt gezeigt, dann besitzt das Objekt dennis diese Eigenschaften:

dennis.name == "Doe, Dennis"
dennis.dept == "engineering"
dennis.projects == ["collabra"]
dennis.machine == "hugo"
dennis.hobby == "scuba"

Die Eigenschaft hobby des dennis-Objekts bekommt also nicht den Wert des Hobbyist-Konstruktors zugewiesen. Und angenommen man fügt dem Prototype des Hobbyist-Konstruktors die Eigenschaft hinzu:

Hobbyist.prototype.equipment = ["mask", "fins", "regulator", "bcd"]

Dann erbt das Objekt dennis diese neue Eigenschaft nicht.


Vererbung überdacht

Unter Vererbung und der Prototype des Konstruktors findet man eine Beschreibung zur Vererbung bei JavaScript und dem Prototype des Konstruktors.

Vererbung war von Anfang bei JavaScript vorhanden. Die Beispiele auf dieser Seite setzen jedoch einige Methoden ein, die erst mit ECMAScript 5 eingeführt wurden. Auf den entsprechenden Referenzseiten für die Methoden kann man herausfinden, ob diese sich für ältere Versionen emulieren lassen.

Beispiel

B soll von A erben:

function A(a){
  this.varA = a;
}

A.prototype = {
  varA : null,
  doSomething : function(){
    // ...
  }
}

function B(a, b){
  A.call(this, a);
  this.varB = b;
}
B.prototype = Object.create(A.prototype, {
  varB : {
    value: null,
    enumerable: true,
    configurable: true,
    writable: true
  },
  doSomething : {
    value: function(){ // override
      A.prototype.doSomething.apply(this, arguments); // call super
      // ...
    },
    enumerable: true,
    configurable: true,
    writable: true
  }
});

var b = new B();
b.doSomething();

Die wichtigen Teile sind:

  • Typen sind in .prototype definiert
  • Die Methode Object.create()wird zum Vererben benutzt

prototype und Object.getPrototypeOf

JavaScript ist ein wenig verwirrend für Entwickler, die von Java oder C++ kommen, da bei JavaScript alles dynamisch und laufzeitbasiert ist und überhaupt keine Klassen existieren. Alles sind nur Instanzen (Objekte). Sogar die "Klassen", die wir simulieren, sind nur function-Objekte.

Vielleicht haben Sie schon bemerkt, dass die Funktion A eine spezielle Eigenschaft prototype besitzt. Diese spezielle Eigenschaft arbeitet mit dem Operator new von JavaScript. Die Referenz auf das Prototype-Objekt wird für die interne Eigenschaft [[Prototype]] der neuen Instanz kopiert. Bei der Anweisung var a1 = new A() setzt JavaScript (nachdem das Objekt im Speicher erstellt und bevor die Funktion A() aufgerufen und this zugewiesen wurde) z. B. a1.[[Prototype]] = A.prototype. Beim Zugriff auf Eigenschafen der Instanz überprüft JavaScript dann zuerst, ob diese Eigenschaft direkt existiert und wenn nicht wird in [[Prototype]] gesucht. Das bedeutet, dass alles, was für prototype definiert wird, tatsächlich von allen Instanzen geteilt wird. Später kann man Teile von prototype verändern und diese Änderungen werden daraufhin, wenn man so will, für alle Instanzen übernommen.

Bei der Anweisung var a1 = new A(); var a2 = new A(); aus dem Beispiel oben, verweist a1.doSomething auf Object.getPrototypeOf(a1).doSomething, was dasselbe ist wie A.prototype.doSomething, das definiert wurde, z. B. Object.getPrototypeOf(a1).doSomething == Object.getPrototypeOf(a2).doSomething == A.prototype.doSomething.

Kurz: prototype ist für Typen und Object.getPrototypeOf() dasselbe für Instanzen.

[[Prototype]] wird rekursiv durchlaufen, z. B. a1.doSomething, Object.getPrototypeOf(a1).doSomething, Object.getPrototypeOf(Object.getPrototypeOf(a1)).doSomething usw. bis etwas gefunden wurde. Falls nichts gefunden wurde, gibt Object.getPrototypeOf den Wert null zurück.

Beim Aufruf:

var o = new Foo();

macht JavaScript also folgendes:

var o = new Object();
o.[[Prototype]] = Foo.prototype;
o.Foo();

(oder ähnliches) und wird später diese Anweisung ausgeführt:

o.someProp;

Dann wird erst überprüft, ob o die Eigenschaft someProp besitzt. Wenn nicht, wird Object.getPrototypeOf(o).someProp überprüft und wenn die Eigenschaft auch dann nicht gefunden wurde, wird Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp überprüft. Dies wird solange fortgesetzt, bis die Eigenschaft entweder gefunden oder festgestellt wurde, dass sie nicht existert.


Iteratoren und Generatoren

Introduced in JavaScript 1.7

Es kommt bei der Programmierung immer wieder vor, dass man Elemente einer Kollektion verarbeiten möchte. Bei JavaScirpt gibt es für das Iterieren über Elemente einer solchen Ansammlung verschiedene Möglichkeiten. Angefangen von einfachen for- und for each-Schleifen über map(), filter() bis zu Array-Comprehensions. Iteratoren und Generatoren, die mit JavaScript 1.7 eingeführt wurden, verankern das Prinzip der Iteration direkt im Kern der Sprache und verfügen über Mechanismen für die Beeinflussung des Verhaltens von for...in und for each-Schleifen.

Iteratoren

Ein Iterator ist ein Objekt, das darüber bescheid weiß, wie der Zugriff auf die Elemente nacheinander stattfinden muss, während es sich die aktuelle Position innerhalb der Sequenz merkt. Bei JavaScript ist ein Iterator ein Objekt, das eine Methode next() zur Verfügung stellt, welche das nächste Element der Sequenz zurückliefert. Diese Methode kann optional eine StopIteration-Exception auslösen, wenn das Ende der Sequenz erreicht ist.

Einmal erstellt kann ein Iterator-Objekt zum einen explizit angesprochen werden, indem die Methode next() wiederholt aufgerufen wird. Zum anderen kann der Zugriff auch implizit stattfinden, indem die Schleifenkonstrukte for...in und for each von JavaScript eingesetzt werden.

Einfache Iteratoren für Objekte und Arrays können mit der Funktion Iterator() erstellt werden:

var lang = { name: 'JavaScript', birthYear: 1995 };
var it = Iterator(lang);

Nachdem der Iterator einmal initalisiert wurde, kann die Methode next() aufgerufen werden, um Schlüssel-Wert-Paare von dem Objekt abzurufen:

var pair = it.next(); // Das Paar ist: ["name", "JavaScript"]
pair = it.next(); // Das Paar ist: ["birthYear", 1995]
pair = it.next(); // Eine StopIteration-Exception wird ausgeworfen

Anstatt der direkten Benutzung der next()-Methode kann auch eine for...in-Schleife zum Einsatz kommen. Die Schleife bricht automatisch ab, wenn die StopIteration-Exception ausgelöst wird.

var it = Iterator(lang);
for (var pair in it)
  print(pair); // gibt [key, value] Paare aus

Möchte man nur über die Schlüssel des Objekts iterieren, übergibt man ein zweites Argument true an die Iterator()-Funktion:

var it = Iterator(lang, true);
for (var key in it)
  print(key); // gibt jeden Schlüssel aus

Ein Vorteil bei der Benutzung der Iterator()-Funktion ist, dass die Inhalte von benutzerdefinierten Eigenschaften, die Object.prototype hinzugefügt wurden, in der Sequenz nicht enthalten sind.

Iterator() kann auch auf Arrays angewendet werden:

var langs = ['JavaScript', 'Python', 'C++'];
var it = Iterator(langs);
for (var pair in it)
  print(pair); // gibt jedes [index, language]-Paar aus

Genau wie bei Objekten kann man auch hier true als zweiten Parameter übergeben, um nur über die Schlüssel zu iteriert:

var langs = ['JavaScript', 'Python', 'C++'];
var it = Iterator(langs, true);
for (var i in it)
  print(i); // prints 0, then 1, then 2

Darüber hinaus ist es möglich, mit Hilfe des let-Schlüsselworts die Index-Positionen und Werte an blockbegrenzte Variablen innerhalb der for-Schleife zuzuweisen.

var langs = ['JavaScript', 'Python', 'C++'];
var it = Iterator(langs);
for (let [i, lang] in it)
 print(i + ': ' + lang); // gibt "0: JavaScript" aus, usw.

Iteratoren selbst definieren

Einige Ojekte repräsentieren Kollektionen von Elementen, über die auf spezielle Weise iteriert werden sollte.

  • Beim Iterieren über ein Bereichsobjekt sollten die Nummern in diesem Bereich eine nach der anderen ausgegeben werden.
  • Das Laub eines Baums kann mit Hilfe der Tiefensuche oder Breitensuche durchquert werden.
  • Beim Iterieren über ein Objekt, welches die Ergebnisse einer Datenbankabfrage enthält, sollten die Reihen eine nach der anderen zurückgegeben werden, auch wenn der ganze Satz nicht in ein einzelnes Array geladen wurde.
  • Ein Iterator für eine unenedliche mathematische Sequenz (z. B. die Fibonnacci-Sequenz) sollte in der Lage sein, ein Ergebnis nach dem anderen auszugeben, ohne dass eine Datenstruktur mit unendlicher Länge erstellt werden muss.

JavaScript ermöglicht das Schreiben von Code mit selbstdefinierter Iterationslogik und das verknüpfen dieser Logik mit einem Objekt.

Wir erstellen z. B. ein einfaches Range-Objekt, das einen unteren und oberen Wert zur Begrenzung entgegennimmt:

function Range(low, high){
  this.low = low;
  this.high = high;
}

Anschließend erstellen wir einen selbstdefinierten Iterator, der eine Sequenz von Ganzzahlen ausgeben kann, die zwischen den Begrenzungswerten liegen. Dem Iterator-Interface muss eine next()-Methode hinzugefügt werden, die entweder ein Element der Sequenz zurückgibt oder eine StopIteration Ausnahme auswirft.

function RangeIterator(range){
  this.range = range;
  this.current = this.range.low;
}
RangeIterator.prototype.next = function(){
  if (this.current > this.range.high)
    throw StopIteration;
  else
    return this.current++;
};

Unser RangeIterator wird mit einer Range-Instanz instanziert und besitzt eine eigene Eigenschaft current, um sich die aktuelle Position in der Sequenz zu merken.

Abschließend muss Range noch eine spezielle Methode __iterator__ hinzugefügt werden, um den RangeIterator mit dem Range-Objekt zu verknüpfen. Diese wird beim Versuch über eine Range-Instanz zu iterieren aufgerufen und sollte eine Instanz von RangeIterator zurückgeben, womit die Iterator-Logik implementiert wurde.

Range.prototype.__iterator__ = function(){
  return new RangeIterator(this);
};

Nachdem wir unseren selbstdefinierten Iterator angekoppelt haben, können wir wie folgt über eine range-Instanz interieren:

var range = new Range(3, 5);
for (var i in range)
  print(i); // prints 3, then 4, then 5 in sequence

Generatoren: ein besserer Weg zur Erstellung von Iteratoren

Während selbstdefiniert Iteratoren ein nützliches Werkzeug sind, muss bei ihrer Erstellung sehr auf sorgfältiges Programmieren geachtet werden, da der interne Zustand explizit instandgehalten werden muss. Generatoren stellen eine beachtenswerte Alternative dar: Sie erlauben die Definition eines iterativen Algorithmus über eine einzelne Funktion, die ihren Zustand behält.

Ein Generator ist eine spezielle Art von Funktion, die wie eine Fabrikanlage für Iteratoren arbeitet. Eine Funktion wird zu einem Generator, wenn sie eine oder mehrere yield-Ausdrücke enthält.

Hinweis: Das yield-Schlüsselwort ist bei Codeblöcke in HTML-Dokumenten nur verwendebar, wenn der Code innerhalb eines <script type="application/javascript;version=1.7"> Blocks (oder höhere Version) steht. XUL-Script-Tags haben ohne diese spezielle Angabe Zugriff auf dieses Feature.

Beim Aufruf einer Generatorfunktion wird der Code der Funktion nicht einfach ausgeführt, sondern ein Generator-Iterator-Objekt zurückgegeben. Bei jedem Aufruf der next()-Methode des Generator-Iterators wird dann der Funktionscode bis zum yield-Ausdruck ausgeführt und das Ergebnis zurückgegeben. Ist das Ende der Funktion oder eine return-Anweisung erreicht, wird eine StopIteration Ausnahme ausgeworfen.

Folgendes Beispiel veranschaulicht diesen Ablauf:

function simpleGenerator(){
  yield "first";
  yield "second";
  yield "third";
  for (var i = 0; i < 3; i++)
    yield i;
}

var g = simpleGenerator();
print(g.next()); // Ausgabe: "first"
print(g.next()); // Ausgabe: "second"
print(g.next()); // Ausgabe: "third"
print(g.next()); // Ausgabe: 0
print(g.next()); // Ausgabe: 1
print(g.next()); // Ausgabe: 2
print(g.next()); // StopIteration wird ausgeworfen

Eine Generatorfunktion kann direkt als __iterator__-Methode einer Klasse verwendet werden. Damit wird die Menge des Codes, der für die Erzeugung von selbstdefinierten Iteratoren nötig ist, stark reduziert.

Hier noch mal Range unter Einsatz eines Generators:

function Range(low, high){
  this.low = low;
  this.high = high;
}
Range.prototype.__iterator__ = function(){
  for (var i = this.low; i <= this.high; i++)
    yield i;
};
var range = new Range(3, 5);
for (var i in range)
  print(i); // gibt 3, dann 4, dann 5 in Reihe aus

Nicht alle Generatoren terminieren. Es kann auch ein Generator erstellt werden, der eine unendliche Folge repräsentiert. Mit folgendem Generator wird die Fibonacci-Folge implementiert: jedes Element ist die Summe der beiden vorherigen Elemente.

function fibonacci(){
  var fn1 = 1;
  var fn2 = 1;
  while (1){
    var current = fn2;
    fn2 = fn1;
    fn1 = fn1 + current;
    yield current;
  }
}

var sequence = fibonacci();
print(sequence.next()); // 1
print(sequence.next()); // 1
print(sequence.next()); // 2
print(sequence.next()); // 3
print(sequence.next()); // 5
print(sequence.next()); // 8
print(sequence.next()); // 13

Generatorfunktionen können Argumente entgegennehmen, welche beim ersten Aufruf der Funktion eingebunden werden. Mit einer return-Anweisung können Generatoren abgebrochen werden (wodurch eine StopIteration ausgelöst wird). Die folgende Abwandlung von fibonacci() nimmt eine optinale Begrenzungszahl als Argument entgegen und bricht ab, sobald diese Grenze erreicht ist.

function fibonacci(limit){
  var fn1 = 1;
  var fn2 = 1;
  while (1){
    var current = fn2;
    fn2 = fn1;
    fn1 = fn1 + current;
    if (limit && current > limit)
      return;
    yield current;
  }
}

Erweiterte Generatoren

Generatoren berechnen ihre Werte bei der Anforderung. Dies erlaubt die effiziente Erzeugung von Folgen, die aufwändig zu berechnen sind. Sogar unendliche Sequenzen können von Generatoren repräsentiert werden.

Zusätzlich zur Methode next() besitzen Generator-Iterator-Objekte eine send()-Methode, die zur Veränderung des internen Zustands benutzt werden kann. Ein Wert, der an send() übergeben wird, wird als Ergebnis des letzten Aufrufs des yield-Ausdrucks interpretiert. Zum Starten eines Generators muss die next()-Methode mindestens einmal aufgerufen werden, bevor über die send()-Methode ein bestimmter Wert gesetzt werden kann.

Hier noch mal der Fibonacci-Generator unter Einsatz der send()-Methode zum Zurücksetzen der Sequenz:

function fibonacci(){
  var fn1 = 1;
  var fn2 = 1;
  while (1){
    var current = fn2;
    fn2 = fn1;
    fn1 = fn1 + current;
    var reset = yield current;
    if (reset){
        fn1 = 1;
        fn2 = 1;
    }
  }
}

var sequence = fibonacci();
print(sequence.next());     // 1
print(sequence.next());     // 1
print(sequence.next());     // 2
print(sequence.next());     // 3
print(sequence.next());     // 5
print(sequence.next());     // 8
print(sequence.next());     // 13
print(sequence.send(true)); // 1
print(sequence.next());     // 1
print(sequence.next());     // 2
print(sequence.next());     // 3
Achtung: Es ist interessant zu wissen, dass der Aufruf von send(undefined) äquivalent zum Aufruf von next() ist. Allerdings führt der Aufruf von send() zu Beginn nach Erstellung eines neu generierten Objekts mit einem anderen Wert als undefined zu einem TypeError Ausnahmefehler.

Man kann einen Generator mit dem Aufruf seiner throw()-Methode dazu zwingen, eine Ausnahme auszuwerfen. Dabei übergibt man den Wert, der ausgeworfen soll an die Methode. Diese Ausnahme wird im Kontext der aktuellen Unterbrechung des Generators ausgeworfen, als ob das yield, das an dieser Stelle unterbrochen wurde, eine throw Wert Anweisung gewesen wäre.

Wird ein yield bei der Ausführung der ausgeworfenen Ausnahme nicht erreicht, dann verbreitet sich die Ausnahme durch den Aufruf bis zu throw() nach oben und nachfolgende Aufrufe von next() resultieren in einem Auswurf einer StopIteration Ausnahme.

Generatoren besitzen außerdem eine close()-Methode, welche den Generator dazu zwngt, sich selbst zu schließen. Dies hat folgende Effekte:

  1. Alle finally-Abschnitte, die in der Generatorfunktion aktiv sind, werden ausgeführt.
  2. Wenn in einem finally-Abschnitt eine andere Ausnahme als StopIteration ausgeworfen wird, wird diese an den Aufrufer der close()-Methode übertragen.
  3. Der Generator terminiert.

Generator-Ausdrücke

Eingeführt in JavaScript 1.8

Ein großer Nachteil bei Array-Comprehensions ist, dass ein neues Array im Speicher erzeugt werden muss. Wenn die Eingabe der Comprehension nur ein kleines Array ist, spielt der hierdurch zusätzlich erzeugte Overhead keine Rolle. Ist die Eingabe allerdings ein großes Array oder ein aufwendiger (oder auch unendlicher) Generator, kann dieser Umstand zum Problem werden.

Generatoren ermöglichen die Erzeugung von Sequenzen on-demand; Elemente werden bei der Anforderung, dann wenn sie gebraucht werden, erstellt. Generator-Ausdrücke sind den Array-Comprehensions syntaktisch ähnlich, jedoch werden runde statt eckigen Klammern verwendet (und for...in anstatt for each...in). Generator-Ausdrücke erzeugen keine Arrays sondern Generatoren, welche die „träge“ Anfertigung von Sequenzen ermöglichen. Man kann sie sich als Kurzschreibweise für die Erzeugung von Generatoren vorstellen.

Angenommen man hat einen Iterator it, der über eine große Anzahl von Ganzzahlen iteriert und man will einen neuen Iterator erstellen, der über die doppelten Werte iteriert. Eine Array-Comprehension würde ein ganzes Array mit den verdoppelten Werten im Speicher erzeugen:

var doubles = [i * 2 for (i in it)];

Ein Generator-Ausdruck würde hingegen einen neuen Iterator erstellen, womit die doppelten Werte erst dann wenn Sie tatsächlich benötigt werden, erzeugt würden:

var it2 = (i * 2 for (i in it));
print(it2.next()); // The first value from it, doubled
print(it2.next()); // The second value from it, doubled

Wenn ein Generator-Ausdruck als Argument für eine Funktion eingesetzt wird, gelten die Klammern des Funktionsaufrufs auch für den Generator-Ausdruck und auf die äußeren Klammern kann verzichtet werden:

var result = doSomething(i * 2 for (i in it));


Closures (Funktionsabschlüsse)

Closures werden bei JavaScript gerne als ein fortgeschrittenes Feature angesehen. Sie zu verstehen ist jedoch wichtig, um die Sprache zu meistern.

Im einem Satz: Variablen, die innerhalb eines Sichtbarkeitsbereichs einer äußeren Funktion liegen, können von einer inneren Funktionsdefinition aus angesprochen werden.

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();

Die init()-Funktion erstellt eine lokale Variable name und definiert dann eine Funktion displayName(). Diese ist eine innere Funktion - sie ist im Sichtbarkeitsbereich von init() definiert und nur innerhalb dieses Bereichs verfügbar. Die Funktion displayName() besitzt selbst keine lokalen Variablen, greift jedoch auf die name-Variable der äußeren Funktion zu.

Dies funktioniert problemlos - probieren Sie den Code doch einfach selbst aus, um zu sehen was passiert! Es ist ein Beispiel für functional scoping: Bei JavaScript wird der Sichtbarkeitsbereich einer Variablen durch die Position im Quellcode bestimmt und eingebettete Funktionen haben Zugriff auf Variablen des äußeren Sichtbarkeitsbereichs.

Nun ein weiteres Beispiel:

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

Wenn dieser Code ausgeführt wird, passiert so ziemlich das Gleiche wie beim vorherigen init()-Beispiel: Der String "Mozilla" wird als Alarmmeldung ausgegeben. Der Unterschied der beiden Beispiele besteht darin, dass die innere displayName()-Funktion über return zurückgegeben wird, bevor diese ausgeführt wird.

Es mag etwas merkwürdig erscheinen, dass dieser Code tatsächlich funktioniert. Normalerweise existiert eine lokale Variable innerhalb einer Funktion nur solange die Funktion ausgeführt wird. Man könnte also davon ausgehen, dass nachdem makeFunc() ausgeführt wurde, die name-Variable nicht länger bestehen bleibt. Da der Code aber wie erwartet funktioniert, ist dies offensichtlich nicht der Fall.

Des Rätsels Lösung: Die Funktion myFunc wurde zu einer Closure - einer speziellen Art von Objekt, das zwei Dinge kombiniert: eine Funktion und die Umgebung, in der die Funktion erstellt wurde. Die Umgebung schließt alle lokalen Variablen ein, die zur Zeit der Closure-Erstellung im selben Sichtbarkeitsbereich lagen. Bei unserem Beispiel ist myFunc also eine Closure, die beides einbezieht - die displayName-Funktion und den String "Mozilla", welcher bei Erstellung der Closure in der Umgebung existierte.

Hier ein noch etwas interessanteres Beispiel - eine makeAdder-Funktion:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

print(add5(2));  // 7
print(add10(2)); // 12

Bei diesem Beispiel wurde eine Funktion makeAdder(x) definiert, die ein einziges Argument x entgegennimmt und eine neue Funktion zurückgibt. Diese zurückgegebene Funktion nimmt wiederum ein Argument y entgegen und gibt die Summe von x und y zurück.

Alles in allem ist die makeAdder-Funktion eine factory - sie „produziert“ Funktionen, die einen bestimmten Wert mit dem Argument addieren. Bei dem Beispiel oben benutzen wir unsere "Funktionsfabrik", um zwei neue Funktionen zu erstellen - eine, die 5 zum Argument addiert und eine weitere, die 10 addiert.

add5 und add10 sind beide Closures - sie haben die gleiche Funktionsdefinition, aber beziehen ihre Werte aus unterschiedlichen Umgebungen. In der Umgebung von add5 entspricht x dem Wert 5; in der Umgebung von add10 hingegen dem Wert 10.

Praktische Closures

Theorie beiseite und ein mal nachgefragt: Sind denn Closures tatsächlich auch nützlich? Finden wir es heraus, indem wir Überlegungen zum praktischen Nutzen von Closures anstellen. Eine Closure erlaubt die Verknüpfung von Daten (aus der Umgebung) mit einer Funktion, die mit diesen Daten arbeitet. Dies hat Ähnlichkeit mit objektorientierter Programmierung, wo Daten über Objekte (die Eigenschaften des Objekts) mit Methoden verknüpft werden.

Konsequenterweise kann man eine Closure überall dort einsetzen, wo man normalerweise ein Objekt, welches nur eine einzige Methode besitzt, verwenden würde.

Solche Situationen kommen bei der Web-Programmierung immer wieder vor, denn ein Großteil des Codes ist Event-basiert - es wird ein bestimmter Vorgang definiert und dann an ein Event gebunden, das vom Benutzer ausgelöst wird (wie z. B. ein Klick oder Tastendruck). Der Code wird in der Regel als Callback angebunden: eine einzelne Funktion, die als Reaktion auf das Event ausgeführt wird.

Im Folgenden ein praktisches Beispiel: Angenommen man möchte einer Seite ein paar Buttons hinzufügen, womit die Schriftgröße eingestellt werden kann. Eine Möglichkeit dazu wäre, für die Schriftgröße des body-Elements einen Pixelwert festzulegen und für andere Elemente auf der Seite (wie z. B. Header) einen relativen em-Wert zu spezifizieren:

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}
h2 {
  font-size: 1.2em;
}

Mit den interaktiven Buttons kann der Benutzer dann die Schriftgröße für das body-Element einstellen und die Schriftgröße der anderen Elemente passt sich augrund der relativen Werte entsprechend an.

Nun der JavaScript-Code:

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12, size14 und size16 sind Funktionen, welche die Schriftgröße des body auf 12, 14 oder 16 Pixel setzen. Die Funktionen können mit Buttons (in diesem Fall Links) wie folgt verknüpft werden:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>

Sicht auf jsFiddle

Emulieren von privaten Methoden mit Closures

Sprachen wie Java ermöglichen die Deklaration von privaten Methoden, was bedeutet, dass sie nur von anderen Methoden in derselben Klasse aufgerufen werden können.

JavaScript verfügt nativ über kein solches Feature, jedoch ist es möglich, private Methoden mit Closures nachzubilden. Private Methoden sind nicht nur nützlich, um den Zugriff einzuschränken, sodern ermöglichen auch die komfortable Verwaltung des globalen Namensraums, wodurch verhindert wird, dass nicht-essenzielle Methoden das öffentliche Interface zum Code durcheinanderbringen.

Beim nächsten Beispiel definieren wir einige öffentliche Funktionen, die über Closures auf private Funktionen und Variablen zugreifen können. Dieses Konstrukt ist auch als module pattern (Modul-Entwurfsmuster) bekannt.

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
})();

alert(Counter.value()); /* Alert-Meldung: 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* Alert-Meldung: 2 */
Counter.decrement();
alert(Counter.value()); /* Alert-Meldung: 1 */

Hier passiert eine ganze Menge. Bei den früheren Beispielen hatte jede Closure ihre eigene seperate Umgebung; bei diesem Beispiel befinden sich die drei Funktionen Counter.increment, Counter.decrement und Counter.value in ein und derselben Umgebung.

Die gemeinsame Umgebung wird im Funktionskörper einer anonymen Funktion erzeugt, welche sofort nach ihrer Definition aufgerufen wird. Die Umgebung enthält zwei private Elemente: eine Variable privateCounter und eine Funktion changeBy. Auf keines dieser beiden Elemente kann von außerhalb der anonymen Funktion zugegriffen werden. Stattdessen müssen zur Veränderung bzw. dem Abruf des Wertes von privateCounter die drei Funktionen benutzt werden, die von der anonymen Funktion zurückgegeben werden.

Diese drei öffentlich zugänglichen Funktionen sind Closures, die sich ihre Umgebung teilen. Dank dem lexikalischen Scoping von JavaScript kann jede dieser Methoden auf die Variable privateCounter und die changeBy-Funktion zugreifen.

Wir haben also eine anonyme Funktion definiert, die einen Zähler erstellt. Diese wird sofort aufgerufen und das Ergebnis der Counter-Variablen zugewiesen. Das Ergebnis könnte man auch in einer extra Variablen speichern und diese für die Erstellung mehrerer Zähler benutzen:

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* Alerts 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* Alerts 2 */
Counter1.decrement();
alert(Counter1.value()); /* Alerts 1 */
alert(Counter2.value()); /* Alerts 0 */

Die beiden Zähler arbeiten komplett unabhängig voneinander. Die Umgebung der Zähler ist während der Aufrufe der makeCounter()-Funktion jedesmal unterschiedlich und die Closure-Variable privateCounter hält jedesmal eine andere Instanz.

Der Einsatz von Closures auf diese Weise hat viele Vorteile, die normalerweise der objektorientierten Programmierung zugeschrieben werden; insbesondere das "Verstecken" von Daten (data hiding) und die Datenkapselung.

Erzeugung von Closures in Schleifen: Ein häufiger Fehler

Vor der Einführung des let-Schlüsselworts mit JavaScript 1.7 gab es häufig Probleme mit Closures, wenn sie innerhalb einer Schleife erzeugt wurden. Hier ein Beispiel:

<p id="help">Hilfreiche Notizen sollen hier stehen</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Ihre E-Mail-Adresse'},
      {'id': 'name', 'help': 'Vor- und Nachname.'},
      {'id': 'age', 'help': 'Ihr Alter (mindestens 16 Jahre)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}

setupHelp();

Sicht auf jsFiddle

Im Array helpText werden drei Hinweistexte definiert und jeweils über eine ID mit einem Eingabefeld im Dokument verknüpft. Die Schleifen durchläuft diese drei Elemente des Arrays und verbindet jedes mit einem onfocus-Event für das Anzeigen des entsprechenden Hilfetextes.

Probiert man diesen Code aus, wird man feststellen, dass er nicht funktioniert - egal welches Element fokussiert wird, immer erscheint der Text für das Alter.

Der Grund dafür ist, dass die Funktionen, welche onfocus zugewiesen wurden, Closures sind. Sie bestehen aus der Funktionsdefinition und der integrierten Umgebung innerhalb des Sichtbarkeitsbreichs der setupHelp-Funktion. Es wurden zwar drei Closures erstellt, doch alle teilen sich dieselbe Umgebung. Zur Zeit der Ausfühurng der onfucus-Callbacks ist die Schleife bereits durchgelaufen und die item-Variable (die sich alle Cloures teilen) hat den Wert des letzten helpText-Elements.

Eine Lösung für dieses Problem könnte die Benutzung von mehr Closures sein; also eine factory, wie bereits oben beschrieben:

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}

function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}

setupHelp();

Sicht auf jsFiddle

Nun funktioniert der Code wie gewünscht - anstatt dass sich die Callbacks alle eine Umgebung teilen, erzeugt die makeHelpCallback-Funktion jeweils eine seperate Umgebung.

Überlegungen zur Performance (Effizienz)

Es ist nicht empfehlenswert, Funktionen ineinander zu verschachteln, wenn Closures zur Erledigung einer bestimmten Aufgabe nicht unbedingt nötig sind, da dies die Performance sowohl hinsichtlich der Ausführungsgeschwindigkeit als auch des Speicherverbrauchs negativ beeinflusst.

Erstellt man beispielsweise ein neues Objekt, sollten Methoden normalerweise mit dem Prototype des Objekts verknüpft und nicht im Konstruktor definiert werden, da sonst die Methoden bei jedem Aufruf des Konstruktors (jeder Erstellung eines Objekts) erneut zugewiesen werden.

Hier ein nicht sehr praxistaugliches aber anschauliches Beispiel:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

Dieser Code zieht keinen Nutzen aus den Vorteilen von Closures und sollte daher wie folgt abgeändert werden:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

Alternativ:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

Bei den beiden letzteren Beispielen teilen sich alle Objekte den Prototype und die Methoden müssen nicht bei jeder Objekterstellung erzeugt werden. Im Kapitel Feinheiten des Objektmodells findet man dazu nähere Informationen.