JavaScript
JavaScript ist eine objektorientierte Skriptsprache, die von der Firma Netscape entwickelt wurde, um statische HTML-Seiten dynamisch zu gestalten. Im Gegensatz zu serverseitigen Skriptsprachen wie zum Beispiel Perl oder PHP wird JavaScript auf dem Client ausgeführt. (Netscape nutzte JavaScript mit einer im Kern identischen Syntax allerdings auch serverseitig.) Mittels einer Schnittstelle zum Document Object Model (DOM) können Elemente der Webseite manipuliert werden, nachdem diese zum Client übertragen wurden.
Die Grundlage von JavaScript wurde unter Einbeziehung von Microsofts JScript unter dem Namen ECMAScript als ECMA-262 standardisiert. JavaScript in der Version 1.5 entspricht ECMA-262 Edition 3. Die Syntax der Sprache ähnelt der der Programmiersprache Java und auch einige Schlüsselwörter beider Sprachen sind identisch. Semantisch jedoch unterscheiden sich beide Sprachen deutlich. Interessant ist die Tatsache, dass JavaScript im Gegensatz zu klassisch objektorientierten Sprachen keine Klassen einsetzt, sondern statt dessen Objekte als Prototypen verwendet.
JavaScript vereinheitlicht die aus anderen Sprachen bekannten Funktionen und Objekte zu einem einheitlichen Konzept. Die Sprache verfügt über integrierte Reguläre Ausdrücke. Mit E4X (ECMA-357) wird auch XML als nativer Datentyp unterstützt.
Trotz des ähnlichen Namens ist JavaScript nicht mit der Programmiersprache Java zu verwechseln. Die Namensgleichheit entstand – ähnlich wie beim Java Enterprise System – aus Marketinggründen.
Datenstrukturen und Objekte
Vordefinierte Objekte
JavaScript kennt mehrere eingebaute Objekte, die von ECMAScript definiert werden.
-
Object
als allgemeiner Prototyp, von dem alle Objekte abgeleitet sind -
Function
als Prototyp für Funktionen -
Array
als Prototyp für Arrays -
String
als Prototyp für Zeichenketten -
Boolean
als Prototyp für Boolesche Variablen -
Number
als Prototyp für Zahlen (64-Bit Gleitkommazahlen gemäß IEEE 754) -
Math
stellt Konstanten und Methoden für mathematische Operationen bereit.Math
kann nicht als Konstruktor dienen. -
Date
für Operationen mit Daten bzw. Zeitpunkten und Datumsformaten -
RegExp
für reguläre Ausdrücke
Die restlichen Objekte, die beim clientseitigen
JavaScript verwendet werden, entstanden historisch
vor allem durch die
Netscape-Spezifikationen (window
,
document
usw.). Zahlreiche Unterobjekte
von document
wurden mittlerweile durch
DOM HTML standardisiert (title
,
images
, links
, forms
usw.). Aktuelle Browser unterstützen zudem DOM Core
und andere W3C-DOM-Standards sowie Erweiterungen von
Microsoft
JScript.
Die vordefinierten Prototypen korrespondieren
teilweise mit grundlegenden
Datentypen. Zeichenketten haben den Typ
String
, numerische Werte den Typ Number
und boolsche Werte den Typ Boolean
. Des
Weiteren gibt es die Typen Null
und
Undefined
. Dies sind sogenannte
einfache Werte (primitive values). Alle
anderen Werte sind Objekte (objects)
und haben den Typ Object
.
Funktionen
,
Arrays
, Datumsobjekte und reguläre Ausdrücke sind so
gesehen spezielle Objekte.
Beim direkten Notieren von Zeichenketten, Zahlen
oder booleschen Variablen entstehen einfache Werte
(z.B. bei var variable = "Beispieltext";
).
Beim Nutzen der jeweiligen Konstruktorfunktionen
hingegen entstehen Objekte (z.B. bei var
variable = new String("Beispieltext");
).
Zugriff auf Objekteigenschaften und -methoden
Eigenschaften und Methoden von Objekten werden über folgende Notation angesprochen:
objekt.eigenschaft
bzw.
objekt.methode()
Allen Objekten können zur
Laufzeit neue Eigenschaften hinzugefügt. In
diesem Beispiel wird eigenschaftA
mit 1
belegt:
objekt.eigenschaftA = 1;
Eigenschaften können äquivalent über die folgende Notation angesprochen werden, bei der der Eigenschaftsname als Zeichenkette notiert wird:
objekt["eigenschaftA"] = 1;
Durch diese Schreibweise ist es möglich, alle Eigenschaften eines Objektes mit einer For ... in-Schleife zu durchlaufen. In diesem Beispiel wird „objekt.eigenschaftA = 1“ ausgegeben.
for (var eigenschaftsname in objekt) { window.alert("objekt." + eigenschaftsname + " = " + objekt[eigenschaftsname]); }
Zum Entfernen von Eigenschaften wird der
delete
-Operator verwendet:
delete objekt.eigenschaftA;
Die wichtigsten Eigenschaften und Methoden der Prototypen
|
Neues Objekt erstellen |
|
Neues Objekt erstellen (Alternative) |
|
Neues Array anlegen |
|
Neues Array anlegen (Alternative) |
|
Anzahl der Elemente zurückgeben |
|
Zwei Arrays verbinden |
|
Array in Zeichenkette umwandeln |
|
Letztes Element aus Array löschen |
|
Ein neues Element an das Ende des Array anhängen |
|
Reihenfolge der Elemente umkehren |
Ein String kann in der Kurzschreibweise
"Zeichenkette"
oder 'Zeichenkette'
notiert werden. Alternativ kann der String
-Konstruktor
verwendet werden: new String(10)
gibt
etwa die Zahl 10 als String wieder.
|
Neuen String anlegen |
|
Neuen String anlegen (Alternative) |
|
Gibt die Länge des Strings wieder |
|
Gibt den String komplett kleingeschrieben wieder |
|
Gibt den String komplett großgeschrieben wieder |
|
Wandelt den String durch ein Trennzeichens in einen Array mit Teilstrings um |
|
Gibt die Position des ersten Vorkommens des angegebenen Strings wieder. Wird dieser darin gefunden, so gibt die Methode -1 zurück |
Während die Kurzschreibweise "Hallo"
ein einfacher Wert des Typs String
angelegt wird, entsteht bei new
String("Hallo")
ein Objekt des Typs
Object
. Dies ist in den meisten Fällen
unwesentlich. Im Falle der objektunabhängigen
Funktion eval()
, die einen String-Wert
entgegennimmt und diesen als JavaScript-Ausdruck
interpretiert, zeigt sich jedoch der feine
Unterschied:
|
ergibt |
|
ergibt "25+25" (Typ |
|
Ein neues Date-Objekt erzeugen |
|
Die größere der beiden Zahlen zurückgeben |
|
Die kleinere der beiden Zahlen zurückgeben |
|
Ergebnis der Exponentialrechnung zurückgeben |
|
Eine Zufallszahl zurückgeben, die gleich oder größer 0 und kleiner als 1 ist |
|
Rundet die angegebene Zahl |
Kontrollstrukturen
If ... else (Bedingte Anweisung)
if (Bedingung) { Anweisungen; } else { Anweisungen; }
In JavaScript gibt es im Gegensatz zu anderen
Programmiersprachen keine Kontrollstruktur if
... else if ...
. An dessen Stelle kann man
zwei if
-Anweisungen verwenden, von
denen die erste die zweite in ihrem else
-Teil
enthält:
if (Bedingung) { Anweisungen; } else if (Bedingung) { Anweisungen; } else { Anweisungen; }
Es können jedoch beliebig viele else if
-Blöcken
verwendet werden:
if (Bedingung) { Anweisungen; } else if (Bedingung) { Anweisungen; } else if (Bedingung) { Anweisungen; } else { Anweisungen; }
switch (Ausdruck) { case marke1 : Anweisungen; break; case marke2 : Anweisungen; break; default : Anweisungen; }
while (Bedingung) { Anweisungen; }
do { Anweisungen; } while (Bedingung);
for ([Startausdruck]; [Bedingung]; [Inkrementier-Ausdruck]) { Anweisungen; }
Mit dieser Anweisung werden alle Eigenschaften eines Objektes durchlaufen (oder auch alle Elemente eines Feldes).
for (eigenschaftsname in objekt) { Anweisungen; }
Eine Funktion ist ein Block mit Anweisungen, dem ein Name zugewiesen wird. Eine Funktion hat eine Liste von Parametern, die auch leer sein kann, und kann einen Resultatwert zurückgeben.
function meineFunktion (Parameter1, Parameter2, Parameter3) { Anweisungen; return Ausdruck; }
Neben der obigen gängigen Notation kann alternativ eine sogenannte Function Expression verwendet werden:
var Addieren = function (zahl1, zahl2) { return zahl1 + zahl2; };
Da alle Funktionen vom Prototyp-Objekt
Function
abgeleitet sind, kann eine Funktion
zudem mithilfe des Function
-Konstruktors
notiert werden. Das Besondere an dieser Schreibweise
ist, dass sowohl Parameternamen als auch der Code
des Funktionskörpers als Strings notiert werden.
var Addieren = new Function("zahl1", "zahl2", "return zahl1 + zahl2;");
Beispiel: Der ursprüngliche Algorithmus von Euklid zur Ermittlung des größten gemeinsamen Teilers: Es ist eine geometrische Lösung; die kleinere Strecke wird jeweils von der größeren abgezogen.
function gcd (a, b) { while (a != b) { if (a > b) { a = a - b; } else { b = b - a; } } return a; }
Die Anzahl der Parameter beim Aufruf muss nicht
zwingend mit der Anzahl der Parameter in der
Funktionsdefinition übereinstimmen. Wenn beim Aufruf
weniger Parameter angegeben werden, dann wird für
die übrigen Parameter einfach der Wert undefined
eingesetzt. Weiter kann innerhalb der Funktion auch
über das arguments
-Array auf die
Parameter zugegriffen werden.
Funktionales Programmieren
Da Funktionen in JavaScript vollwertige Objekte sind, sind sie Funktionen höherer Ordnung und können auch als Parameter an andere Funktionen übergeben werden.
Beispiel für Verwendung einer Funktion höherer Ordnung:
Array.prototype.fold = function (functor) { var result = 0; for (var i = 0; i < this.length; i++) { result = functor(result, this[i]); } return result; } var array = new Array(21,33,17,12); var addition = function (a, b) { return a + b; }; var sum = array.fold(addition);
Ebenso ist es möglich curried functions zu schreiben:
function add (a, b) { if (arguments.length < 1) return add; if (arguments.length < 2) { return function (c) { return a + c; }; } else { return a + b; } }
Eigene Objekte definieren
Konstruktor-Funktionen
Eine JavaScript-Funktion kann dazu genutzt
werden, um ein mit new
erstelltes
Objekt zu initialisieren. In diesem Fall spricht man
von einem
Konstruktor oder einer Konstruktor-Funktion.
Innerhalb dieser Funktion kann das neue Objekt über
die this
-Variable angesprochen werden.
Darüber können dem Objekt Eigenschaften angehängt
werden.
function meinObjekt () { this.zahl = 1; }
Neue Instanz erstellen:
var objekt = new meinObjekt();
Zugriff auf die Eigenschaft zahl
, es
wird „1“ ausgegeben:
window.alert(objekt.zahl);
Im Beispiel wurde dem Objekt eine Eigenschaft vom
Typ Number
, d.h. eine Zahl angehängt.
Erlaubt sind Eigenschaften jedes möglichen Typs,
darunter auch Funktionen. Objektmethoden sind somit
nur spezielle Eigenschaften vom Typ function
.
Öffentliche und private Methoden und Eigenschaften
Beim Definieren eigener Prototypen können Eigenschaften und Methoden entweder als öffentlich oder als privat notiert werden. Diese Unterscheidung bezieht sich auf ihre Verfügbarkeit, das heißt aus welchem Kontext der Zugriff erlaubt ist. Private Eigenschaften können nur aus der Konstruktor-Funktion und aus objekteigenen Methoden heraus gelesen und geschrieben werden. Der Zugriff von außerhalb des Objekts ist nur bei öffentlichen Eigenschaften möglich. Dementsprechend können öffentliche Methoden – die letztlich nur besondere Eigenschaften sind – aus jedem Kontext heraus, in dem das Objekt verfügbar ist, ausgeführt werden. Private Methoden sind analog nur objektintern aufrufbar.
Methoden werden zudem anhand ihrer Zugriffsrechte
auf objekteigene Eigenschaften und Methoden
unterschieden. Private Methoden haben prinzipiell
Zugriff auf alle öffentlichen und privaten
Eigenschaften und Methoden. Dasselbe gilt für
sogenannte privilegierte öffentliche
Methoden. Nicht-privilegierte öffentliche
Methoden können nur auf öffentliche Eigenschaften
und Methoden zugreifen. Erstgenannte werden
innerhalb der Konstruktor-Funktion definiert,
letztgenannte werden von außen über die
prototype
-Eigenschaft der
Konstruktor-Funktion hinzugefügt (siehe den
folgenden Abschnitt).
Im folgenden Beispiel werden die verschiedenen Eigenschafts- und Methodentypen demonstriert.
function meinObjekt (parameter) { /* parameter ist eine private Eigenschaft */ /* Speichere eine Referenz auf das aktuelle Objekt in der privaten Eigenschaft self */ var self = this; /* private Eigenschaft */ var private_eigenschaft = "privat"; /* öffentliche Eigenschaft */ this.oeffentliche_eigenschaft = "öffentlich"; /* private Methode */ var private_methode = function () { window.alert(private_eigenschaft + " " + self.oeffentliche_eigenschaft); }; /* privilegierte öffentliche Methode */ this.privilegierte_methode = function () { window.alert(private_eigenschaft + " " + this.oeffentliche_eigenschaft); private_methode(); }; } /* nicht-privilegierte öffentliche Methode */ meinObjekt.prototype.oeffentliche_methode = function () { window.alert(typeof(private_eigenschaft) + " " + typeof(private_methode)); window.alert(this.oeffentliche_eigenschaft); };
Neue Instanz erstellen:
var objekt = new meinObjekt();
Auf private_eigenschaft
und
private_methode
kann von außen nicht
zugegriffen werden. Daher wird zweimal „undefined“
ausgegeben:
window.alert(objekt.private_eigenschaft + " " + objekt.private_methode);
Der Zugriff auf oeffentliche_eigenschaft
hingegen ist von außen möglich. Es wird „öffentlich“
ausgegeben:
window.alert(objekt.oeffentliche_eigenschaft);
Ebenso können privilegierte_methode
und oeffentliche_methode
von außen
aufgerufen werden:
objekt.privilegierte_methode(); objekt.oeffentliche_methode();
Diese Methoden demonstrieren die unterschiedlichen Zugriffsrechte.
privilegierte_methode
gibt die
Eigenschaften private_eigenschaft
und
oeffentliche_eigenschaft
aus. Im
Meldungsfenster wird „privat öffentlich“ ausgegeben.
Danach ruft sie private_methode()
auf,
die noch einmal dasselbe demonstriert.
oeffentliche_methode
hingegen hat
keinen Zugriff auf private_eigenschaft
und private_methode
. Das
Meldungsfenster zeigt zweimal „undefined“. Lediglich
der Zugriff auf oeffentliche_eigenschaft
gelingt.
Aufgrund eines Fehlers in der
ECMAScript-Spezifikation ist das Ansprechen von
öffentlichen Eigenschaften und Methoden in privaten
Methoden schwierig, weil this
nicht wie
in öffentlichen Methoden auf das aktuelle Objekt
verweist. Daher bedient man sich eines Tricks, indem
man im Konstruktor this
in einer
privaten Eigenschaft speichert, gängigerweise
self
. Darüber gelingt der Zugriff auf
öffentliche Eigenschaften und Methoden in privaten
Methoden.
Vererbung (prototype-Eigenschaft)
Jede Funktion, d.h. jedes vom Function
-Prototyp
abgeleitete Objekt verfügt über eine prototype
-Eigenschaft.
Diese übernimmt eine wichtige Aufgabe, wenn die
Funktion als Konstruktor benutzt wird, um neue
Objekte zu initialisieren. Die prototype
-Eigenschaft
definiert in diesem Falle gemeinsame Eigenschaften
aller Objekte, die mit dem Konstruktor erstellt
werden. Man spricht von Prototyp-basierter
Vererbung. Auf diese Weise ermöglicht JavaScript
mehrstufige Vererbung:
Konstruktor eines Prototyps erstellen:
function Kraftfahrzeug (Fabrikat) { this.Fabrikat = Fabrikat; this.Beispieleigenschaft = "Beispielwert"; }
Konstruktor des abgeleiteten Prototyps erstellen:
function PKW (Fabrikat) { this.constructor(Fabrikat); this.weitereEigenschaft = "Beispielwert"; } PKW.prototype = new Kraftfahrzeug();
Eine neue Instanz des allgemeinen Prototyps
Kraftfahrzeug
dient als Muster des
abgeleiteten Prototyps PKW
. Dadurch
werden die Eigenschaften des allgemeinen Prototyps
an den abgeleiteten Prototyp vererbt: Jedes
PKW-Objekt ist gleichzeitig ein
Kraftfahrzeug-Objekt. Über this.constructor
kann im PKW-Konstruktor der
Kraftfahrzeug-Konstruktor angesprochen werden. Dies
wird im Beispiel dazu genutzt, die Parameter an
letztgenannten weiterzugeben. So muss
this.Fabrikat = Fabrikat;
nur im
Kraftfahrzeug-Konstruktor notiert sein.
Instanz des Prototyps PKW
erstellen:
var Golf = new PKW("Volkswagen Golf"); var Ente = new PKW("Citroen 2CV");
Über die prototype
-Eigenschaft der
Konstruktorfunktion können einem Prototyp auch
nachträglich Eigenschaften und Methoden hinzugefügt
werden. Diese Änderungen wirken sich auf alle davon
abgeleiteten Objekte aus:
PKW.prototype.Radanzahl = 4; PKW.prototype.zeigeRadanzahl = function () { window.alert(this.Fabrikat + " hat " + this.Radanzahl + " Räder."); }; Golf.zeigeRadanzahl(); // Ausgabe: „Volkswagen Golf hat 4 Räder.“ Ente.zeigeRadanzahl(); // Ausgabe: „Citroen 2CV hat 4 Räder.“
Eine nützliche Methode des Object
-Prototyps
ist hasOwnProperty(Eigenschaftsname)
.
Sie gibt einen booleschen Wert, also true
oder false
zurück. Dadurch lässt sich
ermitteln, ob eine bestimmte Eigenschaft durch
dessen Konstruktor selbst oder durch seine
Prototyp-Kette definiert wird. Im Beispiel ergibt
Golf.hasOwnProperty("Radanzahl")
false
, ebenso wie
Golf.hasOwnProperty("zeigeRadanzahl")
. Beide
Eigenschaften wurden nachträglich über
PKW.prototype
hinzugefügt.
Golf.hasOwnProperty("Fabrikat")
und
Golf.hasOwnProperty("weitereEigenschaft")
hingegen ergeben true
, weil diese
Eigenschaften durch die Konstruktoren
Kraftfahrzeug
und PKW
belegt
wurden.
Benutzerinteraktion
Meist erfolgt die Benutzerinteraktion über die Änderung der Inhalte eines HTML-Dokuments, insbesondere der Formulare, auf dessen Elemente über das DOM zugegriffen wird. Es gibt jedoch auch einige einfache Arten, direkt mit dem Benutzer zu kommunizieren:
Gibt ein Fenster mit einer Textmeldung aus. Beispiel:
window.alert("Hallo Welt");
Zeigt einen Dialog mit den Schaltflächen „OK“ und „Abbrechen“ an. Zurückgegeben wird ein boolescher Wert, je nachdem welche Schaltfläche der Benutzer ausgewählt hat.
var bestaetigt = window.confirm("Bitte bestätigen");
Es wird ein Dialog zur Informationseingabe angezeigt. Beispiel:
var eingabe = window.prompt("Bitte geben Sie einen Text ein:", "");
Gibt den Text in der Statusleiste des Browserfensters aus:
window.status = "Hallo Welt";
Fehlerbehandlung
Die neueren Versionen von ECMAScript, wie sie im
Internet Explorer 5 und
Netscape Navigator 6 eingebaut sind, verfügen
über eine von Java übernommene Anweisung try
... catch
zur
Fehlerbehandlung.
Die Anweisung try ... catch ... finally
fängt
Ausnahmen (exceptions), die aufgrund
eines
Fehlers oder einer throw
-Anweisung
auftreten, ab. Die Syntax ist wie folgt:
try { // Anweisungen, in denen Ausnahmen auftreten oder ausgelöst werden können } catch (error) { // Anweisungsfolge, die im Ausnahmefall ausgeführt wird. // In diesem Teil kann die Fehlerbehandlung erfolgen. } finally { // Anweisungsfolge, die anschließend in jedem Fall ausgeführt wird. } ... throw("sample exception")
Zu Beginn werden die Anweisungen im try-Block ausgeführt. Falls eine Ausnahme auftritt, wird der Kontrollfluss sofort zum catch-Block mit dem Ausnahmeobjekt als Parameter umgeleitet.
Im Normalfall wird der Ausnahmeblock
übersprungen. Nach der Ausführung des try-Blocks
(auch teilweise) und gegebenenfalls des catch-Blocks
werden in jedem Fall die Anweisungen im finally-Block
ausgeführt. Der finally
-Teil kann
weggelassen werden, alternativ der catch
-Teil.