text:
# Java Script: Das umfassende Referenzwerk

Inhaltsverzeichnis

> Vorwort
>
>> Was in dieser Auflage neu ist
>>
>> Wie man dieses Buch verwendet
>>
>> Die in diesem Buch verwendeten Konventionen
>>
>> Verwendung der Codebeispiele
>>
>> Website und Codebeispiele zu diesem Buch
>>
>> Danksagungen
>
> 1. Einführung in JavaScript
>
>> 1.1 Was ist JavaScript?
>>
>>> 1.1.1 JavaScript ist nicht Java
>>>
>>> 1.1.2 JavaScript ist nicht einfach
>>
>> 1.2 Versionen von JavaScript
>>
>> 1.3 Clientseitiges JavaScript
>>
>>> 1.3.1 Clientseitige JavaScript-Beispiele
>>
>> 1.4 JavaScript in anderen Kontexten
>>
>> 1.5 JavaScript erkunden
>
> I. Der JavaScript-Sprachkern
>
>> 2. Lexikalische Struktur
>>
>>> 2.1 Zeichensatz
>>>
>>> 2.2 Groß- und Kleinschreibung
>>>
>>> 2.3 Whitespace und Zeilenenden
>>>
>>> 2.4 Wahlweise Semikola
>>>
>>> 2.5 Kommentare
>>>
>>> 2.6 Literale
>>>
>>> 2.7 Bezeichner
>>>
>>> 2.8 Reservierte Wörter
>>
>> 3. Datentypen und Werte
>>
>>> 3.1 Zahlen
>>>
>>>> 3.1.1 Integer-Literale
>>>>
>>>> 3.1.2 Hexadezimal- und Oktal-Literale
>>>>
>>>> 3.1.3 Gleitkomma-Literale
>>>>
>>>> 3.1.4 Mit Zahlen arbeiten
>>>>
>>>> 3.1.5 Numerische Unwandlungen
>>>>
>>>> 3.1.6 Besondere numerische Werte
>>>
>>> 3.2 Strings
>>>
>>>> 3.2.1 String-Literale
>>>>
>>>> 3.2.2 Escape-Sequenzen in String-Literalen
>>>>
>>>> 3.2.3 Mit Strings arbeiten
>>>>
>>>> 3.2.4 Zahlen in Strings umwandeln
>>>>
>>>> 3.2.5 Strings in Zahlen umwandeln
>>>
>>> 3.3 Boolesche Werte
>>>
>>>> 3.3.1 Boolesche Typumwandlungen
>>>
>>> 3.4 Funktionen
>>>
>>>> 3.4.1 Funktionsliterale
>>>
>>> 3.5 Objekte
>>>
>>>> 3.5.1 Objekte erzeugen
>>>>
>>>> 3.5.2 Objekt-Literale
>>>>
>>>> 3.5.3 Objektumwandlungen
>>>
>>> 3.6 Arrays
>>>
>>>> 3.6.1 Arrays erzeugen
>>>>
>>>> 3.6.2 Array-Literale
>>>
>>> 3.7 null
>>>
>>> 3.8 undefined
>>>
>>> 3.9 Das Date-Objekt
>>>
>>> 3.10 Reguläre Ausdrücke
>>>
>>> 3.11 Fehlerobjekte
>>>
>>> 3.12 Zusammenfassung der Typumwandlungen
>>>
>>> 3.13 Wrapper-Objekte für primitive Datentypen
>>>
>>> 3.14 Umwandlungen von Objekten in elementare Werte
>>>
>>> 3.15 by-value vs. by-reference
>>>
>>>> 3.15.1 Elementare Typen und Referenztypen
>>>>
>>>> 3.15.2 Strings kopieren und übergeben
>>>>
>>>> 3.15.3 Strings vergleichen
>>>>
>>>> 3.15.4 by-value vs. by-reference: Zusammenfassung
>>
>> 4. Variablen
>>
>>> 4.1 Typisierung von Variablen
>>>
>>> 4.2 Deklaration von Variablen
>>>
>>>> 4.2.1 Wiederholte und weggelassene Deklarationen
>>>
>>> 4.3 Der Geltungsbereich von Variablen
>>>
>>>> 4.3.1 Kein Block-Geltungsbereich
>>>>
>>>> 4.3.2 Nicht definiert oder nicht initialisiert?
>>>
>>> 4.4 Primitive Datentypen und Referenztypen
>>>
>>> 4.5 Garbage Collection
>>>
>>> 4.6 Variablen als Eigenschaften
>>>
>>>> 4.6.1 Das globale Objekt
>>>>
>>>> 4.6.2 Lokale Variablen: Das Aufrufobjekt
>>>>
>>>> 4.6.3 JavaScript-Ausführungskontext
>>>
>>> 4.7 Noch einmal zum Geltungsbereich von Variablen
>>
>> 5. Ausdrücke und Operatoren
>>
>>> 5.1 Ausdrücke
>>>
>>> 5.2 Übersicht über die Operatoren
>>>
>>>> 5.2.1 Anzahl der Operanden
>>>>
>>>> 5.2.2 Typen von Operanden
>>>>
>>>> 5.2.3 Rangfolge der Operatoren
>>>>
>>>> 5.2.4 Assoziativität von Operatoren
>>>
>>> 5.3 Arithmetische Operatoren
>>>
>>> 5.4 Gleichheitsoperatoren
>>>
>>>> 5.4.1 Gleichheit (==) und Identität (===)
>>>>
>>>> 5.4.2 Ungleichheit (!=) und Nichtidentität (!==)
>>>
>>> 5.5 Relationsoperatoren
>>>
>>>> 5.5.1 Vergleichsoperatoren
>>>>
>>>> 5.5.2 Der in-Operator
>>>>
>>>> 5.5.3 Der instanceof-Operator
>>>
>>> 5.6 String-Operatoren
>>>
>>> 5.7 Logische Operatoren
>>>
>>>> 5.7.1 Logisches UND (&&)
>>>>
>>>> 5.7.2 Logisches ODER (||)
>>>>
>>>> 5.7.3 Logisches Nicht (!)
>>>
>>> 5.8 Bit-Operatoren
>>>
>>> 5.9 Zuweisungsoperatoren
>>>
>>>> 5.9.1 Zuweisung mit Operation
>>>
>>> 5.10 Sonstige Operatoren
>>>
>>>> 5.10.1 Der Bedingungsoperator (?:)
>>>>
>>>> 5.10.2 Der typeof-Operator
>>>>
>>>> 5.10.3 Operator zur Objekterzeugung (new)
>>>>
>>>> 5.10.4 Der delete-Operator
>>>>
>>>> 5.10.5 Der void-Operator
>>>>
>>>> 5.10.6 Der Kommaoperator (,)
>>>>
>>>> 5.10.7 Operatoren für den Array- und Objektzugriff
>>>>
>>>> 5.10.8 Der Operator für Funktionsaufrufe
>>
>> 6. Anweisungen
>>
>>> 6.1 Anweisungen mit Ausdrücken
>>>
>>> 6.2 Zusammengesetzte Anweisungen
>>>
>>> 6.3 if
>>>
>>> 6.4 else if
>>>
>>> 6.5 switch
>>>
>>> 6.6 while
>>>
>>> 6.7 do/while
>>>
>>> 6.8 for
>>>
>>> 6.9 for/in
>>>
>>> 6.10 Markierungen
>>>
>>> 6.11 break
>>>
>>> 6.12 continue
>>>
>>> 6.13 var
>>>
>>> 6.14 function
>>>
>>> 6.15 return
>>>
>>> 6.16 throw
>>>
>>> 6.17 try/catch/finally
>>>
>>> 6.18 with
>>>
>>> 6.19 Die leere Anweisung
>>>
>>> 6.20 Zusammenfassung der JavaScript-Anweisungen
>>
>> 7. Objekte und Arrays
>>
>>> 7.1 Objekte erzeugen
>>>
>>> 7.2 Objekteigenschaften
>>>
>>>> 7.2.1 Eigenschaften aufzählen
>>>>
>>>> 7.2.2 Die Existenz von Eigenschaften prüfen
>>>>
>>>> 7.2.3 Eigenschaften löschen
>>>
>>> 7.3 Objekte als assoziative Arrays
>>>
>>> 7.4 Universelle Objekteigenschaften und -methoden
>>>
>>>> 7.4.1 Die constructor-Eigenschaft
>>>>
>>>> 7.4.2 Die toString( )-Methode
>>>>
>>>> 7.4.3 Die toLocaleString( )-Methode
>>>>
>>>> 7.4.4 Die valueOf( )-Methode
>>>>
>>>> 7.4.5 Die hasOwnProperty( )-Methode
>>>>
>>>> 7.4.6 Die propertyIsEnumerable( )-Methode
>>>>
>>>> 7.4.7 Die isPrototypeOf( )-Methode
>>>
>>> 7.5 Arrays
>>>
>>>> 7.5.1 Array-Elemente lesen und schreiben
>>>>
>>>> 7.5.2 Neue Elemente zu einem Array hinzufügen
>>>>
>>>> 7.5.3 Array-Elemente löschen
>>>>
>>>> 7.5.4 Array-Länge
>>>>
>>>> 7.5.5 Arrays durchlaufen
>>>>
>>>> 7.5.6 Arrays verkleinern und vergrößern
>>>>
>>>> 7.5.7 Mehrdimensionale Arrays
>>>
>>> 7.6 Array-Methoden
>>>
>>>> 7.6.1 join( )
>>>>
>>>> 7.6.2 reverse( )
>>>>
>>>> 7.6.3 sort( )
>>>>
>>>> 7.6.4 concat( )
>>>>
>>>> 7.6.5 slice( )
>>>>
>>>> 7.6.6 splice( )
>>>>
>>>> 7.6.7 push( ) und pop( )
>>>>
>>>> 7.6.8 unshift( ) und shift( )
>>>>
>>>> 7.6.9 toString( ) und toLocaleString( )
>>>>
>>>> 7.6.10 Array-Extras
>>>
>>> 7.7 Array-artige Objekte
>>
>> 8. Funktionen
>>
>>> 8.1 Definition und Aufruf von Funktionen
>>>
>>>> 8.1.1 Geschachtelte Funktionen
>>>>
>>>> 8.1.2 Funktionsliterale
>>>>
>>>> 8.1.3 Funktionsnamen
>>>
>>> 8.2 Funktionsargumente
>>>
>>>> 8.2.1 Optionale Argumente
>>>>
>>>> 8.2.2 Argumentlisten variabler Länge: Das Arguments-Objekt
>>>>
>>>> 8.2.3 Objekteigenschaften als Argumente verwenden
>>>>
>>>> 8.2.4 Argumenttypen
>>>
>>> 8.3 Funktionen als Datentypen
>>>
>>> 8.4 Funktionen als Methoden
>>>
>>> 8.5 Konstruktorfunktionen
>>>
>>> 8.6 Funktionseigenschaften und -methoden
>>>
>>>> 8.6.1 Die length-Eigenschaft
>>>>
>>>> 8.6.2 Die prototype-Eigenschaft
>>>>
>>>> 8.6.3 Definition eigener Funktionseigenschaften
>>>>
>>>> 8.6.4 Die Methoden apply( ) und call( )
>>>
>>> 8.7 Beispiele für Hilfsfunktionen
>>>
>>> 8.8 Funktionsgeltung und Closures
>>>
>>>> 8.8.1 Lexikalische Geltung
>>>>
>>>> 8.8.2 Das Aufrufobjekt
>>>>
>>>> 8.8.3 Das Aufrufobjekt als Namensraum
>>>>
>>>> 8.8.4 Eingebettete Funktionen als Closures
>>>
>>> 8.9 Der Function( )-Konstruktor
>>
>> 9. Klassen, Konstruktoren und Prototypen
>>
>>> 9.1 Konstruktoren
>>>
>>> 9.2 Prototypen und Vererbung
>>>
>>>> 9.2.1 Geerbte Eigenschaften lesen und schreiben
>>>>
>>>> 9.2.2 Eingebaute Typen erweitern
>>>
>>> 9.3 Klassen in JavaScript simulieren
>>>
>>>> 9.3.1 Instanzeigenschaften
>>>>
>>>> 9.3.2 Instanzmethoden
>>>>
>>>> 9.3.3 Klasseneigenschaften
>>>>
>>>> 9.3.4 Klassenmethoden
>>>>
>>>> 9.3.5 Beispiel: Eine Kreis-Klasse
>>>>
>>>> 9.3.6 Beispiel: Komplexe Zahlen
>>>>
>>>> 9.3.7 Private Member
>>>
>>> 9.4 Allgemeine Objektmethoden
>>>
>>>> 9.4.1 Die Methode toString( )
>>>>
>>>> 9.4.2 Die Methode valueOf( )
>>>>
>>>> 9.4.3 Vergleichsmethoden
>>>
>>> 9.5 Superklassen und Subklassen
>>>
>>>> 9.5.1 Konstruktorverkettung
>>>>
>>>> 9.5.2 Überschriebene Methoden aufrufen
>>>
>>> 9.6 Erweitern, ohne zu erben
>>>
>>> 9.7 Den Objekttyp ermitteln
>>>
>>>> 9.7.1 instanceof und constructor
>>>>
>>>> 9.7.2 Typbestimmung von Objekten mit Object.toString( )
>>>>
>>>> 9.7.3 Duck Typing
>>>
>>> 9.8 Beispiel: Eine defineClass( )-Hilfsmethode
>>
>> 10. Module und Namensräume
>>
>>> 10.1 Namensräume und Module erzeugen
>>>
>>>> 10.1.1 Die Verfügbarkeit eines Moduls prüfen
>>>>
>>>> 10.1.2 Klassen als Module
>>>>
>>>> 10.1.3 Initialisierungscode für Module
>>>
>>> 10.2 Symbole aus Namensräumen importieren
>>>
>>>> 10.2.1 Öffentliche und private Symbole
>>>>
>>>> 10.2.2 Closures als privater Namensraum und Geltungsbereich
>>>
>>> 10.3 Modulwerkzeuge
>>
>> 11. Mustervergleich mit regulären Ausdrücken
>>
>>> 11.1 Reguläre Ausdrücke definieren
>>>
>>>> 11.1.1 Literalzeichen
>>>>
>>>> 11.1.2 Zeichenklassen
>>>>
>>>> 11.1.3 Alternativen, Gruppierung und Referenzen
>>>>
>>>> 11.1.4 Vergleichspositionen angeben
>>>>
>>>> 11.1.5 Flags
>>>>
>>>> 11.1.6 Perl-Funktionen von RegExp, die in JavaScript nicht unterstützt werden
>>>
>>> 11.2 String-Methoden für den Mustervergleich
>>>
>>> 11.3 Das RegExp-Objekt
>>>
>>>> 11.3.1 RegExp-Methoden für den Mustervergleich
>>>>
>>>> 11.3.2 Instanzeigenschaften von RegExp
>>
>> 12. Java skripten
>>
>>> 12.1 JavaScript einbetten
>>>
>>>> 12.1.1 Typumwandlungen mit javax.script
>>>>
>>>> 12.1.2 Skripten kompilieren
>>>>
>>>> 12.1.3 JavaScript-Funktionen aufrufen
>>>>
>>>> 12.1.4 In JavaScript Interfaces implementieren
>>>
>>> 12.2 Java skripten
>>>
>>>> 12.2.1 Die Klasse JavaPackage
>>>>
>>>> 12.2.2 Die Klasse JavaClass
>>>>
>>>> 12.2.3 Packages und Klassen importieren
>>>>
>>>> 12.2.4 Die Klasse JavaObject
>>>>
>>>> 12.2.5 Java-Methoden
>>>>
>>>> 12.2.6 Die Klasse JavaArray
>>>>
>>>> 12.2.7 Java-Arrays erzeugen
>>>>
>>>> 12.2.8 Mit LiveConnect Interfaces implementieren
>>>>
>>>> 12.2.9 LiveConnect-Datenumwandlung
>
> II. Clientseitiges JavaScript
>
>> 13. JavaScript in Webbrowsern
>>
>>> 13.1 Die Webbrowser-Umgebung
>>>
>>>> 13.1.1 Das Fenster als globaler Ausführungskontext
>>>>
>>>> 13.1.2 Die clientseitige Objekt-Hierarchie und das Document Object Model
>>>>
>>>> 13.1.3 Event-gesteuertes Programmieren
>>>>
>>>> 13.1.4 Die Rolle von JavaScript im Web
>>>>
>>>> 13.1.5 Barrierefreies JavaScript
>>>
>>> 13.2 Skripten in HTML einbetten
>>>
>>>> 13.2.1 Das <script>-Tag
>>>>
>>>> 13.2.2 Skripten in externen Dateien
>>>>
>>>> 13.2.3 Die Skriptsprache angeben
>>>>
>>>> 13.2.4 Das defer-Attribut
>>>>
>>>> 13.2.5 Das <noscript>-Tag
>>>>
>>>> 13.2.6 Das </script>-Tag
>>>>
>>>> 13.2.7 Skripten vor älteren Browsern verbergen
>>>>
>>>> 13.2.8 Nicht-standardgemäße <script>-Attribute
>>>
>>> 13.3 Event-Handler in HTML
>>>
>>> 13.4 JavaScript in URLs
>>>
>>>> 13.4.1 Bookmarklets
>>>
>>> 13.5 Ausführung von JavaScript-Programmen
>>>
>>>> 13.5.1 Skripten ausführen
>>>>
>>>> 13.5.2 Der onload-Event-Handler
>>>>
>>>> 13.5.3 Event-Handler und JavaScript-URLs
>>>>
>>>> 13.5.4 Der onunload-Event-Handler
>>>>
>>>> 13.5.5 Das Window-Objekt als Ausführungskontext
>>>>
>>>> 13.5.6 Das Threading-Modell von clientseitigem JavaScript
>>>>
>>>> 13.5.7 Das Dokument während des Ladens manipulieren
>>>
>>> 13.6 Clientseitige Kompatibilität
>>>
>>>> 13.6.1 Die Geschichte der Inkompatibilität
>>>>
>>>> 13.6.2 Ein Wort zu »modernen Browsern«
>>>>
>>>> 13.6.3 Feature Testing
>>>>
>>>> 13.6.4 Browserermittlung
>>>>
>>>> 13.6.5 Bedingte Kommentare im Internet Explorer
>>>
>>> 13.7 Zugänglichkeit
>>>
>>> 13.8 JavaScript-Sicherheit
>>>
>>>> 13.8.1 Was JavaScript nicht machen kann
>>>>
>>>> 13.8.2 Die Same Origin Policy
>>>>
>>>> 13.8.3 Plug-ins und ActiveX-Steuerelemente skripten
>>>>
>>>> 13.8.4 Cross Site Scripting
>>>>
>>>> 13.8.5 Denial-of-Service-Angriffe
>>>
>>> 13.9 Andere webbezogene JavaScript-Einbettungen
>>
>> 14. Browserfenster skripten
>>
>>> 14.1 Timer
>>>
>>> 14.2 Die Location- und History-Objekte
>>>
>>>> 14.2.1 URLs parsen
>>>>
>>>> 14.2.2 Neue Dokumente laden
>>>>
>>>> 14.2.3 Das History-Objekt
>>>
>>> 14.3 Window-, Screen- und Browser-Informationen abrufen
>>>
>>>> 14.3.1 Window-Geometrie
>>>>
>>>> 14.3.2 Das Screen-Objekt
>>>>
>>>> 14.3.3 Das Navigator-Objekt
>>>
>>> 14.4 Fenster öffnen und manipulieren
>>>
>>>> 14.4.1 Fenster öffnen
>>>>
>>>> 14.4.2 Fenster schließen
>>>>
>>>> 14.4.3 Fenster manipulieren
>>>>
>>>> 14.4.4 Tastaturfokus und Sichtbarkeit
>>>>
>>>> 14.4.5 Scrollen
>>>>
>>>> 14.4.6 Beispiel zu Window-Methoden
>>>
>>> 14.5 Einfache Dialogfenster
>>>
>>> 14.6 Die Statusleiste skripten
>>>
>>> 14.7 Fehlerbehandlung
>>>
>>> 14.8 Mehrere Fenster und Frames
>>>
>>>> 14.8.1 Beziehungen zwischen Frames
>>>>
>>>> 14.8.2 Fensternamen und Frame-Namen
>>>>
>>>> 14.8.3 JavaScript in interagierenden Fenstern
>>>
>>> 14.9 Beispiel: Eine Navigationsleiste in einem Frame
>>
>> 15. Dokumente skripten
>>
>>> 15.1 Dynamischer Dokumentinhalt
>>>
>>> 15.2 Document-Eigenschaften
>>>
>>> 15.3 Alt-DOM: Dokumentobjekt-Sammlungen
>>>
>>>> 15.3.1 Dokumentobjekten Namen geben
>>>>
>>>> 15.3.2 Event-Handler auf Document-Objekten
>>>>
>>>> 15.3.3 Alt-DOM-Beispiele
>>>
>>> 15.4 Überblick über das W3C DOM
>>>
>>>> 15.4.1 Dokumente als Bäume darstellen
>>>>
>>>> 15.4.2 Knoten
>>>>
>>>> 15.4.3 Die DOM HTML API
>>>>
>>>> 15.4.4 DOM Level und Features
>>>>
>>>> 15.4.5 DOM-Konformität
>>>>
>>>> 15.4.6 Sprachunabhängige DOM-Interfaces
>>>
>>> 15.5 Ein Dokument durchqueren
>>>
>>> 15.6 Elemente in einem Dokument finden
>>>
>>> 15.7 Ein Dokument modifizieren
>>>
>>>> 15.7.1 Attribute verändern
>>>>
>>>> 15.7.2 Mit Dokumentfragmenten arbeiten
>>>
>>> 15.8 Einem Dokument Inhalt hinzufügen
>>>
>>>> 15.8.1 Hilfsmethoden zur Erstellung von Knoten
>>>>
>>>> 15.8.2 Die innerHTML-Eigenschaft
>>>
>>> 15.9 Beispiel: Ein dynamisch erzeugtes Inhaltsverzeichnis
>>>
>>> 15.10 Ausgewählten Text abfragen
>>>
>>> 15.11 Das IE 4 DOM
>>>
>>>> 15.11.1 Ein Dokument durchqueren
>>>>
>>>> 15.11.2 Dokumentelemente finden
>>>>
>>>> 15.11.3 Dokumente modifizieren
>>
>> 16. Cascading Style Sheets und dynamisches HTML
>>
>>> 16.1 Überblick über CSS
>>>
>>>> 16.1.1 Stilregeln auf Elemente eines Dokuments anwenden
>>>>
>>>> 16.1.2 Stylesheets zu Dokumenten zuordnen
>>>>
>>>> 16.1.3 Die Kaskade
>>>>
>>>> 16.1.4 Versionen von CSS
>>>>
>>>> 16.1.5 Ein CSS-Beispiel
>>>
>>> 16.2 CSS für DHTML
>>>
>>>> 16.2.1 Der Schlüssel zu DHTML: Absolute Positionierung
>>>>
>>>> 16.2.2 CSS-Positionierungsbeispiel: Text mit Schatten
>>>>
>>>> 16.2.3 Abfrage der Position und Abmessungen von Elementen
>>>>
>>>> 16.2.4 Die dritte Dimension: z-index
>>>>
>>>> 16.2.5 Anzeige und Sichtbarkeit von Elementen
>>>>
>>>> 16.2.6 Das CSS-Boxmodell und Positionierungsdetails
>>>>
>>>> 16.2.7 Farbe, Transparenz und Durchsichtigkeit
>>>>
>>>> 16.2.8 Partielle Sichtbarkeit: overflow und clip
>>>>
>>>> 16.2.9 Beispiel: Überlappende durchsichtige Fenster
>>>
>>> 16.3 Eingebettete Styles per Skript steuern
>>>
>>>> 16.3.1 Namenskonventionen: CSS-Eigenschaften in JavaScript
>>>>
>>>> 16.3.2 Arbeiten mit Stileigenschaften
>>>>
>>>> 16.3.3 Beispiel: CSS-Tooltips
>>>>
>>>> 16.3.4 DHTML-Animationen
>>>
>>> 16.4 Berechnete Styles per Skript steuern
>>>
>>> 16.5 CSS-Klassen per Skript steuern
>>>
>>> 16.6 Stylesheets per Skript steuern
>>>
>>>> 16.6.1 Stylesheets aktivieren und deaktivieren
>>>>
>>>> 16.6.2 Stylesheet-Objekte und Stylesheet-Regeln
>>
>> 17. Events und Event-Handling
>>
>>> 17.1 Grundlegendes Event-Handling
>>>
>>>> 17.1.1 Events und Event-Typen
>>>>
>>>> 17.1.2 Event-Handler als Attribute
>>>>
>>>> 17.1.3 Event-Handler als Eigenschaften
>>>>
>>>> 17.1.4 Event-Handler explizit aufrufen
>>>>
>>>> 17.1.5 Rückgabewerte von Event-Handlern
>>>>
>>>> 17.1.6 Event-Handler und das Schlüsselwort this
>>>>
>>>> 17.1.7 Geltungsbereich von Event-Handlern
>>>
>>> 17.2 Fortgeschrittenes Event-Handling mit dem DOM Level 2
>>>
>>>> 17.2.1 Event-Propagation
>>>>
>>>> 17.2.2 Registrierung von Event-Handlern
>>>>
>>>> 17.2.3 addEventListener( ) und das Schlüsselwort this
>>>>
>>>> 17.2.4 Registrierung von Objekten als Event-Handler
>>>>
>>>> 17.2.5 Module und Typen von Events
>>>>
>>>> 17.2.6 Mischen von Event-Modellen
>>>
>>> 17.3 Das Event-Modell des Internet Explorers
>>>
>>>> 17.3.1 Das Event-Objekt des IE
>>>>
>>>> 17.3.2 Das Event-Objekt des IE als globale Variable
>>>>
>>>> 17.3.3 Registrierung von Event-Handlern im IE
>>>>
>>>> 17.3.4 Event-Bubbling im IE
>>>>
>>>> 17.3.5 Abfangen von Maus-Events
>>>>
>>>> 17.3.6 attachEvent( ) und das Schlüsselwort this
>>>>
>>>> 17.3.7 Event-Handler und Speicherlecks
>>>>
>>>> 17.3.8 Beispiel: Event-Modell-Kompatibilität für IE
>>>
>>> 17.4 Maus-Events
>>>
>>>> 17.4.1 Mauskoordinaten konvertieren
>>>>
>>>> 17.4.2 Beispiel: Ziehen von Dokumentelementen
>>>
>>> 17.5 Tasten-Events
>>>
>>>> 17.5.1 Tasten-Event-Typen
>>>>
>>>> 17.5.2 Tasten-Event-Details
>>>>
>>>> 17.5.3 Tastatureingaben filtern
>>>>
>>>> 17.5.4 Tastenkürzel mit einer Keymap
>>>
>>> 17.6 Das onload-Event
>>>
>>> 17.7 Synthetische Events
>>
>> 18. Formulare und Formularelemente
>>
>>> 18.1 Das Form-Objekt
>>>
>>> 18.2 Definition von Formularelementen
>>>
>>> 18.3 Formularelemente skripten
>>>
>>>> 18.3.1 Formulare und Formularelemente benennen
>>>>
>>>> 18.3.2 Eigenschaften von Formularelementen
>>>>
>>>> 18.3.3 Event-Handler von Formularelementen
>>>>
>>>> 18.3.4 Button-Schaltflächen
>>>>
>>>> 18.3.5 Schalter
>>>>
>>>> 18.3.6 Textfelder
>>>>
>>>> 18.3.7 Select- und Option-Elemente
>>>>
>>>> 18.3.8 Versteckte Elemente
>>>>
>>>> 18.3.9 Fieldset-Elemente
>>>
>>> 18.4 Ein Beispiel zur Überprüfung von Formularen
>>
>> 19. Cookies und Persistenz auf der Clientseite
>>
>>> 19.1 Ein Überblick über Cookies
>>>
>>> 19.2 Cookies speichern
>>>
>>>> 19.2.1 Beschränkungen von Cookies
>>>
>>> 19.3 Cookies lesen
>>>
>>> 19.4 Ein Cookie-Beispiel
>>>
>>> 19.5 Cookie-Alternativen
>>>
>>>> 19.5.1 IE userData-Persistenz
>>>>
>>>> 19.5.2 Flash-SharedObject-Persistenz
>>>>
>>>> 19.5.3 Beispiel: Persistente Objekte
>>>>
>>>> 19.5.4 ActionScript-Code für Flash-Persistenz
>>>
>>> 19.6 Persistente Daten und Sicherheit
>>
>> 20. HTTP skripten
>>
>>> 20.1 Verwendung von XMLHttpRequest
>>>
>>>> 20.1.1 Wie komme ich an ein Request-Objekt?
>>>>
>>>> 20.1.2 Einen Request übermitteln
>>>>
>>>> 20.1.3 Eine synchrone Antwort erhalten
>>>>
>>>> 20.1.4 Umgang mit einer asynchronen Response
>>>>
>>>> 20.1.5 XMLHttpRequest Security
>>>
>>> 20.2 XMLHttpRequest-Beispiele und Utilities
>>>
>>>> 20.2.1 Einfache GET-Utilities
>>>>
>>>> 20.2.2 Nur Header holen
>>>>
>>>> 20.2.3 HTTP POST
>>>>
>>>> 20.2.4 HTML, XML und JSON-codierte Responses
>>>>
>>>> 20.2.5 Zeitabbruch eines Requests
>>>
>>> 20.3 Ajax und dynamisches Skripten
>>>
>>>> 20.3.1 Ajax-Beispiel
>>>>
>>>> 20.3.2 Einseiten-Anwendungen
>>>>
>>>> 20.3.3 Remote Scripting
>>>>
>>>> 20.3.4 Vorsicht bei Ajax
>>>
>>> 20.4 Skriptgesteuertes HTTP mit <script>-Tags
>>
>> 21. JavaScript und XML
>>
>>> 21.1 XML-Dokumente beschaffen
>>>
>>>> 21.1.1 Ein neues Dokument erstellen
>>>>
>>>> 21.1.2 Ein Dokument aus dem Netzwerk laden
>>>>
>>>> 21.1.3 XML-Text parsen
>>>>
>>>> 21.1.4 XML-Dokumente aus Dateninseln
>>>
>>> 21.2 XML über die DOM API manipulieren
>>>
>>>> 21.2.1 XML im Vergleich zum HTML DOM
>>>>
>>>> 21.2.2 Beispiel: Aus XML-Daten eine HTML-Tabelle erstellen
>>>
>>> 21.3 XML mit XSLT transformieren
>>>
>>> 21.4 XML mit XPath abfragen
>>>
>>>> 21.4.1 XPath-Beispiele
>>>>
>>>> 21.4.2 XPath-Ausdrücke auswerten
>>>>
>>>> 21.4.3 Mehr zur W3C XPath API
>>>
>>> 21.5 Serialisierung von XML
>>>
>>> 21.6 HTML-Templates mit XML-Daten erweitern
>>>
>>> 21.7 XML und Webservices
>>>
>>> 21.8 E4X: ECMAScript für XML
>>
>> 22. Skriptgesteuerte Grafiken auf der Clientseite
>>
>>> 22.1 Skriptgesteuerte Bilder
>>>
>>>> 22.1.1 Bilder und das Level 0 DOM
>>>>
>>>> 22.1.2 Herkömmliche Bild-Rollover
>>>>
>>>> 22.1.3 Offscreen-Bilder und Caching
>>>>
>>>> 22.1.4 Unauffällige Bild-Rollover
>>>>
>>>> 22.1.5 Bildanimationen
>>>>
>>>> 22.1.6 Weitere Image-Eigenschaften
>>>
>>> 22.2 Grafiken mit CSS
>>>
>>>> 22.2.1 Balkendiagramme mit CSS
>>>>
>>>> 22.2.2 Eine CSSDrawing-Klasse
>>>
>>> 22.3 SVG: Scalable Vector Graphics
>>>
>>> 22.4 VML: Vector Markup Language
>>>
>>> 22.5 Grafiken auf einem <canvas>
>>>
>>> 22.6 Grafiken mit Flash
>>>
>>> 22.7 Grafiken mit Java
>>>
>>>> 22.7.1 Tortendiagramme mit Java
>>>>
>>>> 22.7.2 Clientseitige Sparkline-Bilder mit Java
>>
>> 23. Java-Applets und Flash-Movies skripten
>>
>>> 23.1 Applets skripten
>>>
>>> 23.2 Skriptsteuerung des Java-Plug-ins
>>>
>>> 23.3 Skriptsteuerung mit Java
>>>
>>>> 23.3.1 Applets, die JSObject verwenden, kompilieren und einsetzen
>>>>
>>>> 23.3.2 Daten von Java nach JavaScript konvertieren
>>>>
>>>> 23.3.3 Die Common DOM API
>>>
>>> 23.4 Flash skripten
>>>
>>>> 23.4.1 Flash-Movies einbetten und auf sie zugreifen
>>>>
>>>> 23.4.2 Den Flash-Player steuern
>>>>
>>>> 23.4.3 Flash-Movies skripten
>>>>
>>>> 23.4.4 JavaScript von Flash aus aufrufen
>>>>
>>>> 23.4.5 Beispiel: von Flash zu JavaScript und zurück zu Flash
>>>
>>> 23.5 Flash 8 skripten
>
> III. Referenz für den JavaScript-Sprachkern
>
>> Referenz für den Sprachkern von JavaScript
>>
>>> Beispiel-Referenzeintrag — So werden diese Referenzeinträge gelesen
>>>
>>> arguments[ ] — Ein Array von Funktionsargumenten
>>>
>>> Arguments — Argumente und andere Eigenschaften einer Funktion
>>>
>>> Arguments.callee — Die Funktion, die gerade läuft
>>>
>>> Arguments.length — Die Anzahl der an eine Funktion übergebenen Argumente
>>>
>>> Array — Eingebaute Array-Unterstützung
>>>
>>> Array.concat( ) — Verkettet Arrays
>>>
>>> Array.join( ) — Verkettet Array-Elemente zu einem String
>>>
>>> Array.length — Die Größe eines Arrays
>>>
>>> Array.pop( ) — Entfernt und liefert das letzte Element eines Arrays
>>>
>>> Array.push( ) — Hängt Elemente an ein Array an
>>>
>>> Array.reverse( ) — Kehrt die Elemente eines Arrays um
>>>
>>> Array.shift( ) — Schiebt Array-Elemente nach unten
>>>
>>> Array.slice( ) — Gibt einen Teil eines Arrays zurück
>>>
>>> Array.sort( ) — Sortiert die Elemente eines Arrays
>>>
>>> Array.splice( ) — Fügt Array-Elemente ein oder entfernt oder ersetzt sie
>>>
>>> Array.toLocaleString( ) — Konvertiert ein Array in einen lokalisierten String
>>>
>>> Array.toString( ) — Konvertiert ein Array in einen String
>>>
>>> Array.unshift( ) — Fügt am Array-Anfang Elemente ein
>>>
>>> Boolean — Unterstützung für Boolesche Werte
>>>
>>> Boolean.toString( ) — Konvertiert einen Booleschen Wert in einen String
>>>
>>> Boolean.valueOf( ) — Der Boolesche Wert eines Boolean-Objekts
>>>
>>> Date — Bearbeitet Datums- und Uhrzeitwerte
>>>
>>> Date.getDate( ) — Gibt das Monatstag-Feld eines Date-Objekts zurück
>>>
>>> Date.getDay( ) — Gibt das Wochentagsfeld eines Date-Objekts zurück
>>>
>>> Date.getFullYear( ) — Gibt des Jahresfeld eines Date-Objekts zurück
>>>
>>> Date.getHours( ) — Gibt das Stundenfeld zurück
>>>
>>> Date.getMilliseconds( ) — Gibt das Millisekundenfeld zurück
>>>
>>> Date.getMinutes( ) — Gibt das Minutenfeld zurück
>>>
>>> Date.getMonth( ) — Gibt das Monatsfeld zurück
>>>
>>> Date.getSeconds( ) — Gibt das Sekundenfeld zurück
>>>
>>> Date.getTime( ) — Gibt ein Date in Millisekunden zurück
>>>
>>> Date.getTimezoneOffset( ) — Ermittelt die Zeitdifferenz gegenüber GMT
>>>
>>> Date.getUTCDate( ) — Gibt das Monatstag-Feld eines Date-Objekts zurück (Universalzeit)
>>>
>>> Date.getUTCDay( ) — Gibt das Wochentagsfeld eines Date-Objekts zurück (Universalzeit)
>>>
>>> Date.getUTCFullYear( ) — Gibt das Jahresfeld eines Date-Objekts zurück (Universalzeit)
>>>
>>> Date.getUTCHours( ) — Gibt das Stundenfeld zurück (Universalzeit)
>>>
>>> Date.getUTCMilliseconds( ) — Gibt das Millisekundenfeld zurück (Universalzeit)
>>>
>>> Date.getUTCMinutes( ) — Gibt das Minutenfeld zurück (Universalzeit)
>>>
>>> Date.getUTCMonth( ) — Gibt das Monat-des-Jahrs-Feld eines Date-Objekts zurück (Universalzeit)
>>>
>>> Date.getUTCSeconds( ) — Gibt das Sekundenfeld zurück (Universalzeit)
>>>
>>> Date.getYear( ) — Gibt das Jahresfeld zurück
>>>
>>> Date.parse( ) — Parst einen Datums/Uhrzeit-String
>>>
>>> Date.setDate( ) — Setzt das Monatstag-Feld eines Date-Objekts
>>>
>>> Date.setFullYear( ) — Setzt das Jahr und optional den Monat und das Datum
>>>
>>> Date.setHours( ) — Setzt die Felder für Stunden, Minuten, Sekunden und Millisekunden eines Date-Objekts
>>>
>>> Date.setMilliseconds( ) — Setzt das Millisekundenfeld eines Date-Objekts
>>>
>>> Date.setMinutes( ) — Setzt die Felder für Minuten, Sekunden und Millisekunden eines Date-Objekts
>>>
>>> Date.setMonth( ) — Setzt die Felder für Monat und Tag eines Date-Objekts
>>>
>>> Date.setSeconds( ) — Setzt die Felder für Sekunden und Millisekunden eines Date
>>>
>>> Date.setTime( ) — Setzt ein Date in Millisekunden
>>>
>>> Date.setUTCDate( ) — Setzt das Monatstag-Feld eines Date-Objekts (Universalzeit)
>>>
>>> Date.setUTCFullYear( ) — Setzt Jahr, Monat und Tag eines Date-Objekts (Universalzeit)
>>>
>>> Date.setUTCHours( ) — Setzt die Felder für Stunden, Minuten, Sekunden und Millisekunden eines Date-Objekts (Universalzeit)
>>>
>>> Date.setUTCMilliseconds( ) — Setzt das Millisekundenfeld eines Date-Objekts (Universalzeit)
>>>
>>> Date.setUTCMinutes( ) — Setzt die Felder für Minuten, Sekunden und Millisekunden eines Date-Objekts (Universalzeit)
>>>
>>> Date.setUTCMonth( ) — Setzt die Felder für Monat und Tag eines Date-Objekts (Universalzeit)
>>>
>>> Date.setUTCSeconds( ) — Setzt die Felder für Sekunden und Millisekunden eines Date-Objekts (Universalzeit)
>>>
>>> Date.setYear( ) — Setzt das Jahresfeld eines Date-Objekts
>>>
>>> Date.toDateString( ) — Gibt den Datumsteil eines Date-Objekts als String zurück
>>>
>>> Date.toGMTString( ) — Konvertiert ein Date-Objekt in einen Universalzeit-String
>>>
>>> Date.toLocaleDateString( ) — Gibt den Datumsteil eines Date-Objekts als String im ortsüblichen Format aus
>>>
>>> Date.toLocaleString( ) — Konvertiert ein Date-Objekt in einen String im ortsüblichen Format
>>>
>>> Date.toLocaleTimeString( ) — Gibt den Uhrzeitteil eines Date-Objekts als String im ortsüblichen Format zurück
>>>
>>> Date.toString( ) — Konvertiert ein Date-Objekt in einen String
>>>
>>> Date.toTimeString( ) — Gibt den Uhrzeitteil eines Date-Objekts als String zurück
>>>
>>> Date.toUTCString( ) — Konvertiert ein Date-Objekt in einen String (Universalzeit)
>>>
>>> Date.UTC( ) — Konvertiert eine Date-Angabe in Millisekunden
>>>
>>> Date.valueOf( ) — Konvertiert ein Date-Objekt in Millisekunden
>>>
>>> decodeURI( ) — Entfernt Escape-Zeichen aus einer URI
>>>
>>> decodeURIComponent( ) — Entfernt Escape-Zeichen aus einem URI-Bestandteil
>>>
>>> encodeURI( ) — Ersetzt Zeichen in einer URI durch Escape-Sequenzen
>>>
>>> encodeURIComponent( ) — Ersetzt Zeichen in einem URI-Bestandteil durch Escape-Sequenzen
>>>
>>> Error — Eine generische Exception
>>>
>>> Error.message — Eine für Menschen lesbare Fehlermeldung
>>>
>>> Error.name — Der Typ eines Fehlers
>>>
>>> Error.toString( ) — Konvertiert ein Error-Objekt in einen String
>>>
>>> escape( ) — Codiert einen String
>>>
>>> eval( ) — Führt JavaScript-Code in einem String aus
>>>
>>> EvalError — Wird bei falscher Verwendung von eval( ) ausgelöst
>>>
>>> Function — Eine JavaScript-Funktion
>>>
>>> Function.apply( ) — Ruft eine Funktion als Methode eines Objekts auf
>>>
>>> Function.arguments[] — An eine Funktion übergebene Argumente
>>>
>>> Function.call( ) — Ruft eine Funktion als Methode eines Objekts auf
>>>
>>> Function.caller — Die Funktion, die diese hier aufrief
>>>
>>> Function.length — Die Anzahl der deklarierten Argumente
>>>
>>> Function.prototype — Der Prototyp für eine Klasse von Objekten
>>>
>>> Function.toString( ) — Konvertiert eine Funktion in einen String
>>>
>>> getClass( ) — Gibt die JavaClass eines JavaObjects zurück
>>>
>>> Global — Das globale Objekt
>>>
>>> Infinity — Eine numerische Eigenschaft, die unendlich darstellt
>>>
>>> isFinite( ) — Stellt fest, ob eine Zahl endlich ist
>>>
>>> isNaN( ) — Prüft, ob der Wert Not-a-Number ist
>>>
>>> Core DOMjava — Das JavaPackage für die java.*-Package-Hierarchie
>>>
>>> JavaArray — JavaScript-Repräsentation eines Java-Arrays
>>>
>>> JavaClass — JavaScript-Repräsentation einer Java-Klasse
>>>
>>> JavaObject — JavaScript-Repräsentation eines Java-Objekts
>>>
>>> JavaPackage — JavaScript-Repräsentation eines Java-Packages
>>>
>>> JSObject — Siehe JSObject in Teil IV
>>>
>>> Math — Mathematische Funktionen und Konstanten
>>>
>>> Math.abs( ) — Berechnet einen Absolutwert
>>>
>>> Math.acos( ) — Berechnet einen Arkus Kosinus
>>>
>>> Math.asin( ) — Berechnet einen Arkus Sinus
>>>
>>> Math.atan( ) — Berechnet einen Arkus Tangens
>>>
>>> Math.atan2( ) — Berechnet den Winkel zwischen X-Achse und einem Punkt
>>>
>>> Math.ceil( ) — Rundet eine Zahl auf
>>>
>>> Math.cos( ) — Berechnet einen Kosinus
>>>
>>> Math.E — Die mathematische Konstante _e_
>>>
>>> Math.exp( ) — Berechnet _e_
>>>
>>> Math.floor( ) — Rundet eine Zahl ab
>>>
>>> Math.LN10 — Die mathematische Konstante _e_
>>>
>>> Math.LN2 — Die mathematische Konstante _e_
>>>
>>> Math.log( ) — Berechnet einen natürlichen Logarithmus
>>>
>>> Math.LOG10E — Die mathematische Konstante log10e
>>>
>>> Math.LOG2E — Die mathematische Konstante _e_
>>>
>>> Math.max( ) — Gibt das größte Argument zurück
>>>
>>> Math.min( ) — Gibt das kleinste Argument zurück
>>>
>>> Math.PI — Die mathematische Konstante π
>>>
>>> Math.pow( ) — Berechnet _e_
>>>
>>> Math.random( ) — Gibt eine Pseudozufallszahl zurück
>>>
>>> Math.round( ) — Rundet auf die nächste ganze Zahl
>>>
>>> Math.sin( ) — Berechnet einen Sinus
>>>
>>> Math.sqrt( ) — Berechnet eine Quadratwurzel
>>>
>>> Math.SQRT1_2 — Die mathematische Konstante _e_
>>>
>>> Math.SQRT2 — Die mathematische Konstante _e_
>>>
>>> Math.tan( ) — Berechnet einen Tangens
>>>
>>> NaN — Die Eigenschaft Not-a-Number
>>>
>>> Number — Unterstützung für Zahlen
>>>
>>> Number.MAX_VALUE — Der größtmögliche Zahlwert
>>>
>>> Number.MIN_VALUE — Der kleinstmögliche Zahlwert
>>>
>>> Number.NaN — Der Spezialwert Not-a-Number
>>>
>>> Number.NEGATIVE_INFINITY — Minus unendlich
>>>
>>> Number.POSITIVE_INFINITY — Unendlich
>>>
>>> Number.toExponential( ) — Formatiert eine Zahl in Exponentialschreibweise
>>>
>>> Number.toFixed( ) — Formatiert eine Zahl in Festkommaschreibweise
>>>
>>> Number.toLocaleString( ) — Konvertiert eine Zahl in einen String im ortsüblichen Format
>>>
>>> Number.toPrecision( ) — Formatiert die signifikanten Ziffern einer Zahl
>>>
>>> Number.toString( ) — Konvertiert eine Zahl in einen String
>>>
>>> Number.valueOf( ) — Gibt den Wert als elementare Zahl zurück
>>>
>>> Object — Eine Oberklasse, die Features aller JavaScript-Objekte enthält
>>>
>>> Object.constructor — Die Konstruktorfunktion eines Objekts
>>>
>>> Object.hasOwnProperty( ) — Prüft, ob eine Eigenschaft geerbt wurde
>>>
>>> Object.isPrototypeOf( ) — Prüft, ob ein Objekt Prototyp eines anderen ist
>>>
>>> Object.propertyIsEnumerable( ) — Kann eine for/in-Schleife diese Eigenschaft erkennen?
>>>
>>> Object.toLocaleString( ) — Gibt die lokalisierte String-Darstellung eines Objekts zurück
>>>
>>> Object.toString( ) — Definiert die String-Darstellung eines Objekts
>>>
>>> Object.valueOf( ) — Der elementare Wert des angegebenen Objekts
>>>
>>> Packages — Das Wurzel-JavaPackage
>>>
>>> parseFloat( ) — Konvertiert einen String in eine Zahl
>>>
>>> parseInt( ) — Konvertiert einen String in einen Integer
>>>
>>> RangeError — Wird ausgelöst, wenn eine Zahl außerhalb ihres zulässigen Wertebereichs liegt
>>>
>>> ReferenceError — Wird beim Lesen einer nicht-vorhandenen Variablen ausgelöst
>>>
>>> RegExp — Reguläre Ausdrücke für die Mustererkennung
>>>
>>> RegExp.exec( ) — Mustererkennung für allgemeine Zwecke
>>>
>>> RegExp.global — Zeigt an, ob ein regulärer Ausdruck eine globale Erkennung durchführt
>>>
>>> RegExp.ignoreCase — Zeigt an, ob ein regulärer Ausdruck die Groß- und Kleinschreibung ignoriert
>>>
>>> RegExp.lastIndex — Die Position, an der die nächste Erkennung beginnt
>>>
>>> RegExp.source — Der Test des regulären Ausdrucks
>>>
>>> RegExp.test( ) — Testet, ob ein String zu einem Muster passt
>>>
>>> RegExp.toString( ) — Konvertiert einen regulären Ausdruck in einen String
>>>
>>> String — Unterstützung für Strings
>>>
>>> String.charAt( ) — Holt das _n_ te Zeichen aus einem String
>>>
>>> String.charCodeAt( ) — Holt den _n_ ten Zeichencode aus einem String
>>>
>>> String.concat( ) — Verkettet Strings
>>>
>>> String.fromCharCode( ) — Erzeugt einen String aus Zeichencodierungen
>>>
>>> String.indexOf( ) — Durchsucht einen String
>>>
>>> String.lastIndexOf( ) — Durchsucht einen String rückwärts
>>>
>>> String.length — Die Länge eines Strings
>>>
>>> String.localeCompare( ) — Vergleicht einen String mit einem anderen und verwendet dazu die locale-spezifische Reihenfolge
>>>
>>> String.match( ) — Sucht eine oder mehrere Übereinstimmungen mit einem regulären Ausdruck
>>>
>>> String.replace( ) — Ersetzt zu einem regulären Ausdruck passende(n) Teilstring(s)
>>>
>>> String.search( ) — Sucht nach einem regulären Ausdruck
>>>
>>> String.slice( ) — Extrahiert einen Teilstring
>>>
>>> String.split( ) — Zerlegt einen String in ein String-Array
>>>
>>> String.substr( ) — Extrahiert einen Teilstring
>>>
>>> String.substring( ) — Gibt einen Teilstring eines Strings zurück
>>>
>>> String.toLocaleLowerCase( ) — Konvertiert einen String in Kleinschreibung
>>>
>>> String.toLocaleUpperCase( ) — Konvertiert einen String in Großschreibung
>>>
>>> String.toLowerCase( ) — Konvertiert einen String in Kleinschreibung
>>>
>>> String.toString( ) — Gibt den String zurück
>>>
>>> String.toUpperCase( ) — Konvertiert einen String in Großbuchstaben
>>>
>>> String.valueOf( ) — Gibt den String zurück
>>>
>>> SyntaxError — Signalisiert einen Syntaxfehler
>>>
>>> TypeError — Zeigt an, dass ein Wert den falschen Typ hat
>>>
>>> undefined — Der Wert undefined
>>>
>>> unescape( ) — Decodiert einen String mit Escape-Sequenzen
>>>
>>> URIError — Wird von Methoden zum Codieren und Decodieren von URIs ausgelöst
>
> IV. Referenz für clientseitiges JavaScript
>
>> Referenz für clientseitiges JavaScript
>>
>>> Anchor — Das Ziel eines Hypertext-Links
>>>
>>> Anchor.focus( ) — Scrolle, um den Ort des Anchors sichtbar zu machen
>>>
>>> Applet — Ein in eine Webseite eingebettetes Applet
>>>
>>> Attr — Ein Attribut eines Dokumentelements
>>>
>>> Button — Siehe Input
>>>
>>> Canvas — Ein HTML-Element zum skriptgesteuerten Zeichnen
>>>
>>> Canvas.getContext( ) — Liefert einen Context zum Schreiben auf dem Canvas zurück
>>>
>>> CanvasGradient — Ein Farbgradient, der auf einem Canvas verwendet wird
>>>
>>> CanvasGradient.addColorStop( ) — Hinzufügen einer Farbänderung zum Gradienten
>>>
>>> CanvasPattern — Ein auf einem Bild basierendes Muster zur Verwendung auf einem Canvas
>>>
>>> CanvasRenderingContext2D — Das Objekt wird zum Zeichnen auf einem Canvas verwendet
>>>
>>> CanvasRenderingContext2D.arc( ) — Ein Kreisbogen wird mit Hilfe eines Mittelpunktes und eines Radius zum bestehenden Subpfad eines Canvas hinzugefügt.
>>>
>>> CanvasRenderingContext2D.arcTo( ) — Fügt dem momentanen Subpfad einen Kreisbogen unter Verwendung von Tangentenpunkten und eines Radius hinzu
>>>
>>> CanvasRenderingContext2D.beginPath( ) — Beginnt eine neue Gruppe von Subpfaden auf einem Canvas
>>>
>>> CanvasRenderingContext2D.bezierCurveTo( ) — Fügt dem momentanen Subpfad eine dreidimensionale Bézierkurve hinzu
>>>
>>> CanvasRenderingContext2D.clearRect( ) — Löschen eines rechteckigen Bereichs auf dem Canvas
>>>
>>> CanvasRenderingContext2D.clip( ) — Legt den Clippingpfad des Canvas fest
>>>
>>> CanvasRenderingContext2D.closePath( ) — Schließt einen offenen Subpfad
>>>
>>> CanvasRenderingContext2D.createLinearGradient( ) — Einen linearen Farbgradienten erzeugen
>>>
>>> CanvasRenderingContext2D.createPattern( ) — Erzeugt ein Muster aus gekachelten Bildern
>>>
>>> CanvasRenderingContext2D.createRadialGradient( ) — Erzeugt einen radialen Farbgradienten
>>>
>>> CanvasRenderingContext2D.drawImage( ) — Zeichnet ein Bild
>>>
>>> CanvasRenderingContext2D.fill( ) — Füllt den Pfad
>>>
>>> CanvasRenderingContext2D.fillRect( ) — Füllt ein Rechteck
>>>
>>> CanvasRenderingContext2D.globalCompositeOperation — Legt fest, wie Farben auf dem Canvas kombiniert werden
>>>
>>> CanvasRenderingContext2D.lineCap — Gibt an, wie die Linienenden wiedergegeben werden.
>>>
>>> CanvasRenderingContext2D.lineJoin — Legt fest, wie Eckpunkte wiedergeben werden
>>>
>>> CanvasRenderingContext2D.lineTo( ) — Fügt dem momentanen Subpfad eine gerade Linie hinzu
>>>
>>> CanvasRenderingContext2D.miterLimit — Verhältnis zwischen der maximalen Gehrungslänge und der Linienbreite
>>>
>>> CanvasRenderingContext2D.moveTo( ) — Legt die derzeitige Position fest und beginnt mit einem neuen Subpfad.
>>>
>>> CanvasRenderingContext2D.quadraticCurveTo( ) — Fügt dem momentanen Subpfad eine quadratische Bézierkurve hinzu
>>>
>>> CanvasRenderingContext2D.rect( ) — Fügt dem Pfad einen rechteckigen Subpfad hinzu
>>>
>>> CanvasRenderingContext2D.restore( ) — Setzt den Zeichnungsstatus auf die gespeicherten Werte zurück
>>>
>>> CanvasRenderingContext2D.rotate( ) — Dreht das Koordinatensystem des Canvas
>>>
>>> CanvasRenderingContext2D.save( ) — Speichert eine Kopie des momentanen Grafikstatus
>>>
>>> CanvasRenderingContext2D.scale( ) — Skaliert das Benutzerkoordinatensystem des Canvas.
>>>
>>> CanvasRenderingContext2D.stroke( ) — Zeichnet den momentanen Pfad
>>>
>>> CanvasRenderingContext2D.strokeRect( ) — Zeichnet ein Rechteck
>>>
>>> CanvasRenderingContext2D.translate( ) — Verschiebt das Benutzerkoordinatensystem des Canvas
>>>
>>> CDATASection — Ein CDATA-Knoten in einem XML-Dokument
>>>
>>> CharacterData — Gemeinsame Funktionalität für Text- und Kommentarknoten
>>>
>>> CharacterData.appendData( ) — Hängt einen String an einen Text- oder Kommentarknoten an
>>>
>>> CharacterData.deleteData( ) — Löscht Zeichen aus einem Text- oder Kommentarknoten
>>>
>>> CharacterData.insertData( ) — Fügt einen String in einen Text- oder Kommentarknoten ein
>>>
>>> CharacterData.replaceData( ) — Ersetzt Zeichen eines Text- oder Kommentarknotens durch einen String
>>>
>>> CharacterData.substringData( ) — Extrahiert einen Teilstring aus einem Text- oder Kommentarknoten
>>>
>>> Checkbox — Siehe Input
>>>
>>> Comment — Ein HTML- oder XML-Kommentar
>>>
>>> CSS2Properties — Eine Menge von CSS-Attributen und ihren Werten
>>>
>>> CSSRule — Eine Regel in einem CSS-Stylesheet
>>>
>>> CSSStyleSheet — Ein CSS-Stylesheet
>>>
>>> CSSStyleSheet.addRule( ) — IE-spezifische Methode, um eine Regel in ein Stylesheet einzufügen.
>>>
>>> CSSStyleSheet.deleteRule( ) — Löscht eine Regel aus einem Stylesheet
>>>
>>> CSSStyleSheet.insertRule( ) — Fügt eine Regel in ein Stylesheet ein
>>>
>>> CSSStyleSheet.removeRule( ) — IE-spezifische Methode zum Entfernen einer CSS-Regel aus einem Stylesheet.
>>>
>>> Document — Ein HTML- oder XML-Dokument
>>>
>>> Document.addEventListener( ) — siehe Element.addEventListener( )
>>>
>>> Document.attachEvent( ) — siehe Element.attachEvent( )
>>>
>>> Document.createAttribute( ) — Erzeugt einen neuen Attr-Knoten
>>>
>>> Document.createAttributeNS( ) — Erzeugt ein Attr mit einem Namen und Namensraum
>>>
>>> Document.createCDATASection( ) — Erzeugt einen neuen CDATASection-Knoten
>>>
>>> Document.createComment( ) — Erzeugt einen neuen Kommentarknoten
>>>
>>> Document.createDocumentFragment( ) — Erzeugt einen neuen, leeren DocumentFragment-Knoten
>>>
>>> Document.createElement( ) — Erzeugt einen neuen Element-Knoten
>>>
>>> Document.createElementNS( ) — Erzeugt einen neuen Element-Knoten mit Namensraum
>>>
>>> Document.createEvent( ) — Erzeugt ein Event-Objekt
>>>
>>> Document.createExpression( ) — Erzeugt einen XPath-Ausdruck zur späteren Auswertung
>>>
>>> Document.createProcessingInstruction( ) — Erzeugt einen ProcessingInstruction-Knoten
>>>
>>> Document.createRange( ) — Erzeugt ein Range-Objekt
>>>
>>> Document.createTextNode( ) — Erzeugt einen neuen Text-Knoten
>>>
>>> Document.detachEvent( ) — siehe Element.detachEvent( )
>>>
>>> Document.dispatchEvent( ) — siehe Element.dispatchEvent( )
>>>
>>> Document.evaluate( ) — Wertet einen XPath-Ausdruck aus
>>>
>>> Document.getElementById( ) — Sucht ein Element mit der angegebenen eindeutigen ID
>>>
>>> Document.getElementsByTagName( ) — Gibt alle Element-Knoten mit dem angegebenen Namen zurück
>>>
>>> Document.getElementsByTagNameNS( ) — Gibt alle Element-Knoten mit einem angegebenen Namen und Namensraum zurück
>>>
>>> Document.importNode( ) — Kopiert einen Knoten eines anderen Dokuments zur Nutzung in diesem Dokument
>>>
>>> Document.loadXML( ) — Füllt dieses Dokument durch Parsen eines Strings mit XML-Markup
>>>
>>> Document.removeEventListener( ) — Siehe Element.removeEventListener( )
>>>
>>> DocumentFragment — Benachbarte Knoten und ihre Teilbäume
>>>
>>> DocumentType — Die DTD eines XML-Dokuments
>>>
>>> DOMException — Signalisiert Exceptions oder Fehler für Core DOM-Objekte
>>>
>>> DOMImplementation — Methoden, die nicht von einem bestimmten Dokument abhängig sind
>>>
>>> DOMImplementation.createDocument( ) — Erzeugt ein neues Document und das angegebene Wurzelelement
>>>
>>> DOMImplementation.createDocumentType( ) — Erzeugt einen DocumentType-Knoten
>>>
>>> DOMImplementation.hasFeature( ) — Stellt fest, ob die Implementierung ein bestimmtes Feature unterstützt
>>>
>>> DOMParser — Parst XML-Markup, um ein Document zu erzeugen
>>>
>>> DOMParser.parseFromString( ) — Parst XML-Markup
>>>
>>> Element — Ein HTML- oder XML-Element
>>>
>>> Element.addEventListener( ) — Registriert einen Event-Handler
>>>
>>> Element.attachEvent( ) — Registriert einen Event-Handler
>>>
>>> Element.detachEvent( ) — Löscht einen Event-Listener
>>>
>>> Element.dispatchEvent( ) — Übermittelt ein synthetisches Event an diesen Knoten
>>>
>>> Element.getAttribute( ) — Gibt den Stringwert eines angegebenen Attributs zurück
>>>
>>> Element.getAttributeNode( ) — Gibt den Attr-Knoten für das angegebene Attribut zurück
>>>
>>> Element.getAttributeNodeNS( ) — Gibt den Attr-Knoten für ein Attribut mit einem Namensraum zurück
>>>
>>> Element.getAttributeNS( ) — Holt den Wert eines Attributs, das Namensräume verwendet
>>>
>>> Element.getElementsByTagName( ) — Sucht Nachfahren-Elemente mit einem angegebenen Tag-Namen
>>>
>>> Element.getElementsByTagNameNS( ) — Gibt Nachfahren-Elemente mit dem angegebenen Namen und Namensraum zurück
>>>
>>> Element.hasAttribute( ) — Stellt fest, ob dieses Element ein angegebenes Attribut hat
>>>
>>> Element.hasAttributeNS( ) — Stellt fest, ob dieses Element ein angegebenes Attribut hat
>>>
>>> Element.removeAttribute( ) — Löscht ein angegebenes Elementattribut
>>>
>>> Element.removeAttributeNode( ) — Löscht einen Attr-Knoten aus einem Element
>>>
>>> Element.removeAttributeNS( ) — Löscht ein durch Name und Namensraum spezifiziertes Attribut
>>>
>>> Element.removeEventListener( ) — Löscht einen Event-Listener
>>>
>>> Element.setAttribute( ) — Erzeugt oder ändert ein Elementattribut
>>>
>>> Element.setAttributeNode( ) — Fügt einem Element einen neuen Attr-Knoten hinzu
>>>
>>> Element.setAttributeNodeNS( ) — Fügt einem Element einen Namensraum-Attr-Knoten hinzu
>>>
>>> Element.setAttributeNS( ) — Erzeugt oder ändert ein Attribut mit einem Namensraum
>>>
>>> Event — Informationen über ein Event
>>>
>>> Event.initEvent( ) — Initialisiert die Eigenschaften eines neuen Events
>>>
>>> Event.preventDefault( ) — Hebt die Standardaktion eines Events auf
>>>
>>> Event.stopPropagation( ) — Reicht ein Event nicht mehr weiter
>>>
>>> ExternalInterface — Ein bidirektionales Interface zu Flash
>>>
>>> ExternalInterface.addCallback( ) — Exponiert eine ActionScript-Methode zur Ausführung von JavaScript aus
>>>
>>> ExternalInterface.call( ) — Ruft eine JavaScript-Funktion aus ActionScript heraus auf
>>>
>>> FileUpload — Siehe Input
>>>
>>> FlashPlayer — Plug-in für Flash-Movies
>>>
>>> FlashPlayer.GetVariable( ) — Gibt einen in einem Flash-Movie definierten Wert zurück
>>>
>>> FlashPlayer.GotoFrame( ) — Springt zum angegebenen Frame eines Movies
>>>
>>> FlashPlayer.IsPlaying( ) — Überprüft, ob ein Movie läuft
>>>
>>> FlashPlayer.LoadMovie( ) — Lädt ein Hilfs-Movie
>>>
>>> FlashPlayer.Pan( ) — Verschiebt den Viewport des Movies
>>>
>>> FlashPlayer.PercentLoaded( ) — Zeigt an, wie viel des Movies bereits geladen wurde
>>>
>>> FlashPlayer.Play( ) — Spielt ein Movie ab
>>>
>>> FlashPlayer.Rewind( ) — Spult das Movie auf sein erstes Frame zurück
>>>
>>> FlashPlayer.SetVariable( ) — Setzt eine von einem Flash-Movie definierte Variable
>>>
>>> FlashPlayer.SetZoomRect( ) — Legt den Viewport eines Movies fest
>>>
>>> FlashPlayer.StopPlay( ) — Hält das Movie an
>>>
>>> FlashPlayer.TotalFrames( ) — Gibt die Länge des Movies zurück, gemessen in Frames
>>>
>>> FlashPlayer.Zoom( ) — Heranzoomen oder wegzoomen
>>>
>>> Form — Ein <form>-Element in einem HTML-Dokument
>>>
>>> Form.elements[] — Die Eingabeelemente eines Formulars
>>>
>>> Form.onreset — Event-Handler, der zum Zurücksetzen des Formulars aufgerufen wird
>>>
>>> Form.onsubmit — Der Event-Handler, der beim Zurücksetzen eines Formulars aufgerufen wird
>>>
>>> Form.reset( ) — Setzt die Elemente eines Formulars auf ihre Standardwerte zurück
>>>
>>> Form.submit( ) — Überträgt Formulardaten an einen Webserver
>>>
>>> Frame — Ein <frame> in einem HTML-Dokument
>>>
>>> Hidden — Siehe Input
>>>
>>> History — Die URL-History des Browsers
>>>
>>> History.back( ) — Geht zur vorigen URL zurück
>>>
>>> History.forward( ) — Besucht die nächste URL
>>>
>>> History.go( ) — Besucht eine URL erneut
>>>
>>> HTMLCollection — Array von HTML-Elementen, auf die nach Position oder Namen zugegriffen wird
>>>
>>> HTMLCollection.item( ) — Holt ein Element anhand seiner Position
>>>
>>> HTMLCollection.namedItem( ) — Holt ein Element anhand seines Namens
>>>
>>> HTMLDocument — Die Wurzel eines HTML-Dokumentbaums
>>>
>>> HTMLDocument.all[] — Alle HTML-Elemente in einem Dokument
>>>
>>> HTMLDocument.close( ) — Schließt ein offenes Dokument und zeigt es an
>>>
>>> HTMLDocument.cookie — Die Cookies des Dokuments
>>>
>>> HTMLDocument.domain — Die Sicherheits-Domain eines Dokuments
>>>
>>> HTMLDocument.getElementsByName( ) — Sucht Elemente mit dem angegebenen name-Attribut
>>>
>>> HTMLDocument.open( ) — Beginnt ein neues Dokument und löscht das aktuelle
>>>
>>> HTMLDocument.write( ) — Hängt HTML-Text an ein geöffnetes Dokument an
>>>
>>> HTMLDocument.writeln( ) — Hängt HTML-Text und ein Newline-Zeichen an ein offenes Dokument an
>>>
>>> HTMLElement — Ein Element in einem HTML-Dokument
>>>
>>> HTMLElement.onclick — Event-Handler, der aufgerufen wird, wenn der Benutzer auf ein Element klickt
>>>
>>> HTMLElement.ondblclick — Dieser Event-Handler wird aufgerufen, wenn der Benutzer auf ein Element doppelklickt
>>>
>>> HTMLElement.onkeydown — Dieser Event-Handler wird aufgerufen, wenn der Benutzer eine Taste drückt
>>>
>>> HTMLElement.onkeypress — Dieser Event-Handler wird aufgerufen, wenn der Benutzer eine Taste drückt
>>>
>>> HTMLElement.onkeyup — Dieser Event-Handler wird aufgerufen, wenn der Benutzer eine Taste loslässt
>>>
>>> HTMLElement.onmousedown — Dieser Event-Handler wird aufgerufen, wenn der Benutzer eine Maustaste drückt
>>>
>>> HTMLElement.onmousemove — Dieser Event-Handler wird aufgerufen, wenn sich die Maus innerhalb eines Elements bewegt
>>>
>>> HTMLElement.onmouseout — Dieser Event-Handler wird aufgerufen, wenn sich die Maus von einem Element wegbewegt
>>>
>>> HTMLElement.onmouseover — Dieser Event-Handler wird aufgerufen, wenn sich die Maus über ein Element bewegt
>>>
>>> HTMLElement.onmouseup — Dieser Event-Handler wird aufgerufen, wenn der Benutzer eine Maustaste loslässt
>>>
>>> HTMLElement.scrollIntoView( ) — Macht ein Element sichtbar
>>>
>>> IFrame — Ein <iframe> in einem HTML-Dokument
>>>
>>> Image — Ein Bild in einem HTML-Dokument
>>>
>>> Image.onabort — Dieser Event-Handler wird aufgerufen, wenn der Benutzer das Laden des Bildes abbricht
>>>
>>> Image.onerror — Dieser Event-Handler wird aufgerufen, wenn beim Laden eines Bildes ein Fehler auftritt
>>>
>>> Image.onload — Dieser Event-Handler wird aufgerufen, wenn ein Bild fertig geladen wurde
>>>
>>> Input — Ein Eingabeelement in einem HTML-Formular
>>>
>>> Input.blur( ) — Entzieht einem Formularelement den Eingabefokus
>>>
>>> Input.click( ) — Simuliert einen Mausklick auf einem Formularelement
>>>
>>> Input.focus( ) — Gibt einem Formularelement den Tastaturfokus
>>>
>>> Input.onblur — Dieser Handler wird aufgerufen, wenn ein Element den Fokus verliert
>>>
>>> Input.onchange — Dieser Event-Handler wird aufgerufen, wenn sich der Wert eines Formularelements ändert
>>>
>>> Input.onclick — Dieser Event-Handler wird aufgerufen, wenn ein Formularelement angeklickt wird
>>>
>>> Input.onfocus — Dieser Event-Handler wird aufgerufen, wenn das Formularelement den Fokus erhält
>>>
>>> Input.select( ) — Wählt einen Text in einem Formularelement aus
>>>
>>> JavaArray, JavaClass, JavaObject, JavaPackage — Siehe Teil III
>>>
>>> JSObject — Java-Darstellung eines JavaScript-Objekts
>>>
>>> JSObject.call( ) — Ruft eine Methode eines JavaScript-Objekts auf
>>>
>>> JSObject.eval( ) — Wertet einen String mit JavaScript-Code aus
>>>
>>> JSObject.getMember( ) — Liest eine Eigenschaft eines JavaScript-Objekts
>>>
>>> JSObject.getSlot( ) — Liest ein Array-Element eines JavaScript-Objekts
>>>
>>> JSObject.getWindow( ) — Gibt ein erstes JSObject eines Browserfensters zurück
>>>
>>> JSObject.removeMember( ) — Löscht eine Eigenschaft eines JavaScript-Objekts
>>>
>>> JSObject.setMember( ) — Setzt eine Eigenschaft eines JavaScript-Objekts
>>>
>>> JSObject.setSlot( ) — Setzt ein Array-Element eines JavaScript-Objekts
>>>
>>> JSObject.toString( ) — Gibt den Stringwert eines JavaScript-Objekts zurück
>>>
>>> KeyEvent — Einzelheiten über ein Tastatur-Event
>>>
>>> Layer — Eine veraltete Netscape-API
>>>
>>> Link — Ein Hyperlink oder Anker in einem HTML-Dokument
>>>
>>> Link.blur( ) — Entzieht einem Hyperlink den Tastaturfokus
>>>
>>> Link.focus( ) — Lässt einen Link sichtbar werden und gibt ihm den Tastaturfokus
>>>
>>> Link.onclick — Dieser Event-Handler wird aufgerufen, wenn ein Link angeklickt wird
>>>
>>> Link.onmouseover — Dieser Event-Handler wird aufgerufen, wenn die Maus einen Link verlässt
>>>
>>> Link.onmouseover — Dieser Event-Handler wird aufgerufen, wenn sich die Maus über einen Link bewegt
>>>
>>> Location — Repräsentiert und steuert die vom Browser angezeigte Webadresse
>>>
>>> Location.reload( ) — Lädt das aktuelle Dokument neu
>>>
>>> Location.replace( ) — Ersetzt ein angezeigtes Dokument durch ein anderes
>>>
>>> MimeType — Repräsentiert einen MIME-Datentyp
>>>
>>> MouseEvent — Einzelheiten über ein Maus-Event
>>>
>>> MouseEvent.initMouseEvent( ) — Initialisiert die Eigenschaften eines MouseEvent-Objekts
>>>
>>> Navigator — Informationen über den verwendeten Browser
>>>
>>> Navigator.javaEnabled( ) — Testet, ob Java zur Verfügung steht
>>>
>>> Node — Ein Knoten in einem Dokumentenbaum
>>>
>>> Node.appendChild( ) — Fügt einen Knoten als letztes Kind dieses Knotens ein
>>>
>>> Node.cloneNode( ) — Dupliziert einen Knoten und optional auch alle seine Nachfahren
>>>
>>> Node.hasAttributes( ) — Stellt fest, ob ein Knoten Attribute hat
>>>
>>> Node.hasChildNodes( ) — Stellt fest, ob ein Knoten Kinder hat
>>>
>>> Node.insertBefore( ) — Fügt einen Knoten vor dem angegebenen Knoten in den Dokumentenbaum ein
>>>
>>> Node.isSupported( ) — Stellt fest, ob ein Knoten ein Feature unterstützt
>>>
>>> Node.normalize( ) — Verschmilzt benachbarte und löscht leere Text-Knoten
>>>
>>> Node.removeChild( ) — Löscht (und liefert) das angegebene Kind dieses Knotens
>>>
>>> Node.replaceChild( ) — Ersetzt einen Kindknoten durch einen neuen
>>>
>>> Node.selectNodes( ) — Wählt Knoten über eine XPath-Query aus
>>>
>>> Node.selectSingleNode( ) — Finde einen Knoten, der mit einer XPath-Abfrage übereinstimmt
>>>
>>> Node.transformNode( ) — Transformiert mit XSLT einen Knoten in einen String
>>>
>>> Node.transformNodeToObject( ) — Transformiert mit XSLT einen Knoten in ein Dokument
>>>
>>> NodeList — Ein schreibgeschütztes Knoten-Array
>>>
>>> NodeList.item( ) — Holt ein Element einer NodeList
>>>
>>> Option — Eine Option in einem Select-Element
>>>
>>> Packages — Siehe Packages in Teil III
>>>
>>> Password — Siehe Input
>>>
>>> Plugin — Beschreibt ein installiertes Plug-in
>>>
>>> ProcessingInstruction — Eine Verarbeitungsanweisung in einem XML-Dokument
>>>
>>> Radio — Siehe Input
>>>
>>> Range — Repräsentiert einen zusammenhängenden Dokumentbereich
>>>
>>> Range.cloneContents( ) — Kopiert Bereichsinhalte in ein DocumentFragment
>>>
>>> Range.cloneRange( ) — Kopiert diesen Bereich
>>>
>>> Range.collapse( ) — Macht beide Grenzpunkte gleich
>>>
>>> Range.compareBoundaryPoints( ) — Vergleicht die Positionen zweier Bereiche
>>>
>>> Range.deleteContents( ) — Löscht einen Dokumentteil
>>>
>>> Range.detach( ) — Gibt ein Range-Objekt frei
>>>
>>> Range.extractContents( ) — Löscht den Dokumentinhalt und gibt ihn in einem DocumentFragment zurück
>>>
>>> Range.insertNode( ) — Fügt am Anfang eines Bereichs einen Knoten ein
>>>
>>> Range.selectNode( ) — Setzt Bereichsgrenzen auf einen Knoten
>>>
>>> Range.selectNodeContents( ) — Setzt die Bereichsgrenzen auf die Kinder eines Knotens
>>>
>>> Range.setEnd( ) — Setzt den Endpunkt eines Bereichs
>>>
>>> Range.setEndAfter( ) — Lässt einen Bereich hinter dem angegebenen Knoten enden
>>>
>>> Range.setEndBefore( ) — Lässt einen Bereich vor dem angegebenen Knoten enden
>>>
>>> Range.setStart( ) — Setzt den Anfangspunkt eines Bereichs
>>>
>>> Range.setStartAfter( ) — Beginnt einen Bereich hinter dem angegebenen Knoten
>>>
>>> Range.setStartBefore( ) — Lässt einen Bereich vor dem angegebenen Knoten beginnen
>>>
>>> Range.surroundContents( ) — Umgibt den Bereichsinhalt mit dem angegebenen Knoten
>>>
>>> Range.toString( ) — Holt den Bereichsinhalt als String mit einfachem Text
>>>
>>> RangeException — Signalisiert eine bereichsspezifische Exception
>>>
>>> Reset — Siehe Input
>>>
>>> Screen — Gibt Informationen über den Bildschirm
>>>
>>> Select — Eine grafische Auswahlliste
>>>
>>> Select.add( ) — Fügt ein <option>-Element ein
>>>
>>> Select.blur( ) — Entzieht diesem Element den Tastaturfokus
>>>
>>> Select.focus( ) — Gibt diesem Element den Tastaturfokus
>>>
>>> Select.onchange — Dieser Event-Handler wird aufgerufen, wenn etwas anderes ausgewählt wird
>>>
>>> Select.options[] — Die in einem Select-Objekt verfügbaren Auswahlen
>>>
>>> Select.remove( ) — Entfernt eine <option>
>>>
>>> Style — Siehe CSS2Properties
>>>
>>> Submit — Siehe Input
>>>
>>> Table — Eine <table> in einem HTML-Dokument
>>>
>>> Table.createCaption( ) — Holt oder erzeugt eine <caption>
>>>
>>> Table.createTFoot( ) — Holt oder erzeugt ein <tfoot>
>>>
>>> Table.createTHead( ) — Holt oder erzeugt einen <thead>
>>>
>>> Table.deleteCaption( ) — Löscht die <caption> einer Tabelle
>>>
>>> Table.deleteRow( ) — Löscht eine Zeile aus einer Tabelle
>>>
>>> Table.deleteTFoot( ) — Löscht den <tfoot> einer Tabelle
>>>
>>> Table.deleteTHead( ) — Löscht den <thead> einer Tabelle
>>>
>>> Table.insertRow( ) — Fügt der Tabelle eine neue, leere Zeile hinzu
>>>
>>> TableCell — Eine <td>\- oder <th>-Zelle in einer HTML-Tabelle
>>>
>>> TableRow — Ein <tr>-Element in einer HTML-Tabelle
>>>
>>> TableRow.deleteCell( ) — Löscht eine Zelle einer Tabellenzeile
>>>
>>> TableRow.insertCell( ) — Fügt ein neues, leeres <td>-Element in eine Tabellenzeile ein
>>>
>>> TableSection — Ein Header-, Footer- oder Body-Abschnitt einer Tabelle
>>>
>>> TableSection.deleteRow( ) — Löscht eine Zeile in einem Tabellenabschnitt
>>>
>>> TableSection.insertRow( ) — Fügt eine neue, leere Zeile in diesen Tabellenabschnitt ein
>>>
>>> Text — Ein Fließtext in einem HTML- oder XML-Dokument
>>>
>>> Text.splitText( ) — Spaltet einen Text-Knoten in zwei auf
>>>
>>> Textarea — Ein mehrzeiliger Texteingabebereich
>>>
>>> Textarea.blur( ) — Entzieht diesem Element den Tastaturfokus
>>>
>>> Textarea.focus( ) — Gibt diesem Element den Tastaturfokus
>>>
>>> Textarea.onchange — Dieser Event-Handler wird aufgerufen, wenn sich der Eingabewert ändert
>>>
>>> Textarea.select( ) — Markiert den Text in diesem Element
>>>
>>> TextField — Siehe Input
>>>
>>> UIEvent — Einzelheiten über Events der Benutzeroberfläche
>>>
>>> UIEvent.initUIEvent( ) — Initialisiert die Eigenschaften eines UIEvent-Objekts
>>>
>>> Window — Ein Browserfenster oder Frame
>>>
>>> Window.addEventListener( ) — Siehe Element.addEventListener( )
>>>
>>> Window.alert( ) — Zeigt eine Nachricht in einem Dialogfeld an
>>>
>>> Window.attachEvent( ) — Siehe Element.attachEvent( )
>>>
>>> Window.blur( ) — Entzieht einem Toplevel-Fenster den Tastaturfokus
>>>
>>> Window.clearInterval( ) — Hält eine regelmäßige Code-Ausführung an
>>>
>>> Window.clearTimeout( ) — Löscht eine aufgeschobene Programmausführung
>>>
>>> Window.close( ) — Schließt ein Browserfenster
>>>
>>> Window.confirm( ) — Stellt eine Ja/Nein-Frage
>>>
>>> Window.defaultStatus — Der Standardtext auf der Statuszeile
>>>
>>> Window.detachEvent( ) — Siehe Element.detachEvent( )
>>>
>>> Window.focus( ) — Gibt einem Fenster den Tastaturfokus
>>>
>>> Window.getComputedStyle( ) — Ruft die CSS-Styles ab, mit denen ein Element wiedergegeben wird
>>>
>>> Window.moveBy( ) — Verschiebt ein Fenster an eine relative Position
>>>
>>> Window.moveTo( ) — Verschiebt ein Fenster an eine absolute Position
>>>
>>> Window.onblur — Dieser Event-Handler wird aufgerufen, wenn das Fenster den Tastaturfokus verliert
>>>
>>> Window.onerror — Dieser Event-Handler wird aufgerufen, wenn ein JavaScript-Fehler auftritt
>>>
>>> Window.onfocus — Dieser Event-Handler wird aufgerufen, wenn ein Fenster den Fokus erhält
>>>
>>> Window.onload — Dieser Event-Handler wird aufgerufen, wenn ein Dokument fertig geladen wurde
>>>
>>> Window.onresize — Dieser Event-Handler wird aufgerufen, wenn die Größe eines Fensters angepasst wird
>>>
>>> Window.onunload — Dieser Handler wird aufgerufen, wenn der Browser eine Seite verlässt
>>>
>>> Window.open( ) — Öffnet ein neues Browserfenster oder sucht ein angegebenes Fenster
>>>
>>> Window.print( ) — Druckt das Dokument
>>>
>>> Window.prompt( ) — Benutzereingabe über ein Dialogfeld beziehen
>>>
>>> Window.removeEventListener( ) — Siehe Element.removeEventListener( )
>>>
>>> Window.resizeBy( ) — Passt die Größe eines Fensters um einen relativen Betrag an
>>>
>>> Window.resizeTo( ) — Passt die Größe eines Fensters an
>>>
>>> Window.scrollBy( ) — Scrollt das Dokument um einen relativen Betrag
>>>
>>> Window.scrollTo( ) — Scrollt das Dokument
>>>
>>> Window.setInterval( ) — Führt einen spezifizierten Code regelmäßig aus
>>>
>>> Window.setTimeout( ) — Verschiebt die Code-Ausführung
>>>
>>> Window.status — Gibt eine vorübergehende Statuszeilenmeldung an
>>>
>>> XMLHttpRequest — Ein HTTP-Request und die dazugehörige Response
>>>
>>> XMLHttpRequest.abort( ) — Einen HTTP-Request abbrechen
>>>
>>> XMLHttpRequest.getAllResponseHeaders( ) — Gibt nicht geparste HTTP-Response-Header zurück
>>>
>>> XMLHttpRequest.getResponseHeader( ) — Holt den Wert eines angegebenen HTTP-Response-Headers
>>>
>>> XMLHttpRequest.onreadystatechange — Diese Event-Handler-Funktion wird aufgerufen, wenn sich readyState ändert
>>>
>>> XMLHttpRequest.open( ) — Initialisiert HTTP-Request-Parameter
>>>
>>> XMLHttpRequest.send( ) — Senden eines HTTP-Requests
>>>
>>> XMLHttpRequest.setRequestHeader( ) — Fügt einen HTTP-Request-Header zum Request hinzu
>>>
>>> XMLSerializer — Serialisiert XML-Dokumente und -Knoten
>>>
>>> XMLSerializer.serializeToString( ) — Konvertiert ein XML-Dokument oder einen XML-Knoten in einen String
>>>
>>> XPathExpression — Eine kompilierte XPath-Abfrage
>>>
>>> XPathExpression.evaluate( ) — Wertet eine kompilierte XPath-Abfrage aus
>>>
>>> XPathResult — Das Ergebnis einer XPath-Abfrage
>>>
>>> XPathResult.iterateNext( ) — Gibt den nächsten Knoten zurück, der auf eine XPath-Abfrage passt
>>>
>>> XPathResult.snapshotItem( ) — Gibt einen Knoten zurück, der auf eine XPath-Abfrage passt
>>>
>>> XSLTProcessor — Transformiert XML mit XSLT-Stylesheets
>>>
>>> XSLTProcessor.clearParameters( ) — Löscht alle Stylesheet-Parameterwerte
>>>
>>> XSLTProcessor.getParameter( ) — Gibt den Wert eines benannten Parameters zurück
>>>
>>> XSLTProcessor.importStylesheet( ) — Gibt ein XSLT-Stylesheet für Transformationen an
>>>
>>> XSLTProcessor.removeParameter( ) — Löscht einen Parameterwert
>>>
>>> XSLTProcessor.reset( ) — Setzt einen XSLTProcessor auf seinen Ausgangsstatus zurück
>>>
>>> XSLTProcessor.setParameter( ) — Setzt einen Stylesheet-Parameter
>>>
>>> XSLTProcessor.transformToDocument( ) — Transformiert einen Knoten oder ein Dokument in ein neues Dokument
>>>
>>> XSLTProcessor.transformToFragment( ) — Transformiert einen Knoten oder ein Dokument in ein DocumentFragment

# Java Script: Das umfassende Referenzwerk

### David Flanagan

### Lars Schulten

### Ulrich Speidel

Copyright © 2007 O'Reilly Verlag GmbH & Co. KG

3 . Auflage

Die Informationen in diesem Buch wurden mit größter Sorgfalt erarbeitet. Dennoch können Fehler nicht vollständig ausgeschlossen werden. Verlag, Autoren und Übersetzer übernehmen keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene Fehler und deren Folgen.

Alle Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt und sind möglicherweise eingetragene Warenzeichen. Der Verlag richtet sich im Wesentlichen nach den Schreibweisen der Hersteller. Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

Satz: Tim Mergemeier, reemers publishing services gmbh, Krefeld; www.reemers.de

Kommentare und Fragen können Sie gerne an uns richten:
O'Reilly Verlag
Balthasarstr. 81
50670 Köln
Tel: 0221/9731600
Fax: 0221/9731608
E-Mail: kommentar@oreilly.de

Lektorat: Inken Kiupel

Korrektorat: Friederike Daenecke

Produktion: Andrea Miß

ISBN des gedruckten Buches: 9783897214910

* * *

# dedication

Dieses Buch ist allen gewidmet, die Frieden lehren und Gewalt Widerstand leisten.

# Vorwort

Nachdem die vierte Auflage von _JavaScript: Das umfassende Handbuch_ veröffentlicht wurde, wurde das Document Object Model – die grundlegende API für die clientseitige JavaScript?-Programmierung – zu großen Teilen, wenn auch nicht vollständig in Webbrowsern implementiert. Das bedeutete, dass Webentwickler eine stabile, reife Sprache (JavaScript 1.5) und eine gemeinsame API für die Manipulation von Webseiten auf dem Client hatten. Es folgten einige Jahre der Stabilität.

Aber die Dinge haben begonnen, wieder interessant zu werden. Entwickler verwenden JavaScript jetzt, um HTTP zu skripten, um XML-Daten zu manipulieren und sogar um dynamische Grafiken in Webbrowsern zu zeichnen. Viele JavaScript-Entwickler haben auch begonnen, längere Programme zu schreiben und ausgefeiltere Programmiertechniken zu verwenden. Diese aktualisierte Auflage wurde vollständig für die neue Welt von Ajax und Web 2.0-Technologien überarbeitet.

# Was in dieser Auflage neu ist

In Teil I, Der JavaScript-Sprachkern, wurde das Kapitel zu Funktionen (Kapitel 8) erweitert. Ein Schwerpunkt liegt dabei auf eingebetteten Funktionen und Closures. Das Material zur Definition eigener Klassen wurde ebenfalls erweitert und in ein eigenes Kapitel (Kapitel 9) verschoben. Kapitel 10 ist ein weiteres neues Kapitel, das Namensräume behandelt, die grundlegend für das Schreiben von modularem, wiederverwendbarem Code sind. Schließlich zeigt Kapitel 12, wie man JavaScript einsetzt, um tatsächlich Java zu skripten. Es zeigt, wie man einen JavaScript-Interpreter in eine Java 6-Anwendung einbettet und wie man JavaScript verwendet, um Java-Objekte zu erstellen und auf diesen Objekten Methoden aufzurufen.

In Teil II, Clientseitiges JavaScript, wurde die Behandlung des alten Document Object Models (Level 0) mit der Behandlung des W3C-Standard-DOMs verschmolzen. Weil das DOM jetzt universell implementiert ist, gibt es keinen Grund für zwei separate Kapitelz zur Manipulation von Dokumenten mehr. Die größte Veränderung in Teil II ist aber die Menge an neuem Material:

  * Kapitel 19, _Cookies und Persistenz auf der Clientseite_ , hat die Behandlung von Cookies aktualisiert und schließt die Behandlung anderer Techniken für clientseitige Persistenz ein.

  * Kapitel 20, _HTTP skripten_ , erklärt, wie man unter Verwendung des mächtigen XMLHttpRequest-Objekts geskriptete HTTP-Anfragen macht, die die Technologie für Ajax-artige Webanwendungen sind.

  * Kapitel 21, _JavaScript und XML_ , zeigt, wie man JavaScript verwendet, um XML-Dokumentinhalte zu erzeugen, zu laden, zu parsen, zu transformieren, abzufragen, zu serialisieren und herauszuziehen. Es führt außerdem in die E4X-Erweiterung der Kern-JavaScript-Sprache ein.

  * Kapitel 22, _Skriptgesteuerte Grafiken auf der Clientseite_ , erklärt die Grafikfähigkeiten von JavaScript. Es behandelt einfache Image-Rollover und Animationen, erklärt aber auch fortgeschrittene geskriptete Grafiken mit dem brandneuen `<canvas>`-Tag. Es zeigt auch andere Mittel, um dynamische, geskriptete clientseitige Grafiken mit SVG, VML, dem Flash-Plug-in und dem Java-Plug-in zu erzeugen.

  * In Kapitel 23, _Java-Applets und Flash-Movies skripten_ , wurde die Behandlung des Java-Plug-ins um eine Behandlung des Flash-Plug-ins erweitert. Es erklärt jetzt, wie man Flash-Filme und Java-Applets skriptet.

Teil III, der Referenzteil für die Kern-JavaScript-API, wurde im Vergleich zur vorangehenden Auflage nur wenig geändert, weil diese API stabil geblieben ist. Wenn Sie die vorangegangene Auflage verwendet haben, wird Ihnen dieser Teil des Buchs angenehm vertraut vorkommen.

Die größte Änderung am Referenzmaterial ist, dass die Dokumentation der DOM API, die zuvor in einen eigenen Teil ausgelagert war, vollständig in Teil IV, die Referenz zu clientseitigem JavaScript, integriert wurde. Jetzt muss nur noch in einer clientseitigen Referenz nachgeschlagen werden. Es ist nicht mehr notwendig, das Document-Objekt in einem Referenzabschnitt nachzuschlagen und das HTMLDocument-Objekt dann in dem anderen. Referenzmaterial zu den DOM-Interfaces, die in Webbrowsern nie (sonderlich verbreitet) implementiert waren, wurde weggelassen. Das NodeIterator-Interface ist in Webbrowsern einfach nicht verfügbar und verstopft jetzt nicht mehr sinnlos dieses Buch. Die Perspektive wurde außerdem von den schrecklich formalen Interfaces, die von der DOM-Spezifikation definiert werden, zu den JavaScript-Objekten verschoben, die diese Interfaces tatsächlich implementieren. Beispielsweise ist `getComputedStyle( )` jetzt als Methode des Window-Objekts dokumentiert, wo man sie auch erwarten würde, nicht mehr als Methode des Interfaces AbstractView. Für clientseitige JavaScript-Programmierer gibt es keinen Grund, sich über AbstractView Gedanken zu machen, deswegen wurde dieses Interface einfach aus der Referenz entfernt. All diese Änderungen führen zu einem einfacheren, leichter zu verwendenden clientseitigen Referenzteil.

# Wie man dieses Buch verwendet

Kapitel 1 bietet eine Einführung in JavaScript. Der Rest des Buchs ist in vier Teile eingeteilt. Teil I, der unmittelbar auf dieses Kapitel folgt, dokumentiert den Sprachkern von JavaScript. Die Kapitel 2 bis 6 behandeln etwas langweiliges, aber erforderliches Material. Diese Kapitel enthalten die grundlegenden Informationen, die man verstehen muss, wenn man eine neue Programmiersprache lernt:

  * Kapitel 2, _Lexikalische Struktur_ , erklärt die Grundstruktur der Sprache.

  * Kapitel 3, _Datentypen und Werte_ , dokumentiert die Datentypen, die von JavaScript unterstützt werden.

  * Kapitel 4, _Variablen_ , behandelt Variablen, Geltungsbereiche von Variablen und verwandte Themen.

  * Kapitel 5, _Ausdrücke und Operatoren_ , erklärt Ausdrücke in JavaScript und dokumentiert alle Operatoren, die von JavaScript unterstützt werden. Weil JavaScript an Java angelehnt ist, das seinerseits an C und C++ angelehnt ist, können erfahrene C-, C++- oder Java-Programmierer große Teile von diesem Kapitel überspringen.

  * Kapitel 6, _Anweisungen_ , beschreibt die Syntax und Verwendung aller JavaScript-Anweisungen. Wieder können erfahrene C-, C++- und Java-Programmierer einiges, aber nicht alles, in diesem Kapitel überspringen.

Die nächsten sechs Kapitel von Teil I werden interessanter. Sie behandeln immer noch den Kern der Sprache JavaScript, dokumentieren aber Teile der Sprache, mit denen man auch dann noch nicht vertraut ist, wenn man bereits C oder Java kennt. Diese Kapitel müssen Sie sorgfältig studieren, wenn Sie JavaScript wirklich verstehen wollen.

  * Kapitel 7, _Objekte und Arrays_ , erklärt Objekte und Arrays in JavaScript.

  * Kapitel 8, _Funktionen_ , zeigt, wie in JavaScript Funktionen definiert, aufgerufen und manipuliert werden. Es schließt außerdem fortgeschrittenes Material zu Closures ein.

  * Kapitel 9, _Klassen, Konstruktoren und Prototypen_ , behandelt die OO-Programmierung in JavaScript, erklärt, wie man Konstruktordefinitionen für neue Klassen von Objekten definiert und wie die prototypbasierte Vererbung von JavaScript funktioniert. Dieses Kapitel zeigt auch, wie man traditionelle klassenbasierte OO-Idiome in JavaScript simuliert.

  * Kapitel 10, _Module und Namensräume_ , zeigt, wie JavaScript-Objekte Namensräume definieren, und erklärt Programmierpraktiken, die Ihre Module mit JavaScript-Code vor Namensraumkollisionen schützen können.

  * Kapitel 11, _Mustervergleich mit regulären Ausdrücken_ , erklärt, wie man in JavaScript reguläre Ausdrücke verwendet, um Mustervergleichs- und Suchen/Ersetzen-Operationen durchzuführen.

  * Kapitel 12, _Java skripten_ , zeigt, wie man einen JavaScript-Interpreter in eine Java-Anwendung einbettet, und erklärt, wie JavaScript-Programme, die in so einer Anwendung laufen, Java-Objekte skripten können. Dieses Kapitel ist nur für Java-Programmierer interessant.

Teil II erklärt JavaScript in Webbrowsern. Die ersten sechs Kapitel behandeln Kern-Features von clientseitigem JavaScript:

  * Kapitel 13, _JavaScript in Webbrowsern_ , erklärt die Integration von JavaScript in Webbrowser. Es betrachtet den Webbrowser als eine Programmierumgebung und erklärt die verschiedenen Weisen, wie JavaScript in Webseiten integriert wird, damit es auf der Clientseite ausgeführt wird.

  * Kapitel 14, _Browserfenster skripten_ , dokumentiert das zentrale Objekt von clientseitigem JavaScript – das Window-Objekt – und erklärt, wie Sie dieses Objekt verwenden können, um Webbrowser-Fenster zu steuern.

  * Kapitel 15, _Dokumente skripten_ , behandelt das Document-Objekt und erklärt, wie JavaScript den Inhalt skripten kann, der in einem Webbrowser-Fenster angezeigt wird. Dies ist das wichtigste Kapitel in Teil II.

  * Kapitel 16, _Cascading Style Sheets und dynamisches HTML_ , erklärt, wie JavaScript und CSS-Stylesheets interagieren. Es demonstriert, wie JavaScript den Style, das Erscheinungsbild und die Position der Elemente in einem HTML-Dokument manipulieren kann, um die visuellen Effekte hervorzubringen, die als DHTML bekannt sind.

  * Kapitel 17, _Events und Event-Handling_ , behandelt JavaScript-Events und -Event-Handler, die zentral für alle JavaScript-Programme sind, die mit dem Benutzer interagieren.

  * Kapitel 18, _Formulare und Formularelemente_ , erklärt, wie JavaScript mit HTML-Formularen und -Formularelementen arbeiten kann. Das ist die logische Erweiterung von Kapitel 15, aber das Thema ist so wesentlich, dass es ein eigenes Kapitel verdient.

Auf diese ersten sechs Kapitel von Teil II folgen fünf weitere, die fortgeschrittenere Themen von clientseitigem JavaScript behandeln.

  * Kapitel 19, _Cookies und Persistenz auf der Clientseite_ , behandelt die clientseitige Persistenz, d.h. die Fähigkeit von Skripten, Daten auf dem Computer des Benutzers zu speichern, damit diese später abgerufen werden können. Dieses Kapitel erklärt, wie man HTTP-Cookies verwendet, um Daten persistent zu machen, und wie man Persistenz mit proprietären Features des Internet Explorer und des Flash-Plug-ins erzielt.

  * Kapitel 20, _HTTP skripten_ , zeigt, wie JavaScript das HTTP-Protokoll skripten kann, indem es über das XMLHttpRequest-Objekt Anfragen an Webserver schickt und von ihnen Antworten erhält. Diese Fähigkeit ist der zentrale Punkt in der Webanwendungsarchitektur, die als Ajax bekannt ist.

  * Kapitel 21, _JavaScript und XML_ , demonstriert, wie man JavaScript verwendet, um Inhalte von XML-Dokumenten zu erzeugen, zu laden, zu parsen, abzufragen, zu serialisieren und herauszuziehen.

  * Kapitel 22, _Skriptgesteuerte Grafiken auf der Clientseite_ , erklärt die Grafikfähigkeiten von JavaScript. Es behandelt einfache Image-Rollover und Animationen, erklärt aber auch fortgeschrittene grafische Techniken, die Scalable Vector Graphics (SVG), die Vector Markup Language (VML), das `<canvas>`-Tag, das Flash-Plug-in und das Java-Plug-in verwenden.

  * Kapitel 23, _Java-Applets und Flash-Movies skripten_ , erklärt, wie Sie JavaScript einsetzen können, um mit Java-Applets und Flash-Filmen zu kommunizieren und diese zu steuern. Es behandelt auch, wie Sie das Gegenteil tun können – also wie Sie JavaScript-Code aus Java-Applets und Flash-Movies aufrufen können.

Die Teile III und IV enthalten Referenzmaterial, das Kern-JavaScript bzw. clientseitiges JavaScript behandelt. Diese Teile dokumentieren die wichtigsten Objekte, Methoden und Eigenschaften in alphabetischer Reihenfolge.

# Die in diesem Buch verwendeten Konventionen

In diesem Buch werden die folgenden Formatierungskonventionen verwendet:

 **Fett**

Wird gelegentlich verwendet, um auf bestimmte Tasten auf einer Computer-Tastatur oder Teile einer Benutzerschnittstelle wie auf den **Zurück** -Button oder auf das **Optionen** -Menü zu verweisen.

 _Kursiv_

Wird zur Hervorhebung genutzt und um die erste Verwendung eines Begriffs zu kennzeichnen. _Kursiv_ wird ebenfalls für E-Mail-Adressen, Webseites, FTP-Sites, Datei- und Verzeichnisnamen und Newsgroups verwendet. Schließlich wird _Kursiv_ in diesem Buch für die Namen von Java-Klassen verwendet, um sie leichter von JavaScript-Namen unterscheiden zu können.

`Nichtproportionalschrift`

Wird in allen JavaScript-Code- und HTML-Text-Listings verwendet sowie allgemein für alles, das man beim Programmieren wörtlich eingeben würde.

 **`Nichtproportionalschrift fett`**

Wird verwendet, um Kommandozeilentext anzuzeigen, der vom Benutzer eingegeben werden soll.

 _`Nichtproportionalschrift kursiv`_

Wird für die Namen von Funktionsargumenten verwendet und allgemein als Platzhalter eingesetzt, um Elemente zu kennzeichnen, die in Ihren Programmen durch tatsächliche Werte ersetzt werden sollten.

# Verwendung der Codebeispiele

Dieses Buch soll Ihnen bei der Arbeit helfen. Den Code, den wir hier zeigen, dürfen Sie generell in Ihren Programmen und Dokumentationen verwenden. Sie brauchen uns nicht um Genehmigung zu bitten, sofern Sie nicht große Teile des Codes reproduzieren. Wenn Sie zum Beispiel ein Programm schreiben, das mehrere Codeabschnitte aus diesem Buch wiederverwendet, brauchen Sie unser Einverständnis nicht. Doch wenn Sie eine CD-ROM mit Codebeispielen aus O'Reilly-Büchern verkaufen oder verteilen wollen, müssen Sie sehr wohl eine Erlaubnis einholen. Eine Frage mit einem Zitat aus diesem Buch und seinen Codebeispielen erfordert keine Erlaubnis, aber es ist nicht ohne Weiteres gestattet, große Teile unseres Texts oder Codes in eine eigene Produktdokumentation aufzunehmen.

Wir freuen uns über eine Quellenangabe, verlangen sie aber nicht unbedingt. Zu einer Quellenangabe gehören normalerweise der Titel, der Autor, der Verlag und die ISBN, zum Beispiel: »David Flanagan: _JavaScript – Das umfassende Referenzwerk_. O'Reilly: 2007, ISBN 978-3-89721-491-0«.

Wenn Sie das Gefühl haben, dass Ihr Einsatz unserer Codebeispiele über die Grenzen des Erlaubten hinausgeht, schreiben Sie uns bitte eine E-Mail an permissions@oreilly.com.

# Website und Codebeispiele zu diesem Buch

Auf deutschen Website zu diesem Buch finden Sie Errata, Codebeispiele und Zusatzinformationen:

<http://www.oreilly.de/catalog/jscript5ger>
---

Für Kommentare zu diesem Buch senden Sie uns eine E-Mail an:

kommentar@oreilly.de
---

# Danksagungen

Brendan Eich von der Mozilla Foundation ist der Urheber und Haupterneuerer von JavaScript. Viele andere Webentwickler und ich schulden Brendan ungeheuere Dankbarkeit für die Entwicklung von JavaScript und dafür, dass er bei seinem verrückten Terminkalender immer noch Zeit findet, unsere Fragen zu beantworten, und uns sogar um Feedback bittet. Brendan hat nicht nur meine Fragen geduldig beantwortet, er hat auch die erste und die dritte Auflage dieses Buchs gelesen und sehr hilfreiche Hinweise gegeben.

Dieses Buch hatte das Glück, von erstklassigen Gutachtern gelesen zu werden, deren Kommentare viel dazu beigetragen haben, das Buch besser und genauer zu machen. Aristotle Pagaltzis (<http://plasmasturm.org>) hat das neue Material zu Funktionen und die in dieser Auflage neuen Kapitel über Klassen und Namensräume geprüft. Er hat einen besonders genauen Blick auf meinen Code geworfen und hat viele hilfreiche Hinweise gegeben. Douglas Crockford (<http://www.crockford.com>) hat das neue Material zu Funktionen und Klassen gelesen. Norris Boyd, der Schöpfer des Rhino JavaScript-Interpreters, hat das Kapitel zum Einbetten von JavaScript in Java-Anwendungen geprüft. Peter-Paul Koch (<http://www.quirksmode.org>), Christian Heilmann (<http://www.wait-till-i.com>) und Ken Cooper haben die Ajax-bezogenen Kapitel dieses Buchs geprüft. Ken war so nett, am Ende einzuspringen und beim neuen Material für die clientseitige Referenz auszuhelfen. Todd Ditchendorf (<http://www.ditchnet.org>) und Geoff Stearns (<http://blog.deconcept.com>) haben das Kapitel zu geskripteten clientseitigen Grafiken gelesen. Todd war so nett, für mich einen Bug zu finden und zu isolieren, und Geoff hat mir geholfen, Flash und ActionScript zu verstehen. Schließlich hat Sanders Kleinfeld das vollständige Buch mit bemerkenswerter Aufmerksamkeit gelesen. Seine Vorschläge und Korrekturen haben das Buch klarer und präziser gemacht. Mein aufrichtiger Dank gilt ihnen allen für ihre Sorgfalt. Alle verbleibenden Fehler sind natürlich meine.

Auch den Gutachtern für die vierte englische Auflage bin ich dankbar. Waldemar Horwat von Netscape hat das neue Material zu JavaScript 1.5 geprüft. Das neue Material zum W3C DOM wurde von Philippe Le Hegaret vom W3C und von Peter-Paul Koch, Dylan Schiemann und Jeff Yates geprüft. Obwohl er kein Gutachter war, war Joseph Kesselman von IBM Research sehr hilfreich, indem er meine Fragen zum W3C DOM beantwortet hat.

Die dritte englische Auflage dieses Buchs wurde von Brendan Eich, Waldemar Horwat und Vidur Apparao von Netscape, Herman Venter von Microsoft und zwei unabhängigen JavaScript-Entwicklern, Jay Hodges und Angelo Sirigos, geprüft. Dan Shafer hat einige Vorarbeiten für die dritte Auflage geleistet. Obwohl sein Material in dieser Auflage nicht verwendet wurde, waren seine Ideen und sein allgemeines Konzept sehr hilfreich. Norris Boyd und Scott Furman von Netscape haben ebenfalls hilfreiche Informationen für dieses Buch geliefert, und Vidur Apparao von Netscape und Scott Issacs von Microsoft haben sich beide die Zeit genommen, mit mir über den aufkommenden Document Object Model-Standard zu sprechen. Schließlich bot mir Dr. Tankred Hirschmann anspruchsvolle Einsichten in die Feinheiten von JavaScript 1.2.

Die zweite englische Auflage hat sehr von der Hilfe und den Kommentaren von Nick Thompson und Richard Yaker von Netscape, Dr. Shon Katzenberger, Larry Sullivan und Dave C. Mitchell von Microsoft und Lynn Rollins von R&B Communications profitiert. Die erste Auflage wurde von Neil Berkman von Bay Networks und Andrew Schulman und Terry Allen von O'Reilly geprüft.

Dieses Buch schöpft seine Stärke auch aus der Vielzahl von Lektoren, die es hatte. Deb Cameron ist die Lektorin der aktuellen Auflage und hat dafür gesorgt, dass das Buch eine gründliche Bearbeitung und eine höchst erforderliche Auffrischung erhalten hat, insbesondere in Bezug auf die Entfernung des veralteten Materials. Paula Ferguson hat die dritte und die vierte englische Auflage lektoriert. Frank Willison hat die zweite englische Auflage lektoriert und Andrew Schulman die erste.

Abschließend mein Dank, wie immer aus so vielen Gründen, an Christie.

—David Flanagan

<http://www.davidflanagan.com/>

April 2006

# Kapitel 1. Einführung in JavaScript

JavaScript ist eine interpretierte Programmiersprache mit objektorientierten Fähigkeiten. Syntaktisch betrachtet hat der Sprachkern von JavaScript Ähnlichkeit mit C, C++ und Java; hier wie dort gibt es Programmierkonstrukte wie die `if` -Anweisung, die `while` -Schleife und den Operator `&&`. Die Ähnlichkeit ist jedoch auf diesen syntaktischen Bereich beschränkt. JavaScript ist eine lose typisierte Sprache, was bedeutet, dass für Variablen kein Typ angegeben werden muss. In JavaScript bilden Objekte Eigenschaftsnamen auf beliebige Werte ab. Deswegen ähneln sie eher Name/Wert-Listen oder assoziativen Arrays (wie in Perl) als Structs (wie in C) oder Objekten (wie in C++ oder Java). Der OO-Vererbungsmechanismus von JavaScript basiert auf Prototypen wie der der wenig bekannten Programmiersprache Self. Das ist ganz anders als die Vererbung in C++ und Java. Ebenso wie Perl ist JavaScript eine interpretierte Sprache, und in mancherlei Hinsicht wurde sie von Perl inspiriert. Dies ist zum Beispiel bei den regulären Ausdrücken und der Behandlung von Arrays der Fall.

Der Sprachkern von JavaScript unterstützt Zahlen, Strings und Boolesche Werte als elementare Datentypen. Er bietet außerdem eingebaute Objekte für Arrays, Datumswerte und reguläre Ausdrücke.

JavaScript wird am häufigsten in Webbrowsern verwendet. In diesem Kontext wird der allgemeine Kern mit Objekten erweitert, die es Skripten ermöglichen, mit dem Benutzer in Interaktion zu treten, den Webbrowser zu steuern und das Dokument zu ändern, das im Webbrowserfenster angezeigt wird. Diese eingebettete Version von JavaScript führt Skripten aus, die in HTML-Webseiten eingebaut sind. Sie wird üblicherweise als _clientseitiges_ JavaScript bezeichnet, um zu betonen, dass die Skripten vom Clientrechner ausgeführt werden und nicht vom Webserver.

Der JavaScript-Sprachkern und seine eingebauten Datentypen unterliegen internationalen Standards, und die Kompatibilität der verschiedenen Implementierungen ist sehr gut. Teile des clientseitigen JavaScripts sind formell standardisiert, andere Teile sind De-facto-Standards, und andere Teile sind browserspezifische Erweiterungen. Die browserübergreifende Kompatibilität ist für clientseitige JavaScript-Programmierer oft eine wichtige Sache.

Dieses Kapitel bietet einen groben Überblick über JavaScript. Es bietet die Hintergrundinformationen, die Sie benötigen, bevor Sie mit dem Studium der Sprache beginnen können. Als Motivation und Einführung schließt es einige einfache Beispiele mit clientseitigem JavaScript-Code ein.

# 1.1 Was ist JavaScript?

Über JavaScript kursieren viele irrige Annahmen. Bevor wir in unserer Beschreibung fortfahren, müssen wir mit zwei verbreiteten und hartnäckigen Irrtümern über diese Sprache aufräumen.

## 1.1.1 JavaScript ist nicht Java

Eines der häufigsten Missverständnisse bezüglich JavaScript ist der Glaube, es sei eine vereinfachte Version von Java, der Programmiersprache von Sun Microsystems. Abgesehen von einer teilweisen syntaktischen Ähnlichkeit und der Tatsache, dass sowohl Java als auch JavaScript in Webbrowsern ausführbare Inhalte bereitstellen können, haben die beiden Sprachen nichts miteinander gemein. Die Ähnlichkeit der Namen ist eine reine Marketingmaßnahme von Netscape und Sun (die Sprache hieß ursprünglich LiveScript, und der Name wurde erst in letzter Minute in JavaScript geändert). Allerdings kann JavaScript tatsächlich Java skripten (siehe Kapitel 12 und 23).

## 1.1.2 JavaScript ist nicht einfach

Weil JavaScript interpretiert statt kompiliert wird, wird es häufig als Skriptsprache und nicht als eine richtige Programmiersprache bezeichnet. Dabei wird unterstellt, dass Skriptsprachen einfacher sind, Programmiersprachen für Nichtprogrammierer gewissermaßen. Der Umstand, dass JavaScript nicht streng typisiert ist, macht es Fehlern gegenüber weniger empfindlich und für unerfahrene Programmierer leichter verwendbar. Und viele Webdesigner waren in der Lage, JavaScript für begrenzte, kochbuchartige Programmieraufgaben zu verwenden.

Hinter dieser Fassade einer scheinbaren Einfachheit ist JavaScript jedoch eine vollständige Programmiersprache, die mindestens so komplex ist wie alle anderen Sprachen. Programmierer, die mit JavaScript schwierigere Aufgaben zu lösen versuchen, stellen häufig fest, dass die Arbeit mit JavaScript frustrierend ist, wenn man die Sprache nicht wirklich gut beherrscht. Da das vorliegende Buch JavaScript umfassend dokumentiert, gibt es Ihnen die Möglichkeit, die Sprache in all ihren Feinheiten zu verstehen. Wenn Sie kochbuchartige JavaScript-Anleitungen kennen, werden Sie vielleicht von der Tiefe und Informationsfülle der kommenden Kapitel überrascht sein.

# 1.2 Versionen von JavaScript

Wie alle neuen Technologien hat sich JavaScript sehr schnell entwickelt, als es neu war. Vorangehende Versionen dieses Buchs haben diese Entwicklung Schritt für Schritt dokumentiert und genau erläutert, welche Sprachfeatures in welcher Version der Sprache eingeführt wurden. Mittlerweile hat sich die Sprache allerdings stabilisiert und wurde von der European Computer Manufacturer's Association (ECMA) standardisiert.[1] Implementierungen dieses Standards sind beispielsweise der JavaScript 1.5-Interpreter von Netscape und der Mozilla Foundation und der JScript 5.5-Interpreter von Microsoft. Alle Webbrowser, die neuer sind als Netscape 4.5 oder Internet Explorer 4, unterstützen die neueste Version der Sprache. In der Praxis ist es also unwahrscheinlich, dass Sie auf einen nicht-konformen Interpreter stoßen könnten.

Beachten Sie, dass der offizielle Name der Sprache gemäß dem ECMA-262-Standard ECMAScript ist. Aber dieser umständliche Name wird normalerweise nur verwendet, wenn explizit auf den Standard verwiesen wird. Genau genommen verweist der Name »JavaScript« nur auf die Sprachimplementierungen von Netscape und der Mozilla Foundation. In der Praxis nennt allerdings jeder die Sprache JavaScript.

Nach einer langen Phase der Stabilität gibt es jetzt einige Vorboten von Änderungen an JavaScript. Der Webbrowser Firefox 1.5 von der Mozilla Foundation schließt einen neuen JavaScript-Interpreter mit der Versionsnummer 1.6 ein, Firefox 2.0 wird sogar mit JavaScript 1.7 ausgeliefert. Diese Versionen schließen einige neue (nicht-standardisierte) Methoden zur Manipulation von Arrays ein, die in "7.6 Array-Methoden" beschrieben werden, und bieten außerdem Unterstützung für E4X, das im Folgenden erläutert wird.

Zusätzlich zur ECMA-262-Spezifikation, die den JavaScript-Sprachkern standardisiert, hat die ECMA einen weiteren JavaScript-bezogenen Standard veröffentlicht. ECMA-357 standardisiert eine Erweiterung für JavaScript, die als E4X oder ECMAScript for XML bekannt ist. Diese Erweiterung fügt der Sprache einen XML-Datentyp sowie Operatoren und Anweisungen zur Manipulation von XML-Werten hinzu. Aktuell wird E4X nur von JavaScript 1.6 und Firefox ab Version 1.5 implementiert. E4X wird in diesem Buch nicht explizit dokumentiert, aber Kapitel 21 schließt eine ausführlichere Einführung in Tutorial-Form ein.

Vorschläge für eine vierte Auflage der ECMA-262-Spezifikation zur Standardisierung von JavaScript 2.0 sind seit einigen Jahren auf dem Tisch. Diese Vorschläge beschreiben eine Generalüberholung der Sprache, die strenge Typisierung und eine echte klassenbasierte Vererbung einschließt. Bisher wurden aber nur wenige Fortschritte in Bezug auf eine Standardisierung von JavaScript 2.0 erzielt. Mit Microsofts JScript.NET-Sprache und den Sprachen ActionScript 2.0 und ActionScript 3.0, die vom Adobe (früher Macromedia) Flash-Player verwendet werden, gibt es allerdings schon Implementierungen, die auf den Entwurfsvorschlägen basieren. Aktuell gibt es Anzeichen dafür, dass die Arbeit an JavaScript 2.0 wieder aufgenommen wird, und die Veröffentlichung von JavaScript 1.6 und 1.7 kann als erster Schritt in diese Richtung angesehen werden. Natürlich ist davon auszugehen, dass jede neue Version der Sprache rückwärtskompatibel mit der hier beschriebenen Version ist. Und auch wenn JavaScript 2.0 standardisiert ist, wird es noch einige Jahre dauern, bis es in Webbrowsern weit verbreitet ist.

* * *

[1] Der Standard ist ECMA-262, Version 3 (verfügbar unter<http://www.ecma-international.org/publications/files/ecma-st/ECMA-262.pdf>).

# 1.3 Clientseitiges JavaScript

Wenn ein JavaScript-Interpreter in einen Webbrowser eingebettet wird, ist das Ergebnis clientseitiges JavaScript. Dies ist die bei weitem gebräuchlichste Variante von JavaScript: Die meisten Menschen meinen clientseitiges JavaScript, wenn sie von JavaScript sprechen. Dieses Buch dokumentiert clientseitiges JavaScript in Verbindung mit dem Sprachkern von JavaScript, der im clientseitigen JavaScript enthalten ist.

Clientseitiges JavaScript kombiniert die Skripting-Fähigkeiten eines JavaScript-Interpreters mit dem von einem Webbrowser definierten Document Object Model (DOM). Dokumente können JavaScript-Skripten enthalten, und diese Skripten können das DOM nutzen, um das Dokument zu modifizieren oder den Webbrowser zu steuern, der das Dokument anzeigt. Man könnte auch sagen, dass clientseitiges JavaScript ansonsten statischen Webinhalten Verhalten hinzufügt. Clientseitiges JavaScript ist das Herz von Webentwicklungstechniken wie DHTML (siehe Kapitel 16) und Architekturen wie Ajax (siehe Kapitel 20). Die Einführung in Kapitel 13 enthält einen Überblick über die vielen Möglichkeiten von clientseitigem JavaScript.

Genau wie die ECMA-262-Spezifikation eine Standardversion der JavaScript-Kernsprache definiert, hat das World Wide Web Consortium (W3C) eine DOM-Spezifikation veröffentlicht, die die Features standardisiert, die ein Browser in seinem DOM unterstützen muss. (In den Kapitel 15, Kapitel 16 und Kapitel 17 werden Sie noch viel mehr zu diesem Standard lernen.) Die Kernteile des W3C DOM-Standards werden von den meisten wichtigen Browsern gut unterstützt. Eine wichtige Ausnahme ist Microsofts Internet Explorer, der den W3C-Standard für das Event-Handling nicht unterstützt.

## 1.3.1 Clientseitige JavaScript-Beispiele

Wenn ein Webbrowser um einen JavaScript-Interpreter erweitert wird, können ausführbare Inhalte als JavaScript-Skripten über das Internet verbreitet werden. Beispiel 1-1 zeigt, wie das aussieht: Es ist ein einfaches JavaScript-Programm oder -Skript, das in eine HTML-Datei eingebettet ist.

Beispiel 1-1. Ein einfaches JavaScript-Programm

    <html>
    <head><title>Fakultäten</title></head>
    <body>
    <h2>Fakultätstabelle</h2>
    <script>
    var fact = 1;
    for(i = 1; i < 10; i++) {
      fact = fact*i;
      document.write(i + "! = " + fact + "<br>");
    }
    </script>
    </body>
    </html>

Wenn dieses Skript in einen JavaScript-fähigen Browser geladen wird, liefert es die in Abbildung 1-1 gezeigte Ausgabe.

Abbildung 1-1. Eine Webseite, die mit JavaScript generiert wurde

Wie Sie in diesem Beispiel sehen, werden die Tags `<script>` und `</script>` dafür verwendet, JavaScript-Code in eine HTML-Datei einzubetten. Über das `<script>` -Tag werden Sie in Kapitel 13 mehr erfahren. Das wichtigste JavaScript-Merkmal, das dieses Beispiel zeigt, ist die Methode `document.write( )`.[2] Diese Methode wird verwendet, um dynamisch HTML-Text in ein HTML-Dokument auszugeben, während es in den Browser geladen wird.

JavaScript kann nicht nur den Inhalt von HTML-Dokumenten, sondern auch das Verhalten dieser Dokumente steuern. Das heißt, ein JavaScript-Programm könnte auf bestimmte Weise reagieren, wenn Sie in ein Eingabefeld einen Wert eingeben oder die Maus über ihm schweben lassen. Dazu definiert JavaScript für das Dokument Event-Handler, d.h. JavaScript-Code, der dann ausgeführt wird, wenn ein bestimmtes Event eintritt, zum Beispiel wenn der Anwender einen Button anklickt. Beispiel 1-2 zeigt ein einfaches HTML-Fragment, das einen Event-Handler enthält, der ausgeführt wird, wenn auf einen Button geklickt wird.

Beispiel 1-2. Ein HTML-Button, für den ein JavaScript-Event-Handler definiert ist

    <button onclick="alert('Sie haben auf den Button geklickt');">
    Hier klicken
    </button>

Abbildung 1-2 zeigt das Ergebnis nach dem Anklicken des Buttons.

Abbildung 1-2. Die JavaScript-Reaktion auf ein Event

Das in Beispiel 1-2 vorgeführte Attribut `onclick` enthält einen String mit JavaScript-Code, der ausgeführt wird, wenn der Benutzer auf den Button klickt. In diesem Fall ruft der Event-Handler `onclick` die Funktion `alert( )` auf. Wie Sie in Abbildung 1-2 sehen, öffnet `alert( )` ein Dialogfenster, um die angegebene Nachricht anzuzeigen.

Die Beispiel 1-2 und Beispiel 1-3 demonstrieren nur die einfachsten Features von clientseitigem JavaScript. Die wahre Macht von JavaScript auf der Clientseite besteht darin, dass Skripten Zugriff auf den Inhalt von HTML-Dokumenten haben. Beispiel 1-3 enthält ein vollständiges, nicht-triviales JavaScript-Programm. Das Programm berechnet die monatlichen Zahlungen für eine Immobilienhypothek oder für einen anderen Kredit, bei dem Kreditbetrag, Zinssatz und Zahlungszeitraum festgelegt sind. Es liest die Benutzereingaben aus HTML-Formularfeldern, führt anhand dieser Eingaben die Berechnungen durch und ändert dann das Dokument, um das Ergebnis der Berechnung anzuzeigen.

Abbildung 1-3 zeigt, wie das Programm aussieht, wenn es in einem Webbrowser angezeigt wird. Wie Sie sehen können, besteht es aus einem HTML-Formular und etwas zusätzlichem Text. Allerdings kann die Abbildung nur eine statische Momentaufnahme des Programms wiedergeben. Durch JavaScript-Code wird das Formular dynamisch: Jedes Mal, wenn der Anwender die Kreditsumme, den Zinssatz oder die Anzahl der Raten verändert, berechnet der JavaScript-Code die monatliche Rate, die Summe aller Raten und die Zinsen, die während der gesamten Laufzeit des Kredits gezahlt werden, neu.

Abbildung 1-3. Ein JavaScript-Rechner für eine Kreditrückzahlung

Die erste Hälfte von Beispiel 1-3 ist ein einfaches CSS-Stylesheet und ein HTML-Formular, das in einer HTML-Tabelle formatiert ist. Beachten Sie, dass Formularelemente die Event-Handler `onchange` oder `onclick` definieren. Der Webbrowser löst diese Event-Handler aus, wenn der Anwender die Eingabe ändert oder den Button **Berechnen** anklickt, der auf dem Formular angezeigt wird. In beiden Fällen ist der Wert des Event-Handler-Attributs ein String mit JavaScript-Code: `berechnen( )`. Wenn der Event-Handler ausgelöst wird, führt er diesen Code aus, der die Funktion `berechnen( )` aufruft.

Die Funktion `berechnen( )` ist in der zweiten Hälfte des Beispiels innerhalb der `<script>` -Tags definiert. Die Funktion liest die Benutzereingaben aus dem Formular, führt die Berechnungen aus, die erforderlich sind, um die monatliche Rate zu ermitteln, und fügt die Ergebnisse dieser Berechnungen in `<span>` -Tags, denen extra mit `id` -Attributen ein Name gegeben wurde, in das Dokument ein.

Beispiel 1-3 ist ein kurzes, aber gradliniges Beispiel, das es verdient, dass Sie es sich genau ansehen. Sie sollten nicht erwarten, dass Sie jetzt schon den ganzen Code verstehen, aber das HTML, CSS und JavaScript sind jeweils kommentiert, und eine genaue Betrachtung dieses Beispiels sollte Ihnen ein Gefühl für clientseitiges JavaScript vermitteln.[3]

Beispiel 1-3. Mit JavaScript Kreditraten berechnen

    <html>
    <head>
    <title>JavaScript-Kreditrechner</title>
    <style>
    /* Dies ist ein CSS-Stylesheet: Es fügt der Programmausgabe Styles hinzu. */
    .ergebnis { font-weight: bold; } /* Für Elemente mit class="ergebnis" */
    #zahlung { text-decoration: underline; } /* Für das Element mit der id="zahlung" */
    </style>
    </head>
    <body>
    <!--
     In dieses HTML-Formular kann der Benutzer Daten eingeben, und JavaScript
     kann ihm Berechnungsergebnisse darin anzeigen. Die Formularelemente
     sind in eine Tabelle eingebettet, damit sie besser aussehen. Das Formular
     heißt "kreditdaten", und seine Felder erhalten Namen wie "zinssatz" und
     "jahre". Diese Feldnamen werden im folgenden JavaScript-Code benutzt.
     Einige Formularelemente definieren die Event-Handler "onchange" oder
     "onclick". Diese geben JavaScript-Code an, der ausgeführt wird, wenn
     der Benutzer Daten eingibt oder auf einen Button klickt.
    -->
    <form name="kreditdaten">
     <table>
      <tr><td><b>Geben Sie die Kreditinformationen an:</b></td></tr>
      <tr>
       <td>1) Kreditbetrag (in beliebiger Währung):</td>
       <td><input type="text" name="betrag" onchange="berechnen( );"></td>
      </tr>
      <tr>
       <td>2) Jährlicher Zinssatz in Prozent:</td>
       <td><input type="text" name="zinssatz" onchange="berechnen( );"></td>
      </tr>
      <tr>
       <td>3) Rückzahlungszeitraum in Jahren:</td>
       <td><input type="text" name="jahre" onchange="berechnen( );"></td>
      </tr>
      <tr><td></td>
       <td><input type="button" value="Berechnung" onclick="berechnen( );"></td>
      </tr>
      <tr><td><b>Zahlungsinformationen:</b></td></tr>
      <tr>
       <td>4) Ihre monatliche Raten:</td>
       <td><span class="ergebnis" id="zahlung"></span> €</td>
      </tr>
      <tr>
       <td>5) Ingesamt zu zahlende Summe:</td>
       <td><span class="ergebnis" id="gesamt"></span> €</td>
      </tr>
      <tr>
       <td>6) Ihre insgesamt zu zahlenden Zinsen:</td>
       <td><span class="ergebnis" id="gesamtzinsen"></span> €</td>
      </tr>
     </table>
    </form>

    <script language="JavaScript">
    /*
     * Das ist die JavaScript-Funktion, die bewirkt, dass das Beispiel funktioniert. Beachten
     * Sie, dass dieses Skript die Funktion berechnen( ) definiert, die von den Event-Handlern
     * im Formular aufgerufen wird. Die Funktion liest die Werte aus den <input>-Feldern des
     * Formulars und verwendet dazu die im vorangehenden HTML-Code definierten Namen. Sie
     * gibt ihre Ergebnisse in den benannten <span>-Elementen aus.
     */
    function berechnen() {
      // Hole die Benutzereingabe aus dem Formular, und nimm an, dass alles
      // korrekt ist. Konvertiere die Zinsen von Prozenten in Dezimalform
      // und von einem Jahres- in einen Monatssatz sowie den Rückzahlungszeitraum
      // von Jahren in die Anzahl der Monatsraten.
      var betrag = document.kreditdaten.betrag.value;
      var zinssatz = document.kreditdaten.zinssatz.value / 100 / 12;
      var zahlungen = document.kreditdaten.jahre.value * 12;

      // Berechne die Höhe der Monatsrate mit esoterischen Matheregeln.
      var x = Math.pow(1 + zinssatz, zahlungen);
      var monatsrate = (betrag*x*zinssatz)/(x-1);

      // Die benannten <span>-Elemente aus dem Formular abrufen.
      var zahlung = document.getElementById("zahlung");
      var gesamt = document.getElementById("gesamt");
      var gesamtzinsen = document.getElementById("gesamtzinsen");

      // Prüfen, dass das Ergebnis eine endliche Zahl ist. Wenn das der Fall ist, wird
      // das Ergebnis angezeigt, indem der HTML-Inhalt der <span>-Elemente gesetzt wird.
      if (isFinite(monatsrate)) {
        zahlung.innerHTML = monatsrate.toFixed(2);
        gesamt.innerHTML = (monatsrate * zahlungen).toFixed(2);
        gesamtzinsen.innerHTML = ((monatsrate*zahlungen)-betrag).toFixed(2);
      }
      // Andernfalls war die Benutzereingabe wahrscheinlich ungültig, und es wird nichts
      // angezeigt.
      else {
        zahlung.innerHTML = "";
        gesamt.innerHTML = ""
        gesamtzinsen.innerHTML = "";
      }
    }
    </script>
    </body>
    </html>

* * *

[2] »Methode« ist der objektorientierte Begriff für eine Funktion oder Prozedur; dieser Begriff wird im ganzen Buch verwendet.

3] Wenn Ihr Gefühl Ihnen sagt, dass es keine gute Idee ist, HTML-Markup, CSS-Styles und JavaScript-Code auf diese Weise zu mischen, sind Sie nicht allein. Der Trend unter JavaScript-Programmierern und Webdesignern geht dahin, Inhalt, Darstellung und Verhalten in separate Dateien zu packen. ["13.1.5 Barrierefreies JavaScript" von Kapitel 13 erklärt, wie man das macht.

# 1.4 JavaScript in anderen Kontexten

JavaScript ist eine Allzweck-Programmiersprache; ihre Verwendung ist nicht auf Webbrowser beschränkt. JavaScript wurde dafür entworfen, in eine Anwendung eingebettet zu werden und sie mit Skriptfähigkeiten auszustatten. Von Anfang an enthielten die Webserver von Netscape einen JavaScript-Interpreter, damit serverseitige Skripten in JavaScript geschrieben werden konnten. Auch Microsoft verwendet den JScript-Interpreter nicht nur im Internet Explorer, sondern auch im IIS-Webserver und im Windows Scripting Host. Adobe nutzt eine von JavaScript abgeleitete Sprache für das Scripting des Flash-Players. Und Sun bündelt seine Java 6.0-Distribution mit einem JavaScript-Interpreter, damit Java-Anwendungen problemlos Scripting-Fähigkeiten hinzugefügt werden können (wie das geht, zeigt Kapitel 12).

Sowohl Netscape als auch Microsoft haben ihre JavaScript-Interpreter den Unternehmen und Programmierern zur Verfügung gestellt, die die Interpreter in ihre Anwendungen einbetten möchten. Netscapes Interpreter wurde als Open Source-Produkt veröffentlicht und steht nun über die Organisation Mozilla (<http://www.mozilla.org/js/>) zur Verfügung. Mozilla bietet zwei verschiedene Versionen des Interpreters für JavaScript 1.5 an. Eine ist in C geschrieben und heißt SpiderMonkey, die andere ist in Java geschrieben und heißt Rhino – eine schmeichelhafte Anspielung auf dieses Buch.

Es wird zunehmend mehr Anwendungen geben, die JavaScript als eingebettete Skriptsprache verwenden. Wenn Sie Skripten für solche Anwendungen schreiben, ist der erste Teil dieses Buchs, der den Sprachkern dokumentiert, für Sie von Nutzen. Die Kapitel, die sich speziell auf Webbrowser beziehen, werden für Ihre Skripten wahrscheinlich weniger relevant sein.

# 1.5 JavaScript erkunden

Der beste Weg, eine neue Programmiersprache wirklich zu erlernen, besteht darin, Programme mit ihr zu schreiben. Ich möchte Sie dazu ermuntern, nicht nur das Buch zu lesen, sondern die Merkmale von JavaScript, die Sie dabei kennenlernen, auch gleich auszuprobieren. Mehrere Verfahren erleichtern das Experimentieren mit JavaScript.

Die nächstliegende Art, JavaScript zu erkunden, besteht darin, einfache Skripten zu schreiben. Eine der angenehmen Eigenschaften von JavaScript ist, dass jeder, der einen Webbrowser und einen einfachen Texteditor besitzt, über eine vollständige Entwicklungsumgebung verfügt. Sie können mit dem Schreiben von JavaScript-Skripten beginnen, ohne vorher spezielle Software kaufen oder herunterladen zu müssen.

Beispielsweise könnten Sie Beispiel 1-1 so verändern, dass die Fibonacci-Folge anstelle von Fakultäten angezeigt wird:

    <script>
    document.write("<h2>Tabelle der Fibonacci-Zahlen</h2>");
    for (i=0, j=1, k=0, fib =0; i<50; i++, fib=j+k, j=k, k=fib){
      document.write("Fibonacci (" + i + ") = " + fib);
      document.write("<br>");
    }
    </script>

Dieser Code mag verwirrend erscheinen (machen Sie sich keine Sorgen, wenn Sie ihn noch nicht verstehen), aber es geht nur darum, dass Sie kurze Programme wie dieses, mit denen Sie experimentieren möchten, einfach eintippen und über eine lokale `file:` -URL in Ihrem Webbrowser anzeigen können. Beachten Sie, dass der Code die Methode `document.write( )` verwendet, um die HTML-Ausgabe anzuzeigen, sodass Sie das Ergebnis Ihrer Berechnungen betrachten können. Beim Experimentieren mit JavaScript ist dies ein wichtiges Verfahren. Alternativ dazu können Sie mit der Methode `alert()` die Ausgabe als einfachen Text in einem Dialogfenster anzeigen:

    alert("Fibonacci (" + i + ") = " + fib);

Beachten Sie auch, dass Sie bei einfachen JavaScript-Experimenten wie diesem die Tags `<html>`, `<head>` und `<body>` in der HTML-Datei weglassen können.

Für noch einfachere Experimente mit JavaScript können Sie manchmal auch das URL-Pseudoprotokoll `javascript:` verwenden, um einen JavaScript-Ausdruck auszuwerten und das Ergebnis zurückzugeben. Eine JavaScript-URL besteht aus dem Protokollbezeichner `javascript:`, dem beliebiger JavaScript-Code folgt (wobei Anweisungen durch Semikola voneinander getrennt werden). Wenn der Browser eine derartige URL lädt, führt er den JavaScript-Code aus. Der Wert des letzten Ausdrucks in der URL wird in einen String umgewandelt, und diesen String zeigt der Webbrowser als sein neues Dokument an. So könnten Sie zum Beispiel die folgenden JavaScript-URLs in das Adressfeld Ihres Webbrowsers eingeben, um zu prüfen, ob Sie die genannten JavaScript-Operatoren und -Anweisungen verstanden haben.

    javascript:5%2
    javascript:x = 3; (x < 5)? "x ist kleiner": "x ist größer"
    javascript:d = new Date( ); typeof d;
    javascript:for(i=0,j=1,k=0,fib=0; i<5; i++,fib=j+k,j=k,k=fib) alert(fib);
    javascript:s=""; for(i in navigator) s+=i+":"+navigator[i]+"\n"; alert(s);

Im Webbrowser Firefox können Sie auch einzeilige Experimente in die JavaScript-Konsole eingeben, auf die Sie über das Menü **Extras** zugreifen können. Geben Sie einfach einen Ausdruck oder eine Anweisung ein, den Sie auswerten bzw. die Sie ausführen wollen. Wenn Sie die Konsole anstelle der Adressleiste verwenden, lassen Sie das Präfix `javascript:` weg.

Bei der Erkundung von JavaScript werden Sie wahrscheinlich Code schreiben, der nicht so funktioniert, wie Sie es möchten, und den Sie von Fehlern bereinigen müssen. In JavaScript sieht das grundlegende Verfahren der Fehlerbehebung genauso aus wie in anderen Programmiersprachen: Geben Sie in Ihren Code Anweisungen ein, die die Werte der relevanten Variablen ausgeben, um herauszufinden, was eigentlich geschieht. Wie im vorangehenden Beispiel gezeigt, können Sie dazu Methoden wie `document.write( )` oder `alert( )` verwenden. (Beispiel 15-9 stellt eine ausgefeiltere Einrichtung zum Logging von Debuggin-Meldungen vor.)

Die (in Kapitel 6 beschriebene) `for/in` -Schleife ist bei der Fehlerbehebung ebenfalls nützlich. Sie können sie beispielsweise in Verbindung mit der Methode `alert( )` einsetzen, um eine Liste der Namen und Werte aller Eigenschaften eines Objekts auszugeben. Eine derartige Funktion kann nützlich sein, wenn Sie die Programmiersprache erkunden oder Fehler in Ihrem Programm suchen.

Wenn Ihre JavaScript-Fehler dauerhaft und nervend sind, interessieren Sie sich vielleicht für einen richtigen JavaScript-Debugger. Im Internet Explorer können Sie den Microsoft Script Debugger verwenden. In Firefox können Sie eine Debugger-Erweiterung namens Venkman verwenden. Beide Werkzeuge gehen über den Horizont dieses Buchs hinaus. Aber Sie können Sie leicht mit einer Internet-Suche aufstöbern. Ein weiteres nützliches Werkzeug, das eigentlich kein Debugger ist, ist _jslint_. Es sucht nach häufigen Problemen in JavaScript-Code (siehe <http://jslint.com>).

# Teil I. Der JavaScript-Sprachkern

Dieser Teil des Buchs, der die Kapitel 2 bis 12 umfasst, dokumentiert den Sprachkern von JavaScript und stellt gleichzeitig eine JavaScript-Sprachreferenz dar. Nachdem Sie diesen ersten Teil zum Lernen der Sprache durchgearbeitet haben, werden Sie später vielleicht noch einmal auf ihn zurückkommen, um Ihre Erinnerung an einige der kniffeligeren Aspekte von JavaScript aufzufrischen:

Kapitel 2, _Lexikalische Struktur_
---
Kapitel 3, _Datentypen und Werte_
Kapitel 4, _Variablen_
Kapitel 5, _Ausdrücke und Operatoren_
Kapitel 6, _Anweisungen_
Kapitel 7, _Objekte und Arrays_
Kapitel 8, _Funktionen_
Kapitel 9, _Klassen, Konstruktoren und Prototypen_
Kapitel 10, _Module und Namensräume_
Kapitel 11, _Mustervergleich mit regulären Ausdrücken_
Kapitel 12, _Java skripten_

# Kapitel 2. Lexikalische Struktur

Die lexikalische Struktur einer Programmiersprache ist die Menge der elementaren Regeln, die angeben, wie man Programme in dieser Sprache schreibt. Sie ist die unterste Syntaxebene einer Sprache: Sie gibt beispielsweise an, wie Variablennamen aussehen, welche Zeichen für Kommentare verwendet werden und wie die Programmanweisungen voneinander getrennt werden. Dieses kurze Kapitel dokumentiert die lexikalische Struktur von JavaScript.

# 2.1 Zeichensatz

JavaScript-Programme werden im Zeichensatz Unicode geschrieben. Im Gegensatz zum 7-Bit-ASCII-Zeichensatz, der nur für Englisch nützlich ist, und dem 8-Bit-Zeichensatz ISO Latin-1, der nur für Englisch und die wichtigeren westeuropäischen Sprachen sinnvoll ist, kann die 16-Bit-Codierung mit Unicode fast alle Schriftsprachen der Welt darstellen. Dies ist für die Internationalisierung von Bedeutung und besonders für Programmierer wichtig, die nicht Englisch sprechen.

Englischsprachige und auch deutschsprachige Programmierer schreiben ihre Programme normalerweise mit einem Texteditor, der nur die Zeichencodierung in ASCII oder Latin-1 unterstützt. Sie können daher nicht ohne weiteres auf den vollständigen Unicode-Zeichensatz zugreifen. Dies stellt allerdings kein Problem dar, da sowohl ASCII als auch Latin-1 Untergruppen von Unicode sind, sodass jedes JavaScript-Programm, das in diesen Zeichensätzen geschrieben ist, absolut zulässig ist. Programmierer, die daran gewöhnt sind, sich Zeichen als 8-Bit-Größen vorzustellen, finden es möglicherweise beunruhigend, dass JavaScript jedes Zeichen mit zwei Bytes darstellt, aber diese Tatsache ist für den Programmierer nicht spürbar und kann einfach ignoriert werden.

Der Standard ECMAScript v3 lässt Unicode-Zeichen zwar überall in einem JavaScript-Programm zu, aber die Versionen 1 und 2 dieses Standards erlauben Unicode-Zeichen nur in Kommentaren sowie in String-Literalen, die in Anführungszeichen gesetzt sind; alle Teile sind auf den ASCII-Zeichensatz beschränkt. Alle JavaScript-Versionen, die älter als die Standardisierung durch ECMAScript sind, unterstützen Unicode normalerweise überhaupt nicht.

# 2.2 Groß- und Kleinschreibung

JavaScript ist eine Sprache, die zwischen Groß- und Kleinschreibung unterscheidet. Das bedeutet, dass Schlüsselwörter der Sprache, Variablen, Funktionsnamen und alle anderen Bezeichner immer mit derselben Groß- oder Kleinschreibung eingegeben werden müssen. Das Schlüsselwort `while` muss zum Beispiel als »while« eingetippt werden, nicht als »While« oder »WHILE«. Entsprechend sind auch `online`, `Online`, `OnLine` und `ONLINE` die Namen von vier verschiedenen Variablen.

Beachten Sie jedoch, dass HTML nicht zwischen Groß- und Kleinschreibung unterscheidet (im Gegensatz zu XHTML). Wegen der engen Verbindung von HTML und clientseitigem JavaScript kann diese Unterscheidung verwirrend sein. Viele JavaScript-Objekte und -Eigenschaften haben dieselben Namen wie die HTML-Tags und -Attribute, die sie repräsentieren. Während diese Tags und Attributnamen in HTML aber ganz nach Belieben mit Groß- oder Kleinbuchstaben geschrieben werden können, müssen sie in JavaScript in der Regel durchgehend kleingeschrieben werden. So wird zum Beispiel das Attribut des HTML-Event-Handlers `onclick` in HTML manchmal als `onClick` angegeben, in JavaScript-Code (oder in XHTML-Dokumenten) muss es jedoch `onclick` heißen.

# 2.3 Whitespace und Zeilenenden

JavaScript ignoriert Whitespace, Tabulatoren und Zeilentrenner, die zwischen den Tokens in einem Programm auftreten. Da Sie Leerzeichen, Tabulatoren und Zeilenumbrüche in Ihren Programmen frei verwenden können, können Sie die Programme auch ordentlich und einheitlich formatieren und einrücken, sodass sie leicht zu lesen und zu verstehen sind. Beachten Sie jedoch, dass das Einfügen von Zeilenumbrüchen einer kleinen Einschränkung unterliegt, die wir im folgenden Abschnitt beschreiben werden.

# 2.4 Wahlweise Semikola

Ebenso wie in C, C++ und Java steht auch in JavaScript hinter einfachen Anweisungen normalerweise ein Semikolon (;). Das Semikolon trennt einzelne Anweisungen voneinander. In JavaScript können Sie das Semikolon jedoch weglassen, wenn sich jede Anweisung auf einer eigenen Zeile befindet. Der folgende Code kann zum Beispiel auch ohne Semikola geschrieben werden:

    a = 3;
    b = 4;

Bei der folgenden Formatierung ist das erste Semikolon jedoch unbedingt erforderlich:

    a = 3; b = 4;

Das Weglassen der Semikola ist schlechter Programmierstil. Sie sollten sich angewöhnen, sie immer zu verwenden.

Theoretisch können in JavaScript zwischen zwei Tokens zwar immer Zeilenumbrüche eingefügt werden, aber da JavaScript automatisch Semikola einfügt, gibt es von dieser Regel einige Ausnahmen. Wenn Sie eine Codezeile so umbrechen, dass die Zeile vor dem Umbruch als eine vollständige Anweisung erscheint, geht JavaScript davon aus, dass Sie das Semikolon wegelassen haben, fügt ein Semikolon ein und verändert damit den Sinn des Codes. Besondere Vorsicht ist bei den Anweisungen `return`, `break` und `continue` geboten (die wir in Kapitel 6 beschreiben werden). Sehen Sie sich zum Beispiel den folgenden Code an:

    return
    true;

JavaScript geht davon aus, dass diese Zeilen folgendermaßen aussehen sollten:

    return;
    true;

Doch Sie hatten wahrscheinlich dies im Sinn:

    return true;

Darauf müssen Sie besonders achten, denn dieser Code löst keinen Syntaxfehler aus und wird auf schwer nachvollziehbare Weise fehlschlagen. Beim folgenden Code besteht ein ähnliches Problem:

    break
    outerloop;

JavaScript fügt hinter dem Schlüsselwort `break` ein Semikolon ein. Wenn es dann die nächste Zeile zu interpretieren versucht, wird ein Syntaxfehler ausgelöst. Aus ähnlichen Gründen müssen auch die Postfixoperatoren `++` und `−−` (siehe Kapitel 5) auf derselben Zeile erscheinen wie die Ausdrücke, auf die sie angewendet werden.

# 2.5 Kommentare

Ebenso wie Java unterstützt auch JavaScript Kommentare im Stil von C++ und C. Sämtlicher Text zwischen `//` und dem Zeilenende wird als ein Kommentar behandelt und von JavaScript ignoriert. Auch sämtlicher Text zwischen den Zeichen `/*` und `*/` wird als ein Kommentar behandelt. Diese Kommentare im Stil von C können mehrere Zeilen umfassen, dürfen aber nicht geschachtelt sein. Die folgenden Codezeilen sind zulässige JavaScript-Kommentare:

    // Dies ist ein einzeiliger Kommentar.
    /* Dies ist auch ein Kommentar. */ // Und hier ist noch einer.
    /*
     * Dies ist noch ein weiterer Kommentar.
     * Er hat mehrere Zeilen.
     */

# 2.6 Literale

Ein _Literal_ ist ein Datenwert, der direkt in einem Programm vorkommt. Literale können zum Beispiel folgendermaßen aussehen:

    12        // Die Zahl zwölf
    1.2       // Die Zahl eins komma zwei
    "Hallo Welt"   // Ein Text-String
    'Hi'       // Noch ein String
    true       // Ein Boolescher Wert
    false      // Der andere Boolesche Wert
    /javascript/gi  // Ein Literal für einen "regulären Ausdruck" (zur Mustererkennung)
    null       // Kein Objekt vorhanden

ECMAScript v3 unterstützt zudem Ausdrücke, die als Array- oder Objekt-Literale dienen:

    { x:1, y:2 }  // Ein Objekt-Initialisierer
    [1,2,3,4,5]   // Ein Array-Initialisierer

Literale sind ein wichtiger Bestandteil jeder Programmiersprache, da man ohne sie kein Programm schreiben kann. In Kapitel 3 werden wir die verschiedenen JavaScript-Literale ausführlich beschreiben.

# 2.7 Bezeichner

Ein _Bezeichner_ (engl. _identifier_ ) ist einfach ein Name. In JavaScript dienen Bezeichner als Variablen- und Funktionsnamen sowie als Marken bestimmter Schleifen in JavaScript-Code. Die Regeln für zulässige Bezeichner sind in JavaScript dieselben wie in Java und vielen anderen Programmiersprachen. Das erste Zeichen muss ein Buchstabe, ein Unterstrich (`_`) oder ein Dollar-Zeichen (`$`)[4] sein. Danach folgende Zeichen können Buchstaben, Ziffern, Unterstriche oder Dollar-Zeichen sein. (Da das erste Zeichen keine Zahl sein darf, kann JavaScript leicht zwischen Bezeichnern und Zahlen unterscheiden.) Die folgenden Codezeilen zeigen Beispiele für zulässige Bezeichner:

    i
    mein_variablenname
    v13
    _dummy
    $str

In ECMAScript v3 können Bezeichner Buchstaben und Ziffern aus dem gesamten Unicode-Zeichensatz enthalten. Vor dieser Version des Standards waren die JavaScript-Bezeichner auf den ASCII-Zeichensatz beschränkt. ECMAScript v3 erlaubt in Bezeichnern zudem Unicode-Escape-Sequenzen. Eine Unicode-Escape-Sequenz enthält die Zeichen `\u`, gefolgt von vier hexadezimalen Ziffern, die eine 16-Bit-Zeichencodierung angeben. So kann der Bezeichner π zum Beispiel auch als `\u03c0` geschrieben werden. Diese Syntax ist zwar etwas umständlich, ermöglicht es aber, JavaScript-Programme, die Unicode-Zeichen enthalten, in eine Form zu übersetzen, die ihre Bearbeitung mit Texteditoren und anderen Werkzeugen ermöglicht, die nicht den vollständigen Unicode-Zeichensatz unterstützen.

Bezeichner dürfen nicht mit den Schlüsselwörtern übereinstimmen, die in JavaScript für andere Zwecke verwendet werden. Der folgende Abschnitt führt die besonderen Schlüsselwörter auf, die in JavaScript reserviert sind.

* * *

[4] Beachten Sie, dass Dollar-Zeichen erst ab der JavaScript-Version 1.1 in Bezeichnern zugelassen sind. Da sie ausschließlich für Codegenerierungs-Tools gedacht sind, sollten Sie in selbst geschriebenem Code in Bezeichnern keine Dollar-Zeichen verwenden.

# 2.8 Reservierte Wörter

In JavaScript gibt es eine Reihe von reservierten Wörtern. Dies sind diejenigen Wörter, die Sie in Ihren JavaScript-Programmen nicht als Bezeichner (Variablennamen, Funktionsnamen und Schleifenmarken) verwenden können. Tabelle 2-1 enthält die Schlüsselwörter, die ECMAScript v3 standardisiert. Diese Wörter haben in JavaScript eine besondere Bedeutung: Sie gehören zur Syntax dieser Sprache.

Tabelle 2-1. Reservierte Schlüsselwörter von JavaScript

`break` | `do` | `if` | `switch` | `typeof`

---|---|---|---|---

`case` | `else` | `in` | `this` | `var`

`catch` | `false` | `instanceof` | `throw` | `void`

`continue` | `finally` | `new` | `true` | `while`

`default` | `for` | `null` | `try` | `with`

`delete` | `function` | `return`

|  |

Tabelle 2-2 führt weitere reservierte Schlüsselwörter auf. Diese Wörter werden derzeit in JavaScript zwar nicht verwendet, sind aber von ECMAScript v3 als mögliche zukünftige Spracherweiterungen reserviert.

Tabelle 2-2. Wörter, die für ECMA-Erweiterungen reserviert sind

`abstract` | `double` | `goto` | `native` | `static`

---|---|---|---|---

`boolean` | `enum` | `implements` | `package` | `super`

`byte` | `export` | `import` | `private` | `synchronized`

`char` | `extends` | `int` | `protected` | `throws`

`class` | `final` | `interface` | `public` | `transient`

`const` | `float` | `long` | `short` | `volatile`

`debugger`

|  |  |  |

Zusätzlich zu den gerade genannten formal reservierten Wörtern erwägt der Standard ECMAScript v4 auch die Verwendung der Schlüsselwörter `as`, `is`, `namespace` und `use`. Die derzeit verfügbaren JavaScript-Interpreter werden Sie zwar nicht daran hindern, diese vier Wörter als Bezeichner zu verwenden, aber Sie sollten sie dennoch vermeiden.

Außerdem sollten Sie die Namen von globalen Variablen und Funktionen, die in JavaScript vordefiniert sind, nicht als Bezeichner verwenden. Wenn Sie Variablen und Funktionen mit diesen Namen erstellen, erhalten Sie entweder eine Fehlermeldung (wenn die Eigenschaft schreibgeschützt ist) oder Sie definieren die vorhandene Variable oder Funktion neu, was Sie jedoch nur machen sollten, wenn Sie genau wissen, was Sie tun. Tabelle 2-3 enthält die globalen Variablen und Funktionen, die im Standard ECMAScript v3 definiert sind. Konkrete Implementierungen können andere globale Eigenschaften definieren, und jede konkrete JavaScript-Einbettung (clientseitig, serverseitig usw.) verfügt über ihre eigene umfangreiche Liste globaler Eigenschaften.[5]

Tabelle 2-3. Andere Bezeichner, die Sie vermeiden sollten

`arguments` | `encodeURI` | `Infinity` | `Object` | `String`

---|---|---|---|---

`Array` | `Error` | `isFinite` | `parseFloat` | `SyntaxError`

`Boolean` | `escape` | `isNaN` | `parseInt` | `TypeError`

`Date` | `eval` | `Math` | `RangeError` | `undefined`

`decodeURI` | `EvalError` | `NaN` | `ReferenceError` | `unescape`

`decodeURIComponent` | `Function` | `Number` | `RegExp` | `URIError`

* * *

5] Eine Liste der zusätzlichen globalen Variablen und Funktionen, die von clientseitigem JavaScript definiert werden, finden Sie in [Teil IV.

# Kapitel 3. Datentypen und Werte

Computerprogramme arbeiten, indem sie _Werte_ wie zum Beispiel die Zahl 3,14 oder den Text »Hallo Welt« verarbeiten. Die Werttypen, die in einer Programmiersprache dargestellt und verändert werden können, werden als Datentypen bezeichnet. Eine der grundlegendsten Eigenschaften einer Programmiersprache ist die Menge der Datentypen, die sie unterstützt. JavaScript ermöglicht Ihnen die Arbeit mit den drei primitiven Datentypen Zahlen, Text (der als _String_ bezeichnet wird) und Booleschen Wahrheitswerten (die als _Boolesche Werte_ bezeichnet werden). Außerdem definiert JavaScript die beiden trivialen Datentypen `null` und `undefined`, die jeweils nur einen einzigen Wert definieren.

Zusätzlich zu diesen primitiven Datentypen unterstützt JavaScript einen zusammengesetzten Datentyp, das sogenannte _Objekt_. Ein Objekt (ein Element des Datentyps _Objekt_ ) stellt eine Sammlung von Werten (entweder von einfachen Werten wie Zahlen und Strings oder von zusammengesetzten Werten wie zum Beispiel anderen Objekten) dar. JavaScript-Objekte haben eine Doppelnatur: Sie können eine ungeordnete Sammlung benannter Werte oder eine geordnete Sammlung nummerierter Werte darstellen. Im zweiten Fall bezeichnet man das Objekt als Array. Objekte und Arrays sind zwar im Grunde genommen derselbe JavaScript-Datentyp, aber sie verhalten sich ziemlich unterschiedlich. In diesem Buch werden wir sie in der Regel als zwei verschiedene Typen betrachten.

JavaScript definiert noch eine weitere besondere Objektart, die sogenannte Funktion. Eine Funktion ist ein Objekt, mit dem ausführbarer Code verbunden ist. Eine Funktion kann man aufrufen, damit sie irgendeine Operation durchführt. Ebenso wie Arrays verhalten sich auch Funktionen anders als andere Objektarten, und JavaScript definiert für die Arbeit mit Funktionen eine spezielle Syntax. Wir werden den Datentyp Funktion daher unabhängig von den Typen Objekt und Array behandeln.

Zusätzlich zu den Funktionen und Arrays definiert der JavaScript-Sprachkern einige spezialisierte Objektarten. Diese Objekte stellen keine neuen Datentypen dar, sondern lediglich neue Klassen von Objekten. Die Klasse Date definiert Objekte, die Datumsangaben darstellen, die Klasse RegExp definiert Objekte, die reguläre Ausdrücke darstellen (ein mächtiges Werkzeug zur Mustererkennung, mit dem wir uns in Kapitel 11 befassen), und die Klasse Error definiert Objekte, die Syntax- und Laufzeitfehler darstellen, die in einem JavaScript-Programm auftreten können.

Der Rest dieses Kapitels dokumentiert die einzelnen primitiven Datentypen ausführlich. Außerdem führt er die Datentypen Objekt, Array und Funktion ein, die in den Kapitel 7 und Kapitel 8 detailliert beschrieben werden. Schließlich liefert dieses Kapitel noch einen Überblick über die Klassen Date, RegExp und Error, die in Teil III ausführlich besprochen werden. Das Kapitel schließt mit einigen fortgeschritteneren Informationen, die Sie beim ersten Lesen vielleicht überspringen sollten.

# 3.1 Zahlen

Zahlen sind der einfachste Datentyp; sie bedürfen fast keiner Erklärung. JavaScript unterscheidet sich insofern von Programmiersprachen wie C und Java, als es nicht zwischen Integer- und Gleitkommawerten unterscheidet. In JavaScript werden alle Zahlen als Gleitkommawerte dargestellt. JavaScript stellt Zahlen im 64-Bit-Gleitkommaformat dar, das vom Standard IEEE 754 definiert wird.[6] Es können also Zahlen zwischen ±1.7976931348623157 × 10308 und ±5 × 10−324 dargestellt werden.

Wenn in einem JavaScript-Programm eine Zahl direkt auftritt, bezeichnen wir sie als _numerisches Literal_. JavaScript unterstützt numerische Literale in mehreren Formaten, die wir in den folgenden Abschnitten beschreiben werden. Beachten Sie, dass vor einem numerischen Literal ein Minuszeichen stehen kann, durch das die Zahl negativ wird. Rein technisch betrachtet gehört das Zeichen »-« jedoch nicht zur Syntax der numerischen Literale, sondern ist der unäre Minus-Operator (Kapitel 5).

## 3.1.1 Integer-Literale

In einem JavaScript-Programm wird ein Integer zur Basis 10 als eine Folge von Ziffern geschrieben, zum Beispiel:

    0
    3
    10000000

Mit dem Zahlenformat von JavaScript können Sie alle Integer zwischen einschließlich −9007199254740992 (-253) und 9007199254740992 (253) genau darstellen. Wenn Sie größere Integer-Werte verwenden, verlieren Sie in den Endziffern möglicherweise an Genauigkeit. Beachten Sie jedoch, dass bestimmte Integer-Operationen in JavaScript (insbesondere mit den in Kapitel 5 beschriebenen Bit-Operatoren) auf 32-Bit-Integern mit einem Wertebereich von −2147483648 (-231) bis 2147483647 (231 −1) durchgeführt werden.

## 3.1.2 Hexadezimal- und Oktal-Literale

JavaScript erkennt nicht nur ganzzahlige Literale zur Basis 10, sondern auch Hexadezimalwerte (zur Basis 16). Ein Hexadezimal-Literal beginnt mit »0x« oder »0X«, gefolgt von einer Folge Hexadezimalziffern. Eine Hexadezimalziffer ist eine Ziffer zwischen 0 und einschließlich 9 oder ein Buchstabe zwischen a (oder A) und f (oder F) einschließlich, wobei die Buchstaben die Werte 10 bis 15 angeben. Hier sehen Sie einige Beispiele für hexadezimale, ganzzahlige Literale:

    0xff // 15*16 + 15 = 255 (Basis 10)
    0xCAFE911

Der Standard ECMAScript unterstützt ganzzahlige Literale im Oktalformat (zur Basis 8) zwar nicht, aber in einigen JavaScript-Implementierungen können sie dennoch angegeben werden. Ein Oktal-Literal beginnt mit der Ziffer 0, gefolgt von einer Reihe von Ziffern, die alle zwischen 0 und 7 liegen müssen. Hierzu ein Beispiel:

    0377 // 3*64 + 7*8 + 7 = 255 (Basis 10)

Da einige Implementierungen Oktal-Literale unterstützen, andere hingegen nicht, sollten Sie kein ganzzahliges Literal schreiben, das mit einer Null beginnt: Sie wissen in diesem Fall nicht, ob eine Implementierung dieses Literal als Oktal- oder als Dezimalwert interpretieren wird.

## 3.1.3 Gleitkomma-Literale

Gleitkomma-Literale können einen Dezimalpunkt haben. Sie verwenden die traditionelle Syntax (des englischen Sprachraums) für reelle Zahlen. Ein reeller Wert wird als ein ganzzahliger Zahlenteil dargestellt, auf den ein Dezimalpunkt und der Bruchteil der Zahl folgen.

Gleitkomma-Literale können außerdem in der Exponentialschreibweise dargestellt werden. In diesem Fall stehen hinter einer reellen Zahl der Buchstabe e (oder E), ein optionales Plus- oder Minuszeichen und ein ganzzahliger Exponent. Diese Notation repräsentiert die reelle Zahl, die mit dem Exponenten zur Basis 10 multipliziert wird.

In Kurzform sieht die Syntax folgendermaßen aus:

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

Hierzu einige Beispiele:

    3.14
    2345.789
    .333333333333333333
    6.02e23    // 6.02 × 1023
    1.4738223E-32 // 1.4738223 × 10−32

Beachten Sie, dass es zwar unendlich viele reelle Zahlen gibt, dass aber nur eine begrenzte Anzahl davon (nämlich genau 18437736874454810627) im Gleitkommaformat von JavaScript exakt dargestellt werden kann. Dies bedeutet, dass eine reelle Zahl in JavaScript häufig eine Annäherung an die tatsächliche Zahl ist. Dies stellt allerdings selten ein Problem dar, da der Näherungswert relativ gut ist.

## 3.1.4 Mit Zahlen arbeiten

JavaScript-Programme verwenden für die Arbeit mit Zahlen die von der Sprache bereitgestellten arithmetischen Operatoren: `+` für die Addition, `-` für die Subtraktion, `*` für die Multiplikation und `/` für die Division. Weitere Einzelheiten zu diesen und anderen arithmetischen Operatoren finden Sie in Kapitel 5.

Zusätzlich zu diesen einfachen arithmetischen Operationen unterstützt JavaScript auch komplexere mathematische Operationen, für die es zahlreiche mathematische Funktionen gibt, die zum Sprachkern gehören. Der Einfachheit halber sind alle diese Funktionen als Eigenschaften des Math-Objekts gespeichert, sodass wir auf sie immer mit dem Namensliteral `Math` zugreifen. Mit dem folgenden Code berechnen Sie zum Beispiel den Sinus des numerischen Werts `x`:

    sinus_von_x = Math.sin(x);

Um die Quadratwurzel eines numerischen Ausdrucks zu berechnen, verwenden Sie Code wie den folgenden:

    hypot = Math.sqrt(x*x + y*y);

Weitere Einzelheiten über die von JavaScript unterstützten mathematischen Funktionen finden Sie in Teil III im Abschnitt zum Math-Objekt und in den dortigen Aufstellungen.

## 3.1.5 Numerische Unwandlungen

JavaScript kann Zahlen als Strings formatieren und Strings zu Zahlen parsen. Diese Umwandlung von Zahlen in Strings und umgekehrt wird in "3.2 Strings" beschrieben.

## 3.1.6 Besondere numerische Werte

JavaScript verwendet mehrere numerische Spezialwerte. Wenn ein Gleitkommawert größer als die größte darstellbare endliche Zahl wird, dann ist das Ergebnis der Spezialwert »unendlich«, den JavaScript als `Infinity` ausgibt. Und wenn ein negativer Wert kleiner als die kleinste darstellbare negative Zahl wird, dann ist das Ergebnis minus unendlich. Dies wird als `-Infinity` ausgegeben.

Ein weiterer besonderer Zahlenwert von JavaScript wird dann zurückgegeben, wenn eine mathematische Operation (zum Beispiel die Division von null durch null) ein nicht definiertes Ergebnis oder einen Fehler liefert. In diesem Fall ist das Ergebnis der spezielle Wert Not-a-Number, der als `NaN` ausgegeben wird. Der Wert Not-a-Number verhält sich ungewöhnlich: Jeder Vergleich von Not-a-Number mit einer Zahl und sogar der Vergleich von Not-a-Number mit sich selbst hat das Ergebnis »ungleich«! Aus diesem Grund benötigt man die spezielle Funktion `isNaN( )`, um auf diesen Wert prüfen zu können. Eine verwandte Funktion, `isFinite( )`, prüft, ob eine Zahl kein NaN-Wert und weder minus noch plus unendlich ist.

Tabelle 3-1 führt mehrere Konstanten auf, die JavaScript für die Darstellung dieser besonderen numerischen Werte definiert.

Tabelle 3-1. Besondere numerische Konstanten

Konstante | Bedeutung

---|---

`Infinity` | Besonderer Wert für die Darstellung von »unendlich«

`NaN` | Besonderer Not-a-Number-Wert

`Number.MAX_VALUE` | Größte darstellbare Zahl

`Number.MIN_VALUE` | Kleinste darstellbare Zahl (am nächsten an null)

`Number.NaN` | Besonderer Not-a-Number-Wert

`Number.POSITIVE_INFINITY` | Besonderer Wert für die Darstellung von »unendlich«

`Number.NEGATIVE_INFINITY` | Besonderer Wert für die Darstellung von »negativ unendlich«

Die Konstanten `Infinity` und `NaN` werden vom Standard ECMAScript v1 definiert und sind erst ab der Version JavaScript 1.3 implementiert. Die verschiedenen Number-Konstanten waren hingegen bereits in JavaScript 1.1 implementiert.

* * *

[6] Java-Programmierern sollte dieses Format als der Typ `double` vertraut sein. Es ist auch das Format `double`, das in den meisten modernen Implementierungen von C und C++ verwendet wird.

# 3.2 Strings

Ein _String_ ist eine Folge von Unicode-Buchstaben, -Ziffern, -Interpunktionszeichen usw.: Es ist der JavaScript-Datentyp für die Darstellung von Text. Wie Sie gleich sehen werden, können Sie String-Literale in Ihre Programme einfügen, indem Sie sie in Paare einzelner oder doppelter Anführungszeichen einschließen. Beachten Sie, dass JavaScript im Gegensatz zu C, C++ und Java keinen Zeichendatentyp wie zum Beispiel `char` hat. Um ein einzelnes Zeichen darzustellen, verwenden Sie einfach einen String der Länge 1.

## 3.2.1 String-Literale

Ein String ist eine Folge von null oder mehr Unicode-Zeichen in einfachen oder doppelten Anführungszeichen (`'` oder `"`). Zeichen in doppelten Anführungszeichen dürfen innerhalb von Strings auftreten, die durch einfache Anführungszeichen begrenzt sind, und Zeichen in einfachen Anführungszeichen dürfen innerhalb von Strings auftreten, die von doppelten Anführungszeichen begrenzt sind. String-Literale müssen auf einer einzigen Zeile stehen und dürfen nicht auf mehrere Zeilen verteilt sein. Wenn Sie in ein String-Literal einen Zeilenumbruch einfügen müssen, verwenden Sie die Zeichenfolge `\n`, die im folgenden Abschnitt beschrieben wird. Beispiele für String-Literale sind:

    "" // Der leere String: Er hat null Zeichen
    'Test'
    "3.14"
    'name="mein_Formular"'
    "Hätten Sie nicht lieber das Buch von O'Reilly?"
    "Dieser String\nhat zwei Zeilen"
    "π ist das Verhältnis von Kreisumfang zu Kreisdurchmesser"

Wie der letzte Beispiel-String veranschaulicht, lässt der Standard ECMAScript v1 Unicode-Zeichen in String-Literalen zu. Implementierungen, die älter als JavaScript 1.3 sind, unterstützen in Strings normalerweise aber nur ASCII- oder Latin-1-Zeichen. Wie Sie im folgenden Abschnitt sehen werden, können Sie Unicode-Zeichen mit Hilfe besonderer »Escape-Sequenzen« in String-Literale einfügen. Dies ist nützlich, wenn Ihr Texteditor Unicode nicht vollständig unterstützt.

Wenn Sie Strings durch einfache Anführungszeichen begrenzen, müssen Sie bei Wörtern mit Apostroph wie zum Beispiel »Tu' es« oder »O'Reilly« aufpassen. Da der Apostroph mit dem einfachen Anführungszeichen übereinstimmt, müssen Sie jeden Apostroph, der innerhalb eines Strings mit einfachen Anführungszeichen vorkommt, durch einen Backslash (`\`) außer Kraft setzen (mehr dazu im folgenden Abschnitt).

Bei der Programmierung von clientseitigem JavaScript enthält der JavaScript-Code häufig Strings mit HTML-Code, und HTML-Code enthält häufig Strings mit JavaScript-Code. Ebenso wie JavaScript begrenzt auch HTML Strings entweder mit einfachen oder mit doppelten Anführungszeichen. Wenn Sie also JavaScript- und HTML-Code kombinieren, ist es hilfreich, die eine Form der Anführungszeichen für JavaScript und die andere für HTML zu verwenden. Im folgenden Beispiel ist »Danke« mit einfachen Anführungszeichen in einen JavaScript-Ausdruck eingefügt, der wiederum mit doppelten Anführungszeichen in ein HTML-Event-Handler-Attribut eingefügt ist.

    <a href="" onclick="alert('Danke')">Klick mich an</a>

## 3.2.2 Escape-Sequenzen in String-Literalen

Der Backslash (`\`) spielt in JavaScript-Strings eine besondere Rolle. Gemeinsam mit dem nachfolgenden Zeichen repräsentiert er ein Zeichen, das in einem String nicht auf andere Art dargestellt werden kann. So ist zum Beispiel `\n` eine _Escape-Sequenz_ , die ein Zeilenumbruchszeichen darstellt.[7]

Ein weiteres Beispiel, das im vorangegangenen Abschnitt erwähnt wurde, ist die Escape-Sequenz `\'`, die das einzelne Anführungszeichen (oder den Apostroph) darstellt. Diese Escape-Sequenz ist dann nützlich, wenn Sie ein Apostroph in ein Stringliteral einfügen möchten, das in einfache Anführungszeichen gefasst ist. Es ist offensichtlich, warum wir diese Zeichen als Escape-Sequenzen, also als »Fluchtsequenzen«, bezeichnen: Der Backslash ermöglicht es uns, der üblichen Interpretation eines einfachen Anführungszeichens zu entgehen. Wir markieren damit nicht das Ende des Strings, sondern wir verwenden es als Apostroph:

    'Glaub\'s mir, \'s ist kein Anführungszeichen'

Tabelle 3-2 enthält die Escape-Sequenzen von JavaScript sowie die Zeichen, die sie repräsentieren. Zwei Escape-Sequenzen sind generische Sequenzen, mit denen Sie jedes beliebige Zeichen angeben können, indem Sie seinen Unicode- oder Latin-1-Zeichencode als Hexadezimalzahl angeben. Die Sequenz `\xA9` stellt zum Beispiel das Urheberrechtssymbol © dar, dessen Latin-1-Codierung durch die Hexadezimalzahl A9 angegeben wird. Die Sequenz `\u` repräsentiert hingegen ein beliebiges Unicode-Zeichen, das mit vier Hexadezimalziffern angegeben wird. `\u03c0` steht zum Beispiel für das Zeichen π. Beachten Sie, dass der Standard ECMAScript v1 Unicode-Escape-Sequenzen verlangt, diese aber in Implementierungen, die älter als JavaScript 1.3 sind, in der Regel nicht unterstützt werden. Bei einigen JavaScript-Implementierungen ist es zudem möglich, ein Latin-1-Zeichen durch einen Backslash und drei Oktalziffern anzugeben. Diese Sequenz wird vom Standard ECMAScript v3 jedoch nicht unterstützt und sollte nicht mehr verwendet werden.

Tabelle 3-2. Escape-Sequenzen von JavaScript

Sequenz | Dargestelltes Zeichen

---|---

`\0` | das Zeichen NUL (`\u0000`)

`\b` | Rücktaste (`\u0008`)

`\t` | Horizontaler Tabulator (`\u0009`)

`\n` | Zeilenumbruch (Newline, `\u000A`)

`\v` | Vertikaler Tabulator (`\u000B`)

`\f` | Seitenvorschub (`\u000C`)

`\r` | Wagenrücklauf (Carriage Return, `\u000D`)

`\"` | doppeltes Anführungszeichen (`\u0022`)

`\'` | Apostroph oder einzelnes Anführungszeichen (`\u0027`)

`\\` | Backslash (`\u005C`)

`\x` XX | das Latin-1-Zeichen, das durch die beiden Hexadezimalziffern XX angegeben wird

`\u` XXXX | das Unicode-Zeichen, das durch die vier Hexadezimalziffern XXXX angegeben wird

`\` XXX | das Latin-1-Zeichen, das von den Oktalziffern XXX (zwischen 0 und 377) angegeben wird. Wird von ECMAScript v3 nicht unterstützt; verwenden Sie diese Escape-Sequenz nicht.

Die Backslash-Escape-Sequenz können Sie nicht vor einem Zeilenumbruch verwenden, um damit einen String (oder anderen JavaScript-Code) über mehrere Zeilen hinweg fortzusetzen oder um einen literalen Zeilenumbruch in einen String einzufügen. Wenn das Zeichen `\` vor einem anderen als den in Tabelle 3-2 genannten Zeichen steht, wird es einfach ignoriert (wobei zukünftige JavaScript-Versionen natürlich neue Escape-Sequenzen definieren können). `\#` ist zum Beispiel dasselbe wie `#`.

## 3.2.3 Mit Strings arbeiten

Eine der eingebauten Möglichkeiten von JavaScript besteht darin, Strings zu verketten. Wenn Sie den Operator `+` mit Zahlen verwenden, addiert er diese Zahlen. Wenn Sie diesen Operator jedoch auf Strings anwenden, verbindet er die Strings, indem er den zweiten an den ersten anhängt. Hierzu ein Beispiel:

    nachricht = "Hallo, " + "Welt"; // Erstellt den String "Hallo, Welt"
    gruss = "Willkommen auf meinem Blog," + " " + name;

Mit der Eigenschaft `length` des Strings können Sie seine Länge ermitteln, d.h. die Anzahl der in ihm enthaltenen Zeichen. Wenn die Variable `s` einen String enthält, können Sie folgendermaßen dessen Länge herausfinden:

    s.length

Es gibt mehrere Methoden, mit denen Sie mit Strings arbeiten können. Mit der folgenden Methode holen Sie zum Beispiel das letzte Zeichen vom String `s`:

    letztes_Zeichen = s.charAt(s.length – 1)

Sie können auch das zweite, dritte und vierte Zeichen vom String `s` holen:

    teilstring = s.substring(1,4);

Mit der folgenden Methode ermitteln Sie, an welcher Position im String `s` der Buchstabe "a" zum ersten Mal vorkommt:

    i = s.indexOf('a');

Es gibt noch zahlreiche weitere Methoden, die Sie für die Arbeit mit Strings verwenden können. Eine vollständige Dokumentation dieser Methoden finden Sie in Teil III im Abschnitt über das String-Objekt sowie in den zugehörigen Listings.

Wie Sie in den obigen Beispielen gesehen haben, beginnt die Indizierung von JavaScript-Strings (ebenso wie die von JavaScript-Arrays, dazu weiter unten mehr) bei null. Dies bedeutet, dass das erste Zeichen in einem String das Zeichen 0 ist. C-, C++- und Java-Programmierer sollten mit dieser Konvention gut zurechtkommen, während Programmierer, die an Sprachen gewöhnt sind, bei denen die Indizierung von Strings und Arrays bei 1 beginnt, für die Umstellung vielleicht einige Zeit brauchen.

In einigen Implementierungen von JavaScript kann man mit der Array-Notation einzelne Zeichen aus Strings lesen (aber nicht in Strings schreiben). Wir können den bereits gezeigten Aufruf von `charAt( )` also auch folgendermaßen schreiben:

    letztes_Zeichen = s[s.length – 1];

Diese Syntax ist im Standard ECMAScript v3 jedoch nicht enthalten. Sie ist nicht portierbar und sollte daher auch vermieden werden.

Wenn wir den Datentyp Objekt besprechen, werden Sie sehen, dass Objekteigenschaften und -methoden auf dieselbe Art verwendet werden wie die Stringeigenschaften und -methoden in den obigen Beispielen. Dies bedeutet jedoch nicht, dass Strings ein Objekttyp sind. Sie sind ein eigenständiger JavaScript-Datentyp. Strings verwenden für den Zugriff auf Eigenschaften und Methoden zwar die Objektsyntax, sind selbst aber keine Objekte. Am Ende dieses Kapitels werden wir Ihnen eine Begründung dafür nennen.

## 3.2.4 Zahlen in Strings umwandeln

Zahlen werden automatisch in Strings umgewandelt, wenn das erforderlich ist. Wenn eine Zahl in einem Stringverkettungsausdruck verwendet wird, wird die Zahl beispielsweise erst in einen String umgewandelt:

    var n = 100;
    var s = n + " Flaschen Bier an der Wand.";

Diese Umwandlung-durch-Verkettung-Funktionalität von JavaScript führt zu einer Technik, auf die Sie gelegentlich stoßen können. Um eine Zahl in einen String umzuwandeln, wird an sie einfach der leere String angehängt:

    var n_als_string = n + "";

Expliziter können Sie Zahl-in-String-Umwandlungen über die Funktion `String( )` durchführen:

    var stringwert = String(zahl);

Eine weitere Technik zur Umwandlung von Zahlen in Strings, nutzt die Methode `toString( )`:

    stringwert = zahl.toString( );

Die `toString( )`-Methode des Number-Objekts (elementare Zahlen werden in Number-Objekte umgewandelt, damit diese Methode aufgerufen werden kann) erwartet ein optionales Argument, das eine Wurzel oder Basis für die Umwandlung angibt. Geben Sie das Argument nicht an, wird die Umwandlung zur Basis 10 durchgeführt. Allerdings können Sie auch Zahlen zu anderen Basen (zwischen 2 und 36) umwandeln.[8] Zum Beispiel:

    var n = 17;
    binaer_string = n.toString(2);       // Ergibt "10001"
    oktal_string = "0" + n.toString(8);  // Ergibt "021"
    hex_string = "0x" + n.toString(16);  // Ergibt "0x11"

Ein Mangel der JavaScript-Versionen vor JavaScript 1.5 ist, dass es keine eingebaute Möglichkeit gibt, eine Zahl in einen String umzuwandeln, und dabei die Anzahl von Dezimalstellen anzugeben, die eingeschlossen werden sollen, oder anzugeben, welche Exponentialnotation verwendet werden soll. Das kann es erschweren Zahlen in konventionellen Formaten anzugeben, beispielsweise Zahlen, die Geldbeträge repräsentieren.

ECMAScript v3 und JavaScript 1.5 lösen dieses Problem, indem sie der Klasse Number drei neue Methoden für Zahl-in-String-Umwandlungen hinzufügen. `toFixed( )` wandelt eine Zahl in einen String um und zeigt eine angeforderte Anzahl von Nachkommastellen an. Sie verwendet keine Exponentialnotation. `toExponential( )` wandelt eine Zahl in einen String um und verwendet dabei die Exponentialnotation mit einer Stelle vor dem Komma und einer angeforderten Zahl von Stellen nach dem Komma. `toPrecision( )` zeigt eine Zahl an und verwendet dabei die angeforderte Anzahl signifikanter Ziffern. Die Methode verwendet die Exponentialnotation, wenn die Anzahl signifikanter Stellen nicht groß genug ist, um den gesamten ganzzahligen Anteil der Zahl anzuzeigen. Beachten Sie, dass alle drei Methoden die letzten Ziffern der Ergebnisstrings nach Bedarf runden. Betrachten Sie die folgenden Beispiele:

    var n = 123456.789;
    n.toFixed(0);       // "123457"
    n.toFixed(2);       // "123456.79"
    n.toExponential(1); // "1.2e+5"
    n.toExponential(3); // "1.235e+5"
    n.toPrecision(4);   // "1.235e+5"
    n.toPrecision(7);   // "123456.8"

## 3.2.5 Strings in Zahlen umwandeln

Wenn ein String in einem numerischen Kontext verwendet wird, wird er automatisch in eine Zahl umgewandelt. Das heißt beispielsweise, dass der folgende Code tatsächlich funktioniert:

    var produkt = "21" * "2"; // Das Produkt ist die Zahl 42.

Sie können das ausnutzen, um einen String in eine Zahl umzuwandeln, indem Sie einfach 0 von ihm abziehen:

    var zahl = stringwert – 0;

(Beachten Sie allerdings, dass der Versuch, einem String 0 hinzuzufügen, zu einer Stringverkettung statt einer Typumwandlung führt.)

Ein etwas weniger trickreicher und expliziterer Weg, einen String in eine Zahl umzuwandeln, ist das Aufrufen des `Number( )`-Konstruktors als Funktion:

    var zahl = Number(stringwert);

Das Problem mit einer solchen String-in-Zahl-Umwandlung ist, dass sie unnötig streng ist. Sie funktioniert mit Zahlen zur Basis 10 und erlaubt zwar führende und folgende Leerzeichen, aber keinerlei Nicht-Leerzeichen-Zeichen nach der Zahl.

Flexiblere Umwandlungen sind mit `parseInt( )` und `parseFloat( )` möglich. Diese Funktionen wandeln eine Zahl am Anfang eines Strings um und liefern sie zurück. Sie ignorieren dabei alle folgenden Nicht-Zahl-Zeichen. `parseInt( )` parst nur Ganzzahlen, während `parseFloat( )` Ganzzahlen und Gleitkommazahlen parst. Wenn ein String mit »0x« oder »0X« beginnt, interpretiert `parseInt( )` ihn als eine hexadezimale Zahl.[9] Zum Beispiel:

    parseInt("3 blinde Mäuse"); // Liefert 3
    parseFloat("3.14 Meter");   // Liefert 3.14
    parseInt("12.34");          // Liefert 12
    parseInt("0xFF");           // Liefert 255

`parseInt( )` akzeptiert außerdem ein zweites Argument, das die Wurzel (Basis) der Zahl angibt, die geparst werden soll. Zulässig sind Werte zwischen 2 und 36. Zum Beispiel:

    parseInt("11", 2);      // Liefert 3 (1*2 + 1)
    parseInt("ff", 16);     // Liefert 255 (15*16 + 15)
    parseInt("zz", 36);     // Liefert 1295 (35*36 + 35)
    parseInt("077", 8);     // Liefert 63 (7*8 + 7)
    parseInt("077", 10);    // Liefert 77 (7*10 + 7)

Wenn `parseInt( )` oder `parseFloat( )` den angegebenen String nicht in eine Zahl umwandeln können, liefern sie `NaN`:

    parseInt("elf");       // Liefert NaN
    parseFloat("i72.47");  // Liefert NaN

* * *

[7] C-, C++- und Java-Programmierer sind mit dieser und anderen Escape-Sequenzen von JavaScript bereits vertraut.

[8] Die ECMAScript-Spezifikation unterstützt das Basis-Argument für die `toString( )`-Methode, gestattet der Methode aber, einen implementierungsabhängigen String für jede Basis außer 10 zurückzuliefen. Deswegen können standardkonforme Implementierungen das Argument einfach ignorieren und immer ein Ergebnis zur Basis 10 zurückliefern. Allerdings werden die angeforderten Basen in der Praxis von den Implementierungen berücksichtigt.

[9] Die ECMAScript-Spezifikation sagt, dass Strings, die mit »0« (aber nicht »0x« oder »0X«) beginnen, von `parseInt( )` als eine Oktalzahl oder eine Dezimalzahl geparst werden können. Weil das Verhalten unbestimmt ist, sollten Sie `parseInt( )` nicht einsetzen, um Zahlen mit führenden Nullen zu parsen, es sei denn, sie geben explizit die zu verwendende Basis an

# 3.3 Boolesche Werte

Die Datentypen Zahl und String können viele bzw. unendlich viele verschiedene Werte annehmen. Der Datentyp Boolescher Wert hat hingegen nur zwei zulässige Werte, die durch die beiden Literale `true` und `false` dargestellt werden. Ein Boolescher Wert ist ein Wahrheitswert: Er besagt, ob etwas wahr ist oder nicht.

Boolesche Werte sind normalerweise das Ergebnis eines Vergleichs, den Sie in einem JavaScript-Programm vornehmen. Sehen wir uns hierzu ein Beispiel an:

    a == 4

Dieser Code prüft, ob der Wert in der Variablen `a` gleich der Zahl `4` ist. Falls dies zutrifft, ist das Ergebnis dieses Vergleichs der Boolesche Wert `true`. Ist `a` hingegen nicht gleich `4`, ist das Ergebnis des Vergleichs `false`.

Boolesche Werte verwendet man in JavaScript in der Regel in Strukturen zur Ablaufsteuerung. So führt die `if/else`-Anweisung in JavaScript zum Beispiel eine bestimmte Aktion durch, falls ein Boolescher Wert `true` ist, und eine andere Aktion, falls der Wert `false` ist. Normalerweise verbindet man einen Vergleich, der einen Booleschen Wert erzeugt, direkt mit einer Anweisung, die diesen Wert verwendet. Das Ergebnis sieht folgendermaßen aus:

    if (a == 4)
     b = b + 1;
    else
     a = a + 1;

Dieser Code prüft, ob `a` gleich `4` ist. Falls dies zutrifft, addiert er `1 zu b`. Ansonsten addiert er `1` zu `a`.

Manchmal ist es einfacher, sich die beiden möglichen Booleschen Werte nicht als `true` und `false` vorzustellen, sondern als `ein` (`true`) und `aus` (`false`) oder als `ja` (`true`) und `nein` (`false`).

## 3.3.1 Boolesche Typumwandlungen

Boolesche Werte können leicht in andere Typen umgewandelt werden und umgekehrt. Diese Umwandlungen erfolgen oft automatisch.[10] Wenn ein Boolescher Wert in einem numerischen Kontext verwendet wird, ergibt `true` die Zahl 1 und `false` die Zahl 0. Wenn ein Boolescher Wert in einem Stringkontext verwendet wird, ergibt `true` den String »true« und `false` den String »false«.

Wenn eine Zahl an einem Ort verwendet wird, an dem ein Boolescher Wert erwartet wird, wird die Zahl in `true` umgewandelt, es sei denn, die Zahl ist 0 oder `NaN`, die beide in `false` umgewandelt werden. Wenn ein String an einem Ort verwendet wird, an dem ein Boolescher Wert erwartet wird, wird er in `true` umgewandelt, es sei denn, es ist der leere String, der in `false` umgewandelt wird. `null` und der Wert `undefined` werden in `false` umgewandelt, jedes Objekt, jedes Array und jede Funktion, die nicht `null` sind, werden in `true` umgewandelt.

Wenn Sie Ihre Typumwandlungen lieber explizit angeben wollen, können Sie die Funktion `Boolean( )` verwenden:

    var x_als_boolean = Boolean(x);

Eine andere Technik ist die doppelte Verwendung des Booleschen NICHT-Operators:

    var x_als_boolean = !!x;

* * *

[10] C-Programmierer sollten darauf achten, dass JavaScript für Boolesche Werte einen eigenständigen Datentyp hat, während C sie einfach mit ganzzahligen Werten simuliert. Java-Programmierer sollten darauf achten, dass JavaScript zwar den Typ Boolescher Wert hat, dieser aber bei Weitem nicht so rein ist wie der Java-Datentyp `boolean`. In JavaScript kann man Boolesche Werte leicht in andere Datentypen umwandeln und umgekehrt, sodass die Verwendung der Booleschen Werte in JavaScript wesentlich mehr Ähnlichkeit mit der in C als mit der in Java hat.

# 3.4 Funktionen

Eine _Funktion_ ist ausführbarer Code, der von einem JavaScript-Programm definiert wird oder in der JavaScript-Implementierung vordefiniert ist. Obwohl eine Funktion nur ein einziges Mal definiert wird, kann ein JavaScript-Programm jede Funktion beliebig oft aufrufen oder ausführen. Einer Funktion kann man _Argumente_ oder _Parameter_ übergeben und damit den Wert oder die Werte angeben, auf dem oder denen sie ihre Berechnungen vornehmen soll. Außerdem kann eine Funktion einen Wert zurückgeben, der das Ergebnis dieser Berechnung darstellt. JavaScript-Implementierungen bieten viele vordefinierte Funktionen wie zum Beispiel die Funktion `Math.sin( )`, die den Sinus eines Winkels berechnet.

Darüber hinaus können JavaScript-Programme auch eigene Funktionen definieren. Der entsprechende Code sieht folgendermaßen aus:

    function quadrat(x) // Die quadrat genannte Funktion erwartet
                        // ein Argument x.
    {                   // Hier beginnt der Body der Funktion.
     return x*x;        // Die Funktion quadriert ihr Argument und gibt diesen Wert zurück.
    }                   // Hier endet die Funktion.

Sobald Sie eine Funktion definiert haben, können Sie sie aufrufen, wobei Sie hinter dem Namen der Funktion in Klammern eine optionale Liste von Argumenten angeben, deren Elemente durch Kommata voneinander getrennt sind. Die folgenden Zeilen sind Funktionsaufrufe:

    y = Math.sin(x);
    y = quadrat(x);
    d = berechne_abstand(x1, y1, z1, x2, y2, z2);
    move( );

Eine wesentliche Eigenschaft von JavaScript besteht darin, dass Funktionen Werte sind, die mit JavaScript-Code verändert werden können. In vielen Programmiersprachen, darunter auch Java, sind Funktionen nur ein syntaktisches Merkmal der Sprache; sie können zwar definiert und aufgerufen werden, sind aber keine Datentypen. Die Tatsache, dass Funktionen in JavaScript echte Werte sind, erhöht die Flexibilität dieser Sprache erheblich. Sie bedeutet, dass Funktionen in Variablen, Arrays und Objekten gespeichert und als Argumente an andere Funktionen übergeben werden können. Diese Möglichkeit ist häufig nützlich. Die Definition und der Aufruf von Funktionen sowie ihre Verwendung als Datenwerte wird in Kapitel 8 behandelt.

Da Funktionen ebenso wie Zahlen und Strings Werte sind, können sie auch genauso wie andere Werte an Objekteigenschaften zugewiesen werden. Wenn eine Funktion einer Eigenschaft eines Objekts zugewiesen wird (der Datentyp Objekt und die Objekteigenschaften werden im "3.5 Objekte" beschrieben), bezeichnet man sie häufig als eine Methode dieses Objekts. Methoden sind ein wesentlicher Bestandteil der objektorientierten Programmierung. In Kapitel 7 werden wir ausführlicher auf Methoden eingehen.

## 3.4.1 Funktionsliterale

Im vorherigen Abschnitt haben wir die Funktion `quadrat( )` definiert. Die meisten Funktionen in den meisten JavaScript-Programmen werden mit der Syntax definiert, die wir bei diesem Beispiel verwendet haben. Allerdings gibt JavaScript für die Definition von Funktionsliteralen eine Syntax vor. Die Definition eines Funktionsliterals beginnt mit dem Schlüsselwort `function`, hinter dem optional ein Funktionsname steht, dem eine in runde Klammern gesetzte Liste von Funktionsargumenten und der in geschweifte Klammern gesetzte Rumpf der Funktion folgen. Anders ausgedrückt: Der einzige Unterschied zwischen einem Funktionsliteral und einer Funktionsdefinition besteht darin, dass das Funktionsliteral keinen Namen haben muss. Wichtig ist vor allem, dass Funktionsliterale in anderen JavaScript-Ausdrücken vorkommen können. So können wir die Funktion `quadrat( )` wie bisher mit einer Funktionsdefinition definieren:

    function quadrat(x) { return x*x; }

Wir können sie aber auch mit einem Funktionsliteral definieren:

    var quadrat = function(x) { return x*x; }

Auf diese Art definierte Funktionen bezeichnet man manchmal als _Lambda-Funktionen_. Dies ist ein Tribut an die Programmiersprache LISP, die es als eine der ersten zuließ, dass unbenannte Funktionen als literale Datenwerte in ein Programm eingebettet werden. Es ist möglicherweise nicht unmittelbar ersichtlich, warum man in einem Programm überhaupt Funktionsliterale verwenden sollte. Weiter unten werden wir jedoch sehen, dass sie in komplexeren Skripten durchaus nützlich und sinnvoll sein können.

Es gibt noch eine weitere Möglichkeit, eine Funktion zu definieren: Sie können die Argumentliste und den Body der Funktion wie im folgenden Beispiel als Strings an den Konstruktor `Function()` übergeben:

    var quadrat = new Function("x", "return x*x;");

Es ist nicht oft sinnvoll, eine Funktion auf diese Art zu definieren. Normalerweise ist es recht umständlich, den Funktionsbody als String auszudrücken, und in vielen JavaScript-Implementierungen sind auf diese Art definierte Funktionen weniger effizient als Funktionen, die mit einem der beiden anderen Verfahren definiert wurden.

# 3.5 Objekte

Ein _Objekt_ ist eine Sammlung benannter Werte. Diese werden normalerweise als Eigenschaften des Objekts bezeichnet. (Manchmal werden sie auch _Felder_ des Objekts genannt, aber dieser Begriff kann Verwirrung stiften.) Um auf eine Eigenschaft eines Objekts zu verweisen, geben Sie das Objekt an, fügen dann einen Punkt ein und nennen den Namen der Eigenschaft. Wenn das Objekt `bild` zum Beispiel die Eigenschaften `width` und `height` hat, können wir folgendermaßen auf diese Eigenschaften verweisen:

    bild.breite
    bild.hoehe

Eigenschaften von Objekten stimmen in vielerlei Hinsicht mit JavaScript-Variablen überein: Sie können jeden beliebigen Datentyp enthalten, einschließlich Arrays, Funktionen und andere Objekte. Daher könnte Ihnen durchaus JavaScript-Code wie der folgende begegnen:

    document.meinformular.button

Dieser Code verweist auf die Eigenschaft `button` eines Objekts, das in der Eigenschaft `meinformular` des Objekts `document` gespeichert ist.

Wie wir bereits erwähnt haben, wird eine Funktion, deren Wert in einer Eigenschaft eines Objekts gespeichert ist, häufig als Methode bezeichnet, wobei der Eigenschaftsname zum Methodennamen wird. Um eine Methode eines Objekts aufzurufen, extrahieren Sie zuerst mit der `.`-Syntax den Funktionswert aus dem Objekt und rufen diese Funktion dann mit der `( )`-Syntax auf. Mit dem folgenden Code können Sie zum Beispiel die Methode `write( )` des Document-Objekts aufrufen:

    document.write("Dies ist ein Test");

In JavaScript können Objekte als assoziative Arrays dienen, d.h., sie können beliebige Datenwerte mit beliebigen Strings verbinden. Bei einer solchen Verwendung eines Objekts ist für den Zugriff auf die Objekteigenschaften normalerweise eine andere Syntax erforderlich: Ein String, der den Namen der gewünschten Eigenschaften enthält, wird in eckige Klammern eingefügt. Mit dieser Syntax können wir folgendermaßen auf die Eigenschaften des bereits genannten Objekts `bild` zugreifen:

    bild["breite"]
    bild["hoehe"]

Assoziative Arrays sind ein mächtiger Datentyp. Sie sind für zahlreiche Programmierverfahren nützlich. In Kapitel 7 werden Sie mehr darüber erfahren, wie Objekte traditionell und als assoziative Arrays verwendet werden.

## 3.5.1 Objekte erzeugen

Wie Sie in Kapitel 7 erfahren werden, erzeugt man Objekte, indem man spezielle Konstruktorfunktionen aufruft. So erzeugen zum Beispiel alle folgenden Zeilen neue Objekte:

    var o = new Object( );
    var jetzt = new Date( );
    var muster = new RegExp("\\sjava\\s", "i");

Sobald Sie ein Objekt erzeugt haben, können Sie es verwenden und seine Eigenschaften ganz nach Bedarf setzen:

    var punkt = new Object( );
    punkt.x = 2.3;
    punkt.y = −1.2;

## 3.5.2 Objekt-Literale

JavaScript definiert eine Syntax für Objekt-Literale, mit der Sie ein Objekt erzeugen und seine Eigenschaften angeben können. Ein Objekt-Literal (das auch als Objekt-Initialisierer bezeichnet wird) besteht aus einer in geschweifte Klammern gesetzten Liste von Eigenschaft/Wert-Paaren, bei der die Eigenschaften und Werte jeweils durch Doppelpunkte und die einzelnen Paare durch Kommata voneinander getrennt sind. Das Objekt `punkt` aus dem obigen Codebeispiel kann also folgendermaßen erzeugt und initialisiert werden:

    var punkt = { x:2.3, y:-1.2 };

Objekt-Literale können auch geschachtelt werden:

    var rechteck = { obenLinks: { x: 2, y: 2 },
             untenRechts: { x: 4, y: 4}
            };

Die in Objekt-Literalen verwendeten Eigenschaftswerte brauchen außerdem keine Konstanten zu sein. Sie können beliebige JavaScript-Ausdrücke sein. Außerdem können die Eigenschaftsnamen in Objekt-Literalen Strings anstelle von Bezeichnern sein:

    var quadrat = { "obenLinks": { x:punkt.x, y:punkt.y },
                  'untenRechts': { x:(punkt.x + seite), y:(punkt.y+seite) }};

## 3.5.3 Objektumwandlungen

Wenn ein Nicht-`null`-Objekt in einem Booleschen Kontext verwendet wird, wird es in `true` umgewandelt. Wenn ein Objekt in einem Stringkontext verwendet wird, ruft JavaScript die `toString( )`-Methode des Objekts auf und verwendet den von dieser Methode zurückgelieferten Stringwert. Wenn ein Objekt in einem numerischen Kontext verwendet wird, ruft JavaScript zunächst die `valueOf( )`-Methode des Objekts auf. Liefert diese Methode einen elementaren Wert zurück, wird dieser Wert verwendet. In den meisten Fällen liefert die Methode `valueOf( )` allerdings das Objekt selbst zurück. In diesem Fall wandelt JavaScript zuerst das Objekt über die Methode `toString( )` in einen String um und versucht dann, diesen in eine Zahl umzuwandeln.

Die Umwandlung eines Objekts in elementare numerische Werte hat einige Haken. Deswegen werden wir zu diesem Thema am Ende des Kapitels noch einmal zurückkehren.

# 3.6 Arrays

Ebenso wie ein Objekt ist auch ein _Array_ eine Sammlung von Datenwerten. Während aber jeder Datenwert in einem Objekt einen Namen hat, hat jeder Wert in einem Array eine Nummer, den sogenannten Index. In JavaScript rufen Sie einen Wert aus einem Array ab, indem Sie hinter dem Array-Namen einen Index in eckigen Klammern einfügen. Wenn ein Array zum Beispiel `a` heißt und `i` eine nicht-negative ganze Zahl ist, dann ist `a[i]` ein Element des Arrays. Array-Indizes beginnen bei null. `a[2]` verweist also auf das dritte Element des Arrays `a`.

Arrays können einen beliebigen JavaScript-Datentyp enthalten, also auch Funktionen oder Referenzen auf andere Arrays oder Objekte:

    document.images[1].width

Dieser Code verweist auf die Eigenschaft `width` eines Objekts, das im zweiten Element eines Arrays gespeichert ist, das wiederum in der Eigenschaft `images` des Objekts `document` gespeichert ist.

Beachten Sie, dass sich die hier beschriebenen Arrays von den assoziativen Arrays unterscheiden, die wir im "3.5 Objekte" beschrieben haben. Die normalen Arrays, um die es hier geht, werden mit nicht-negativen ganzen Zahlen indiziert. Assoziative Arrays werden hingegen mit Strings indiziert. Beachten Sie auch, dass JavaScript mehrdimensionale Arrays, außer als Arrays von Arrays, nicht unterstützt. Da JavaScript eine nicht-typisierte Sprache ist, brauchen die Elemente außerdem nicht alle denselben Typ zu haben, wie es bei typisierten Sprachen wie zum Beispiel Java erforderlich ist. Über Arrays werden Sie in Kapitel 7 mehr erfahren.

## 3.6.1 Arrays erzeugen

Arrays können mit der Konstruktorfunktion `Array( )` angelegt werden. Sobald Sie ein Array angelegt haben, können Sie ihm leicht eine beliebige Anzahl indizierter Elemente zuweisen:

    var a = new Array( );
    a[0] = 1.2;
    a[1] = "JavaScript";
    a[2] = true;
    a[3] = { x:1, y:3 };

Sie können Arrays aber auch initialisieren, indem Sie dem Konstruktor `Array( )` Array-Elemente übergeben. Damit können wir den obigen Code für die Erstellung und Initialisierung eines Arrays auch folgendermaßen schreiben:

    var a = new Array(1.2, "JavaScript", true, { x:1, y:3 });

Wenn Sie dem Konstruktor `Array( )` nur eine einzige Zahl übergeben, gibt diese die Länge des Arrays an:

    var a = new Array(10);

Dieser Code erstellt ein neues Array mit zehn undefinierten Elementen.

## 3.6.2 Array-Literale

JavaScript definiert eine Literalsyntax für die Erzeugung und Initialisierung von Arrays. Ein Array-Literal (Array-Initialisierer) ist eine in eckige Klammern gesetzte Liste von Werten, deren Elemente jeweils durch Kommata voneinander getrennt werden. Die Werte innerhalb der eckigen Klammern werden den Array-Indizes sequenziell zugewiesen, wobei man mit null beginnt. So kann man den obigen Code für die Erstellung und Initialisierung eines Arrays auch folgendermaßen schreiben:

    var a = [1.2, "JavaScript", true, { x:1, y:3 }];

Ebenso wie Objekt-Literale können auch Array-Literale geschachtelt werden:

    var matrix = [[1,2,3], [4,5,6], [7,8,9]];

Ebenso wie die Elemente von Objekt-Literalen können auch die Elemente von Array-Literalen beliebige Ausdrücke sein und sind nicht auf Konstanten beschränkt:

    var basis = 1024;
    var tabelle = [basis, basis+1, basis+2, basis+3];

In ein Array-Literal kann man undefinierte Elemente einfügen, indem man einfach einen Wert zwischen zwei Kommata weglässt. Das folgende Array enthält zum Beispiel fünf Elemente, von denen drei nicht definiert sind:

    var duenn_besetztes_Array = [1,,,,5];

# 3.7 null

Das JavaScript-Schlüsselwort `null` ist ein besonderer Wert, der für »kein Wert« steht. `null` wird normalerweise als ein spezieller Wert des Typs Objekt betrachte, d.h. als ein Wert, der »kein Objekt« angibt. `null` ist ein eindeutiger Wert, der sich von allen anderen Werten unterscheidet. Wenn eine Variable den Wert `null` enthält, weiß man, dass sie kein zulässiges Objekt oder Array, keine zulässige Zahl und keinen zulässigen String oder Booleschen Wert enthält.[11]

Wenn `null` in einem Booleschen Kontext verwendet wird, wird es in `false` umgewandelt. Wird es in einem numerischen Kontext verwendet, wird es in 0 umgewandelt. Und wenn es in einem Stringkontext verwendet wird, wird es in »null« umgewandelt.

* * *

[11] C- und C++-Programmierer sollten darauf achten, dass `null` in JavaScript im Gegensatz zu C und C++ nicht dasselbe wie 0 ist. `null` wird zwar automatisch in 0 umgewandelt, aber es ist nicht gleichbedeutend mit 0.

# 3.8 undefined

Es gibt noch einen weiteren besonderen Wert, der in JavaScript gelegentlich benutzt wird. Dies ist der Wert `undefined`, der zurückgegeben wird, wenn man entweder eine Variable benutzt, die zwar deklariert, der aber kein Wert zugewiesen wurde, oder wenn man eine Objekteigenschaft benutzt, die es nicht gibt. Beachten Sie, dass der besondere Wert `undefined` nicht dasselbe ist wie `null`.

Obwohl `null` und `undefined` jeweils eindeutige Werte sind, betrachtet sie der Gleichheitsoperator `==` als gleich. Betrachten Sie zum Beispiel den folgenden Code:

    meine.eigenschaft == null

Dieser Vergleich ist `true`, wenn entweder die Eigenschaft `meine.eigenschaft` nicht existiert oder wenn sie zwar existiert, aber den Wert `null` enthält. Da sowohl `null` als auch der Wert `undefined` das Fehlen eines Wertes angeben, ist diese Gleichheit häufig genau das, was man möchte. Wenn Sie jedoch eindeutig zwischen einem `null`-Wert und einem `undefined`-Wert unterscheiden müssen, verwenden Sie entweder den Identitätsoperator `===` oder den Operator `typeof` (mehr dazu in Kapitel 5).

Im Gegensatz zu `null` ist `undefined` in JavaScript kein reserviertes Wort. Der Standard ECMAScript v3 legt fest, dass es immer eine globale Variable namens `undefined` gibt, deren Anfangswert der Wert `undefined` ist. In einer standardkonformen Implementierung können Sie `undefined` also als ein Schlüsselwort behandeln, sofern Sie der Variablen keinen Wert zuweisen.

Wenn Sie nicht sicher wissen, ob Ihre Implementierung die Variable `undefined` hat, können Sie sie einfach selbst deklarieren:

    var undefined;

Indem Sie die Variable deklarieren, aber nicht initialisieren, stellen Sie sicher, dass Ihre Implementierung den Wert `undefined` hat. Der Operator `void` (mehr dazu in Kapitel 5) bietet eine weitere Möglichkeit, den Wert `undefined` zu erhalten.

Wenn der Wert `undefined` in einem Booleschen Kontext verwendet wird, wird er in `false` umgewandelt. Wenn er in einem numerischen Kontext verwendet wird, wird er in `NaN` umgewandelt. Und wenn er in einem Stringkontext verwendet wird, wird er in »undefined« umgewandelt.

# 3.9 Das Date-Objekt

In den vorangegangenen Abschnitten haben wir alle elementaren Datentypen beschrieben, die von JavaScript unterstützt werden. Datums- und Zeitwerte gehören nicht zu diesen grundlegenden Typen. JavaScript stellt jedoch eine Objekt-Klasse bereit, die Datums- und Uhrzeitangaben darstellt und zur Bearbeitung dieses Datentyps verwendet werden kann. In JavaScript wird ein Date-Objekt mit dem Operator `new` und dem Konstruktor `Date( )` erzeugt (den Operator `new` besprechen wir in Kapitel 5 und die Erzeugung von Objekten in Kapitel 7):

    // Erzeuge ein Objekt mit dem jetzigen Datum und der jetzigen Uhrzeit.
    var jetzt = new Date( );
    // Erzeuge ein Date-Objekt, das Weihnachten darstellt.
    // Da Monate ab null zählen, ist Dezember der Monat 11!
    var weihnacht = new Date(2006, 11, 24);

Die Methoden des Date-Objekts ermöglichen es, die verschiedenen Datums- und Zeitwerte zu holen und das Datum entweder gemäß Ortszeit oder gemäß GMT in einen String umzuwandeln:

    weihnacht.setFullYear(weihnacht.getFullYear( ) + 1);
    // Wechsele auf nächste Weihnachten.
    var wochentag = weihnacht.getDay( );
    // Heiligabend 2007 ist ein Mittwoch.
    document.write("Wir haben den: " + jetzt.toLocaleString( ));
    // Datum/Uhrzeit von jetzt.

Das Date-Objekt definiert außerdem Funktionen (nicht Methoden; sie werden nicht über ein Date-Objekt aufgerufen), mit denen man ein Datum, das in einem String oder in numerischer Form angegeben ist, in eine interne, auf Millisekunden basierende Darstellung umwandeln kann, die für einige Rechenoperationen mit Datumsangaben nützlich ist.

Die vollständige Dokumentation des Date-Objekts und seiner Methoden finden Sie in Teil III.

# 3.10 Reguläre Ausdrücke

Reguläre Ausdrücke bieten eine mächtige Syntax für die Beschreibung von Textmustern; sie dienen zum Mustervergleich und zur Implementierung von Suchen-und-Ersetzen-Operationen. JavaScript hat die Syntax für reguläre Ausdrücke aus der Programmiersprache Perl übernommen.

In JavaScript werden reguläre Ausdrücke durch das RegExp-Objekt dargestellt und können mit dem Konstruktor `RegExp( )` erzeugt werden. Ebenso wie das Date-Objekt gehört auch das RegExp-Objekt nicht zu den primitiven Datentypen von JavaScript. Es ist einfach eine spezialisierte Objektart, die von allen konformen JavaScript-Implementierungen bereitgestellt wird.

Im Gegensatz zum Date-Objekt haben RegExp-Objekte jedoch eine Literalsyntax und können direkt in JavaScript-Programme eingefügt werden. Text zwischen zwei Schrägstrichen bildet einen literalen regulären Ausdruck. Hinter dem zweiten Schrägstrich können noch ein oder mehrere Buchstaben folgen, die die Bedeutung des Musters verändern. Hierzu einige Beispiele:

    /^HTML/
    /[1-9][0-9]*/
    /\bjavascript\b/i

Die Grammatik der regulären Ausdrücke ist komplex und wird in Kapitel 11 ausführlich dokumentiert. Vorerst brauchen Sie nur zu wissen, wie ein literaler regulärer Ausdruck in JavaScript-Code aussieht.

# 3.11 Fehlerobjekte

ECMAScript v3 definiert eine Reihe von Klassen, die Fehler darstellen. Der JavaScript-Interpreter »löst« ein Objekt eines dieser Typen »aus«, sobald ein Laufzeitfehler auftritt. (Über das Auslösen und Abfangen von Fehlern erfahren Sie mehr, wenn wir in Kapitel 6 die Anweisungen `throw` und `try` beschreiben.) Jedes Fehlerobjekt hat die Eigenschaft `message`, die eine implementierungsspezifische Fehlermeldung enthält. Es gibt die folgenden vordefinierten Fehlerobjekte: Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError und URIError. In Teil III finden Sie weitere Einzelheiten zu diesen Klassen.

# 3.12 Zusammenfassung der Typumwandlungen

Bei der Beschreibung der einzelnen Datentypen in den vorangehenden Abschnitten habe ich erwähnt, wie die einzelnen Typen in die anderen Typen umgewandelt werden. Die Grundregel ist, dass ein Wert eines Typs, der in einem Kontext verwendet wird, der einen anderen Typ erwartet, von JavaScript automatisch in den erforderlichen Wert umgewandelt wird. Wenn eine Zahl in einem Booleschen Kontext verwendet wird, wird sie beispielsweise in einen Booleschen Wert umgewandelt. Wenn ein Objekt in einer Stringverkettung verwendet wird, wird es in einen String umgewandelt. Wenn ein String in einem numerischen Kontext verwendet wird, versucht JavaScript ihn in eine Zahl umzuwandeln. Tabelle 3-3 fasst all diese Typumwandlungen zusammen und gibt die Umwandlung an, die durchgeführt wird, wenn ein bestimmter Typ von Wert in einem bestimmten Kontext verwendet wird.

Tabelle 3-3. Automatische Datentypumwandlung

Wert

|  |

Kontext, in dem der Wert verwendet wird

|
---|---|---|---
|

String | Zahl | Boolesch | Objekt

undefinierter Wert | `"undefined"` | `NaN` | `false` | Fehler

`null` | `"null"` | `0` | `false` | Fehler

nicht-leerer String | unverändert | der numerische Wert des Strings oder `NaN` | `true` | String-Objekt

leerer String | unverändert | `0` | `false` | String-Objekt

`0` | `"0"` | unverändert | `false` | Number-Objekt

`NaN` | `"NaN"` | unverändert | `false` | Number-Objekt

unendlich | `"Infinity"` | unverändert | `true` | Number-Objekt

minus unendlich | `"-Infinity"` | unverändert | `true` | Number-Objekt

jede andere Zahl | Stringwert der Zahl | unverändert | `true` | Number-Objekt

`true` | `"true"` | `1` | unverändert | Boolean-Objekt

`false` | `"false"` | `0` | unverändert | Boolean-Objekt

Objekt | `toString( )` | `valueOf( )`, `toString( )` oder `NaN` | `true` | unverändert

# 3.13 Wrapper-Objekte für primitive Datentypen

Bei der Besprechung der Strings weiter oben in diesem Kapitel habe ich auf ein eigenartiges Merkmal dieses Datentyps hingewiesen: Für die Operation auf Strings verwendet man die Objektnotation.[12] Eine typische Operation auf Strings könnte zum Beispiel folgendermaßen aussehen:

    var s = "Jetzt heißt es Maßhalten.";
    var letztes_wort = s.substring(s.lastIndexOf(" ")+1, s.length);

Wenn man es nicht besser wüsste, könnte man meinen, `s` sei ein Objekt, dessen Methoden wir aufrufen und dessen Eigenschaftswerte wir lesen.

Was geschieht hier? Sind Strings Objekte oder primitive Datentypen? Der Operator `typeof` (mehr dazu in Kapitel 5) teilt uns mit, dass Strings den Datentyp »String« haben, der sich vom Datentyp »Objekt« eindeutig unterscheidet. Warum werden Strings also mit der Objektnotation bearbeitet?

Es ist einfach so, dass für jeden der drei primitiven Datentypen eine entsprechende Objekt-Klasse definiert ist. Dies bedeutet, dass JavaScript nicht nur die Datentypen Zahl, String und Boolescher Wert unterstützt, sondern auch die Klassen Number, String und Boolean. Diese Klassen sind die Hüllen (engl. _wrapper_ ) der primitiven Datentypen. Ein Wrapper enthält denselben primitiven Datentyp, definiert aber auch Eigenschaften und Methoden, mit denen diese Daten bearbeitet werden können.

JavaScript kann Werte flexibel von einem Typ in einen anderen umwandeln. Wenn wir in einem Objektkontext einen String verwenden, d.h., wenn wir auf eine Eigenschaft oder Methode dieses Strings zuzugreifen versuchen, erzeugt JavaScript intern ein String-Wrapper-Objekt für den Stringwert. Dieses String-Objekt wird anstelle des primitiven Datentyps String verwendet. Da für das Objekt Eigenschaften und Methoden definiert sind, verläuft die Verwendung des primitiven Datentyps in einem Objektkontext erfolgreich. Dasselbe gilt natürlich auch für die anderen Grundtypen und ihre entsprechenden Wrapper-Objekte. Allerdings verwenden wir die anderen primitiven Datentypen wesentlich seltener in einem Objektkontext als Strings.

Wenn wir einen String in einem Objektkontext verwenden, ist das erzeugte String-Objekt nur vorübergehend vorhanden: Es dient dazu, den Zugriff auf eine Eigenschaft oder Methode zu ermöglichen, wird danach aber nicht mehr benötigt und daher vom System wieder freigegeben. Nehmen wir zum Beispiel an, `s` sei ein String, und mit einer Zeile wie der folgenden wird die Länge dieses Strings festgelegt:

    var l = s.length;

In diesem Fall bleibt `s` ein String. Der ursprüngliche Stringwert wird nicht verändert. Ein neues, nur vorübergehend vorhandenes String-Objekt, das uns den Zugriff auf die Eigenschaft `length` ermöglicht, wird erzeugt und anschließend wieder gelöscht, wobei sich der ursprüngliche Wert `s` nicht verändert. Wenn Sie nun meinen, dieses Konzept klinge einerseits elegant und andererseits übermäßig kompliziert, dann haben Sie recht. Normalerweise führen JavaScript-Implementierungen diese interne Umwandlung höchst effizient durch, und Sie brauchen sich keine Gedanken darüber zu machen.

Wenn wir in unserem Programm ausdrücklich ein String-Objekt verwenden möchten, müssen wir ein dauerhaftes Objekt erzeugen, das nicht automatisch vom System gelöscht wird. String-Objekte werden genau wie andere Objekte erzeugt, also mit dem Operator `new`:

    var s = "Hallo Welt";              // Ein einfacher Stringwert
    var S = new String("Hallo Welt");  // Ein String-Objekt

Was können wir mit dem String-Objekt `S` machen, nachdem wir es erzeugt haben? Nichts, was wir nicht auch mit dem entsprechenden einfachen Stringwert machen könnten. Wenn wir den Operator `typeof` verwenden, teilt er uns mit, dass `s` in der Tat kein Stringwert, sondern ein Objekt ist, aber davon abgesehen können wir in der Regel nicht zwischen einem einfachen String und dem String-Objekt unterscheiden.[13] Wie wir bereits gesehen haben, werden Strings bei Bedarf automatisch in String-Objekte umgewandelt. Auch das Gegenteil trifft zu. Wenn wir an einer Stelle, an der ein einfacher Stringwert erwartet wird, ein String-Objekt verwenden, wandelt JavaScript das Objekt automatisch in einen Stringwert um. Wenn wir unser String-Objekt also mit dem Operator `+` verwenden, dann wird ein einfacher, temporärer Stringwert erzeugt, damit die Verkettung der Strings vorgenommen werden kann:

    msg = S + '!';

Denken Sie daran, dass alles, was wir in diesem Abschnitt zu den Stringwerten und String-Objekten gesagt haben, auch für die Zahlenwerte und Booleschen Werte sowie für die entsprechenden Number- und Boolean-Objekte gilt. Mehr über die Klassen Number und Boolean erfahren Sie bei den jeweiligen Einträgen in Teil III.

Schließlich sollten Sie noch beachten, dass jede Zahl, jeder String oder jeder Boolesche Wert mit der Funktion `Object( )` in das entsprechende Wrapper-Objekt umgewandelt werden kann:

    var number_wrapper = Object(3);

* * *

[12] Dieser Abschnitt behandelt fortgeschrittenere Themen, die Sie beim ersten Lesen eventuell überspringen sollten.

[13] Beachten Sie jedoch, dass die Methode `eval( )` Stringwerte und String-Objekte unterschiedlich behandelt und sich nicht so verhält, wie Sie es erwarten, wenn Sie ihr anstelle eines einfachen Stringwertes aus Versehen ein String-Objekt übergeben.

# 3.14 Umwandlungen von Objekten in elementare Werte

In der Regel können Objekte auf die in "3.5.3 Objektumwandlungen" beschriebene gradlinige Weise in elementare Werte umgewandelt werden. Einige Details dieser Umwandlung verlangen allerdings zusätzliche Erörterung.[14]

Beachten Sie zunächst, dass ein Nicht-`null`-Objekt, das in einem Booleschen Kontext verwendet wird, in `true` umgewandelt wird. Das gilt für alle Objekt (einschließlich Arrays und Funktionen), selbst für alle Wrapper-Objekte, die einen primitiven Wert repräsentieren, der in `false` umgewandelt würde. Beispielsweise werden alle folgenden Objekte in `true` umgewandelt, wenn sie in einem Booleschen Kontext verwendet werden:

    new Boolean(false) // Der interne Wert ist false, aber das Objekt wird in true
                       // umgewandelt
    new Number(0)
    new String("")
    new Array( )

Tabelle 3-3 hat gezeigt, dass Objekte in Zahlen konvertiert werden, indem zunächst die `valueOf( )`-Methode des Objekts aufgerufen wird. Die meisten Objekte erben die Default-`valueOf( )`-Methode der Klasse Object, die einfach das Objekt selbst zurückliefert. Da die Default-`valueOf( )`-Methode keinen elementaren Wert zurückliefert, versucht JavaScript dann das Objekt in eine Zahl umzuwandeln, indem es seine `toString( )`-Methode aufruft und dann den resultierenden String in eine Zahl umwandelt.

Bei Arrays führt das zu interessanten Ergebnissen. Die `toString( )`-Methode von Arrays wandelt die Array-Elemente in Strings um und liefert als Ergebnis eine Verkettung dieser Strings mit Kommas als Trennzeichen zurück. Deswegen wird ein Array, das keine Elemente enthält, in den leeren String umgewandelt, der seinerseits in die Zahl null umgewandelt wird! Und wenn ein Array nur ein Element hat, das die Zahl n ist, wird das Array in die Stringdarstellung von n umgewandelt, die dann wieder in n selbst zurückgewandelt wird. Wenn ein Array mehrere Elemente enthält oder eines seiner Elemente keine Zahl ist, wird das Array in `NaN` umgewandelt.

Die Umwandlung eines Werts ist von dem Kontext abhängig, in dem er verwendet wird. Es gibt in JavaScript ein paar Gelegenheiten, bei denen der Kontext mehrdeutig ist. Der `+`-Operator und die Vergleichsoperatoren (`<`, `<=`, `>` und `>=`) operieren auf Zahlen und Strings. Wenn ein Objekt mit einem dieser Operatoren verwendet wird, ist also nicht klar, ob es in eine Zahl oder in einen String umgewandelt werden soll. In den meisten Fällen versucht JavaScript zuerst, das Objekt umzuwandeln, indem es seine `valueOf( )`-Methode aufruft. Wenn diese Methode einen primitiven Wert zurückliefert (in der Regel eine Zahl), wird dieser Wert verwendet, Häufig liefert `valueOf( )` jedoch einfach das nicht umgewandelte Objekt zurück. In diesem Fall versucht JavaScript, das Objekt durch einen Aufruf seiner `toString( )`-Methode in einen String umzuwandeln.

Es gibt allerdings eine Ausnahme zu dieser Umwandlungsregel: Wenn ein Date-Objekt mit dem `+`-Operator verwendet wird, wird die Umwandlung mit der `toString( )`-Methode durchgeführt. Diese Ausnahme gibt es, weil Date eine `toString( )`\- und eine `valueOf( )`-Methode hat. Wenn ein Datum mit `+` verwendet wird, wollen Sie fast immer eine Stringverkettung durchführen. Aber wenn Sie ein Datum mit einem der Vergleichsoperatoren verwenden, wollen Sie fast immer einen numerischen Vergleich durchführen, um zu ermitteln, welche der beiden Zeiten früher ist als die andere.

Die meisten Objekte haben entweder keine `valueOf( )`-Methode oder keine `valueOf( )`-Methode, die brauchbare Ergebnisse liefert. Wenn Sie ein Objekt mit dem `+`-Operator verwenden, erhalten Sie in der Regel eine Stringverkettung und keine Addition. Wenn Sie ein Objekt mit einem Vergleichsoperator verwenden, erhalten Sie in der Regel einen Stringvergleich und keinen numerischen Vergleich.

Ein Objekt, das eine eigene `valueOf( )`-Methode definiert, kann sich anders verhalten. Wenn Sie eine `valueOf( )`-Methode definierten, die eine Zahl zurückliefert, können Sie arithmetische und andere Operationen mit Ihrem Objekt verwenden. Aber wenn Sie versuchen, Ihr Objekt einem String hinzuzufügen, erhalten Sie vielleicht nicht mehr das gewünschte Ergebnis: Die `toString( )`-Methode wird nicht mehr aufgerufen und eine Stringdarstellung der Zahl, die von `valueOf( )` zurückgeliefert wurde, wird mit dem String verkettet.

Denken Sie schließlich daran, dass `valueOf( )` nicht `toNumber( )` heißt. Genauer gesagt, heißt das, dass es ihre Aufgabe ist, ein Objekt in einen vernünftigen elementaren Wert umzuwandeln. Manche Objekte können also auch `valueOf( )`-Methoden haben, die Strings zurückliefern.

* * *

[14] Dieser Abschnitt behandelt fortgeschrittene Themen, die Sie beim ersten Lesen eventuell überspringen sollten.

# 3.15 by-value vs. by-reference

Wie in allen wichtigen Programmiersprachen können Sie in JavaScript einen Datenwert auf drei wichtige Arten manipulieren.[15] Erstens können Sie ihn kopieren. Beispielsweise könnten Sie ihn einer neuen Variable zuweisen. Zweitens können Sie ihn als Argument an eine Funktion oder Methode übergeben. Drittens können Sie ihn mit einem anderen Wert vergleichen, um zu prüfen, ob die beiden Werte gleich sind. Wenn Sie eine Programmiersprache verstehen wollen, müssen Sie verstehen, wie diese Operationen in dieser Sprache durchgeführt werden.

Es gibt zwei grundsätzlich unterschiedliche Verfahren, Datenwerte zu manipulieren. Diese Techniken werden _by-value_ (über den Wert) und _by-reference_ (über die Referenz) genannt. Wenn ein Datenwert über den Wert manipuliert wird, geht es um den _Wert_ des Datenwerts. In einer Zuweisung wird eine Kopie des tatsächlichen Werts angelegt und diese Kopie in einer Variablen, einer Objekteigenschaft oder in einem Array-Element gespeichert. Die Kopie und der ursprüngliche Wert sind zwei vollkommen unabhängige Werte, die getrennt gespeichert werden. Wenn ein Datenwert by-value an eine Funktion übergeben wird, wird der Funktion eine Kopie des Datenwerts übergeben. Wenn die Funktion den Wert verändert, betreffen diese Änderungen nur die Kopie des Datenwerts, die die Funktion erhalten hat – nicht den ursprünglichen Datenwert. Und wenn ein Datenwert by-value mit einem anderen Datenwert verglichen wird, dann müssen die beiden unterschiedlichen Datenteile genau den gleichen Wert darstellen (was in der Regel heißt, dass über einen byte-weisen Vergleich geprüft wird, ob sie gleich sind).

Die andere Möglichkeit, einen Wert zu manipulieren, ist by-reference. Bei dieser Technik gibt es nur eine wirkliche Kopie des Werts, und es werden Referenzen auf diesen Wert manipuliert.[16] Wenn ein Wert by-reference manipuliert wird, enthalten Variablen diesen Wert nicht direkt. Sie enthalten nur eine Referenz auf ihn. Und es sind diese Referenzen, die kopiert, übergeben und verglichen werden. Bei einer Zuweisung by-reference wird also die Referenz auf den Wert zugewiesen, keine Kopie des Werts und auch nicht der Wert selbst. Nach der Zuweisung referenziert die neue Variable den gleichen Wert wie die ursprüngliche Variable. Beide Referenzen sind gleichermaßen gültig, und beide können verwendet werden, um den Wert zu manipulieren. Wenn der Wert über eine Referenz geändert wird, scheint diese Änderung auch auf die ursprüngliche Referenz durch. Das ist ähnlich, wenn einer Funktion ein Wert by-reference übergeben wird. Eine Referenz auf den Wert wird der Funktion übergeben, und die Funktion kann diese Referenz nutzen, um den Wert selbst zu ändern. Alle auf diese Weise durchgeführten Modifikationen sind auch außerhalb der Funktion sichtbar. Und wenn ein Wert by-reference mit einem anderen Wert verglichen wird, werden die beiden Referenzen verglichen, um zu prüfen, ob sie die gleiche Kopie eines Werts referenzieren. Referenzen, die auf zwei getrennte Werte verweisen, die zufälligerweise gleich sind (d.h. aus den gleichen Bytes bestehen), werden als nicht gleich behandelt.

Das sind zwei sehr unterschiedliche Weisen, Werte zu manipulieren, und sie haben wichtige Implikationen, die Sie kennen sollten. Tabelle 3-4 fasst diese Implikationen zusammen. Diese Betrachtung der Manipulation von Daten by-value und by-reference war allgemein, aber die Unterschiede gelten für alle Programmiersprachen. Die folgenden Abschnitte erläutern, wie sich diese Unterschiede speziell auf JavaScript auswirken. Sie erörtern, welche Datentypen by-value manipuliert werden und welche by-reference.

Tabelle 3-4. by-value vs. by-reference | by-value | by-reference

---|---|---

Kopieren | Der Wert wird tatsächlich kopiert. Es gibt zwei verschiedene, unabhängige Kopien. | Es wird nur eine Referenz auf den Wert kopiert. Wird der Wert über die neue Referenz manipuliert, sind diese Änderungen auch über die ursprüngliche Referenz sichtbar.

Übergeben | Es wird eine neue Kopie des Werts an die Funktion übergeben. Änderungen an dieser Kopie haben keine Auswirkungen außerhalb der Funktion. | Es wird eine Referenz auf den Wert an die Funktion übergeben. Wenn die Funktion den Wert ändert, sind die Veränderungen außerhalb der Funktion sichtbar.

Vergleichen | Es werden zwei getrennte Werte verglichen (häufig Byte-weise), um zu prüfen, ob sie den gleichen Wert darstellen. | Es werden zwei Referenzen verglichen, um zu prüfen, ob sie auf den gleichen Wert verweisen. Zwei Referenzen auf getrennte Werte sind nicht gleich, selbst wenn die beiden Werte aus den gleichen Bytes bestehen.

## 3.15.1 Elementare Typen und Referenztypen

Die JavaScript-Grundregel lautet folgendermaßen: _elementare Typen_ werden by-value manipuliert und _Referenztypen_ , wie schon der Name andeutet, by-reference. Zahlen und Boolesche Werte sind in JavaScript elementare Typen – elementar, weil sie nur aus einer kleinen, festgelegten Anzahl von Bytes bestehen, die sich leicht auf den unteren Stufen des JavaScript-Interpreters manipulieren lassen. Objekte, andererseits, sind Referenztypen. Arrays und Funktionen sind besondere Arten von Objekten, deswegen sind auch sie Referenztypen. Diese Datentypen können eine beliebige Anzahl von Eigenschaften oder Elementen haben. Deswegen können sie nicht so leicht manipuliert werden wie die elementaren Werte, die eine festgelegte Größe haben. Da Objekt- und Array-Werte ziemlich groß werden können, macht es keinen Sinn, diese Typen by-value zu manipulieren, weil das zu ineffizienten Kopiervorgängen und Vergleichen großer Speichermengen führen könnte.

Und was ist mit Strings? Ein String kann eine beliebige Länge haben. Es könnte also scheinen, dass Strings Referenztypen sein sollten. Tatsächlich werden sie allerdings in JavaScript in der Regel als elementare Typen betrachtet. Der einzige Grund dafür ist, dass sie keine Objekte sind. Aber eigentlich passen Strings nicht in die Elementar-vs-Referenz-Dichotomie. Strings und ihr Verhalten werden etwas später deswegen genauer erklärt.

Die Unterschiede zwischen der Datenmanipulation by-value und by-reference schaut man sich am besten anhand eines Beispiels an. Sehen Sie sich die folgenden Beispiele genau an, und achten Sie auf die Kommentare. Beispiel 3-1 kopiert, übergibt und vergleicht Zahlen. Weil Zahlen elementare Typen sind, illustriert dieses Beispiel die Datenmanipulation by-value.

Beispiel 3-1. by-value kopieren, übergeben und vergleichen

    // Zuerst zeigen wir das Kopieren by-value
    var n = 1; // Variable n enthält den Wert 1
    var m = n; // by-value kopieren: Variable m enthält einen separaten Wert 1

    // Hier ist eine Funktion, die wir einsetzen werden, um das Übergeben by-value zu zeigen.
    // Wie wir sehen werden, funktioniert die Funktion nicht so, wie wir es gern hätten.
    function zu_summe_hinzufuegen(gesamt, x)
    {
      gesamt = gesamt + x; // Diese Zeile ändert nur die internte Kopie von total
    }

    // Jetzt rufen wir die Funktion auf und übergeben die Werte in n und m by-value.
    // Der Wert von n wird kopiert, und dieser kopierte Wert heißt in dieser Funktion
    // total. Die Funktion addiert dieser Kopie von n die Kopie von m hinzu. Aber eine
    // Addition zu einer Kopie von n hat keine Auswirkungen auf den ursprünglichen Wert
    // außerhalb der Funktion. Der Aufruf der Funktion bewirkt also überhaupt nichts.
    zu_summe_hinzufuegen(n, m);

    // Jetzt sehen wir uns einen Vergleich by-value an.
    // In der folgenden Codezeile ist das Literal 1 eindeutig ein separater numerischer Wert,
    // der in das Programm geschrieben ist. Wir vergleichen ihn mit dem Wert, der von der
    // Variablen n festgehalten wird. Bei einem Vergleich by-value werden die Bytes von zwei
    // Zahlen geprüft, um zu sehen, ob sie gleich sind.
    if (n == 1) m = 2; // n enthält den gleichen Wert wie das Literal 1; m ist jetzt 2.

Sehen Sie sich jetzt Beispiel 3-2 an. Dieses Beispiel kopiert, übergibt und vergleicht ein Objekt. Da Objekte Referenztypen sind, werden diese Operationen by-reference durchgeführt. Dieses Beispiel verwendet Date-Objekte, über die Sie in Teil III mehr erfahren können.

Beispiel 3-2. By-reference kopieren, übergeben und vergleichen

    // Hier erzeugen wir ein Objekt, das das Datum für den 1. Weihnachtstag 2007 repräsentiert.
    // Die Variable weihnacht enthält eine Referenz auf das Objekt, nicht das Objekt selbst.
    var weihnacht = new Date(2007, 11, 25);

    // Wenn wir by-reference kopieren, erhalten wir eine neue Referenz auf das ursprüngliche
    // Objekt.
    var mittwinter = weihnacht; // Beide Variablen verweisen jetzt auf die gleichen Objektwerte

    // Hier ändern wir das Objekt über unsere neue Referenz darauf.
    mittwinter.setDate(21);

    // Diese Änderung ist auch über die ursprüngliche Referenz sichtbar.
    weihnacht.getDate( ); // Gibt 21 zurück, nicht den Originalwert 25

    // Das Gleiche gilt, wenn Objekte und Arrays an Funktionen übergeben werden.
    // Die folgende Funktion addiert jedem Element in einem Array einen Wert hinzu.
    // An die Funktion wird eine Referenz auf das Array übergeben, keine Kopie des Arrays.
    // Deswegen kann die Funktion den Inhalt des Arrays über die Referenz ändern.
    // Und diese Änderungen bleiben sichtbar, wenn die Funktion zurückkehrt.
    function zu_summen_hinzufuegen(summen, x)
    {
      summen[0] = summen[0] + x;
      summen[1] = summen[1] + x;
      summen[2] = summen[2] + x;
    }

    // Abschließend sehen wir uns einen Vergleich by-reference an.
    // Wenn wir die beiden oben definierten Variablen vergleichen, stellen wir fest,
    // dass sie gleich sind, weil sie auf das gleiche Objekt verweisen, auch wenn wir
    // versucht haben, sie auf zwei unterschiedliche Daten verweisen zu lassen:
    (weihnacht == mittwinter) // Ergibt true

    // Die beiden hier definierten Variablen verweisen auf zwei unterschiedliche
    // Objekte, die beide genau das gleiche Datum repräsentieren.
    var weihnacht = new Date(2007, 11, 25);
    var mittwinter_plus_4 = new Date(2007, 11, 25);

    // Aber gemäß der Regel "by-reference vergleichen" sind zwei verschiedene Objekte // nicht gleich!
    (weihnacht != mittwinter_plus_4) // Ergibt true

Bevor wir das Thema der Manipulation von Objekten und Arrays by-reference verlassen, sollten wir noch eine Frage der Terminologie klären. Die Phrase »pass-by-reference« (per Referenz übergeben) kann unterschiedliche Bedeutungen haben. Für einige Leser verweist diese Phrase auf eine Technik des Funktionsaufrufs, die es einer Funktion ermöglicht, ihren Argumenten neue Werte zuzuweisen und diese veränderten Werte außerhalb der Funktion sichtbar zu machen. Aber auf diese Weise wird der Begriff in diesem Buch nicht verwendet. Hier will ich damit nur sagen, dass einer Funktion eine Referenz auf ein Objekt oder ein Array übergeben wird – nicht das Objekt selbst. Eine Funktion kann die Referenz verwenden, um die Properties des Objekts oder die Elemente des Arrays zu ändern. Aber wenn die Funktion die Referenz mit einer Referenz auf ein neues Objekt oder Array überschreibt, ist diese Veränderung außerhalb der Funktion nicht sichtbar. Leser, die mit der anderen Bedeutung dieses Begriffs vertraut sind, ziehen es vielleicht vor, zu sagen, dass Objekte und Arrays by-value übergeben werden, aber dass der Wert, der übergeben wird, tatsächlich eine Referenz auf das Objekt selbst ist. Beispiel 3-3 illustriert diese Sache.

Beispiel 3-3. Referenzen werden selbst by-value übergeben

    // Das ist eine andere Version der zu_summen_hinzufuegen( )-Funktion. Sie funktioniert
    // allerdings nicht, weil sie, statt das Array selbst zu ändern, versucht, die
    // Referenz auf das Array zu ändern.
    function zu_summen_hinzufuegen2(summen, x)
    {
      neuesummen = new Array(3);
      neuesummen[0] = summen[0] + x;
      neuesummen[1] = summen[1] + x;
      neuesummen[2] = summen[2] + x;
      summen = neuesummen; // Diese Zeile hat außerhalb der Funktion keine Auswirkungen.
    }

## 3.15.2 Strings kopieren und übergeben

Wie bereits gesagt, passen JavaScript-Strings nicht richtig in die Elementar-vs.-Referenz-Typunterscheidung. Da Strings keine Objekte sind, ist es nur natürlich, dass man voraussetzt, dass sie elementar sind. Und wenn sie elementare Typen sind, müssten sie gemäß der oben angeführten Regeln eigentlich by-value manipuliert werden. Da Strings beliebig lang werden können, scheint es aber ineffizient, sie Byte für Byte zu kopieren, zu übergeben und zu vergleichen. Deswegen müsste man anderseits wieder annehmen, dass Strings als Referenztypen implementiert sind.

Statt Vermutungen über Strings anzustellen, sollten wir etwas JavaScript-Code schreiben, um mit Stringmanipulationen zu experimentieren. Falls Strings by-reference kopiert und übergeben werden, sollte man den Inhalt eines Strings über die in einer anderen Variablen gespeicherte oder an eine Funktion übergebene Referenz verändern können.

Aber wenn Sie den Code schreiben, um dieses Experiment durchzuführen, werden Sie auf einen großen Stolperstein stoßen: Es gibt keine Möglichkeit, den Inhalt eines Strings zu verändern. Die Methode `charAt( )` liefert das Zeichen an einer bestimmten Position in einem String zurück, aber es gibt keine korrespondierende `setCharAt( )`-Methode. Das ist kein Versehen. JavaScript-Strings sind absichtlicherweise _unveränderbar_ – das heißt, dass es keine JavaScript-Syntax, -Methode oder -Eigenschaft gibt, die es Ihnen ermöglichen würde, die Zeichen in einem String zu verändern.

Da Strings unveränderbar sind, ist die Ausgangsfrage irrelevant: Es gibt keine Möglichkeit zu sagen, ob Strings by-value oder by-reference übergeben werden. Sie können annehmen, dass JavaScript so implementiert ist, dass Strings aus Gründen der Effizienz by-reference übergeben werden. Aber in Wirklichkeit spielt das keine Rolle, weil es keine praktischen Auswirkungen auf den Code hat, den Sie schreiben.

## 3.15.3 Strings vergleichen

Auch wenn Sie nicht feststellen können, ob Strings by-value oder by-reference kopiert und übergeben werden, können Sie JavaScript-Code schreiben, um festzustellen, ob sie by-value oder by-reference verglichen werden. Beispiel 3-4 zeigt den Code, mit dem Sie das feststellen können.

Beispiel 3-4. Werden Strings by-value oder by-reference verglichen?

    // Man kann leicht feststellen, ob Strings by-value oder by-reference verglichen werden.
    // Wir vergleichen zwei vollkommen separate Strings, die zufälligerweise die
    // gleichen Zeichen enthalten. Wenn sie by-value verglichen werden, sind sie gleich,
    // aber wenn sie by-reference verglichen werden, sind sie nicht gleich:
    var s1 = "Hallo";
    var s2 = "Hall" + "o";
    if (s1 == s2) document.write("Strings werden by-value verglichen");

Dieses Experiment zeigt, dass Strings by-value verglichen werden. Das mag für manche Programmierer überraschend sein. In C, C++ und Java sind Strings Referenztypen, die by-reference verglichen werden. Wenn Sie den tatsächlichen Inhalt von zwei Strings vergleichen wollen, müssen Sie eine besondere Methode oder Funktion verwenden. Aber JavaScript ist eine hochstufigere Sprache und berücksichtigt, dass Sie, wenn Sie Strings vergleichen, in der Regel ihre Werte vergleichen wollen. Obwohl JavaScript-Strings aus Gründen der Effizienz (wahrscheinlich) by-reference kopiert und übergeben werden, werden sie also by-value verglichen.

## 3.15.4 by-value vs. by-reference: Zusammenfassung

Tabelle 3-5 fasst zusammen, wie die verschiedenen JavaScript-Typen manipuliert werden.

Tabelle 3-5. Datentypmanipulation in JavaScript

Typ | Kopieren | Übergeben | Vergleichen

---|---|---|---

Zahl | by-value | by-value | by-value

Boolescher Wert | by-value | by-value | by-value

String | unveränderbar | unveränderbar | by-value

Objekt | by-reference | by-reference | by-reference

* * *

[15] Dieser Abschnitt behandelt fortgeschrittene Themen, die Sie beim ersten Lesen eventuell überspringen sollten.

[16] C-Programmierer und alle anderen, die mit dem Zeigerkonzept vertraut sind, sollten verstehen, was Referenz in diesem Kontext bedeutet. Beachten Sie allerdings, dass JavaScript keine Zeiger unterstützt.

# Kapitel 4. Variablen

Eine _Variable_ ist ein Name, der mit einem Wert verbunden ist. Man spricht davon, dass die Variable den Wert speichert oder enthält. Variablen ermöglichen es Ihnen, Daten in Ihren Programmen zu speichern und zu bearbeiten. Die folgende JavaScript-Zeile weist zum Beispiel der Variablen `i` den Wert `2` zu:

    i = 2;

Die folgende Zeile addiert `i` und `3` und weist das Ergebnis der neuen Variablen `summe` zu:

    var summe = i + 3;

Diese beiden Codezeilen veranschaulichen schon alles, was Sie über Variablen wissen müssen. Um jedoch ganz zu verstehen, wie Variablen in JavaScript arbeiten, müssen Sie noch einige weitere Konzepte kennenlernen. Leider sind diese Konzepte nicht in einigen wenigen Zeilen zu erklären. Der Rest dieses Kapitels erläutert die Typisierung, die Deklaration, den Geltungsbereich, den Inhalt und die Auflösung von Variablen. Außerdem befasst es sich mit der Speicherbereinigung und der Dualität von Variable und Eigenschaft.[17]

# 4.1 Typisierung von Variablen

Ein wichtiger Unterschied zwischen JavaScript und Sprachen wie zum Beispiel Java und C besteht darin, dass JavaScript _nicht-typisiert_ ist. Dies bedeutet unter anderem, dass eine JavaScript-Variable einen Wert eines beliebigen Typs speichern kann, während eine Java- oder C-Variable nur den Datentyp speichern kann, für den sie deklariert wurde. In JavaScript ist es zum Beispiel absolut zulässig, einer Variablen zuerst eine Zahl und später einen String zuzuweisen:

    i = 10;
    i = "zehn";

In C, C++, Java und jeder anderen stark typisierten Sprache ist derartiger Code nicht zulässig.

Da JavaScript nicht-typisiert ist, kann es Werte bei Bedarf bequem und automatisch von einem Typ in einen anderen umwandeln. Wenn Sie zum Beispiel eine Zahl an einen String anzuhängen versuchen, wandelt JavaScript die Zahl automatisch in den passenden String um, damit sie angehängt werden kann. In Kapitel 3 werden Sie mehr über die Typumwandlung erfahren.

JavaScript wird einfacher dadurch, dass es nicht-typisiert ist. Der Vorteil stark typisierter Programmiersprachen wie zum Beispiel C++ und Java besteht demgegenüber darin, dass sie peinlich genaue Programmiertechniken erzwingen, wodurch lange und komplexe Programme leichter zu schreiben, zu warten und wiederzuverwenden sind. Da viele JavaScript-Programme kürzere Skripten sind, ist diese Strenge hier jedoch nicht erforderlich, und wir profitieren von der einfacheren Syntax.

* * *

17] Diese Konzepte sind nicht ganz einfach. Um dieses Kapitel wirklich zu verstehen, müssen Sie auch Konzepte verstanden haben, die wir erst in späteren Kapiteln besprechen werden. Wenn Sie bisher nur wenig Programmiererfahrung haben, empfehlen wir Ihnen, nur die ersten beiden Abschnitte dieses Kapitels zu lesen, dann mit den [Kapitel 5, Kapitel 6 und Kapitel 7 weiterzumachen und erst danach den Rest dieses Kapitels durchzuarbeiten.

# 4.2 Deklaration von Variablen

Bevor Sie in einem JavaScript-Programm eine Variable verwenden können, müssen Sie sie _deklarieren_.[18] Variablen werden mit dem Schlüsselwort `var` deklariert:

    var i;
    var summe;

Es ist auch möglich, mit einem `var` mehrere Variablen zu deklarieren:

    var i, summe;

Außerdem können Sie die Deklaration einer Variablen mit ihrer Initialisierung verbinden:

    var nachricht = "Hallo";
    var i = 0, j = 0, k = 0;

Wenn Sie in der `var`-Anweisung für eine Variable keinen Anfangswert angeben, dann wird die Variable zwar deklariert, aber ihr Anfangswert ist so lange `undefined`, bis Ihr Code einen Wert in ihr speichert.

Beachten Sie, dass die `var`-Anweisung auch Teil einer `for`\- oder `for/in`-Schleife sein kann (siehe Kapitel 6). Dadurch können Sie die Schleifenvariable ganz rasch im Rahmen der Schleifensyntax deklarieren:

    for(var i = 0; i < 10; i++) document.write(i, "<br>");
    for(var i = 0, j=10; i < 10; i++,j--) document.write(i*j, "<br>");
    for(var i in o) document.write(i, "<br>");

Variablen, die mit `var` deklariert werden, sind _dauerhaft:_ Der Versuch, eine solche Variable mit dem Operator `delete` zu löschen, löst einen Fehler aus. (Den Operator `delete` werden wir in Kapitel 5 besprechen.)

## 4.2.1 Wiederholte und weggelassene Deklarationen

Es ist zulässig und harmlos, eine Variable mehrmals mit der `var`-Anweisung zu deklarieren. Wenn die wiederholte Deklaration einen Initialisierer hat, verhält sich dieser so, als sei er einfach eine Zuweisungsanweisung.

Wenn Sie den Wert einer nicht deklarierten Variable zu lesen versuchen, erzeugt JavaScript einen Fehler. Wenn Sie einer Variablen, die Sie nicht mit `var` deklariert haben, einen Wert zuweisen, deklariert JavaScript diese Variable implizit. Beachten Sie jedoch, dass implizit deklarierte Variablen immer als globale Variablen angelegt werden, selbst wenn sie innerhalb eines Funktionsbodys verwendet werden. Um zu verhindern, dass eine globale Variable erstellt wird (oder dass eine vorhandene globale Variable verwendet wird), wenn Sie eigentlich eine lokale Variable erstellen wollten, die nur innerhalb einer bestimmten Funktion verwendet werden soll, müssen Sie innerhalb von Funktionsbodys immer die `var`-Anweisung verwenden. Am besten ist es, wenn Sie `var` für alle Variablen verwenden, seien sie nun global oder lokal. (Den Unterschied zwischen lokalen und globalen Variablen werden wir im folgenden Abschnitt ausführlicher besprechen.)

* * *

[18] Wenn Sie eine Variable nicht explizit deklarieren, deklariert JavaScript sie implizit.

# 4.3 Der Geltungsbereich von Variablen

Der _Geltungsbereich_ einer Variablen ist der Bereich eines Programms, in dem sie definiert ist. Eine _globale_ Variable hat einen globalen Geltungsbereich: Sie ist überall in Ihrem JavaScript-Code definiert. Variablen, die innerhalb einer Funktion deklariert sind, sind hingegen nur innerhalb des Bodys dieser Funktion definiert. Dies sind _lokale_ Variablen, und sie haben einen lokalen Geltungsbereich. Funktionsparameter gelten ebenfalls als lokale Variablen und sind nur innerhalb des Funktionsbodys definiert.

Innerhalb des Funktionsbodys hat eine lokale Variable Vorrang vor einer globalen Variablen desselben Namens. Wenn Sie eine lokale Variable oder einen Funktionsparameter mit demselben Namen deklarieren, den bereits eine globale Variable trägt, dann verbergen Sie dadurch die globale Variable. Der folgende Code gibt zum Beispiel das Wort »lokal« aus:

    var geltungsbereich = "global";        // Deklariere eine globale Variable
    function geltungsbereich_pruefen( ) {
      var geltungsbereich = "lokal";       // Deklariere eine gleichnamige lokale Variable
      document.write(geltungsbereich);     // Verwende die lokale, nicht die globale Variable
    }
    geltungsbereich_pruefen( );            // Gibt "lokal" aus

Wenn Sie Code im globalen Geltungsbereich schreiben, brauchen Sie die `var` Anweisung zwar nicht zu verwenden, aber bei der Deklaration lokaler Variablen ist sie unbedingt erforderlich. Der folgende Code veranschaulicht, was geschieht, wenn Sie dies nicht machen:

    geltungsbereich = "global";            // Deklariere eine globale Variable ohne var
    function geltungsbereich_pruefen( ) {
    geltungsbereich = "lokal";             // Huch! Wir haben die globale Variable gerade
                                           // geändert.
    document.write(geltungsbereich);       // Verwendet die globale Variable
    mein_geltungsbereich = "lokal";        // Deklariert implizit eine neue
                                           // globale Variable
    document.write(mein_geltungsbereich);  // Verwendet die neue globale Variable
    }
    geltungsbereich_pruefen();             // Gibt "lokallokal" aus
    document.write(geltungsbereich);       // Gibt "lokal" aus
    document.wirte(mein_geltungsbereich);  // Gibt "lokal" aus

Im Allgemeinen wissen Funktionen nicht, welche Variablen im globalen Geltungsbereich definiert sind oder wofür sie verwendet werden. Wenn eine Funktion also anstelle einer lokalen eine globale Variable verwendet, besteht die Gefahr, dass sie einen Wert verändert, auf den sich ein anderer Teil des Programms stützt. Zum Glück können Sie dieses Problem leicht vermeiden: Deklarieren Sie alle Variablen mit `var`.

Funktionsdefinitionen können geschachtelt werden. Da jede Funktion ihren eigenen lokalen Geltungsbereich hat, kann es mehrere geschachtelte Ebenen lokaler Geltungsbereiche geben. Hierzu ein Beispiel:

    var geltungsbereich = "globaler Geltungsbereich";        // Eine globale Variable
    function geltungsbereich_pruefen( ) {
    var geltungsbereich = "lokaler Geltungsbereich";         // Eine lokale Variable
    function geschachtelt( ) {
    var geltungsbereich = "geschachtelter Geltungsbereich";  // Ein geschachtelter Geltungs-
                                                             // bereich für lokale Variablen
    document.write(geltungsbereich);                         // Gibt "geschachtelter
                                                             // Geltungsbereich" aus
    }
    geschachtelt( );
    }
    geltungsbereich_pruefen( );

## 4.3.1 Kein Block-Geltungsbereich

Im Gegensatz zu C, C++ und Java definieren bei JavaScript Blöcke keine eigenen Geltungsbereiche. Alle Variablen, die in einer Funktion deklariert sind, sind, unabhängig vom Ort ihrer Deklaration, für die _ganze_ Funktion definiert. Im folgenden Code haben die Variablen `i`, `j` und `k` denselben Geltungsbereich: Alle drei sind im gesamten Body der Funktion definiert. Dies wäre nicht der Fall, wenn es sich hier um C-, C++- oder Java-Code handelte.

    function test(o) {
      var i = 0;                       // i ist in der ganzen Funktion definiert
      if (typeof o == "object") {
        var j = 0;                     // j ist überall definiert, nicht nur im Block
        for(var k = 0; k < 10; k++) {  // k ist überall definiert, nicht nur in der
                                       // Schleife
          document.write(k);
        }
        document.write(k);             // k ist immer noch definiert, gibt 10 aus
      }
      document.write(j);               // j ist definiert, aber evtl. nicht initialisiert
    }

Wie der folgende Code zeigt, kann die Regel, dass alle in einer Funktion deklarierten Variablen in der gesamten Funktion definiert sind, zu überraschenden Ergebnissen führen:

    var geltungsbereich = "global";
    function f( ) {
      alert(geltungsbereich);         // Zeigt "undefined" an, nicht "global"
      var geltungsbereich = "lokal";  // Variable ist hier initialisiert,
                                      // aber überall definiert
      alert(geltungsbereich);         // Zeigt "lokal" an
    }
    f( );

Man könnte meinen, dass der erste Aufruf von `alert( )` »global« anzeigt, da die `var`-Anweisung, die die lokale Variable deklariert, noch nicht ausgeführt wurde. Aufgrund der Regeln für den Geltungsbereich trifft dies jedoch nicht zu. Die lokale Variable ist im gesamten Body der Funktion definiert, weshalb die gleichnamige globale Variable in der gesamten Funktion verborgen ist. Die lokale Variable ist zwar durchgängig definiert, wird aber erst initialisiert, nachdem die `var`-Anweisung ausgeführt worden ist. Die Funktion `f` im obigen Beispiel ist also mit der folgenden Funktion äquivalent:

    function f( ) {
      var geltungsbereich;       // Lokale Variable wird am Anfang der Funktion definiert
      alert(geltungsbereich);    // Hier hat sie noch den Wert "undefined"
      geltungsbereich = "lokal"; // Jetzt wird sie initialisiert und erhält einen Wert
      alert(geltungsbereich);    // Und jetzt hat sie einen Wert
    }

Dieses Beispiel veranschaulicht, warum es eine gute Angewohnheit ist, immer alle Variablendeklarationen an den Anfang einer Funktion zu setzen.

## 4.3.2 Nicht definiert oder nicht initialisiert?

Die Beispiele in den vorangegangenen Abschnitten veranschaulichen einen subtilen Aspekt der JavaScript-Programmierung: Es gibt zwei Arten von undefinierten Variablen. Zur ersten Art gehören die Variablen, die niemals deklariert wurden. Ein Versuch, den Wert einer solchen, nicht deklarierten Variablen zu lesen, löst einen Laufzeitfehler aus. _Nicht deklarierte_ Variablen sind ganz einfach deshalb undefiniert, weil es sie nicht gibt. Wie bereits beschrieben, wird kein Fehler ausgelöst, wenn Sie einer nicht deklarierten Variablen einen Wert zuzuweisen versuchen. Stattdessen wird die Variable implizit im globalen Geltungsbereich deklariert.

Zur zweiten Art undefinierter Variablen gehören diejenigen Variablen, die zwar deklariert sind, denen aber nie ein Wert zugewiesen wurde. Wenn Sie den Wert einer dieser Variablen lesen, erhalten Sie den Standardwert `undefined`. Diese Art undefinierter Variable könnte man auch als _nicht initialisiert_ bezeichnen, um sie von dem schwerwiegenderen Fall zu unterscheiden, dass eine Variable noch nicht einmal deklariert wurde und daher nicht existiert.

Das folgende Codestück veranschaulicht einige Unterschiede zwischen wirklich undefinierten Variablen und Variablen, denen einfach nie ein Wert zugewiesen wurde.

    var x;    // Deklariere eine nicht zugewiesene Variable.
              // Ihr Wert ist undefiniert.
    alert(u); // Die Verwendung einer nicht deklarierten Variablen
              // löst einen Fehler aus.
    u = 3;    // Wenn Sie einer nicht deklarierten Variablen einen Wert
              // zuweisen, wird die Variable erzeugt.

# 4.4 Primitive Datentypen und Referenztypen

Nun müssen wir uns mit dem Inhalt von Variablen befassen. Häufig wird gesagt, dass Variablen Werte haben oder enthalten. Was genau enthalten sie nun aber eigentlich? Um diese scheinbar einfache Frage zu beantworten, müssen wir uns noch einmal die Datentypen ansehen, die JavaScript unterstützt. Es gibt zwei Gruppen von Typen: die primitiven Datentypen und die Referenztypen. Zahlen, Boolesche Werte und die Typen `null` und `undefined` sind primitive Datentypen. Objekte, Arrays und Funktionen sind Referenztypen.

Ein primitiver Datentyp nimmt im Speicher eine feste Größe ein. Eine Zahl nimmt zum Beispiel acht Bytes Speicherplatz ein, während ein Boolescher Wert mit nur einem Bit dargestellt werden kann. Der größte primitive Datentyp ist der Zahlentyp. Wenn jede JavaScript-Variable acht Bytes Speicherplatz reserviert, kann die Variable jeden Grunddatenwert direkt speichern.[19]

Bei den Referenztypen sieht das ganz anders aus. Objekte können zum Beispiel jede beliebige Länge haben; sie haben keine feste Größe. Dasselbe gilt auch für Arrays: Ein Array kann beliebig viele Elemente enthalten. Und eine Funktion kann beliebig viel JavaScript-Code umfassen. Da diese Typen keine feste Größe haben, können ihre Werte nicht direkt in den acht Bytes Speicherplatz untergebracht werden, die jeder Variablen zugewiesen sind. Stattdessen speichert die Variable eine _Referenz_ auf den Wert. Normalerweise ist diese Referenz irgendeine Art von Zeiger oder Speicheradresse. Sie ist nicht der Datenwert, sondern sie teilt der Variablen mit, wo diese nach dem Wert zu suchen hat.

Die Unterscheidung von primitiven Datentypen und Referenztypen ist wichtig, da sich beide Typen unterschiedlich verhalten. Betrachten Sie zum Beispiel den folgenden Code, der mit Zahlen (einem primitiven Datentyp) arbeitet:

    var a = 3.14; // Deklariere und initialisiere eine Variable
    var b = a;    // Kopiere ihren Wert in eine neue Variable
    a = 4;        // Ändere den Wert der Originalvariablen
    alert(b)      // Zeigt 3.14 an; die Kopie ist unverändert

An diesem Code ist nichts Ungewöhnliches. Aber schauen wir uns an, was passiert, wenn wir den Code leicht ändern und Arrays (einen Referenztyp) anstelle von Zahlen verwenden:

    var a = [1,2,3]; // Initialisiere eine Variable mit Array-Referenz
    var b = a;       // Kopiere die Referenz in eine neue Variable
    a[0] = 99;       // Ändere das Array mit der Originalreferenz
    alert(b);        // Zeige das neue Array [99,2,3] mit der neuen
                     // Referenz an

Wenn Ihnen dieses Ergebnis nicht überraschend erscheint, sind Sie mit dem Unterschied zwischen primitiven Datentypen und Referenztypen bereits gut vertraut. Wenn es Ihnen hingegen überraschend vorkommt, sollten Sie sich die zweite Zeile genauer ansehen. Achten Sie darauf, dass in dieser Anweisung nicht das Array selbst zugewiesen wird, sondern die Referenz auf den Array-Wert. Nach der zweiten Codezeile haben wir immer noch nur ein einziges Array-Objekt; es gibt lediglich zwei Referenzen, die auf dieses Objekt verweisen.

Wenn Ihnen die Unterscheidung von Grunddaten- und Referenztypen noch neu ist, merken Sie sich am besten, welchen Inhalt die Variablen speichern. Bei den primitiven Datentypen speichern die Variablen die tatsächlichen Werte, bei den Referenztypen hingegen nur Referenzen auf die Werte. Das unterschiedliche Verhalten von Grund- und Referenztypen werden wir in "3.15 by-value vs. by-reference" genauer erkunden.

Möglicherweise ist Ihnen aufgefallen, dass ich nicht angegeben habe, ob Strings in JavaScript Grunddaten- oder Referenztypen sind. Strings sind ein Sonderfall. Da ihre Größe nicht festgelegt ist, können sie offensichtlich nicht direkt in Variablen mit fester Größe gespeichert werden. Man könnte nun erwarten, dass JavaScript aus Effizienzgründen nicht den tatsächlichen Inhalt von Strings, sondern Referenzen auf Strings kopiert. Andererseits verhalten sich Strings in vielerlei Hinsicht wie primitive Datentypen. Allerdings erübrigt sich die Frage, ob Strings ein primitiver Datentyp oder ein Referenztyp sind, da Strings _unveränderlich_ sind: Es gibt keine Möglichkeit, den Inhalt eines Stringwerts zu verändern. Dies bedeutet, dass wir für Strings kein Beispiel wie das obige konstruieren können, das veranschaulicht hat, warum Arrays als Referenz gespeichert werden. Letztlich spielt es keine große Rolle, ob Sie sich Strings als unveränderlichen Referenztyp vorstellen, der sich wie ein primitiver Datentyp verhält, oder als einen primitiven Datentyp, der mit der internen Effizienz eines Referenztyps implementiert ist.

* * *

[19] Dies ist eine übermäßige Vereinfachung, die nicht als wirkliche Beschreibung einer echten JavaScript-Implementierung gedacht ist.

# 4.5 Garbage Collection

Referenztypen haben keine feste Größe; einige davon können sogar sehr groß werden. Wie wir bereits besprochen haben, speichern Variablen Referenzwerte nicht direkt. Der Wert wird an einer anderen Stelle gespeichert, und die Variablen enthalten lediglich eine Referenz auf diesen Speicherort. Nun können wir uns kurz mit dem Speichern des Wertes befassen.

Da Strings, Objekte und Arrays keine feste Größe haben, muss der Speicherplatz für sie dynamisch zugewiesen werden, sobald ihre Größe bekannt ist. Jedes Mal, wenn ein JavaScript-Programm einen String, ein Array oder ein Objekt erstellt, muss der Interpreter den Speicherplatz zuweisen, in dem diese Entität gespeichert wird. Wenn der Speicherplatz auf diese Art dynamisch zugewiesen wird, muss er irgendwann auch wieder für die Wiederverwendung freigegeben werden, weil der JavaScript-Interpreter sonst den ganzen verfügbaren Speicherplatz des Systems aufbraucht und dann abstürzt.

In Programmiersprachen wie C und C++ muss der Speicher manuell freigegeben werden. Der Programmierer ist dafür verantwortlich, alle erzeugten Objekte im Auge zu behalten und sie zu entfernen (ihren Speicher freizugeben), sobald sie nicht mehr benötigt werden. Dies kann nicht nur eine mühsame Arbeit sein, sondern ist auch eine häufige Fehlerursache.

JavaScript erfordert keine manuelle Freigabe, sondern stützt sich auf ein Verfahren, das als _Garbage Collection_ bezeichnet wird. Der JavaScript-Interpreter kann erkennen, wann ein Objekt vom Programm nicht mehr verwendet werden wird. Wenn er feststellt, dass ein Objekt nicht erreichbar ist (d.h., wenn es keine Möglichkeit mehr gibt, mit den Variablen im Programm auf dieses Objekt zu verweisen), dann weiß er, dass das Objekt nicht mehr benötigt wird und sein Speicher freigegeben werden kann. Betrachten Sie zum Beispiel die folgenden Codezeilen:

    var s = "hallo";           // Weise Speicher für einen String zu
    var u = s.toUpperCase( );  // Erzeuge einen neuen String
    s = u;                     // Überschreibe Referenz auf Originalstring

Nachdem dieser Code ausgeführt wurde, ist der ursprüngliche String »hallo« nicht mehr erreichbar: Keine der Variablen im Programm verweist auf ihn. Das System erkennt dies und gibt den Speicherplatz des Strings für die Wiederverwendung frei.

Die Garbage Collection geschieht automatisch und ist für den Programmierer nicht sichtbar. Sie müssen über die Garbage Collection nur wissen, dass Sie sich darauf verlassen können, dass sie funktioniert: Sie müssen nur wissen, dass Sie so viele Müllobjekte erzeugen können, wie Sie wollen, und das System hinter Ihnen schon aufräumen wird

# 4.6 Variablen als Eigenschaften

Mittlerweile haben Sie vielleicht festgestellt, dass es in JavaScript viele Ähnlichkeiten zwischen Variablen und Eigenschaften gibt. Beide werden auf dieselbe Art zugewiesen, beide werden in JavaScript-Ausdrücken auf dieselbe Art verwendet usw. Gibt es wirklich einen grundlegenden Unterschied zwischen der Variablen `i` und der Eigenschaft `i` eines Objekts `o`? Die Anwort lautet: nein. In JavaScript sind Variablen im Grunde dasselbe wie Objekteigenschaften.

## 4.6.1 Das globale Objekt

Wenn der JavaScript-Interpreter startet, gehört zu den ersten Dingen, die er noch vor der Ausführung irgendeines JavaScript-Codes macht, die Erzeugung eines _globalen Objekts_. Die Eigenschaften dieses Objekts sind die globalen Variablen des JavaScript-Programms. Wenn Sie eine globale JavaScript-Variable deklarieren, definieren Sie eigentlich eine Eigenschaft des globalen Objekts.

Der JavaScript-Interpreter initialisiert das globale Objekt mit einer Reihe von Eigenschaften, die auf vordefinierte Werte und Funktionen verweisen. Die Eigenschaft `Infinity` verweist zum Beispiel auf den mathematischen Begriff »unendlich«, die Eigenschaft `parseInt` auf die vordefinierte Funktion `parseInt( )` und die Eigenschaft `Math` auf das vordefinierte Math-Objekt. Informationen zu diesen globalen Werten finden Sie in Teil III.

In Toplevel-Code (d.h. in JavaScript-Code, der nicht zu einer Funktion gehört) können Sie mit dem JavaScript-Schlüsselwort `this` auf das globale Objekt verweisen. Innerhalb von Funktionen hat `this` eine andere Bedeutung, mit der wir uns in Kapitel 8 befassen werden.

In clientseitigem Javascript dient das Window-Objekt als globales Objekt für jeglichen JavaScript-Code, der in dem von diesem Window-Objekt dargestellten Browserfenster läuft. Das globale Window-Objekt hat die selbstreferenzielle Eigenschaft `window`, die man anstelle von `this` dafür verwenden kann, auf das globale Objekt zu verweisen. Das Window-Objekt definiert die globalen Eigenschaften des Sprachkerns wie zum Beispiel `parseInt` und `Math` sowie alle globalen clientseitigen Eigenschaften wie zum Beispiel `navigator` und `screen`.

## 4.6.2 Lokale Variablen: Das Aufrufobjekt

Wenn globale Variablen Eigenschaften des globalen Objekts sind, was sind dann lokale Variablen? Sie sind ebenfalls Eigenschaften eines Objekts. Dieses Objekt wird als das _Aufrufobjekt_ bezeichnet. Das Aufrufobjekt hat eine kürzere Lebensdauer als das globale Objekt, dient aber demselben Zweck. Während der Body einer Funktion ausgeführt wird, werden die Funktionsargumente und lokalen Variablen als Eigenschaften dieses Aufrufobjekts gespeichert. Dank der Verwendung eines eigenen Objekts für lokale Variablen kann JavaScript lokale Variablen davon abhalten, die Werte gleichnamiger globaler Variablen zu überschreiben.

## 4.6.3 JavaScript-Ausführungskontext

Jedes Mal, wenn der JavaScript-Interpreter eine Funktion auszuführen beginnt, erschafft er für diese Funktion einen neuen _Ausführungskontext_. Ein Ausführungskontext ist natürlich der Kontext, in dem ein JavaScript-Codesegment ausgeführt wird. Ein wichtiger Teil des Kontexts ist das Objekt, in dem Variablen definiert werden. JavaScript-Code, der nicht Teil einer Funktion ist, wird daher in einem Ausführungskontext ausgeführt, der für Variablendefinitionen das globale Objekt verwendet. Und jede JavaScript-Funktion wird in ihrem eigenen, eindeutigen Ausführungskontext und mit ihrem eigenen Aufrufobjekt, in dem lokale Variablen definiert werden, ausgeführt.

Ein interessanter Aspekt besteht darin, dass JavaScript-Implementierungen mehrere globale Ausführungskontexte zulassen können, von denen jeder ein _anderes_ globales Objekt hat (obwohl in diesem Fall die globalen Objekte nicht hundertprozentig global sind).[20] Das deutlichste Beispiel hierfür ist clientseitiges JavaScript, bei dem jedes Browserfenster oder jeder Frame in einem Fenster einen eigenen globalen Ausführungskontext definiert. Der clientseitige JavaScript-Code in jedem dieser Frames oder Fenster läuft in seinem eigenen Ausführungskontext und hat ein eigenes globales Objekt. Allerdings haben diese separaten clientseitigen globalen Objekte Eigenschaften, über die sie verbunden sind. So könnte der JavaScript-Code in einem Frame mit dem Ausdruck `parent.frames[1]` auf einen anderen Frame verweisen, und die globale Variable `x` im ersten Frame könnte vom Ausdruck `parent.frames[0].x` im zweiten Frame referenziert werden.

Sie brauchen im Moment noch nicht restlos zu verstehen, wie separate Fenster- oder Frame-Ausführungskontexte in clientseitigem JavaScript miteinander verknüpft werden. Mit diesem Thema werden wir uns in Kapitel 13 ausführlich befassen, wenn wir erklären, wie JavaScript in Webbrowser integriert wird. Sie sollten jedoch bereits jetzt verstehen, dass JavaScript so flexibel ist, dass ein einziger JavaScript-Interpreter Skripten in verschiedenen globalen Ausführungskontexten ausführen kann, und dass diese Kontexte nicht gänzlich voneinander getrennt zu sein brauchen, sondern aufeinander verweisen können.

Der zuletzt genannte Aspekt erfordert zusätzliche Überlegungen. Wenn JavaScript-Code in einem Ausführungskontext Eigenschaftswerte lesen und schreiben und Funktionen ausführen kann, die in einem anderen Ausführungskontext definiert sind, dann haben wir eine Komplexität erreicht, bei der wir über Fragen der Sicherheit nachdenken müssen. Betrachten wir als Beispiel clientseitiges JavaScript. Angenommen, das Browserfenster A führt Skripten aus oder enthält Informationen, die aus Ihrem lokalen Intranet stammen, und Fenster B führt ein Skript aus, das von irgendeiner Website stammt. Normalerweise soll der Code im Fenster B nicht auf die Eigenschaften im Fenster A zugreifen können, da er sonst zum Beispiel unternehmensinterne Informationen lesen und stehlen könnte. Damit der JavaScript-Code auf sichere Art ausgeführt werden kann, muss es einen Sicherheitsmechanismus geben, der einen unerwünschten Zugriff von einem Ausführungskontext auf einen anderen verhindert, wenn ein solcher Zugriff verboten werden sollte. Zu diesem Thema werden wir in "13.8 JavaScript-Sicherheit" zurückkehren.

* * *

[20] Dies ist lediglich eine Randbemerkung. Wenn sie Sie nicht interessiert, können Sie auch zum nächsten Abschnitt weiterblättern.

# 4.7 Noch einmal zum Geltungsbereich von Variablen

Meine frühere Definition des Geltungsbereichs von Variablen basierte ausschließlich auf der lexikalischen Struktur von JavaScript-Code: Globale Variablen haben einen globalen Geltungsbereich, und in Funktionen deklarierte Variablen haben einen lokalen Geltungsbereich. Wenn eine Funktionsdefinition in eine andere geschachtelt ist, dann haben die in dieser geschachtelten Funktion deklarierten Variablen einen geschachtelt-lokalen Geltungsbereich. Nachdem wir nun wissen, dass globale Variablen Eigenschaften eines globalen Objekts und lokale Variablen Eigenschaften eines besonderen Aufrufobjekts sind, können wir zum Begriff des Geltungsbereichs der Variablen zurückkehren und ihn neu definieren. Diese neue Beschreibung des Geltungsbereichs ist eine Art, sich Variablen in vielen Kontexten vorzustellen, und sie ermöglicht es Ihnen, die Arbeitsweise von JavaScript von Grund auf zu verstehen.

Mit jedem JavaScript-Ausführungskontext ist eine _Geltungsbereichskette_ verbunden. Diese Geltungsbereichskette ist eine Liste oder Kette von Objekten. Wenn JavaScript-Code den Wert einer Variablen `x` nachschlagen muss (diesen Prozess bezeichnet man als _Auflösung des Variablennamens_ ), dann betrachtet er zuerst das erste Objekt in der Kette. Wenn dieses Objekt eine Eigenschaft namens `x` hat, wird der Wert dieser Eigenschaft verwendet. Hat das erste Objekt keine Eigenschaft namens `x`, so setzt JavaScript die Suche beim nächsten Objekt in der Kette fort. Wenn das zweite Objekt auch keine Eigenschaft `x` hat, wird die Suche beim nächsten Objekt fortgesetzt usw.

In JavaScript-Code der ersten Ebene (d.h. in Code, der in keiner Funktionsdefinition enthalten ist) besteht die Geltungsbereichskette aus einem einzigen Objekt, nämlich dem globalen Objekt. Alle Variablen werden in diesem Objekt nachgeschlagen. Wenn eine Variable nicht existiert, ist der Variablenwert undefiniert. In einer (nicht geschachtelten) Funktion besteht die Geltungsbereichskette aus zwei Objekten. Das erste ist das Aufrufobjekt der Funktion und das zweite das globale Objekt. Wenn die Funktion auf eine Variable verweist, wird zuerst das Aufrufobjekt (der lokale Geltungsbereich) und dann das globale Objekt (der globale Geltungsbereich) geprüft. Die Geltungsbereichskette einer geschachtelten Funktion enthält drei oder mehr Objekte. Abbildung 4-1 veranschaulicht das Nachschlagen eines Variablennamens in der Geltungsbereichskette einer Funktion.

Abbildung 4-1. Geltungsbereichskette und Variablenauflösung

# Kapitel 5. Ausdrücke und Operatoren

In diesem Kapitel wird erklärt, wie Ausdrücke und Operatoren in JavaScript funktionieren. Wenn Sie mit C, C++ oder Java vertraut sind, werden Sie feststellen, dass die Ausdrücke und Operatoren in JavaScript sehr ähnlich aussehen, und Sie können dieses Kapitel schnell durchblättern. Wenn Sie diese Programmiersprachen hingegen nicht kennen, wird Ihnen dieses Kapitel alles mitteilen, was Sie über Ausdrücke und Operatoren in JavaScript wissen müssen.

# 5.1 Ausdrücke

Ein _Ausdruck_ ist eine JavaScript-Phrase, die ein JavaScript-Interpreter auswerten kann, sodass ein Wert herauskommt. Die einfachsten Ausdrücke sind Literale oder Variablennamen wie die folgenden:

    1.7                             // Ein numerisches Literal
    "JavaScript macht Spaß!"  // Ein String-Literal
    true                            // Ein Boolesches Literal
    null                            // Der Literalwert null
    /java/                          // Ein literaler regulärer Ausdruck
    { x:2, y:2 }                    // Ein Objekt-Literal
    [2,3,5,7,11,13,17,19]           // Ein Array-Literal
    function(x){return x*x;}        // Ein Funktionsliteral
    i                               // Die Variable i
    summe                           // Die Variable summe

Der Wert eines literalen Ausdrucks ist einfach der Literalwert selbst. Der Wert eines Variablenausdrucks ist der Wert, den die Variable enthält oder auf den sie verweist.

Diese Ausdrücke sind nicht sonderlich interessant. Komplexere (und interessantere) Ausdrücke kann man durch Zusammensetzen einfacher Ausdrücke erstellen. Zum Beispiel zeigen die vorigen Beispiele, dass sowohl `1.7` als auch `i` Ausdrücke sind. Daher ist das Folgende ebenfalls ein Ausdruck:

    i + 1.7

Den Wert dieses Ausdrucks bestimmt man, indem man die Werte der beiden einfacheren Ausdrücke addiert. Das Pluszeichen in diesem Beispiel ist ein Operator, der dazu dient, die beiden Ausdrücke zu einem komplexeren Ausdruck zu kombinieren. Ein weiterer Operator ist das Minuszeichen, das dazu benutzt wird, Ausdrücke durch Subtraktion zusammenzufügen. Hierzu ein Beispiel:

    (i + 1.7) − summe

Dieser Ausdruck benutzt den Operator −, um den Wert der Variablen `summe` vom Wert des vorherigen Ausdrucks, `i + 1.7`, abzuziehen. Wie Sie im folgenden Abschnitt sehen werden, unterstützt JavaScript abgesehen von + und – noch eine Reihe weiterer Operatoren.

# 5.2 Übersicht über die Operatoren

Falls Sie schon in den Sprachen C, C++ oder Java programmiert haben, werden Ihnen fast alle JavaScript-Operatoren bereits recht gut bekannt sein. Tabelle 5-1 fasst die Operatoren zusammen. Sie können diese Tabelle als Referenz benutzen. Beachten Sie, dass die meisten Operatoren durch Sonderzeichen wie zum Beispiel + oder = dargestellt werden. Einige werden jedoch durch Schlüsselwörter wie `delete` oder `instanceof` repräsentiert. Schlüsselwortoperatoren sind ebenso reguläre Operatoren wie diejenigen, die durch Sonderzeichen dargestellt werden. Sie werden einfach mit einer besser lesbaren und weniger knappen Syntax ausgedrückt.

In der Tabelle gibt die mit »P« überschriebene Spalte die Operatorpräzedenz an, während die mit »A« bezeichnete Spalte die Assoziativität des Operators angibt, die L (von links nach rechts) oder R (von rechts nach links) sein kann. Falls Sie mit den Begriffen Operatorpräzedenz und Assoziativität noch nicht vertraut sind, finden Sie in den Abschnitten unter der Tabelle Erläuterungen dazu. Die Operatoren selbst sind im Anschluss daran dokumentiert.

Tabelle 5-1. JavaScript-Operatoren

P | A | Operator | Operandentyp(en) | Durchgeführte Operation

---|---|---|---|---

15 | L | `.` | Objekt, Bezeichner | Zugriff auf eine Eigenschaft

|

L | `[]` | Array, ganze Zahl | Array-Index

|

L | `( )` | Funktion, Argumente | Funktionsaufruf

|

R | `new` | Konstruktoraufruf | Erzeugen eines neuen Objekts

14 | R | `++` | lvalue | Prä- oder Postinkrement (unär)

|

R | `−−` | lvalue | Prä- oder Postdekrement (unär)

|

R | `-` | Zahl | unäres Minus (Negation)

|

R | `+` | Zahl | unäres Plus (No-Op)

|

R | `~` | ganze Zahl | Bitweises Komplement (unär)

|

R | `!` | Boolescher Wert | Logisches Komplement (unär)

|

R | `delete` | lvalue | Löschen einer Eigenschaft (unär)

|

R | `typeof` | beliebig | Rückgabe eines Datentyps (unär)

|

R | `void` | beliebig | Rückgabe des undefinierten Wertes (unär)

13 | L | `*, /, %` | Zahlen | Multiplikation, Division, Rest

12 | L | `+, -` | Zahlen | Addition, Subtraktion

|

L | `+` | Strings | String-Verkettung

11 | L | `<<` | Integer | Verschieben nach links

|

L | `>>` | Integer | Verschieben nach rechts mit Vorzeichenerweiterung

|

L | `>>>` | Integer | Verschieben nach rechts mit Null-Auffüllung

10 | L | `<, <=` | Zahlen oder Strings | kleiner als, kleiner gleich

|

L | `>, >=` | Zahlen oder Strings | größer als, größer gleich

|

L | `instanceof` | Objekt, Konstruktor | Objekttyp prüfen

|

L | `in` | String, Objekt | Prüfen, ob die Eigenschaft existiert

9 | L | `==` | beliebig | Auf Gleichheit prüfen

|

L | `!=` | beliebig | Auf Ungleichheit prüfen

|

L | `===` | beliebig | Auf Identität prüfen

|

L | `!==` | beliebig | Auf Nichtidentität prüfen

8 | L | `&` | Integer | Bitweises UND

7 | L | `^` | Integer | Bitweises exklusives ODER

6 | L | `|` | Integer | Bitweises ODER

5 | L | `&&` | Boolescher Wert | Logisches UND

4 | L | `||` | Boolescher Wert | Logisches ODER

3 | R | `?:` | Boolescher Wert, beliebig, beliebig | Bedingungsoperator (drei Operanden)

2 | R | `=` | lvalue, beliebig | Zuweisung

|

R | `*=, /=, %=, +=, -=, <<=, >>=, >>>=, &=, ^=, |=` | lvalue, beliebig | Zuweisung mit Operation

1 | L | , | beliebig | Mehrfachauswertung

## 5.2.1 Anzahl der Operanden

Operatoren können nach der Anzahl der Operanden, die sie erwarten, unterteilt werden. Die meisten JavaScript-Operatoren sind ebenso wie der Operator `+`, den wir bereits gesehen haben, _binäre Operatoren_ , die zwei Ausdrücke zu einem einzigen, komplexeren Ausdruck verbinden. Sie operieren also auf zwei Operanden. Außerdem unterstützt JavaScript eine Reihe von _unären Operatoren_ , die einen einzelnen Ausdruck in einen einzelnen, aber komplexeren Ausdruck umwandeln. Der `-`-Operator im Ausdruck `−3` ist ein unärer Operator, der die Negationsoperation auf dem Operanden `3` durchführt. Schließlich unterstützt JavaScript noch einen _ternären Operator_ , nämlich den Bedingungsoperator `?:`, der die Werte dreier Ausdrücke zu einem einzigen Ausdruck kombiniert.

## 5.2.2 Typen von Operanden

Wenn man JavaScript-Ausdrücke erstellt, muss man sowohl auf die Datentypen achten, die man den Operatoren übergibt, als auch auf diejenigen, die zurückgegeben werden. Verschiedene Operatoren erwarten, dass die Ausdrücke ihrer Operanden als Werte eines bestimmten Datentyps ausgewertet werden. Da es zum Beispiel nicht möglich ist, Strings zu multiplizieren, ist der Ausdruck `"a" * "b"` in JavaScript nicht zulässig. Beachten Sie jedoch, dass JavaScript die Ausdrücke nach Möglichkeit in den passenden Typ umzuwandeln versucht, sodass der Ausdruck `"3" * "5"` durchaus zulässig ist. Sein Wert ist die Zahl `15`, nicht der String `"15"`. JavaScript-Typumwandlungen werden in "3.12 Zusammenfassung der Typumwandlungen" ausführlicher behandelt.

Außerdem verhalten sich einige Operatoren anders, je nachdem mit welchem Typ von Operand sie verwendet werden. Am auffälligsten ist dies beim Operator +, der numerische Operanden addiert, String-Operanden hingegen verkettet. Wenn man ihm einen String und eine Zahl übergibt, wandelt er zuerst die Zahl in einen String um und verkettet dann die beiden Strings. `"1" + 0` ergibt zum Beispiel den String `"10"`.

Beachten Sie, dass die Zuweisungsoperatoren sowie einige andere Operatoren erwarten, dass die Ausdrücke auf der linken Seite Lvalues sind. Mit Lvalue bezeichnet man traditionell »einen Ausdruck, der auf der linken Seite eines Zuweisungsausdrucks erscheinen darf«. In JavaScript sind Variablen, Objekteigenschaften und Array-Elemente Lvalues. Die ECMAScript-Spezifikation lässt zwar zu, dass integrierte Funktionen Lvalues zurückgeben, definiert aber keine integrierte Funktion, die sich tatsächlich so verhält.

Schließlich ist noch zu beachten, dass Operatoren nicht immer den Typ zurückgeben, den ihre Operanden haben. Die Vergleichsoperatoren (kleiner als, gleich, größer als usw.) nehmen zwar Operanden verschiedener Typen entgegen; aber wenn Vergleichsausdrücke ausgewertet werden, liefern sie immer einen einfachen Booleschen Wert, der angibt, ob der Vergleich wahr ist oder nicht. Der Ausdruck `a < 3` gibt zum Beispiel `true` zurück, falls der Wert der Variablen `a` kleiner ist als `3`. Wie wir noch sehen werden, werden die Booleschen Werte, die von Vergleichsoperatoren zurückgegeben werden, in `if`-Anweisungen, `while`\- und `for`-Schleifen verwendet, also in JavaScript-Anweisungen, die die Ausführung eines Programms in Abhängigkeit davon steuern, welches Ergebnis die Auswertung von Ausdrücken liefert, die Vergleichsoperatoren enthalten.

## 5.2.3 Rangfolge der Operatoren

In Tabelle 5-1 gibt die mit »P« überschriebene Spalte die Präzedenz der einzelnen Operatoren an. Die Präzedenz der Operatoren bestimmt, in welcher Reihenfolge die Operationen ausgeführt werden. Operatoren mit einer höheren Zahl in der Spalte »P« werden vor Operatoren mit niedrigeren Zahlen ausgeführt.

Betrachten Sie z. B. den folgenden Ausdruck:

    w = x + y*z;

Da der Multiplikationsoperator `*` eine höhere Präzedenz als der Operator + hat, wird die Multiplikation vor der Addition ausgeführt. Da der Zuweisungsoperator = die niedrigste Präzedenz hat, wird die Zuweisung erst vorgenommen, nachdem alle Operationen auf der rechten Seite abgeschlossen sind.

Die Operatorpräzedenz kann durch die ausdrückliche Verwendung von Klammern überschrieben werden. Um zu erzwingen, dass die Addition im obigen Beispiel zuerst ausgeführt wird, schreiben wir:

    w = (x + y)*z;

Wenn Sie sich in der Praxis einmal unsicher sind, welcher Operator Vorrang hat, ist es am einfachsten, die Reihenfolge der Auswertung durch Klammern ausdrücklich anzugeben. Die einzigen Regeln, die Sie kennen sollten, sind die folgenden: Multiplikation und Division werden vor Addition und Subtraktion durchgeführt, und die Zuweisung hat eine sehr niedrige Präzedenz und wird fast immer zuletzt vorgenommen.

## 5.2.4 Assoziativität von Operatoren

In Tabelle 5-1 gibt die mit »A« überschriebene Spalte die Assoziativität des jeweiligen Operators an. Der Wert L steht für Assoziativität von links nach rechts und der Wert R für die Assoziativität von rechts nach links. Die Assoziativität eines Operators gibt die Reihenfolge an, in der Operationen derselben Präzedenz durchgeführt werden. Die Assoziativität von links nach rechts bedeutet, dass die Operationen von links nach rechts ausgeführt werden. Da der Additionsoperator eine Assoziativität von links nach rechts hat, ist

    w = x + y + z;

dasselbe wie:

    w = ((x + y) + z);

Andererseits sind die (mehr oder weniger sinnlosen) Ausdrücke

    x = ~-~y;
    w = x = y = z;
    q = a?b:c?d:e?f:g;

äquivalent zu den folgenden, da die unären Operatoren, der Zuweisungs- und der ternäre Bedingungsoperator eine Assoziativität von rechts nach links haben.

    x = ~(-(~y));
    w = (x = (y = z));
    q = a?b:(c?d:(e?f:g));

# 5.3 Arithmetische Operatoren

Nachdem wir die Operatorpräzedenz und -assoziativität sowie weitere Grundlagen besprochen haben, können wir nun damit beginnen, die Operatoren selbst zu beschreiben. Dieser Abschnitt erläutert die arithmetischen Operatoren:

 _Addition_ (`+`)

Der Operator `+` addiert numerische Operanden oder verkettet String-Operanden. Wenn der eine Operand ein String ist, wird der andere in einen String umgewandelt, und dann werden die beiden Strings verkettet. Objektoperanden werden in Zahlen oder Strings umgewandelt, die addiert oder verkettet werden können. Die Umwandlung wird mit der Methode `valueOf( )` und/oder der Methode `toString( )` des Objekts vorgenommen.

 _Subtraktion_ (`-`)

Wenn `-` als binärer Operator verwendet wird, subtrahiert es den zweiten Operanden vom ersten. Wenn es mit nicht-numerischen Operanden verwendet wird, versucht es, diese in Zahlen umzuwandeln.

 _Multiplikation_ (`*`)

Der Operator `*` multipliziert seine beiden Operanden. Wenn er mit nicht-numerischen Operanden verwendet wird, versucht er, diese in Zahlen umzuwandeln.

 _Division_ (`/`)

Der Operator `/` dividiert seinen ersten Operanden durch den zweiten. Wenn er mit nicht-numerischen Operanden verwendet wird, versucht er, diese in Zahlen umzuwandeln. Wenn Sie an Programmiersprachen gewöhnt sind, die zwischen ganzen und Gleitkommazahlen unterscheiden, erwarten Sie vielleicht, ein ganzzahliges Ergebnis zu erhalten, wenn Sie eine ganze Zahl durch eine andere dividieren. Da in JavaScript jedoch alle Zahlen Gleitkommazahlen sind, ist auch das Ergebnis jeder Division eine Gleitkommazahl: `5/2` wird als `2.5` ausgewertet, nicht als `2`. Die Division durch null ergibt positiv oder negativ unendlich, während `0/0` als `NaN` ausgewertet wird.

 _Modulo_ (`%`)

Der Operator `%` berechnet den ersten Operanden modulo dem zweiten. Mit anderen Worten: Er liefert den Rest zurück, wenn der erste Operand eine bestimmte Anzahl von Malen durch den zweiten Operand dividiert wird. Wenn der Modulo-Operator mit nicht-numerischen Operanden verwendet wird, versucht er, diese in Zahlen umzuwandeln. Das Ergebnis hat dasselbe Vorzeichen wie der erste Operand. `5 % 2` wird zum Beispiel als `1` ausgewertet.

Der Modulo-Operator wird zwar normalerweise mit ganzzahligen Operanden verwendet, aber er funktioniert auch mit Gleitkommawerten. So wird zum Beispiel `−4.3 % 2.1` als `−0.1` ausgewertet.

 _Unäre Negation_ (`-`)

Wenn `-` als unärer Operator vor einem einzelnen Operanden verwendet wird, führt er eine unäre Negation durch. Anders ausgedrückt: Er wandelt einen positiven Wert in den entsprechenden negativen Wert um und umgekehrt. Wenn der Operand keine Zahl ist, versucht der Operator, ihn in eine Zahl umzuwandeln.

 _Unäres Plus_ (`+`)

Wegen der Symmetrie mit dem unären Minusoperator verfügt JavaScript auch über einen unären Plusoperator. Dieser Operator ermöglicht es Ihnen, das Vorzeichen numerischer Literale ausdrücklich anzugeben, falls Sie den Eindruck haben, dass Ihr Code dadurch klarer wird.

    var gewinn = +1000000;

In Code wie diesem macht der Operator + nichts. Er wird einfach als der Wert seines Arguments ausgewertet. Beachten Sie jedoch, dass der Operator `+` ein nicht-numerisches Argument in eine Zahl umwandelt. Falls er das Argument nicht umwandeln kann, gibt er `NaN` zurück.

 _Inkrement_ (`++`)

Der Operator `++` inkrementiert seinen einzigen Operanden, d.h., er addiert 1 zu dessen Wert. Der Operand muss eine Variable, ein Array-Element oder eine Objekteigenschaft sein. Wenn der Wert der Variablen, des Elements oder der Eigenschaft keine Zahl ist, versucht der Operator zuerst, ihn in eine Zahl umzuwandeln. Das genaue Verhalten des Operators hängt davon ab, an welcher Position er sich relativ zum Operanden befindet. Wenn er als sogenannter Präinkrement-Operator vor dem Operanden verwendet wird, inkrementiert er den Operanden und hat den inkrementierten Wert dieses Operanden zum Ergebnis. Wenn er als sogenannter Postinkrement-Operator hinter dem Operanden verwendet wird, inkrementiert er zwar ebenfalls den Operanden, liefert als Ergebnis aber den _nicht-inkrementierten_ Wert dieses Operanden. Wenn der Wert, der inkrementiert werden soll, keine Zahl ist, wird er während dieses Prozesses in eine Zahl umgewandelt.

Beispielsweise setzt das folgende Progammsegment sowohl `i` als auch `j` auf `2`:

    i = 1;
    j = ++i;

Die folgenden Zeilen setzen hingegen `i` auf `2` und `j` auf `1` :

    i = 1;
    j = i++;

Dieser Operator wird in beiden Formen am häufigsten dafür verwendet, einen Zähler zu erhöhen, der eine Schleife steuert. Beachten Sie, dass Sie zwischen den Postinkrement- oder Postdekrement-Operator und den vorangehenden Operanden keinen Zeilenumbruch einfügen dürfen, da JavaScript den Operanden sonst als eine in sich geschlossene Anweisung behandelt und davor ein Semikolon einfügt.

 _Dekrement_ (`−−`)

Der Operator `−−` dekrementiert seinen einzigen numerischen Operanden, d.h., er subtrahiert 1 von seinem Wert. Der Operand muss eine Variable, ein Array-Element oder eine Objekteigenschaft sein. Wenn der Wert dieser Variablen, dieses Elements oder dieser Eigenschaft keine Zahl ist, versucht der Operator zuerst, ihn in eine Zahl umzuwandeln. Ebenso wie beim Operator ++ hängt auch das genaue Verhalten von `−−` von der Position des Operators in bezug auf den dekrementierten Wert ab. Wenn der Operator vor dem Operanden verwendet wird, dekrementiert er den Wert und gibt den verringerten Wert zurück. Wenn er nach dem Operanden verwendet wird, dekrementiert er den Operanden ebenfalls, gibt aber den _nicht-dekrementierten_ Wert zurück.

# 5.4 Gleichheitsoperatoren

Dieser Abschnitt beschreibt die Gleichheits- und Ungleichheitsoperatoren von JavaScript. Dabei handelt es sich um die Operatoren, die zwei Werte vergleichen, um zu prüfen, ob sie gleich oder ungleich sind, und die je nach dem Ergebnis des Vergleichs einen der Booleschen Werte `true` oder `false` zurückgeben. Wie wir in Kapitel 6 sehen werden, werden sie am häufigsten in Konstruktionen wie `if`-Anweisungen und `for`-Schleifen verwendet, wo sie den Ablauf der Programmausführung steuern.

## 5.4.1 Gleichheit (==) und Identität (===)

Die Operatoren `==` und `===` prüfen, ob zwei Werte übereinstimmen, verwenden dabei aber zwei unterschiedliche Definitionen der Übereinstimmung. Beide Operatoren nehmen Operanden eines beliebigen Typs entgegen. Beide geben `true` zurück, falls ihre Operanden übereinstimmen, und sie geben `false` zurück, falls die Operanden nicht übereinstimmen. Der Operator `===` wird als Identitätsoperator bezeichnet; er prüft mit einer strengen Definition der Übereinstimmung, ob seine beiden Operanden »identisch« sind. Der Operator `==` wird als Gleichheitsoperator bezeichnet; er prüft, ob seine beiden Operanden »gleich« sind und geht dabei von einer weniger strengen Definition der Übereinstimmung aus, die auch Typumwandlungen zulässt.

Der Identitätsoperator wird von ECMAScript v3 standardisiert und ist ab JavaScript 1.3 implementiert. Seit der Einführung des Identitätsoperators unterstützt JavaScript die Operatoren `=`, `==` und `===`. Es ist wichtig, dass Sie den Unterschied zwischen dem Zuweisungs-, dem Gleichheits- und dem Identitätsoperator verstehen und beim Schreiben von Code den korrekten Operator verwenden! Es ist zwar verlockend, für alle drei Operatoren »ist gleich« zu sagen, aber um Verwirrung zu vermeiden, sollten Sie für `=` »wird zugewiesen«, für `==` »ist gleich« und für `===` »ist identisch mit« sagen.

In JavaScript werden Zahlen, Strings und Boolesche Werte _nach ihrem Wert (by value)_ verglichen. In diesem Fall sind an dem Vergleich zwei Werte beteiligt, und die Operatoren `==` und `===` prüfen, ob diese beiden Werte gleich bzw. identisch sind. Dies bedeutet, dass zwei Variablen genau dann gleich bzw. identisch sind, wenn sie denselben Wert enthalten. Zwei Strings sind zum Beispiel genau dann gleich, wenn sie genau dieselben Zeichen enthalten.

Objekte, Arrays und Funktionen werden hingegen _nach ihrer Referenz (by reference)_ verglichen. Dies bedeutet, dass zwei Variablen genau dann gleich sind, wenn sie auf dasselbe Objekt verweisen. Zwei separate Arrays sind nie gleich oder identisch, selbst dann nicht, wenn sie gleiche oder identische Elemente enthalten. Zwei Variablen, die Referenzen auf Objekte, Arrays oder Funktionen enthalten, sind genau dann gleich, wenn sie auf dasselbe Objekt oder Array oder auf dieselbe Funktion verweisen. Wollen Sie prüfen, ob zwei Objekte dieselben Eigenschaften haben oder ob zwei Arrays dieselben Elemente enthalten, dann müssen Sie die Eigenschaften und Elemente einzeln auf Gleichheit oder Identität prüfen. (Und falls eine Eigenschaft oder ein Element auch selbst wieder ein Objekt oder ein Array ist, müssen Sie entscheiden, wie weit der Vergleich in die Tiefe gehen soll.)

Mit den folgenden Regeln kann man ermitteln, ob zwei Werte laut dem Operator `===` identisch sind:

  * Wenn die beiden Werte unterschiedliche Typen haben, sind sie nicht identisch.

  * Wenn beide Werte Zahlen sind und denselben Wert haben, sind sie identisch, sofern nicht einer oder beide `NaN`-Werte sind. In diesem Fall sind sie nicht identisch. Der Wert `NaN` ist nie mit einem anderen Wert und auch nicht mit sich selbst identisch. Um zu prüfen, ob ein Wert `NaN` ist, verwenden Sie die Funktion `isNaN( )`.

  * Wenn beide Werte Strings sind und genau dieselben Zeichen an genau denselben Positionen enthalten, sind sie identisch. Wenn die Strings sich in Länge oder Inhalt unterscheiden, sind sie nicht identisch. Beachten Sie, dass der Unicode-Standard in einigen Fällen für einen String mehrere Schreibweisen zulässt. Aus Effizienzgründen werden Strings in JavaScript jedoch streng Zeichen für Zeichen verglichen, und der String-Vergleich geht davon aus, dass alle Strings vor dem Vergleich in eine »normalisierte Form« umgewandelt wurden. Eine weitere Möglichkeit, Strings zu vergleichen, finden Sie im Referenzeintrag `String.localeCompare( )` im Referenzteil dieses Buches.

  * Wenn beide Werte der Boolesche Wert `true` oder wenn beide der Boolesche Wert `false` sind, dann sind sie identisch.

  * Wenn beide Werte auf dasselbe Objekt oder Array oder dieselbe Funktion verweisen, sind sie identisch. Wenn sie auf verschiedene Objekte (oder Arrays oder Funktionen) verweisen, sind sie selbst dann nicht identisch, wenn beide Objekte identische Eigenschaften oder beide Arrays identische Elemente haben.

  * Wenn beide Werte `null` oder beide Werte `undefined` sind, sind sie identisch.

Mit den folgenden Regeln kann man ermitteln, ob zwei Werte laut dem Operator `==` gleich sind:

  * Prüfen Sie die beiden Werte auf Identität, falls sie denselben Typ haben. Falls die Werte identisch sind, sind sie gleich; wenn sie nicht identisch sind, sind sie nicht gleich.

  * Die beiden Werte können auch gleich sein, wenn sie nicht denselben Typ haben. Mit den folgenden Regeln und Typumwandlungen können Sie auf Gleichheit prüfen:

    * Wenn der eine Wert `null` und der andere `undefined` ist, sind sie gleich.

    * Wenn der eine Wert eine Zahl und der andere ein String ist, wandeln Sie den String in eine Zahl um und versuchen dann, den Vergleich mit dem umgewandelten Wert vorzunehmen.

    * Wenn einer der Werte `true` ist, wandeln Sie ihn in 1 um, und versuchen Sie, den Vergleich noch einmal vorzunehmen. Wenn einer der Werte `false` ist, wandeln Sie ihn in 0 um, und versuchen Sie dann, den Vergleich noch einmal vorzunehmen.

    * Wenn der eine Wert ein Objekt und der andere eine Zahl oder ein String ist, wandeln Sie das Objekt in einen Wert eines primitiven Datentyps um, und nehmen Sie den Vergleich erneut vor. Für die Umwandlung des Objekts in einen einfachen Wert verwenden Sie entweder seine Methode `toString( )` oder seine Methode `valueOf( )`. Die integrierten Klassen des Sprachkerns von JavaScript versuchen erst die Umwandlung mit `valueOf( )` und dann erst die mit `toString( )`. Die einzige Ausnahme ist die Klasse Date, die Umwandlungen mit `toString( )` vornimmt. Objekte, die nicht zum Sprachkern von JavaScript gehören, können sich selbst auf eine Weise, die von der jeweiligen Implementierung definiert wird, in einfache Werte umwandeln.

    * Alle anderen Kombinationen von Werten sind nicht gleich.

Sehen Sie sich als Beispiel für die Gleichheitsprüfung den folgenden Vergleich an:

    "1" == true

Dieser Ausdruck wird als `true` ausgewertet, was darauf hinweist, dass diese Werte zwar sehr unterschiedlich aussehen, aber dennoch gleich sind. Zuerst wird der Boolesche Wert `true` in die Zahl 1 umgewandelt, und anschließend wird der Vergleich noch einmal vorgenommen. Dann wird der String `"1"` in die Zahl 1 umgewandelt. Da beide Zahlen gleich sind, gibt der Vergleich `true` zurück.

## 5.4.2 Ungleichheit (!=) und Nichtidentität (!==)

Die Operatoren `!=` und `!==` sind den Operatoren `==` und `===` genau entgegengesetzt. Der Ungleichheitsoperator `!=` gibt `false` zurück, falls zwei Werte gleich sind; ansonsten gibt er `true` zurück. Der Nichtidentitätsoperator `!==` gibt `false` zurück, falls zwei Werte miteinander identisch sind; ansonsten gibt er `true` zurück. Dieser Operator wurde von ECMAScript v3 standardisiert und ist ab der Version JavaScript 1.3 implementiert.

Wie Sie noch sehen werden, berechnet der Operator `!` die Boolesche Operation NICHT. Deshalb kann man sich leicht merken, dass `!=` für »ungleich« und `!==` für »nicht identisch mit« steht. Einzelheiten dazu, wie Gleichheit und Identität für verschiedene Datentypen definiert sind, finden Sie im vorhergehenden Abschnitt.

# 5.5 Relationsoperatoren

Dieser Abschnitt beschreibt die Relationsoperatoren in JavaScript. Das sind diejenigen Operatoren, die prüfen, ob zwischen zwei Werten eine bestimmte Beziehung (zum Beispiel »kleiner als« oder »Eigenschaft von«) besteht, und die je nachdem, ob es diese Beziehung gibt oder nicht, `true` oder `false` zurückgeben. Wie in Kapitel 6 gezeigt wird, werden sie am häufigsten in Strukturen wie der `if`-Anweisung und `while`-Schleifen eingesetzt, um den Ablauf der Programmausführung zu steuern.

## 5.5.1 Vergleichsoperatoren

Die Relationsoperatoren, die am häufigsten verwendet werden, sind die Vergleichsoperatoren, mit denen man die relative Reihenfolge zweier Werte ermittelt. Es gibt die folgenden Vergleichsoperatoren:

 _Kleiner als_ (`<`)

Der Operator `<` hat das Ergebnis `true`, wenn sein erster Operand kleiner ist als der zweite; sonst liefert er `false`.

 _Größer als_ (`>`)

Der Operator `>` hat das Ergebnis `true`, falls sein erster Operand größer ist als der zweite; sonst liefert er `false`.

 _Kleiner gleich_ (`<=`)

Der Operator `<=` hat das Ergebnis `true`, falls sein erster Operand kleiner oder gleich dem zweiten Operanden ist; sonst liefert er `false`.

 _Größer gleich_ (`>=`)

Der Operator `>=` hat das Ergebnis `true`, falls sein erster Operand größer oder gleich dem zweiten Operanden ist; sonst liefert er `false`.

Die Operanden dieser Vergleichsoperatoren können einen beliebigen Typ haben. Da jedoch nur Zahlen und Strings verglichen werden können, werden Operanden, die keine Zahlen oder Strings sind, umgewandelt. Der Vergleich und die Umwandlung werden folgendermaßen vorgenommen:

  * Wenn beide Operanden Zahlen sind oder wenn beide in Zahlen umgewandelt werden können, werden sie numerisch verglichen.

  * Wenn beide Operanden Strings sind oder in Strings umgewandelt werden können, werden sie als Strings verglichen.

  * Wenn der eine Operand ein String ist oder in einen String umgewandelt werden kann und der andere eine Zahl ist oder in eine Zahl umgewandelt werden kann, dann versucht der Operator, den String in eine Zahl umzuwandeln und einen numerischen Vergleich durchzuführen. Wenn der String keine Zahl darstellt, wird er in `NaN` umgewandelt, und der Vergleich ist `false`. (In JavaScript 1.1 gibt die Umwandlung eines Strings in eine Zahl nicht `NaN` zurück, sondern löst einen Fehler aus.)

  * Wenn ein Objekt entweder in eine Zahl oder in einen String umgewandelt werden kann, nimmt JavaScript eine numerische Umwandlung vor. Dies bedeutet zum Beispiel, dass Date-Objekte numerisch verglichen werden und es sinnvoll ist, zwei Daten zu vergleichen, um herauszufinden, ob das eine Datum einen früheren Zeitpunkt angibt als das andere.

  * Wenn die Operanden der Vergleichsoperatoren nicht in Zahlen oder Strings umgewandelt werden können, geben diese Operatoren immer `false` zurück.

  * Wenn einer der Operanden ein `NaN` ist oder in einen `NaN` umgewandelt wird, gibt der Vergleichsoperator immer `false` zurück.

Denken Sie daran, dass der String-Vergleich streng Zeichen für Zeichen vorgenommen wird, wofür jeweils der numerische Wert verwendet wird, den ein Zeichen in der Unicode-Kodierung hat. In einigen Fällen lässt der Unicode-Standard zwar zu, dass äquivalente Strings mit verschiedenen Zeichenfolgen geschrieben werden, aber die JavaScript-Vergleichsoperatoren erkennen diese Kodierungsunterschiede nicht, sondern gehen davon aus, dass alle Strings in normalisierter Form ausgedrückt sind. Achten Sie insbesondere darauf, dass der String-Vergleich zwischen Groß- und Kleinschreibung unterscheidet. In der Unicode-Kodierung (zumindest in der ASCII-Untergruppe) sind alle Großbuchstaben »kleiner als« alle Kleinbuchstaben. Diese Regel kann zu verwirrenden Ergebnissen führen, falls Sie nicht damit rechnen. Laut dem Operator `<` ist der String »Zoo« zum Beispiel kleiner als der String »aardvark«.

Die Methode `String.localeCompare( )` liefert einen robusteren Algorithmus für den String-Vergleich. Außerdem berücksichtigt diese Methode eine locale-spezifische Definition der alphabetischen Reihenfolge. Soll ein Vergleich die Groß- und Kleinschreibung nicht berücksichtigen, so müssen Sie zuerst alle Strings entweder mit `String.toLowerCase( )` in Kleinbuchstaben oder mit `String.toUpperCase( )` in Großbuchstaben umwandeln.

Die Operatoren `<=` (kleiner gleich) und `>=` (größer gleich) stützen sich nicht auf den Gleichheits- und den Identitätsoperator, wenn sie ermitteln, ob zwei Werte »gleich« sind. Stattdessen ist der Kleiner-gleich-Operator einfach als »nicht größer als« definiert und der Größer-gleich-Operator ist als »nicht kleiner als« definiert. Nur wenn einer der Operanden ein `NaN` ist (oder in einen `NaN` umgewandelt wird), geben alle vier Vergleichsoperatoren `false` zurück.

## 5.5.2 Der in-Operator

Der Operator `in` erwartet auf der linken Seite einen Operanden, der ein String ist oder in einen String umgewandelt werden kann. Auf der rechten Seite erwartet er einen Operanden, der ein Objekt (oder Array) ist. Er wird als `true` ausgewertet, wenn der Wert auf der linken Seite der Name einer Eigenschaft des Objekts auf der rechten Seite ist. Hierzu einige Beispiele:

    var punkt = { x:1, y:1 };            // Definiere ein Objekt
    var hat_x_koordinate = "x" in punkt; // Wird als true ausgewertet
    var hat_y_koordinate = "y" in punkt; // Wird als true ausgewertet
    var hat_z_koordinate = "z" in punkt; // Wird als false ausgewertet;
                                         // kein 3-D-Punkt
    var ts = "toString" in punkt;        // geerbte Eigenschaft;
                                         // wird als true ausgewertet

## 5.5.3 Der instanceof-Operator

Der Operator `instanceof` erwartet als Operanden auf der linken Seite ein Objekt und als Operanden auf der rechten Seite den Namen einer Objekt-Klasse. Der Operator liefert das Ergebnis `true`, wenn das Objekt auf der linken Seite eine Instanz der Klasse auf der rechten Seite ist; ansonsten wird er als `false` ausgewertet. In Kapitel 9 werden Sie sehen, dass Klassen von Objekten in JavaScript von der Konstruktorfunktion definiert werden, mit der sie initialisiert werden. Der rechte Operand von `instanceof` sollte daher der Name einer Konstruktorfunktion sein. Denken Sie daran, dass alle Objekte Instanzen von `Object` sind. Hierzu einige Beispiele:

    var d = new Date();  // Erzeuge ein neues Objekt mit dem Konstruktor
                         // Date( )
    d instanceof Date;   // Wird als true ausgewertet; d wurde mit
                         // Date( ) erzeugt
    d instanceof Object; // Wird als true ausgewertet; alle Objekte
                         // sind Instanzen von Object
    d instanceof Number; // Wird als false ausgewertet; d ist kein
                         // Number-Objekt
    var a = [1, 2, 3];   // Erzeuge ein Array mit der Array-Literalsyntax
    a instanceof Array;  // Wird als true ausgewertet; a ist ein Array
    a instanceof Object; // Wird als true ausgewertet; alle Arrays sind
                         // Objekte
    a instanceof RegExp; // Wird als false ausgewertet; Arrays sind keine
                         // regulären Ausdrücke

Wenn der Operand auf der linken Seite von `instanceof` kein Objekt ist oder der Operand auf der rechten Seite zwar ein Objekt, aber keine Konstruktorfunktion ist, dann gibt `instanceof` den Wert `false` zurück. Der Operator gibt hingegen einen Laufzeitfehler zurück, wenn der Operand auf der rechten Seite kein Objekt ist.

# 5.6 String-Operatoren

Wie bereits in den vorangegangenen Abschnitten besprochen, gibt es verschiedene Operatoren, die ein besonderes Verhalten zeigen, wenn ihre Operanden Strings sind.

Der Operator + verkettet zwei String-Operanden, d.h., er erzeugt einen neuen String, der aus dem ersten String und dem dahinter folgenden zweiten String besteht. Der folgende Ausdruck wird als der String »Hallo, du da« ausgewertet:

    "Hallo" + ", " + "du da"

Und die folgenden Zeilen erzeugen den String »22«:

    a = "2"; b = "2";
    c = a + b;

Die Operatoren `<`, `<=`, `>` und `>=` vergleichen zwei Strings daraufhin, in welcher Reihenfolge sie stehen. Der Vergleich verwendet die alphabetische Ordnung. Wie in "5.5.1 Vergleichsoperatoren" angemerkt, basiert diese alphabetische Reihenfolge auf der Unicode-Zeichenkodierung, die von JavaScript verwendet wird. In dieser Kodierung kommen alle Großbuchstaben im lateinischen Alphabet vor den Kleinbuchstaben (d.h. Großbuchstaben »sind kleiner als« Kleinbuchstaben), was zu unerwarteten Ergebnissen führen kann.

Die Operatoren `==` und `!=` funktionieren auf Strings, aber wie wir bereits gesehen haben, funktionieren sie mit allen Datentypen und zeigen bei der Verwendung mit Strings kein besonderes Verhalten.

Der Operator + ist ein ganz besonderer Operator: Er gibt String-Operanden Vorrang vor numerischen Operanden. Wenn einer der Operanden von + ein String (oder ein Objekt) ist, wird der andere Operand in einen String umgewandelt (oder beide Operanden werden in Strings umgewandelt), und die beiden Strings werden nicht addiert, sondern verkettet. Andererseits führen die Vergleichsoperatoren nur dann einen String-Vergleich durch, wenn beide Operanden Strings sind. Wenn nur ein Operand ein String ist, versucht JavaScript, ihn in eine Zahl umzuwandeln. Die folgenden Zeilen veranschaulichen dies:

    1 + 2          // Addition. Ergebnis ist 3.
    "1" + "2"      // Verkettung. Ergebnis ist "12".
    "1" + 2        // Verkettung; 2 wird in "2" konvertiert.
                   // Ergebnis ist "12".
    11 < 3      // Numerischer Vergleich. Ergebnis ist false.
    "11" < "3"  // String-Vergleich. Ergebnis ist true.
    "11" < 3    // Numerischer Vergleich; "11" wird in 11 konvertiert.
                   // Ergebnis ist false.
    "eins" < 3  // Numerischer Vergleich; "eins" wird in NaN konvertiert.
                   // Ergebnis ist false.

Schließlich muss man noch bedenken, dass der Operator `+` nicht unbedingt assoziativ ist, wenn er mit Strings und Zahlen verwendet wird. Mit anderen Worten: Das Ergebnis kann von der Reihenfolge abhängen, in der die Operationen ausgeführt werden. Die folgenden Beispiele veranschaulichen dies:

    s = 1 + 2 + " Chinesen mit nem Kontrabass";          // Ergibt "3 Chinesen mit
                                                         // nem Kontrabass"
    t = "Anzahl Chinesen mit nem Kontrabass: " + 1 + 2;  // Ergibt "Anzahl Chinesen mit
                                                         // nem Kontrabass: 12"

Der Grund für diesen erstaunlichen Unterschied im Verhalten liegt darin, dass der Operator + von links nach rechts vorgeht, sofern nicht Klammern diese Reihenfolge abändern. Daher sind die beiden obigen Beispiele äquivalent zu den folgenden:

    s = (1 + 2) + "Chinesen mit nem Kontrabass";           // erstes + ergibt Zahl,
                                                           // zweites ergibt String
    t = ("Anzahl Chinesen mit nem Kontrabass: " + 1) + 2;  // beide Operationen ergeben
                                                           // Strings

# 5.7 Logische Operatoren

Die logischen Operatoren verwendet man normalerweise für Boolesche Algebra. Häufig verwendet man sie zusammen mit Vergleichsoperatoren, um komplexe Vergleiche auszudrücken, an denen mehr als eine Variable beteiligt ist. Außerdem werden sie häufig mit `if`-, `while`\- und `for`-Anweisungen verwendet.

## 5.7.1 Logisches UND (&&)

Wenn der Operator `&&` mit Booleschen Operanden verwendet wird, führt er auf den beiden Werten die Boolesche Operation UND aus. Er gibt nur dann `true` zurück, wenn der erste _und_ der zweite Operand `true` sind. Wenn einer oder beide Operanden `false` sind, gibt der Operator `false` zurück.

Das tatsächliche Verhalten dieses Operators ist etwas kompliziert. Zuerst wertet er den ersten Operanden aus, also den Ausdruck auf seiner linken Seite. Wenn der Wert dieses Ausdrucks in `false` umgewandelt werden kann (zum Beispiel, wenn der linke Operand als `null`, `0`, `""` oder `undefined` ausgewertet wird), dann gibt der Operator den Wert des linken Ausdrucks zurück. Andernfalls wertet er seinen zweiten Operanden, den Ausdruck auf der rechten Seite, aus und gibt den Wert dieses Ausdrucks zurück.[21]

Beachten Sie, dass es bei diesem Operator vom Wert des linken Ausdrucks abhängt, ob der Ausdruck auf der rechten Seite ausgewertet wird oder nicht. Manchmal sieht man Code, der diese Eigenschaft des Operators `&&` bewusst nutzt. So haben zum Beispiel die folgenden beiden JavaScript-Zeilen dieselbe Wirkung:

    if (a == b) stop( );
    (a == b) && stop( );

Einige Programmierer (insbesondere Perl-Programmierer) halten dies zwar für ein natürliches und nützliches Programmieridiom, aber ich rate davon ab. Die Tatsache, dass die rechte Seite nicht unbedingt ausgewertet wird, ist eine häufige Fehlerquelle. Betrachten Sie zum Beispiel den folgenden Code:

    if ((a == null) && (b++ > 10)) stop( );

Diese Anweisung macht vermutlich nicht das, was der Programmierer eigentlich beabsichtigt, da der Inkrement-Operator auf der rechten Seite nicht ausgewertet wird, falls der Vergleich auf der linken Seite `false` ist. Dieses Problem können Sie umgehen, indem Sie nur dann Ausdrücke verwenden, die auf der rechten Seite von `&&` Seiteneffekte (Zuweisungen, Inkremente, Dekremente und Funktionsaufrufe) haben, wenn Sie mit absoluter Sicherheit wissen, was Sie tun.

Trotz der etwas verwirrenden Arbeitsweise dieses Operators ist es am einfachsten und sichersten, ihn sich schlicht als einen Booleschen Algebra-Operator vorzustellen. Er gibt zwar nicht wirklich einen Booleschen Wert zurück, aber der Rückgabewert kann immer in einen Booleschen Wert umgewandelt werden.

## 5.7.2 Logisches ODER (||)

Wenn der Operator `||` mit Booleschen Operanden verwendet wird, führt er auf den beiden Werten die Boolesche Operation ODER durch: Falls der erste oder der zweite Operand `true` ist oder beide Operanden `true` sind, gibt er `true` zurück. Falls beide Operanden `false` sind, gibt er `false` zurück.

Der Operator `||` wird zwar häufig einfach als ein Boolescher ODER-Operator verwendet, aber ebenso wie der Operator `&&` hat auch er eigentlich ein komplexeres Verhalten. Zuerst wertet er den ersten Operanden aus, also den Ausdruck auf seiner linken Seite. Wenn der Wert dieses Ausdrucks in `true` umgewandelt werden kann, gibt der Operator den nicht umgewandelten Wert des linken Ausdrucks zurück. Andernfalls wertet er den zweiten Operanden, den Ausdruck auf seiner rechten Seite, aus und gibt den Wert dieses Ausdrucks zurück.[22]

Ebenso wie beim Operator `&&` sollten Sie auch bei diesem Operator auf der rechten Seite nur dann Operanden verwenden, die Seiteneffekte haben, wenn Sie die Tatsache nutzen möchten, dass der rechte Ausdruck möglicherweise nicht ausgewertet wird.

Selbst wenn der Operator `||` mit Operanden verwendet wird, die keine Booleschen Werte sind, kann man ihn dennoch als Booleschen ODER-Operator betrachten, da sein Ergebniswert unabhängig von seinem Typ immer in einen Booleschen Wert umgewandelt werden kann.

Andererseits werden Sie gelegentlich auf Code stoßen, der `||` mit Nicht-Booleschen Operanden verwendet und den Umstand ausnutzt, dass der Rückgabewert kein Boolescher Wert ist. In einer eigentümlichen Verwendung dieses Operators wird er eingesetzt, um den ersten Wert aus einer Gruppe von Alternativen zurückzuliefern, der definiert und nicht-`null` ist (das heißt, der erste Wert, der nicht in `false` konvertiert wird). Hier ist ein Beispiel:

    // Verwendet maximalebreite, wenn es definiert ist. Andernfalls wird nach einem Wert im
    // optionen-Objekt gesucht. Ist dieser nicht definiert, wird eine vorgegebene Konstante
    // verwendet.
    var max = maximalebreite || optionen.maximalbreite || 500;

## 5.7.3 Logisches Nicht (!)

Der Operator `!` ist ein unärer Operator, der vor seinem einzigen Operanden steht. Sein Zweck besteht darin, den Booleschen Wert des Operanden »umzudrehen«. Hat beispielsweise die Variable `a` den Wert `true` (oder einen Wert, der in `true` umgewandelt werden kann), dann hat `!a` den Wert `false`. Und wenn der Ausdruck `p && q` den Wert `false` ergibt (oder einen Wert, der in `false` umgewandelt werden kann), dann ergibt `!(p && q)` den Wert `true`.

Falls es erforderlich ist, wandelt der `!`-Operator seinen Operanden (unter Anwendung der in Kapitel 3 beschriebenen Regeln) in einen Booleschen Wert um und invertiert dann den umgewandelten Wert. Das bedeutet, dass Sie jeden beliebigen Wert `x` in sein Boolesches Äquivalent umwandeln können, indem Sie diesen Operator zweifach anwenden: `!!x`.

* * *

[21] Wenn der linke Ausdruck in JavaScript 1.0 und JavaScript 1.1 als `false` ausgewertet wird, gibt der Operator `&&` nicht den nicht-umgewandelten Wert des linken Ausdrucks, sondern `false` zurück.

[22] Wenn der linke Ausdruck in JavaScript 1.0 und JavaScript 1.1 in `true` umgewandelt werden kann, gibt der Operator nicht den nicht-umgewandelten Wert des linken Ausdrucks, sondern `true` zurück.

# 5.8 Bit-Operatoren

Ungeachtet dessen, dass alle Zahlen in JavaScript Gleitkommazahlen sind, benötigen die Bit-Operatoren numerische Operanden mit ganzzahligen Werten. Sie operieren auf diesen ganzzahligen Operanden, indem sie eine 32-Bit-Integer-Darstellung statt der äquivalenten Gleitkommadarstellung benutzen. Vier dieser Operatoren führen auf den einzelnen Bits der Operanden Boolesche Algebra aus, wobei sie sich so verhalten, als sei jedes Bit in jedem der Operanden ein Boolescher Wert, und ähnliche Operationen durchführen wie die logischen Operatoren, die wir bereits gesehen haben. Die anderen drei Bit-Operatoren dienen dazu, Bits nach links oder nach rechts zu verschieben.

Wenn die Bit-Operatoren mit Operanden verwendet werden, die keine ganzen Zahlen sind oder die für die 32-Bit-Integer-Darstellung zu groß sind, erzwingen sie die Darstellung der Operanden als 32-Bit-Integer, indem sie einfach alle Dezimalstellen des Operanden oder alle Bits nach dem 32. Bit fallen lassen. Die Shift-Operatoren erfordern auf der rechten Seite einen Operanden zwischen 0 und 31. Nachdem sie diesen Operanden in einen 32-Bit-Integer umgewandelt haben, lassen sie alle Bits nach dem fünften fallen, wodurch sich eine Zahl im passenden Wertebereich ergibt.

Wenn Sie mit Binärzahlen und der Binärdarstellung ganzer Zahlen im Dezimalsystem nicht vertraut sind, können Sie die in diesem Abschnitt beschriebenen Operatoren überspringen. Der Zweck dieser Operatoren ist hier nicht beschrieben; sie werden für die Bearbeitung von Binärzahlen auf niedrigster Ebene benötigt und in der JavaScript-Programmierung nur selten verwendet. Es gibt die folgenden Bit-Operatoren:

 _Bitweises UND_ (`&`)

Der Operator `&` führt auf jedem Bit seiner ganzzahligen Argumente eine Boolesche UND-Operation durch. Ein Bit im Ergebnis wird nur dann gesetzt, wenn in beiden Operanden das entsprechende Bit gesetzt ist. So wird zum Beispiel `0x1234 & 0x00FF` als `0x0034` ausgewertet.

 _Bitweises ODER_ (`|`)

Der Operator `|` führt auf jedem Bit seiner ganzzahligen Argumente eine Boolesche ODER-Operation durch. Ein Bit im Ergebnis wird nur dann gesetzt, wenn in einem oder beiden Operanden das entsprechende Bit gesetzt ist. So wird zum Beispiel `9 | 10` als `11` ausgewertet.

 _Bitweises exklusives ODER_ (`^`)

Der Operator `^` führt auf jedem Bit seiner ganzzahligen Argumente eine Boolesche Exklusives-ODER-Operation durch. Exklusives ODER bedeutet, dass entweder der erste Operand oder der zweite Operand `true` ist, nicht aber beide. Ein Bit wird im Ergebnis dieser Operation gesetzt, wenn in einem der beiden Operanden (aber nicht in beiden) das entsprechende Bit gesetzt ist. So wird zum Beispiel `9 ^ 10` als `3` ausgewertet.

 _Bitweises NICHT_ (`~`)

Der Operator `~` ist ein unärer Operator, der vor seinem einzigen, ganzzahligen Argument steht. Er kehrt alle Bits im Operanden um. Aufgrund der Art, wie vorzeichenbehaftete ganze Zahlen in JavaScript dargestellt werden, ist die Anwendung des Operators `~` auf einen Wert gleichbedeutend damit, sein Vorzeichen zu ändern und 1 zu subtrahieren. So wird zum Beispiel `~0x0f` als `0xfffffff0`, also `−16`, ausgewertet.

 _Verschieben nach links_ (`<<`)

Der Operator `<<` verschiebt alle Bits in seinem ersten Operanden um die Anzahl Stellen nach links, die der zweite Operand angibt. Dieser Operand sollte ein Integer zwischen 0 und 31 sein. In der Operation `a << 1` wird zum Beispiel das erste Bit (das für die Einer-Stelle der Binärzahl steht) von `a` zum zweiten Bit (das für die Zweier-Stelle der Binärzahl steht), das zweite Bit von `a` wird zum dritten usw. Als neues erstes Bit wird eine Null eingefügt, und der Wert des 32. Bit geht verloren. Die Verschiebung eines Wertes um eine Position nach links ist gleichbedeutend mit seiner Multiplikation mit 2; die Verschiebung um zwei Positionen ist äquivalent zur Multiplikation mit 4 usw. So wird z. B. `7 << 1` als `14` ausgewertet.

 _Verschieben nach rechts mit Vorzeichenerweiterung_ (`>>`)

Der Operator `>>` verschiebt alle Bits in seinem ersten Operanden um die Anzahl von Stellen nach rechts, die der zweite Operand (ein Integer von 0 bis 31) angibt. Bits, die rechts hinausgeschoben werden, gehen verloren. Die auf der linken Seite eingefügten Bits hängen vom Vorzeichenbit des ursprünglichen Operanden ab. Dadurch wird das Vorzeichen des Ergebnisses beibehalten. Wenn der erste Operand positiv ist, werden im Ergebnis die oberen Bits auf null gesetzt; und wenn der erste Operand negativ ist, werden im Ergebnis die oberen Bits auf eins gesetzt. Das Verschieben eines Wertes um eine Position nach rechts ist gleichbedeutend mit seiner Division durch 2 (unter Vernachlässigung des Rests), das Verschieben um zwei Positionen nach rechts ist gleichbedeutend mit der ganzzahligen Division durch 4 usw. So wird z. B. `7 >> 1` als `3` ausgewertet, und `−7 >> 1` wird als `−4` ausgewertet.

 _Verschieben nach rechts mit Null-Auffüllung_ (`>>>`)

Der Operator `>>>` ist dem Operator `>>` sehr ähnlich. Allerdings sind die auf der linken Seite eingeschobenen Bits unabhängig vom Vorzeichen des ersten Operanden immer 0. So wird zum Beispiel `−1 >> 4` als `−1` ausgewertet, `−1 >>> 4` jedoch als `268435455` (`0x0fffffff`).

# 5.9 Zuweisungsoperatoren

Wie wir bereits bei der Erörterung der Variablen in Kapitel 4 gesehen haben, wird `=` in JavaScript dafür verwendet, einer Variablen einen Wert zuzuweisen. Hierzu ein Beispiel:

    i = 0

Obwohl man eine solche JavaScript-Zeile normalerweise vielleicht nicht als einen Ausdruck ansieht, der ausgewertet werden kann, handelt es sich doch um einen Ausdruck, und rein technisch gesehen ist `=` ein Operator.

Der Operator = erwartet als linken Operanden entweder eine Variable, das Element eines Arrays oder eine Eigenschaft eines Objekts. Als rechten Operanden erwartet er einen beliebigen Wert eines beliebigen Typs. Der Wert eines Zuweisungsausdrucks ist der Wert des rechten Operanden. Als Seiteneffekt weist der Operator = den Wert auf der rechten Seite der Variablen, dem Element oder der Eigenschaft auf der linken Seite zu, sodass bei einer späteren Verwendung der Variablen, des Elements oder der Eigenschaft dieser Wert benutzt wird.

Da = als Operator definiert ist, können Sie ihn in komplexere Ausdrücke einfügen. Beispielsweise kann man mit Code wie dem folgenden innerhalb eines einzigen Ausdrucks einen Wert zuweisen und prüfen:

    (a = b) == 0

Wenn Sie dies machen, sollte Ihnen der Unterschied zwischen den Operatoren `=` und `==` vollkommen klar sein

Der Zuweisungsoperator ist von rechts nach links assoziativ. Dies bedeutet, dass bei mehreren Zuweisungsoperatoren innerhalb eines einzigen Ausdrucks von rechts nach links ausgewertet wird. Als Folge davon kann man Code wie den folgenden schreiben, um einen Wert mehreren Variablen zuzuweisen:

    i = j = k = 0;

Denken Sie daran, dass jeder Zuweisungsausdruck einen Wert hat, der gleich dem Wert der rechten Seite ist. Im obigen Code wird also der Wert der ersten (ganz rechts stehenden) Zuweisung zur rechten Seite der zweiten Zuweisung (der mittleren), und dieser Wert wird die rechte Seite der letzten Zuweisung (die ganz links steht).

## 5.9.1 Zuweisung mit Operation

Neben dem normalen Zuweisungsoperator = unterstützt JavaScript noch eine Reihe weiterer Zuweisungsoperatoren, die Abkürzungen bieten, da sie eine Zuweisung mit einer anderen Operation verbinden. So führt der Operator `+=` zum Beispiel eine Addition und eine Zuweisung aus. Der Ausdruck

    gesamt += umsatzsteuer

ist äquivalent zu diesem Ausdruck:

    gesamt = gesamt + umsatzsteuer

Wie zu erwarten wäre, funktioniert der Operator `+=` sowohl bei Zahlen als auch bei Strings. Bei numerischen Operanden führt er eine Addition und eine Zuweisung durch, und bei String-Operanden führt er eine Verkettung und eine Zuweisung aus.

Ähnliche Operatoren sind `-=`, `*=`, `&=` usw. Tabelle 5-2 enthält alle diese Operatoren.

Tabelle 5-2. Zuweisungsoperatoren

Operator | Beispiel | Äquivalent

---|---|---

`+=` | `a += b` | `a = a + b`

`-=` | `a -= b` | `a = a – b`

`*=` | `a *= b` | `a = a * b`

`/=` | `a /= b` | `a = a / b`

`%=` | `a %= b` | `a = a % b`

`<<=` | `a <<= b` | `a = a << b`

`>>=` | `a >>= b` | `a = a >> b`

`>>>=` | `a >>>= b` | `a = a >>> b`

`&=` | `a &= b` | `a = a & b`

`|=` | `a |= b` | `a = a | b`

`^=` | `a ^= b` | `a = a ^ b`

In den meisten Fällen ist der folgende Ausdruck, bei dem op für einen Operator steht,

    a _op_ = b

gleichbedeutend mit:

    a = a _op_ b

Diese Ausdrücke unterscheiden sich nur dann, wenn `a` Seiteneffekte wie zum Beispiel einen Funktionsaufruf oder einen Inkrement-Operator enthält.

# 5.10 Sonstige Operatoren

JavaScript unterstützt eine Reihe weiterer Operatoren, die in den folgenden Abschnitten beschrieben werden.

## 5.10.1 Der Bedingungsoperator (?:)

Der Bedingungsoperator ist in JavaScript der einzige ternäre Operator (mit drei Operanden), weshalb er manchmal auch als _der ternäre Operator_ bezeichnet wird. Dieser Operator wird gelegentlich als `?:` geschrieben, obwohl er im Code nicht ganz so aussieht. Da dieser Operator drei Operanden hat, steht der erste Operand vor dem `?`, der zweite zwischen `?` und `:` und der dritte hinter dem `:`. Er wird folgendermaßen verwendet:

    x > 0 ? x*y : -x*y

Der erste Operand eines Bedingungsoperators muss ein Boolescher Wert sein (oder in einen umgewandelt werden können). Normalerweise ist er das Ergebnis eines Vergleichsausdrucks. Der zweite und der dritte Operand können beliebige Werte haben. Der vom Bedingungsoperator zurückgegebene Wert hängt von dem Booleschen Wert des ersten Operanden ab. Wenn dieser Operand `true` ist, ist der Wert des Bedingungsausdrucks der Wert des zweiten Operanden. Wenn der erste Operand `false` ist, ist der Wert des Bedingungsausdrucks der Wert des dritten Operanden.

Mit der `if`-Anweisung können Sie zwar ähnliche Ergebnisse erzielen, aber der Operator `?:` bietet häufig eine praktische Abkürzung. Der folgende Code zeigt eine typische Verwendungsmöglichkeit. Er prüft, ob eine Variable definiert ist. Trifft dies zu, dann verwendet er sie, ansonsten stellt er einen Standardwert bereit.

    gruss = "Hallo, " + (username != null ? username : "du da");

Dies ist gleichbedeutend mit der folgenden `if`-Anweisung, aber kompakter:

    gruss = "Hallo, ";
    if (username != null)
       gruss += username;
    else
       gruss += "du da";

## 5.10.2 Der typeof-Operator

`typeof` ist ein unärer Operator, der vor seinem einzigen Operanden steht, der einen beliebigen Typ haben kann. Sein Wert ist ein String, der den Datentyp des Operanden angibt.

Der Operator `typeof` wird als »number«, »string« oder »boolean« ausgewertet, wenn sein Operand eine Zahl, ein String oder ein Boolescher Wert ist. Für Objekte, Arrays und (überraschenderweise) `null` wird er als »object« ausgewertet. Für Funktionsoperanden wird er als »function« ausgewertet und als »undefined«, wenn der Operand nicht definiert ist.

`typeof` wird als »object« ausgewertet, wenn sein Operand ein Number-, String- oder Boolean-Wrapper-Objekt ist. Außerdem wird dieser Operator für Date- und RegExp-Objekte als »object« ausgewertet. Bei Objekten, die nicht zum Sprachkern von JavaScript gehören, sondern von dem Kontext bereitgestellt werden, in den JavaScript eingebettet ist, wird `typeof` als ein implementierungsabhängiger Wert ausgewertet. In clientseitigem JavaScript wird `typeof` hingegen normalerweise für alle clientseitigen Objekte genauso wie für alle Objekte des Sprachkerns als »object« ausgewertet.

Den Operator `typeof` können Sie zum Beispiel in Ausdrücken wie den folgenden verwenden:

    typeof i
    (typeof wert == "string") ? "'" + wert + "'" : wert

Beachten Sie, dass Sie den Operanden von `typeof` in Klammern setzen können, wodurch `typeof` eher wie ein Funktionsname als wie ein Operatorschlüsselwort aussieht:

    typeof(i)

Da `typeof` für alle Objekt- und Array-Typen als »object« ausgewertet wird, ist er nur sinnvoll, wenn man Objekte von Grundtypen unterscheiden möchte. Wenn Sie einen Objekttyp von anderen Objekttypen unterscheiden möchten, benötigen Sie andere Verfahren wie zum Beispiel den Operator `instanceof` (siehe "5.5.3 Der instanceof-Operator") oder die Eigenschaft `constructor` (siehe den Eintrag `Object.constructor` im Referenzteil).

Der Operator `typeof` wurde von der Spezifikation ECMAScript v1 definiert und ist ab der Version JavaScript 1.1 implementiert.

## 5.10.3 Operator zur Objekterzeugung (new)

Der Operator `new` erzeugt ein neues Objekt und ruft eine Konstruktorfunktion auf, um das Objekt zu initialisieren. `new` ist ein unärer Operator, der vor einem Konstruktoraufruf steht. Er hat die folgende Syntax:

    new _Konstruktor_ ( _Argumente_ )

Der _Konstruktor_ muss ein Ausdruck sein, der als eine Konstruktorfunktion ausgewertet wird. Dahinter sollten in runden Klammern null oder mehr durch Kommata voneinander getrennte Argumente folgen. Speziell für den Operator `new` vereinfacht JavaScript die Grammatik und lässt zu, dass die Klammern weggelassen werden, wenn der Funktionsaufruf keine Argumente enthält. Hier sehen Sie einige Beispiele für den Operator `new`:

    o = new Object;  // Optionale Klammern können hier weggelassen werden
    d = new Date( ); // Gibt ein Date-Objekt mit dem jetzigen Zeitpunkt zurück
    c = new Rechteck(3.0, 4.0, 1.5, 2.75);  // Erzeuge ein Objekt
                                            // der Klasse Rechteck
    obj[i] = new konstruktoren[i]( );

Der Operator `new` erzeugt zuerst ein neues Objekt, für das keine Eigenschaften definiert werden. Anschließend ruft er die angegebene Konstruktorfunktion auf, wobei er die angegebenen Argumente übergibt und das neu erzeugte Objekt als Wert des Schlüsselworts `this` übergibt. Die Konstruktorfunktion kann das neue Objekt dann mit dem Schlüsselwort `this` auf jede gewünschte Art initialisieren. Über den Operator `new`, das Schlüsselwort `this` und die Konstruktorfunktionen werden Sie in Kapitel 7 mehr erfahren.

Mit dem Operator `new` kann man auch Arrays erstellen, wofür man die Schreibweise `new Array( )` verwendet. Die Erzeugung von Objekten und Arrays sowie die Arbeit mit ihnen wird ausführlicher in Kapitel 7 beschrieben.

## 5.10.4 Der delete-Operator

`delete` ist ein unärer Operator, der die Variable, die Objekteigenschaft oder das Array-Element, die oder das als sein Operand angegeben ist, zu löschen versucht.[23] Ist der Löschvorgang erfolgreich verlaufen, wird `true` zurückgegeben; konnte der Operand hingegen nicht gelöscht werden, gibt der Operator `false` zurück. Nicht alle Variablen und Eigenschaften können gelöscht werden: Einige in den Sprachkern integrierte Eigenschaften und auch clientseitige Eigenschaften sind gegen das Löschen immun, und benutzerdefinierte Variablen, die mit der `var`-Anweisung deklariert wurden, können nicht gelöscht werden. Wenn der Operator `delete` auf einer nicht vorhandenen Eigenschaft aufgerufen wird, gibt er `true` zurück. (Überraschenderweise gibt der Standard ECMAScript vor, dass `delete` auch dann `true` zurückgibt, wenn der Operand weder eine Eigenschaft noch ein Array-Element noch eine Variable ist.) Die folgenden Beispiele veranschaulichen die Verwendung dieses Operators:

    var o = {x:1, y:2}; // Definiere eine Variable; initialisiere
                        // sie als Objekt
    delete o.x;         // Lösche eine der Objekteigenschaften;
                        // gibt true zurück
    typeof o.x;         // Eigenschaft nicht vorhanden;
                        // gibt "undefined" zurück
    delete o.x;         // Lösche nicht-existente Eigenschaft;
                        // gibt true zurück
    delete o;           // Kann eine deklarierte Variable nicht löschen;
                        // gibt false zurück
    delete 1;           // Kann einen Integer nicht löschen;
                        // gibt true zurück
    x = 1;              // Deklariere eine Variable implizit ohne das
                        // Schlüsselwort var
    delete x;           // Kann diese Art Variable nicht löschen;
                        // gibt true zurück
    x;                  // Laufzeitfehler: x ist nicht definiert

Beachten Sie, dass eine gelöschte Eigenschaft oder Variable oder ein gelöschtes Element nicht einfach auf den Wert `undefined` gesetzt wird. Wenn eine Eigenschaft gelöscht wird, hört sie zu existieren auf. Lesen Sie dazu auch den "4.3.2 Nicht definiert oder nicht initialisiert?".

Es ist wichtig zu verstehen, dass `delete` nur die Eigenschaften betrifft und nicht die Objekte, auf die diese Eigenschaften verweisen. Betrachten Sie zum Beispiel den folgenden Code:

    var mein = new Object();                        // Erzeuge ein Objekt namens "mein"
    mein.einstellungsdatum = new Date();            // mein.einstellungsdatum verweist auf
                                                    // ein Date-Objekt
    mein.entlassungsdatum = mein.einstellungsdatum; // mein.entlassungsdatum verweist auf
                                                    // dasselbe Objekt
    delete mein.einstellungsdatum;                  // einstellungsdatum-Eigenschaft wurde
                                                    // gelöscht; gibt true zurück
    delete.write(mein.entlassungsdatum);            // ... aber mein.entlassungsdatum
                                                    // verweist immer noch auf das Date-
                                                    // Objekt

## 5.10.5 Der void-Operator

`void` ist ein unärer Operator, der vor seinem einzigen Operanden steht, der einen beliebigen Typ haben kann. Dieser Operator dient einem ungewöhnlichen Zweck: Er verwirft den Wert seines Operanden und gibt `undefined` zurück. Am häufigsten wird dieser Operator in der clientseitigen URL `javascript:` verwendet, wo er es Ihnen ermöglicht, einen Ausdruck seiner Seiteneffekte wegen auszuwerten, ohne den Wert des ausgewerteten Ausdrucks im Browser anzuzeigen.

Sie können den Operator `void` zum Beispiel folgendermaßen in einem HTML-Tag verwenden:

    <a href="javascript:void window.open( );">Öffne neues Fenster</a>

Eine weitere Verwendung von `void` besteht darin, den Wert `undefined` gezielt zu erzeugen. `void` wurde von ECMAScript v1 spezifiziert und in JavaScript 1.1 implementiert. Die globale Eigenschaft `undefined` wurde jedoch erst von ECMAScript v3 angegeben und in der JavaScript-Version 1.5 implementiert. Wegen der Abwärtskompatibilität kann es daher sinnvoll sein, sich nicht auf die Eigenschaft `undefined` zu verlassen, sondern einen Ausdruck wie zum Beispiel `void 0` zu verwenden.

## 5.10.6 Der Kommaoperator (,)

Der Kommaoperator ist sehr einfach. Er wertet zuerst sein linkes Argument aus, dann das rechte und gibt schließlich den Wert des rechten Arguments zurück. Die folgende Zeile wird daher als 2 ausgewertet:

    i=0, j=1, k=2;

Sie ist im Wesentlichen äquivalent zu den folgenden Zeilen:

    i = 0;
    j = 1;
    k = 2;

Dieser seltsame Operator ist nur in wenigen, eng begrenzten Situationen nützlich, vor allem wenn Sie in einer Situation, in der nur ein einziger Ausdruck zulässig ist, mehrere voneinander unabhängige Ausdrücke mit Seiteneffekten auswerten müssen. In der Praxis wird der Kommaoperator normalerweise nur zusammen mit der Schleifenanweisung `for` verwendet, die Sie in Kapitel 6 kennenlernen werden.

## 5.10.7 Operatoren für den Array- und Objektzugriff

Wie wir schon in Kapitel 3 kurz angemerkt haben, können Sie auf die Elemente eines Arrays mit eckigen Klammern (`[]`) und auf die Elemente eines Objekts mit einem Punkt (`.`) zugreifen. Sowohl `[]` als auch `.` werden in JavaScript als Operatoren betrachtet.

Der Operator `.` erwartet als linken Operanden ein Objekt und als rechten einen Bezeichner (einen Eigenschaftsnamen). Der rechte Operand sollte weder ein String noch eine Variable, die einen String enthält, sondern der literale Name der Eigenschaft oder Methode ohne jegliche Anführungszeichen sein. Hierzu einige Beispiele:

    document.lastModified
    navigator.appName
    frames[0].length
    document.write("Hallo Welt")

Wenn die angegebene Eigenschaft in dem Objekt nicht existiert, gibt JavaScript keine Fehlermeldung aus, sondern gibt als Wert des Ausdrucks stattdessen einfach `undefined` zurück.

Die meisten Operatoren erlauben für beide Operanden beliebige Ausdrücke, solange der Operandentyp passt. Der Operator `.` stellt eine Ausnahme dar: Der rechte Operand muss ein Bezeichner sein. Nichts anderes ist zulässig.

Der Operator `[]` ermöglicht den Zugriff auf Array-Elemente. Außerdem ermöglicht er den Zugriff auf Objekteigenschaften, allerdings ohne die Restriktionen, die der Operator `.` dem rechten Operanden auferlegt. Wenn der erste Operand (der vor der linken eckigen Klammer steht) auf ein Array verweist, sollte der rechte Operand (der zwischen den eckigen Klammern steht) ein Ausdruck sein, der als ganze Zahl ausgewertet wird. Hierzu einige Beispiele:

    frames[1]
    document.forms[i + j]
    document.forms[i].elements[j++]

Wenn der erste Operand des Operators `[]` eine Referenz auf ein Objekt ist, sollte der zweite Operand ein Ausdruck sein, der als String ausgewertet wird, der eine Eigenschaft des Objekts benennt. Achten Sie in diesem Fall darauf, dass der zweite Operand ein String und kein Bezeichner ist. Er sollte eine in Anführungszeichen gesetzte Konstante oder eine Variable oder ein Ausdruck sein, die oder der auf einen String verweist. Hierzu einige Beispiele:

    document["lastModified"]
    frames[0]['length']
    data["wert" + i]

Der Operator `]` wird normalerweise dafür verwendet, auf die Elemente eines Arrays zuzugreifen. Für den Zugriff auf die Eigenschaften eines Objekts ist er weniger praktisch als der Operator `.`, da der Name der Eigenschaft in Anführungszeichen gesetzt werden muss. Wenn jedoch ein Objekt als assoziatives Array verwendet wird und die Eigenschaftsnamen dynamisch generiert werden, dann kann nicht der Operator `.`, sondern nur der Operator `[]` verwendet werden. Dies ist allgemein dann der Fall, wenn Sie die `for/in`-Schleife verwenden, die in [Kapitel 6 eingeführt wird. Der folgende JavaScript-Code gibt mit Hilfe einer `for/in`-Schleife und des Operators `[]` die Namen und Werte aller Eigenschaften in einem Objekt `o` aus:

    for (f in o) {
      document.write('o.' + f + ' = ' + o[f]);
      document.write('<br>');
    }

## 5.10.8 Der Operator für Funktionsaufrufe

Der Operator `( )` wird in JavaScript zum Aufrufen von Funktionen verwendet. Dieser Operator ist ungewöhnlich, weil er keine feste Anzahl von Operanden hat. Der erste Operand ist immer der Name einer Funktion oder eines Ausdrucks, der auf eine Funktion verweist. Dahinter folgen die linke runde Klammer und beliebig viele zusätzliche Operanden, die beliebige Ausdrücke sein können und durch Kommata voneinander getrennt sind. Hinter dem letzten Operanden folgt die rechte runde Klammer. Der Operator `( )` wertet alle seine Operanden aus und ruft dann die Funktion auf, die vom ersten Operanden angegeben wird, wobei die Werte der übrigen Operanden als Argumente übergeben werden. Hierzu einige Beispiele:

    document.close( )
    Math.sin(x)
    alert("Willkommen, " + name)
    Date.UTC(2000, 11, 31, 23, 59, 59)
    funcs[i].f(funcs[i].args[0], funcs[i].args[1])

* * *

[23] Wenn Sie ein C++-Programmierer sind, sollten Sie darauf achten, dass der JavaScript-Operator `delete` keineswegs mit dem Operator `delete` in C++ übereinstimmt. In JavaScript wird die Deallokation von Speicherplatz automatisch von der Garbage Collection vorgenommen, und Sie brauchen sich nie um eine manuelle Freigabe von Speicher zu kümmern. Daher ist auch kein Operator `delete` im Stil von C++ nötig, der ganze Objekte löscht.

# Kapitel 6. Anweisungen

Wie wir im vorigen Kapitel gesehen haben, sind Ausdrücke JavaScript-Phrasen, die ausgewertet werden können, damit sie einen Wert liefern. Operatoren innerhalb eines Ausdrucks können zwar Seiteneffekte haben, aber im Allgemeinen machen Ausdrücke überhaupt nichts. Damit etwas passiert, müssen Sie JavaScript-Anweisungen verwenden, die einem vollständigen Satz oder Befehl entsprechen. Dieses Kapitel beschreibt die verschiedenen JavaScript-Anweisungen und erklärt ihre Syntax. Da ein JavaScript-Programm einfach eine Sammlung von Anweisungen ist, können Sie mit dem Schreiben von JavaScript-Programmen beginnen, sobald Sie mit den JavaScript-Anweisungen vertraut sind.

Bevor wir uns die JavaScript-Anweisungen genauer ansehen, sollten Sie sich in Erinnerung rufen, dass Anweisungen in JavaScript durch Semikola voneinander getrennt werden (siehe "2.4 Wahlweise Semikola", »Wahlweise Semikola«). Wenn Sie jede Anweisung auf eine eigene Zeile setzen, können Sie die Semikola in JavaScript weglassen. Dennoch ist es sinnvoll, sich anzugewöhnen, überall Semikola zu verwenden.

# 6.1 Anweisungen mit Ausdrücken

Die einfachste Art von JavaScript-Anweisungen sind Ausdrücke, die Seiteneffekte haben. Diese Art von Anweisung wurde schon in Kapitel 5 gezeigt. Eine große Gruppe hiervon sind die Zuweisungsanweisungen. Einige Beispiele:

    s = "Hallo " + name;
    i *= 3;

Der Inkrement-Operator `++` und der Dekrement-Operator `−−` sind mit den Zuweisungsanweisungen verwandt. Ihr Seiteneffekt besteht darin, dass sie den Wert einer Variablen ändern, als ob eine Zuweisung stattgefunden hätte:

    zaehler++;

Der Operator `delete` hat den wichtigen Seiteneffekt, dass er eine Objekteigenschaft löscht. Daher wird er normalerweise nicht als Teil eines größeren Ausdrucks, sondern fast immer als Anweisung verwendet:

    delete o.x;

Funktionsaufrufe sind eine weitere wichtige Gruppe von Ausdrucksanweisungen. Einige Beispiele:

    alert("Willkommen, " + name);
    window.close( );

Diese clientseitigen Funktionsaufrufe sind zwar Ausdrücke, haben aber auch Auswirkungen auf den Webbrowser und sind demnach ebenfalls Anweisungen.

Hat eine Funktion keinerlei Seiteneffekte, dann ist es sinnlos, sie aufzurufen, sofern sie nicht Teil einer Zuweisungsanweisung ist. So würden Sie zum Beispiel nicht einfach einen Kosinus berechnen und dann das Ergebnis verwerfen:

    Math.cos(x);

Stattdessen würden Sie den Wert berechnen und ihn zur weiteren Verwendung einer Variablen zuweisen:

    cx = Math.cos(x);

Achten Sie auch hier wieder darauf, dass jede Codezeile in diesen Beispielen mit einem Semikolon endet.

# 6.2 Zusammengesetzte Anweisungen

In Kapitel 5 wurde beschrieben, wie man mit dem Kommaoperator eine Reihe von Ausdrücken zu einem einzigen Ausdruck zusammenfassen kann. In JavaScript kann außerdem eine Reihe von Anweisungen zu einer einzigen Anweisung, einem sogenannten Anweisungsblock, zusammengefasst werden. Hierfür schließt man einfach eine beliebige Anzahl von Anweisungen in geschweifte Klammern ein. So verhalten sich die folgenden Zeilen wie eine einzige Anweisung und können überall verwendet werden, wo JavaScript eine einzelne Anweisung erwartet:

    {
      x = Math.PI;
      cx = Math.cos(x);
      alert("cos(" + x + ") = " + cx);
    }

Beachten Sie, dass dieser Anweisungsblock zwar als eine einzige Anweisung agiert, aber nicht mit einem Semikolon endet. Die elementaren Anweisungen innerhalb des Blocks enden mit Semikola, der Block selbst hingegen nicht.

Während Ausdrücke nur relativ selten mit Hilfe des Kommaoperators kombiniert werden, ist das Kombinieren von Ausdrücken zu größeren Anweisungsblöcken ein außerordentlich weit verbreitetes Verfahren. Wie Sie in den folgenden Abschnitten sehen werden, enthalten einige JavaScript-Anweisungen ihrerseits wiederum Anweisungen (genauso, wie Ausdrücke wiederum Ausdrücke enthalten können). Diese Anweisungen nennt man _zusammengesetzte Anweisungen_. Die formale JavaScript-Syntax legt fest, dass jede zusammengesetzte Anweisung eine einzige Unteranweisung enthält. Mit Anweisungsblöcken können Sie beliebig viele Anweisungen in diese eine zulässige Unteranweisung setzen.

Um eine zusammengesetzte Anweisung auszuführen, führt der JavaScript-Interpreter einfach die Anweisungen, aus denen sie besteht, in der Reihenfolge aus, in der sie geschrieben sind. Normalerweise führt der JavaScript-Interpreter alle Anweisungen aus. Unter bestimmten Umständen kann eine zusammengesetzte Anweisung jedoch abrupt enden. Dieser Abbruch tritt dann ein, wenn die zusammengesetzte Anweisung eine der Anweisungen `break`, `continue`, `return` oder `throw` enthält, einen Fehler auslöst oder eine Funktion aufruft, die einen nicht abgefangenen Fehler oder eine nicht abgefangene Exception auslöst. Über diese Arten des abrupten Abbruchs werden Sie in späteren Abschnitten mehr erfahren.

# 6.3 if

Die Anweisung `if` ist die grundlegende Steueranweisung, die es JavaScript gestattet, Entscheidungen zu fällen, d.h. Anweisungen je nach den Umständen auszuführen. Diese Anweisung existiert in zwei Formen. Die erste lautet:

    if ( _Ausdruck_ )
                        _Anweisung_

In dieser Form wird der _`Ausdruck`_ ausgewertet. Ist der Ergebniswert `true` oder kann er in `true` umgewandelt werden, dann wird die _`Anweisung`_ ausgeführt. Ist der _`Ausdruck`_ jedoch `false` oder kann er in `false` umgewandelt werden, dann wird die _`Anweisung`_ nicht ausgewertet. Hierzu ein Beispiel:

    if (username == null)          // Wenn username null oder
      username = "Theobald Tiger"; // undefined ist, definiere ihn

Und noch ein Beispiel:

    // Wenn username null, undefined, 0, "" oder NaN ist, kann er in false
    // konvertiert werden, und diese Anweisung weist ihm einen
    // neuen Wert zu.
    if (!username) username = "Theobald Tiger";

Obwohl sie vielleicht eigenartig wirken, sind die Klammern rund um den Ausdruck ein zwingend erforderlicher Bestandteil der Syntax der `if`-Anweisung.

Wie wir im vorangegangenen Abschnitt bereits festgestellt haben, können wir eine einzelne Anweisung durch einen Anweisungsblock ersetzen. Demnach könnte die `if`-Anweisung auch folgendermaßen aussehen:

    if ((adresse == null) || (adresse == "")) {
      adresse = "undefined";
      alert("Bitte geben Sie eine Postadresse an.");
    }

Die Einrückung in diesen Beispielen ist nicht vorgeschrieben. Zusätzliche Leerzeichen und Tabulatoren werden in JavaScript ignoriert. Da wir hinter jede elementare Anweisung ein Semikolon gesetzt haben, hätten wir diese Beispiele auch alle in eine Zeile schreiben können. Mit Zeilenumbrüchen und Einrückung, wie wir sie hier gezeigt haben, ist der Code jedoch leichter zu lesen und zu verstehen.

Die zweite Form der `if`-Anweisung führt die `else`-Klausel ein, die ausgeführt wird, wenn der _`Ausdruck`_ `false` ist. Die Syntax sieht folgendermaßen aus:

    if ( _Ausdruck_
    )
      _Anweisung1_

    else
      _Anweisung2_

Auch in dieser Form der Anweisung wird zunächst der _`Ausdruck`_ ausgewertet. Ist er `true`, so wird die _`Anweisung1`_ ausgeführt; andernfalls wird die _`Anweisung2`_ ausgeführt. Hierzu ein Beispiel:

    if (username != null)
      alert("Hallo " + username + "\nWillkommen auf meinem Blog.");
    else {
      username = prompt("Willkommen!\nWie heißen Sie?");
      alert("Hallo " + username);
    }

Wenn mehrere `if`-Anweisungen mit `else`-Klauseln geschachtelt sind, ist Vorsicht geboten. Es muss sichergestellt werden, dass die `else`-Klausel mit der passenden `if`-Anweisung korrespondiert. Betrachten Sie die folgenden Zeilen:

    i = j = 1;
    k = 2;
    if (i == j)
      if (j == k)
        document.write("i ist gleich k");
    else
      document.write("i ungleich j");  // Falsch!

In diesem Beispiel bildet die innere `if`-Anweisung die einzelne Anweisung, die die Syntax der äußeren `if`-Anweisung zulässt. Leider ist (abgesehen von dem Hinweis, den die Einrückung gibt) nicht klar, zu welchem `if` das `else` gehört. Und in diesem Beispiel ist der durch die Einrückung gegebene Hinweis falsch, da ein JavaScript-Interpreter das obige Beispiel folgendermaßen interpretiert:

    if (i == j) {
      if (j == k)
        document.write("i ist gleich k");
      else
      document.write("i ungleich j");  // Hoppla!
    }

In JavaScript (wie in den meisten anderen Programmiersprachen) lautet die Regel, dass standardmäßig eine `else`-Klausel immer Bestandteil der nächstgelegenen `if`-Anweisung ist. Damit dieses Beispiel eindeutiger und leichter zu lesen, zu verstehen, zu pflegen und von Fehlern zu bereinigen ist, sollten Sie geschweifte Klammern verwenden:

    if (i == j) {
      if (j == k) {
        document.write("i ist gleich k");
      }
    }
    else { // Der Standort der geschweiften Klammern macht viel aus!
      document.write("i ungleich j");
    }

Wir gehen in diesem Buch zwar nicht so vor, aber viele Programmierer setzen die Bodys von `if`\- und `else`-Anweisungen (sowie aller anderen zusammengesetzten Anweisungen wie zum Beispiel der `while`-Schleifen) immer in geschweifte Klammern. Wenn Sie dieses Verfahren konsequent anwenden, können Sie Probleme wie das gerade gezeigte vermeiden.

# 6.4 else if

Wir haben bereits gesehen, dass die `if/else`-Anweisung nützlich ist, wenn man eine Bedingung prüft und je nach dem Ergebnis der Prüfung eines von zwei Codesegmenten ausführt. Was ist aber, wenn wir eines von vielen Codesegmenten ausführen müssen? Eine Möglichkeit ist die Verwendung einer `else if`-Anweisung. `else if` ist eigentlich keine JavaScript-Anweisung, sondern einfach ein häufig verwendetes Programmieridiom, das dadurch zustande kommt, dass man mehrere `if/else`-Anweisungen verwendet:

    if (n == 1) {
      // Führe Codeblock 1 aus
    }
    else if (n == 2) {
      // Führe Codeblock 2 aus
    }
    else if (n == 3) {
      // Führe Codeblock 3 aus
    }
    else {
      // Wenn alle Elses scheitern, führe Codeblock 4 aus
    }

An diesem Code ist nichts Besonderes. Er ist einfach eine Folge von `if`-Anweisungen, wobei jedes `if` ein Teil der `else`-Klausel der vorhergehenden Anweisung ist. Mit dem Idiom `else if` ist der Code besser lesbar, als wenn wir die Anweisungen in der syntaktisch äquivalenten, vollständig geschachtelten Form ausschreiben:

    if (n == 1) {
      // Führe Codeblock 1 aus
    }
    else {
      if (n == 2) {
        // Führe Codeblock 2 aus
      }
      else {
        if (n == 3) {
          // Führe Codeblock 3 aus
        }
        else {
          // Wenn alle Elses scheitern, führe Codeblock 4 aus
        }
      }
    }

# 6.5 switch

Eine `if`-Anweisung führt zu einer Gabelung im Ablauf der Programmausführung. Wenn Sie wie im obigen Abschnitt mehrere `if`-Anweisungen verwenden, können Sie sogar eine Mehrfachverzweigung des Ablaufs bewirken. Allerdings ist dies nicht immer die beste Lösung, vor allem dann nicht, wenn alle Verzweigungen vom Wert einer einzigen Variablen abhängen. In diesem Fall verschwenden Sie nur Ressourcen, wenn Sie den Wert einer einzigen Variablen in mehreren `if`-Anweisungen prüfen.

Die `switch`-Anweisung ist für eben diese Situation zuständig, wobei sie zudem effizienter als wiederholte `if`-Anweisungen ist. Die JavaScript-Anweisung `switch` ähnelt den `switch`-Anweisungen von Java oder C sehr. Ähnlich wie bei der `if`-Anweisung folgen auch hinter dem Schlüsselwort `switch` ein Ausdruck und ein Codeblock.

    switch( _Ausdruck_ ) {
      _Anweisungen_
    }

Die vollständige Syntax der `switch`-Anweisung ist jedoch komplexer. Verschiedene Stellen im Codeblock werden mit dem Schlüsselwort `case` markiert, hinter dem ein Wert und ein Doppelpunkt folgen. Wenn eine `switch`-Anweisung ausgeführt wird, berechnet sie den Wert des _`Ausdrucks`_ und sucht dann nach einer `case`-Markierung, die mit diesem Wert übereinstimmt. Wenn sie eine findet, beginnt sie mit der Ausführung des Codeblocks bei der ersten Anweisung, die auf die `case`-Markierung folgt. Wenn sie keine `case`-Markierung mit einem passenden Wert findet, beginnt sie die Ausführung bei der ersten Anweisung, die auf die Sonderfallmarkierung `default:` folgt. Falls es die Markierung `default:` nicht gibt, überspringt sie den Codeblock ganz.

`switch` ist nicht einfach zu erklären; wie diese Anweisung funktioniert, lässt sich am besten an einem Beispiel vorführen. Die folgende `switch`-Anweisung ist gleichbedeutend mit den wiederholten `if/else`-Anweisungen, die wir im vorangegangenen Abschnitt gezeigt haben.

    switch(n) {
     case 1:           // Starte hier, wenn n == 1
      // Führe Codeblock 1 aus.
      break;           // Stoppe hier
     case 2:           // Starte hier, wenn n == 2
      // Führe Codeblock 2 aus.
      break;           // Stoppe hier
     case 3:           // Starte hier, wenn n == 3
      // Führe Codeblock 3 aus.
      break;           // Stoppe hier
     default:           // Wenn alles andere scheitert ...
      // Führe Codeblock 4 aus.
      break;           // Stoppe hier
    }

Achten Sie auf das Schlüsselwort `break` am Ende jedes `case`-Blocks im obigen Code. Die `break`-Anweisung, die wir etwas weiter unten in diesem Kapitel beschreiben werden, veranlasst die Ablaufsteuerung, an das Ende einer `switch`-Anweisung oder Schleife zu springen. Die `case`-Klauseln in einer `switch`-Anweisung geben nur den _Anfangspunkt_ des gewünschten Codesegments an, nicht aber seinen Endpunkt. Gibt es keine `break`-Anweisungen, so beginnt eine `switch`-Anweisung die Ausführung ihres Codeblocks bei der `case`-Markierung, die mit dem Wert ihres _`Ausdrucks`_ übereinstimmt, und führt so lange alle weiteren Anweisungen aus, bis sie das Ende des Blocks erreicht. In seltenen Fällen ist es nützlich, Code wie diesen zu schreiben, der von einer `case`-Markierung zur nächsten durchfällt. Aber in fast allen Fällen sollten Sie genau darauf achten, dass jede `case`-Klausel in einer `switch`-Anweisung mit einer `break`-Anweisung endet. (Wenn Sie `switch` innerhalb einer Funktion verwenden, können Sie anstelle einer `break`-Anweisung auch eine `return`-Anweisung benutzen. Beide beenden die `switch`-Anweisung und verhindern, dass die Ausführung mit dem nächsten `case` weitermacht.)

Der folgende Code zeigt ein realistischeres Beispiel für die `switch`-Anweisung: Er wandelt einen Wert in einen String um, wobei die Art der Umwandlung vom Typ des Wertes abhängt:

    function konvertierung(x) {
      switch(typeof x) {
       case 'number':          // Konvertiere die Zahl in einen
                               // Hexadezimal-Integer
        return x.toString(16);
       case 'string':          // Gib den in Anführungszeichen
                               // stehenden String zurück
        return '"' + x + '"';
       case 'boolean':         // Konvertiere in TRUE oder FALSE
                               // (großgeschrieben)
        return x.toString().toUpperCase( );
       default:                // Konvertiere andere Typen wie
                               // üblich
        return x.toString( )
      }
    }

Beachten Sie, dass in den beiden vorangehenden Beispielen hinter dem Schlüsselwort `case` immer Zahlen- und String-Literale folgen. Dies ist zwar die Art, wie die `switch`-Anweisung am häufigsten verwendet wird, aber laut dem Standard ECMAScript darf hinter jedem `case` ein beliebiger Ausdruck folgen.[24] Hierzu einige Beispiele:

    case 60*60*24:
    case Math.PI:
    case n+1:
    case a[0]:

Die `switch`-Anweisung wertet zuerst den Ausdruck aus, der nach dem Schlüsselwort `switch` folgt. Dann wertet sie die `case`-Ausdrücke so lange in der Reihenfolge aus, in der sie erscheinen, bis sie einen passenden Wert findet.[25] Da der passende Wert nicht mit dem Gleichheitsoperator `==`, sondern mit dem Identitätsoperator `===` ermittelt wird, müssen die Ausdrücke ohne Typumwandlung passen.

Beachten Sie, dass es kein guter Programmierstil ist, `case`-Ausdrücke zu verwenden, die Seiteneffekte wie zum Beispiel Funktionsaufrufe oder Zuweisungen enthalten, da nicht bei jeder Ausführung der `switch`-Anweisung alle `case`-Ausdrücke ausgewertet werden. Wenn Seiteneffekte nur gelegentlich auftreten, kann es schwierig sein, das richtige Verhalten des Programms zu verstehen und vorherzusehen. Am sichersten ist es, wenn Sie die `case`-Ausdrücke auf Ausdrücke mit Konstanten beschränken.

Wie wir bereits erläutert haben, beginnt die `switch`-Anweisung mit der Ausführung ihres Bodys bei der Anweisung mit der Markierung `default:`, sofern sie keine `case`-Ausdrücke findet, die mit dem `switch`-Ausdruck übereinstimmen. Wenn es die Markierung `default:` nicht gibt, überspringt die `switch`-Anweisung den gesamten Body. Achten Sie auch darauf, dass die Markierung `default:` in den früheren Beispielen am Ende des Bodys von `switch` hinter allen `case`-Markierungen erscheint. Es ist sowohl logisch als auch üblich, `default:` an dieser Stelle einzufügen, obwohl `default:` auch an jeder anderen Stelle im Body der Anweisung plaziert werden kann.

* * *

[24] Dadurch unterscheidet sich die `switch`-Anweisung in JavaScript wesentlich von der in C, C++ oder Java. In diesen Programmiersprachen müssen `case`-Ausdrücke statische Konstanten sein, als ganze Zahlen oder andere ganzzahlige Typen ausgewertet werden und denselben Typ haben.

[25] Dies bedeutet, dass die `switch`-Anweisung in JavaScript weit weniger effizient ist als die `switch`-Anweisung in C, C++ und Java. Da die `case`-Ausdrücke in diesen Programmiersprachen statische Konstanten sind, brauchen sie im Gegensatz zu den `case`-Ausdrücken in JavaScript nie zur Laufzeit ausgewertet zu werden. Und da die `case`-Ausdrücke in C, C++ und Java ganzzahlige Werte sind, kann die `switch`-Anweisung häufig mit einer höchst effizienten Sprungtabelle implementiert werden.

# 6.6 while

Während die `if`-Anweisung die grundlegende Steueranweisung ist, mit deren Hilfe JavaScript Entscheidungen fällt, ist die `while`-Anweisung die grundlegende Anweisung, mit der JavaScript wiederholte Aktionen ausführen kann. Sie hat die folgende Syntax:

    while ( _Ausdruck_ )
    _Anweisung_

Die `while`-Anweisung wertet zuerst den _`Ausdruck`_ aus. Wenn dieser `false` ist, geht JavaScript zur nächsten Anweisung im Programm weiter. Ist er hingegen `true`, so wird die angegebene _`Anweisung`_ ausgeführt, die den Body der Schleife bildet, und der _`Ausdruck`_ wird erneut ausgewertet. Wenn der Wert des _`Ausdrucks`_ `false` ist, macht JavaScript mit der nächsten Anweisung im Programm weiter; andernfalls führt es die _`Anweisung`_ erneut aus. Dieser Kreislauf wird so lange fortgesetzt, bis der _`Ausdruck`_ als `false` ausgewertet wird. Dann wird die Ausführung der `while`-Anweisung beendet und JavaScript setzt den Programmablauf fort. Beachten Sie, dass Sie mit der Syntax `while(true)` Endlosschleifen schreiben können.

Normalerweise möchte man nicht, dass JavaScript exakt dieselbe Operation immer wieder ausführt. In fast allen Schleifen ändern sich eine oder mehrere Variablen bei jeder Iteration der Schleife. Da sich die Variablen verändern, ist es durchaus möglich, dass die Ausführung der _`Anweisung`_ nicht bei jedem Schleifendurchlauf dieselbe Aktion bewirkt. Wenn die sich verändernde Variable (oder die Variablen) im _`Ausdruck`_ auftritt (bzw. auftreten), kann der Ausdruck zudem bei jedem Schleifendurchlauf einen anderen Wert haben. Dies ist wichtig, da ein Ausdruck, der als `true` beginnt, sonst nie `false` werden könnte und die Schleife niemals enden würde! Der folgende Code zeigt ein Beispiel für eine `while` -Schleife:

    var zaehler = 0;
    while (zaehler < 10) {
      document.write(zaehler + "<br>");
      zaehler++;
    }

Wie Sie sehen, beginnt die Variable `zaehler` in diesem Beispiel mit dem Wert 0 und wird jedes Mal um 1 erhöht, wenn der Body der Schleife ausgeführt wird. Nachdem die Schleife zehnmal ausgeführt wurde, wird der Ausdruck `false` (d.h. die Variable `zaehler` ist nicht mehr kleiner als 10), die `while`-Anweisung endet, und JavaScript kann mit der nächsten Anweisung im Programm weitermachen. Die meisten Schleifen haben eine Zählervariable wie `zaehler`. Normalerweise verwendet man die Variablennamen `i`, `j` und `k` als Schleifenzähler; aber wenn Ihr Code dadurch verständlicher wird, sollten Sie aussagekräftigere Namen benutzen.

# 6.7 do/while

Die `do/while`-Schleife hat große Ähnlichkeit mit der `while`-Schleife. Allerdings wird der Schleifenausdruck nicht am Anfang, sondern am Ende der Schleife geprüft. Dies bedeutet, dass der Body der Schleife immer mindestens einmal ausgeführt wird. Die Syntax sieht folgendermaßen aus:

    do
      Anweisung
    while ( _Ausdruck_ );

Die `do/while`-Schleife wird nicht so häufig verwendet wie die `while`-Schleife. Dies liegt daran, dass es in der Praxis kaum einen Fall gibt, in dem Sie immer sicher sind, dass Sie eine Schleife mindestens einmal ausführen müssen. Hier sehen Sie ein Beispiel für eine `do/while`-Schleife:

    function array_ausgeben(a) {
      if (a.length == 0)
        document.write("Leeres Array");
      else {
        var i = 0;
        do {
         document.write(a[i] + "<br>");
        } while (++i < a.length);
      }
    }

Zwischen der `do/while`-Schleife und der normalen `while`-Schleife bestehen einige syntaktische Unterschiede. Erstens verlangt die `do`-Schleife sowohl das Schlüsselwort `do` (um den Anfang der Schleife zu kennzeichnen) als auch das Schlüsselwort `while` (um das Ende der Schleife zu kennzeichnen und die Schleifenbedingung einzuführen). Außerdem wird die `do`\- im Gegensatz zur `while`-Schleife mit einem Semikolon beendet. Der Grund hierfür ist, dass die `do`-Schleife nicht mit einer einfachen geschweiften Klammer endet, die das Ende des Schleifen-Bodys kennzeichnet, sondern mit der Schleifenbedingung.

# 6.8 for

Die `for`-Anweisung ist ein Schleifenkonstrukt, das häufig praktischer ist als die `while`-Anweisung. Die `for`-Anweisung nutzt ein Muster, das den meisten Schleifen (einschließlich der `while`-Schleife, die wir weiter oben gesehen haben) gemeinsam ist. Die meisten Schleifen haben eine Zählervariable. Diese Variable wird initialisiert, bevor die Schleife beginnt, und sie wird als Teil des _`Ausdrucks`_ geprüft, der vor jeder Iteration der Schleife ausgewertet wird. Schließlich wird die Zählervariable ganz am Ende des Schleifen-Bodys, direkt vor der erneuten Auswertung des _`Ausdrucks`_ , inkrementiert oder auf andere Art aktualisiert.

Die Initialisierung, der Test und die Aktualisierung sind die drei entscheidenden Bearbeitungsschritte einer Schleifenvariablen. Die `for`-Anweisung macht diese drei Schritte zu ausdrücklichen Teilen der Schleifensyntax. Dadurch ist besonders leicht zu verstehen, was die `for`-Schleife macht. Außerdem kann man dadurch leicht Fehler vermeiden – wie zum Beispiel den, dass man die Schleifenvariable zu initialisieren oder zu inkrementieren vergisst. Die `for`-Anweisung hat die folgende Syntax:

    for( _Initialisierung_ ; _Test_ ; _Inkrementierung_ )
    _Anweisung_

Am einfachsten können wir erklären, was die `for`-Schleife macht, indem wir die entsprechende `while`-Schleife zeigen:[26]

     _Initialisierung_ ;
    while( _Test_ ) {
      _Anweisung_
      _Inkrementierung_ ;
    }

Anders ausgedrückt: Der Ausdruck _`Initialisierung`_ wird einmal ausgewertet, bevor die Schleife beginnt. Damit dieser Ausdruck etwas nützt, muss er Seiteneffekte haben (normalerweise eine Zuweisung). JavaScript lässt auch zu, dass _`Initialisierung`_ eine `var`-Anweisung für die Variablendeklaration ist, sodass Sie einen Schleifenzähler zugleich deklarieren und initialisieren können. Der Ausdruck _`Test`_ wird vor jeder Iteration ausgewertet und steuert, ob der Body der Schleife ausgeführt wird. Wenn der Ausdruck _`Test`_ `true` ist, wird die _`Anweisung`_ ausgeführt, die den Schleifen-Body verkörpert. Schließlich wird der Ausdruck _`Inkrementierung`_ ausgewertet. Auch dieser muss ein Ausdruck mit Seiteneffekten sein, damit er sinnvoll ist. Normalerweise ist er entweder ein Zuweisungsausdruck oder verwendet einen der Operatoren `++` oder `−−`.

Das Beispiel für die `while`-Schleife im vorigen Abschnitt lässt sich folgendermaßen als eine `for`-Schleife schreiben, die von 0 bis 9 zählt:

    for(var zaehler = 0 ; zaehler < 10 ; zaehler++)
      document.write(zaehler + "<br>");

Beachten Sie, dass diese Syntax alle wichtigen Informationen über die Schleifenvariable auf eine einzige Zeile setzt und damit verdeutlicht, wie die Schleife ausgeführt wird. Achten Sie auch darauf, dass der Ausdruck _`Inkrementierung`_ in die `for`-Anweisung gesetzt und der Body der Schleife damit zu einer einzigen Anweisung vereinfacht wurde: Wir brauchen noch nicht einmal geschweifte Klammern zu setzen, um einen Anweisungsblock zu erstellen.

Schleifen können natürlich erheblich komplizierter als diese einfachen Beispiele sein. Manchmal gibt es dabei mehr als eine Variable, die sich bei jedem Schleifendurchlauf ändert. Dies ist die einzige Stelle, an der der Kommaoperator in JavaScript relativ häufig verwendet wird: Er ermöglicht es, mehrere Initialisierungs- und Inkrementierungsausdrücke zu einem einzigen Ausdruck zusammenzufügen, der dann in einer `for` Schleife eingesetzt werden kann. Hierzu ein Beispiel:

    for(i = 0, j = 10 ; i < 10 ; i++, j--)
      summe += i * j;

* * *

[26] Bei der `continue`-Anweisung werden wir sehen, dass diese `while`-Schleife nicht genau mit der `for`-Schleife identisch ist.

# 6.9 for/in

Das Schlüsselwort `for` wird in JavaScript auf zwei unterschiedliche Arten verwendet.Wie es in der `for`-Schleife verwendet wird, haben wir gerade gesehen. Es wird aber auch in der `for/in`-Anweisung benutzt. Diese Anweisung ist eine etwas andere Art von Schleife, die die folgende Syntax hat:

    for ( _Variable_ in _Objekt_ )
      _Anweisung_

Die _`Variable`_ sollte der Name einer Variablen, eine `var`-Anweisung, die eine Variable deklariert, ein Element eines Arrays oder eine Eigenschaft eines Objekts sein (d.h., sie sollte etwas sein, das auf der linken Seite eines Zuweisungsausdrucks stehen kann). _`Objekt`_ ist der Name eines Objekts oder ein Ausdruck, der als Objekt ausgewertet wird. Die _`Anweisung`_ ist wie üblich die Anweisung oder der Anweisungsblock, die oder der den Body der Schleife bildet.

Die Elemente eines Arrays können Sie durchlaufen, indem Sie bei jedem Durchlauf einer `while`\- oder `for`-Schleife eine Indexvariable erhöhen. Die `for/in`-Anweisung bietet eine Möglichkeit, die Eigenschaften eines Objekts zu durchlaufen. Der Body der `for/in`-Schleife wird für jede Eigenschaft von _`Objekt`_ einmal ausgeführt. Bevor der Body der Schleife ausgeführt wird, wird der _`Variable`_ der Name einer der Objekteigenschaften als String zugewiesen. Innerhalb des Schleifen-Bodys können Sie mit Hilfe dieser Variablen den Wert der Objekteigenschaft mit dem Operator `[]` nachschlagen. Die folgende `for/in`-Schleife gibt zum Beispiel den Namen und den Wert der Eigenschaften eines Objekts aus:

    for (var prop in mein_Objekt) {
      document.write("Name: " + prop + "; Wert: " + mein_Objekt[prop], "<br>");
    }

Beachten Sie, dass die _`Variable`_ in der `for/in`-Schleife ein beliebiger Ausdruck sein kann, solange er als etwas ausgewertet wird, das auf der linken Seite einer Zuweisung stehen kann. Dieser Ausdruck wird bei jedem Schleifendurchlauf ausgewertet, was bedeutet, dass er jedes Mal etwas anderes ergeben kann. Mit Code wie dem folgenden können Sie zum Beispiel die Namen aller Objekteigenschaften in ein Array kopieren:

    var o = {x:1, y:2, z:3};
    var a = new Array( );
    var i = 0;
    for(a[i++] in o) /* leerer Schleifen-Body */;

JavaScript-Arrays sind einfach eine spezialisierte Objektart. Die `for/in`-Schleife zählt daher sowohl Array-Indizes als auch Objekteigenschaften auf. Wenn wir an den obigen Codeblock zum Beispiel die folgende Zeile anhängen, dann zählen wir die Array-Eigenschaften 0, 1 und 2:

    for(i in a) alert(i);

Die `for/in`-Schleife legt nicht fest, in welcher Reihenfolge die Eigenschaften eines Objekts der Variablen zugewiesen werden. Die Reihenfolge lässt sich nicht voraussagen und kann bei unterschiedlichen Implementierungen oder Versionen von JavaScript verschieden sein. Wenn der Body einer `for/in`-Schleife eine Eigenschaft löscht, die noch nicht aufgezählt wurde, dann wird diese Eigenschaft auch nicht aufgezählt. Wenn der Body der Schleife neue Eigenschaften definiert, hängt es von der jeweiligen Implementierung ab, ob diese Eigenschaften aufgezählt werden oder nicht.

Die `for/in`-Schleife durchläuft nicht unbedingt alle möglichen Eigenschaften aller Objekte. So wie einige Objekteigenschaften als schreibgeschützt oder permanent (nicht löschbar) gekennzeichnet sind, sind einige Eigenschaften als nicht aufzählbar markiert. Diese Eigenschaften werden von der `for/in`-Schleife nicht aufgezählt. Während alle benutzerdefinierten Eigenschaften aufgezählt werden, ist dies bei vielen eingebauten Eigenschaften einschließlich aller eingebauten Methoden nicht der Fall. Wie Sie in Kapitel 7 sehen werden, können Objekte Eigenschaften anderer Objekte erben. Geerbte Eigenschaften, die benutzerdefiniert sind, werden von der `for/in`-Schleife ebenfalls aufgezählt.

# 6.10 Markierungen

Die Markierungen `case` und `default:`, die mit der `switch`-Anweisung verwendet werden, sind ein Sonderfall einer allgemeineren Markierungsanweisung. Man kann jede Anweisung markieren, indem man ihr einen Bezeichnernamen und einen Doppelpunkt voranstellt:

     _Bezeichner_ : _Anweisung_

Der _`Bezeichner`_ kann irgendein zulässiger JavaScript-Bezeichner sein, der kein reserviertes Wort ist. Da sich Markierungsnamen von Variablen- und Funktionsnamen unterscheiden, brauchen Sie keine Namenskollisionen zu befürchten, falls Sie einer Markierung denselben Namen geben wie einer Variable oder Funktion. Der folgende Code zeigt ein Beispiel für eine markierte `while`-Anweisung:

    parser:
     while(token != null) {
       // Hier wird Code weggelassen
    }

Indem Sie eine Anweisung markieren, geben Sie ihr einen Namen, mit dem Sie von anderen Stellen in Ihrem Programm aus auf sie verweisen können. Sie können zwar jede Anweisung markieren, üblich ist dies jedoch nur bei den Schleifenanweisungen `while`, `do/while`, `for` und `for/in`. Wenn Sie einer Schleife einen Namen geben, können Sie mit `break` die ganze Schleife und mit `continue` eine einzelne Schleifeniteration verlassen (siehe die nächsten beiden Abschnitte).

# 6.11 break

Die `break`-Anweisung sorgt dafür, dass die innerste umgebende Schleife oder eine `switch`-Anweisung sofort verlassen wird. Sie hat eine einfache Syntax:

    break;

Da diese Form der `break`-Anweisung bewirkt, dass eine Schleife oder `switch`-Anweisung abgebrochen wird, ist sie nur innerhalb einer dieser Anweisungen zulässig.

In JavaScript darf hinter dem Schlüsselwort `break` der Name einer Markierung folgen:

    break _Markierungsname_ ;

Denken Sie daran, dass der _`Markierungsname`_ einfach ein Bezeichner ist. Im Gegensatz zur Definition einer markierten Anweisung folgt hier hinter dem Markierungsnamen kein Doppelpunkt.

Wenn `break` mit einer Markierung verbunden wird, springt die Ablaufsteuerung an das Ende der benannten Anweisung, die eine beliebige umgebende Anweisung sein kann, d.h., sie bricht diese Anweisung ab. Die genannte Anweisung braucht keine Schleife oder `switch`-Anweisung zu sein. Eine `break`-Anweisung, die mit einer Markierung verwendet wird, braucht noch nicht einmal in einer Schleife oder einem `switch` enthalten zu sein. Die einzige Einschränkung, der die Markierung einer `break`-Anweisung unterworfen ist, besteht darin, dass sie eine umgebende Anweisung benennen muss. Die Markierung kann zum Beispiel eine `if`-Anweisung oder sogar einen Anweisungsblock angeben, der nur deswegen in geschweifte Klammern gesetzt wurde, um ihn mit einer Markierung zu versehen.

Wie wir in Kapitel 2 bereits besprochen haben, ist zwischen dem Schlüsselwort `break` und dem _`Markierungsnamen`_ kein Zeilenumbruch erlaubt. Dies ist eine Merkwürdigkeit der JavaScript-Syntax, die daher rührt, dass JavaScript weggelassene Semikola automatisch einfügt. Wenn Sie eine Codezeile zwischen dem Schlüsselwort `break` und der folgenden Markierung umbrechen, geht JavaScript davon aus, dass Sie die einfache, unbenannte Form der Anweisung verwenden wollten, und fügt ein Semikolon ein.

Für die `break`-Anweisung innerhalb einer `switch`-Anweisung haben wir bereits ein Beispiel gesehen. In Schleifen wird diese Anweisung normalerweise dann verwendet, wenn es aus irgendeinem Grund nicht mehr notwendig ist, die Schleife vollständig zu durchlaufen. Wenn eine Schleife komplexe Beendigungsbedingungen hat, ist es häufig einfacher, einige dieser Bedingungen mit `break`-Anweisungen zu implementieren, als zu versuchen, alle in einem einzigen Schleifenausdruck zu formulieren.

Der folgende Code durchsucht die Elemente eines Arrays nach einem bestimmten Wert. Die Schleife endet auf natürliche Art, wenn sie das Ende des Arrays erreicht. Sie endet hingegen mit einer `break`-Anweisung, wenn sie den gesuchten Wert im Array findet.

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

Die Form der `break`-Anweisung mit Angabe einer Markierung benötigen Sie nur, wenn Sie geschachtelte Schleifen oder `switch`-Anweisungen verwenden und eine Anweisung abbrechen müssen, die nicht die innerste Anweisung ist.

Das folgende Beispiel zeigt beschriftete `for`-Schleifen und beschriftete `break`-Anweisungen. Versuchen Sie herauszufinden, welche Ausgabe dieser Code liefert:

    aeussere_schleife:
     for(var i = 0; i < 10; i++) {
      innere_schleife:
       for(var j = 0; j < 10; j++) {
         if (j > 3) break;                  // Verlasse innerste Schleife
         if (i == 2) break innere_schleife;    // Dasselbe
         if (i == 4) break aeussere_schleife;  // Verlasse äußere Schleife
         document.write("i = " + i + " j = " + j + "<br>");
       }
     }
     document.write("FINAL i = " + i + " j = " + j + "<br>");

# 6.12 continue

Die `continue`-Anweisung gleicht der `break`-Anweisung. Allerdings bricht `continue` eine Schleife nicht ab, sondern beginnt sie erneut in einer neuen Iteration. Die Syntax der `continue`-Anweisung ist genauso einfach wie die der `break`-Anweisung:

    continue;

Die `continue`-Anweisung kann auch mit einer Markierung verwendet werden:

    continue _Markierungsname_ ;

Die `continue`-Anweisung kann sowohl mit als auch ohne Markierungsnamen nur innerhalb des Bodys einer `while`-, `do/while`-, `for`\- oder `for/in`-Schleife verwendet werden. An allen anderen Stellen löst sie einen Syntaxfehler aus.

Wenn die `continue`-Anweisung ausgeführt wird, wird der aktuelle Durchlauf der umgebenden Schleife beendet und die nächste Iteration beginnt. Bei unterschiedlichen Schleifentypen hat dies unterschiedliche Auswirkungen:

  * In einer `while`-Schleife wird der angegebene _`Ausdruck`_ am Anfang der Schleife erneut geprüft, und wenn er `true` ist, wird der Schleifen-Body von Anfang an ausgeführt.

  * In einer `do/while`-Schleife springt die Ablaufsteuerung an das Ende der Schleife, wo die Schleifenbedingung erneut geprüft wird, bevor die Schleife wieder am Anfang begonnen wird.

  * In einer `for`-Schleife wird der Ausdruck _`Inkrementierung`_ ausgewertet und der Ausdruck _`Test`_ erneut geprüft, um zu ermitteln, ob eine weitere Iteration vorgenommen werden soll.

  * In einer `for/in`-Schleife fährt die Schleife mit dem nächsten Eigenschaftsnamen fort, der der angegebenen Variablen zugewiesen wird.

Achten Sie darauf, dass sich die `continue`-Anweisung in einer `while`-Schleife anders verhält als in einer `for`-Schleife: Eine `while`-Schleife kehrt direkt zu ihrer Bedingung zurück, eine `for`-Schleife wertet hingegen zuerst den Ausdruck _`Inkrementierung`_ aus und kehrt erst danach zu ihrer Bedingung zurück. Bei der Besprechung der `for`-Schleife habe ich das Verhalten dieser Schleife anhand einer gleichbedeutenden `while`-Schleife erklärt. Da sich die `continue`-Anweisung jedoch bei beiden Schleifenarten unterschiedlich verhält, kann man eine `for`-Schleife nicht vollständig mit einer `while`-Schleife simulieren.

Das folgende Beispiel zeigt eine `continue`-Anweisung ohne Markierungsnamen, deren Zweck darin besteht, die aktuelle Iteration einer Schleife abzubrechen, wenn ein Fehler auftritt:

    for(i = 0; i < daten.length; i++) {
      if (daten[i] == null)
        continue; // Kann mit undefinierten Daten nicht arbeiten
      total += daten[i];
    }

Ebenso wie die `break`-Anweisung kann man auch die `continue`-Anweisung in ihrer Form mit Markierung verwenden, wenn die Schleife, die erneut begonnen werden soll, keine direkt umgebende Schleife ist. Außerdem sind ebenso wie bei der `break`-Anweisung auch zwischen der `continue`-Anweisung und ihrem _`Markierungsnamen`_ keine Zeilenumbrüche erlaubt.

# 6.13 var

Die `var`-Anweisung ermöglicht es Ihnen, eine oder mehrere Variablen ausdrücklich zu deklarieren. Die Syntax dieser Anweisung sieht folgendermaßen aus:

    var _Name_1_ [ = _Wert_1_ ] [,..., _Name_n_ [= _Wert_n_ ]]

Dem Schlüsselwort `var` folgt eine Liste von Variablen, die durch Kommata voneinander getrennt sind. Jede Variable in der Liste kann optional einen Initialisierungsausdruck haben, der ihren Anfangswert angibt. Hierzu einige Beispiele:

    var i;
    var j = 0;
    var p, q;
    var gruss = "Hallo " + name;
    var x = 2.34, y = Math.cos(0.75), r, theta;

Die `var`-Anweisung definiert die einzelnen benannten Variablen, indem sie im Aufrufobjekt der umschließenden Funktion oder im globalen Objekt, falls die Deklaration nicht innerhalb eines Funktionsbodys steht, eine Eigenschaft mit diesem Namen anlegt. Die von der `var`-Anweisung erstellten Eigenschaften kann man nicht mit dem Operator `delete` löschen. Beachten Sie, dass sich das Verhalten einer `var`-Anweisung nicht ändert, wenn man sie in eine `with`-Anweisung (siehe "6.18 with", »with«) einschließt.

Wenn die `var`-Anweisung für eine Variable keinen Anfangswert angibt, dann ist diese Variable zwar definiert, aber ihr Anfangswert ist `undefined`.

Die `var`-Anweisung kann auch Teil einer `for`\- oder `for/in`-Schleife sein, wie die folgenden Beispiele zeigen:

    for(var i = 0; i < 10; i++) document.write(i, "<br>");
    for(var i = 0, j=10; i < 10; i++,j--) document.write(i*j, "<br>");
    for(var i in o) document.write(i, "<br>");

Kapitel 4 enthält noch viele weitere Informationen über Variablen und Variablendeklarationen in JavaScript.

# 6.14 function

Die Anweisung `function` definiert eine JavaScript-Funktion. Sie hat die folgende Syntax:

    function _Funkname_ ([ _Arg1_ [, _Arg2_ [..., _Argn_ ]]]) {
      _Anweisungen_

    }

 _`Funkname`_ ist der Name der zu definierenden Funktion. Dies muss ein Bezeichner sein, kein String oder Ausdruck. Dem Funktionsnamen folgt eine in runde Klammern gesetzte Liste von Argumentnamen, die durch Kommata voneinander getrennt sind. Mit diesen Bezeichnern kann man innerhalb des Bodys der Funktion auf die Argumentwerte verweisen, die beim Aufruf der Funktion übergeben werden.

Der Body der Funktion besteht aus beliebig vielen JavaScript-Anweisungen, die in geschweifte Klammern gesetzt sind. Diese Anweisungen werden nicht ausgeführt, wenn die Funktion definiert wird. Stattdessen werden sie kompiliert und mit dem neuen Funktionsobjekt verbunden, um dann ausgeführt zu werden, wenn die Funktion mit dem Funktionsaufrufoperator `( )` aufgerufen wird. Beachten Sie, dass die geschweiften Klammern ein zwingend erforderlicher Bestandteil der `function`-Anweisung sind. Im Gegensatz zu dem Anweisungsblock, der mit `while`-Schleifen und anderen Anweisungen verwendet wird, verlangt ein Funktionsbody selbst dann geschweifte Klammern, wenn er nur aus einer einzigen Anweisung besteht.

Eine Funktionsdefinition erzeugt ein neues Funktionsobjekt und speichert es in einer neu angelegten Eigenschaft namens _`Funkname`_. Der folgende Code zeigt einige Beispiele für Funktionsdefinitionen:

    function willkommen( ) { alert("Willkommen auf meinem Blog!"); }

    function ausgabe(msg) {
      document.write(msg, "<br>");
    }

    function hypothenuse(x, y) {
      return Math.sqrt(x*x + y*y); // return wird im nächsten Abschnitt erklärt
    }

    function fakultaet(n) {        // eine rekursive Funktion
      if (n <= 1) return 1;
      return n * fakultaet(n – 1);
    }

Funktionsdefinitionen kommen normalerweise in JavaScript-Code der obersten Ebene vor. Sie können zwar auch in andere Funktionsdefinitionen geschachtelt sein, allerdings nur auf der obersten Ebene dieser Funktionen. Sie können also nicht in `if`-Anweisungen, `while`-Schleifen oder irgendwelchen anderen Anweisungen vorkommen.

Rein technisch betrachtet ist die `function`-Anweisung keine Anweisung. Anweisungen lösen in einem JavaScript-Programm dynamisches Verhalten aus, während Funktionsdefinitionen die statische Struktur eines Programms beschreiben. Anweisungen werden zur Laufzeit ausgeführt, Funktionen werden hingegen dann definiert, wenn JavaScript-Code geparst (kompiliert) wird, also bevor er ausgeführt wird. Wenn der JavaScript-Parser auf eine Funktionsdefinition trifft, analysiert und speichert er die Anweisungen (ohne sie auszuführen), die den Body dieser Funktion bilden. Anschließend definiert er eine Eigenschaft (im Aufrufobjekt, falls die Funktionsdefinition in eine andere Funktion geschachtelt ist; ansonsten im globalen Objekt), die denselben Namen hat wie die Funktion und die die Funktion speichert.

Die Tatsache, dass Funktionen nicht zur Laufzeit, sondern zur Kompilierzeit definiert werden, hat einige überraschende Auswirkungen. Betrachten Sie zum Beispiel den folgenden Code:

    alert(f(4));    // Zeigt 16 an. f( ) kann aufgerufen werden,
                    // bevor es definiert ist.
    var f = 0;      // Diese Anweisung überschreibt die Eigenschaft f.
    function f(x) { // Diese "Anweisung" definiert die Funktion f, bevor
      return x*x;   // eine der obigen Zeilen ausgeführt wird.
    }
    alert(f);       // Zeigt 0 an. f( ) wurde von der Variablen f
                    // überschrieben.

Zu diesem ungewöhnlichen Ergebnis kommt es dadurch, dass Funktionen zu einer anderen Zeit als Variablen definiert werden. Zum Glück sind derartige Situationen selten.

Über Funktionen werden Sie in Kapitel 8 mehr erfahren.

# 6.15 return

Wie Sie bereits wissen, ist ein Funktionsaufruf mit dem Operator `( )` ein Ausdruck. Jeder Ausdruck hat einen Wert, und mit der `return`-Anweisung gibt man den Wert an, den eine Funktion zurückgibt. Dieser Wert ist der Wert des Ausdrucks, der die Funktion aufgerufen hat. Die Syntax der `return`-Anweisung sieht folgendermaßen aus:

    return _Ausdruck_ ;

Eine `return`-Anweisung kann nur innerhalb des Bodys einer Funktion auftreten. Es stellt einen Syntaxfehler dar, wenn sie an irgendeiner anderen Stelle erscheint. Wenn die `return`-Anweisung ausgeführt wird, wird der _`Ausdruck`_ ausgewertet und als Wert der Funktion zurückgegeben. Die Ausführung der Funktion endet, sobald die `return`-Anweisung ausgeführt ist, und dies selbst dann, wenn es im Funktionsbody noch weitere Anweisungen gibt. Mit der `return`-Anweisung kann man einen Wert folgendermaßen zurückgeben:

    function quadrat(x) { return x*x; }

Man kann die `return`-Anweisung auch ohne einen _`Ausdruck`_ verwenden, um einfach die Ausführung der Funktion zu beenden, ohne einen Wert zurückzugeben. Hierzu ein Beispiel:

    function zeige_Objekt(obj) {
      // Prüfe zuerst, ob das Argument gültig ist
      // Ist es ungültig, überspringe den Rest der Funktion
      if (obj == null) return;
      // Hier steht die eigentliche Funktion
    }

Wenn eine Funktion eine `return`-Anweisung ohne _`Ausdruck`_ ausführt oder wenn sie zurückkehrt, weil sie das Ende des Funktionsbodys erreicht hat, ist der Wert des Ausdrucks für den Funktionsaufruf `undefined`.

Da JavaScript Semikola automatisch einfügt, dürfen Sie zwischen das Schlüsselwort `return` und den danach folgenden Ausdruck keinen Zeilenumbruch setzen.

# 6.16 throw

Eine _Exception_ (Ausnahme) ist ein Signal, das darauf hinweist, dass irgendeine unvorhergesehene Bedingung oder ein Fehler aufgetreten ist. Wenn man eine Exception auslöst, signalisiert man damit einen derartigen Fehler oder eine solche Exception-Bedingung. Eine Exception abzufangen bedeutet, dass man sie behandelt, also die erforderlichen oder angemessenen Aktionen durchführt, um nach der Exception geordnet fortfahren zu können. In JavaScript werden Exceptions immer dann ausgelöst, wenn ein Laufzeitfehler auftritt oder das Programm mit einer `throw`-Anweisung ausdrücklich einen Fehler auslöst. Exceptions werden mit der `try/catch/finally`-Anweisung abgefangen, die wir im nächsten Abschnitt beschreiben werden.[27]

Die `throw`-Anweisung hat die folgende Syntax:

    throw _Ausdruck_ ;

Der _`Ausdruck`_ kann als ein beliebiger Typ ausgewertet werden. Im Allgemeinen ist er jedoch ein Error-Objekt oder eine Instanz einer der Unterklassen von Error. Es kann auch nützlich sein, einen String, der eine Fehlermeldung enthält, oder einen numerischen Wert auszulösen, der einen Fehlercode darstellt. Der folgende Beispielcode löst mit der `throw`-Anweisung eine Exception aus:

    function fakultaet(x) {
      // Ist das Eingabeargument ungültig, löse eine Exception aus!
      if (x < 0) throw new Error("x darf nicht negativ sein");
      // Berechne ansonsten den Wert und kehre normal zurück
      for(var f = 1; x > 1; f *= x, x--) /* leer */ ;
      return f;
    }

Wenn eine Exception ausgelöst wird, hält der JavaScript-Interpreter die normale Programmausführung sofort an und springt zum nächstgelegenen Exception-Handler. Exception-Handler schreibt man mit der `catch`-Klausel der `try/catch/finally`-Anweisung, die wir im folgenden Abschnitt beschreiben werden. Wenn mit dem Codeblock, in dem die Exception ausgelöst wurde, keine `catch`-Klausel verbunden ist, prüft der Interpreter, ob der nächsthöhere umgebende Codeblock mit einem Exception-Handler verbunden ist. Dies wird so lange fortgesetzt, bis ein Handler gefunden wird. Wenn eine Exception in einer Funktion ausgelöst wird, die keine `try/catch/finally`-Anweisung zur Behandlung dieser Exception enthält, dann wird die Exception an den Code weitergegeben, der die Funktion aufgerufen hat. Auf diese Art werden Exceptions durch die lexikalische Struktur von JavaScript-Methoden den Aufruf-Stack hinauf weitergegeben. Wird kein Exception-Handler gefunden, wird die Exception als ein Fehler behandelt und dem Anwender gemeldet.

Die `throw`-Anweisung wurde von ECMAScript v3 standardisiert und ist in JavaScript 1.4 implementiert. Die Klasse Error und ihre Unterklassen sind ebenfalls Bestandteil von ECMAScript v3, wurden aber erst in JavaScript 1.5 implementiert.

* * *

[27] Die JavaScript-Anweisungen `throw` und `try/catch/finally` haben zwar Ähnlichkeit mit den entsprechenden Anweisungen in C++ und Java, sind aber nicht mit ihnen identisch.

# 6.17 try/catch/finally

Die `try/catch/finally`-Anweisung ist der JavaScript-Mechanismus für das Exception-Handling. Die `try`-Klausel dieser Anweisung definiert den Codeblock, dessen Exceptions behandelt werden sollen. Dem `try`-Block folgt eine `catch`-Klausel, also der Block mit den Anweisungen, die aufgerufen werden, wenn irgendwo im `try`-Block eine Exception auftritt. Der `catch`-Klausel folgt ein `finally`-Block, der den Bereinigungscode enthält, der unabhängig davon, was im `try`-Block geschieht, auf jeden Fall ausgeführt wird. Sowohl der `catch`\- als auch der `finally`-Block sind optional, aber mit einem `try`-Block muss mindestens einer dieser Blöcke verbunden sein. Jeder `try`-, `catch`\- und `finally`-Block beginnt und endet mit geschweiften Klammern. Diese Klammern sind ein zwingend erforderlicher Bestandteil der Syntax und dürfen selbst dann nicht weggelassen werden, wenn eine Klausel nur eine einzige Anweisung enthält. Ebenso wie die `throw`-Anweisung wurde auch die `try/catch/finally`-Anweisung von ECMAScript v3 standardisiert und in JavaScript 1.4 implementiert.

Der folgende Code veranschaulicht die Syntax und den Zweck der `try/catch/finally`-Anweisung. Achten Sie insbesondere darauf, dass dem Schlüsselwort `catch` ein Bezeichner in runden Klammern folgt. Dieser Bezeichner gleicht einem Funktionsargument. Er benennt eine lokale Variable, die nur innerhalb des Bodys eines `catch`-Blocks existiert. Dieser Variable weist JavaScript das Exception-Objekt oder den Exception-Wert zu, das oder der ausgelöst wurde.

    try {
     // Normalerweise wird dieser Codeblock von oben bis unten problemlos
     // ausgeführt. Manchmal löst er aber eine Exception aus, entweder
     // direkt mit einer throw-Anweisung oder indirekt durch Aufruf einer
     // Methode, die eine Exception auslöst.
    }
    catch (e) {
     // Die Anweisungen in diesem Block werden genau dann ausgeführt, wenn
     // der try-Block eine Exception auslöst. Die Anweisungen können mit der
     // lokalen Variablen e das Error-Objekt oder den Wert referenzieren,
     // das oder der ausgelöst wurde. Dieser Block kann die Exception
     // irgendwie behandeln oder sie durch Nichtstun ignorieren oder sie
     // mit throw erneut auslösen.
    }
    finally {
     // Dieser Block enthält Anweisungen, die immer ausgeführt werden,
     // egal was im try-Block geschieht. Sie werden ausgeführt, wenn der
     // try-Block
     // 1. normal am Ende des Blocks endet
     // 2. wegen einer break-, continue- oder return-Anweisung endet
     // 3. mit einer Exception endet, die durch die obige catch-Klausel
     //  behandelt wird
     // 4. mit einer nicht-abgefangenen Exception endet, die immer
     //  noch weitergereicht wird
    }

Das folgende Beispiel für die `try/catch`-Anweisung ist realistisch. Es verwendet die im vorangegangenen Abschnitt definierte Methode `fakultaet( )` und die clientseitigen JavaScript-Methoden `prompt()` und `alert( )` für die Ein- und Ausgabe:

    try {
      // Bitte den Benutzer um Eingabe einer Zahl
      var n = prompt("Bitte eine positive ganze Zahl eingeben", "");
      // Berechne die Fakultät dieser Zahl, vorausgesetzt, dass die
      // Benutzereingabe gültig ist
      var f = faktultaet(n);
      // Zeige das Ergebnis an
      alert(n + "! = " + f);
    }
    catch (ex) { // Wenn die Benutzereingabe ungültig war, ist hier Schluss
      // Teile dem Benutzer die Art des Fehlers mit
      alert(ex);
    }

Dieses Beispiel zeigt eine `try/catch`-Anweisung ohne `finally`-Klausel. `finally` wird zwar nicht so häufig verwendet wie `catch`, ist aber oft nützlich. Allerdings bedarf das Verhalten dieser Klausel einer zusätzlichen Erläuterung. Die `finally`-Klausel wird auf jeden Fall ausgeführt, wenn irgendein Teil des `try`-Blocks ausgeführt wird, und zwar unabhängig davon, wie der Code im `try`-Block beendet wird. Normalerweise verwendet man diese Klausel dafür, hinter dem Code in der `try`-Klausel aufzuräumen.

Im Normalfall erreicht der Ablauf das Ende des `try`-Blocks und macht dann mit dem `finally`-Block weiter, der alle nötigen Bereinigungsarbeiten durchführt. Wenn der Ablauf den `try`-Block aufgrund einer `return`-, `continue`\- oder `break`-Anweisung verlässt, wird der `finally`-Block ausgeführt, bevor der Ablauf ab dem neuen Bestimmungsort weitergeht.

Wenn eine Exception im `try`-Block auftritt und es einen zugehörigen `catch`-Block gibt, der die Exception behandeln kann, geht der Ablauf zuerst zum `catch`-Block und dann zum `finally`-Block über. Gibt es hingegen keinen lokalen `catch`-Block, der die Exception behandeln kann, dann geht der Ablauf zuerst zum `finally`-Block über und dann zur nächstgelegenen umgebenden `catch`-Klausel, die die Exception behandeln kann.

Wenn ein `finally`-Block den Ablauf selbst mit einer `return`-, `continue`-, `break`\- oder `throw`-Anweisung oder durch das Aufrufen einer Methode, die eine Exception auslöst, weiterleitet, wird statt der eigentlich anstehenden diese neue Änderung des Ablaufs verarbeitet. Wenn zum Beispiel eine `finally`-Klausel eine Exception auslöst, dann ersetzt diese Exception alle anderen, die gerade ausgelöst werden sollten. Wenn eine `finally`-Klausel eine `return`-Anweisung absetzt, kehrt die Methode selbst dann normal zurück, wenn eine Exception ausgelöst, aber noch nicht behandelt wurde.

Man kann `try` und `finally` zusammen auch ohne eine `catch`-Klausel verwenden. In diesem Fall ist der `finally`-Block einfach Bereinigungscode, der unabhängig von allen `break`-, `continue`\- oder `return`-Anweisungen, die möglicherweise in der `try`-Klausel enthalten sind, auf jeden Fall ausgeführt wird. So stellt zum Beispiel der folgende Code mit einer `try/finally`-Anweisung sicher, dass eine Schleifenzähler-Variable am Ende jeder Iteration inkrementiert wird, selbst wenn eine Iteration aufgrund einer `continue`-Anweisung abrupt endet:

    var i = 0, gesamt = 0;
    while(i < a.length) {
      try {
        if ((typeof a[i] != "number") || isNaN(a[i])) // Ist es NaN,
          continue;     // so gehe zur nächsten Schleifeniteration.
        gesamt += a[i]; // Addiere andernfalls die Zahl zum Gesamtwert.
      }
      finally {
        i++; // Inkrementiere i immer, auch dann,
             // wenn wir oben continue benutzt haben.
      }
    }

# 6.18 with

In Kapitel 4 haben wir den Geltungsbereich von Variablen sowie die Geltungsbereichskette beschrieben. Diese ist eine Liste von Objekten, die der Reihe nach durchsucht werden, damit die Variablennamen aufgelöst werden können. Die `with`-Anweisung dient dazu, die Geltungsbereichskette vorübergehend zu verändern. Sie hat die folgende Syntax:

    with ( _Objekt_ )
      _Anweisung_

Diese Anweisung fügt das _`Objekt`_ praktisch am Anfang der Geltungsbereichskette ein, führt die _`Anweisung`_ aus und stellt dann den ursprünglichen Zustand der Geltungsbereichskette wieder her.

In der Praxis können Sie sich mit der `with`-Anweisung eine Menge Tipparbeit ersparen. In clientseitigem JavaScript ist es zum Beispiel durchaus üblich, mit tief geschachtelten Objekthierarchien zu arbeiten. So kann es sein, dass Sie für den Zugriff auf die Elemente eines HTML-Formulars Ausdrücke wie den folgenden schreiben müssen:

    frames[1].document.forms[0].adressfeld.value

Wenn Sie mehrmals auf dieses Formular zugreifen müssen, können Sie es mit Hilfe der `with`-Anweisung zur Geltungsbereichskette hinzufügen:

    with(frames[1].document.forms[0]) {
      // Greife hier direkt auf Formularelemente zu. Beispiel:
      name.value = "";
      adressfeld.value = "";
      email.value = "";
    }

Damit verringern Sie Ihre Tipparbeit erheblich: Sie brauchen nicht mehr vor jedem Namen einer Formulareigenschaft `frames[1].document.forms[0]` einzugeben. Dieses Objekt ist vorübergehend Teil der Geltungsbereichskette und wird automatisch durchsucht, wenn JavaScript einen Bezeichner wie zum Beispiel `adressfeld` auflösen muss.

Die `with`-Anweisung erleichtert zwar manchmal die Arbeit, wird aber dennoch nicht gerne gesehen. JavaScript-Code, der `with` verwendet, ist schwer zu optimieren und läuft daher eventuell langsamer als gleichbedeutender Code ohne `with`-Anweisung. Darüber hinaus können Funktionsdefinitionen und Variableninitialisierungen innerhalb des Bodys einer `with`-Anweisung ein überraschendes und kontraproduktives Verhalten haben.[28] Aus diesen Gründen raten wir von der `with`-Anweisung ab.

Denken Sie daran, dass es auch andere, völlig legitime Arten gibt, wie Sie sich einiges an Tipparbeit ersparen können. Den obigen Beispielcode können Sie zum Beispiel auch folgendermaßen schreiben:

    var formular = frames[1].document.forms[0];
    form.name.value = "";
    form.adressfeld.value = "";
    form.email.value = "";

* * *

[28] Dieses Verhalten und seine Ursachen sind zu kompliziert, um sie hier zu erklären.

# 6.19 Die leere Anweisung

Eine letzte zulässige Anweisung in JavaScript ist die leere Anweisung. Sie sieht folgendermaßen aus:

    ;

Die Ausführung der leeren Anweisung hat offensichtlich keinerlei Auswirkungen und führt keinerlei Aktion durch. Nun könnte man zwar meinen, es könne kaum einen Grund geben, eine derartige Anweisung zu verwenden. Wenn Sie aber eine Schleife erzeugen möchten, die einen leeren Body hat, kann die leere Anweisung gelegentlich durchaus nützlich sein. Hierzu ein Beispiel:

    // Initialisiere das Array a
    for(i=0; i < a.length; a[i++] = 0) ;

Denken Sie daran, dass das versehentliche Einfügen eines Semikolons nach der rechten Klammer einer `for`-Schleife, `while`-Schleife oder `if`-Anweisung frustrierende Programmfehler zur Folge haben kann, die schwer zu finden sind. So macht zum Beispiel der folgende Code wahrscheinlich nicht das, was sein Autor beabsichtigt:

    if ((a == 0) || (b == 0));  // Huch! Diese Zeile macht gar nichts ...
       o = null;                // und diese wird immer ausgeführt.

Wenn Sie die leere Anweisung absichtlich verwenden, sollten Sie sie in Ihrem Code kommentieren, damit ersichtlich ist, dass Sie sie wirklich absichtlich eingefügt haben:

    for(i=0; i < a.length; a[i++] = 0) /* Leer */ ;

# 6.20 Zusammenfassung der JavaScript-Anweisungen

In diesem Kapitel sind alle Anweisungen der Programmiersprache JavaScript eingeführt worden. Tabelle 6-1 fasst diese Anweisungen, ihre Syntax und ihren Zweck zusammen.

Tabelle 6-1. Syntax der JavaScript-Anweisungen

Anweisung | Syntax | Zweck

---|---|---

`break`

%|

    break;
    break _Markierung_ ; | Die innerste Schleife oder `switch`-Anweisung oder durch die _`Markierung`_ benannte Anweisung verlassen.

`case`

%|

    case _Ausdruck_ : | Eine Anweisung innerhalb einer `switch`-Anweisung markieren.

`continue`

%|

    continue;
    continue _Markierung_ ; | Die innerste Schleife oder die von _`Markierung`_ benannte Schleife erneut beginnen.

`default`

%|

`default:` | Die Standardanweisung innerhalb einer `switch`-Anweisung markieren.

`do/while`

%|

    do
      _Anweisung_
    while ( _Ausdruck_ ); | Eine Alternative zur `while`-Schleife.

`leer`

%|

`;` | Nichts tun.

`for`

%|

    for ( _Initialisierung_ ; _Test_ ;
    _Inkrementierung_ )
      _Anweisung_ | Leicht zu verwendende Schleife.

`for/in`

%|

    for ( _Variable_ in _Objekt_ )
    _Anweisung_ | Die Eigenschaften eines Objekts durchlaufen.

`function`

%|

    function _Funktionsname_ ([ _Arg1_
    [..., _Argn_ ]])
    {
      _Anweisungen_
    } | Eine Funktion deklarieren.

`if/else`

%|

    if ( _Ausdruck_
    )
      _Anweisung1_
    [else _Anweisung2_ ] | Code in Abhängigkeit von den Bedingungen ausführen.

`label`

%|

     _Bezeichner_ : _Anweisung_ | Der _`Anweisung`_ den Namen _`Bezeichner`_ geben.

`return`

%|

    return [ _Ausdruck_ ]; | Von einer Funktion zurückkehren oder den Wert eines _`Ausdrucks`_ aus einer Funktion zurückgeben.

`switch`

%|

    switch ( _Ausdruck_ ) {
      _Anweisungen_
    } | Mehrfachverzweigung zu Anweisungen, die mit `case` oder `default:` markiert sind.

`throw`

%|

throw _Ausdruck_ ; | Eine Exception auslösen.

`try`

%|

    try {
      _Anweisungen_
    }
    catch ( _Bezeichner_ ) {
      _Anweisungen_
    }
    finally {
      _Anweisungen_
    } | Eine Exception abfangen.

`var`

%|

    var _Name_1_ [ = _Wert_1_ ]
    [ ..., _Name_n_ [ = _Wert_n_ ]]; | Variablen deklarieren und initialisieren.

`while`

%|

    while ( _Ausdruck_ )
      _Anweisung_ | Ein einfaches Schleifenkonstrukt.

`with`

%|

    with ( _Objekt_ )
      _Anweisung_ | Die Geltungsbereichskette erweitern. (Veraltet.)

# Kapitel 7. Objekte und Arrays

In Kapitel 3 wurde erläutert, dass Objekte und Arrays in JavaScript zwei grundlegende Datentypen sind. Es sind auch zwei der wichtigsten. Objekte und Arrays unterscheiden sich von elementaren Datentypen wie Strings und Zahlen: Sie repräsentieren keinen einzelnen Wert, sondern eine Sammlung von Werten. Ein Objekt ist eine Ansammlung von benannten Werten, und ein Array ist eine besondere Art von Objekt, das sich wie eine geordnete Ansammlung nummerierter Werte verhält. Dieses Kapitel bietet eine ausführliche Beschreibung von JavaScript-Objekten und -Arrays.

# 7.1 Objekte erzeugen

Objekte sind zusammengesetzte Datentypen: Sie setzen mehrere Werte zu einer einzigen Einheit zusammen und erlauben es uns, diese Werte mit einem Namen zu speichern und abzurufen. Man könnte auch sagen, dass ein Objekt eine ungeordnete Sammlung von _Eigenschaften_ ist, von denen jede einen Namen und einen Wert hat. Die benannten Werte, die von einem Objekt gespeichert werden, können elementare Werte wie Zahlen und Strings sein, oder sie können selbst Objekte sein.

Der einfachste Weg, ein Objekt zu erstellen, besteht darin, in Ihren JavaScript-Code ein Objekt-Literal einzuschließen. Ein _Objekt-Literal_ ist eine kommaseparierte Liste von Name/Wert-Paaren, die in geschweifte Klammern eingeschlossen ist. Jeder Eigenschaftsname kann ein JavaScript-Bezeichner oder String sein, und jeder Eigenschaftswert kann eine Konstante oder ein beliebiger JavaScript-Ausdruck sein. Hier sind einige Beispiele:

    var leer = {}; // Ein Objekt ohne Eigenschaften
    var punkt = { x:0, y:0 };
    var kreis = { x:punkt.x, y:punkt.y+1, radius:2 };
    var homer = {
      "name": "Homer Simpson",
      "alter": 34,
      "verheiratet": true,
      "beruf": "Industriearbeiter",
      'email': "homer@beispiel.com"
    };

Ein Objekt-Literal ist ein Ausdruck, der bei jeder Auswertung ein neues und eigenständiges Objekt erzeugt und initialisiert. Das heißt, ein einziges Objekt-Literal kann viele neue Objekte erzeugen, wenn es im Rumpf einer Schleife in einer Funktion erscheint, die wiederholt aufgerufen wird.

Der `new`-Operator kann besondere Arten von Objekten erzeugen. Sie lassen auf ihn einen Aufruf einer Konstruktorfunktion folgen, die die Eigenschaften des Objekts initialisiert. Zum Beispiel:

    var a = new Array( ); // Ein leeres Array erzeugen
    var d = new Date( ); // Ein Objekt erzeugen, dass das aktuelle Datum repräsentiert
    var r = new RegExp("javascript", "i"); // Ein Objekt für Mustervergleiche erzeugen

Die oben gezeigten `Array( )`-, `Date( )`\- und `RegExp( )`-Konstruktoren sind ein eingebauter Teil des JavaScript-Sprachkerns. (Der `Array( )`-Konstruktor wird weiter unten in diesem Kapitel beschrieben. Die anderen können Sie in Teil III nachschlagen.) Der `Object( )`-Konstruktor erzeugt ein leeres Objekt, genau wie es das Literal `{}` tut.

Sie können auch Ihre eigenen Konstruktorfunktionen definieren, um neu erzeugte Objekte auf eine Art zu initialisieren, die Sie bestimmen möchten. Wie Sie das tun, lernen Sie in Kapitel 9.

# 7.2 Objekteigenschaften

Normalerweise verwenden Sie den Punktoperator `.`, um auf den Wert einer Objekteigenschaft zuzugreifen. Der Wert auf der linken Seite des `.` sollte das Objekt sein, auf dessen Eigenschaft Sie zugreifen möchten. In der Regel ist das einfach der Name der Variablen, die die Objektreferenz enthält, es kann aber auch ein beliebiger JavaScript-Ausdruck sein, der zu einem Objekt ausgewertet wird. Der Wert auf der rechten Seite des Operators sollte der Name der Eigenschaft sein. Hierbei muss es sich um einen Bezeichner handeln, nicht um einen String oder einen Ausdruck. Sie würden zum Beispiel auf die Eigenschaft `e` im Objekt `o` mit `o.e` verweisen oder auf die Eigenschaft `radius` im Objekt `kreis` mit `kreis.radius`. Objekteigenschaften funktionieren wie Variablen, Sie können Werte in ihnen speichern und Werte aus ihnen lesen:

    // Erzeuge ein Objekt, und speichere eine Referenz darauf in einer Variablen.
    var buch = {};

    // Setze in dem Objekt eine Eigenschaft.
    buch.titel = "JavaScript: Das umfassende Referenzwerk";

    // Setze weitere Eigenschaften. Achten Sie auf die geschachtelten
    // Objekte.
    buch.kapitel1 = new Object( );
    buch.kapitel1.titel = "Einführung in JavaScript";
    buch.kapitel1.seiten = 11;
    buch.kapitel2 = { titel: "Lexikalische Struktur", seiten: 6 };

    // Lies einige Eigenschaftswerte aus dem Objekt.
    alert("Beschreibung: " + buch.titel + "\n\t" +
       "[Kapitel 1](ch01.html "Kapitel 1. Einführung in JavaScript") " + buch.kapitel1.titel + "\n\t" +
       "[Kapitel 2](ch02.html "Kapitel 2. Lexikalische Struktur") " + buch.kapitel2.titel);

Ein wichtiger Punkt, den Sie bei diesem Beispiel beachten sollten, ist, dass Sie eine neue Eigenschaft eines Objekts einfach erzeugen können, indem Sie ihr einen Wert zuweisen. Wir deklarieren zwar Variablen mit dem Schlüsselwort `var`, aber es gibt keine Notwendigkeit (und keine Möglichkeit), das auch mit Objekteigenschaften zu tun. Außerdem können Sie, wenn Sie eine Objekteigenschaft erst einmal durch eine Wertzuweisung erzeugt haben, den Wert dieser Eigenschaft jederzeit durch eine neue Wertzuweisung ändern:

    buch.titel = "JavaScript: Das Buch mit dem Nashorn";

## 7.2.1 Eigenschaften aufzählen

Die `for/in`-Schleife, die in Kapitel 6 beschrieben wurde, bietet eine Möglichkeit, eine Schleife über die Eigenschaften eines Objekts laufen zu lassen bzw. sie aufzuzählen. Das kann beim Debuggen von Skripten nützlich sein oder wenn Sie mit Objekten arbeiten, die beliebige Eigenschaften haben können, deren Namen Sie im Voraus nicht kennen. Der folgende Code zeigt eine Funktion, mit der Sie die Eigenschaftsnamen eines Objekts auflisten können:

    function zeigeEigenschaftsnamen(obj) {
      var namen = "";
      for(var name in obj) namen += name + "\n";
      alert(namen);
    }

Beachten Sie, dass die `for/in`-Schleife die Eigenschaften nicht in einer bestimmten Reihenfolge auflistet und dass zwar alle benutzerdefinierten Eigenschaften aufgezählt werden, nicht aber bestimmte vordefinierte Eigenschaften oder Methoden.

## 7.2.2 Die Existenz von Eigenschaften prüfen

Der `in`-Operator (siehe Kapitel 5) kann eingesetzt werden, um die Existenz einer Eigenschaft zu prüfen. Die linke Seite dieses Operators sollte der Name einer Eigenschaft als String sein. Die rechte Seite sollte das Objekt sein, das geprüft werden soll. Zum Beispiel:

    // Wenn o eine Eigenschaft names "x" hat, wird sie gesetzt.
    if ("x" in o) o.x = 1;

Der `in`-Operator wird allerdings nicht oft benötigt, weil der Wert `undefined` zurückgeliefert wird, wenn Sie eine Eigenschaft abfragen, die nicht existiert. Der vorangehende Code könnte also auch so geschrieben werden:

    // Wenn die Eigenschaft x existiert und nicht undefined ist, wird sie gesetzt.
    if (o.x !== undefined) o.x = 1;

Beachten Sie, dass eine Eigenschaft existieren und trotzdem `undefined` sein kann – beispielsweise, wenn Sie Folgendes schreiben:

    o.x = undefined

Die Eigenschaft `x` existiert dann zwar, hat aber keinen Wert. Ist das der Fall, setzt das erste Beispiel in diesem Abschnitt `x` auf `1`, während das zweite Beispiel nichts tut.

Beachten Sie auch, dass oben der `!==`-Operator verwendet wurde und nicht der üblichere `!=`-Operator. `!==` und `===` unterscheiden zwischen `undefined` und `null`. Manchmal möchten Sie diese Unterscheidung aber nicht machen:

    // Wenn die Eigenschaft machWas existiert und nicht null oder undefined ist,
    // wird davon ausgegangen, dass sie eine Funktion ist, die dann aufgerufen wird!
    if (o.machWas) o.machWas( );

## 7.2.3 Eigenschaften löschen

Sie können den `delete`-Operator einsetzen, um eine Eigenschaft eines Objekts zu löschen:

    delete buch.kapitel2;

Beachten Sie, dass die Eigenschaft, wenn sie gelöscht wird, nicht einfach auf `undefined` gesetzt wird. Die Eigenschaft wird tatsächlich aus dem Objekt gelöscht. Nachdem sie gelöscht wurde, wird die Eigenschaft von `for/in` nicht mehr aufgezählt und vom `in`-Operator nicht mehr aufgespürt.

# 7.3 Objekte als assoziative Arrays

Sie haben bereits die Verwendung des Punktoperators zum Zugriff auf die Eigenschaften eines Objekts kennengelernt. Sie können auch den Operator `[]` zum Zugriff auf Eigenschaften benutzen, selbst wenn dieser Operator üblicherweise für Arrays genutzt wird. Die beiden folgenden JavaScript-Ausdrücke haben somit den gleichen Wert:

    objekt.eigenschaft
    objekt["eigenschaft"]

Der wichtige Unterschied zwischen diesen beiden Schreibweisen besteht darin, dass der Eigenschaftsname im ersten Fall ein Bezeichner ist, im zweiten hingegen ein String. Wir werden weiter unten sehen, warum dies so bedeutsam ist.

In C, C++, Java und anderen stark typisierten Sprachen kann ein Objekt nur eine festgelegte Anzahl von Eigenschaften (bzw. »Feldern«, wie sie häufig genannt werden) haben, und die Namen dieser Eigenschaften müssen vorher festgelegt werden. Da JavaScript eine schwach typisierte Sprache ist, trifft diese Regel hier nicht zu. Ein Programm kann jede beliebige Anzahl von Eigenschaften für jedes beliebige Objekt erzeugen. Wenn Sie jedoch den Operator. zum Zugriff auf eine Objekteigenschaft benutzen, wird der Name der Eigenschaft als Bezeichner ausgedrückt. Bezeichner sind ihrerseits kein JavaScript-Datentyp. Sie müssen wörtlich in das JavaScript-Programm hineingeschrieben werden und können durch das Programm nicht mehr verändert werden.

Greifen Sie jedoch andererseits mit der Array-Schreibweise `[]` auf eine Objekteigenschaft zu, wird der Name der Eigenschaft als String ausgedrückt. Strings sind ein JavaScript-Datentyp und können während des Programmablaufs erzeugt und verändert werden. Sie können also zum Beispiel die folgenden Programmzeilen in JavaScript schreiben:

    var adresse = "";
    for(i = 0; i < 4; i++) {
      adresse += kunde["adresse" + i] + '\n';
    }

Dieses Programmfragment liest die Eigenschaften `adresse0`, `adresse1`, `adresse2` und `adresse3` des Objekts `kunde` und verkettet sie.

Das obige Programmfragment zeigt die Flexibilität der Array-Schreibweise, wenn Sie mit String-Ausdrücken auf Objekteigenschaften zugreifen möchten. Man kann dieses Beispiel zwar auch in der Punktnotation schreiben; es gibt jedoch Fälle, in denen nur die Array-Schreibweise funktioniert. Nehmen wir beispielsweise an, Sie schreiben ein Programm, das unter Nutzung von Netzwerkressourcen den aktuellen Börsenwert der Anlagen eines Benutzers berechnet. Der Benutzer kann in das Programm den Namen und die Anzahl jedes Wertpapiers in seinem Besitz eingeben. Sie benutzen beispielsweise ein Objekt namens `depot` zur Speicherung dieser Informationen. Das Objekt besäße dann für jedes Wertpapier eine Eigenschaft. Der Name der Eigenschaft wäre der Name des Wertpapiers, und der Wert der Eigenschaft wäre die Anzahl der jeweiligen Papiere einer Art. Wenn also ein Benutzer 50 Aktien der Firma IBM besäße, dann hätte die Eigenschaft `depot.ibm` den Wert `50`.

Ein Teil dieses Programms besteht aus einer Schleife, in der der Benutzer zunächst darum gebeten wird, den Namen eines Wertpapiers anzugeben. Anschließend wird die Anzahl der jeweils gehaltenen Aktien erfragt. Innerhalb der Schleife stehen Programmzeilen wie die folgenden:

    var wertpapier = hole_wp_name_vom_benutzer( );
    var stuecke = hole_anzahl_der_stuecke( );
    depot[wertpapier] = stuecke;

Da der Benutzer die Wertpapiernamen zur Laufzeit eingibt, können Sie die Namen der Eigenschaften unmöglich schon vorher kennen. Weil Sie aber beim Schreiben des Programms die Namen der Eigenschaften nicht kennen, können Sie unmöglich den Operator `.` benutzen, um auf die Eigenschaften des Objekts `portfolio` zuzugreifen. Sie können jedoch den Operator [] benutzen, da er einen Stringwert (der dynamisch ist und sich zur Laufzeit ändern kann) statt eines Bezeichners (der statisch ist und fest im Programm stehen muss) zum Benennen der Eigenschaft verwendet.

Wenn ein Objekt auf diese Weise verwendet wird, wird es oft assoziatives Array genannt. Das ist eine Datenstruktur, die es ermöglicht, beliebige Datenwerte mit beliebigen Strings zu assoziieren. Häufig wird auch der Begriff _abbilden_ verwendet, um diese Situation zu beschreiben: JavaScript-Objekte bilden Strings (Eigenschaftsnamen) auf Werte ab.

Die Punktschreibweise für den Zugriff auf Eigenschaften lässt JavaScript-Objekte wie die statischen Objekte von C++ und Java aussehen, und sie funktionieren so auch wie gewünscht. Zusätzlich bieten sie jedoch die äußerst mächtige Fähigkeit, Werte mit beliebigen Strings zu assoziieren; in dieser Hinsicht sind JavaScript-Objekte den Hashes von Perl viel ähnlicher als den Objekten von C++ oder Java.

In Kapitel 6 wurde die `for/in`-Schleife eingeführt. Wie nützlich diese JavaScript-Anweisung wirklich ist, wird klar, wenn Sie die Verwendung im Zusammenhang mit assoziativen Arrays betrachten. Um zu dem Wertpapierbeispiel zurückzukehren: Nachdem der Benutzer den Bestand eingegeben hat, könnten Sie Programmzeilen wie die folgenden benutzen, um den aktuellen Gesamtwert zu berechnen:

    var wert = 0;
    for (wertpapier in depot) {
      // Hole für jeden Titel im Depot den Kurswert und
      // multipliziere ihn mit der Anzahl der Stücke.
      wert += hole_kurs(wertpapier) * depot[wertpapier];
    }

Dieses Programmfragment könnte nicht ohne die `for/in`-Schleife geschrieben werden, weil die Namen der Wertpapiere vorher nicht bekannt sind. Dies ist die einzige Möglichkeit, diese Eigenschaftsnamen aus dem assoziativen Array (d.h. dem JavaScript-Objekt) namens `depot` herauszubekommen.

# 7.4 Universelle Objekteigenschaften und -methoden

Wie bereits erörtert, erben alle Objekte in JavaScript von der Klasse Object. Während spezialisiertere Kategorien von Objekten (wie die, die mit den `Date( )`\- und `RegExp( )`-Konstruktoren erzeugt werden) eigene Eigenschaften und Methoden definieren, unterstützen alle Objekte, ganz gleich wie sie erzeugt wurden, die Eigenschaften und Methoden, die von Object definiert werden. Wegen ihrer Universalität sind diese Eigenschaften und Methoden von besonderem Interesse.

## 7.4.1 Die constructor-Eigenschaft

Innerhalb von JavaScript hat jedes Objekt die Eigenschaft `constructor`, die auf die Konstruktorfunktion verweist, die das Objekt initialisiert. Wenn ich zum Beispiel ein Objekt `d` mit dem Konstruktor `Date( )` erzeuge, verweist die Eigenschaft `d.constructor` auf `Date`:

    var d = Date;
    d.constructor == Date; // Ergibt true

Da Konstruktormethoden neue Kategorien oder Klassen von Objekten definieren, kann die Eigenschaft `constructor` dabei helfen, den Typ eines Objekts zu ermitteln. Sie könnten zum Beispiel Code wie den folgenden verwenden, um den Wert eines unbekannten Objekts zu bestimmen:

    if ((typeof o == "object") && (o.constructor == Date))
      // Hier etwas mit dem Date-Objekt machen...

Der `instanceof`-Operator prüft den Wert der `constructor`-Eigenschaft. Der vorangehende Code könnte also auch so geschrieben werden:

    if ((typeof o == "object") && (o instanceof Date))
      // Mache jetzt etwas mit dem Date-Objekt...

## 7.4.2 Die toString( )-Methode

Die Methode `toString( )` erwartet keine Parameter und liefert einen String, der auf irgendeine Weise den Typ und/oder den Wert des Objekts darstellt, auf dem die Methode aufgerufen wird. JavaScript ruft diese Methode auf, wenn das Objekt in einen String umgewandelt werden muss. Dies geschieht zum Beispiel dann, wenn Sie den Operator `+` dazu benutzen, einen String und ein Objekt aneinanderzuhängen, oder wenn Sie einer Methode wie `alert( )`, die einen String erwartet, ein Objekt übergeben.

Die Standardmethode `toString( )` ist nicht sehr informativ. Beispielsweise wird die folgende Codezeile einfach zu dem String »[object Object]« ausgewertet:

    var s = { x:1, y:1 }.toString( );

Da diese Standardmethode nicht viele nützliche Informationen anzeigt, definieren viele Klassen ihre eigenen Versionen von `toString( )`. Wenn ein Array zum Beispiel in einen String umgewandelt wird, erhalten Sie eine Liste der Array-Elemente, die jeweils selbst in einen String umgewandelt wurden, und wenn eine Funktion in einen String umgewandelt wird, erhalten Sie den Quellcode für die Funktion.

Kapitel 9 beschreibt, wie man eine benutzerdefinierte `toString( )`-Methode für eigene Objekttypen schreibt.

## 7.4.3 Die toLocaleString( )-Methode

In ECMAScript v3 und JavaScript 1.5 definiert die Klasse Object zusätzlich zu der `toString( )`-Methode eine Methode namens `toLocaleString( )`. Der Sinn dieser Methode ist es, eine lokalisierte String-Darstellung eines Objekts zurückzugeben. Die Standardmethode `toLocaleString( )`, die von Object definiert wurde, führt selbst keine Lokalisierung durch. Sie gibt immer genau das Gleiche wie `toString( )` zurück. Unterklassen können aber ihre eigenen Versionen von `toLocaleString( )` definieren. In ECMAScript v3 definieren die Klassen Array, Date und Number `toLocaleString( )`-Methoden, die lokalisierte Werte zurückgeben.

## 7.4.4 Die valueOf( )-Methode

Die Methode `valueOf( )` ist der Methode `toString( )` sehr ähnlich, wird aber aufgerufen, wenn JavaScript ein Objekt in einen anderen elementaren Typ als einen String umwandeln muss, normalerweise in eine Zahl. JavaScript ruft diese Methode automatisch auf, wenn das Objekt in einem Kontext verwendet wird, in dem ein elementarer Wert erforderlich ist. Die Default-`valueOf( )`-Methode macht nichts Interessantes, aber einige der eingebauten Kategorien von Objekten definieren ihre eigenen `valueOf( )`-Methoden (siehe beispielsweise `Date.valueOf( )`). Kapitel 9 erklärt, wie man eine benutzerdefinierte `valueOf( )`-Methode für selbst definierte Objekttypen schreibt.

## 7.4.5 Die hasOwnProperty( )-Methode

Die Methode `hasOwnProperty( )` gibt `true` zurück, wenn das Objekt lokal eine nicht-geerbte Eigenschaft mit dem Namen definiert, der von dem einzigen String-Argument angegeben wird. Andernfalls gibt sie `false` zurück. Zum Beispiel:

    var o = {};
    o.hasOwnProperty("undef");    // false: Eigenschaft ist nicht definiert
    o.hasOwnProperty("toString"); // false: toString ist eine geerbte Eigenschaft
    Math.hasOwnProperty("cos");   // true: Das Math-Objekt hat die Eigenschaft cos

Eigenschaftsvererbung wird in Kapitel 9 erklärt.

Diese Methode ist in ECMAScript v3 definiert und in JavaScript 1.5 und höher implementiert.

## 7.4.6 Die propertyIsEnumerable( )-Methode

Die Methode `propertyIsEnumerable( )` gibt `true` zurück, wenn das Objekt eine geerbte Eigenschaft mit dem Namen definiert, der von dem einzigen String-Argument der Methode angegeben wird, und wenn diese Eigenschaft von einer `for/in`-Schleife aufgezählt würde. Andernfalls gibt sie `false` zurück. Zum Beispiel:

    var o = { x:1 };
    o.propertyIsEnumerable("x");       // true: Eigenschaft existiert und ist aufzählbar
    o.propertyIsEnumerable("y");       // false: Eigenschaft existiert nicht
    o.propertyIsEnumerable("valueOf"); // false: Eigenschaft ist geerbt

Diese Methode ist in ECMAScript v3 definiert und in JavaScript 1.5 und höher implementiert.

Beachten Sie, dass alle benutzerdefinierten Eigenschaften eines Objekts aufzählbar sind. Nicht aufzählbare Eigenschaften sind üblicherweise geerbte Eigenschaften (eine Erörterung der Eigenschaftsvererbung finden Sie in Kapitel 9). Diese Methode liefert also fast immer das gleiche Ergebnis zurück wie `hasOwnProperty( )`.

## 7.4.7 Die isPrototypeOf( )-Methode

Die Methode `isPrototypeOf( )` liefert `true`, wenn das Objekt, an das die Methode geknüpft ist, das Prototyp-Objekt des Arguments ist. Andernfalls gibt sie `false` zurück. Zum Beispiel:

    var o = {};
    Object.prototype.isPrototypeOf(o);        // true: o.constructor == Object
    Object.isPrototypeOf(o);                  // false
    o.isPrototypeOf(Object.prototype);        // false
    Function.prototype.isPrototypeOf(Object); // true: Object.constructor == Function

Prototyp-Methoden werden in Kapitel 9 beschrieben.

# 7.5 Arrays

Ein _Array_ ist eine geordnete Sammlung von Werten. Jeder Wert wird _Element_ genannt, und jedes Element hat eine numerische Position im Array, die als sein _Index_ bezeichnet wird. Weil JavaScript eine nicht-typisierte Sprache ist, können die Elemente eines Arrays also einen beliebigen Typ haben, und verschiedene Elemente eines Arrays können unterschiedliche Typen haben. Array-Elemente können sogar andere Array-Elemente enthalten. Das ermöglicht es Ihnen, Datenstrukturen zu erzeugen, die Arrays von Arrays sind.

In diesem Buch behandeln wir Objekte und Arrays oft als unterschiedliche Datentypen. Hierbei handelt es sich um eine nützliche und vernünftige Vereinfachung: Sie können Objekte und Arrays bei der JavaScript-Programmierung meist als separate Typen behandeln. Um aber das Verhalten von Objekten und Arrays vollkommen zu verstehen, müssen Sie die Wahrheit wissen: Ein Array ist nichts weiter als ein Objekt mit einem dünnen Layer zusätzlicher Funktionalität. Sie sehen das beim `typeof`-Operator: Wird er auf einen Array-Wert angewendet, gibt er den String »object« zurück.

Am einfachsten erzeugt man ein Array über ein Array-Literal. Das ist einfach eine kommaseparierte Liste von Array-Elementen in eckigen Klammern. Zum Beispiel:

    var empty = [];   // Ein Array ohne Elemente
    var primes = [2, 3, 5, 7, 11];    // Ein Array mit 5 numerischen Elementen
    var misc = [ 1.1, true, "a", ];   // 3 Elemente unterschiedlicher Typen

Die Werte in einem Array-Literal müssen keine Konstanten sein. Es können beliebige Ausdrücke sein:

    var base = 1024;
    var table = [base, base+1, base+2, base+3];

Array-Literale können Objekt-Literale und andere Array-Literale enthalten:

    var b = [[1,{x:1, y:2}], [2, {x:3, y:4}]];

Der erste Wert in einem Array-Literal wird beim Index 0 des neu erzeugten Arrays gespeichert. Der zweite Wert wird beim Index 1 gespeichert und so weiter. Undefinierte Elemente werden erzeugt, indem einfach der Elementwert zwischen den Kommas weggelassen wird:

    var count = [1,,3]; // Ein Array mit 3 Elementen, von denen das mittlere undefined ist.
    var undefs = [,,]; // Ein Array mit 2 Elementen, die beide undefined sind.

Arrays können auch über den `Array( )`-Konstruktor erzeugt werden. Sie können diesen Konstruktor auf drei unterschiedliche Arten aufrufen.

  * Rufen Sie ihn ohne Argumente auf:

        var a = new Array( );

Auf diese Weise wird ein leeres Array ohne Elemente erzeugt, genau wie mit dem Array-Literal `[]`.

  * Geben Sie explizit Werte für die ersten _n_ Elemente eines Arrays an:

        var a = new Array(5, 4, 3, 2, 1, "Test, Test");

In dieser Form nimmt der Konstruktor eine Liste mit Argumenten entgegen. Jedes Argument gibt einen Elementwert an und kann jeden Typ haben. Elemente werden dem Array beginnend mit dem Element 0 zugewiesen. Die Array-Eigenschaft `length` wird auf die Anzahl der Argumente gesetzt, die dem Konstruktor übergeben werden. Die Verwendung eines Array-Literals ist fast immer einfacher als die Verwendung des `Array( )`-Konstruktors.

  * Rufen Sie ihn mit einem einzelnen numerischen Argument auf, das eine Länge angibt:

        var a = new Array(10);

Diese Technik erzeugt ein Array mit der angegebenen Anzahl von Elementen (die alle den Wert `undefined` haben) und setzt die `length`-Eigenschaft des Arrays auf den angegebenen Wert. Diese Form des `Array( )`-Konstruktors kann verwendet werden, um vorab ein Array zu reservieren, wenn Sie im Voraus wissen, wie viele Elemente benötigt werden. In diesem Fall sind Array-Literale üblicherweise nicht hilfreich.

## 7.5.1 Array-Elemente lesen und schreiben

Sie greifen auf die Elemente eines Arrays mit dem Operator `[]` zu. Links von den eckigen Klammern sollte ein Verweis auf ein Array stehen. Innerhalb der Klammern kann ein beliebiger Ausdruck stehen, der eine natürliche Zahl (0 oder größer) als Wert hat. Diese Schreibweise kann sowohl zum Lesen als auch zum Schreiben des Wertes von Array-Elementen benutzt werden. Die folgenden Zeilen sind deswegen gültiges JavaScript:

    wert = a[0];
    a[1] = 3.14;
    i = 2;
    a[i] = 3;
    a[i + 1] = "Hallo";
    a[a[i]] = a[0];

In einigen Programmiersprachen hat das erste Element eines Arrays den Index 1. In JavaScript hingegen steht das erste Array-Element unter dem Index 0, genau wie in C, C++ und Java.

Wie wir bereits gesehen haben, kann der Operator `[]` auch verwendet werden, um auf benannte Objekteigenschaften zuzugreifen:

    mein['gehalt'] *= 2;

Weil Arrays eine besondere Art von Objekt sind, können Sie auf einem Array nicht-numerische Objekt-Eigenschaften definieren und auf diese Eigenschaften über die `.`\- oder die `[]`-Syntax zugreifen.

Beachten Sie, dass Array-Indizes aus Integern bestehen müssen, die größer oder gleich 0 und kleiner als 232-1 sind. Wenn Sie eine Zahl verwenden, die zu groß ist, eine negative Zahl oder eine Gleitkommazahl (oder einen Booleschen Wert, ein Objekt oder einen anderen Wert), dann wandelt JavaScript ihn in einen String um und verwendet den resultierenden String als Namen einer Objekteigenschaft und nicht als Array-Index. Daher erzeugt die folgende Zeile eine neue Eigenschaft namens »-1.23«. Sie definiert kein neues Array-Element:

    a[-1.23] = true;

## 7.5.2 Neue Elemente zu einem Array hinzufügen

In Sprachen wie C und Java haben Arrays eine feste Anzahl von Elementen, die beim Anlegen des Arrays angegeben werden muss. In JavaScript verhält sich das anders. Arrays können beliebig viele Elemente enthalten, und Sie können ihre Anzahl jederzeit verändern.

Um einem Array ein neues Element hinzuzufügen, weisen Sie ihm einfach einen Wert zu:

    a[10] = 10;

Arrays in JavaScript können »dünn besetzt« sein. (Der englische Fachausdruck lautet _sparse_.) Das bedeutet, dass die einzelnen Array-Indizes nicht unbedingt unmittelbar aufeinander folgen müssen. Speicherplatz wird nur für diejenigen Array-Elemente verbraucht, die tatsächlich belegt sind. Wenn Sie das folgende Programmfragment ausführen, reserviert der JavaScript-Interpreter nur für die Array-Indizes 0 und 10.000 Speicherplatz, nicht aber für die 9.999 Indizes dazwischen.

    a[0] = 1;
    a[10000] = "Dies ist das Element 10.000";

Beachten Sie, dass Array-Elemente auch zu Objekten hinzugefügt werden können:

    var k = new Kreis(1,2,3);
    k[0] = "Dies ist ein Array-Element eines Objekts!"

Dieses Beispiel definiert aber einfach eine neue Objekteigenschaft namens »0«. Das Hinzufügen von Array-Elementen zu einem Objekt macht daraus noch kein Array.

## 7.5.3 Array-Elemente löschen

Der `delete`-Operator setzt ein Array-Element auf den Wert `undefined`, aber das Element selbst bleibt weiter bestehen. Soll ein Element so gelöscht werden, dass alle Elemente über ihm zu niedrigeren Indizes verschoben werden, müssen Sie eine Array-Methode verwenden. `Array.shift( )` löscht das erste Element eines Arrays, `Array.pop( )` löscht das letzte Element, und `Array.splice( )` löscht einen zusammenhängenden Bereich aus einem Array. Diese Funktionen werden weiter unten in diesem Kapitel und in Teil III ausführlicher beschrieben.

## 7.5.4 Array-Länge

Alle Arrays – ob sie mit dem Konstruktor `Array( )` erzeugt oder mit einem Array-Literal definiert worden sind – haben eine besondere Eigenschaft namens `length`, die angibt, wie viele Elemente das Array enthält. Genauer gesagt ist die Eigenschaft `length` immer um eins größer als die größte Elementnummer im Array, da Arrays undefinierte Elemente haben können. Im Gegensatz zu regulären Objekteigenschaften wird die Eigenschaft `length` eines Arrays automatisch aktualisiert, um diese unveränderliche Tatsache zu erhalten, wenn dem Array neue Elemente hinzugefügt werden. Der folgende Code veranschaulicht das:

    var a = new Array( );  // a.length == 0 (keine Elemente definiert)
    a = new Array(10);                     // a.length == 10 (leere Elemente 0-9 definiert)
    a = new Array(1,2,3);                  // a.length == 3 (Elemente 0-2 definiert)
    a = [4, 5];                            // a.length == 2 (Elemente 0 und 1 definiert)
    a[5] = −1;                             // a.length == 6 (Elemente 0, 1 und 5 definiert)
    a[49] = 0;                             // a.length == 50 (Elemente 0, 1, 5 und 49 definiert)

Denken Sie daran, dass Array-Indizes kleiner als 232-1 sein müssen, was bedeutet, dass der größte mögliche Wert der Eigenschaft `length` 232-1 ist.

## 7.5.5 Arrays durchlaufen

Die wahrscheinlich gängigste Verwendung der Eigenschaft `length` eines Arrays ist es, eine Schleife über den Elementen eines Arrays ausführen zu können:

    var fruechte = ["mango", "banane", "kirsche", "birne"];
    for(var i = 0; i < fruechte.length; i++)
      alert(fruechte[i]);

Dieses Beispiel geht natürlich davon aus, dass die Array-Elemente aufeinander folgen und mit dem Element 0 beginnen. Wenn das nicht der Fall ist, sollten Sie für jedes Array-Element testen, ob es definiert ist, ehe Sie es verwenden:

    for(var i = 0; i < fruechte.length; i++)
      if (fruechte[i]) alert(fruechte[i]);

Sie können die gleiche Schleifensyntax einsetzen, um die Elemente eines Arrays zu initialisieren, das mit dem `Array( )`-Konstruktor erzeugt wurde:

    var suchtabelle = new Array(1024);
    for(var i = 0; i < suchtabelle.length; i++)
      lookup_table[i] = i * 512;

## 7.5.6 Arrays verkleinern und vergrößern

Die `length`-Eigenschaft eines Arrays kann gelesen und geschrieben werden. Wenn Sie `length` auf einen Wert setzen, der kleiner als die aktuelle Größe ist, wird das Array auf die neue Länge abgeschnitten. Alle Elemente, die nicht mehr hinein passen, werden verworfen, und ihre Werte gehen verloren.

Wenn Sie `length` auf einen Wert setzen, der größer als der aktuelle ist, werden neue, undefinierte Elemente am Ende des Arrays hinzugefügt, um es auf die neu angegebene Größe zu erweitern.

Beachten Sie, dass Array-Elemente zwar Objekten zugewiesen werden können, dass diese aber keine `length`-Eigenschaft haben. Die Eigenschaft `length` ist mit ihrem besonderen Verhalten das wichtigste Merkmal eines Arrays. Die anderen Merkmale, die Arrays von Objekten unterscheiden, sind die verschiedenen Methoden, die von der Klasse Array definiert werden und die in "7.7 Array-artige Objekte", »Array-Methoden«, beschrieben werden.

## 7.5.7 Mehrdimensionale Arrays

JavaScript unterstützt keine echten mehrdimensionalen Arrays. Sie können jedoch stattdessen auf sehr schöne Weise ungefähr dasselbe mit Arrays von Arrays erreichen. Um auf ein Datenelement in einem Array von Arrays zuzugreifen, benutzen Sie den Operator `[]` einfach doppelt. Nehmen wir beispielsweise an, die Variable `matrix` sei ein Array von Zahlen-Arrays. Jedes Element in `matrix[x]` ist dann ein Zahlen-Array. Um gezielt auf eine dieser Zahlen zuzugreifen, benutzt man einen Ausdruck wie `matrix[x][y]`.

Hier ist ein konkretes Beispiel, das ein zweidimensionales Array als Multiplikationstabelle nutzt:

    // Ein mehrdimensionales Array erzeugen
    var tabelle = new Array(10);        // Die 10 Zeilen der Tabelle
    for(var i = 0; i < tabelle.length; i++)
      tabelle[i] = new Array(10);      // Jede Zeile hat 10 Spalten

    // Das Array initialisieren
    for(var zeile = 0; zeile < tabelle.length; zeile++) {
      for(spalte = 0; spalte < tabelle[zeile].length; spalte++) {
        tabelle[zeile][spalte] = zeile*spalte;
      }
    }

    // Das mehrdimensionale Array verwenden, um 5*7 zu berechnen
    var produkt = tabelle[5][7]; // 35

# 7.6 Array-Methoden

Außer mit dem Operator `]` können Arrays mit verschiedenen anderen Methoden bearbeitet werden, die von der Klasse Array bereitgestellt werden. Diese Methoden werden in den folgenden Abschnitten erläutert. Viele dieser Methoden sind teilweise von der Programmiersprache Perl inspiriert und werden Perl-Programmierern angenehm vertraut vorkommen. Wie üblich ist dies nur ein Überblick, ausführliche Einzelheiten finden Sie in [Teil III.

## 7.6.1 join( )

Die Methode `Array.join( )` wandelt alle Elemente des Arrays in Strings um und hängt diese aneinander, wobei zwischen je zwei Elemente ein String gesetzt wird, der der Methode als Parameter übergeben werden kann. Wenn kein solcher Trenn-String angegeben wird, wird standardmäßig ein Komma benutzt. Die folgenden Programmzeilen erzeugen zum Beispiel den String »1,2,3«:

    var a = [1, 2, 3];  // Erzeuge ein neues Array mit diesen
              // drei Elementen
    var s = a.join( );  // s == "1,2,3"

Die folgenden Zeilen geben den optionalen Trenn-String an, um ein etwas anderes Ergebnis zu erzielen:

    s = a.join(", ");  // s == "1, 2, 3"

Beachten Sie die Leerstelle hinter dem Komma.

Die Methode `Array.join( )` ist die Umkehrung der Methode `String.split( )`, die ein Array erzeugt, indem sie einen String in Stücke zerlegt.

## 7.6.2 reverse( )

Die Methode `Array.reverse( )` kehrt die Reihenfolge der Elemente in einem Array um. Dies geschieht an Ort und Stelle: Es wird kein neues Array für die umsortierten Elemente angelegt, sondern sie werden innerhalb des schon bestehenden Arrays umsortiert. So erzeugt das folgende Programmfragment unter Benutzung der Methoden `reverse( )` und `join( )` den String »3,2,1«:

    var a = new Array(1,2,3);  // a[0] = 1, a[1] = 2, a[2] = 3
    a.reverse( );              // jetzt a[0] = 3, a[1] = 2, a[2] = 1
    var s = a.join( );         // s == "3,2,1"

## 7.6.3 sort( )

`Array.sort( )` sortiert die Elemente eines Arrays an Ort und Stelle und gibt das sortierte Array zurück. Wenn `sort( )` ohne Argumente aufgerufen wird, sortiert es die Array-Elemente in alphabetischer Reihenfolge (wobei es sie wenn nötig temporär in Strings umwandelt, um den Vergleich durchzuführen):

    var a = new Array("banane", "kirsche", "apfel");
    a.sort( );
    var s = a.join(", "); // s == "apfel, banane, kirsche"

Wenn ein Array undefinierte Elemente enthält, werden sie am Ende des Arrays einsortiert.

Um ein Array in einer anderen Reihenfolge als der alphabetischen zu sortieren, müssen Sie `sort( )` eine Vergleichsfunktion als Argument übergeben. Diese Funktion entscheidet, welches ihrer beiden Argumente im sortierten Array zuerst erscheinen soll. Wenn das erste Argument vor dem zweiten erscheinen soll, sollte die Vergleichsfunktion eine Zahl zurückgeben, die kleiner als null ist, und wenn das erste Argument im sortierten Array hinter dem zweiten stehen soll, sollte sie eine Zahl zurückgeben, die größer als null ist. Wenn die beiden Werte äquivalent sind (d.h., wenn ihre Reihenfolge irrelevant ist), sollte die Vergleichsfunktion 0 zurückgeben. Um also zum Beispiel Array-Elemente numerisch statt alphabetisch zu sortieren, könnten Sie folgendermaßen vorgehen:

    var a = [33, 4, 1111, 222];
    a.sort( );              // Alphabetische Ordnung: 1111, 222, 33, 4
    a.sort(function(a,b) {  // Numerische Ordnung: 4, 33, 222, 1111
          return a-b;       // Gibt je nachdem < 0, 0 oder > 0 zurück
        });

Beachten Sie die Verwendung eines Funktionsliterals in diesem Code. Da die Vergleichsfunktion nur einmal genutzt wird, besteht nicht die Notwendigkeit, ihr einen Namen zu geben.

Als weiteres Beispiel für das Sortieren von Array-Elementen könnten Sie ein Array aus Strings alphabetisch sortieren, ohne die Groß- und Kleinschreibung zu berücksichtigen, indem Sie eine Vergleichsfunktion übergeben, die beide Argumente (mit der Methode `toLowerCase( )`) in Kleinbuchstaben umwandelt, ehe sie sie vergleicht. Ihnen fallen wahrscheinlich noch andere Vergleichsfunktionen ein, die Zahlen in verschiedenen Reihenfolgen sortieren: numerisch absteigend, ungerade Zahlen vor geraden Zahlen usw. Die Möglichkeiten werden natürlich interessanter, wenn es sich bei den Array-Elementen, die Sie sortieren, um Objekte und nicht um einfache Typen wie Zahlen oder Strings handelt.

## 7.6.4 concat( )

Die Methode `Array.concat( )` erzeugt und liefert ein neues Array, das die Elemente des ursprünglichen Arrays enthält, auf dem `concat( )` aufgerufen wurde, gefolgt von den Argumenten von `concat( )`. Wenn irgendeines dieser Argumente selbst ein Array ist, wird es in seine einzelnen Elemente aufgelöst, und seine Elemente werden dem Rückgabe-Array hinzugefügt. Beachten Sie aber, dass `concat( )` Arrays von Arrays nicht rekursiv in Elemente auflöst. Hier sehen Sie einige Beispiele:

    var a = [1,2,3];
    a.concat(4, 5)         // Gibt [1,2,3,4,5] zurück
    a.concat([4,5]);       // Gibt [1,2,3,4,5] zurück
    a.concat([4,5],[6,7])  // Gibt [1,2,3,4,5,6,7] zurück
    a.concat(4, [5,[6,7]]) // Gibt [1,2,3,4,5,[6,7]] zurück

## 7.6.5 slice( )

Die Methode `Array.slice( )` gibt ein _Stück_ (engl. _slice_ ) oder ein Teil-Array des angegebenen Arrays zurück. Ihre beiden Argumente geben den Beginn und das Ende des zurückzugebenden Stücks an. Das Rückgabe-Array enthält das Element, das vom ersten Argument angegeben wurde, und alle nachfolgenden Elemente bis ausschließlich zu dem Element, das vom zweiten Argument angegeben wurde. Wenn nur ein Argument angegeben wurde, enthält das Rückgabe-Array alle Elemente von der Startposition bis zum Ende des Arrays. Wenn ein Argument negativ ist, gibt es ein Array-Element relativ zum letzten Element im Array an. Das Argument −1 gibt zum Beispiel das letzte Element im Array an und das Argument −3 das drittletzte Element des Arrays. Hier sehen Sie einige Beispiele:

    var a = [1,2,3,4,5];
    a.slice(0,3);   // Gibt [1,2,3] zurück
    a.slice(3);     // Gibt [4,5] zurück
    a.slice(1,-1);  // Gibt [2,3,4] zurück
    a.slice(-3,-2); // Gibt [3] zurück

## 7.6.6 splice( )

Die Methode `Array.splice( )` ist eine Allzweckmethode zum Einfügen und Entfernen von Array-Elementen. `splice( )` verändert das Array an Ort und Stelle und gibt kein neues Array zurück, wie `slice( )` und `concat( )` es tun. Beachten Sie, dass `splice( )` und `slice( )` zwar sehr ähnliche Namen haben, aber grundverschiedene Operationen durchführen.

`splice( )` kann Elemente in einem Array löschen, neue Elemente in ein Array einfügen oder beide Operationen gleichzeitig durchführen. Array-Elemente, die nach dem eingefügten oder gelöschten Element erscheinen, werden bei Bedarf verschoben, sodass sie lückenlos an das übrige Array anschließen. Das erste Argument von `splice( )` gibt die Array-Position an, an der der Einfüge- und/oder Löschvorgang beginnen soll. Das zweite Argument gibt die Anzahl von Elementen an, die aus dem Array gelöscht werden sollen. Wenn dieses zweite Argument ausgelassen wird, werden alle Array-Elemente vom Startelement bis zum Ende des Arrays entfernt. `splice( )` gibt ein Array der gelöschten Elemente zurück oder ein leeres Array, wenn keine Elemente gelöscht wurden:

    var a = [1,2,3,4,5,6,7,8];
    a.splice(4);   // Gibt [5,6,7,8] zurück; a ist [1,2,3,4]
    a.splice(1,2); // Gibt [2,3] zurück; a ist [1,4]
    a.splice(1,1); // Gibt [4] zurück; a ist [1]

Die ersten beiden Argumente von `splice( )` geben also an, welche Array-Elemente gelöscht werden sollen. Auf diese Argumente kann eine beliebige Anzahl zusätzlicher Argumente folgen, die Elemente angeben, die in das Array eingefügt werden sollen, und zwar beginnend mit der Position, die vom ersten Argument angegeben wird:

    var a = [1,2,3,4,5];
    a.splice(2,0,'a','b'); // Gibt [] zurück; a ist [1,2,'a','b',3,4,5]
    a.splice(2,2,[1,2],3); // Gibt ['a','b'] zurück; a ist [1,2,[1,2],3,3,4,5]

Beachten Sie, dass `splice( )` im Gegensatz zu `concat( )` Array-Argumente, die es einfügt, nicht in Elemente auflöst. Das bedeutet, dass die Methode, wenn ihr ein Array zum Einfügen übergeben wird, das Array selbst und nicht die Elemente des Arrays einfügt.

## 7.6.7 push( ) und pop( )

Die Methoden `push( )` und `pop( )` ermöglichen es Ihnen, mit Arrays zu arbeiten, als wären es Stacks. Die Methode `push( )` hängt ein oder mehrere neue Elemente an das Ende des Arrays an und gibt die neue Länge des Arrays zurück. Die Methode `pop( )` macht das Gegenteil: Sie löscht das letzte Element eines Arrays, verringert die Länge des Arrays und gibt den entfernten Wert zurück. Beachten Sie, dass beide Methoden das Array an Ort und Stelle bearbeiten, anstatt eine veränderte Kopie des Arrays zu erzeugen. Die Kombination von `push( )` und `pop( )` ermöglicht es Ihnen, ein JavaScript-Array zu verwenden, um einen Last-in-first-out-Stack zu implementieren. Zum Beispiel:

    var stack = [];    // Stack: []
    stack.push(1,2);   // Stack: [1,2]   Gibt 2 zurück
    stack.pop( );      // Stack: [1]    Gibt 2 zurück
    stack.push(3);     // Stack: [1,3]   Gibt 2 zurück
    stack.pop( );      // Stack: [1]    Gibt 3 zurück
    stack.push([4,5]); // Stack: [1,[4,5]] Gibt 2 zurück
    stack.pop( )       // Stack: [1]    Gibt [4,5] zurück
    stack.pop( );      // Stack: []    Gibt 1 zurück

## 7.6.8 unshift( ) und shift( )

Die Methoden `unshift( )` und `shift( )` verhalten sich ähnlich wie `push( )` und `pop( )`, mit dem Unterschied, dass sie Elemente am Beginn und nicht am Ende eines Arrays einfügen und entfernen. `unshift( )` fügt ein oder mehrere Elemente am Beginn eines Arrays ein, verschiebt die bestehenden Array-Elemente auf höhere Indizes, um Platz zu machen, und gibt die neue Länge des Arrays zurück. `shift( )` entfernt das erste Element aus dem Array und gibt es zurück, wobei die Methode alle nachfolgenden Elemente um einen Platz aufrücken lässt, um den frei gewordenen Platz am Beginn des Arrays wieder zu belegen. Ein Beispiel:

    var a = [];         // a: []
    a.unshift(1);       // a: [1]     Rückgabe: 1
    a.unshift(22);      // a: [22,1]   Rückgabe: 2
    a.shift( );         // a: [1]     Rückgabe: 22
    a.unshift(3,[4,5]); // a: [3,[4,5],1] Rückgabe: 3
    a.shift( );         // a: [[4,5],1]  Rückgabe: 3
    a.shift( );         // a: [1]     Rückgabe: [4,5]
    a.shift( );         // a: []     Rückgabe: 1

Beachten Sie das möglicherweise überraschende Verhalten von `unshift( )`, wenn die Methode mit mehreren Argumenten aufgerufen wird. Die Argumente werden nicht nacheinander, sondern alle auf einmal eingefügt (wie mit der Methode `splice( )`). Das bedeutet, dass sie in dem Ergebnis-Array in der gleichen Reihenfolge wie in der Argumentliste erscheinen. Würden die Elemente nacheinander eingefügt, wäre ihre Reihenfolge umgekehrt worden.

## 7.6.9 toString( ) und toLocaleString( )

Ein Array hat, wie jedes JavaScript-Objekt, eine `toString( )`-Methode. Bei einem Array wandelt diese Methode jedes seiner Elemente in einen String um (indem bei Bedarf die `toString( )`-Methoden seiner Elemente aufgerufen werden) und gibt eine durch Kommata getrennte Liste dieser Strings aus. Beachten Sie, dass die Ausgabe keine eckigen Klammern oder irgendeinen anderen Begrenzer um die Array-Werte enthält. Ein Beispiel:

    [1,2,3].toString( )         // Ergibt '1,2,3'
    ["a", "b", "c"].toString( ) // Ergibt 'a,b,c'
    [1, [2,'c']].toString( )    // Ergibt '1,2,c'

Beachten Sie, dass die Methode `join( )` den gleichen String zurückliefert, wenn sie ohne Argumente aufgerufen wird.

`toLocaleString( )` ist die lokalisierte Version von `toString( )`. Sie wandelt jedes Array-Element in einen String um, indem sie die Methode `toLocaleString( )` des Elements aufruft. Dann verknüpft sie die resultierenden Strings mit einem gebietsspezifischen (und durch die Implementierung definierten) Trenn-String.

## 7.6.10 Array-Extras

Der Browser Firefox 1.5 erhöht seine JavaScript-Versionsnummer auf 1.6 und fügt ein Bündel zusätzlicher nativer Array-Methoden ein, die als _Array-Extras_ bezeichnet werden. Sie enthalten vor allem die Methoden `indexOf( )` und `lastIndexOf( )`, die es ermöglichen, ein Array schnell nach einem bestimmten Wert zu durchsuchen (siehe `String.indexOf( )` in Teil III für eine vergleichbare Methode). Weitere Methoden sind beispielsweise `forEach( )`, die die angegebene Funktion für jedes Element des Arrays aufruft, `map( )`, die ein Array mit den Ergebnissen zurückliefert, die erhalten werden, wenn alle Elemente im Array an die angegebene Funktion übergeben werden, und `filter( )`, die ein Array mit den Elementen zurückliefert, für die das angegebene Prädikat `true` zurückgeliefert hat.

Aktuell sind diese zusätzlichen Array-Funktionen nur in Firefox verfügbar und noch keine offiziellen oder De-facto-Standards. Sie werden hier nicht dokumentiert. Aber wenn Sie explizit für den Firefox-Browser schreiben oder eine Kompatibilitätsschicht verwenden, die diese leicht zu emulierenden Array-Methoden stellt, finden Sie die Dokumentation zu ihnen online unter <http://developer.mozilla.org>.

In Kapitel 8 finden Sie einige Beispielimplementierungen für Array-Hilfsmethoden.

# 7.7 Array-artige Objekte

Ein JavaScript-Array ist ungewöhnlich, weil sich seine `length`-Eigenschaft ungewöhnlich verhält:

  * Sein Wert wird automatisch aktualisiert, wenn der Liste neue Elemente hinzugefügt werden.

  * Wird die Eigenschaft gesetzt, wird das Array vergrößert oder verkleinert.

JavaScript-Arrays sind auch Instanzen von `Array`, und die verschiedenen Array-Methoden können auf ihnen aufgerufen werden.

Das sind die einzigartigen Funktionalitäten von JavaScript-Arrays. Aber es sind nicht die wesentlichen Funktionalitäten, die ein Array definieren. Häufig ist es vollkommen vernünftig, jedes Objekt mit einer `length`-Eigenschaft und korrespondierenden nicht-negativen ganzzahligen Eigenschaften als eine Art Array zu behandeln. Diese Array-artigen Objekte tauchen in der Praxis tatsächlich gelegentlich auf, und obwohl Sie auf ihnen keine Array-Methoden aufrufen oder ein besonders Verhalten der `length`-Eigenschaft erwarten können, können Sie sie immer noch mit dem gleichen Code durchlaufen, den Sie für ein echtes Array verwenden. Es stellt sich heraus, dass viele Array-Algorithmen genauso gut mit Array-artigen Objekten funktionieren wie mit echten Arrays. Solange Sie nicht versuchen, dem Array Elemente hinzuzufügen oder die Eigenschaft `length` zu ändern, können Sie Array-artige Objekte wie echte Arrays behandeln.

Der folgende Code nimmt ein gewöhnliches Objekt, fügt ihm Eigenschaften hinzu, um es zu einem Array-artigen Objekt zu machen, und durchläuft dann die »Elemente« des resultierenden Pseudo-Arrays:

    var a = {};

    // Wir beginnen mit einem gewöhnlichen leeren Objekt,
    // und fügen Eigenschaften hinzu, um es "Array-artig" zu machen.
    var i = 0;
    while(i < 10) {
      a[i] = i * i;
      i++;
    }

    a.length = i;

    // Jetzt wird es durchlaufen, als ob es ein echtes Array wäre.
    var total = 0;
    for(var j = 0; j < a.length; j++)
      total += a[j];

Das Arguments-Objekt, das in "8.2.2 Argumentlisten variabler Länge: Das Arguments-Objekt" beschrieben wird, ist ein Array-artiges Objekt. In clientseitigem JavaScript liefern eine Reihe von DOM-Methoden wie `document.getElementsByTagName( )` Array-artige Objekte zurück.

# Kapitel 8. Funktionen

Eine _Funktion_ ist ein Block JavaScript-Code, der einmal definiert wird, aber beliebig oft aufgerufen oder ausgeführt werden kann. Funktionen können _Parameter_ oder _Argumente_ haben – lokale Variablen, deren Wert angegeben wird, wenn die Funktion aufgerufen wird. Häufig verwenden Funktionen diese Argumente, um einen _Rückgabewert_ zu berechnen, der zum Wert des Ausdrucks wird, der die Funktion aufruft. Wenn eine Funktion _auf_ einem Objekt aufgerufen wird, wird die Funktion als _Methode_ bezeichnet. Das Objekt, auf dem sie aufgerufen wird, wird der Funktion dabei als implizites Argument übergeben. Vielleicht ist Ihnen das Funktionskonzept bereits unter einem Namen wie _Subroutine_ oder _Prozedur_ bekannt.

Dieses Kapitel konzentriert sich auf das Definieren und Aufrufen Ihrer eigenen JavaScript-Funktionen. Man darf nicht vergessen, dass JavaScript einige eingebaute Funktionen hat, wie zum Beispiel die Methoden `eval( )`, `parseInt( )` und `sort( )` der Klasse Array. Das JavaScript der Clientseite definiert noch andere, wie zum Beispiel `document.write( )` und `alert( )`. In JavaScript eingebaute Funktionen können genau so verwendet werden wie benutzerdefinierte Funktionen. Mehr Informationen über die hier erwähnten eingebauten Funktionen finden Sie in den Teilen III und IV dieses Buchs.

Funktionen und Objekte sind in JavaScript miteinander verbunden. Aus diesem Grund verschiebe ich die Erörterung einiger Merkmale von Funktionen auf Kapitel 9.

# 8.1 Definition und Aufruf von Funktionen

Wie in Kapitel 6 bereits erwähnt wurde, ist die `function-` Anweisung die gängigste Art, eine Funktion zu definieren. Diese Anweisung besteht aus dem Schlüsselwort `function`, gefolgt von:

  * dem Namen der Funktion,

  * null oder mehr in Klammern eingeschlossenen Parameternamen, die jeweils durch Kommata getrennt werden, und

  * in geschweiften Klammern eingeschlossenen JavaScript-Anweisungen, die den Body der Funktion bilden.

Beispiel 8-1 zeigt die Definitionen mehrerer Funktionen. Auch wenn diese Funktionen kurz und sehr einfach sind, enthält jede von ihnen alle oben genannten Elemente. Funktionen können so definiert werden, dass sie eine wechselnde Anzahl von Parametern entgegennehmen, und sie können eine `return`-Anweisung enthalten, müssen es aber nicht. Die `return`-Anweisung ist in Kapitel 6 eingeführt worden. Sie bewirkt, dass die Ausführung der Funktion beendet und der Wert ihres Ausdrucks, falls vorhanden, an den Aufrufer zurückgegeben wird. Wenn der `return`-Anweisung kein Ausdruck zugeordnet ist, liefert sie den Wert `undefined` zurück. Wenn eine Funktion keine `return`-Anweisung enthält, wird einfach jede Anweisung im Funktionsbody ausgeführt und am Ende der Wert `undefined` an die aufrufende Stelle zurückgegeben.

Beispiel 8-1. Definition von JavaScript-Funktionen

    // Eine Abkürzungsfunktion, manchmal praktischer als document.write( )
    // Sie hat keine return-Anweisung, liefert also undefined zurück.
    function ausgabe(nachricht) {
      document.write(nachricht, "<br>");
    }

    // Diese Funktion berechnet und liefert den Abstand zwischen zwei Punkten
    function abstand(x1, y1, x2, y2) {
      var dx = x2 – x1;
      var dx = x2 – x1;
      return Math.sqrt(dx*dx + dy*dy);
    }

    // Diese rekursive (sich selbst aufrufende) Funktion berechnet die Fakultät.
    // x! ist das Produkt aller positiven Zahlen, die kleiner oder gleich x sind
    function fakultaet(x)
    {
      if (x <= 1)
        return 1;
      return x * fakultaet(x-1);
    }

Ist eine Funktion einmal definiert, so kann sie mit dem `( )`-Operator aufgerufen werden, der in Kapitel 5 eingeführt wurde. Erinnern Sie sich daran, dass die Klammern auf den Namen der Funktion folgen (tatsächlich können Klammern auf jeden JavaScript-Ausdruck folgen, der zu einem Funktionswert ausgewertet wird) und dass die optionale kommaseparierte Liste mit Argumentwerten (oder Ausdrücken) in den Klammern steht. Die in Beispiel 8-1 definierten Funktionen können mit dem folgenden Code aufgerufen werden:

    print("Hallo " + name);
    print("Willkommen auf meinem Blog!");
    gesamt_abst = abstand(0,0,2,1) + abstand(2,1,3,5);
    print("Das geschieht mit der Wahrscheinlichkeit: "
        + fakultaet(5)/fakultaet(13));

Beim Aufruf einer Funktion wird jeder Ausdruck zwischen den Klammern ausgewertet, und jedes dieser Ergebnisse bildet ein Argument der Funktion. Diese Werte werden der Reihe nach den Parametern zugewiesen, die bei der Definition der Funktion in den runden Klammern angegeben wurden. Die Funktion arbeitet mit ihren Parametern, indem sie deren Namen benutzt. Beachten Sie, dass diese Parametervariablen normalerweise nur im Body der ausgeführten Funktion sichtbar sind: Von außerhalb des Funktionsrumpfs oder nachdem die Funktion zurückgekehrt ist, kann normalerweise nicht auf sie zugegriffen werden. (Eine wichtige Ausnahme finden Sie in "8.8 Funktionsgeltung und Closures".)

Da JavaScript keine streng typisierte Sprache ist, wird nicht erwartet, dass Sie einen Datentyp für Funktionsparameter angeben. Umgekehrt überprüft JavaScript auch nicht, ob Sie den Datentyp übergeben, den die Funktion erwartet. Wenn der Datentyp eines Arguments wichtig ist, können Sie ihn mit dem Operator `typeof` selbst überprüfen. JavaScript überprüft auch nicht, ob Sie die richtige Anzahl von Argumenten übergeben. Wenn Sie mehr Argumente übergeben, als die Funktion erwartet, ignoriert die Funktion die zusätzlichen Argumente. Wenn Sie weniger Argumente übergeben, als erwartet werden, erhalten die Parameter, die Sie weglassen, den Wert `undefined`. Einige Funktionen sind so geschrieben, dass sie fehlende Argumente tolerieren können, aber andere verhalten sich fehlerhaft, wenn Sie ihnen nicht alle Argumente übergeben, die sie erwarten. Später in diesem Kapitel werden Sie erfahren, wie eine Funktion ermittelt, wie viele Argumente ihr genau übergeben wurden, und wie Sie auf diese Argument über ihre Position in der Argumentliste statt über den Namen zugreifen können.

Beachten Sie, dass die in Beispiel 8-1 definierte Funktion `print( )` keine `return`-Anweisung enthält, sodass sie immer den Wert `undefined` zurückgibt und nicht als Teil eines größeren Ausdrucks verwendet werden kann. Die Funktionen `abstand( )` und `fakultaet( )` können im Gegensatz dazu auch als Teile größerer Ausdrücke verwendet werden, wie in den oben gezeigten Beispielen zu sehen ist.

## 8.1.1 Geschachtelte Funktionen

In JavaScript können Funktionen in andere Funktionen eingebettet werden. Zum Beispiel:

    function hypothenuse(a, b) {
      function quadrat(x) { return x*x; }
      return Math.sqrt(quadrat(a) + quadrat(b));
    }

Eingebettete Funktionen können nur auf der obersten Ebene der Funktion definiert werden, in die sie eingebettet sind. Das heißt, dass sie nicht innerhalb von Anweisungsblöcken wie dem Rumpf einer `if`-Anweisung oder `while`-Schleife stehen dürfen.[29] Beachten Sie, dass diese Einschränkung nur für Funktionen gilt, die mit der Funktions _anweisung_ definiert werden. Funktionsliterale, die im folgenden Abschnitt beschrieben werden, können überall stehen.

Einige interessante Programmiertechniken nutzen eingebettete Funktionen, mit denen wir uns später in diesem Kapitel noch beschäftigen.

## 8.1.2 Funktionsliterale

In JavaScript ist es erlaubt, Funktionen mit Funktionsliteralen zu definieren. Wie bereits in Kapitel 3 erörtert wurde, ist ein Funktionsliteral ein Ausdruck, der eine unbenannte Funktion definiert. Die Syntax für ein Funktionsliteral ähnelt stark der der `function`-Anweisung, mit den Einschränkungen, dass das Funktionsliteral als Ausdruck statt als Anweisung verwendet wird und kein Funktionsname erforderlich ist. Die folgenden zwei Codezeilen definieren zwei mehr oder weniger identische Funktionen mit einer `function`-Anweisung und einem Funktionsliteral:

    function fx) { return x*x; }        // Funktionsanweisung
    var f = function(x) { return x*x; };    // Funktionsliteral

Funktionsliterale erzeugen zwar unbenannte Funktionen, die Syntax lässt aber optional auch zu, dass ein Funktionsname angegeben wird. Das ist nützlich, wenn Sie rekursive Funktionen schreiben, die sich selbst aufrufen. Zum Beispiel:

    var f = function fakultaet(x) { if (x <= 1) return 1; else return x*fakultaet(x-1); };

Diese Codezeile definiert eine unbenannte Funktion und speichert eine Referenz darauf in der Variablen `f`. Sie speichert _keine_ Referenz auf die Funktion in einer Variablen namens _fact_ , sondern ermöglicht es dem Body der Funktion, sich selbst über den Namen zu referenzieren. Beachten Sie aber, dass diese Art des benannten Funktionsliterals vor JavaScript 1.5 nicht richtig implementiert war.

Da Funktionsliterale von JavaScript-Ausdrücken statt von Anweisungen erzeugt werden, sind sie flexibel und besonders für Funktionen geeignet, die nur einmal verwendet und nicht benannt werden müssen. Die Funktion, die von einem Funktionsliteralausdruck angegeben wird, kann zum Beispiel in einer Variablen gespeichert, an eine andere Funktion übergeben oder direkt aufgerufen werden:

    f[0] = function(x) { return x*x; }; // Definiere und speichere eine Funktion
    a.sort(function(a,b){return a-b;}); // Definiere eine Funktion, und
                       // übergib sie an eine andere
    var zehnquadrat = (function(x) {return x*x;})(10); // Definiere eine Funktion,
                              // und rufe sie auf

## 8.1.3 Funktionsnamen

Jeder zulässige JavaScript-Bezeichner kann als Funktionsname verwendet werden. Versuchen Sie, Funktionsnamen zu wählen, die aussagekräftig, aber kompakt sind. Das richtige Gleichgewicht zu finden, ist eine Kunst, die man erst mit der Erfahrung lernt. Gut gewählte Funktionsnamen können einen großen Unterschied bei der Lesbarkeit (und damit bei der Wartbarkeit) Ihres Codes machen.

Häufig sind Funktionsnamen Verben oder Phrasen, die mit Verben beginnen. Es ist eine allgemeine Konvention, Funktionsnamen mit einem Kleinbuchstaben zu beginnen. Wenn ein Name mehrere Wörter einschließt, werden diese gemäß einer Konvention durch Unterstriche getrennt, `so_zum_beispiel( )`. Gemäß einer anderen Konvention werden alle Wörter außer dem ersten mit einem Großbuchstaben begonnen, `soZumBeispiel( )`. Funktionen, die intern oder verborgen sein sollen, gibt man manchmal Namen, die mit einem Unterstrich beginnen.

Bei einigen Programmierstilen oder in wohldefinierten Programmier-Frameworks kann es hilfreich sein, häufig verwendeten Funktionen sehr kurze Namen zu geben. Das clientseitige JavaScript-Framework Prototype (<http://prototype.conio.net>) verwendet beispielsweise auf elegante Weise eine Funktion namens `$( )` (ja, nur das Dollarzeichen) als intelligenten Ersatz für den häufig verwendeten, aber umständlich zu tippenden Ausdruck `document.getElementById( )`. (In Kapitel 2 wurde erklärt, dass Dollarzeichen und Unterstrich die beiden Zeichen sind, die neben Buchstaben und Zahlen in JavaScript-Bezeichnern zulässig sind.)

* * *

[29] Verschiedene Implementierungen von JavaScript können in Bezug auf Funktionsdefinitionen etwas nachlässiger sein, als der Standard fordert. Beispielsweise erlaubt Netscapes Implementierung von JavaScript 1.5 »bedingte Funktionsdefinitionen«, die in if-Anweisungen stehen.

# 8.2 Funktionsargumente

JavaScript-Funktionen können mit einer beliebigen Anzahl von Argumenten aufgerufen werden – unabhängig von der Anzahl von Argumenten, die in der Funktionsdefinition benannt wurden. Weil Funktionen nicht streng typisiert sind, gibt es keine Möglichkeit, den Typ der Argumente anzugeben, die sie erwarten. Deswegen können Funktionen Werte beliebiger Typen übergeben werden. Der folgende Unterabschnitt behandelt diese Themen.

## 8.2.1 Optionale Argumente

Wenn eine Funktion mit weniger Argumenten aufgerufen wird, als deklariert wurden, haben die restlichen Argumente den Wert `undefined`. Häufig ist es hilfreich, Funktionen so zu schreiben, dass einige Argumente optional sind und weggelassen werden können, wenn die Funktion aufgerufen wird. Dazu müssen Sie einen vernünftigen Default-Wert für Argumente zuweisen können, die weggelassen werden (oder als `null` angegeben werden). Hier ist ein Beispiel:

    // Die Namen der aufzählbaren Eigenschaften des Objekts o werden an das Array
    // a gehängt, und a wird zurückgeliefert. Wenn a weggelassen wird oder null ist,
    // wird ein neues Array erzeugt,
    function kopiereEigenschaftsnamenInArray(o, /* optional */ a) {
      if (!a) a = []; // Ist a undefined oder null, wird ein leeres Array verwendet
      for(var eigenschaft in o) a.push(eigenschaft);
      return a;
    }

Wenn die Funktion auf diese Weise definiert ist, bietet Ihnen das Flexibilität beim Aufruf:

    // Die Eigenschaftsnamen der Objekte o und p abrufen
    var a = kopiereEigenschaftsnamenInArray(o); // Die Eigenschaften von o in ein neues Array
                                              // einlesen
    kopiereEigenschaftsnamenInArray(p,a);    // Die Eigenschaften von p an dieses Array
                          // anhängen

Statt der `if`-Anweisung in der ersten Zeile der Funktion können Sie den `||`-Operator auf folgende eigentümliche Weise verwenden:

    a = a || [];

Denken Sie daran, dass der `||`-Operator, wie in Kapitel 5 gesagt wurde, sein erstes Argument zurückliefert, wenn dieses Argument `true` oder ein Wert ist, der in `true` umgewandelt wird. Andernfalls liefert er sein zweites Argument. Hier liefert er `a`, wenn `a` definiert und nicht-`null` ist, selbst wenn `a` leer ist. Andernfalls liefert er ein neues leeres Array zurück.

Beim Entwurf von Funktionen mit optionalen Argumenten müssen Sie darauf achten, dass Sie die optionalen Argumente ans Ende der Argumentliste stellen, damit sie weggelassen werden können. Ein Programmierer, der Ihre Funktion aufruft, kann beispielsweise nicht das erste Argument weglassen und das zweite übergeben. Er müsste dann explizit `undefined` oder `null` für das erste Argument übergeben.

## 8.2.2 Argumentlisten variabler Länge: Das Arguments-Objekt

Im Body einer Funktion hat der Bezeichner `arguments` eine besondere Bedeutung. `arguments` ist eine besondere Eigenschaft, die auf ein Objekt verweist, das als das Arguments _-_ Objekt bezeichnet wird. Das Arguments-Objekts ist ein Array-artiges Objekt (siehe "7.7 Array-artige Objekte"), das es ermöglicht, die Argumentwerte, die der Funktion übergeben wurden, über einen Index statt über einen Namen abzurufen. Das Arguments-Objekt definiert außerdem eine zusätzliche `callee`-Eigenschaft, die im folgenden Abschnitt beschrieben wird.

Obwohl JavaScript-Funktionen mit einer festgelegten Anzahl von Argumenten definiert werden, kann ihnen eine beliebige Anzahl von Argumenten übergeben werden, wenn sie aufgerufen werden. Das Arguments-Objekt bietet vollständigen Zugriff auf diese Argumentwerte, selbst wenn einige von ihnen unbenannt sind. Nehmen Sie an, Sie definieren eine Funktion `f`, die erwartet, dass ihr ein Argument `x` übergeben wird. Wenn Sie diese Funktion mit zwei Argumenten aufrufen, kann in der Funktion auf das erste über den Namen `x` oder `arguments[0]` zugegriffen werden. Auf das zweite Argument kann nur über `arguments[1]` zugegriffen werden. Wie ein echtes Array hat `arguments` außerdem eine `length`-Eigenschaft, die die Anzahl von Elementen angibt, die es enthält. Im Body der Funktion `f`, die mit zwei Argumenten aufgerufen wurde, hat `arguments.length` also den Wert 2.

Das `arguments`-Objekt ist auf mehrere Weisen hilfreich. Das folgende Beispiel zeigt, wie Sie es einsetzen können, um zu prüfen, ob eine Funktion mit der erforderlichen Anzahl von Argumenten aufgerufen wurde, da JavaScript das nicht für Sie prüft:

    function f(x, y, z)
    {
      // Erst wird geprüft, ob die erforderliche Anzahl von Argumenten übergeben wurde
      if (arguments.length != 3) {
        throw new Error("Funktion f wurde mit " + arguments.length +
                "Argumenten aufgerufen, erwartet aber 3.");
      }
      // Hier kommt die eigentlich Funktion hin ...
    }

Das `arguments`-Objekt öffnet JavaScript-Funktionen eine weitere wichtige Möglichkeit: Sie können so geschrieben werden, dass sie mit einer beliebigen Anzahl von Argumenten funktionieren. Hier ist ein Beispiel, das Ihnen zeigt, wie Sie eine einfache `max( )`-Funktion schreiben können, die eine beliebige Anzahl von Argumenten erwartet und den Wert des größten Arguments zurückliefert, das ihr übergeben wurde (sehen Sie sich dazu auch die eingebaute Funktion `Math.max( )` an, die sich auf gleiche Weise verhält):

    function max(/* ... */)
    {
      var m = Number.NEGATIVE_INFINITY;
      // Alle Argumente durchlaufen und dabei den größten Wert suchen und
      // festhalten
      for(var i = 0; i < arguments.length; i++)
        if (arguments[i] > m) m = arguments[i];
      // Den größten Wert zurückliefern
      return m;
    }

    var maximum = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6);

Funktionen wie diese, die eine beliebige Anzahl von Argumenten annehmen können, werden als _variadische Funktionen_ , _Funktionen variablen Grads_ oder _Varargs-Funktionen_ bezeichnet. Dieses Buch verwendet den umgangsprachlichsten Begriff, _Varargs_ , der in die frühen Tage der Programmiersprache C zurückreicht.

Beachten Sie, dass Varargs-Funktionen keinen Aufruf mit null Argumenten erlauben müssen. Es ist vollkommen vernünftig, das `arguments[]`-Objekt einzusetzen, um Funktionen zu schreiben, die eine festgelegte Anzahl von benannten und erforderlichen Argumenten erwarten, auf die eine beliebige Anzahl von unbenannten, optionalen Argumenten folgt.

Denken Sie daran, dass `arguments` kein echtes Array ist. Es ist ein Arguments-Objekt. Jedes Arguments-Objekt definiert numerierte Array-Elemente und eine `length`-Eigenschaft, ist technisch gesehen aber kein Array. Sie sollten es sich besser als ein Objekt vorstellen, das zufälligerweise einige nummerierte Eigenschaften hat. Die ECMAScript-Spezifikation verlangt nicht, dass das Arguments-Objekt irgendeines der besonderen Verhalten von Arrays implementiert. Obwohl Sie der Eigenschaft `arguments.length` beispielsweise einen Wert zuweisen können, wird die Anzahl der im Objekt definierten Array-Elemente von ECMAScript in Wirklichkeit nicht geändert. (Eine Erläuterung dieses besonderen Verhaltens der `length`-Eigenschaft echter Array-Objekte finden Sie in "7.6.3 sort( )""))

Das Arguments-Objekt hat eine _sehr_ ungewöhnliche Funktionalität. Wenn eine Funktion benannte Argumente hat, sind die Array-Elemente des Arguments-Objekts Synonyme für die lokalen Variablen, die die Funktionsargumente enthalten. Das `arguments[]`-Array und die benannten Argumente sind zwei unterschiedliche Möglichkeiten, auf die gleiche Variable zu verweisen. Wird der Wert eines Arguments mit einem Argumentnamen geändert, ändert das auch den Wert, der über das `arguments[]`-Array abgerufen wird. Umgekehrt ändert sich der über den Argumentnamen abgerufene Wert, wenn der Wert eines Arguments über das `arguments[]`-Array geändert wird. Zum Beispiel:

    function f(x) {
      print(x);       // Zeigt den ursprünglichen Wert des Arguments an
      arguments[0] = null; // Eine Änderung des Array-Elements ändert auch x!
      print(x);       // Zeigt jetzt "null" an
    }

Das ist entspricht in keiner Weise dem Verhalten, das Sie sehen würden, wenn das Arguments-Objekt ein gewöhnliches Array wäre. In diesem Fall könnten `arguments[0]` und `x` anfänglich auf den gleichen Wert verweisen und eine Änderung der einen Referenz hätte keine Auswirkungen auf die andere Referenz.

Schließlich sollten Sie auch daran denken, dass `arguments` nur ein gewöhnlicher JavaScript-Bezeichner und kein reserviertes Wort ist. Wenn eine Funktion ein Argument oder eine lokale Variable mit diesem Namen hat, verbirgt dieses bzw. diese die Referenz auf das Arguments-Objekt. Aus diesem Grund empfiehlt es sich, `arguments` wie ein reserviertes Wort zu behandeln und zu es vermeiden, es als Variablennamen zu verwenden.

### 8.2.2.1 Die callee-Eigenschaft

Zusätzlich zu seinen Array-Elementen definiert das Arguments-Objekt eine `callee`-Eigenschaft, die auf die Funktion verweist, die aktuell ausgeführt wird. Diese Eigenschaft ist nur selten nützlich, kann aber verwendet werden, um es unbenannten Funktionen zu ermöglichen, sich selbst rekursiv aufzurufen. Hier ist beispielsweise ein unbenanntes Funktionsliteral, das Fakultäten berechnet:

    function(x) {
      if (x <= 1) return 1;
      return x * arguments.callee(x-1);
    }

## 8.2.3 Objekteigenschaften als Argumente verwenden

Wenn eine Funktion mehr als drei Argumente erwartet, kann es für den Programmierer, der die Funktion aufruft, schwierig werden, sich die richtige Reihenfolge der Argumente für den Funktionsaufruf zu merken. Um dem Programmierer die Mühe zu ersparen, jedes Mal in der Dokumentation nachzuschlagen, wenn er die Funktion nutzt, sollte man es ermöglichen, dass die Argumente in beliebiger Reihenfolge als Name/Wert-Paare übergeben werden können. Um diese Art des Methodenaufrufs zu implementieren, definieren Sie Ihre Funktion so, dass sie nur ein einziges Argument als Argument erwartet, und lassen den Benutzer dann ein Objekt-Literal übergeben, das die erforderlichen Name/Wert-Paare definiert. Der folgende Code liefert ein Beispiel und zeigt auch, dass diese Art des Funktionsaufrufs es ermöglicht, Defaults für alle Argumente anzugeben, die weggelassen werden:

    // laenge-Elemente des Arrays von in das Array nach kopieren.
    // Zuerst wird das Element von_anfang im Array von kopiert
    // und in das Element nach_anfang des Arrays nach eingefügt.
    // Es ist schwierig, sich die Reihenfolge der Argumente zu merken.
    function arraykopie(/* array */ von, /* index */ von_anfang,
              /* array */ nach,  /* index */ nach_anfang,
              /* integer */ laenge)
    {
      // Hier kommt der Code hin
    }

    // Diese Version ist etwas weniger effizient, aber Sie müssen sich die
    // Reihenfolge der Argumente nicht merken und für von_anfang und
    // nach_anfang kann der Default-Wert 0 vorgegeben werden.
    function einfachekopie(args) {
      arraykopie(args.von,
           args.von_anfang || 0, // Achten Sie auf den bereitgestellten Default-Wert
           args.nach,
           args.nach_anfang || 0,
           args.laenge);
    }

    // So könnten Sie einfachekopie( ) aufrufen:
    var a = [1,2,3,4];
    var b = new Array(4);
    einfachekopie({von: a, nach: b, laenge: 4});

## 8.2.4 Argumenttypen

Da JavaScript nicht streng typisiert ist, werden für Methodenargumente keine Typen deklariert und wird für die Werte, die Sie einer Funktion übergeben, keine Typprüfung durchgeführt. Sie können Ihren Code selbstdokumentierend machen, indem Sie für die Funktionsargumente aussagekräftige Namen wählen und Argumenttypen in Kommentaren angeben wie bei der gerade gezeigten Methode `arraykopie( )`. Bei Argumenten, die optional sind, können Sie in den Kommentar das Wort »optional« einschließen. Und wenn eine Methode eine beliebige Anzahl von Argumenten akzeptiert, können Sie ein Auslassungszeichen verwenden:

    function max(/* number... */) { /* Hier kommt der Code hin */ }

Wie in Kapitel 3 beschrieben, führt JavaScript je nach Bedarf nachsichtige Typumwandlungen durch. Wenn Sie eine Funktion schreiben, die ein Stringargument erwartet, und die Funktion mit einem Wert eines anderen Typs aufrufen, wird der von Ihnen übergebene Wert also einfach in einen String umgewandelt, wenn die Funktion versucht, ihn als einen String zu verwenden. Alle elementaren Typen können in Strings umgewandelt werden, und alle Objekte haben `toString( )`-Methoden (wenn diese auch nicht notwendigerweise nützlich sind). In diesem Fall kann also nie ein Fehler auftreten.

Das ist allerdings nicht immer der Fall. Werfen Sie erneut einen Blick auf die zuvor gezeigte Methode `arraykopie( )`. Sie erwartet als erstes Argument ein Array. Jede vernünftige Implementierung schlägt fehl, wenn das erste Argument etwas anderes als ein Array (oder eventuell ein Array-artiges Objekt) ist. Wenn Sie keine »Wegwerf-Funktion« schreiben, die nur ein, zwei Mal aufgerufen wird, ist es deswegen sinnvoll, Code hinzuzufügen, der den Typ von Argumenten wie diesem prüft. Wenn Argumente des falschen Typs übergeben werden, lösen Sie dann eine Exception aus, die den Umstand meldet. Es ist besser, wenn eine Funktion sofort und verhersagbar fehlschlägt, wenn falsche Daten übergeben werden, als wenn sie mit der Ausführung beginnt und später fehlschlägt, wenn Sie beispielsweise versuchen, auf ein Array-Element einer Zahl zuzugreifen, wie in diesem Code-Fragment:

    // Die Summe der Elemente des Arrays (oder Array-artigen Objekts) a zurückliefern.
    // Die Elemente müssen alle Zahlen sein, aber Elemente, die null oder undefined
    // sind, werden ignoriert.
    function summe(a) {
      if ((a instanceof Array) ||             // wenn a ein Array ist
        (a && typeof a == "object" && "length" in a)) { // oder Array-artig ist
        var gesamt = 0;
        for(var i = 0; i < a.length; i++) {
          var element = a[i];
          if (!element) continue; // null- und undefined-Elemente ignorieren
          if (typeof element == "number") gesamt += element;
          else throw new Error("summe( ): Alle Array-Elemente müssen Zahlen sein");
        }
        return gesamt;
      }
      else throw new Error("summe( ): Das Argument muss ein Array sein");
    }

Diese `summe( )`-Methode ist ziemlich streng in Bezug auf das Argument, das sie akzeptiert, und löst geeignete informative Fehler aus, wenn ungeeignete Werte übergeben werden. Sie bietet aber ein gewisses Maß an Flexibilität, indem sie neben echten Arrays auch Array-artige Objekt akzeptiert und Array-Elemente ignoriert, die `null` und `undefined` sind.

JavaScript ist eine sehr flexible und locker typisierte Sprache, und manchmal ist es passend, dass man Funktionen schreibt, die in Bezug auf die Anzahl und den Typ der übergebenen Argumente flexibel sind. Die folgende `flexisumme( )`-Methode wählt diesen Ansatz (und treibt ihn wahrscheinlich auch zum Äußersten). Beispielsweise akzeptiert sie eine beliebige Anzahl von Argumenten und verarbeitet alle Argumente rekursiv, die Arrays sind. Auf diese Weise kann sie als Varargs-Methode oder mit einem Array-Argument verwendet werden. Außerdem gibt sie sich größte Mühe, nicht-numerische Werte in Zahlen umzuwandeln, bevor sie einen Fehler auslöst:

    function flexisumme(a) {
      var gesamt = 0;
      for(var i = 0; i < arguments.length; i++) {
        var element = arguments[i];
        if (!element) continue; // Argumente ignorieren, die null und undefined sind

        // Versuchen, das Argument auf Basis seines Typs in eine
        // Zahl n umzuwandeln.
        var n;
        switch(typeof element) {
        case "number":
          n = element;         // Keine Umwandlung erforderlich
          break;
        case "object":
          if (element instanceof Array) // Rekursion bei Arrays
            n = flexisumme.apply(this, element);
          else n = element.valueOf( );  // valueOf-Methode für andere Objekte
          break;
        case "function":
          n = element( );        // Versucht, die Funktion aufzurufen
          break;
        case "string":
          n = parseFloat(element);   // Versucht, Strings zu parsen
          break;
        case "boolean":
          n = NaN;           // Boolesche Werte lassen sich nicht umwandeln
          break;
        }

        // Wurde eine gültige Zahl erhalten, wird sie der Summe hinzuaddiert.
        if (typeof n == "number" && !isNaN(n)) gesamt += n;
        // Andernfalls wird ein Fehler gemeldet.
        else throw new Error("summe( ): Kann " + element + " nicht in Zahl umwandeln");
      }
      return total;
    }

# 8.3 Funktionen als Datentypen

Die wichtigsten Eigenschaften von Funktionen sind, dass sie definiert und aufgerufen werden können, wie Sie im vorherigen Abschnitt gesehen haben. Funktionsdefinition und -aufruf gehören zur Syntax von JavaScript wie auch der meisten anderen Programmiersprachen. In JavaScript sind Funktionen jedoch nicht nur syntaktische Einheiten, sondern haben darüber hinaus auch den Charakter von Daten. Das bedeutet, dass sie Variablen zugewiesen, in den Eigenschaften von Objekten oder den Elementen von Arrays gespeichert, als Argumente an Funktionen übergeben werden können usw.[30]

Um zu verstehen, wie Funktionen gleichzeitig JavaScript-Daten und JavaScript-Syntax sein können, stellen Sie sich diese Funktionsdefinition vor:

    function quadrat(x) { return x*x; }

Diese Definition erzeugt ein neues Funktionsobjekt und weist es der Variablen `square` zu. Der Name einer Funktion ist eigentlich unerheblich, es ist einfach der Name einer Variablen, die sich auf die Funktion bezieht. Die Funktion kann einer anderen Variablen zugewiesen werden und wird weiterhin auf die gleiche Art funktionieren:

    var a = quadrat(4); // a enthält die Zahl 16
    var b = quadrat;   // Jetzt verweist b auf dieselbe Funktion wie quadrat
    var c = b(5);    // c enthält die Zahl 25

Funktionen können auch nicht nur globalen Variablen, sondern auch Eigenschaften von Objekten zugewiesen werden. In diesem Fall heißen sie _Methoden_ :

    var o = new Object;
    o.square = function(x) { return x*x; }    // Funktionsliteral
    y = o.quadrat(16);              // y ist 256

Funktionen brauchen nicht einmal Namen, wenn sie zum Beispiel Array-Elementen zugewiesen werden:

    var a = new Array(3);
    a[0] = function(x) { return x*x; } // Dies sind Funktionsliterale
    a[1] = 20;
    a[2] = a[0](a[1]);                // a[2] enthält 400

Die Syntax für den Funktionsaufruf mag in diesem letzten Beispiel etwas seltsam aussehen, ist aber immer noch eine zulässige Verwendung des JavaScript-Operators `( )`

Beispiel 8-2 ist ein ausführliches Beispiel dafür, was mit der Verwendung von Funktionen als Daten alles gemacht werden kann. Hier wird gezeigt, wie Funktionen als Argumente an andere Funktionen übergeben werden können. Dieses Beispiel ist vielleicht ein wenig knifflig, die Kommentare erläutern aber, was passiert. Es lohnt sich, dieses Beispiel genau anzusehen.

Beispiel 8-2. Verwendung von Funktionen als Daten

    // Hier definieren wir ein paar einfache Funktionen
    function addiere(x,y) { return x + y; }
    function subtrahiere(x,y) { return x – y; }
    function multipliziere(x,y) { return x * y; }
    function dividiere(x,y) { return x / y; }

    // Diese Funktion nimmt eine der obigen Funktionen als Argument
    // und ruft sie auf zwei Operanden auf
    function fuehre_aus(operator, operand1, operand2)
    {
      return operator(operand1, operand2);
    }

    // Wir könnten diese Funktion wie folgt aufrufen, um den Wert von
    // (2+3) + (4*5) zu berechnen:
    var i = fuehre_aus(addiere, fuehre_aus(addiere, 2, 3),
        fuehre_aus(multipliziere, 4, 5));

    // Für die Zwecke des Beispiels implementieren wir die einfachen Funktionen erneut,
    // diesmal aber über Funktionsliterale in einem Objekt-Literal.
    var operatoren = {
      addiere:    function(x,y) { return x+y; },
      subtrahiere:  function(x,y) { return x-y; },
      multipliziere: function(x,y) { return x*y; },
      dividiere:   function(x,y) { return x/y; },
      potenziere:  Math.pow // Funktioniert auch mit vordefinierten Funktionen
    };

    // Diese Funktion nimmt den Namen eines Operators, schlägt ihn im Array
    // nach und ruft ihn dann auf den übergebenen Operanden auf. Beachten Sie
    // die Aufrufsyntax der Operatorfunktion.
    function fuehre_aus2(op_name, operand1, operand2)
    {
      if (typeof operatoren[op_name] == "function")
        return operatoren[op_name](operand1, operand2);
      else throw "Unbekannter Operator";
    }

    // Wir könnten die Funktion folgendermaßen aufrufen, um den Wert
    // ("Hallo" + " " + "Welt") zu berechnen:
    var j = fuehre_aus2("addiere", "Hallo", fuehre_aus2("addiere", " ", "Welt"))
    // Mit der vordefinierten Funktion Math.pow( ) geht es so:
    var k = fuehre_aus2("potenziere", 10, 2)

Wenn dieses Beispiel Sie nicht davon überzeugt, wie nützlich es ist, Funktionen als Argumente an andere Funktionen übergeben und als Datenwerte behandeln zu können, dann denken Sie an die Funktion `Array.sort( )`, die die Elemente eines Arrays sortiert. Da es viele mögliche Arten gibt, wie man etwas sortieren kann (numerisch, alphabetisch, nach Datum, aufsteigend, absteigend usw.), nimmt die Funktion `sort( )` optional eine weitere Funktion als Argument entgegen, die ihr sagt, wie die Sortierung durchgeführt werden soll. Diese Funktion hat eine sehr einfache Aufgabe: Sie bekommt zwei Elemente des Arrays übergeben, vergleicht diese und gibt dann einen Wert zurück, der angibt, welches Element das größere ist. Dieses Funktionsargument macht die Methode `sort( )` universell anwendbar und unendlich flexibel: So kann jeder beliebige Datentyp in jede nur denkbare Ordnung gebracht werden. (Ein Beispiel für die Verwendung von `Array.sort( )` finden Sie in "7.3 Objekte als assoziative Arrays".)

* * *

[30] Das mag Ihnen nicht besonders interessant erscheinen, es sei denn, Sie sind mit Sprachen wie Java vertraut, in denen Funktionen ein Teil eines Programms sind, von dem Programm aber nicht bearbeitet werden können.

# 8.4 Funktionen als Methoden

Eine _Methode_ ist nichts anderes als eine JavaScript-Funktion, die in einer Eigenschaft eines Objektes gespeichert ist und über dieses Objekt aufgerufen wird. Erinnern Sie sich daran, dass Funktionen Datenwerte sind und dass die Namen, mit denen sie definiert werden, nichts Besonderes haben. Eine Funktion kann jeder Variablen oder sogar jeder Eigenschaft eines Objekts zugewiesen werden. Wenn Sie eine Funktion `f` und ein Objekt `o` haben, können Sie mit folgendem Code eine Methode mit dem Namen `m` definieren:

    o.m = f;

Nachdem Sie die Methode `m( )` des Objekts `o` definiert haben, rufen Sie sie folgendermaßen auf:

    o.m( );

Oder wenn `m( )` zwei Argumente erwartet, können Sie sie so aufrufen:

    o.m(x, x+2);

Methoden haben eine sehr wichtige Eigenschaft: Das Objekt, über das eine Methode aufgerufen wird, wird innerhalb des Bodys der Methode zum Wert des Schlüsselworts `this`. Wenn Sie `o.m( )` aufrufen, kann im Body der Methode also über das Schlüsselwort `this` das Objekt `o` referenziert werden. Hier ist ein konkretes Beispiel:

    var rechner = { // Ein Objekt-Literal
      operand1: 1,
      operand2: 1,
      berechne: function( ) {
        this.ergebnis = this.operand1 + this.operand2;
      }
    };
    rechner.berechne( );    // Was ist 1+1?
    print(rechner.ergebnis);  // Das Ergebnis anzeigen

Das Schlüsselwort `this` ist wichtig. Jeder Funktion, die als Methode verwendet wird, wird ein implizites Argument übergeben – das Objekt, über das sie aufgerufen wurde. Üblicherweise führt die Methode irgendeine Operation auf diesem Objekt durch, deswegen ist die Syntax für den Methodenaufruf eine besonders elegante Art, den Umstand auszudrücken, dass die Funktion auf dem Objekt operiert. Vergleichen Sie die folgenden beiden Codezeilen:

    rechteck.setGroesse(breite, hoehe);
    setRechteckgroesse(rechteck, breite, hoehe);

Die beiden hypothetischen Funktionen, die in diesen beiden Zeilen aufgerufen werden, könnten genau die gleiche Operation auf dem (hypothetischen) Objekt `rechteck` durchführen, aber die Methodenaufruf-Syntax in der ersten Zeile bringt viel klarer den Gedanken zum Ausdruck, dass es das Objekt `rechteck` ist, das der primäre Gegenstand der Operation ist. (Wenn Ihnen die erste Zeile nicht als die natürlichere Syntax erscheint, dann liegt das sicher daran, dass Sie die objektorientierte Programmierung noch nicht kennen.)

Wenn eine Funktion als eine Funktion und nicht als eine Methode aufgerufen wird, verweist das Schlüsselwort `this` auf das globale Objekt. Verwirrenderweise gilt das auch, wenn eine eingebettete Funktion (als Funktion) aufgerufen wird, die in einer Methode enthalten ist, die als Methode aufgerufen wurde. In der umschließenden Methode hat das Schlüsselwort `this` einen Wert, verweist im Body der eingebetteten Funktion (der Intuition entgegen) aber auf das globale Objekt.

Beachten Sie, dass `this` ein Schlüsselwort, keine Variable und kein Eigenschaftsname ist. Die JavaScript-Syntax erlaubt Ihnen nicht, `this` einen Wert zuzuweisen.

# 8.5 Konstruktorfunktionen

Eine _Konstruktorfunktion_ ist eine Funktion, die die Eigenschaften eines Objekts initialisiert und für die Verwendung mit dem `new`-Operator gedacht ist. Konstruktoren werden in Kapitel 9 ausführlich behandelt. Kurz gesagt: Der `new`-Operator erzeugt ein neues Objekt und ruft dann die Konstruktorfunktion auf und übergibt dabei das neu erzeugte Objekt als Wert des Schlüsselworts `this`.

# 8.6 Funktionseigenschaften und -methoden

Wir haben gesehen, dass Funktionen in JavaScript-Programmen als Datenwerte verwendet werden können. Der `typeof`-Operator liefert den String »function« zurück, wenn er auf eine Funktion angewandt wird, aber Funktionen sind eigentlich besondere Arten von JavaScript-Objekten. Da Funktionen Objekte sind, haben sie Eigenschaften und Methoden wie beispielsweise die Date- und RegExp-Objekte.

## 8.6.1 Die length-Eigenschaft

Wie weiter oben gezeigt wurde, gibt im Body einer Funktion die `length`-Eigenschaft des Arrays `arguments` an, wie viele Argumente der Funktion übergeben wurden. Die `length`-Eigenschaft der Funktion selbst hat aber eine andere Bedeutung. Diese schreibgeschützte Eigenschaft gibt die Anzahl der Argumente zurück, die die Funktion bei der Übergabe _erwartet_ , also die Anzahl der Parameter, die sie in ihrer Parameterliste deklariert. Erinnern Sie sich daran, dass eine Funktion unabhängig von der Anzahl der Parameter, die sie deklariert, mit jeder beliebigen Anzahl von Argumenten aufgerufen werden kann, die sie über das Array `arguments` abrufen kann. Die Eigenschaft `length` des Function-Objekts gibt genau an, wie viele deklarierte Parameter eine Funktion hat. Beachten Sie, dass diese `length`-Eigenschaft im Gegensatz zu `arguments.length` sowohl innerhalb als auch außerhalb des Funktionsbodys zur Verfügung steht.

Der folgende Code definiert eine Funktion namens `pruefen( )`, der von einer anderen Funktion das Array `arguments` übergeben wird. Sie vergleicht die Eigenschaft `arguments.length` mit der Eigenschaft `Function.length` (auf die sie als `arguments.callee. length` zugreift), um zu sehen, ob ihr die erwartete Anzahl von Argumenten übergeben wurde. Wenn das nicht der Fall ist, löst sie eine Exception aus. Auf die Funktion `pruefen( )` folgt die Testfunktion `f( )`, die zeigt, wie `pruefen( )` verwendet werden kann:

    function pruefen(args) {
      var tatsaechlich = args.length;   // Die tatsächliche Anzahl Argumente
      var erwartet = args.callee.length; // Die erwartete Anzahl Argumente
      if (tatsaechlich != erwartet) {   // Löse eine Exception aus, wenn die Zahlen
                        // nicht übereinstimmen
        throw new Error("Falsche Anzahl Argumente: erwartet: " +
            erwartet + "; übergeben " + tatsaechlich);
      }
    }

    function f(x, y, z) {
      // Prüfe, ob die erwartete und tatsächliche Anzahl Argumente gleich
      // sind, löse andernfalls eine Exception aus
      pruefen(arguments);
      // Durchlaufe nun den Rest der Funktion ganz normal
      return x + y + z;
    }

## 8.6.2 Die prototype-Eigenschaft

Jede Funktion hat eine Eigenschaft namens `prototype`, die auf ein vordefiniertes Prototyp-Objekt verweist. Dieses Prototyp-Objekt kommt ins Spiel, wenn die Funktion als Konstruktor mit dem Operator `new` verwendet wird. Sie spielt eine wichtige Rolle bei der Definition von neuen Objektarten. Diese Eigenschaft wird in Kapitel 9 ausführlich beschrieben.

## 8.6.3 Definition eigener Funktionseigenschaften

Wenn eine Funktion eine Variable verwenden muss, deren Wert über die Aufrufe hinweg bestehen bleibt, verwendet man besser eine Eigenschaft des Function-Objekts, anstatt den Namensraum vollzustopfen, indem man eine globale Variable definiert. Angenommen, Sie möchten zum Beispiel eine Funktion schreiben, die bei jedem Aufruf einen eindeutigen Integer zurückgibt. Die Funktion darf nicht zweimal den gleichen Wert zurückgeben. Um dies zu erreichen, muss die Funktion die Werte nachhalten, die sie bereits zurückgegeben hat, und diese Information muss über den Aufruf der Funktion hinweg bestehen bleiben. Sie könnten diese Information zwar auch in einer globalen Variablen speichern, aber das ist unnötig, da die Information nur von der Funktion selbst verwendet wird. Es ist besser, die Information in einer Eigenschaft des Function-Objekts zu speichern. Hier sehen Sie ein Beispiel, das bei jedem Aufruf einen eindeutigen Integer zurückgibt:

    // Erzeuge und initialisiere die "statische" Variable.
    // Da Funktionsdeklarationen verarbeitet werden, ehe der Code läuft,
    // können wir diese Zuweisung vor der Funktionsdeklaration vornehmen.
    eindeutigerInteger.zaehler = 0;

    // Hier ist die Funktion. Sie gibt bei jedem Aufruf einen anderen Wert
    // zurück und behält den jeweils letzten Rückgabewert mit einer eigenen
    // "statischen" Eigenschaft im Auge.
    function eindeutigerInteger( ) {
      // Inkrementiere unsere "statische" Variable, und gib sie zurück.
      return eindeutigerInteger.zaehler++;
    }

## 8.6.4 Die Methoden apply( ) und call( )

ECMAScript spezifiziert zwei Methoden, die für alle Funktionen definiert sind, nämlich `call( )` und `apply( )`. Mit diesen Methoden können Sie eine Funktion so aufrufen, als sei sie eine Methode eines anderen Objekts. Das erste Argument von `call( )` und `apply( )` ist das Objekt, auf dem die Funktion aufgerufen werden soll. Dieses Argument wird zum Wert des Schlüsselwortes `this` im Body der Funktion. Die verbleibenden Argumente von `call( )` sind die Werte, die der aufgerufenen Funktion übergeben werden. Um zum Beispiel zwei Zahlen an die Funktion `f( )` zu übergeben und sie so aufzurufen, als sei sie eine Methode des Objekts `o`, könnten Sie folgenden Code verwenden:

    f.call(o, 1, 2);

Dieser ähnelt den folgenden Codezeilen:

    o.m = f;
    o.m(1,2);
    delete o.m;

Die Methode `apply( )` ähnelt der Methode `call( )`, nur dass hier die Argumente, die der Funktion übergeben werden, als Array angegeben werden:

    f.apply(o, [1,2]);

Um zum Beispiel die größte Zahl in einem Array mit Zahlen zu finden, können Sie mit der Methode `apply( )` die Elemente des Arrays an die Funktion `Math.max( )` übergeben:

    var groesste = Math.max.apply(null, zahlen_array);

# 8.7 Beispiele für Hilfsfunktionen

Dieser Abschnitt schließt einige nützliche Beispielfunktionen für die Manipulation von Objekten, Arrays und Funktionen ein. Beginnen wir mit einigen Objekt-Hilfsfunktionen in Beispiel 8-3.

Beispiel 8-3. Hilfsfunktionen für Objekte

    // Ein Array zurückliefern, das die Namen der aufzählbaren Eigenschaften von o enthält
    function getEigenschaftsnamen(/* object */o) {
      var r = [];
      for(name in o) r.push(name);
      return r;
    }

    // Die aufzählbaren Eigenschaften des Objekts von in das Objekt nach kopieren.
    // Wenn nach null ist, wird ein neues Objekt erzeugt. Die Funktion liefert nach
    // oder das neu erzeugte Objekt zurück.
    function kopiereEigenschaften(/* object */ von, /* optionales object */ nach) {
      if (!nach) nach = {};
      for(p in von) nach[p] = von[p];
      return nach;
    }

    // Die aufzählbaren Eigenschaften des Objekts von in das Objekt nach kopieren,
    // aber nur die berücksichtigen, die von nach noch nicht definiert werden.
    // Das ist beispielsweise nützlich, wenn von Default-Werte enthält, die
    // wir verwenden wollen, wenn sie in nach noch nicht definiert sind.
    function kopiereUndefinedEigenschaften(/* object */ von, /* object */ nach) {
      for(p in von) {
        if (!p in nach) nach[p] = von[p];
      }
    }

Als Nächstes gibt es hier in Beispiel 8-4 einige Hilfsfunktionen für Arrays.

Beispiel 8-4. Hilfsfunktionen für Arrays

    // Alle Elemente des Arrays an die angegebene Prüffunktion übergeben.
    // Liefert ein Array zurück, das die Elemente enthält, für die die Prüffunktion
    // true zurückliefert.
    function filtereArray(/* Array */ a, /* Boolesche Funktion */ praedikat) {
      var ergebnisse = [];
      var laenge = a.length; // Falls die Prüffunktion die Länge ändert!
      for(var i = 0; i < laenge; i++) {
        var element = a[i];
        if (praedikat(element)) ergebnisse.push(element);
      }
      return ergebnisse;
    }

    // Die Array-Werte zurückliefern, die sich ergeben, wenn alle Elemente des
    // Arrays an die Funktion f übergeben werden
    function arrayAbbilden(/* Array */a, /* Funktion */ f) {
      var r = [];       // Zum Festhalten der Ergebnisse
      var laenge = a.length; // Falls f die Länge ändert!
      for(var i = 0; i < laenge; i++) r[i] = f(a[i]);
      return r;
    }

Schließlich bietet Beispiel 8-5 Hilfsfunktionen für die Arbeit mit Funktionen. Tatsächlich verwenden sie eingebettete Funktionen und liefern diese zurück. Auf diese Weise zurückgelieferte eingebettete Funktionen werden gelegentlich als Closures bezeichnet und können ziemlich verwirrend sein. Closures werden im nächsten Abschnitt behandelt.

Beispiel 8-5. Hilfsfunktionen für Funktionen

    // Eine eigenständige Funktion zurückliefern, die die Funktion f als eine Methode
    // des Objekts o aufruft. Das ist nützlich, wenn Sie einer Funktion eine
    // Methode übergeben müssen. Wenn Sie sie nicht an ihr Objekt binden, geht die
    // Verknüpfung verloren. Die Methode wird dann wie eine gewöhnliche Funktion aufgerufen.
    function bindeMethode(/* Objekt */ o, /* Funktion */ f) {
      return function( ) { return f.apply(o, arguments) }
    }

    // Eine Funktion zurückliefern, die die Funktion f mit den angegebenen Argumenten
    // und eventuellen zusätzlichen Argumenten aufruft, die der zurückgekehrten
    // Funktion übergeben werden. (Das wird auch als "currying" oder würzen bezeichnet.)
    function bindeArgumente(/* Funktion */ f /*, ursprüngliche Argumente... */) {
      var gebundeneArgs = arguments;
      return function( ) {
        // Ein Array mit Argumenten aufbauen. Es beginnt mit den zuvor gebundenen
        // Argumenten und wird mit denen erweitert, die jetzt übergeben werden.
        var args = [];
        for(var i = 1; i < gebundeneArgs.length; i++) args.push(gebundeneArgs[i]);
        for(var i = 0; i < arguments.length; i++) args.push(arguments[i]);

        // Jetzt die Funktion mit diesen Argumenten aufrufen
        return f.apply(this, args);
      }
    }

# 8.8 Funktionsgeltung und Closures

Wie in Kapitel 4 beschrieben, wird der Body einer JavaScript-Funktion in einem lokalen Geltungsbereich ausgeführt, der sich vom globalen Geltungsbereich unterscheidet. Dieser Abschnitt erklärt diese und damit verwandte Fragen zu Geltungsbereichen einschließlich Closures.[31]

## 8.8.1 Lexikalische Geltung

Funktionen haben in JavaScript eher lexikalische als dynamische Geltung. Das heißt, dass sie in dem Geltungsbereich laufen, in dem sie definiert wurden, und nicht in dem, in dem sie ausgeführt werden. Wenn eine Funktion definiert wird, wird die aktuelle Geltungsbereichskette gespeichert und wird zu einem internen Zustand der Funktion. Auf der obersten Ebene besteht die Geltungsbereichskette aus dem globalen Objekt. Lexikalische Geltung ist dann nicht sonderlich relevant. Aber wenn Sie eine eingebettete Funktion definieren, schließt die Geltungsbereichskette die einschließende Funktion ein. Das bedeutet, dass eingebettete Funktionen Zugriff auf alle Argumente und lokalen Variablen der einschließenden Funktion haben.

Beachten Sie, dass, obwohl die Geltungsbereichskette fixiert wird, wenn die Funktion definiert wird, die Eigenschaften, die in dieser Geltungsbereichskette definiert sind, nicht fixiert sind. Die Geltungsbereichskette »lebt«, und eine Funktion hat Zugriff auf alle Bindungen, die aktuell sind, wenn sie aufgerufen wird.

## 8.8.2 Das Aufrufobjekt

Wenn der JavaScript-Interpreter eine Funktion aufruft, setzt er zunächst die Geltungsbereichskette, die galt, als die Funktion definiert wurde. Als Nächstes setzt er ein neues Objekt, das als das _Aufrufobjekt_ (engl. _call object_ ) bezeichnet wird (die ECMAScript-Spezifikation verwendet den Begriff _Aktivierungsobjekt_ , engl. _activation object_ ), an den Anfang der Geltungsbereichskette. Das Aufrufobjekt wird dann mit einer Eigenschaft namens `arguments` initialisiert, die auf das Arguments-Objekt für die Funktion verweist. Dann werden dem Aufrufobjekt die benannten Parameter der Funktion hinzugefügt. Alle lokalen Variablen, die mit der `var`-Anweisung deklariert werden, werden ebenfalls in diesem Objekt definiert. Da dieses Aufrufobjekt am Anfang der Geltungsbereichskette steht, gelten lokale Variablen, Funktionsparameter und das Arguments-Objekt alle innerhalb der Funktion. Das bedeutet auch, dass sie natürlich alle Eigenschaften mit dem gleichen Namen verbergen, die sich weiter oben in der Geltungsbereichskette befinden.

Beachten Sie, dass `this` im Unterschied zu `arguments` ein Schlüsselwort und keine Eigenschaft des Aufrufobjekts ist.

## 8.8.3 Das Aufrufobjekt als Namensraum

Manchmal ist es nützlich, einfach eine Funktion zu definieren, um ein Call-Objekt zu erzeugen, das als temporärer Namensraum dient, in dem Sie Variablen definieren und Eigenschaften erzeugen können, ohne den globalen Namensraum zu beschädigen. Nehmen Sie beispielsweise an, Sie haben eine Datei mit JavaScript-Code, den Sie in einer Reihe unterschiedlicher JavaScript-Programme verwenden wollen (oder, bei clientseitigem JavaScript, in einer Anzahl verschiedener Webseiten). Nehmen Sie an, dass dieser Code, wie der meiste Code, Variablen definiert, um die Zwischenergebnisse seiner Berechnungen zu speichern. Das Problem ist, dass Sie, da der Code in vielen unterschiedlichen Programmen verwendet wird, nicht wissen, ob die Variablen, die er erzeugt, in Konflikt mit Variablen geraten, die von den Programmen verwendet werden, die ihn importieren.

Die Lösung ist natürlich, den Code in eine Funktion zu packen und dann die Funktion aufzurufen. Auf diese Weise werden die Variablen im Aufrufobjekt der Funktion gespeichert:

    function init( ) {
      // Hier kommt der Code hin.
      // Alle deklarierten Variablen werden zu Eigenschaften des Aufrufobjekts,
      // anstatt den globalen Namensraum zu verstopfen.
    }
    init( ); // Aber vergessen Sie nicht, die Funktion aufzurufen

Der Code fügt dem globalen Namensraum nur eine einzige Eigenschaft hinzu: die Eigenschaft `init`, die auf die Funktion verweist. Wenn selbst die Definition einer einzigen Eigenschaft noch zu viel ist, können Sie mit einem einzigen Ausdruck eine anonyme Funktion definieren und aufrufen. Der Code für dieses JavaScript-Idiom sieht so aus:

    (function( ) { // Diese Funktion hat keinen Namen.
      // Hier kommt der Code hin.
      // Alle deklarierten Variablen werden zu Eigenschaften des Aufrufobjekts,
      // anstatt den globalen Namensraum zu verstopfen.
    })( );     // Ende des Funktionsliterals und Aufruf.

Beachten Sie, dass die Klammern um das Funktionsliteral von der JavaScript-Syntax verlangt werden.

## 8.8.4 Eingebettete Funktionen als Closures

Gemeinsam erzeugen die Umstände, dass JavaScript erstens eingebettete Funktionen erlaubt, es zweitens ermöglicht, dass Funktionen als Daten verwendet werden, und drittens lexikalische Geltung verwendet, überraschende und mächtige Effekte. Beginnen Sie damit, sich das genauer anzusehen, indem Sie sich eine Funktion `g` vorstellen, die innerhalb einer Funktion `f` definiert ist. Wenn `f` aufgerufen wird, besteht die Geltungsbereichskette aus dem Aufrufobjekt für den Aufruf von `f`, auf das das globale Objekt folgt. `g` ist in `f` definiert, diese Geltungsbereichskette wird also als Teil der Definiton von `g` gespeichert. Wenn `g` aufgerufen wird, schließt die Geltungsbereichskette drei Objekte ein: `gs` eigenes Aufrufobjekt, das Aufrufobjekt von `f` und das globale Objekt.

Eingebettete Funktionen sind vollkommen verstehbar, wenn sie im gleichen lexikalischen Geltungsbereich aufgerufen werden, in dem sie definiert sind. Beispielsweise macht der folgende Code nichts sonderlich Überraschendes:

    var x = "global";
    function f( ) {
      var x = "local";
      function g( ) { alert(x); }
      g( );
    }
    f( ); // Der Aufruf der Funktion zeigt "local" an

Aber Funktionen sind in JavaScript Daten wie alle anderen Datenwerte. Sie können also aus Funktionen zurückgeliefert werden, Objekteigenschaften zugewiesen werden, in Arrays gespeichert werden und so weiter. Auch das hat keine sonderlich überraschenden Auswirkungen, außer wenn eingebettete Funktionen beteiligt sind. Betrachten Sie den folgenden Code, der eine Funktion enthält, die eine eingebettete Funktion zurückliefert. Jedes Mal, wenn sie aufgerufen wird, liefert sie eine Funktion zurück. Der JavaScript-Code der zurückgelieferten Funktion ist immer der gleiche, aber der Geltungsbereich, in dem sie erzeugt wird, ist bei jedem Aufruf etwas anders, weil die Argumente der äußeren Funktion bei jedem Aufruf anders sind. (Das heißt, bei jedem Aufruf der äußeren Funktion befindet sich ein anderes Aufrufobjekt in der Geltungsbereichskette.) Wenn Sie die zurückgelieferten Funktionen in einem Array speichern und dann einzeln aufrufen, werden Sie feststellen, dass alle unterschiedliche Werte zurückliefern. Da all diese Funktionen aus dem gleichen JavaScript-Code bestehen und alle aus dem gleichen Geltungsbereich aufgerufen werden, ist der Geltungsbereich, in dem die Funktionen definiert wurden, der einzige Faktor, der für die abweichenden Rückgabewerte verantwortlich sein kann:

    // Diese Funktion liefert bei jedem Aufruf eine Funktion zurück.
    // Der Geltungsbereich, in dem die Funktion definiert wird, ist bei
    // jedem Aufruf anders.
    function macheFunktion(x) {
      return function( ) { return x; }
    }

    // makefunc( ) mehrfach aufrufen und die Ergebnisse in einem Array speichern:
    var a = [macheFunktion(0), macheFunktion(1), macheFunktion(2)];

    // Jetzt diese Funktionen aufrufen und ihre Werte anzeigen.
    // Obwohl der Funktionscode immer der gleiche ist, ist die Geltung
    // anders. Das führt dazu, dass jeder Aufruf einen anderen Wert
    // zurückliefert:
    alert(a[0]( )); // Zeigt 0 an
    alert(a[1]( )); // Zeigt 1 an
    alert(a[2]( )); // Zeigt 2 an

Die Ergebnisse dieses Codes sind genau so, wie Sie sie von einer strengen Anwendung der Regel lexikalischer Geltung erwarten würden: Eine Funktion wird in dem Geltungsbereich ausgeführt, in dem sie definiert wurde. Der Grund dafür, dass diese Ergebnisse überraschend sind, ist allerdings, dass man erwarten würde, dass lokale Geltungsbereiche nicht mehr weiterbestehen, wenn die Funktion nicht mehr besteht, die sie definiert hat. Und das ist eigentlich auch das, was normalerweise passiert. Wenn eine Funktion aufgerufen wird, wird ein Aufrufobjekt für sie erzeugt und an den Anfang der Geltungsbereichskette gesetzt. Wenn die Funktion zurückkehrt, wird das Aufrufobjekt aus der Geltungsbereichskette entfernt. Wenn keine eingebetteten Funktionen beteiligt sind, ist die Geltungsbereichskette die einzige Referenz auf das Aufrufobjekt. Wenn das Objekt aus der Kette entfernt wird, gibt es keine Referenzen mehr darauf, und es endet in der Garbage Collection.

Aber eingebettete Funktionen ändern dieses Bild. Wenn eine eingebettete Funktion erzeugt wird, referenziert die Definition dieser Funktion das Aufrufobjekt, weil dieses Aufrufobjekt oben in der Geltungsbereichskette steht, in der die Funktion aufgerufen wurde. Wenn die eingebettete Funktion nur in der äußeren Funktion verwendet wird, befindet sich die einzige Referenz auf die eingebettete Funktion im Aufrufobjekt. Wenn die äußere Funktion zurückkehrt, referenziert die eingebettete Funktion das Aufrufobjekt und das Aufrufobjekt die eingebettete Funktion. Aber es gibt keine anderen Referenzen auf eins von beidem, also sind beide Objekte für die Garbage Collection auswählbar.

Aber das ist anders, wenn Sie eine Referenz auf die eingebettete Funktion im globalen Geltungsbereich speichern. Das machen Sie, indem Sie die eingebettete Funktion als Rückgabewert der äußeren Funktion verwenden oder indem Sie die eingebettete Funktion als Eigenschaft eines anderen Objekts speichern. Dann gibt es eine externe Referenz auf die eingebettete Funktion, und die eingebettete Funktion bewahrt ihre Referenz auf das Aufrufobjekt der äußeren Funktion. Die Folge ist, dass das Aufrufobjekt dieses einen Aufrufs weiterlebt und die Namen und Werte der Funktionsargumente und lokalen Variablen in diesem Objekt bestehen bleiben. JavaScript-Code kann auf das Aufrufobjekt auf keine Weise direkt zugreifen, aber die Eigenschaften, die es definiert, sind Teil der Geltungsbereichskette für jeden Aufruf der eingebetteten Funktion. (Beachten Sie, dass zwei globale Referenzen auf zwei eingebettete Funktionen, die in der äußeren Funktion gespeichert werden, ein Aufrufobjekt teilen. Änderungen, die durch den Aufruf der einen Funktion gemacht werden, sind für Aufrufe der anderen Funktion sichtbar.)

JavaScript-Funktionen sind Kombinationen von Code, der ausgeführt wird, und dem Geltungsbereich, in dem sie ausgeführt werden. Diese Kombination von Code und Geltungsbereich wird in wissenschaftlicher Computer-Literatur als _Closure_ bezeichnet. Alle JavaScript-Funktionen sind Closures. Diese Closures sind aber nur in dem oben besprochenen Fall interessant: wenn eine eingebettete Funktion aus dem Geltungsbereich exportiert wird, in dem sie definiert ist. Wenn eine eingebettete Funktion auf diese Weise verwendet wird, wird das oft explizit als Closure bezeichnet.

Closures sind eine interessante und mächtige Technik. Obwohl sie in der alltäglichen JavaScript-Programmierung nicht häufig verwendet werden, ist es trotzdem gut, wenn man sie versteht. Wenn Sie Closures verstehen, verstehen Sie die Geltungsbereichskette und Aufrufobjekte von Funktionen und können sich selbst mit Recht einen fortgeschrittenen JavaScript-Programmierer nennen.

### 8.8.4.1 Closure-Beispiele

Gelegentlich möchten Sie eine Funktion schreiben, die sich an einen Wert über Aufrufe hinweg erinnern kann. Der Wert kann nicht in einer lokalen Variable gespeichert werden, weil das Aufrufobjekt nicht über Aufrufe hinweg erhalten bleibt. Eine globale Variable würde funktionieren, würde aber den globalen Namensraum verstopfen. In "8.6.3 Definition eigener Funktionseigenschaften" habe ich eine Funktion Namens `eindeutigerInteger( )` vorgestellt, die eine Eigenschaft von sich selbst verwendet, um einen Wert persistent zu speichern. Sie können eine Closure einsetzen, um noch einen Schritt weiter zu gehen und eine persistente und _private_ Variable zu erzeugen. Hier sehen Sie, wie Sie die Funktion ohne Closure schreiben würden:

    // Liefert bei jedem Aufruf einen anderen Ganzzahlwert.
    eindeutigeID = function( ) {
      if (!arguments.callee.id) arguments.callee.id = 0;
      return arguments.callee.id++;
    };

Das Problem bei diesem Ansatz ist, dass jeder `eindeutigeID.id` wieder auf 0 setzen und damit den Auftrag der Funktion unterbrechen kann, niemals den gleichen Wert zweimal zurückliefert. Das können Sie verhindern, indem Sie den persistenten Wert in einer Closure speichern, auf die nur Ihre Funktion Zugriff hat:

    eindeutigeID = (function( ) { // Das Aufrufobjekt für diese Funktion hält unseren Wert
                                                  // fest
      var id = 0;                                // Das ist der private persistente Wert
      // Die äußere Funktion liefert die eingebette Funktion zurück, die Zugriff
      // auf den persistenten Wert hat. Es ist diese eingebettete Funktion, die wir
      // in der Variablen uniqueID von oben speichern.
      return function( ) { return id++; }; // Zurückliefern und inkrementieren
    })( ); // Die äußere Funktion aufrufen, nachdem sie definiert wurde.

Beispiel 8-6 ist ein zweites Closure-Beispiel. Es zeigt, dass private persistente Variablen (wie die oben verwendete), von mehreren Funktionen geteilt werden können.

Beispiel 8-6. Private Eigenschaften mit Closures

    // Diese Funktion fügt dem Objekt o eine Zugriffsmethoden für eine Eigenschaft
    // mit dem angegebenen Namen hinzu. Die Methoden haben die Namen get<name>
    // und set<name>. Wenn eine Prüffunktion angegeben wird, wird diese von der
    // Setter-Methode verwendet, um die Gültigkeit ihres Argments zu prüfen, bevor sie
    // es speichert. Liefert die Prüfmethode false, löst die Setter-Methode eine Exception aus.
    //
    // Das Ungewöhnliche an dieser Funktion ist, dass der Eigenschaftswert, der von
    // den Getter- und Setter-Methoden manipuliert wird, nicht mit dem Objekt o gespeichert
    // wird. Stattdessen wird der Wert nur in einer lokalen Variable der Funktion
    // gespeichert. Die Getter- und Setter-Methoden sind auch lokal in dieser Funktion
    // definiert und haben deshalb Zugriff auf diese lokale Variable. Beachten Sie,
    // dass der Wert privat für die beiden Zugriffsmethoden ist und nur über
    // die Setter-Methode gesetzt oder verändert werden kann.
    function macheEigenschaft(o, name, praedikat) {
      var wert; // Das ist der Eigenschaftswert

      // Die Getter-Methode liefert einfach den Wert zurück.
      o["get" + name] = function( ) { return wert; };

      // Die Setter-Methode speichert den Wert oder löst eine Exception aus,
      // wenn ihre Prüfmethode den Wert verwirft.
      o["set" + name] = function(v) {
        if (praedikat && !praedikat(v))
          throw "set" + name + ": ungültiger Wert " + v;
        else
          wert = v;
      };
    }

    // Der folgende Code zeigt die Verwendung der Methode macheEigenschaft( ).
    var o = {}; // Hier ist ein leeres Objekt

    // Die Eigenschaftszugriffsmethoden getName und setName( ) erzeugen und
    // sicherstellen, dass nur Strings erlaubt sind.
    macheEigenschaft(o, "Name", function(x) { return typeof x == "string"; });

    o.setName("Frank");   // Den Eigenschaftswert setzen
    print(o.getName( ));  // Den Eigenschaftswert abrufen
    o.setName(0);      // Versuchen, einen Wert des falschen Typs zu setzen

Das nützlichste und am wenigsten künstliche Beispiel für die Verwendung von Closures, das mir bekannt ist, ist die Breakpoint-Einrichtung, die Steve Yen geschrieben und unter <http://trimpath.com> als Teil des TrimPath Client-Frameworks veröffentlicht hat. Ein _Breakpoint_ ist ein Punkt in einer Funktion, bei dem die Ausführung des Codes angehalten wird und der Programmierer eine Gelegenheit erhält, die Werte von Variablen zu prüfen, Ausdrücke auszuwerten, Funktionen aufzurufen und so weiter. Steves Breakpoint-Technik nutzt eine Closure, um den aktuellen Geltungsbereich in einer Funktion (einschließlich lokalen Variablen und Funktionsargumenten) festzuhalten, und kombiniert das mit der globalen `eval( )`-Funktion, um eine Inspektion des Geltungsbereichs zu ermöglichen. `eval( )` wertet einen String mit JavaScript-Code aus und liefert sein Ergebnis zurück (mehr darüber können Sie in Teil III lesen). Hier ist eine eingebettete Funktion, die als eine sich selbst inspizierende Closure funktioniert:

    // Den aktuellen Geltungsbereich einfangen und es ermöglichen, dass er mit eval( )
    // inspiziert wird
    var inspektor = function($) { return eval($); }

Diese Funktion nutzt als Argumentnamen den ungewöhnlichen Bezeichner `$`, um die Wahrscheinlichkeit zu verringern, dass es zu Namenskonflikten mit dem Geltungsbereich kommt, der inspiziert werden soll.

Sie können in einer Funktion einen Breakpoint erzeugen, indem Sie diese Closure an eine Funktion wie die in Beispiel 8-7 übergeben.

Beispiel 8-7. Breakpoints unter Verwendung von Closures

    // Diese Funktion implementiert einen Breakpoint. Sie fordert den Benutzer wiederholt
    // zur Eingabe eines Ausdrucks aus und wertet diesen mit der angegebenen sich selbst
    // inspizierenden Closure aus und zeigt das Ergebnis an. Es ist die Closure, die
    // Zugriff auf den zu inspizierenden Geltungsbereich liefert. Jede Funktion muss
    // also ihre eigene Closure anbieten.
    //
    // Inspiriert von Steve Yens breakpoint( )-Funktion unter
    // http://trimpath.com/project/wiki/TrimBreakpoint
    function inspizieren(inspektor, titel) {
      var ausdruck, ergebnis;

      // Sie können einen Breakpoint einsetzen, um nachfolgende Breakpoints abzuschalten,
      // indem Sie auf dieser Funktion eine Eigenschaft namens "ignorieren" erzeugen.
      if ("ignorieren" in arguments.callee) return;

      while(true) {
        // Ermitteln, wie die Eingabeaufforderung für den Benutzer erfolgen soll
        var meldung = "";
        // Wenn ein Titel angegeben wurde, diesen erst anzeigen
        if (titel) meldung = titel + "\n";
        // Wurde bereits ein Ausdruck ausgewertet, diesen und seine Werte anzeigen
        if (ausdruck) meldung += "\n" + ausdruck + " ==> " + ergebnis + "\n";
        else ausdruck = "";
        // Es wird immer mindestens eine elementare Eingabeaufforderung angezeigt:
        meldung += "Geben Sie einen auszuwertenden Ausdruck ein:";

        // Die Benutzereingabe einlesen, unsere Eingabeaufforderung anzeigen
        // und diesmal den letzten Ausdruck als Default-Wert verwenden.
        ausdruck = prompt(meldung, ausdruck);

        // Wenn der Benutzer nichts eingegeben (oder auf Abbrechen geklickt) hat,
        // sind wir fertig und kehren zurück und beenden den Breakpoint.
        if (!ausdruck) return;

        // Andernfalls wird die angegebene Closure verwendet, um den Ausdruck
        // im zu inspizierenden Geltungsbereich auszuwerten.
        // Das Ergebnis wird beim nächsten Durchlauf angezeigt.
        ergebnis = inspektor(ausdruck);
      }
    }

Beachten Sie, dass die `inspizieren( )`-Funktion von Beispiel 8-7 die Methode `Window. prompt( )` einsetzt, um den Text für den Benutzer anzuzeigen und ihm die Eingabe eines Strings zu ermöglichen (mehr Informationen finden Sie bei `Window.prompt( )` in Teil IV).

Hier ist eine Funktion zur Berechnung der Fakultät, die diese Breakpoint-Technik verwendet:

    function fakultaet(n) {
      // Eine Closure für diese Funktion erzeugen
      var inspektor = function($) { return eval($); }
      inspizieren(inspektor, "Eintritt in fakultaet( )");

      var ergebnis = 1;
      while(n > 1) {
        ergebnis = ergebnis * n;
        n--;
        inspizieren(inspektor, "fakultaet( )-Schleife");
      }

      inspizieren(inspektor, "Verlassen von fakultaet( )");
      return ergebnis;
    }

### 8.8.4.2 Closures und Speicherlöcher im Internet Explorer

Microsofts Webbrowser Internet Explorer nutzt eine schwache Form der Garbage Collection für ActiveX-Objekte und clientseitige DOM-Elemente. Für diese clientseitigen Objekte werden Referenzen gezählt. Die Objekte werden freigegeben, wenn ihr Referenzzähler null erreicht. Dieses Schema schlägt fehl, wenn es zirkuläre Referenzen gibt, beispielsweise wenn ein Kern-JavaScript-Objekt ein Dokumentelement referenziert und dieses Dokumentelement eine Eigenschaft (wie einen Event-Handler) hat, der wieder das Kern-JavaScript-Objekt referenziert.

Diese Art zirkulärer Referenz tritt häufig auf, wenn bei der clientseitigen Programmierung im IE Closures verwendet werden. Wenn Sie ein Closure verwenden, denken Sie daran, dass das Aufrufobjekt der einschließenden Funktion einschließlich aller Funktionsargumente und lokalen Variablen so lange bestehen bleibt wie die Closure. Wenn eines dieser Funktionsargumente oder eine der lokalen Variablen ein clientseitiges Objekt referenziert, kann das zu einem Speicherloch führen.

Eine vollständige Diskussion dieses Problems geht über den Horizont dieses Buchs hinaus. Ausführliche Informationen finden Sie unter <http://msdn.microsoft.com/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp>.

* * *

[31] Dieser Abschnitt behandelt fortgeschrittene Themen, die Sie beim ersten Lesen vielleicht überspringen sollten.

# 8.9 Der Function( )-Konstruktor

Wie zuvor bereits erklärt wurde, werden Funktionen in der Regel mit Hilfe des Schlüsselworts `function` erzeugt, entweder in Form einer Funktionsdefinitionsanweisung oder eines Funktionsliteralausdrucks. Funktionen können aber auch mit dem `Function( )`-Konstruktor definiert werden. Die Verwendung des `Function( )`-Konstruktors ist meist schwerer als die Verwendung eines Funktionsliterals, deswegen wird diese Technik nicht häufig verwendet. Hier ist ein Beispiel, das eine Funktion mit dem `Function( )`-Konstruktor erzeugt:

    var f = new Function("x", "y", "return x*y;");

Diese Codezeilen erzeugen eine neue Funktion, die mehr oder weniger mit einer Funktion äquivalent ist, die mit dieser vertrauten Syntax definiert wird:

    function f(x, y) { return x*y; }

Der `Function( )`-Konstruktor erwartet eine beliebige Anzahl von Stringargumenten. Das letzte Argument ist der Body der Funktion. Es kann beliebige JavaScript-Anweisungen enthalten, die voneinander durch Semikolons abgegrenzt werden. Alle anderen Argumente für den Konstruktor sind Strings, die die Namen für die Parameter der Funktion angeben, die definiert wird. Wenn Sie eine Funktion definieren, die keine Argumente erwartet, übergeben Sie nur einen einzigen String – den Funktionsrumpf – an den Konstruktor.

Beachten Sie, dass dem `Function( )`-Konstruktor kein Argument übergeben wird, das einen Namen für die Funktion angibt, die er erzeugt. Wie Funktionsliterale erzeugt der `Function( )`-Konstruktor anonyme Funktionen.

Es gibt einige Dinge, die man über den `Function( )`-Konstruktor wissen muss:

  * Der `Function( )`-Konstruktor ermöglicht es, JavaScript dynamisch zur Laufzeit zu erstellen und zu kompilieren. Er ist in dieser Hinsicht wie die globale Funktion `eval( )` (siehe Teil III).

  * Jedes Mal, wenn er aufgerufen wird, parst der `Function( )`-Konstruktor den Funktionsrumpf und erzeugt ein neues Funktionsobjekt. Wenn der Aufruf an den Konstruktor in einer Schleife oder einer Funktion steht, die häufig aufgerufen wird, kann dieser Vorgang ineffizient sein. Im Unterschied dazu wird ein Funktionsliteral oder eine eingebettete Funktion, die in einer Schleife oder Funktion steht, nicht jedes Mal neu kompiliert, wenn sie angetroffen wird. Und es wird auch nicht jedes Mal ein neues Funktionsobjekt erzeugt, wenn das Funktionsliteral angetroffen wird. (Obwohl, wie bereits gesagt, eine neue Closure erforderlich sein kann, um Unterschiede in dem lexikalischen Geltungsbereich festzuhalten, in dem die Funktion definiert wird.)

  * Eine letzte und sehr wichtige Eigenschaft des `Function( )`-Konstruktors ist, dass die Funktionen, die er erzeugt, keine lexikalische Geltung verwenden. Stattdessen werden sie immer kompiliert, als wären sie Funktionen der obersten Ebene, wie der folgende Code beweist:

        var y = "global";
    function konstruiereFunktion( ) {
      var y = "lokal";
      return new Function("return y"); // Fängt den lokalen Geltungsbereich nicht ein!
    }
    // Diese Zeile zeigt "global" an, weil die Funktion, die vom
    // Function( )-Konstruktor zurückgeliefert wird, nicht den lokalen
    // Geltungsbereich verwendet. Wäre stattdessen ein Funktionsliteral
    // verwendet worden, würde die Zeile "lokal" anzeigen.
    alert(konstruiereFunktion( )( )); // Zeigt "global" an

# Kapitel 9. Klassen, Konstruktoren und Prototypen

JavaScript-Objekte wurden in Kapitel 7 eingeführt. Jenes Kapitel hat jedes Objekt als einen einzigartigen Satz von Eigenschaften betrachtet, der von allen anderen Objekten verschieden ist. In vielen objektorientierten Programmiersprachen kann man eine _Klasse_ von Objekten und dann einzelne Objekte erzeugen, die _Instanzen_ dieser Klasse sind. Sie könnten beispielsweise eine Klasse namens Komplex definieren, um arithmetische Operationen auf komplexen Zahlen zu repräsentiern und durchzuführen. Ein Komplex-Objekt würde eine einzelne komplexe Zahl repräsentieren und wäre eine Instanz dieser Klasse.

JavaScript unterätzt keine echten Klassen auf die Weise, wie es Sprachen wie Java, C++ und C# machen. [32] Aber es ist trotzdem möglich, in JavaScript Pseudoklassen zu definieren. Die Werkzeuge dafür sind Konstruktorfunktionen und Prototyp-Objekte. Dieses Kapitel erklärt Konstruktoren und Prototypen und schließt Beispiele für eine Reihe von JavaScript-Pseudoklassen und sogar JavaScript-Pseudounterklassen ein.

In Ermangelung eines besseren Begriffs gestatte ich mir in diesem Kapitel eine lockere Verwendung des Worts »Klasse«. Sie sollten allerdings aufpassen, dass Sie diese informellen Klassen nicht mit den echten Klassen von JavaScript 2 und anderen Sprachen verwechseln.

# 9.1 Konstruktoren

Kapitel 7 hat Ihnen gezeigt, wie Sie ein neues leeres Objekt entweder mit dem Objekt-Literal `{}` oder mit folgendem Ausdruck erzeugen:

    new Object( )

Sie haben auch gesehen, wie andere Arten von JavaScript-Objekten mit dieser ähnlichen Syntax erzeugt wurden:

    var array = new Array(10);
    var heute = new Date( );

Auf den `new`-Operator muss ein Funktionsaufruf folgen. Er erzeugt ein neues Objekt ohne Eigenschaften und ruft dann die Funktion auf und übergibt ihr das neue Objekt als Wert des Schlüsselworts `this`. Eine Funktion, die für die Verwendung mit dem `new`-Operator gedacht ist, wird als _Konstruktorfunktion_ oder einfach als Konstruktor bezeichnet. Die Aufgabe eines Konstruktors ist es, ein neu erzeugtes Objekt zu initialisieren, indem er alle Eigenschaften setzt, die gesetzt werden müssen, bevor das Objekt verwendet werden kann. Sie können Ihre eigene Konstruktorfunktion schreiben, indem Sie einfach eine Funktion schreiben, die `this` Eigenschaften hinzufügt. Der folgende Code definiert einen Konstruktor und ruft ihn dann zweimal mit dem `new`-Operator auf, um zwei neue Objekte zu erzeugen:

    // Den Konstruktor definieren.
    // Beachten Sie, wie er das Objekt initialsiert, auf das "this" verweist.
    function Rechteck(w, h) {
      this.breite = w;
      this.hoehe = h;
      // HINWEIS: Hier gibt es keine return-Anweisung
    }

    // Den Konstruktor aufrufen, um zwei Rechteck-Objekte zu erzeugen.
    // Wir übergeben dem Konstruktor die Höhe und die Breite, damit er
    // jedes neue Objekt wie erforderlich initialisieren kann.
    var r1 = new Rechteck(2, 4);  // r1 = { breite:2, hoehe:4 };
    var r2 = new Rechteck(8.5, 11); // r2 = { breite:8.5, hoehe:11 };

Beachten Sie, wie der Konstruktor seine Argumente verwendet, um die Eigenschaften des Objekts zu initialisieren, auf das das Schlüsselwort `this` verweist. Sie haben eine Klasse von Objekten definiert, einfach indem Sie eine geeignete Konstruktorfunktion definiert haben. Für alle Objekte, die mit dem `Rechteck( )`-Konstruktor erzeugt werden, ist jetzt gesichert, dass sie initialisierte `breite`\- und `hoehe`-Eigenschaften haben. Das bedeutet, dass Sie Programme schreiben können, die sich auf diese Tatsache verlassen und alle Rechteck-Objekte gleich behandeln. Weil jeder Konstruktor eine Klasse von Objekten definiert, ist es in stilistischer Hinsicht wichtig, dass man einer Konstruktorfunktion einen Namen gibt, der die Klasse der Objekte anzeigt, die sie erzeugt. Ein Rechteck mit `new Rechteck(1,2)` zu erstellen ist beispielsweise erheblich intuitiver als mit `new init_rechteck(1,2)`.

Konstruktorfunktionen haben üblicherweise keinen Rückgabewert. Sie initialisieren das Objekt, das als Wert von `this` übergeben wurde, und liefern nichts zurück. Aber ein Konstruktor darf ein Objekt zurückliefern, und wenn er das macht, wird das zurückgelieferte Objekt zum Wert des `new`-Ausdrucks. In diesem Fall wird das Objekt, das der Wert von `this` war, einfach verworfen.

* * *

[32] Echte Klassen sind allerdings für JavaScript 2.0 geplant.

# 9.2 Prototypen und Vererbung

In Kapitel 8 wurde bereits gesagt, dass eine _Methode_ eine Funktion ist, die als Eigenschaft eines Objekts aufgerufen wird. Wenn eine Funktion auf diese Weise aufgerufen wird, wird das Objekt, über das auf sie zugegriffen wird, zum Wert des Schlüsselworts `this`. Nehmen Sie an, Sie möchten die Fläche des Rechtecks berechnen, das von einem Rechteck-Objekt repräsentiert wird. Hier ist die einzige Möglichkeit, das zu tun:

    function berechneRechteckfläche(r) { return r.breite * r.hoehe; }

Das funktioniert, ist aber nicht objektorientiert. Wenn man Objekte verwendet, ist es besser, eine Methode auf dem Objekt aufzurufen, als das Objekt einer Funktion zu übergeben. Das machen Sie so:

    // Ein neues Rechteck-Objekt erzeugen.
    var r = new Rechteck(8.5, 11);
    // Ihm eine Methode hinzufügen.
    r.flaeche = function( ) { return this.breite * this.hoehe; }
    // Jetzt die Methode zur Flächenberechnung aufrufen.
    var a = r.flaeche( );

Natürlich ist es blöd, wenn man einem Objekt erst eine Methode hinzufügen muss, bevor man sie aufrufen kann. Sie können die Situation verbessern, indem Sie die Eigenschaft `flaeche` so initialisieren, dass sie im Konstruktor auf die Funktion zur Flächenberechnung verweist. Hier ist der verbesserte `Rechteck( )`-Konstruktor:

    function Rechteck(w, h) {
      this.breite = w;
      this.hoehe = h;
      this.flaeche = function( ) { return this.breite * this.hoehe; }
    }

Bei dieser neuen Version des Konstruktors können Sie Code wie diesen schreiben:

    // Wie groß ist ein DIN A4-Blatt in Quadratzentimetern?
    var r = new Rechteck(21, 29.7);
    var a = r.flaeche( );

Diese Lösung funktioniert besser, ist aber noch nicht optimal. Jedes erzeugte Rechteck hat jetzt drei Eigenschaften. Die Eigenschaften `breite` und `hoehe` können sich bei allen Rechtecken unterscheiden, aber die `flaeche`-Eigenschaft jedes Rechteck-Objekts verweist immer auf die gleiche Funktion (natürlich könnte jemand diese ändern, aber in der Regel wollen Sie, dass die Methoden eines Objekts konstant bleiben). Es ist ineffizient, wenn man gewöhnliche Eigenschaften für Methoden verwendet, die von allen Objekten einer Klasse (d.h., allen Objekten, die mit dem gleichen Konstruktor erzeugt wurden) geteilt werden sollen.

Aber es gibt eine Lösung. Es ist so, dass jedes JavaScript-Objekt eine interne Referenz auf ein anderes Objekt enthält, das als Prototyp-Objekt bezeichnet wird. Alle Eigenschaften des Prototyps scheinen Eigenschaften eines Objekts zu sein, für das er der Prototyp ist. Man kann auch sagen, dass ein JavaScript-Objekt Eigenschaften von seinem Prototyp _erbt_.

Im vorangehenden Abschnitt habe ich gezeigt, dass der `new`-Operator ein neues leeres Objekt erzeugt und dann eine Konstruktorfunktion als Methode dieses Objekts aufruft. Das ist allerdings noch nicht die ganze Geschichte. Nachdem das leere Objekt erzeugt worden ist, setzt `new` den Prototyp dieses Objekts. Der Prototype eines Objekts ist der Wert der `prototype`-Eigenschaft seiner Konstruktorfunktion. Alle Funktionen haben eine `prototype`-Eigenschaft, die automatisch erzeugt und initialisiert wird, wenn die Funktion definiert wird. Der Anfangswert der `prototype`-Eigenschaft ist ein Objekt mit einer einzigen Eigenschaft. Diese Eigenschaft heißt `constructor` und zeigt auf die Konstruktorfunktion zurück, mit der der Prototyp verknüpft ist. (Vielleicht erinnern Sie sich an die `constructor`-Eigenschaft aus Kapitel 7. Das ist der Grund dafür, dass jedes Objekt eine `constructor`-Eigenschaft hat.) Alle Eigenschaften, die Sie diesem Prototyp-Objekt hinzufügen, werden als Eigenschaften der Objekte erscheinen, die vom Konstruktor initialisiert werden.

Das lässt sich mit einem Beispiel klarer machen. Hier ist wieder der `Rechteck( )`-Konstruktor:

    // Die Konstruktorfunktion initialisiert die Eigenschaften, die
    // bei den einzelnen Instanzen jeweils anders sein werden.
    function Rechteck(w, h) {
      this.breite = w;
      this.hoehe = h;
    }

    // Das Prototyp-Objekt enthält Methoden und andere Eigenschaften,
    // die sich die einzelnen Instanzen teilen sollen.
    Rechteck.prototype.flaeche = function( ) { return this.breite * this.hoehe; }

Ein Konstruktor stellt einen Namen für eine »Klasse« von Objekten und initialisiert Eigenschaften wie `breite` und `hoehe`, die bei jeder Instanz der Klasse anders sein können. Das Prototyp-Objekt ist mit dem Konstruktor verknüpft, und jedes Objekt, das durch den Konstruktor initialisiert wird, erbt genau den gleichen Satz von Eigenschaften vom Prototyp. Das bedeutet, dass das Prototyp-Objekt der ideale Platz für Methoden und andere konstante Eigenschaften ist.

Beachten Sie, dass Vererbung automatisch als Teil des Vorgangs der Suche nach einem Eigenschaftswert eintritt. Eigenschaften werden _nicht_ vom Prototyp-Objekt in neue Objekte kopiert. Das hat zwei wichtige Folgen. Erstens kann die Verwendung von Prototyp-Objekten den Speicherplatz, den die einzelnen Objekte brauchen, dramatisch reduzieren, weil die Objekte viele ihrer Eigenschaften erben können. Die zweite Folge ist, dass ein Objekt Eigenschaften auch dann erbt, wenn sie seinem Prototyp erst hinzugefügt werden, _nachdem_ es selbst erzeugt wurde. Das bedeutet, dass es möglich (aber deswegen trotzdem nicht unbedingt ein gute Idee) ist, existierenden Klassen neue Methoden hinzuzufügen.

Geerbte Eigenschaften verhalten sich genau wie die gewöhnlichen Eigenschaften eines Objekts. Sie werden von der `for/in`-Schleife aufgezählt und können mit dem `in`-Operator geprüft werden. Sie können Sie nur mit der Methode `Object.hasOwnProperty( )` unterscheiden:

    var r = new Rechteck(2, 3);
    r.hasOwnProperty("breite");   // true: breite ist eine unmittelbare Eigenschaft von r
    r.hasOwnProperty("flaeche");  // false: flaeche ist eine geerbte Eigenschaft von r
    "flaeche" in r;         // true: "flaeche" ist eine Eigenschaft von r

## 9.2.1 Geerbte Eigenschaften lesen und schreiben

Jede Klasse hat ein Prototyp-Objekt mit einem Satz von Eigenschaften. Aber es kann viele Instanzen einer Klasse geben, die jeweils diese Prototyp-Eigenschaften erben. Weil eine Prototyp-Eigenschaft von vielen Objekten geerbt werden kann, muss JavaScript eine fundamentale Asymmetrie zwischen dem Lesen und Schreiben von Eigenschaftswerten erzwingen. Wenn Sie die Eigenschaft `e` eines Objekts `o` lesen, prüft JavaScript zunächst, ob `o` eine Eigenschaft namens `e` hat. Hat es sie nicht, prüft es, ob das Prototyp-Objekt von `o` eine Eigenschaft namens `e` hat. Das ist es, was dafür sorgt, dass die prototypbasierte Vererbung funktioniert.

Aber wenn Sie den Wert einer Eigenschaft schreiben, verwendet JavaScript das Prototyp-Objekt nicht. Um zu verstehen warum, überlegen Sie, was passieren würde, wäre es der Fall: Nehmen wir an, Sie versuchen, den Wert der Eigenschaft `o.e` zu setzen, wenn das Objekt `o` keine Eigenschaft namens `e` hat. Nehmen wir weiter an, dass JavaScript weitergeht und die Eigenschaft `e` im Prototyp-Objekt von `o` sucht und es Ihnen gestattet, die Eigenschaft des Prototyps zu setzen. Jetzt haben Sie den Wert von `e` für eine ganze Klasse von Objekten geändert – und das ist ganz und gar nicht das, was Sie beabsichtigt haben.

Deswegen findet die Eigenschaftsvererbung nur statt, wenn Sie Eigenschaftswerte lesen, und nicht, wenn Sie sie schreiben. Wenn Sie die Eigenschaft `e` eines Objekts `o` setzen, das diese Eigenschaft von seinem Prototyp erbt, erzeugen Sie stattdessen eine neue Eigenschaft `e` direkt in `o`. Jetzt, wo `o` eine Eigenschaft namens `e` hat, erbt es den Wert von `e` nicht mehr von seinem Prototyp. Wenn Sie den Wert von `e` lesen, sieht sich JavaScript zuerst die Eigenschaften von `o` an. Da es feststellt, dass `e` in `o` definiert ist, muss es sich das Prototyp-Objekt nicht mehr ansehen und wird den dort definierten Wert von `e` nie finden. Manchmal sagen wir, dass die Eigenschaft `e` in `o` die Eigenschaft `e` im Prototyp-Objekt `»` verdeckt« oder »verbirgt«. Die Prototyp-Vererbung kann ein verwirrendes Thema sein. Abbildung 9-1 illustriert die hier erörterten Konzepte.

Weil Prototyp-Eigenschaften von allen Objekten einer Klasse geteilt werden, ist es in der Regel sinnvoll, sie nur für Eigenschaften zu verwenden, die bei allen Objekten der Klasse gleich sind. Das macht Prototypen ideal für die Definition von Methoden. Andere Eigenschaften mit konstanten Werten (wie mathematische Konstanten) sind ebenfalls für eine Definition über Prototyp-Eigenschaften geeignet. Wenn Ihre Klasse eine Eigenschaft mit einem sehr häufig verwendeten Default-Wert nutzt, könnten Sie diese Eigenschaft und ihren Default-Wert im Prototyp-Objekt definieren. Die paar Objekte, die vom Default-Wert abweichen wollen, können dann ihre eigenen privaten, nicht geteilten Kopien der Eigenschaft erstellen und ihre vom Default abweichenden Werte definieren.

Abbildung 9-1. Objekte und Prototypen

## 9.2.2 Eingebaute Typen erweitern

Nicht nur benutzerdefinierte Klassen haben Prototyp-Objekte. Auch einbaute Klassen wie String und Date haben Prototyp-Objekte, denen Sie Werte zuweisen können. Der folgende Code definiert beispielsweise eine neue Methode, die für alle String-Objekte verfügbar ist:

    // Liefert true, wenn das letzte Zeichen ein c ist
    String.prototype.endsWith = function(c) {
      return (c == this.charAt(this.length-1))
    }

Nachdem Sie im Prototyp-Objekt von String die neue Methode `endsWith( )` definiert haben, können Sie sie so verwenden:

    var nachricht = "Hallo Welt";
    nachricht.endsWith('H') // Liefert false
    nachricht.endsWith('t') // Liefert true

Es gibt ein starkes Argument gegen die Erweiterung eingebauter Typen durch eigene Methoden. Wenn Sie das machen, erstellen Sie im Grunde Ihre eigene Version der JavaScript-Kern-API. Jeder andere Programmierer, der Ihren Code lesen oder warten muss, wird es wahrscheinlich als verwirrend empfinden, dass Ihr Code Methoden enthält, von denen er noch nie gehört hat. Wenn Sie nicht gerade ein Lowlevel-JavaScript-Framework schaffen, von dem Sie erwarten, dass es von vielen Programmierern übernommen wird, ist es wahrscheinlich am besten, Sie lassen die Finger von den Prototyp-Objekten der eingebauten Typen.

Beachten Sie, dass Sie `Object.prototype` _nie_ Eigenschaften hinzufügen dürfen. Alle Eigenschaften oder Methoden, die Sie hinzufügen, können von einer `for/in`-Schleife aufgezählt werden, und wenn Sie sie `Object.prototype` hinzufügen, machen Sie sie in jedem einzelnen JavaScript-Objekt sichtbar. Von einem leeren Objekt, `{}`, wird erwartet, dass es keine aufzählbaren Eigenschaften hat. Alles, was `Object.prototype` hinzugefügt wird, wird zu einer aufzählbaren Eigenschaft des leeren Objekts und wird wahrscheinlich Code zerstören, der Objekte als assoziative Arrays verwendet.

Dass die hier gezeigte Technik für die Erweiterung der eingebauten Objekttypen funktioniert, ist nur für die »nativen Objekte« von Kern-JavaScript garantiert. Wenn JavaScript in einen Kontext wie einen Webbrowser oder eine Java-Anwendung eingebettet ist, hat es Zugriff auf zusätzliche »Host-Objekte« wie die Objekte, die den Dokumentinhalt des Webbrowsers repräsentieren. Diese Host-Objekte haben häufig keine Konstruktoren und Prototyp-Objekte und können deswegen normalerweise nicht erweitert werden.

Es gibt einen Fall, in dem es sicher und nützlich ist, den Prototyp einer eingebauten nativen Klasse zu erweitern: Es können einem Prototyp Standardmethoden hinzugefügt werden, wenn sie einer älteren oder inkompatiblen JavaScript-Implementierung fehlen. Beispielsweise fehlt in Microsoft Internet Explorer 4 und 5 die Methode `Function.apply( )`. Das ist eine ziemlich wichtige Funktion, und Sie stoßen vielleicht auf Code wie diesen, um sie zu ersetzen:

    // IE 4 & 5 implementieren Function.apply( ) nicht.
    // Dieser Workaround basiert auf Code von Aaron Boodman.
    if (!Function.prototype.apply) {
      // Diese Funktion als Methode des angegebenen Objekts aufrufen und
      // dabei die angegebenen Parameter übergeben. Dazu müssen wir ( ) verwenden.
      Function.prototype.apply = function(object, parameter) {
        var f = this;        // Die aufzurufende Funktion
        var o = object || window;  // Das Objekt, auf dem sie aufgerufen wird
        var args = parameter || []; // Die zu übergebenden Argumente

        // Diese Funktion temporär zu einer Methode von o machen.
        // Dazu verwenden wir einen Eigenschaftsnamen, bei dem es
        // unwahrscheinlich ist, dass er schon existiert.
        o._$_apply_$_ = f;

        // Wir werden eval( ) einsetzen, um die Methode aufzurufen. Dazu
        // müssen wir den Aufruf als String schreiben. Erst bauen wir
        // die Argumentliste auf.
        var stringArgs = [];
        for(var i = 0; i < args.length; i++)
          stringArgs[i] = "args[" + i + "]";

        // Die Argument-Strings zu einer kommaseparierten Liste verketten.
        var argliste = stringArgs.join(",");

        // Jetzt den vollständigen String für den Methodenaufruf aufbauen.
        var methodenaufruf = "o._$_apply_$_(" + argliste + ");";

        // Die Funktion eval( ) verwenden, um den Methodenaufruf durchzuführen.
        var ergebnis = eval(methodenaufruf);

        // Die Funktion aus dem Objekt löschen.
        delete o._$_apply_$_;

        // Und das Ergebnis zurückliefern.
        return ergebnis;
      };
    }

Als weiteres Beispiel können die neuen Array-Methoden dienen, die in Firefox 1.5 implementiert sind (siehe "7.6.10 Array-Extras"). Wenn Sie die neue Methode `Array.map( )` einsetzen möchten, Ihr Code aber auch auf Plattformen funktionieren soll, die diese Methode nativ nicht unterstützen, können Sie zu Kompatibilitätszwecken diesen Code verwenden:

    // Array.map( ) ruft eine Funktion f auf jedem Element des Arrays auf
    // und liefert ein neues Array mit den Werten zurück, die aus den
    // Funktionsaufrufen resultieren. Wenn map( ) mit zwei Argumenten
    // aufgerufen wird, wird die Funktion f als Methode des zweiten
    // Arguments aufgerufen. Beim Aufruf werden f( ) drei Argumente
    // übergeben. Das erste ist der Wert des Array-Elements. Das zweite
    // ist der Index des Array-Elements, und das dritte ist das Array
    // selbst. In den meisten Fällen muss nur das erste Argument verwendet werden.
    if (!Array.prototype.map) {
      Array.prototype.map = function(f, thisObjekt) {
        var ergebnisse = [];
        for(var l = this.length, i = 0; i < l; i++) {
          ergebnisse.push(f.call(thisObjekt, this[i], i, this));
        }
        return ergebnisse;
      }
    }

# 9.3 Klassen in JavaScript simulieren

Obwohl JavaScript einen Datentyp unterstützt, der als Objekt bezeichnet wird, kennt es keinen formellen Klassenbegriff. Das sorgt dafür, dass es sich deutlich von klassischen objektorientierten Sprachen wie C++ und Java unterscheidet. Das Grundkonzept objektorientierter Programmiersprachen ist, dass sie streng typisiert sind und klassenbasierte Vererbung unterstützen. Legt man diese Kriterien zugrunde, könnte man JavaScript leicht damit abtun, dass es keine echte objektorientierte Programmiersprache ist. Andererseits haben Sie aber gesehen, dass JavaScript sich stark auf Objekte stützt und einen eigenen Typ prototypbasierter Vererbung besitzt. JavaScript ist eine echte objektorientierte Sprache. Es holt sich Anregungen aus eine Reihe von anderen (ziemlich obskuren) objektorientierten Sprachen, die prototypbasierte Vererbung statt klassenbasierter Vererbung verwenden.

Obwohl JavaScript keine klassenbasierte objektorientierte Sprache ist, leistet es gute Arbeit bei der Simulation von Funktionalitäten klassenbasierter Sprachen wie Java und C++. Informell habe ich den Begriff _Klasse_ in diesem Kapitel bereits die ganze Zeit verwendet. In diesem Abschnitt sehen wir uns die Parallelen zwischen JavaScript und Sprachen mit echter klassenbasierter Vererbung wie Java und C++ auf formalerer Ebene an.[33]

Beginnen wir mit der Definition einiger Grundbegriffe. Ein _Objekt_ ist, wie Sie bereits gesehen haben, eine Datenstruktur, die verschiedene Stücke benannter Daten enthält und außerdem verschiedene Methoden enthalten kann, die auf diesen Datenstücken operieren. Ein Objekt gruppiert in Beziehung stehende Werte und Methoden zu einem einzigen praktischen Paket, das das Programmieren im Allgemeinen erleichtert, weil es die Modularität und die Wiederverwendbarkeit von Code steigert. In JavaScript haben Objekte eine beliebige Zahl von Eigenschaften, und Eigenschaften können Objekten dynamisch hinzugefügt werden. In streng typisierten Sprachen wie Java und C++ ist das nicht der Fall. In diesen Sprachen hat jedes Objekt eine vordefinierte Menge von Eigenschaften[34] und jede Eigenschaft einen festgelegten Typ. Wenn Sie JavaScript-Objekte verwenden, um Techniken der objektorientierten Programmierung zu simulieren, definieren Sie in der Regel im Voraus eine Menge von Eigenschaften für jedes Objekt und den Typ der Daten, die die einzelnen Eigenschaften enthalten.

In Java und C++ definiert eine _Klasse_ die Struktur eines Objekts. Die Klasse gibt genau an, welche Felder ein Objekt enthält und welche Typen von Daten jedes Feld aufnimmt. Sie definiert außerdem die Methoden, die auf dem Objekt operieren. JavaScript kennt keinen formellen Klassenbegriff, nähert sich, wie bereits gezeigt, mit seinen Konstruktoren und ihren Prototyp-Objekten aber Klassen an.

In JavaScript und in klassenbasierten objektorientierten Sprachen kann es mehrere Objekte einer Klasse geben. Wir sagen oft, dass ein Objekt eine _Instanz_ seiner Klasse ist. Es kann also viele Instanzen einer Klasse geben. Manchmal wird der Begriff _instanziieren_ eingesetzt, um den Prozess der Erstellung eines Objekts (d.h. der Instanz einer Klasse) zu beschreiben.

In Java ist es eine übliche Programmierkonvention, die Namen von Klassen mit einem Großbuchstaben zu beginnen und die Namen von Objekten mit einem Kleinbuchstaben. Diese Konvention hilft dabei, Klassen und Objekte im Code auseinanderzuhalten, und ihr zu folgen ist auch in der JavaScript-Programmierung hilfreich. In vorangehenden Abschnitten dieses Kapitels haben wir beispielsweise eine Rechteck-Klasse definiert und Instanzen dieser Klasse mit Namen wie `rechteck` erzeugt.

Die Member einer Java-Klasse können die folgenden Grundtypen haben: Instanzeigenschaften, Instanzmethoden, Klasseneigenschaften und Klassenmethoden. In den folgenden Abschnitten werden wir uns die Unterschiede zwischen diesen Typen ansehen und zeigen, wie sie in JavaScript simuliert werden.

## 9.3.1 Instanzeigenschaften

Jedes Objekt hat seine eigenen Kopien seiner _Instanzeigenschaften_. Anders gesagt: Wenn es 10 Objekte einer bestimmten Klasse gibt, gibt es 10 Kopien aller Instanzeigenschaften. Beispielsweise hat in unserer Rechteck-Klasse jedes Rechteck-Objekt eine `breite`-Eigenschaft, die die Breite des Rechtecks angibt. In diesem Fall ist `breite` eine Instanzeigenschaft. Da jedes Objekt seine eigene Kopie der Instanzeigenschaften hat, wird auf diese Eigenschaften über die einzelnen Objekte zugegriffen. Wenn `r` ein Objekt ist, das eine Instanz der Klasse Rechteck ist, wird auf seine Breite zum Beispiel so verwiesen:

    r.breite

Standardmäßig ist in JavaScript jede Objekteigenschaft eine Instanzeigenschaft. Zur Simulation traditioneller klassenbasierter objektorientierter Programmierung werden wir allerdings sagen, dass in JavaScript die Eigenschaften Instanzeigenschaften sind, die in der Konstruktorfunktion erzeugt und initialisiert werden.

## 9.3.2 Instanzmethoden

Eine _Instanzmethode_ ist einer Instanzeigenschaft sehr ähnlich – abgesehen davon, dass es ein Methodenwert und kein Datenwert ist. (In Java sind Funktionen und Methoden keine Daten wie in JavaScript. Deswegen ist diese Unterscheidung dort klarer.) Instanzmethoden werden auf einem bestimmten Objekt oder einer bestimmten Instanz aufgerufen. Die `flaeche( )`-Methode unserer Rechteck-Klasse ist eine Instanzmethode. Sie wird so auf einem Rechteck-Objekt `r` aufgerufen:

    f = r.flaeche( );

Die Implementierung einer Instanzmethode nutzt das Schlüsselwort `this`, um auf das Objekt oder die Instanz zu verweisen, auf dem bzw. der sie aufgerufen wurde. Eine Instanzmethode kann für jede Instanz einer Klasse aufgerufen werden, aber das heißt nicht, dass jedes Objekt seine eigene private Kopie der Methode enthält, wie das bei Eigenschaften der Fall ist. Stattdessen teilen sich alle Instanzen einer Klasse alle Instanzmethoden. In JavaScript wird eine Instanzmethode für eine Klasse definiert, indem eine Eigenschaft des Prototyp-Objekts des Konstruktors auf einen Funktionswert gesetzt wird. Auf diese Weise teilen alle Objekte, die von diesem Konstruktor erzeugt wurden, eine geerbte Referenz auf die Funktion und können sie mit der zuvor gezeigten Syntax zum Methodenaufruf aufrufen.

### 9.3.2.1 Instanzmethoden und this

Wenn Sie Java- oder C++-Programmierer sind, ist Ihnen vielleicht ein wichtiger Unterschied zwischen den Instanzmethoden in diesen Sprachen und den Instanzmethoden von JavaScript aufgefallen. In Java und C++ schließt der Geltungsbereich von Instanzmethoden das `this`-Objekt ein. In Java könnte der Body einer Methode zur Flächenberechnung beispielsweise einfach so aussehen:

    return breite * hoehe;

Aber Sie haben gesehen, dass Sie für diese Eigenschaften in JavaScript explizit das Schlüsselwort `this` angeben müssen:

    return this.breite * this.hoehe;

Wenn Sie es umständlich finden, dass Sie jedem Instanzfeld `this` voranstellen müssen, können Sie in jeder Ihrer Methoden die `with`-Anweisung verwenden (die in "6.18 with" behandelt wird). Zum Beispiel:

    Rechteck.prototype.flaeche = function( ) {
      with(this) {
        return breite*hoehe;
      }
    }

## 9.3.3 Klasseneigenschaften

In Java ist eine _Klasseneigenschaft_ eine Eigenschaft, die mit der Klasse selbst verknüpft ist statt mit den einzelnen Instanzen einer Klasse. Es gibt immer nur eine Kopie jeder Klasseneigenschaft, egal wie viele Instanzen einer Klasse erzeugt werden. Genau wie auf Instanzeigenschaften über eine Instanz einer Klasse zugegriffen wird, wird auf eine Klasseneigenschaft über die Klasse selbst zugegriffen. `Number.MAX_VALUE` ist ein Beispiel für eine Klasseneigenschaft in JavaScript: Auf die Eigenschaft `MAX_VALUE` wird über die Klasse Number zugegriffen. Weil es nur eine Kopie jeder Klasseneigenschaft gibt, sind Klasseneigenschaften dem Wesen nach global. Aber was so nett an ihnen ist, ist, dass sie mit einer Klasse verknüpft sind und eine logische Nische haben – einen Ort im JavaScript-Namensraum, an dem sie nicht so schnell durch andere Eigenschaften gleichen Namens überschrieben werden können. Wie wahrscheinlich klar ist, simulieren Sie eine Klasseneigenschaft in JavaScript, indem Sie einfach eine Eigenschaft der Konstruktorfunktion selbst definieren. Beispielsweise können Sie Folgendes tun, um eine Klasseneigenschaft `Rechteck.BASIS` zu erzeugen, die ein besonderes 1x1 Rechteck speichert:

    Rechteck.BASIS = new Rechteck(1,1);

`Rechteck` ist eine Konstruktorfunktion, aber weil JavaScript-Funktionen Objekte sind, können Sie für Funktionen genauso Eigenschaften definieren, wie Sie Eigenschaften für alle anderen Objekte definieren können.

## 9.3.4 Klassenmethoden

Eine _Klassenmethode_ ist mit einer Klasse verknüpft statt mit einer Instanz einer Klasse. Klassenmethoden werden über die Klasse selbst aufgerufen und nicht über eine bestimmte Instanz der Klasse. Die Methode `Date.parse( )` (die Sie in Teil III nachschlagen können) ist eine Klassenmethode. Sie rufen sie immer über das Date-Konstruktorobjekt anstatt über eine bestimmte Instanz der Klasse Date auf.

Weil Klassenmethoden durch die Konstruktorfunktion aufgerufen werden, verweist das Schlüsselwort `this` auf keine spezifische Instanz der Klasse. Stattdessen verweist es auf die Konstruktorfunktion selbst. (Üblicherweise verwendet eine Klassenmethode `this` überhaupt nicht.)

Wie alle Klasseneigenschaften sind Klassenmethoden global. Weil sie nicht auf einem bestimmten Objekt operieren, kann man sich Klassenmethoden in der Regel gut als Funktionen vorstellen, die zufällig über eine Klasse aufgerufen werden. Auch hier gilt, dass der Umstand, dass sie mit einer Klasse verknüpft sind, ihnen eine praktische Nische im JavaScript-Namensraum verschafft und Namensraumkollisionen verhindert. Um in JavaScript eine Klassenmethode zu definieren, machen Sie die entsprechende Funktion einfach zu einer Eigenschaft des Konstruktors.

## 9.3.5 Beispiel: Eine Kreis-Klasse

Der Code in Beispiel 9-1 bietet eine Konstruktorfunktion und ein Prototyp-Objekt für die Erstellung von Objekten, die Kreise repräsentieren. Er enthält Beispiele für Instanzeigenschaften, Instanzmethoden, Klasseneigenschaften und Klassenmethoden.

Beispiel 9-1. Ein Kreis-Klasse

    // Wir beginnen mit dem Konstruktor.
    function Kreis(radius) {
      // r ist eine Instanzeigenschaft, die im Konstruktor definiert und initialisiert wird.
      this.r = radius;
    }

    // Kreis.PI ist eine Klasseneigenschaft – es ist eine Eigenschaft der Konstruktorfunktion.
    Kreis.PI = 3.14159;

    // Hier ist eine Instanzmethode, die die Fläche eines Kreises berechnet.
    Kreis.prototype.flaeche = function( ) { return Kreis.PI * this.r * this.r; }

    // Diese Klassenmethode nimmt zwei Kreis-Objekte und liefert dasjenige mit
    // dem größeren Radius zurück.
    Kreis.max = function(a,b) {
      if (a.r > b.r) return a;
      else return b;
    }

    // Hier ist etwas Code, der all diese Felder verwendet:
    var k = new Kreis(1.0);    // Eine Instanz der Klasse Kreis erzeugen
    k.r = 2.2;           // Die Instanzeigenschaft r setzen
    var f = k.flaeche( );      // Die Instanzmethode flaeche( ) aufrufen
    var x = Math.exp(Kreis.PI);  // Die Klasseneigenschaft PI in einer Berechnung verwenden
    var l = new Kreis(1.2);    // Eine weitere Kreis-Instanz erzeugen
    var groesser = Kreis.max(k,l); // Die Klassenmethode max( ) verwenden

## 9.3.6 Beispiel: Komplexe Zahlen

Beispiel 9-2 ist ein weiteres Beispiel, das etwas formaler ist als das letzte und in JavaScript eine Klasse von Objekten definiert. Sie sollten sich den Code und die Kommentare sorgfältig ansehen.

Beispiel 9-2. Eine Klasse für komplexe Zahlen

    /*
     * Komplex.js:
     * Diese Datei definiert die Klasse Komplex zur Repräsentation komplexer Zahlen.
     * Eine komplexe Zahl ist die Summe des Realteils und
     * eines Imaginärteils, und die Imaginärzahl i ist die Quadratwurzel
     * von −1.
     */

    /*
     * Der erste Schritt bei der Definition einer Klasse ist die Definition der
     * Konstruktorfunktion der Klasse. Dieser Konstruktor sollte alle Instanzeigenschaften
     * des Objekts initialisieren. Das sind die wesentlichen "Zustandsvariablen",
     * die alle Instanzen der Klasse unterschiedlich machen.
     */
    function Komplex(real, imaginaer) {
      this.x = real;    // Der Realteil der Zahl
      this.y = imaginaer; // Der Imaginärteil der Zahl
    }

    /*
     * Der zweite Schritt bei der Definition einer Klasse ist die Definition
     * der Instanzmethoden (und eventueller anderer Eigenschaften im Prototyp-Objekt
     * des Konstruktors. Alle in diesem Objekt definierten Eigenschaften werden von
     * allen Instanzen der Klasse geerbt. Beachten Sie, dass Instanzmethoden auf dem
     * Schlüsselwort this operieren. Bei vielen Methoden sind keine anderen Argumente
     * erforderlich.
     */

    // Liefert den Betrag einer komplexen Zahl. Dieser ist als der Abstand zum
    // Ursprung (0,0) der Ebene der komplexen Zahlen definiert.
    Komplex.prototype.betrag = function( ) {
      return Math.sqrt(this.x*this.x + this.y*this.y);
    };

    // Liefert eine komplexe Zahl zurück, die die Negation dieser Zahl ist.
    Komplex.prototype.negation = function( ) {
      return new Komplex(-this.x, -this.y);
    };

    // Fügt dieser komplexen Zahl eine komplexe Zahl hinzu und liefert die Summe
    // in einem neuen Objekt zurück.
    Komplex.prototype.addiere = function(andere) {
      return new Komplex(this.x + andere.x, this.y + andere.y);
    }

    // Multipliziert diese komplexe Zahl mit einer anderen und liefert das Produkt
    // als neues Komplex-Objekt zurück.
    Komplex.prototype.mulipliziere = function(andere) {
      return new Komplex(this.x * andere.x – this.y * andere.y,
                this.x * andere.y + this.y * andere.x);
    }

    // Wandelt ein Komplex-Objekt auf nützliche Weise in einen String um.
    // Wird aufgerufen, wenn ein Komplex-Objekt als String verwendet wird.
    Komplex.prototype.toString = function( ) {
      return "{" + this.x + "," + this.y + "}";
    };

    // Prüft, ob dieses Komplex-Objekt den gleichen Wert hat wie ein anderes.
    Komplex.prototype.equals = function(andere) {
      return this.x == andere.x && this.y == andere.y;
    }

    // Liefert den Realteil einer komplexen Zahl zurück. Diese Funktion wird
    // aufgerufen, wenn ein Komplex-Objekt als elementarer Wert verwendet wird.
    Komplex.prototype.valueOf = function( ) { return this.x; }

    /*
     * Der dritte Schritt bei der Definition einer Klasse ist die Definition der
     * Klassenmethoden und -konstanten sowie aller erforderlichen Klasseneigenschaften
     * als Eigenschaften der Konstruktorfunktion selbst (statt als Eigenschaften
     * des Prototyp-Objekts des Konstruktors). Beachten Sie, dass Klassenmethoden
     * nicht das Schlüsselwort this verwenden: Sie operieren nur auf ihren Argumenten.
     */

    // Addiert zwei komplexe Zahlen und liefert das Ergebnis zurück.
    // Vergleichen Sie das mit der Instanzmethode addiere( )
    Komplex.summe = function (a, b) {
      return new Komplex(a.x + b.x, a.y + b.y);
    };

    // Multipliziert zwei komplexe Zahlen und liefert das Produkt zurück.
    // Vergleichen Sie das mit der Instanzmethode multipliziere( )
    Komplex.produkt = function(a, b) {
      return new Komplex(a.x * b.x – a.y * b.y,
                a.x * b.y + a.y * b.x);
    };

    // Hier sind einige nützliche vordefinierte komplexe Zahlen.
    // Sie sind als Klasseneigenschaften definiert, und ihre Namen sind in Großbuchstaben,
    // um anzuzeigen, dass es Konstanten sein sollen (auch wenn es nicht möglich ist,
    // JavaScript-Eigenschaften schreibgeschützt zu machen).
    Komplex.NULL = new Komplex(0,0);
    Komplex.EINS = new Komplex(1,0);
    Komplex.I = new Komplex(0,1);

## 9.3.7 Private Member

Ein gebräuchliches Feature traditioneller objektorientierter Sprachen wie Java und C++ ist, dass die Eigenschaften einer Klasse als privat deklariert werden können, damit nur die Methoden der Klasse selbst auf die Eigenschaften zugreifen können und sie von Code außerhalb der Klasse nicht manipuliert werden können. Eine verbreitete Programmiertechnik, die als _Datenkapselung_ bezeichnet wird, macht Eigenschaften privat und gestattet Lese/Schreibzugriff auf sie nur über besondere Zugriffsmethoden. In JavaScript können Sie das mit Closures (ein fortgeschrittenes Thema, das in "8.8 Funktionsgeltung und Closures" behandelt wurde) simulieren, aber dazu müssen die Zugriffsmethoden in jeder einzelnen Objektinstanz gespeichert werden. Sie können nicht vom Prototyp-Objekt gespeichert werden.

Der folgende Code zeigt, wie man das macht. Er implementiert ein unveränderbares Rechteck-Objekt, dessen Breite und Höhe nicht verändert werden können und auf die nur über Zugriffsmethoden zugegriffen werden kann:

    function UnveraenderbaresRechteck(b, h) {
      // Dieser Konstruktor speichert Breite und Höhe nicht in dem Objekt,
      // das er initialisiert. Stattdessen definiert er nur die Zugriffsmethoden
      // im Objekt. Diese Methoden sind Closures, und die Werte für Breite
      // und Höhe werden in ihren Geltungsbereichsketten gefangen.
      this.getBreite = function( ) { return b; }
      this.getHoehe = function( ) { return h; }
    }

    // Beachten Sie, dass die Klasse gewöhnliche Methoden im Prototyp-Objekt haben kann.
    UnveraenderbaresRechteck.prototype.flaeche = function( ) {
      return this.getBreite( ) * this.getHoehe( );
    };

Douglas Crockford wird im Allgemeinen zugestanden, dass er der Erste war, der diese Technik zur Definition privater Eigenschaften entdeckt (oder zumindest veröffentlicht) hat. Seine ursprüngliche Erörterung finden Sie unter <http://www.crockford.com/javascript/private.html>.

* * *

[33] Sie sollten diesen Abschnitt lesen, wenn Sie mit diesen Sprachen und diesem Stil der objektorientierten Programmierung nicht vertraut sind.

[34] In Java und C++ werden diese üblicherweise »Felder« genannt, aber ich werde sie hier als Eigenschaften bezeichnen, da das die JavaScript-Terminologie ist.

# 9.4 Allgemeine Objektmethoden

Wenn Sie eine neue JavaScript-Klasse definieren, gibt es ein paar Methoden, bei denen Sie immer überlegen sollten, ob Sie sie definieren sollten. Diese Methoden werden in den folgenden Unterabschnitten beschrieben.

## 9.4.1 Die Methode toString( )

Der Gedanke hinter `toString( )` ist, dass jede Klasse von Objekten eine bestimmte Stringdarstellung hat und deswegen eine geeignete `toString( )`-Methode für ihre Instanzen definieren sollte, um Objekte in diese Stringform zu konvertieren. Wenn Sie eine Klasse definieren, sollten Sie eine `toString( )`-Methode für sie definieren, damit Instanzen der Klasse in aussagekräftige Strings konvertiert werden können. Die Strings sollten Informationen über das Objekt enthalten, das konvertiert wird, weil das für Debugging-Zwecke nützlich ist. Wenn die Stringdarstellungen sorgfältig gewählt werden, können sie auch in Programmen selbst nützlich sein. Außerdem sollten Sie überlegen, Ihrer Klasse eine statische `parse( )`-Methode hinzuzufügen, um eine Stringausgabe von `toString( )` wieder in die Objektform zu parsen.

Die Komplex-Klasse aus Beispiel 9-2 schließt eine `toString( )`-Methode ein, und der folgende Code zeigt eine `toString( )`-Methode, die Sie für eine Kreis-Klasse definieren könnten:

    Kreis.prototype.toString = function ( ) {
      return "[Kreis mit dem Radius " + this.r + ", mit dem Mittelpunkt bei ("
        + this.x + ", " + this.y + ").]";
    }

Ist diese `toString( )`-Methode definiert, könnte ein typisches Kreis-Objekt in den String »[Kreis mit dem Radius 1, mit dem Mittelpunkt bei (0, 0).]« konvertiert werden.

## 9.4.2 Die Methode valueOf( )

Die Methode `valueOf( )` ist der Methode `toString( )` sehr ähnlich, wird aber aufgerufen, wenn JavaScript ein Objekt in einen elementaren Typ statt in einen String konvertieren muss – üblicherweise eine Zahl. Wenn möglich sollte die Methode einen elementaren Typ zurückliefern, der auf irgendeine Weise den Wert des Objekts repräsentiert, auf das das Schlüsselwort `this` verweist.

Per Definition sind Objekte keine elementaren Werte, deswegen haben die meisten Objekte kein elementares Äquivalent. Aus diesem Grund führt die von der Klasse Object definierte Default-`valueOf( )`-Methode keine Umwandlung durch und liefert einfach das Objekt zurück, auf dem sie aufgerufen wird. Klassen wie Number und Boolean haben offensichtliche elementare Äquivalente, überschreiben also die `valueOf( )`-Methode, um geeignete elementare Werte zurückzuliefern. Auf diese Weise können sich Number- und Boolean-Objekte ziemlich genau so verhalten wie ihre elementaren Äquivalente.

Gelegentlich kann es vorkommen, dass Sie eine Klasse definieren, die ein vernünftiges elementares Äquivalent hat. In diesem sollten Sie eventuell eine benutzerdefinierte `valueOf( )`-Methode für die Klasse definieren. In der Komplex-Klasse aus Beispiel 9-2 sehen Sie, dass eine `valueOf( )`-Methode definiert wurde, die den Realteil der komplexen Zahl zurückliefert. Wenn ein Komplex-Objekt in einem numerischen Kontext verwendet wird, verhält es sich also so, als wäre es eine reelle Zahl ohne imaginäre Komponente. Betrachten Sie beispielsweise den folgenden Code:

    var a = new Komplex(5,4);
    var b = new Komplex(2,1);
    var c = Komplex.summe(a,b); // c ist die komplexe Zahl {7,5}
    var d = a + b;        // d ist die Zahl 7

Ein Warnungshinweis zur Definition einer `valueOf( )`-Methode: Die `valueOf( )`-Methode kann unter bestimmten Umständen Vorrang vor der `toString( )`-Methode haben, wenn ein Objekt in einen String umgewandelt wird. Wenn Sie eine `valueOf( )`-Methode für eine Klasse definieren, müssen Sie in Bezug auf den Aufruf der Methode `toString( )` expliziter sein, wenn Sie ein Objekt der Klasse zwingen wollen, in einen String umgewandelt zu werden. Um mit dem Komplex-Beispiel fortzufahren:

    alert("c = " + c);       // Nutzt valueOf( ); Zeigt "c = 7" an
    alert("c = " + c.toString( ));  // Zeigt "c = {7,5}" an

## 9.4.3 Vergleichsmethoden

Die Gleichheitsoperatoren von JavaScript vergleichen Objekte by-reference, nicht by-value. Das heißt, dass sie, wenn sie zwei Objektreferenzen haben, prüfen, ob beide Referenzen auf dasselbe Objekt sind. Sie prüfen nicht, ob zwei verschiedene Objekte die gleichen Eigenschaftsnamen und -werte haben. Es ist oft nützlich, zwei Objekte auf Gleichheit oder auch eine relative Ordnung (wie es die Operatoren `<` und `>` tun) vergleichen zu können. Wenn Sie eine Klasse definieren und dazu in der Lage sein möchten, zwei Instanzen der Klasse vergleichen zu können, sollten Sie geeignete Methoden definieren, um diese Operationen durchzuführen.

Die Programmiersprache Java nutzt für Objektvergleiche Methoden, und die Übernahme von Java-Konventionen ist eine in JavaScript verbreitete und oft nützliche Sache. Um Gleichheitsprüfungen für Instanzen Ihrer Klasse zu ermöglichen, definieren Sie eine Instanzmethode namens `equals( )`. Sie sollte ein einziges Argument erwarten und `true` zurückliefern, wenn es dem Objekt gleich ist, auf dem die Methode aufgerufen wurde. Natürlich bleibt es Ihnen überlassen, was »gleich« im Kontext Ihrer Klasse heißen soll. Typischerweise vergleichen Sie einfach die Instanzeigenschaften der beiden Objekte, um sich zu vergewissern, dass sie die gleichen Werte haben. Die Klasse Komplex aus Beispiel 9-2 hat eine `equals( )`-Methode dieser Art.

Manchmal ist es nützlich, Objekt gemäß einer Ordnung zu vergleichen. Das heißt, dass man bei manchen Klassen sagen kann, dass eine Instanz »kleiner« oder »größer« als eine andere Instanz ist. Beispielsweise könnten Sie Komplex-Objekte auf Basis ihres `betrag( )`-Werts ordnen. Andererseits ist es nicht klar, dass es eine sinnvolle Ordnung von Kreis-Objekten gibt: Vergleichen Sie sie auf Basis des Radius, der X- und Y-Koordinate oder anhand einer Kombination dieser Werte?

Wenn Sie versuchen, Objekte mit den relationalen Operatoren von JavaScript wie `<` und `<=` zu vergleichen, ruft JavaScript zuerst die `valueOf( )`-Methode dieser Objekte auf und vergleicht diese Werte, wenn die Methoden elementare Werte zurückliefern. Da die Klasse Komplex eine `valueOf( )`-Methode hat, die den Realteil einer komplexen Zahl zurückliefert, können Instanzen der Klasse Komplex verglichen werden, als wären sie reelle Zahlen ohne imaginären Teil. Das kann sein, was Sie eigentlich möchten, oder auch nicht. Um Objekte gemäß einer expliziten definierten Ordnung Ihrer Wahl zu vergleichen, können Sie (wieder den Java-Konventionen folgend) eine Methode namens `compareTo( )` definieren.

Die Methode `compareTo( )` sollte ein einziges Argument akzeptieren und es mit dem Objekt vergleichen, auf dem die Methode aufgerufen wird. Wenn das `this`-Objekt kleiner als das Argumentobjekt ist, sollte `compareTo( )` einen Wert kleiner null zurückliefern. Wenn das `this`-Objekt größer als das Argumentobjekt ist, sollte die Methode einen Wert größer null zurückliefern. Und wenn die beiden Objekte gleich sind, sollte die Methode null zurückliefern. Diese Konventionen in Bezug auf den Rückgabewert sind wichtig und ermöglichen es Ihnen, relationale Operatoren und Vergleichsoperatoren durch die folgenden Ausdrücke zu ersetzen:

Ersetzen Sie das | durch das

---|---

a < b | a.compareTo(b) < 0

a <= b | a.compareTo(b) <= 0

a > b | a.compareTo(b) > 0

a >= b | a.compareTo(b) >= 0

a == b | a.compareTo(b) == 0

a != b | a.compareTo(b) != 0

Hier ist eine `compareTo( )`-Methode für die Klasse Komplex aus Beispiel 9-2, die komplexe Zahlen anhand des Betrags vergleicht:

    Komplex.prototype.compareTo = function(andere) {
      // Wenn kein Argument übergeben wird oder ein Wert übergeben wird, der
      // keine betrag( )-Methode hat, wird eine Exception ausgelöst.
      // Alternativ könnte in diesem Fall −1 oder 1 zurückgeliefert werden,
      // um zu sagen, dass alle Komplex-Objekte immer kleiner oder größer
      // als alle anderen Werte sind.
      if (!andere || !andere.betrag || typeof andere.betrag != "function")
        throw new Error("Falsches Argument für Komplex.compareTo( )");

      // Dieser Subtraktionstrick liefert einen Wert kleiner, gleich oder
      // größer null zurück. Das ist in vielen compareTo( )-Methoden hilfreich.
      return this.betrag( ) – andere.betrag( );
    }

Ein Grund dafür, Instanzen einer Klasse zu vergleichen, besteht darin, dass Arrays dieser Instanzen dann in einer bestimmten Ordnung sortiert werden können. Die Methode `Array.sort( )` akzeptiert als optionales Argument eine Vergleichsfunktion, die die gleichen Rückgabewertkonventionen verwendet wie die Methode `compareTo( )`. Wird die vorgestellte `compareTo( )`-Methode verwendet, ist es einfach, ein Array mit Komplex-Objekten mit Code wie diesem zu sortieren:

    komplexeZahlen.sort(new function(a,b) { return a.compareTo(b); });

Sortieroperationen sind so wichtig, dass Sie überlegen sollten, jeder Klasse, für die Sie eine `compareTo()`-Instanzmethode definieren, eine statische `compare( )`-Methode hinzuzufügen. Eine Methode kann leicht in Bezug auf die andere definiert werden. Zum Beispiel:

    Komplex.compare = function(a,b) { return a.compareTo(b); };

Mit einer Methode wie dieser wird das Sortieren einfacher:

    komplexeZahlen.sort(Komplex.compare);

Beachten Sie, dass die hier gezeigten `compareTo( )`\- und `compare( )`-Methoden nicht in die ursprüngliche Komplex-Klasse von Beispiel 9-2 eingeschlossen sind. Das liegt daran, dass sie nicht _konsistent mit_ der `equals( )`-Methode sind, die in diesem Beispiel definiert wurde. Die Methode `equals( )` sagt, dass zwei Komplex-Objekt gleich sind, wenn ihre Real- und Imaginärteile gleich sind. Aber die `compareTo( )`-Methode liefert für zwei komplexe Zahlen null zurück, die den gleichen Betrag haben. Die beiden Zahlen 1+0 _i_ und 0+1 _i_ haben den gleichen Betrag, und diese Werte sind deswegen gemäß `compareTo( )` gleich. Aber gemäß `equals( )` sind sie nicht gleich. Wenn Sie für die gleiche Klasse `equals( )`\- und `compareTo( )`-Methoden schreiben, ist es ein guter Gedanke, sie konsistent zu machen. Inkonsistente Vorstellungen von Gleichheit können schädliche Fehlerquellen sein. Hier ist eine `compareTo( )`-Methode, die eine Ordnung definiert, die mit der der vorhandenen `equals( )`-Methode konsistent ist:

    // Komplexe Zahlen werden zuerst anhand ihres Realteils verglichen.
    // Wenn ihre Realteile gleich sind, werden sie anhand ihrer
    // Imaginärteile verglichen.
    Komplex.prototype.compareTo = function(andere) {
      var ergbnis = this.x – andere.x;  // Den Realteil über eine Subtraktion vergleichen
      if (ergebnis == 0)                     // wenn sie gleich sind...
        ergebnis = this.y – andere.y;  // werden die imaginären Teile verglichen
      // Jetzt ist unser Ergebnis nur dann 0, wenn this.equals(andere) 0 ist.
      return ergebnis;
    };

# 9.5 Superklassen und Subklassen

Java, C++ und andere klassenbasierte objektorientierte Sprachen haben ein explizites Konzept der _Klassenhierarchie_. Jede Klasse kann eine _Superklasse_ haben, von der sie Eigenschaften und Methoden erbt. Alle Klassen können so erweitert oder abgeleitet werden, dass die resultierende _Subklasse_ ihr Verhalten erbt. Wie zuvor gezeigt, unterstützt JavaScript anstelle einer klassenbasierten Vererbung eine Prototyp-Vererbung. Trotzdem können JavaScript-Analogien zur Klassenhierarchie gezogen werden. In JavaScript ist die Klasse Object am allgemeinsten, und alle anderen Klassen sind spezialisierte Versionen oder Subklassen von ihr. Eine andere Art, das zu sagen, ist, dass Object die Superklasse aller eingebauten Klassen ist und alle Klassen ein paar grundlegende Methoden von Object erben.

Sie werden sich gewiss erinnern, dass Objekte Eigenschaften vom Prototyp-Objekt ihres Konstruktors erben. Wie erben sie dann auch Eigenschaften von der Klasse Object? Denken Sie daran, dass das Prototyp-Objekt selbst ein Objekt ist. Es wird mit dem `Object( )`-Konstruktor erzeugt. Das bedeutet, dass das Prototyp-Objekt selbst Eigenschaften von `Object.prototype` erbt! Prototypbasierte Vererbung ist nicht auf ein einziges Prototyp-Objekt beschränkt. Deswegen erbt ein Komplex-Objekt Eigenschaften von `Komplex.prototype` und `Object.prototype`. Wenn Sie in einem Komplex-Objekt eine Eigenschaft nachschlagen, wird zuerst das Objekt selbst durchsucht. Wenn die Eigenschaft nicht gefunden wird, wird als Nächstes das Objekt `Komplex.prototype` durchsucht. Und wenn die Eigenschaft auch in diesem Objekt nicht gefunden wird, wird zum Abschluss das Objekt `Object.prototype` durchsucht.

Beachten Sie, dass Eigenschaften von `Komplex.prototype` Eigenschaften gleichen Namens von `Object.prototype` verbergen, weil das Komplex-Prototyp-Objekt vor dem Object-Prototyp-Objekt durchsucht wird. Beispielsweise wurde in der Klasse Komplex von Beispiel 9-2 eine `toString( )`-Methode im `Komplex.prototype`-Objekt definiert. `Object. prototype` definiert ebenfalls eine Methode mit diesem Namen, aber Komplex-Objekte sehen diese nie, weil die `toString( )`-Definition in `Komplex.prototype` zuerst gefunden wird.

Die in diesem Kapitel bisher gezeigten Klassen sind alle direkte Subklassen von Object. Wenn es erforderlich ist, ist es aber auch möglich, Subklassen jeder anderen Klasse zu bilden. Denken Sie beispielsweise an die `Rechteck`-Klasse, die weiter oben in diesem Kapitel gezeigt wurde. Sie hatte Eigenschaften, die die Breite und Höhe des Rechtecks repräsentieren, aber keine Eigenschaften, die seine Position beschreiben. Nehmen Sie an, Sie möchten eine Subklasse von Rechteck erzeugen, um Felder und Methoden zu definieren, die sich auf die Position des Rechtecks beziehen. Dazu müssen Sie nur sicherstellen, dass das Prototyp-Objekt der neuen Klasse selbst eine Instanz von Rechteck ist, damit es alle Eigenschaften von `Rechteck.prototype` erbt. Beispiel 9-3 wiederholt die Definition der einfachen Rechteck-Klasse und erweitert sie dann, um eine Klasse PositioniertesRechteck zu definieren.

Beispiel 9-3. Eine Subklasse zu einer JavaScript-Klasse bilden

    // Hier ist eine einfache Rechteck-Klasse.
    // Sie hat eine Breite und eine Höhe und kann ihre Fläche berechnen.
    function Rechteck(b, h) {
      this.breite = b;
      this.hoehe = h;
    }
    Rechteck.prototype.flaeche = function( ) { return this.breite * this.hoehe; }

    // So könnte man eine Subklasse von ihr ableiten.
    function PositioniertesRechteck(x, y, b, h) {
      // Erst wird der Superklassen-Konstruktor auf dem neuen Objekt aufgerufen,
      // damit er die Breite und Höhe initialisieren kann.
      // Wir nutzen die Methode call, damit wir den Konstruktor als
      // Methode des Objekts aufrufen können, das initialisiert werden soll.
      // Das bezeichnet man als Konstruktorverkettung.
      Rechteck.call(this, b, h);

      // Jetzt speichern wir die Position der oberen linken Ecke des Rechtecks.
      this.x = x;
      this.y = y;
    }

    // Wenn wir das Default-Prototyp-Objekt verwenden, das erzeugt wird, wenn wir
    // den PositioniertesRechteck( )-Konstruktor erzeugen, erhalten wir eine Subklasse
    // von Object. Um eine Subklasse von Rechteck zu bilden, müssen wir unser
    // Prototyp-Objekt explizit erzeugen.
    PositioniertesRechteck.prototype = new Rechteck( );

    // Wir erzeugen dieses Prototyp-Objekt zu Vererbungszwecken, wollen aber
    // eigentlich die breite- und hoehe-Eigenschaften nicht erben, die jedes
    // Rechteck-Objekt hat, und löschen sie deswegen aus dem Prototyp.
    delete PositioniertesRechteck.prototype.breite;
    delete PositioniertesRechteck.prototype.hoehe;

    // Da das Prototyp-Objekt mit dem Rechteck( )-Konstruktor erzeugt wurde,
    // hat es eine Konstruktor-Eigenschaft, die auf diesen Konstruktor verweist.
    // Aber wir möchten, dass PositioniertesRechteck-Objekte eine andere constructor-Eigenschaft
    // haben, müssen also diese Default-Konstruktor-Eigenschaft neu zuweisen.
    PositioniertesRechteck.prototype.constructor = PositioniertesRechteck;

    // Jetzt, wo wir das Prototype-Objekt für unsere Subklasse konfiguriert haben,
    // können wir ihm Instanzmethoden hinzufügen.
    PositioniertesRechteck.prototype.enthaelt = function(x,y) {
      return (x > this.x && x < this.x + this.breite &&
          y > this.y && y < this.y + this.hoehe);
    }

Wie Sie aus Beispiel 9-3 sehen können, erfolgt die Erzeugung einer Subklasse in JavaScript nicht so wie die Erzeugung einer Klasse, die direkt von Object erbt. Erstens gibt es das Problem, dass der Superklassenkonstruktor aus dem Subklassenkonstruktor aufgerufen werden muss. Wenn Sie das tun, achten Sie darauf, dass der Superklassenkonstruktor als eine Methode des neu erzeugten Objekts aufgerufen wird. Dann sind Tricks erforderlich, um das Prototyp-Objekt des Subklassenkonstruktors zu setzen. Sie müssen dieses Prototyp-Objekt explizit als eine Instanz der Superklasse erzeugen und dann die `constructor`-Eigenschaft auf das Prototyp-Objekt setzen.[35] Optional möchten Sie vielleicht auch alle Eigenschaften löschen, die der Superklassenkonstruktor im Prototyp-Objekt erzeugt hat, weil das, was wichtig ist, die Eigenschaften sind, die das Prototyp-Objekt von _seinem_ Prototyp erbt.

Nachdem Sie die Klasse PositioniertesRechteck definiert haben, können Sie sie mit Code wie diesem verwenden:

    var r = new PositioniertesRechteck(2,2,2,2);
    print(r.enthaelt(3,3));  // Eine Instanzmethode aufrufen
    print(r.flaeche( ));   // Eine geerbte Instanzmethode aufrufen

    // Die Instanzfelder der Klasse verwenden:
    print(r.x + ", " + r.y + ", " + r.breite + ", " + r.hoehe);

    // Unser Objekt ist eine Instanz aller drei Klassen.
    print(r instanceof PositioniertesRechteck &&
       r instanceof Rechteck &&
       r instanceof Object);

## 9.5.1 Konstruktorverkettung

In dem gerade gezeigten Beispiel musste die Konstruktorfunktion `Positioniertes-Rechteck( )` explizit die Konstruktorfunktion der Superklasse aufrufen. Das wird als _`Konstruktorverkettung`_ bezeichnet und ist bei der Erzeugung von Subklassen ziemlich üblich. Wenn Sie nur eine Ebene an Subklassen haben, können Sie die Syntax für die Konstruktorverkettung vereinfachen, indem Sie dem Prototyp-Objekt der Subklasse eine Eigenschaft namens `superclass` hinzufügen:

    // Wir speichern eine Referenz auf den Superklassenkonstruktor.
    PositioniertesRechteck.prototype.superclass = Rechteck;

Ist diese Eigenschaft definiert, ist die Syntax für die Konstruktorverkettung einfacher:

    function PositioniertesRechteck(x, y, w, h) {
      this.superclass(w,h);
      this.x = x;
      this.y = y;
    }

Beachten Sie, dass die Konstruktorfunktion der Superklasse explizit über das `this`-Objekt aufgerufen wird. Das bedeutet, dass Sie nicht mehr `call( )` oder `apply( )` aufrufen müssen, um den Superklassenkonstruktor als Methode dieses Objekts aufzurufen. Beachten Sie außerdem, dass dieser Trick nur bei oberflächlichen Vererbungshierarchien funktioniert. Wenn B eine Subklasse von A und C eine Subklasse von B ist, und B und C beide diesen Subklassen-Trick benutzen, wird, wenn Sie eine Instanz von C erzeugen, `this.superclass` auf `B()` verweisen und der `B()-` Konstruktor eine Rekursion durchführen. Für alles, was über einfache Subklassen hinaus geht, benutzen Sie die Konstruktorverkettungstechnik aus Beispiel 9-3.

## 9.5.2 Überschriebene Methoden aufrufen

Wenn eine Subklasse eine Methode definiert, die den gleichen Namen wie eine Methode der Superklasse hat, _überschreibt_ die Subklasse diese Methode. Das ist eine relativ häufig auftretende Sache, wenn man Subklassen bestehender Klassen erzeugt. Jedes Mal, wenn Sie eine `toString( )`-Methode für eine Klasse erstellen, überschreiben Sie beispielsweise die `toString( )`-Methode von Object.

Eine Methode, die eine andere Methode überschreibt, will häufig die Funktionalität dieser überschriebenen Methode erweitern, anstatt sie vollkommen zu ersetzen. Dazu muss eine Methode dazu in der Lage sein, die Methode aufzurufen, die sie überschreibt. In gewisser Hinsicht ist das eine Art von Methodenverkettung, wie sie weiter oben in diesem Kapitel für Konstruktoren beschrieben wurde. Es ist allerdings noch umständlicher, eine überschiebene Methode aufzurufen als einen Superklassenkonstruktor aufzurufen.

Sehen wir uns ein Beispiel an. Nehmen wir an, die Klasse Rechteck hätte eine `toString( )`-Methode definiert (was sie natürlich hätte tun sollen):

    Rechteck.prototype.toString = function( ) {
      return "[" + this.breite + "," + this.hoehe + "]";
    }

Wenn Sie Rechteck eine `toString( )`-Methode geben, müssen Sie diese Methode natürlich in PositioniertesRechteck überschreiben, damit Instanzen der Subklasse eine Stringdarstellung haben, die ihre Eigenschaften wiedergibt. PositioniertesRechteck ist als Klasse einfach genug, dass ihre `toString( )`-Methode einfach die Werte aller Eigenschaften zurückliefern kann. Aber zum Zweck dieses Beispiels wollen wir die Positionseigenschaften behandeln und die Ausgabe der `breite`\- und `hoehe`-Eigenschaften an die Superklasse delegieren. So könnte der Code aussehen:

    PositioniertesRechteck.prototype.toString = function( ) {
      return "(" + this.x + "," + this.y + ") " +   // unsere Felder
         Rechteck.prototype.toString.apply(this);  // mit der Superklasse verketten
    }

Die Superklassenimplementierung von `toString( )` ist eine Eigenschaft des Prototyp-Objekts der Superklasse. Beachten Sie, dass Sie sie nicht direkt aufrufen können. Rufen Sie sie mit `apply( )` auf, damit Sie das Objekt angeben können, auf dem sie aufgerufen werden soll.

Wenn Sie eine `superclass`-Eigenschaft zu `PositioniertesRechteck.prototype` hinzufügen, kann dieser Code auf superklassenunabhängige Weise umgeschrieben werden:

    PositioniertesRechteck.prototype.toString = function( ) {
      return "(" + this.x + "," + this.y + ") " +   // unsere Felder
         this.superclass.prototype.toString.apply(this);
    }

Beachten Sie auch hier, dass diese Subklassen-Eigenschaft nur ein Mal in einer Vererbungshierarchie genutzt werden kann. Wenn eine Klasse und eine Subklasse beide diesen Trick anwenden, ergibt dies eine unendliche Rekursion.

* * *

[35] Es gibt einen Bug in Version 1.6r1 und früher von Rhino (dem Java-basierten JavaScript-Interpreter), der die `constructor`-Eigenschaft schreibgeschützt und nicht löschbar macht. In diesen Versionen von Rhino schlägt der Code, mit dem die `constructor`-Eigenschaft gesetzt wird, stillschweigend fehl. Instanzen der Klasse PositioniertesRechteck erben dann eine `constructor`-Eigenschaft, die auf den `Rechteck( )`-Konstruktor verweist. In der Praxis sind die Auswirkungen dieses Bugs nicht sehr groß, weil die Eigenschaftsvererbung richtig funktioniert und der `instanceof`-Operator für Rechteck- und PositioniertesRechteck-Objekte ebenfalls richtig funktioniert.

# 9.6 Erweitern, ohne zu erben

Bei der Beschreibung der Erstellung von Subklassen weiter oben in diesem Kapitel habe ich erklärt, wie man eine neue Klasse erstellt, die die Methoden einer anderen Klasse erbt. JavaScript ist eine so flexible Sprache, dass die Subklassenbildung und Vererbung nicht die einzigen Möglichkeiten sind, eine Klasse zu erweitern. Da JavaScript-Funktionen Datenwerte sind, können Sie einfach die Funktionen aus einer Klasse kopieren (oder »ausleihen«), um sie in einer anderen Klasse zu verwenden. Beispiel 9-4 zeigt eine Funktion, die alle Methoden aus einer Klasse ausleiht und Kopien im Prototyp-Objekt einer anderen Klasse anlegt.

Beispiel 9-4. Methoden aus einer Klasse ausleihen, um sie in einer anderen zu verwenden

    // Methoden aus einer Klasse ausleihen, um sie in einer anderen zu verwenden.
    // Die Argumente sollten die Konstruktorfunktionen für die Klassen sein.
    // Methoden eingebauter Typen wie Object, Array, Date und RegExp sind nicht
    // aufzählbar und können mit dieser Methode nicht ausgeliehen werden.
    function borrowMethods(ausleihenVon, hinzufuegenZu) {
      var von = ausleihenVon.prototype;  // Prototyp-Objekt, aus dem ausgeliehen wird
      var zu = hinzufuegenZu.prototype;  // Prototyp-Objekt, das erweitert werden soll

      for(m in from) { // Alle Eigenschaften des Prototyps durchlaufen
        if (typeof from[m] != "function") continue; // Nicht-Funktionen ignorieren
        to[m] = from[m]; // Die Methoden ausleihen
      }
    }

Viele Methoden sind eng an die Klasse gebunden, die sie definiert. Dann ist der Versuch sinnlos, sie in einer anderen Klasse zu verwenden. Aber es ist möglich, manche Methoden so allgemein zu schreiben, dass sie von beliebigen Klassen verwendet werden können oder zumindest von beliebigen Klassen, die bestimmte Eigenschaften definieren. Beispiel 9-5 enthält zwei Klassen, die nur nützliche Methoden definieren, die andere Klassen ausleihen können. Klassen wie diese, die für das Ausleihen gedacht sind, werden als _Mixin-Klassen_ oder _Mixins_ bezeichnet.

Beispiel 9-5. Mixin-Klassen mit allgemeinen ausleihbaren Methoden

    // Für sich selbst ist diese Klasse zu wenig zu gebrauchen. Aber sie definiert
    // eine allgemeine toString( )-Methode, die für andere Klassen interessant sein könnte.
    function GenericToString( ) {}
    GenericToString.prototype.toString = function( ) {
      var eigenschaften = [];
      for(var name in this) {
        if (!this.hasOwnProperty(name)) continue;
        var wert = this[name];
        var s = name + ":"
        switch(typeof wert) {
        case 'function':
          s += "function";
          break;
        case 'object':
          if (wert instanceof Array) s += "array"
          else s += wert.toString( );
          break;
        default:
          s += String(wert);
          break;
        }
        eigenschaften.push(s);
      }
      return "{" + eigenschaften.join(", ") + "}";
    }

    // Diese Mixin-Klasse definiert eine equals( )-Methode, die einfache Objekte
    // auf Gleichheit prüfen kann.
    function GenericEquals( ) {}
    GenericEquals.prototype.equals = function(andere) {
      if (this == andere) return true;

      // this und that sind nur dann gleich, wenn this alle Eigenschaften von that
      // besitzt und keine zusätzlichen Eigenschaften hat.
      // Beachten Sie, dass wir keinen tiefen Vergleich durchführen. Eigenschaftswerte
      // müssen einander === sein. Eigenschaften, die auf andere Objekte verweisen,
      // müssen auf dasselbe Objekt verweisen, nicht auf Objekte, die gleich sind.
      var eigenschaftenInAndere = 0;
      for(var name in andere) {
        eigenschaftenInAndere++;
        if (this[name] !== andere[name]) return false;
      }

      // Jetzt sicherstellen, dass dieses Objekt keine zusätzlichen Eigenschaften hat.
      var eigenschaftenInThis = 0;
      for(name in this) eigenschaftenInThis++;

      // Wenn this zusätzliche Eigenschaften hat, sind sie nicht gleich.
      if (eigenschaftenInThis != eigenschaftenInAndere) return false;

      // Die beiden Objekte scheinen gleich zu sein.
      return true;
    }

Hier ist eine einfache Rechteck-Klasse, die die `toString( )`\- und `equals( )`-Methoden ausleiht, die von den Mixin-Klassen definiert werden:

    // Hier ist eine einfache Rechteck-Klasse.
    function Rechteck(x, y, b, h) {
      this.x = x;
      this.y = y;
      this.breite = b;
      this.hoehe = h;
    }
    Rechteck.prototype.flaeche = function( ) { return this.breite * this.hoehe; }

    // Einige weitere Methoden für sie ausleihen
    borrowMethods(GenericEquals, Rechteck);
    borrowMethods(GenericToString, Rechteck);

Keines der gezeigten Mixins hat eine Konstruktorfunktion, aber man kann auch Konstruktoren ausleihen. Im folgenden Code wird eine neue Klasse namens FarbigesRechteck erzeugt. Sie erbt die Rechteckfunktionalität von Rechteck und leiht einen Konstruktor und eine Methode von einem Mixin namens Farbig aus:

    // Dieses Mixin hat eine Methode, die von seinem Konstruktor abhängig ist.
    // Deswegen müssen Konstruktor und Methode ausgeliehen werden.
    function Farbig(c) { this.farbe = f; }
    Farbig.prototype.getFarbe = function( ) { return this.farbe; }

    // Den Konstruktor für eine neue Klasse definieren.
    function FarbigesRechteck(x, y, b, h, f) {
      this.superclass(x, y, b, h); // Den Konstruktor der Superklasse aufrufen
      Colored.call(this, f);    // und den Farbig-Konstruktor ausleihen
    }

    // Das Prototyp-Objekt so einrichten, dass die Methoden von Rechteck geerbt werden.
    FarbigesRechteck.prototype = new Rechteck( );
    FarbigesRechteck.prototype.constructor = FarbigesRechteck;
    FarbigesRechteck.prototype.superclass = Rechteck;

    // Und die Methode von Farbig für unsere neue Klasse ausleihen
    borrowMethods(Farbig, FarbigesRechteck);

Die Klasse FarbigesRechteck erweitert (und erbt Methoden von) Rechteck und leiht sich Methoden von Farbig. Rechteck selbst erbt von Object und leiht von GenericEquals und GenericToString. Obwohl hier keine strenge Analogie möglich ist, können Sie sich das als eine Art Mehrfachvererbung vorstellen. Da die Klasse FarbigesRechteck die Methode von Farbig ausleiht, können Instanzen von FarbigesRechteck auch als Instanzen von Farbig betrachtet werden. Das wird vom `instanceof`-Operator nicht gemeldet. Aber in "9.7.3 Duck Typing" werden wir eine allgemeinere Methode dafür entwickeln, wie man ermittelt, ob ein Objekt von einer angegebenen Klasse erbt _oder_ leiht.

# 9.7 Den Objekttyp ermitteln

JavaScript ist nicht streng typisiert, und JavaScript-Objekte sind noch weniger streng typisiert. Es gibt in JavaScript eine Reihe von Techniken, die Sie einsetzen können, um den Typ eines beliebigen Werts zu ermittlen.

Die offensichtlichste dieser Techniken ist natürlich der `typeof`-Operator (siehe "5.10.2 Der typeof-Operator"). `typeof` ist primär nützlich, um elementare Typen von Objekten zu unterscheiden. `typeof` hat einige Eigenheiten. Denken Sie erstens daran, dass `typeof null` »object« ist und `typeof undefined` »undefined«. Außerdem ist der Typ eines Arrays »object«, weil alle Arrays Objekte sind. Aber der Typ einer Funktion ist »function«, obwohl Funktionen ebenfalls Objekte sind.

## 9.7.1 instanceof und constructor

Nachdem Sie ermittelt haben, dass ein Wert ein Objekt und kein elementarer Wert und keine Funktion ist, können Sie den `instanceof`-Operator verwenden, um mehr über es zu erfahren. Beispielsweise wird der folgende Ausdruck mit `true` ausgewertet, wenn `x` ein Array ist:

    x instanceof Array

Die linke Seite von `instanceof` ist der zu prüfende Wert, und die rechte Seite sollte eine Konstruktorfunktion sein, die eine Klasse definiert. Beachten Sie, dass ein Objekt eine Instanz seiner eigenen Klasse und seiner Superklassen ist. Also liefert `o instanceof Object` für jedes Objekt `o` immer `true`. Interessanterweise funktioniert `instanceof` bei Funktionen. Für eine Funktion `f` liefern also all diese Ausdrücke `true`:

    typeof f == "function"
    f instanceof Function
    f instanceof Object

Wenn Sie prüfen möchten, ob ein Objekt eine Instanz der angegebenen Klasse und nicht eine Instanz irgendeiner Subklasse ist, können Sie seine `constructor`-Eigenschaft prüfen. Betrachten Sie den folgenden Code:

    var d = new Date( );            // Ein Date-Objekt; Date erweitert Object
    var isObject = d instanceof Object;    // liefert true
    var echtesObject = d.constructor==Object; // liefert false

## 9.7.2 Typbestimmung von Objekten mit Object.toString( )

Ein Mangel des `instanceof`-Operators und der `constructor`-Eigenschaft ist, dass sie es Ihnen nur ermöglichen, ein Objekt auf Klassen zu prüfen, die Sie bereits kennen. Sie sind nicht geeignet, um unbekannte Objekte zu untersuchen, wie Sie es beispielsweise beim Debugging machen könnten. Ein nützlicher Trick, der die Default-Implementierung der Methode `Object.toString( )` nutzt, kann in diesem Fall hilfreich sein.

Wie in Kapitel 7 gezeigt wird, definiert Object eine Default-`toString( )`-Methode. Jede Klasse, die keine eigene `toString()`-Methode definiert, erbt diese Default-Implementierung. Ein interessantes Feature der Default-`toString( )`-Methode ist, dass sie einige interne Typinformationen über eingebaute Objekte offenbart. Die ECMAScript-Spezifikation verlangt, dass die Default-`toString( )`-Methode immer einen String folgender Form zurückliefert:

    [object _Klasse_ ]

`Klasse` ist der interne Typ des Objekts und entspricht in der Regel dem Namen der Konstruktorfunktion für das Objekt. Beispielsweise ist `Klasse` bei Arrays »Array«, bei Funktionen »Function« und bei Date-Objekten »Date«. Das eingebaute Math-Objekt hat die `Klasse` »Math«, und alle Error-Objekte (einschließlich der Instanzen der verschiedenen Error-Subklassen) haben die `Klasse` »Error«. Clientseitige JavaScript-Objekte und andere Objekte, die von der JavaScript-Implementierung definiert werden, haben eine implementierungsdefinierte `Klasse` (wie »Window«, »Document« oder »Form«). Objekte benutzerdefinierter Typen (wie die weiter oben in diesem Kapitel definierten Klassen Kreis und Komplex) haben allerdings immer die `Klasse` »Object«. Diese `toString( )`-Technik ist deswegen nur bei eingebauten Typen nützlich.

Da die meisten Klassen die Default-`toString( )`-Methode überschreiben, können Sie sie nicht direkt auf einem Objekt aufrufen und erwarten, seinen Klassennamen zu finden. Stattdessen verweisen Sie explizit auf die Default-Methode in `Object.prototype` und nutzen `apply( )`, um sie auf dem Objekt aufzurufen, für dessen Typ Sie sich interessieren:

    Object.prototype.toString.apply(o); // Ruft immer die Default-toString( )-Methode auf

Diese Technik wird in Beispiel 9-6 eingesetzt, um eine Funktion zu definieren, die eine verbesserte »typeof«-Funktionalität liefert. Wie bereits gesagt, funktioniert die `toString( )`-Methode für benutzerdefinierte Klassen nicht. In diesem Fall prüft die Methode, ob eine Eigenschaft namens `classname` mit einem Stringwert vorhanden ist und liefert den entsprechenden Wert zurück, wenn es eine solche Eigenschaft gibt.

Beispiel 9-6. Verbesserte typeof-Prüfung

    function getType(x) {
      // Wenn x null ist, wird "null" zurückgeliefert
      if (x == null) return "null";

      // Jetzt wird der typeof-Operator ausprobiert
      var t = typeof x;
      // Wenn das Ergebnis nicht ungenau ist, wird es zurückgeliefert
      if (t != "object") return t;

      // Andernfalls ist x ein Objekt. Wir verwenden die Default-toString( )-Methode,
      // um den Klassenwert für das Objekt zu erhalten.
      var c = Object.prototype.toString.apply(x); // Liefert "[object Klasse]"
      c = c.substring(8, c.length-1);       // "[object" und "]" abschneiden

      // Wenn die Klasse nicht ungenau ist, wird sie zurückgeliefert.
      if (c != "Object") return c;

      // Wenn wir diesen Punkt erreichen, ist c "Object". Wir prüfen,
      // ob der Wert x tatsächlich ein generisches Objekt ist.
      if (x.constructor == Object) return c; // OK, der Typ ist wirklich "Object"

      // Bei benutzerdefinierten Klassen wird nach einer Eigenschaft namens classname
      // mit einem Stringwert gesucht, die vom Prototyp des Objekts geerbt wird.
      if ("classname" in x.constructor.prototype && // erbt classname
        typeof x.constructor.prototype.classname == "string") // ist ein String
        return x.constructor.prototype.classname;

      // Wenn wir es nicht herausfinden können, melden wir das.
      return "<unbekannter Typ>";
    }

## 9.7.3 Duck Typing

Es gibt eine alte Redewendung: »Wenn es wie eine Ente geht und wie eine Ente quakt, dann ist es eine Ente!« In JavaScript übersetzt, ist dieser Aphorismus nicht annähernd so sinnträchtig. Versuchen Sie es folgendermaßen: »Wenn es alle Methoden implementiert, die von einer Klasse definiert werden, ist es eine Instanz dieser Klasse.« In flexiblen, lose typisierten Sprachen wie JavaScript wird das als _Duck Typing_ bezeichnet: Wenn ein Objekt die von der Klasse X definierten Eigenschaften besitzt, kann es als Instanz der Klasse X behandelt werden, auch wenn es eigentlich nicht mit der Konstruktorfunktion `X( )` erzeugt wurde.[36]

Duck Typing ist insbesondere in Verbindung mit Klassen nützlich, die Methoden von anderen Klassen »ausleihen«. Weiter oben in diesem Kapitel hat eine Rechteck-Klasse die Implementierung einer `equals( )`-Methode von einer anderen Klasse namens GenericEquals ausgeliehen. Sie können also jede Rechteck-Instanz so behandeln, als wäre sie eine Instanz von GenericEquals. Das wird vom `instanceof`-Operator nicht gemeldet, aber Sie können eine eigene Methode definieren, die das macht. Beispiel 9-7 zeigt, wie das geht.

Beispiel 9-7. Prüfen, ob ein Objekt eine Methode von einer anderen Klasse leiht

    // Liefert true, wenn alle Methodeneigenschaften in c.prototype von o ausgeliehen
    // wurden. Wenn o eine Funktion und kein Objekt ist, prüfen wir den
    // Prototyp von o anstelle von o selbst. Beachten Sie, dass die Funktion
    // verlangt, dass die Methoden kopiert und nicht reimplementiert werden.
    // Wenn eine Klasse eine Methode ausleiht und dann überschreibt, liefert
    // die Methode false.
    function borrows(o, c) {
      // Wenn o eine Instanz von c ist, hat es natürlich seine Methoden.
      if (o instanceof c) return true;

      // Man kann nicht prüfen, ob die Methoden von eingebauten Typen ausgeliehen
      // wurden, da die Methoden eingebauter Typen nicht aufzählbar sind.
      // In diesem Fall wird als eine Art "Keine Ahnung"-Antwort undefined zurückgeliefert,
      // anstatt eine Exception auszulösen. Undefined verhält sich ziemlich genau wie false,
      // kann aber von false unterschieden werden, wenn der Aufrufer das muss.
      if (c == Array || c == Boolean || c == Date || c == Error ||
        c == Function || c == Number || c == RegExp || c == String)
        return undefined;

      if (typeof o == "function") o = o.prototype;
      var proto = c.prototype;
      for(var p in proto) {
        // Eigenschaften ignorieren, die keine Funktionen sind.
        if (typeof proto[p] != "function") continue;
        if (o[p] != proto[p]) return false;
      }
      return true;
    }

Die `borrows( )`-Methode aus Beispiel 9-7 ist ziemlich streng. Sie verlangt, dass das Objekt `o` genaue Kopien der Methoden hat, die von der Klasse `c` definiert werden. Echtes Duck Typing ist flexibler: `o` sollte als Instanz von `c` betrachtet werden, solange es Methoden bietet, die wie die Methoden von `c` aussehen. In JavaScript heißt »aussehen« so viel wie »den gleichen Namen haben« und (vielleicht) »mit der gleichen Anzahl von Argumenten deklariert sein«. Beispiel 9-8 zeigt eine Methode, die das prüft.

Beispiel 9-8. Prüfen, ob ein Objekt Methoden bietet

    // Liefert true, wenn o Methoden mit dem gleichen Namen und Grad wie alle
    // Methoden in c.prototype hat. Liefert andernfalls false. Löst eine Exception
    // aus, wenn c ein eingebauter Typ mit nicht-aufzählbaren Methoden ist.
    function provides(o, c) {
      // Wenn o tatsächlich eine Instanz von c ist, sieht es offensichtlich wie c aus.
      if (o instanceof c) return true;

      // Wenn statt eines Objekts ein Konstruktor übergeben wurde, wird sein Prototyp verwendet.
      if (typeof o == "function") o = o.prototype;

      // Die Methoden eingebauter Typen sind nicht aufzählbar. Es wird deswegen
      // undefined zurückgeliefert. Andernfalls würde es so aussehen, als würde
      // jedes Objekt die Methoden der eingebauten Typen besitzen.
      if (c == Array || c == Boolean || c == Date || c == Error ||
        c == Function || c == Number || c == RegExp || c == String)
        return undefined;

      var proto = c.prototype;
      for(var p in proto) { // Alle Eigenschaften in c.prototype durchlaufen.
        // Eigenschaften ignorieren, die keine Funktionen sind.
        if (typeof proto[p] != "function") continue;
        // Wenn o keine Eigenschaft mit dem gleichen Namen hat, wird false geliefert.
        if (!(p in o)) return false;
        // Wenn die Eigenschaft keine Funktion ist, wird false geliefert.
        if (typeof o[p] != "function") return false;
        // Wenn die beiden Funktionen nicht mit der gleichen Anzahl von Argumenten
        // deklariert wurden, wird false geliefert.
        if (o[p].length != proto[p].length) return false;
      }
      // Wenn alle Methoden passen, kann abschließend true zurückgeliefert werden.
      return true;
    }

Betrachten Sie als Beispiel für einen Fall, in dem Duck Typing und die Methode `provides( )` nützlich sind, die in "9.4.3 Vergleichsmethoden" beschriebene Methode `compareTo( )`. `compareTo( )` ist keine Methode, die sich zum Ausleihen anbietet. Aber es wäre trotzdem nett, wenn wir leicht auf Objekte prüfen könnzen, die mit der Methode `compareTo( )` verglichen werden können. Definieren Sie dazu eine Klasse Comparable:

    function Comparable( ) {}
    Comparable.prototype.compareTo = function(that) {
      throw "Comparable.compareTo( ) ist abstrakt. Rufen Sie sie nicht auf!";
    }

Diese Comparable-Klasse ist _abstrakt_ : Es ist eine Methode, die nicht dafür gedacht ist, tatsächlich aufgerufen zu werden, sondern die einfach eine API definieren soll. Wenn diese Klasse definiert ist, können Sie dann aber folgendermaßen prüfen, ob zwei Objekte verglichen werden können:

    // Prüft, ob die Objekte o und p verglichen werden können.
    // Sie müssen den gleichen Typ haben, und dieser Typ muss vergleichbar sein.
    if (o.constructor == p.constructor && provides(o, Comparable)) {
      var order = o.compareTo(p);
    }

Beachten Sie, dass die in diesem Abschnitt vorgestellten Methoden `borrows( )` und `provides( )` beide `undefined` zurückliefern, wenn ihnen ein eingebauter JavaScript-Typ wie Array übergeben wird. Der Grund dafür ist, dass die Eigenschaften des Prototyp-Objekts eingebauter Typen nicht mit einer `for/in`-Schleife aufgezählt werden können. Würden diese Funktionen nicht explizit auf eingebaute Typen prüfen und `undefined` zurückliefern, würden sie annehmen, dass diese eingebauten Typen keine Methoden haben und für eingebaute Typen immer `true` zurückliefern.

Der Typ Array verdient allerdings eine besondere Betrachung. In "7.7 Array-artige Objekte" hatte ich gesagt, dass viele Algorithmen (wie das Durchlaufen der Elemente) auch bei Objekten gut funktionieren, die zwar keine echten Arrays, aber Array-artig sind. Zu ermitteln, welche Objekte wie Arrays aussehen, ist eine andere Anwendung von Duck Typing. Beispiel 9-9 zeigt eine Möglichkeit, das zu tun.

Beispiel 9-9. Auf Array-artige Objekte prüfen

    function isArrayLike(x) {
      if (x instanceof Array) return true; // Echte Arrays sind Array-artig
      if (!("length" in x)) return false; // Arrays müssen eine length-Eigenschaft haben
      if (typeof x.length != "number") return false; // length muss den Typ number haben
      if (x.length < 0) return false;         // und darf nicht kleiner null sein
      if (x.length > 0) {
        // Wenn das Array nicht leer ist, muss es mindestens eine Eigenschaft
        // definieren, deren Name die Zahl length-1 ist
        if (!((x.length-1) in x)) return false;
      }
      return true;
    }

* * *

[36] Der Begriff »Duck Typing« wurde durch die Programmiersprache Ruby bekannt. Ein formellerer Name ist _Allomorphie_.

# 9.8 Beispiel: Eine defineClass( )-Hilfsmethode

Dieses Kapitel schließt mit einer `defineClass( )`-Hilfsmethode, die die vorangehenden Beschreibungen von Konstruktoren, Prototypen, Subklassen und dem Ausleihen und Stellen von Methoden verbindet. Beispiel 9-10 zeigt den Code.

Beispiel 9-10. Eine Hilfsfunktion zur Definition von Klassen

    /**
     * defineClass( ) -- eine Hilfsfunktion zur Definition von JavaScript-Klassen.
     *
     * Diese Funktion erwartet als einziges Argument ein Objekt. Sie definiert
     * auf Basis der Daten im Objekt eine neue JavaScript-Klasse und liefert die
     * Konstruktorfunktion der neuen Klasse zurück. Diese Funktion erledigt alle
     * wiederkehrenden Aufgaben bei der Definition von Klassen: die Einrichtung des
     * Prototyp-Objekts für richtige Vererbung, das Kopieren von Methoden von anderen
     * Typen und so weiter.
     *
     * Das als Argument übergebene Objekt sollte einige oder alle der
     * nachfolgenden Eigenschaften haben:
     *
     *   name: Der Name der zu definierenden Klasse.
     *      Wird er angegeben, wird der Wert in der classname-Eigenschaft
     *      des Prototyp-Objekts gespeichert.
     *
     *  extend: Der Konstruktor der zu erweiternden Klasse. Wird er weggelassen,
     *      wird der Object( )-Konstruktor genutzt. Dieser Wert wird in der
     *      superclass-Eigenschaft des Prototyp-Objekts gespeichert.
     *
     * construct: Die Konstruktorfunktion für die Klasse. Wird sie weggelassen,
     *      wird eine neue leere Funktion verwendet. Dieser Wert wird zum
     *      Rückgabewert der Funktion und wird außerdem in der constructor-
     *      Eigenschaft des Prototyp-Objekts gespeichert.
     *
     *  methods: Ein Objekt, das die Instanzmethoden (und andere geteilte Eigenschaften)
     *      für die Klasse angibt. Die Eigenschaften dieses Objekts werden
     *      in das Prototyp-Objekt der Klasse kopiert. Werden sie weggelassen,
     *      stattdessen ein leeres Objekt verwendet. Eigenschaften mit den
     *      Namen "classname", "superclass" und "constructor" sind reserviert
     *      und sollten in diesem Objekt nicht verwendet werden.
     *
     *  statics: Ein Objekt, das die statischen Methoden (und andere statische
     *      Eigenschaften) für die Klasse definiert. Die Eigenschaften dieses
     *      Objekts werden Eigenschaften der Konstruktorfunktion. Werden sie
     *      weggelassen, wird stattdessen ein leeres Objekt verwendet.
     *
     *  borrows: Eine Konstruktorfunktion oder ein Array von Konstruktorfunktionen.
     *      Die Instanzmethoden aller angegebenen Klassen werden in das Prototyp-Objekt
     *      der neuen Klasse kopiert, damit die neue Klasse die Methoden aller
     *      angegebenen Klassen ausleiht. Die Konstruktoren werden in der
     *      Reihenfolge verarbeitet, in der sie angegeben wurden. Die Methoden
     *      einer am Ende des Arrays angegebenen Klasse können also die Methoden
     *      der zuvor angegebenen Klassen überschreiben. Beachten Sie, dass
     *      geliehene Methoden im Prototyp-Objekt vor den Eigenschaften des oben
     *      erklärten methods-Objekts gespeichert werden. Deswegen können Methoden,
     *      die im methods-Objekt angegeben werden, geliehene Methoden überschreiben.
     *      Wird diese Eigenschaft nicht angegeben, werden keine Methoden ausgeliehen.
     *
     * provides: Eine Konstruktorfunktion oder ein Arry mit Konstruktorfunktionen.
     *      Nachdem das Prototyp-Objekt vollständig initialisiert wurde, prüft
     *      diese Funktion, ob der Prototyp Methoden enthält, deren Namen und Argumentanzahl
     *      den Instanzmethoden entsprechen, die von all diesen Klassen definiert
     *      werden. Es werden keine Methoden kopiert. Das ist einfach eine
     *      Zusicherung, dass diese Klasse die Funktionalität der angegebenen
     *      Klassen "bietet". Wenn die Zusicherung fehlschlägt, löst die Methode
     *      eine Exception aus. Wird keine Exception ausgelöst, kann jede Instanz
     *      der neuen Klasse (auf Basis von Duck Typing) auch als Instanz dieser
     *      anderen Typen betrachtet werden. Wird diese Eigenschaft nicht angegeben,
     *      wird keine derartige Prüfung durchgeführt.
     **/
    function defineClass(data) {
      // Die zu verwendenden Felder aus dem Argumentobjekt herausziehen.
      // Die Default-Werte einrichten.
      var classname = data.name;
      var superclass = data.extend || Object;
      var constructor = data.construct || function( ) {};
      var methods = data.methods || {};
      var statics = data.statics || {};
      var borrows;
      var provides;

      // borrows kann ein einzelner Konstruktor oder ein Array von Konstruktoren sein.
      if (!data.borrows) borrows = [];
      else if (data.borrows instanceof Array) borrows = data.borrows;
      else borrows = [ data.borrows ];

      // Gleiches gilt für die Eigenschaft provides.
      if (!data.provides) provides = [];
      else if (data.provides instanceof Array) provides = data.provides;
      else provides = [ data.provides ];

      // Ein Objekt erzeugen, das der Prototyp für unsere Klasse werden wird.
      var proto = new superclass( );

      // Alle nicht-geerbten Eigenschaften dieses neuen Prototyp-Objekts löschen.
      for(var p in proto)
        if (proto.hasOwnProperty(p)) delete proto[p];

      // Methoden von den "Mixin"-Klassen ausleihen, indem sie in den Prototyp kopiert werden.
      for(var i = 0; i < borrows.length; i++) {
        var c = data.borrows[i];
        borrows[i] = c;
        // Methodeneigenschaften aus dem Prototyp von c in unseren Prototyp kopieren.
        for(var p in c.prototype) {
          if (typeof c.prototype[p] != "function") continue;
          proto[p] = c.prototype[p];
        }
      }

      // Instanzmethoden in das Prototyp-Objekt kopieren.
      // Das kann Methoden der Mixin-Klassen überschreiben.
      for(var p in methods) proto[p] = methods[p];

      // Die reservierten "constructor"-, "superclass"- und "classname"-
      // Eigenschaften des Prototyps einrichten.
      proto.constructor = constructor;
      proto.superclass = superclass;
      // classname wird nur gesetzt, wenn tatsächlich ein Name angegeben wurde.
      if (classname) proto.classname = classname;

      // Prüfen, dass unser Prototyp alle erwarteten Methoden bietet.
      for(var i = 0; i < provides.length; i++) { // Für jede Klasse
        var c = provides[i];
        for(var p in c.prototype) {  // Für jede Eigenschaft
          if (typeof c.prototype[p] != "function") continue; // nur Methoden
          if (p == "constructor" || p == "superclass") continue;
          // Prüfen, dass wir eine Methode mit dem gleichen Namen und der gleichen
          // Anzahl deklarierter Argumente haben. Falls ja, wird fortgefahren.
          if (p in proto &&
            typeof proto[p] == "function" &&
            proto[p].length == c.prototype[p].length) continue;
          // Andernfalls wird eine Exception ausgelöst
          throw new Error("Die Klasse " + classname + " bietet folgende Methode nicht: "+
                  c.classname + "." + p);
        }
      }

      // Das Prototyp-Objekt mit der Konstruktorfunktion verknüpfen.
      constructor.prototype = proto;

      // Die statischen Eigenschaften in den Konstruktor kopieren.
      for(var p in statics) constructor[p] = data.statics[p];

      // Zum Abschluss die Konstruktorfunktion zurückliefern.
      return constructor;
    }

Beispiel 9-11 einsetzen") zeigt Beispielcode, der die Methode `defineClass( )` nutzt.

Beispiel 9-11. Die Methode defineClass( ) einsetzen

    // Eine Comparable-Klasse mit einer abstrakten Methode, damit wir
    // Klassen definieren können, die Comparable "bieten".
    var Comparable = defineClass({
      name: "Comparable",
      methods: { compareTo: function(that) { throw "abstract"; } }
    });

    // Eine Mixin-Klasse mit einer nützlichen allgemeinen ausleihbaren equals( )-Methode.
    var GenericEquals = defineClass({
      name: "GenericEquals",
      methods: {
        equals: function(andere) {
          if (this == andere) return true;
          var eigenschaftenInAndere = 0;
          for(var name in andere) {
            eigenschaftenInAndere++;
            if (this[name] !== andere[name]) return false;
          }

          // Jetzt sicherstellen, dass dieses Objekt keine weiteren Eigenschaften hat.
          var eigenschaftenInThis = 0;
          for(name in this) eigenschaftenInThis++;

          // Hat es weitere Eigenschaften, sind sie nicht gleich.
          if (eigenschaftenInThis != eigenschaftenInAndere) return false;

          // Die beiden Objekt scheinen gleich zu sein.
          return true;
        }
      }
    });
    // Eine sehr einfache Rechteck-Klasse, die Comparable bietet
    var Rechteck = defineClass({
      name: "Rechteck",
      construct: function(b,h) { this.breite = b; this.hoehe = h; },
      methods: {
        flaeche: function( ) { return this.breite * this.hoehe; },
        compareTo: function(that) { return this.flaeche( ) – that.flaeche( ); }
      },
      provides: Comparable
    });
    // Eine Subklasse von Rechteck, die mit dem Superklassenkonstruktor
    // verkettet ist, jeweils eine eigene Instanzmethode und statische Methode
    // definiert und eine equals( )-Methode ausleiht.
    var PositioniertesRechteck = defineClass({
      name: "PositioniertesRechteck",
      extend: Rechteck,
      construct: function(x,y,b,h) {
        this.superclass(b,h); // mit Superklasse verketten
        this.x = x;
        this.y = y;
      },
      methods: {
        enthaelt: function(x,y) {
          return x > this.x && x < this.x+this.breite &&
            y > this.y && y < this.y+this.hoehe;
        }
      },
      statics: {
        comparator: function(a,b) { return a.compareTo(b); }
      },
      borrows: [GenericEquals]
    });

# Kapitel 10. Module und Namensräume

Anfangs wurde JavaScript häufig in einfachen kleinen Skripten verwendet, die direkt in Webseiten eingebettet wurden. Als Webbrowser und Webstandards reiften, wurden JavaScript-Programme länger und komplexer. Heutzutage sind viele JavaScript-Skripten von externen _Modulen_ oder Bibliotheken mit JavaScript-Code abhängig.[37]

Aktuell laufen Anstrengungen, eine Sammlung wiederverwendbarer Open Source-JavaScript-Module zu schaffen. Das _JavaScript Archive Network_ (JSAN) ist dem _Comprehensive Perl Archive Network_ (CPAN) nachgebildet und hofft, für JavaScript das zu tun, was CPAN für die Sprache Perl und ihre Gemeinschaft getan hat. Unter <http://www.openjsan.org> können Sie mehr erfahren und Code finden.

JavaScript bietet keine Sprachfeatures zur Erstellung und Verwaltung von Modulen. Beim Schreiben von portablen, wiederverwendbaren Modulen mit JavaScript-Code geht es also größtenteils darum, einigen grundlegenden Konventionen zu folgen, die in diesem Kapitel beschrieben werden.

Die wichtigste Konvention betrifft die Verwendung von _Namensräumen_ zur Verhinderung von Namensraumkollisionen – das ist das, was eintritt, wenn zwei Module globale Eigenschaften mit dem gleichen Namen definieren: Ein Modul überschreibt die Eigenschaften eines anderen, und eines oder beide Module funktionieren nicht mehr richtig.

Eine weitere Konvention betrifft den Initialisierungscode für Module. Insbesondere in clientseitigem JavaScript ist das wichtig, weil bei Modulen, die ein Dokument in einem Webbrowser manipulieren, häufig die Ausführung von Code ausgelöst werden muss, wenn das Laden des Dokuments abgeschlossen ist.

Die folgenden Abschnitte behandeln Namensräume und die Initialisierung, und das Kapitel schließt mit einem ausführlichen Beispiel für ein Modul mit Hilfsfunktionen für die Arbeit mit Modulen.

# 10.1 Namensräume und Module erzeugen

Wenn Sie ein Modul schreiben möchten, das mit jedem anderen Skript und jedem anderen Modul verwendet werden kann, ist wichtigste Regel, die Sie befolgen müssen, es zu vermeiden, globale Variablen zu definieren. Jedes Mal, wenn Sie eine globale Variable definieren, riskieren Sie, dass diese Variable von einem anderen Modul oder einem anderen Programmierer, der Ihr Modul verwendet, überschrieben wird. Die Lösung ist, stattdessen alle Methoden und Eigenschaften für Ihr Modul in einem Namensraum zu definieren, den Sie extra für Ihr Modul verwenden.

JavaScript bietet keine spezielle Sprachunterstützung für Namensräume, aber JavaScript-Objekte eignen sich gut für diesen Zweck. Denken Sie an die in den Beispiel 9-8 bzw. Beispiel 9-10 entwickelten Hilfsmethoden `provides( )` und `defineClass( )`. Beide Methodennamen sind globale Symbole. Wenn Sie ein Modul mit Funktionen zur Arbeit mit JavaScript-Klassen erstellen wollen, definieren Sie diese Methoden nicht im globalen Namensraum. Stattdessen schreiben Sie Code wie diesen:

    // Wir erzeugen ein leeres Objekt als unseren Namensraum.
    // Dieses eine globale Symbol enthält all unsere anderen Symbole.
    var Class = {};
    // Schließlich definieren wir die Funktionen im Namensraum.
    Class.define = function(daten) { /* hier kommt der Code hin */ }
    Class.provides = function(o, c) { /* hier kommt der Code hin */ }

Beachten Sie, dass Sie hier keine Instanzmethoden (oder auch statische Methoden) einer JavaScript-Klasse definieren. Sie definieren gewöhnliche Funktionen und speichern Referenzen auf sie in einem speziell erstellten Objekt statt im globalen Objekt.

Dieser Code demonstriert die erste Regel für JavaScript-Module: _Ein Modul sollte dem globalen Namensraum nie mehr als ein einziges Symbol hinzufügen_. Hier sind zwei Vorschläge, die gute, allgemeine Ergänzungen zu dieser Regel sind:

  * Wenn ein Modul dem globalen Namensraum ein Symbol hinzufügt, sollte seine Dokumentation klar sagen, was dieses Symbol ist.

  * Wenn ein Modul dem globalen Namensraum ein Symbol hinzufügt, sollte es eine klare Beziehung zwischen dem Namen des Moduls und dem Namen der Datei geben, aus der dieses Modul geladen wird.

Im Falle des Class-Moduls können Sie den Code in eine Datei namens _Class.js_ stecken und diese Datei mit einem Kommentar beginnen, der so aussieht:

    /**
     * Class.js: Ein Modul mit Hilfsfunktionen für die Arbeit mit Klassen.
     *
     *  Dieses Modul definiert ein einziges globales Symbol namens "Class".
     *  Class verweist auf ein Namensraum-Objekt, und alle Hilfsfunktionen sind
     *  als Eigenschaften dieses Namensraums gespeichert.
     **/

Klassen sind in JavaScript ziemlich wichtig, und es gibt sicher mehrere nützliche Module für die Arbeit mit ihnen. Was passiert, wenn zwei Module das globale Symbol `Class` verwenden, um auf ihren Namensraum zu verweisen? Wenn das passiert, sind Sie wieder da, wo Sie begonnen haben, bei einer Namensraumkollision. Indem Sie einen Namensraum eingesetzt haben, haben Sie die Wahrscheinlichkeit einer Kollision verringert, aber Sie haben sie nicht vollständig eliminiert. Es hilft enorm, wenn eine Konvention für Dateinamen verwendet wird. Wenn die in Konflikt tretenden Module beide _Class.js_ heißen, können sie nicht im gleichen Verzeichnis gespeichert werden. Ein Skript kann nur dann zwei verschiedene _Class.js_ -Dateien einschließen, wenn sie in verschiedenen Verzeichnissen gespeichert werden, wie z.B. den Verzeichnissen _utilities/Class.js_ und _flanagan/Class.js_.

Und wenn Skripten in Unterverzeichnissen gespeichert werden, sollten die Unterverzeichnisnamen wahrscheinlich Teil des Modulnamens sein. Das heißt, das hier definierte »Class«-Modul sollte eigentlich `flanagan.Class` heißen. Der Code könnte so aussehen:

    /**
     * flanagan/Class.js: Ein Modul mit Hilfsfunktionen für die Arbeit mit Klassen.
     *
     *  Dieses Modul erzeugt ein einziges globales Symbol namens "flanagan",
     *  wenn dieses noch nicht existiert. Dann erzeugt es ein Namensraum-Objekt
     *  und speichert es in der Class-Eigenschaft des flanagan-Objekts. Alle
     *  Hilfsfunktionen werden in den Namensraum flanagan.Class gesteckt.
     **/
    var flanagan;         // Das eine globale Symbol "flanagan" deklarieren
    if (!flanagan) flanagan = {}; // Wenn es undefined ist, wird es zu einem Objekt gemacht
    flanagan.Class = {}      // Jetzt den Namensraum flanagan.Class erzeugen
    // Jetzt wird der Namensraum mit unseren Hilfsmethoden gefüllt.
    flanagan.Class.define = function(daten) { /* hier kommt der Code hin */ };
    flanagan.Class.provides = function(o, c) { /* hier kommt der Code hin */ };

In diesem Code ist das globale Objekt `flanagan` ein Namensraum für Namensräume. Würde ich ein anderes Modul schreiben, dass Hilfsfunktionen zur Arbeit mit Datumswerten aufnehmen soll, könnte ich diese Hilfsfunktionen im Namensraum `flanagan.Date` speichern. Beachten Sie, dass dieser Code das globale Symbol `flanagan` mit einer `var`-Anweisung deklariert, bevor er die Existenz dieses Symbols prüft. Das liegt daran, dass eine Exception ausgelöst wird, wenn versucht wird, ein nicht deklariertes Symbol zu lesen, während der Versuch, ein deklariertes Symbol zu lesen, das `undefined` ist, einfach den Wert `undefined` zurückliefert. Das ist ein besonderes Verhalten, das nur das globale Objekt besitzt. Wenn Sie versuchen, eine nicht-vorhandene Eigenschaft eines Namensraums-Objekts zu lesen, erhalten Sie einfach den Wert `undefined` ohne eine Exception.

Mit einem zweistufigen Namensraum wie `flanagan.Class` scheinen wir gut geschützt vor Namenskonflikten zu sein. Wenn sich ein anderer JavaScript-Entwickler mit dem Nachnamen Flanagan entschließt, ein Modul mit klassenbezogenen Hilfsfunktionen zu schreiben, würde ein Programmierer, der beide Module verwenden will, auf Probleme stoßen. Aber das scheint ziemlich unwahrscheinlich. Wenn Sie vollständige Sicherheit wünschen, könnten Sie allerdings eine Konvention der Programmiersprache Java für global eindeutige Präfixe für Paketnamen übernehmen: Beginnen Sie mit dem Namen einer Internet-Domain, die Sie halten. Kehren Sie sie so um, dass die Toplevel-Domain ( _.com_ oder eine beliebige andere) zuerst kommt, und verwenden Sie diese Form als Präfix für all Ihre JavaScript-Module. Da meine Webseite davidflanagan.com ist, würde ich mein Modul in einer Datei namens _com/davidflanagan/Class.js_ speichern und den Namensraum `com.davidflanagan.Class` verwenden. Wenn alle JavaScript-Entwickler dieser Konvention folgen, wird kein anderer etwas im Namensraum `com.davidflanagan` definieren, weil kein anderer die Domain davidflanagan.com hält.

Diese Konvention könnte für die meisten JavaScript-Module des Guten zu viel sein und Sie müssen ihr nicht folgen. Aber Sie sollten sie kennen. Erzeugen Sie nicht aus Versehen Namensräume, die der Domainname eines anderen sein könnten: _Definieren Sie nie einen Namensraum unter Verwendung eines umgekehrten Domainnamens, es sei denn, Sie halten die Domain selbst_.

Beispiel 10-1 zeigt, wie man einen `com.davidflanagan.Class`-Namensraum erzeugt. Er fügt die Fehlerprüfung hinzu, die in den vorangehenden Beispielen fehlte, und löst eine Exception aus, wenn `com.davidflanagan.Class` bereits existiert oder wenn `com` oder `com.davidflanagan` bereits existiert und nicht auf ein Objekt verweist. Das Beispiel zeigt außerdem, dass Sie einen Namenraum in einem einzigen Objekt-Literal erzeugen und bevölkern können, anstatt das in mehreren getrennten Schritten zu machen.

Beispiel 10-1. Einen Namensraum erzeugen, der auf einem Domainnamen basiert

    // Das globale Symbol "com" erzeugen, wenn es noch nicht existiert.
    // Eine Exception auslösen, wenn es existiert, aber kein Objekt ist.
    var com;
    if (!com) com = {};
    else if (typeof com != "object")
      throw new Error("com existiert bereits, ist aber kein Objekt");

    // Den Erzeugungs- und Typprüfungscode für die nächste Stufe wiederholen.
    if (!com.davidflanagan) com.davidflanagan = {}
    else if (typeof com.davidflanagan != "object")
      throw new Error("com.davidflanagan existiert bereits, ist aber kein Objekt");

    // Eine Exception auslösen, wenn com.davidflanagan.Class already bereits existiert.
    if (com.davidflanagan.Class)
      throw new Error("com.davidflanagan.Class already exists");

    // Andernfalls den Namensraum mit einem großen Objekt-Literal erzeugen und bevölkern.
    com.davidflanagan.Class = {
      define: function(data) { /* hier kommt der Code hin */ },
      provides: function(o, c) { /* hier kommt der Code hin */ }
    };

## 10.1.1 Die Verfügbarkeit eines Moduls prüfen

Wenn Sie Code schreiben, der von einem externen Modul abhängig ist, können Sie die Verfügbarkeit dieses Moduls prüfen, indem Sie einfach auf seinen Namensraum prüfen. Der Code dafür ist etwas verzwickt, weil er verlangt, dass Sie auf jede Komponente des Namensraums prüfen. Beachten Sie, dass dieser Code das globale Symbol `com` deklariert, bevor er auf seine Existenz prüft. Sie müssen das genau so tun wie bei der Definition des Namensraums:

    var com; // Das globale Symbol deklarieren, bevor seine Existenz geprüft wird
    if (!com || !com.davidflanagan || !com.davidflanagan.Class)
      throw new Error("com/davidflanagan/Class.js wurde nicht geladen");

Wenn Modulautoren einer konsistenten Versionierungskonvention folgen würden – die Versionsnummer eines Moduls beispielsweise über die `VERSION`-Eigenschaft des Namensraums des Moduls veröffentlichen würden, wäre es nicht nur möglich, auf die Existenz eines Moduls zu prüfen, sondern auch auf die Verfügbarkeit einer bestimmten Version des Moduls. Am Ende dieses Kapitels werde ich ein Beispiel dafür liefern, wie man das genau macht.

## 10.1.2 Klassen als Module

Das in Beispiel 10-1 verwendete »Class«-Modul ist einfach eine zusammenwirkende Sammlung von Hilfsfunktionen. Es gibt jedoch keine Einschränkungen in Bezug darauf, was ein Modul sein kann. Es könnte eine einzige JavaScript-Funktion, eine JavaScript-Klasse oder auch eine Sammlung zusammenwirkender Klassen und Funktionen sein.

Beispiel 10-2 zeigt Code, der ein Modul erzeugt, das aus einer einzigen Klasse besteht. Das Modul stützt sich auf das hypothetische Modul `Class` und seine Funktion `define( )`. (Siehe Beispiel 9-10, wenn Sie vergessen haben, wie diese Hilfsfunktion funktioniert.)

Beispiel 10-2. Eine Klasse für komplexe Zahlen als Modul

    /**
     * com/davidflanagan/Komplex.js: Eine Klasse, die komplexe Zahlen repräsentiert
     *
     * Dieses Modul definiert die Konstruktorfunktion com.davidflanagan.Komplex( ).
     * Dieses Modul erfordert das Modul com/davidflanagan/Class.js.
     **/
    // Erst wird auf das Class-Modul geprüft.
    var com; // Das globale Symbol deklarieren, bevor seine Existenz geprüft wird
    if (!com || !com.davidflanagan || !com.davidflanagan.Class)
      throw new Error("com/davidflanagan/Class.js wurde nicht geladen");

    // Durch diesen Test wissen wir, dass der Namensraum com.davidflanagan
    // existiert. Wir müssen ihn hier also nicht erzeugen. Wir werden in ihm einfach
    // unsere Komplex-Klasse definieren.
    com.davidflanagan.Komplex = com.davidflanagan.Class.define({
      name: "Komplex",
      construct: function(x,y) { this.x = x; this.y = y; },
      methods: {
        add: function(c) {
          return new com.davidflanagan.Komplex(this.x + c.x,
                             this.y + c.y);
        }
      },
    });

Sie können auch ein Modul definieren, das aus mehreren Klassen besteht. Beispiel 10-3 ist eine Skizze eines Moduls, das verschiedene Klassen definiert, die geometrische Figuren repräsentieren.

Beispiel 10-3. Ein Modul mit Figur-Klassen

    /**
     * com/davidflanagan/Figuren.js: Ein Modul mit Klassen, die Figuren repräsentieren
     *
     * Dieses Modul definiert Klassen im Namensraum com.davidflanagan.figuren.
     * Dieses Modul erfordert das Modul com/davidflanagan/Class.js.
     **/
    // Erst wird auf das Class-Modul geprüft
    var com; // Das globale Symbol deklarieren, bevor auf seine Existenz geprüft wird
    if (!com || !com.davidflanagan || !com.davidflanagan.Class)
      throw new Error("com/davidflanagan/Class.js wurde nicht geladen");

    // Ein Symbol aus diesem Modul importieren.
    var define = com.davidflanagan.Class.define;

    // Durch den Test auf das Class-Modul wissen wir, dass der Namensraum
    // com.davidflanagan existiert, wir müssen ihn hier also nicht erzeugen.
    // Wir erzeugen in ihm einfach unseren figuren-Namensraum.
    if (com.davidflanagan.figuren)
      throw new Error("com.davidflanagan.figuren-Namensraum existiert bereits");

    // Den Namensraum erzeugen
    com.davidflanagan.figuren = {};

    // Jetzt die Klassen definieren und ihre Konstruktorfunktionen in unserem Namensraum
    // speichern.
    com.davidflanagan.figuren.Kreis = define({ /* hier kommen die Klassendaten hin */ });
    com.davidflanagan.figuren.Rechteck = define({ /* hier kommen die Klassendaten hin */ });
    com.davidflanagan.figuren.Dreieck = define({ /* hier kommen die Klassendaten hin */});

## 10.1.3 Initialisierungscode für Module

Wir neigen dazu, uns JavaScript-Module als Sammlungen von Funktionen (oder Klassen) vorzustellen. An den vorangehenden Beispielen haben Sie aber gesehen, dass Module mehr machen, als Funktionen zu definieren, die später aufgerufen werden können. Sie können auch Code ausführen, wenn sie zum ersten Mal geladen werden, um ihren Namensraum einzurichten und zu füllen. Ein Modul kann eine beliebige Menge von Einweg-Code dieser Art erzeugen, und es ist vollkommen zulässig, Module zu schreiben, die keine Funktionen und Klassen definieren und einfach nur etwas Code ausführen. Die einzige Regel ist, dass sie den globalen Namensraum nicht vollstopfen dürfen. Am Besten strukturiert man ein Modul dieser Art, indem man den Code in eine anonyme Funktion steckt, die unmittelbar nach der Definition aufgerufen wird:

    (function( ) { // Die anonyme Funktion definieren. Kein Name heißt, dass es kein
             // globales Symbol gibt.
      // Hier kommt der Code hin.
      // Alle Variablen sind sicher in diese Funktion eingebettet,
      // es werden also keine globalen Symbole erzeugt.
    })( );     // Die Funktionsdefinition beenden und die Funktion aufrufen.

Einige Module können ihren eigenen Initialisierungscode ausführen, wenn sie geladen werden. Andere brauchen eine Initialisierungsfunktion, die zu einem späteren Zeitpunkt aufgerufen wird. Das ist bei clientseitigem JavaScript üblich: Bei Modulen, die auf HTML-Dokumenten operieren sollen, muss der Initialisierungscode in der Regel angestoßen werden, wenn das HTML-Dokument vollständig in den Webbrowser geladen wurde.

Ein Modul kann eine passive Herangehensweise für das Initialisierungsproblem verwenden, indem es einfach eine Initialisierungsfunktion definiert und dokumentiert und zum erforderlichen Zeitpunkt durch den Benutzer aufrufen lässt. Das ist ein sicheres und konservatives Verfahren, verlangt aber ein HTML-Dokument, das zumindest so viel JavaScript-Code enthält, wie zur Initialisierung der Module erforderlich ist, die auf ihm operieren.

Es gibt eine Denkschule (die _barrierefreies JavaScript_ genannt und in "13.1.5 Barrierefreies JavaScript" beschrieben wird), die sagt, dass Module vollkommen in sich abgeschlossen sein sollten und dass HTML-Dokumente überhaupt keinen JavaScript-Code enthalten sollten. Um Module zu schreiben, die in diesem Grad abgeschlossen sind, müssen Sie Ihre Initialisierungsfunktionen aktiv registrieren, damit sie zum erforderlichen Zeitpunkt aufgerufen werden.

Beispiel 10-5 am Ende dieses Kapitels schließt eine Initialisierungslösung ein, die es Modulen ermöglicht, Initialisierungsfunktionen aktiv zu registrieren. Wenn sie in einem Webbrowser ausgeführt werden, werden alle registrierten Funktionen automatisch als Antwort darauf ausgeführt, dass der Webbrowser das `onload`-Event sendet. (In Kapitel 17 werden Sie alles zu clientseitigen Events und Event-Handlern lernen.)

* * *

37] Kern-JavaScript bietet keinen Mechanismus zum Laden oder Einschließen externer Codemodule. Das liegt im Verantwortungsbereich der jeweiligen Umgebung, in die der JavaSript-Interpreter eingebettet ist. In clientseitigem JavaScript erfolgt das über das `<script src=>`-Tag (siehe [Kapitel 13). Andere Einbettungsmechanismen können eine einfache `load( )`-Funktion zum Laden von Modulen bieten.

# 10.2 Symbole aus Namensräumen importieren

Das Problem mit eindeutigen Namensraumnamen wie `com.davidflanagan.Class` ist, dass sie dazu neigen, zu längeren Funktionsnamen zu führen – z.B. `com.davidflanagan.Class. define( )`. Das ist der vollständig qualifizierte Name der Funktion, aber Sie müssen ihn nicht jedes Mal auf diese Weise eingeben. Da JavaScript-Funktionen Daten sind, können Sie sie hinstecken, wohin Sie möchten. Nachdem er das Modul `com.davidflanagan.Class` geladen hat, könnte ein Benutzer beispielsweise Folgendes schreiben:

    // Das ist ein einfacherer Name, um Tipperei zu sparen.
    var define = com.davidflanagan.Class.define;

Der Modulentwickler hat die Pflicht, Namensräume zu verwenden, um Kollisionen zu vermeiden. Aber der Modulbenutzer hat das Recht, Symbole aus dem Modulnamensraum in den globalen Namensraum zu importieren. Der Programmierer, der das Modul verwendet, weiß, welche anderen Module verwendet werden und welche Namenskonflikte auftreten können. Er kann festlegen, welche Symbole importiert werden und wie sie importiert werden, um Kollisionen zu verhindern.

Beachten Sie, dass das vorangehende Fragment das globale Symbol `define` für eine Hilfsfunktion zur Klassendefinition verwendet. Das ist keine gute Wahl für eine globale Funktion, weil der Name nicht sagt, was definiert wird. Eine Alternative wäre es, den Namen zu ändern:

    var defineClass = com.davidflanagan.Class.define;

Aber die Änderung von Methodennamen ist ebenfalls nicht vollkommen zufriedenstellend. Ein anderer Programmierer, der das Modul zuvor bereits verwendet hat, könnte den Namen `defineClass( )` verwirrend finden, weil er die Funktion unter dem Namen `define( )` kennt. Außerdem stecken Modulentwickler häufig eine ganze Menge Denkarbeit in ihre Funktionsnamen. Die Änderung dieser Namen könnte dem Modul nicht gerecht werden. Eine andere Alternative ist es, den globalen Namensraum zu verwenden, um Symbole in einen leichter zu tippenden Namensraum zu importieren:

    // Einen einfachen Namensraum erzeugen. Es ist keine Fehlerprüfung erforderlich.
    // Der Modulnutzer weiß, das dieses Symbol noch nicht existiert.
    var Class = {};
    // Jetzt wird ein Symbol in diesen neuen Namensraum importiert.
    Class.define = com.davidflanagan.Class.define;

Es gibt einige wichtige Dinge, die man wissen sollte, wenn man Symbole auf diese Weise importiert. Erstens: _Sie können nur Symbole importieren, die auf eine Funktion, ein Objekt oder ein Array verweisen_. Wenn Sie ein Symbol importieren, dessen Wert ein elementarer Typ wie eine Zahl oder ein String ist, erhalten Sie einfach eine statische Kopie des Werts. Alle Änderungen an diesem Wert erfolgen nur in diesem Namensraum und werden von der importierten Kopie des Werts nicht wiedergegeben. Nehmen Sie an, dass die Methode `Class.define( )` die Anzahl der Klassen nachhält, die sie definiert hat, und jedes Mal, wenn sie aufgerufen wird, den Wert von `com.davidflanagan.Class.counter` inkrementiert. Wenn Sie versuchen, diesen Wert zu importieren, erhalten Sie einfach nur eine statische Kopie des aktuellen Werts:

    // Legt nur eine statische Kopie an. Änderungen im Namensraum werden in dieser
    // importierten Eigenschaft nicht wiedergegeben, dass sie einen elementaren
    // Wert enthält.
    Class.counter = com.davidflanagan.Class.counter;

Die Lektion für Modulentwickler ist, dass sie importierbare Zugriffsmethoden anbieten sollten, wenn ihre Module Eigenschaften definieren, die auf elementare Werte verweisen:

    // Eine Eigenschaft elementaren Typs. Sie kann nicht importiert werden.
    com.davidflanagan.Class.counter = 0;

    // Hier ist eine Zugriffsmethode, die importiert werden kann.
    com.davidflanagan.Class.getCounter = function( ) {
      return com.davidflanagan.Class.counter;
    }

Der zweite wichtige Punkt, den man zu Importen wissen sollte, ist, dass sie für die Benutzer eines Moduls gedacht sind. _Modulentwickler müssen grundsätzlich vollständig qualifizierte Namen für ihre Symbole verwenden_. Das können Sie in der gerade gezeigten `getCounter( )`-Methode sehen. Da JavaScript keine eingebaute Unterstützung für Module und Namensräume bietet, gibt es hier keine Kurzformen. Sie müssen den vollständig qualifizierten Namen der Eigenschaft `counter` eingeben, obwohl diese Eigenschaft und die Zugriffsmethode `getCounter( )` beide Teil des gleichen Namensraums sind. Modulautoren dürfen nicht voraussetzen, dass ihre Funktionen in den globalen Namensraum importiert werden. Funktionen, die andere Funktionen im Modul aufrufen, müssen ebenfalls die vollständig qualifizierten Namen verwenden, damit sie korrekt funktionieren, wenn sie aufgerufen werden, ohne dass sie zuvor importiert wurden. (Eine Ausnahme zu dieser Regel, die Closures betrifft, wird in "10.2.2 Closures als privater Namensraum und Geltungsbereich" erörtert.)

## 10.2.1 Öffentliche und private Symbole

Nicht alle Symbole, die im Namensraum eines Moduls definiert werden, sind zur externen Verwendung gedacht. Ein Modul kann seine eigenen internen Funktionen und Variablen haben, die nicht direkt von den Skripten verwendet werden sollen, die das Modul nutzen. JavaScript bietet keinerlei Möglichkeit anzugeben, dass einige Eigenschaften in einem Namensraum öffentlich sind und andere nicht. Auch hier stützen wir uns also wieder auf eine Konvention, um eine ungeeignete Verwendung der privaten Eigenschaften eines Moduls von außerhalb des Moduls zu verhindern.

Das geradlinigste Verfahren ist eine einfache Dokumentation. Der Modulentwickler sollte klar dokumentieren, welche Funktionen und andere Eigenschaften die öffentliche API des Moduls ausmachen. Umgekehrt sollte der Modulnutzer seine Verwendung des Moduls auf diese öffentliche API beschränken und der Versuchung widerstehen, andere Funktionen aufzurufen oder auf andere Eigenschaften zuzugreifen.

Eine Konvention, die dabei helfen kann, die Unterscheidung zwischen öffentlich und privat auch ohne einen Verweis auf die Dokumentation klarzumachen, besteht darin, den privaten Symbolen einen Unterstrich voranzustellen. In der Beschreibung der Zugriffsfunktion `getCounter( )` können Sie verdeutlichen, dass die Eigenschaft `counter` privat ist, indem Sie ihren Namen in `_counter` ändern. Das verhindert nicht, dass externer Code diese Eigenschaft verwendet, sorgt aber besser dagegen vor, dass ein Programmierer aus Versehen eine private Eigenschaft nutzt.

Module, die über JSAN verteilt werden, gehen einen Schritt weiter. Die Moduldefinition schließt Arrays ein, die die öffentlichen Symbole aufführen. Das JSAN-Modul namens _JSAN_ schließt Hilfsfunktionen für den Import von Symbolen aus einem Modul ein, und diese Funktionen weigern sich, Symbole zu importieren, die nicht explizit als öffentlich aufgeführt sind.

## 10.2.2 Closures als privater Namensraum und Geltungsbereich

Erinnern Sie sich aus "8.8 Funktionsgeltung und Closures", dass eine Closure eine Funktion plus der Geltungsbereichskette ist, die wirksam war, als die Funktion definiert wurde.[38] Indem Sie eine Funktion definieren, können Sie deswegen Ihren lokalen Geltungsbereich als privaten Namensraum verwenden. Eingebettete Funktionen, die in äußeren Funktionen definiert werden, haben Zugriff auf diesen privaten Namensraum. Dieses Verfahren hat zwei Vorteile. Da der private Namensraum das erste Objekt in der Geltungsbereichskette ist, können Funktionen im Namensraum andere Funktionen und Eigenschaften aus diesem Namensraum referenzieren, ohne dass dazu ein vollständig qualifizierter Name erforderlich ist.

Der zweite Vorteil der Verwendung einer Funktion zur Definition eines privaten Namensraums ist, dass sie richtig privat ist. Es gibt keine Möglichkeit, von außerhalb der Funktion auf die Symbole zuzugreifen, die in der Funktion definiert sind. Diese Symbole sind nur verfügbar, wenn sie von der Funktion, die sie enthält, in einen externen öffentlichen Namensraum exportiert werden. Das bedeutet, dass sich ein Modul entschließen kann, nur seine öffentlichen Funktionen zu exportieren und Implementierungsdetails wie Hilfsmethoden und Variablen in der Privatsphäre der Closure verschlossen zu halten.

Beispiel 10-4 hilft, diesen Punkt zu illustrieren. Es verwendet eine Closure, um einen privaten Namensraum zu erzeugen, und exportiert dann seine öffentlichen Methoden in einen öffentlichen Namensraum.

Beispiel 10-4. Einen privaten Namensraum mit einer Closure erzeugen

    // Das Namensraumobjekt erzeugen. Der Kürze halber wird die Fehlerprüfung weggelassen.
    var com;
    if (!com) com = {};
    if (!com.davidflanagan) com.davidflanagan = {};
    com.davidflanagan.Class = {};

    // Diesmal stecken wir nichts direkt in den Namensraum.
    // Stattdessen verwenden wir eine anonyme Funktion, die wir definieren und aufrufen,
    // um die Closure zu erzeugen, die uns als privater Namensraum dient. Diese Funktion
    // wird ihre öffentlichen Symbole aus der Closure in das Objekt com.davidflanagan.Class
    // exportieren. Beachten Sie, dass wir eine unbenannte Funktion verwenden,
    // damit wir keine zusätzlichen globalen Symbole erzeugen.
    (function( ) { // Anonyme Funktionsdefinition beginnen
      // Eingebettete Funktionen erzeugen Symbole in der Closure.
      function define(data) { counter++; /* hier mehr Code */ }
      function provides(o, c) { /* Code hier */ }

      // Lokale Variablen sind Symbole in der Closure.
      // Diese wird privat für die Closure bleiben.
      var counter = 0;

      // Diese Funktion kann auf die Variable über einen einfachen Namen verweisen,
      // anstatt sie mit einem Namensraum qualifizieren zu müssen.
      function getCounter( ) { return counter; }

      // Jetzt, wo wir die Eigenschaften definiert haben, die wir privat in unserer
      // Closure haben wollen, können wir die öffentlichen in den öffentlichen
      // Namensraum exportieren und die privaten verborgen lassen.
      var ns = com.davidflanagan.Class;
      ns.define = define;
      ns.provides = provides;
      ns.getCounter = getCounter;
    })( );     // Ende der anonymen Funktion und Aufruf.

* * *

38] Closures sind ein fortgeschrittener Gegenstand, und falls Sie seine Behandlung in [Kapitel 8 übersprungen haben, sollten Sie zu diesem Abschnitt zurückkehren, nachdem Sie die entsprechenden Stellen über Closures gelesen haben.

# 10.3 Modulwerkzeuge

Dieser Abschnitt präsentiert ein ausführlicheres Beispiel (Beispiel 10-5) für ein Modul modulbezogener Werkzeuge. Das `Module.createNamespace( )`-Hilfswerkzeug kümmert sich um die Erzeugung von Namenräumen und die Fehlerprüfung. Ein Modulautor könnte dieses Modul folgendermaßen verwenden:

    // Einen Namensraum für unser Modul erzeugen.
    Module.createNamespace("com.davidflanagan.Class");

    // Jetzt können wir beginnen, den Namensraum zu füllen.
    com.davidflanagan.Class.define = function(data) { /* hier kommt der Code hin */ };
    com.davidflanagan.Class.provides = function(o, c) { /* hier kommt der Code hin */ };

Die Funktion `Module.require( )` prüft, ob die angegebene (oder eine neuere) Version eines angegebenen Moduls verfügbar ist, und löst einen Fehler aus, wenn das nicht der Fall ist. Verwenden Sie sie folgendermaßen:

    // Das Modul Komplex erfordert, dass zuerst das Modul Class geladen wird.
    Module.require("com.davidflanagan.Class", 1.0);

Die Funktion `Module.importSymbols( )` vereinfacht das Importieren von Symbolen in den globalen oder einen anderen angegebenen Namensraum. Hier ist ein Beispiel für seine Verwendung:

    // Die Default-Menge der Module-Symbole in den globalen Namensraum importieren.
    // Eines dieser Default-Symbole ist importSymbols selbst.
    Module.importSymbols(Module); // Hinweis: Wir übergeben den Namensraum, nicht den
                   // Modulnamen

    // Die Klasse Komplex in den globalen Namensraum importieren.
    importSymbols(com.davidflanagan.Komplex);

    // Die Methode com.davidflanagan.Class.define( ) in ein Class-Objekt importieren.
    var Class = {};
    importSymbols(com.davidflanagan.Class, Class, "define");

Schließlich ermöglicht es die Funktion `Module.registerInitializationFunction( )` einem Modul, eine Funktion mit Initialisierungscode zu registrieren, der zu einem späteren Zeitpunkt ausgeführt werden soll.[39] Wenn diese Funktion in clientseitigem JavaScript verwendet wird, wird automatisch ein Event-Handler registriert, um alle Initialisierungsfunktionen für alle geladenen Module aufzurufen, wenn das Laden des Dokuments abgeschlossen ist. Wird sie in einem anderen, nicht-clientseitigen Kontext verwendet, werden die Initialisierungsfunktionen nicht automatisch aufgerufen, können aber explizit mit `Module.runInitializationFunctions( )` aufgerufen werden.

Das Module-Modul wird in Beispiel 10-5 gezeigt. Dieses Beispiel ist lang. Aber es lohnt sich, es sorgfältig durchzuarbeiten. Eine Dokumentation und vollständige Informationen zu allen Hilfsfunktionen finden Sie im Code.

Beispiel 10-5. Ein Modul mit modulbezogenen Werkzeugen

    /**
     * Module.js: Hilfsfunktionen für Module und Namensräume
     *
     * Dies ist ein Modul modulbezogener Hilfsfunktionen, die mit
     * JSAN-artigen Modulen kompatibel sind.
     * Dieses Modul definiert den Namensraum Module.
     */

    // Prüfen, ob Modul noch nicht existiert.
    var Module;
    if (Module && (typeof Module != "object" || Module.NAME))
      throw new Error("Der Namensraum 'Module' besteht bereits");

    // Unseren Namensraum erzeugen.
    Module = {};

    // Das sind einige Metainformationen zu diesem Namensraum.
    Module.NAME = "Module";  // Der Name des Namensraums
    Module.VERSION = 0.1;   // Die Version des Namensraums

    // Das ist die Liste der öffentlichen Symbole, die aus diesem Namensraum exportiert werden.
    // Diese sind für Programmierer interessant, die Module verwenden.
    Module.EXPORT = ["require", "importSymbols"];

    // Das sind andere Symbole, die exportiert werden dürfen. Das sind solche, die
    // normalerweise nur von Modulautoren verwendet werden und üblicherweise nicht importiert
    // werden.
    Module.EXPORT_OK = ["createNamespace", "isDefined",
              "registerInitializationFunction",
              "runInitializationFunctions",
              "modules", "globalNamespace"];

    // Jetzt beginnen wir, dem Namensraum Symbole hinzuzufügen.
    Module.globalNamespace = this; // Damit wir immer auf den globalen
                    // Geltungsbereich verweisen können
    Module.modules = { "Module": Module }; // Modul-Name/Namensraum-Zuordnung.

    /**
     * Diese Funktion erzeugt ein Namensraum-Objekt für den angegebenen Namen und liefert
     * es zurück. Sie führt außerdem eine nützliche Fehlerprüfung durch, um zu sichern,
     * dass der Name nicht in Konflikt mit zuvor geladenen Modulen gerät. Sie löst
     * einen Fehler aus, wenn der Namensraum bereits existiert oder wenn irgendwelche
     * der Eigenschaftskomponenten des Namensraums bereits existieren und keine Objekte sind.
     *
     * Setzt die NAME-Eigenschaft des neuen Namensraums auf den Namen.
     * Wenn das Versionsargument angegeben ist, wird die VERSION-Eigenschaft
     * des Namensraums gesetzt.
     *
     * Dem Module.modules-Objekt wird eine Zuordnung für den neuen Namensraum hinzugefügt.
     */
    Module.createNamespace = function(name, version) {
      // Die Gültigkeit des Namens prüfen. Er muss existieren, darf nicht mit einem
      // Punkt beginnen oder enden oder zwei aufeinanderfolgende Punkte enthalten.
      if (!name) throw new Error("Module.createNamespace( ): Name erforderlich");
      if (name.charAt(0) == '.' ||
        name.charAt(name.length-1) == '.' ||
        name.indexOf("..") != −1)
        throw new Error("Module.createNamespace( ): Unzulässiger Name: " + name);

      // Den Namen an den Punkten zerlegen und die benötigte Objekthierarchie erstellen.
      var parts = name.split('.');

      // Für jede Namensraumkomponente wird entweder ein Objekt erzeugt oder sichergestellt,
      // dass bereits ein Objekt dieses Namens existiert.
      var container = Module.globalNamespace;
      for(var i = 0; i < parts.length; i++) {
        var part = parts[i];
        // Wenn der Container keine Eigenschaft dieses Namens hat,
        // wird ein leeres Objekt erzeugt.
        if (!container[part]) container[part] = {};
        else if (typeof container[part] != "object") {
          // Wenn es bereits eine Eigenschaft dieses Namens gibt,
          // wird geprüft, ob es ein Objekt ist.
          var n = parts.slice(0,i).join('.');
          throw new Error(n + " bereits vorhanden und kein Objekt");
        }
        container = container[part];
      }

      // Der zuletzt durchquerte Container von oben ist der Namensraum, den wir brauchen.
      var namespace = container;

      // Es ist ein Fehler, einen Namensraum zweimal zu definieren. Es ist OK, wenn
      // das Namensraumobjekt bereits besteht, aber für es darf noch keine
      // NAME-Eigenschaft definiert sein.
      if (namespace.NAME) throw new Error("Modul "+name+" ist bereits definiert");

      // Die Namens- und Versionsfelder des Namensraums initialisieren.
      namespace.NAME = name;
      if (version) namespace.VERSION = version;

      // Diesen Namensraum in der Liste aller Module registrieren.
      Module.modules[name] = namespace;

      // Das Namensraumobjekt an den Aufrufer zurückliefern.
      return namespace;
    }
    /**
     * Prüft, ob das Modul mit dem angegebenen Namen definiert ist.
     * Liefert true, wenn es definiert ist, andernfalls false.
     */
    Module.isDefined = function(name) {
      return name in Module.modules;
    };

    /**
     * Diese Funktion löst einen Fehler aus, wenn das angegebene Modul nicht
     * definiert ist oder es zwar definiert ist, die Version aber kleiner
     * als die angegebene Version ist. Wenn der Namenraum existiert und eine
     * geeignete Version hat, kehrt die Funktion einfach zurück, ohne etwas
     * zu tun. Verwenden Sie diese Funktion, um einen fatalen Fehler auszulösen,
     * wenn die Module, die Ihr Code benötigt, nicht vorhanden sind.
     */
    Module.require = function(name, version) {
      if (!(name in Module.modules)) {
        throw new Error("Modul " + name + " ist nicht definiert");
      }

      // Wurde keine Version angegeben, gibt es nichts zu prüfen.
      if (!version) return;

      var n = Module.modules[name];

      // Wenn die definierte Version kleiner als die angeforderte Version ist
      // oder der Namensraum keine Version deklariert, wird ein Fehler ausgelöst.
      if (!n.VERSION || n.VERSION < version)
      throw new Error("Modul " + name + " hat Version " +
              n.VERSION + " aber Version " + version +
              " oder höher ist erforderlich.");
    };

    /**
     * Diese Funktion importiert Symbole aus einem angegebenen Modul. Standardmäßig
     * importiert sie sie in den globalen Namensraum, aber als zweites
     * Argument kann ein anderes Ziel angegeben werden.
     *
     * Wenn keine Symbole explizit angegeben werden, werden die Symbole im EXPORT-Array
     * des Moduls importiert. Wenn dieses Array nicht definiert ist und kein
     * EXPORT_OK definiert ist, werden alle Symbole des Moduls importiert.
     *
     * Um explizit einen bestimmten Satz von Symbolen zu importieren, übergeben Sie
     * ihre Namen als Argumente nach dem Modul und dem optionalen Zielnamensraum.
     * Wenn die Module ein EXPORT- oder EXPORT_OK-Array definieren, werden Symbole
     * nur importiert, wenn sie in einem dieser Arrays aufgeführt werden.
     */
    Module.importSymbols = function(from) {
      // Sicherstellen, dass das Modul richtig angegeben wurde. Wir erwarten, dass
      // es das Namensraumobjekt des Moduls auch mit einem String probieren wird.
      if (typeof from == "string") from = Module.modules[from];
      if (!from || typeof from != "object")
        throw new Error("Module.importSymbols( ): " +
                "Namensraumobjekt erforderlich");

      // Auf den Quellnamensraum können ein optionaler Zielnamensraum und die
      // Namen von einem oder mehreren zu importierenden Symbolen folgen.
      var to = Module.globalNamespace; // Default-Ziel
      var symbols = [];        // Standardmäßig gibt es keine Symbole
      var firstsymbol = 1;       // Index in arguments für den ersten Symbolnamen

      // Prüfen, ob ein Zielname angegeben wurde.
      if (arguments.length > 1 && typeof arguments[1] == "object") {
        if (arguments[1] != null) to = arguments[1];
        firstsymbol = 2;
      }

      // Jetzt die Liste der angegebenen Symbole einlesen.
      for(var a = firstsymbol; a < arguments.length; a++)
        symbols.push(arguments[a]);

      // Wurden keine zu importierenden Symbole angegeben, wird eine vom
      // Modul definierte Liste importiert oder es werden einfach alle Symbole importiert.
      if (symbols.length == 0) {
        // Wenn das Modul ein EXPORT-Array definiert, werden alle
        // Symbole in diesem Array importiert.
        if (from.EXPORT) {
          for(var i = 0; i < from.EXPORT.length; i++) {
            var s = from.EXPORT[i];
            to[s] = from[s];
          }
          return;
        }
        // Andernfalls wird alles im Namensraum des Moduls importiert,
        // falls das Symbol kein EXPORT_OK-Array definiert.
        else if (!from.EXPORT_OK) {
          for(s in from) to[s] = from[s];
          return;
        }
      }

      // Wenn wir an diesen Punkt kommen, wurde explizit ein Array mit zu importierenden
      // Symbolen angegeben. Wenn der Namensraum EXPORT- und/oder EXPORT_OK-Arrays
      // definiert, wird vor dem Import geprüft, ob die einzelnen Symbole in
      // ihnen aufgeführt werden. Es wird ein Fehler ausgelöst, wenn ein
      // angefordertes Symbol nicht existiert oder nicht exportiert werden darf.
      var allowed;
      if (from.EXPORT || from.EXPORT_OK) {
        allowed = {};
        // Alle Symbole aus den Arrays in Eigenschaften eines Objekts kopieren.
        // So können wir effizienter prüfen, ob ein Symbol zulässig ist.
        if (from.EXPORT)
          for(var i = 0; i < from.EXPORT.length; i++)
            allowed[from.EXPORT[i]] = true;
        if (from.EXPORT_OK)
          for(var i = 0; i < from.EXPORT_OK.length; i++)
            allowed[from.EXPORT_OK[i]] = true;
      }

      // Die Symbole importieren.
      for(var i = 0; i < symbols.length; i++) {
        var s = symbols[i];       // Der Name des zu importierenden Symbols
        if (!(s in from))        // Sicherstellen, dass es existiert
          throw new Error("Module.importSymbols( ): Symbol " + s +
                  " nicht definiert");
        if (allowed && !(s in allowed)) // Sicherstellen, dass es ein öffentliches Symbol ist
          throw new Error("Module.importSymbols( ): Symbol " + s +
                  " ist nicht öffentlich und kann nicht importiert werden.");
        to[s] = from[s];         // Symbol importieren
      }
    };

    // Module nutzen diese Funktion, um eine oder mehrere Initialisierungsfunktionen
    // zu registrieren.
    Module.registerInitializationFunction = function(f) {
      // Die Funktion im Array der Initialisierungsfunktionen speichern.
      Module._initfuncs.push(f);
      // Wurde noch kein onload-Event-Handler registriert, registrieren wir ihn jetzt.
      Module._registerEventHandler( );
    }

    // Eine Funktion zum Aufruf aller registrierten Initialisierungsfunktionen.
    // In clientseitigem JavaScript wird sie automatisch aufgerufen, wenn das
    // Laden des Dokuments abgeschlossen ist. In anderen Umgebungen müssen Sie sie
    // explizit aufrufen.
    Module.runInitializationFunctions = function( ) {
      // Alle Initialisierungsfunktionen ausführen und dabei Fehler abfangen
      // und ignorieren, damit ein Fehler bei einem Modul die Initialisierung
      // der anderen Module nicht verhindert.
      for(var i = 0; i < Module._initfuncs.length; i++) {
        try { Module._initfuncs[i]( ); }
        catch(e) { /* Fehler ignorieren */}
      }
      // Das Array löschen, damit die Funktionen nie mehrfach aufgerufen werden.
      Module._initfuncs.length = 0;
    }

    // Ein privates Array, das Initialisierungsfunktionen enthält, die später aufgerufen werden
    // sollen.
    Module._initfuncs = [];

    // Ist das Skript in einen Webbrowser geladen, registriert diese private Funktion
    // einen onload-Event-Handler für die Ausführung der Initialisierungsfunktionen
    // aller geladenen Module. Sie lässt sich nur einmal aufrufen.
    Module._registerEventHandler = function( ) {
      var clientside =  // Auf einige vertraute clientseitige Eigenschaften prüfen
        "window" in Module.globalNamespace &&
        "navigator" in window;

      if (clientside) {
        if (window.addEventListener) { // W3C DOM-Standard-Event-Registrierung
          window.addEventListener("load", Module.runInitializationFunctions,
                      false);
        }
        else if (window.attachEvent) { // IE5+-Event-Registrierung
          window.attachEvent("onload", Module.runInitializationFunctions);
        }
        else {
          // IE4 und alte Browser
          // Wenn <body> ein onload-Tag definiert, wird dieser Event-Listener
          // überschrieben und nie aufgerufen.
          window.onload = Module.runInitializationFunctions;
        }
      }

      // Die Funktion überschreibt sich selbst mit einer leeren Funktion,
      // damit sie nicht mehrfach aufgerufen werden kann.
      Module._registerEventHandler = function( ) {};
    }

* * *

39] In [Beispiel 17-6 finden Sie ein ähnliches Werkzeug zur Registrierung von Initialisierungsfunktionen.

# Kapitel 11. Mustervergleich mit regulären Ausdrücken

Ein _regulärer Ausdruck_ ist ein Objekt, das ein Zeichenmuster beschreibt. Die JavaScript-Klasse RegExp stellt reguläre Ausdrücke dar, und sowohl String als auch RegExp definieren Methoden, die mit regulären Ausdrücken mächtige Mustervergleiche und Suchen-und-Ersetzen-Funktionen im Text ermöglichen.[40]

Reguläre Ausdrücke von JavaScript wurden in ECMAScript v3 standardisiert. JavaScript 1.2 implementiert den Teil der Funktionen für reguläre Ausdrücke, der von ECMAScript v3 verlangt wird, und JavaScript 1.5 implementiert den vollständigen Standard. Reguläre Ausdrücke in JavaScript orientieren sich stark an den regulären Ausdrücken der Programmiersprache Perl. Vereinfacht gesagt implementiert JavaScript 1.2 die regulären Ausdrücke von Perl 4, und JavaScript 1.5 implementiert einen Großteil der regulären Ausdrücke von Perl 5.

In diesem Kapitel wird zuerst die Syntax definiert, mit der reguläre Ausdrücke Textmuster beschreiben. Dann werden die Methoden von String und RegExp beschrieben, die reguläre Ausdrücke verwenden.

# 11.1 Reguläre Ausdrücke definieren

In JavaScript werden reguläre Ausdrücke durch RegExp-Objekte dargestellt. RegExp-Objekte können natürlich mit dem Konstruktor `RegExp( )` erzeugt werden, aber häufiger wird eine spezielle Literalsyntax verwendet. So, wie String-Literale als Zeichen in Anführungszeichen angegeben werden, werden reguläre Ausdrücke als Zeichen in Schrägstrichen (`/`) angegeben. Ihr JavaScript-Code kann also zum Beispiel folgende Zeile enthalten:

    var muster = /s$/;

Diese Zeile erzeugt ein neues RegExp-Objekt und weist es der Variablen `muster` zu. Dieses besondere RegExp-Objekt entspricht jedem String, der mit dem Buchstaben »s« endet. (Wir werden uns gleich mit der Grammatik für die Definition von Mustern befassen.) Dieser reguläre Ausdruck hätte dementsprechend auch mit dem Konstruktor `RegExp( )` definiert werden können:

    var muster = new RegExp("s$");

Das Erzeugen eines RegExp-Objekts mit der Literalsyntax oder mit dem Konstruktor `RegExp( )` ist der einfache Teil. Die schwierigere Aufgabe ist es, das gewünschte Zeichenmuster mit der Syntax der regulären Ausdrücke zu beschreiben. JavaScript übernimmt ziemlich viel von der Syntax von regulären Ausdrücken, die von Perl verwendet wird. Wenn Sie also ein erfahrener Perl-Programmierer sind, wissen Sie bereits, wie in JavaScript Muster beschrieben werden.

Musterspezifikationen für reguläre Ausdrücke bestehen aus einer Reihe von Zeichen. Die meisten Zeichen, darunter auch alle alphanumerischen, beschreiben einfach Zeichen, die wortwörtlich übereinstimmen müssen. Daher entspricht der reguläre Ausdruck `/java/` jedem String, der den Teilstring »java« enthält. Andere Zeichen werden in regulären Ausdrücken nicht wortwörtlich verwendet, sondern haben eine besondere Bedeutung. Der reguläre Ausdruck `/s$/` enthält zum Beispiel zwei Zeichen. Das erste, das »s«, passt wortwörtlich auf sich selbst. Das zweite, das »$«, ist ein besonderes Metazeichen, das das Ende des Strings bezeichnet. Daher entspricht dieser reguläre Ausdruck allen Strings, die den Buchstaben s als letztes Zeichen enthalten.

Die folgenden Abschnitte beschreiben die verschiedenen Zeichen und Metazeichen, die in JavaScript in regulären Ausdrücken verwendet werden. Beachten Sie aber, dass dieses Buch kein vollständiges Tutorial über die Grammatik von regulären Ausdrücken bieten kann. Alle Einzelheiten zur Syntax finden Sie in einem Buch über Perl, zum Beispiel _Programmieren mit Perl_ von Larry Wall et al. (O'Reilly Verlag). _Reguläre Ausdrücke_ von Jeffrey E.F. Friedl (O'Reilly Verlag) ist eine weitere hervorragende Informationsquelle für reguläre Ausdrücke.

## 11.1.1 Literalzeichen

Wie wir gesehen haben, entsprechen alle alphabetischen Zeichen und alle Ziffern in regulären Ausdrücken wortwörtlich sich selbst. Die Syntax von regulären Ausdrücken in JavaScript unterstützt auch bestimmte nicht-alphabetische Zeichen über Escape-Sequenzen, die mit einem Backslash (`\`) beginnen. Die Sequenz `\n` entspricht zum Beispiel einem Zeilenendezeichen in einem String. Tabelle 11-1 listet diese Zeichen auf.

Tabelle 11-1. Literalzeichen in regulären Ausdrücken

Zeichen | Übereinstimmung

---|---

alphanumerische Zeichen | mit sich selbst

`\0` | das NUL-Zeichen `(\u0000`)

`\t` | Tabulator `(\u0009`)

`\n` | Zeilenende (`\u000A`)

`\v` | Vertikaler Tabulator (`\u000B`)

`\f` | Seitenvorschub (`\u000C`)

`\r` | Wagenrücklauf (Carriage Return `\u000D`)

`\x` _`nn`_ | Das lateinische Zeichen, das von der Hexadezimalzahl _`nn`_ angegeben wird. Zum Beispiel ist `\x0A` das Gleiche wie `\n.`

`\u` _`xxxx`_ | Das Unicode-Zeichen, das von der Hexadezimalzahl _`xxxx`_ angegeben wird. Zum Beispiel ist `\u0009` das Gleiche wie `\t.`

`\c` _`X`_ | Das Steuerzeichen `^` _`X`_. Zum Beispiel entspricht `\cJ` dem Zeilenendezeichen `\n.`

Einige Interpunktionszeichen haben in regulären Ausdrücken eine besondere Bedeutung. Diese sind:

    ^ $ . * + ? = ! : | \ / ( ) [ ] { }

Die Bedeutung dieser Zeichen wird in den folgenden Abschnitten besprochen. Einige dieser Zeichen haben nur in einem bestimmten Kontext der regulären Ausdrücke eine besondere Bedeutung und werden in einem anderen Kontext wortwörtlich behandelt. Als Faustregel gilt aber, dass Sie, wenn Sie eines dieser Interpunktionszeichen wörtlich in einem regulären Ausdruck verwenden möchten, ein `\` davor setzen müssen. Andere Interpunktionszeichen wie Fragezeichen und `@` haben keine besondere Bedeutung und stehen in regulären Ausdrücken einfach für sich selbst.

Wenn Sie nicht mehr genau wissen, welche Interpunktionszeichen mit einem Backslash geschützt werden müssen, können Sie vorsichtshalber vor jedes Interpunktionszeichen einen Backslash setzen. Beachten Sie aber, dass viele Buchstaben oder Zahlen andererseits eine besondere Bedeutung haben, wenn ihnen ein Backslash vorangeht, sodass Buchstaben oder Zahlen, die mit sich selbst übereinstimmen sollen, nicht mit einem Backslash geschützt werden dürfen. Um in einem regulären Ausdruck wortwörtlich ein Backslash-Zeichen zu verwenden, müssen Sie es natürlich ebenfalls mit einem Backslash schützen. Der folgende reguläre Ausdruck stimmt zum Beispiel mit jedem String überein, der einen Backslash enthält: `/\\/`.

## 11.1.2 Zeichenklassen

Einzelne Literalzeichen können zu _Zeichenklassen_ kombiniert werden, indem sie in eckige Klammern gesetzt werden. Eine Zeichenklasse passt auf jedes einzelne Zeichen, das in ihr enthalten ist. Daher passt der reguläre Ausdruck `/[abc]/` auf einen beliebigen der Buchstaben a, b oder c. Es können auch negierte Zeichenklassen definiert werden. Diese entsprechen allen Zeichen außer denen, die in den Klammern enthalten sind. Eine negierte Zeichenklasse wird mit einem Caret (^) als erstes Zeichen in der Klammer angegeben. Der reguläre Ausdruck `/[^abc]/` entspricht also jedem beliebigen einzelnen Zeichen außer a, b und c. Sie können in Zeichenklassen einen Bindestrich verwenden, um einen Zeichenbereich anzugeben. Um einen beliebigen Kleinbuchstaben des lateinischen Alphabets zu erfassen, verwenden Sie `/[a-z]/`, und um einen beliebigen Buchstaben des lateinischen Alphabets oder eine Ziffer zu erfassen, verwenden Sie `/[a-zA-Z0-9]/`.

Da bestimmte Zeichenklassen häufig verwendet werden, enthält die Syntax für reguläre Ausdrücke in JavaScript Sonderzeichen und Escape-Sequenzen, um diese häufig verwendeten Klassen darzustellen. So entspricht zum Beispiel `\s` dem Leerzeichen, dem Tabulatorzeichen und jedem anderen Unicode-Zeichen für Whitespace, und `\S` entspricht jedem Zeichen, das kein Unicode-Whitespace ist. Tabelle 11-2 listet diese Zeichen auf und fasst die Zeichenklassensyntax zusammen. (Beachten Sie, dass mehrere dieser Zeichenklassen-Escape-Sequenzen nur ASCII-Zeichen entsprechen und nicht für die Arbeit mit Unicode-Zeichen erweitert wurden. Sie können auch Ihre eigenen Unicode-Zeichenklassen explizit definieren. So entspricht zum Beispiel `/[\u0400-u4FF]/` jedem kyrillischen Zeichen.)

Tabelle 11-2. Zeichenklassen für reguläre Ausdrücke

Zeichen | Übereinstimmung

---|---

`[...]` | Jedes einzelne Zeichen zwischen den Klammern.

`[^...]` | Jedes einzelne Zeichen, das sich nicht in den Klammern befindet.

`.` | Jedes Zeichen außer einem Zeilenumbruch oder anderen Unicode-Zeilenendezeichen.

`\w` | Jedes ASCII-Wortzeichen. Entspricht `[a-zA-Z0-9_]`.

`\W` | Jedes Zeichen, das kein ASCII-Wortzeichen ist. Entspricht `[^a-zA-Z0-9_]`.

`\s` | Jedes Unicode-Whitespace-Zeichen.

`\S` | Jedes Zeichen, das kein Unicode-Whitespace ist. Beachten Sie, dass `\w` und `\S` nicht das Gleiche sind.

`\d` | Jede ASCII-Ziffer. Entspricht `[0-9]`.

`\D` | Jedes Zeichen außer einer ASCII-Ziffer. Entspricht `[^0-9]`.

`[\b]` | Ein wörtlicher Backspace (Sonderfall).

Beachten Sie, dass die Escape-Sequenzen dieser besonderen Zeichenklassen innerhalb von eckigen Klammern verwendet werden können. `\s` entspricht jedem einzelnen Whitespace-Zeichen, und `\d` entspricht jeder einzelnen Zahl, sodass `/[\s\d]/` jedem einzelnen Whitespace-Zeichen oder jeder Ziffer entspricht. Beachten Sie, dass es einen Sonderfall gibt: Wie Sie später sehen werden, hat die Escape-Sequenz `\b` eine spezielle Bedeutung. Wenn sie aber in einer Zeichenklasse verwendet wird, stellt sie das Backspace-Zeichen dar. Um also das Backspace-Zeichen in einem regulären Ausdruck wörtlich zu verwenden, verwenden Sie die Zeichenklasse mit einem einzigen Element: `/[\b]/`.

 **Wiederholungen**

Mit der Syntax der regulären Ausdrücke, die Sie bisher gelernt haben, können Sie eine Zahl mit zwei Ziffern als `/\d\d/` beschreiben und eine Zahl mit vier Ziffern als `/\d\d\d\d/`. Sie haben aber keine Möglichkeit, zum Beispiel eine Zahl zu beschreiben, die eine beliebige Anzahl von Ziffern haben kann, oder einen String mit drei Buchstaben, gefolgt von einer optionalen Ziffer. Diese komplexeren Muster verwenden eine Schreibweise für reguläre Ausdrücke, die angibt, wie häufig ein Element eines regulären Ausdrucks wiederholt werden kann.

Die Zeichen, die eine Wiederholung angeben, stehen immer hinter dem Muster, auf das sie angewendet werden. Da bestimmte Arten der Wiederholung recht häufig verwendet werden, gibt es Sonderzeichen, um diese Fälle darzustellen. So entspricht zum Beispiel `+` einem ein- oder mehrmaligen Auftreten des vorher angegebenen Musters. Tabelle 11-3 fasst die Wiederholungssyntax zusammen.

Tabelle 11-3. Wiederholungszeichen für reguläre Ausdrücke

Zeichen | Bedeutung

---|---

`{` _`n`_ , _`m`_`}` | Entspricht dem vorherigen Element mindestens _`n`_ -mal, aber nicht häufiger als _`m`_ -mal.

`{` _`n`_`,}` | Entspricht dem vorherigen Zeichen _`n`_ \- oder mehrmals.

`{` _`n`_`}` | Entspricht genau dem _`n`_ -maligen Auftreten des vorherigen Elements.

`?` | Entspricht einem null- oder einmaligen Auftreten des vorherigen Elements. Das bedeutet, dass das vorherige Element optional ist. Äquivalent zu `{0,1}`.

`+` | Entspricht einem ein- oder mehrmaligen Auftreten des vorherigen Elements. Äquivalent zu `{1,}`.

`*` | Entspricht einem null- oder mehrmaligen Auftreten des vorherigen Elements. Äquivalent zu `{0,}`.

Die folgenden Zeilen zeigen einige Beispiele:

    /\d{2,4}/  // Matche zwei bis vier Ziffern
    /\w{3}\d?/                // Matche genau drei Wortzeichen und eine optionale Ziffer
    /\s+java\s+/              // Matche "java" mit einem oder mehr Whitespace-Zeichen
                              // davor und dahinter
    /[^"]*/                   // Matche null oder mehr Zeichen, die keine
                              // Anführungszeichen sind

Seien Sie vorsichtig, wenn Sie die Wiederholungszeichen `*` und `?` verwenden. Da diese Zeichen null Instanzen dessen entsprechen können, was vor ihnen steht, dürfen sie auch mit nichts übereinstimmen. Der reguläre Ausdruck `/a*/` entspricht zum Beispiel dem String »bbbb«, da der String kein Auftreten des Buchstabens a enthält

### 11.1.2.1 Nicht-gierige (nongreedy) Wiederholung

Die Wiederholungszeichen in Tabelle 11-3 entsprechen so vielen Übereinstimmungen wie möglich, solange auch nachfolgende Teile des regulären Ausdrucks noch passen. Wir bezeichnen diese Wiederholung als »gierig«. Es ist außerdem möglich (in JavaScript 1.5 und höher, denn hierbei handelt es sich um eines der Merkmale von Perl 5, die in JavaScript 1.2 nicht implementiert sind) anzugeben, dass die Wiederholung nicht-gierig erfolgen soll. Setzen Sie einfach nach dem oder den Wiederholungszeichen ein Fragezeichen: `??`, `+?`, `*?` oder sogar `{1,5}?`. Der reguläre Ausdruck `/a+/` entspricht zum Beispiel einem ein- oder mehrmaligen Auftreten des Buchstabens a. Wenn dieser Ausdruck auf den String »aaa« angewendet wird, entspricht er allen drei Buchstaben. Aber `/a+?/` entspricht einem ein- oder mehrmaligen Auftreten des Buchstabens a mit so wenigen Zeichen wie unbedingt nötig. Wenn dieser Ausdruck auf den gleichen String angewendet wird, entspricht das Muster nur dem ersten Buchstaben a.

Die Verwendung der nicht-gierigen Wiederholung führt nicht immer zu den Ergebnissen, die Sie erwarten. Stellen Sie sich das Muster `/a*b/` vor, das einem oder mehreren Buchstaben a, gefolgt vom Buchstaben b, entspricht. Wenn es auf den String »aaab« angewendet wird, stimmt es mit dem gesamten String überein. Verwenden wir nun die nicht-gierige Version: `/a*?b/`. Diese sollte dem Buchstaben b entsprechen, vor dem die kleinstmögliche Anzahl von »a«s steht. Wenn Sie es auf den gleichen String »aaab« anwenden, würden Sie wahrscheinlich erwarten, dass der Ausdruck nur dem letzten Buchstaben b entspricht. Dieses Muster entspricht aber tatsächlich ebenfalls dem gesamten String, genau wie die gierige Version des Musters. Das liegt daran, dass der Mustervergleich von regulären Ausdrücken dadurch erfolgt, dass die erste Position in einem String gesucht wird, an der es eine Übereinstimmung gibt. Die nicht-gierige Version unseres Musters stimmt am ersten Zeichen des Strings überein, und diese Übereinstimmung wird auch zurückgegeben. Übereinstimmungen von nachfolgenden Zeichen werden gar nicht in Erwägung gezogen.

## 11.1.3 Alternativen, Gruppierung und Referenzen

Die Grammatik der regulären Ausdrücke enthält Sonderzeichen, um Alternativen anzugeben, Teilausdrücke zusammenzufassen und auf vorherige Teilausdrücke zu verweisen. Das `|`-Zeichen trennt mehrere Alternativen. Zum Beispiel entspricht `/ab|cd|ef/` dem String »ab« oder dem String »cd« oder dem String »ef«. Und `/\d{3}|[a-z]{4}/` entspricht entweder drei Ziffern oder vier Kleinbuchstaben.

Beachten Sie, dass Alternativen von links nach rechts betrachtet werden, bis eine Übereinstimmung gefunden wurde. Wenn es für die linke Alternative eine Übereinstimmung gibt, wird die rechte Alternative selbst dann ignoriert, wenn sie eine »bessere« Übereinstimmung hervorgebracht hätte. Wenn also das Muster `/a|ab/` auf den String »ab« angewendet wird, entspricht es nur dem ersten Buchstaben.

Klammern haben in regulären Ausdrücken verschiedene Zwecke. Einer besteht darin, unterschiedliche Elemente in einem einzigen Teilausdruck zusammenzufassen, sodass die Elemente von `|`, `*`, `+`, `?` usw. als eine Einheit behandelt werden können. Zum Beispiel entspricht `/java(script)?/` »java«, gefolgt von einem optionalen »script«. Und `/(ab|cd)+|ef)/` entspricht entweder dem String »ef« oder einer oder mehreren Vorkommen eines der Strings »ab« oder »cd«.

Ein weiterer wichtiger Zweck der Klammern in regulären Ausdrücken ist es, innerhalb des Gesamtmusters Teilmuster zu definieren. Wenn ein regulärer Ausdruck erfolgreich mit einem Zielstring verglichen wurde, ist es möglich, die Teile des Zielstrings zu extrahieren, die einem beliebigen, in Klammern eingefassten Teilmuster entsprechen. (Sie werden weiter unten in diesem Kapitel sehen, wie diese übereinstimmenden Teilstrings abgerufen werden.) Nehmen wir zum Beispiel an, dass Sie nach einem oder mehreren Kleinbuchstaben suchen, gefolgt von einer oder mehreren Ziffern. Sie könnten dazu das Muster `/[a-z]+\d+/` verwenden. Nehmen wir weiterhin an, dass Sie sich aber nur für die Ziffern am Ende jeder Übereinstimmung interessieren. Wenn Sie diesen Teil des Musters in Klammern setzen `(/[a-z]+(\d+)/)`, können Sie die Ziffern aus jeder gefundenen Übereinstimmung extrahieren, wie später erläutert wird.

Eine ähnliche Verwendung von Teilausdrücken in Klammern besteht darin, im gleichen regulären Ausdruck auf einen Teilausdruck zurückzuverweisen. Das geschieht, indem auf ein `\`-Zeichen eine oder mehrere Ziffern folgen. Die Ziffern verweisen auf die Position des geklammerten Teilausdrucks in dem regulären Ausdruck. Zum Beispiel verweist `\1` auf den ersten Teilausdruck und `\3` auf den dritten. Beachten Sie, dass die Position der linken Klammer gezählt wird, da Teilausdrücke in andere eingebettet sein können. In dem folgenden regulären Ausdruck wird zum Beispiel mit `\2` auf den geschachtelten Teilausdruck `([Ss]cript)` verwiesen:

    /([Jj]ava([Ss]cript)?)\smacht\s(Spaß\w*)/

Eine Referenz auf einen früheren Teilausdruck eines regulären Ausdrucks verweist nicht auf das Muster für diesen Teilausdruck, sondern auf den Text, der dem Muster entsprach. Daher können Referenzen verwendet werden, um die Bedingung durchzusetzen, dass unterschiedliche Abschnitte eines Strings genau die gleichen Zeichen enthalten. Der folgende reguläre Ausdruck entspricht zum Beispiel null oder mehr Zeichen in einfachen oder doppelten Anführungszeichen. Es ist aber nicht erforderlich, dass die öffnenden und schließenden Anführungszeichen übereinstimmen (d.h. entweder in beiden Fällen einfache oder in beiden Fällen doppelte Anführungszeichen):

    /['"][^'"]*['"]/

Damit die Anführungszeichen übereinstimmen, benutzen Sie eine Referenz:

    /(['"])[^'"]*\1/

Die `\1` entspricht dem, was dem ersten geklammerten Teilausdruck entsprach. In diesem Beispiel erzwingt dies, dass die schließenden Anführungszeichen den öffnenden entsprechen. Dieser reguläre Ausdruck erlaubt keine einfachen Anführungszeichen in doppelten Anführungszeichen oder umgekehrt. Es ist nicht zulässig, eine Referenz innerhalb einer Zeichenklasse zu verwenden, sodass Sie nicht Folgendes schreiben können:

    /(['"])[^\1]*\1/

Weiter unten in diesem Kapitel werden Sie sehen, dass diese Referenz auf einen eingeklammerten Teilausdruck ein mächtiges Merkmal der Suchen-und-Ersetzen-Operationen von regulären Ausdrücken ist.

In JavaScript 1.5 (aber nicht in JavaScript 1.2) ist es möglich, Elemente in einem regulären Ausdruck zusammenzufassen, ohne eine numerierte Referenz auf diese Elemente zu erzeugen. Anstatt die Elemente einfach mit `(` und `)` zusammenzufassen, beginnt die Gruppe mit `(?:` und endet mit `)`. Stellen Sie sich zum Beispiel das folgende Muster vor:

    /([Jj]ava(?:[Ss]cript)?)\smacht\s(Spaß\w*)/

Hier wird der Teilausdruck `(?:[Ss]cript)` nur für die Gruppierung verwendet. Deswegen kann das Wiederholungszeichen `?` auf die Gruppe angewendet werden. Diese modifizierten Klammern erzeugen keine Referenz, sodass sich `\2` in diesem regulären Ausdruck auf den Text bezieht, der auf `(Spaß\w*)` passt.

Tabelle 11-4 fasst die Operatoren der regulären Ausdrücke für Alternativen, Gruppierungen und Referenzen zusammen.

Tabelle 11-4. Alternativ-, Gruppierungs- und Referenzzeichen für reguläre Ausdrücke

Zeichen | Bedeutung

---|---

`|` | Alternative. Entspricht entweder dem Teilausdruck auf der linken oder dem Teilausdruck auf der rechten Seite.

`(...)` | Gruppierung. Fasst Elemente zu einer einzigen Einheit zusammen, die mit `*`, `+`, `?`, `|` usw. verwendet werden kann. Erinnert sich für spätere Referenzierungen an die Zeichen, die dieser Gruppe entsprechen.

`(?:...)` | Nur Gruppierung. Fasst Elemente in einer einzigen Einheit zusammen, erinnert sich aber nicht an die Zeichen, die dieser Gruppe entsprechen.

`\` _`n`_ | Entspricht den gleichen Zeichen, die gematcht wurden, als die Gruppe Nummer _`n`_ zum ersten Mal matchte. Gruppen sind Teilausdrücke in (möglicherweise verschachtelten) Klammern. Gruppennummern werden durch Zählen der öffnenden Klammern von links nach rechts zugewiesen. Gruppen, die mit `(?:` gebildet wurden, werden nicht nummeriert.

## 11.1.4 Vergleichspositionen angeben

Wie bereits beschrieben wurde, entsprechen viele Elemente eines regulären Ausdrucks einem einzelnen Zeichen in einem String. Zum Beispiel entspricht `\s` einem einzelnen Whitespace-Zeichen. Andere Elemente regulärer Ausdrücke entsprechen den Positionen zwischen den Zeichen statt den tatsächlichen Zeichen. Zum Beispiel entspricht `\b` einer Wortgrenze, nämlich der Grenze zwischen einem `\w` (ASCII-Wortzeichen) und einem `\W` (Nicht-Wort-Zeichen) oder der Grenze zwischen einem ASCII-Wortzeichen und dem Beginn oder Ende eines Strings.[41] Elemente wie `\b` sind keine Zeichen, die in einem String-Vergleich verwendet werden. Sie geben aber gültige Positionen an, an denen eine Übereinstimmung auftreten kann. Manchmal werden diese Elemente »Anker von regulären Ausdrücken« genannt, da sie das Muster an einer besonderen Position im Such-String verankern. Die am häufigsten verwendeten Ankerelemente sind `^`, das das Muster an den Beginn des Strings bindet, und `$`, das das Muster an das Ende des Strings bindet.

Um zum Beispiel nach dem Wort »JavaScript« in einer eigenen Zeile zu suchen, könnten Sie den regulären Ausdruck `/^JavaScript$/` verwenden. Wenn Sie nach dem alleinstehenden Wort »Java« suchen wollten (nicht als Präfix wie in »JavaScript«), könnten Sie es mit dem Muster `/\sJava\s/` versuchen, das je ein Leerzeichen vor und hinter dem Wort erforderlich macht. Bei dieser Lösungsmöglichkeit gibt es aber zwei Probleme. Erstens passt es nicht, wenn das Wort »Java« zu Beginn oder am Ende eines Strings auftaucht, sondern nur, wenn das Wort auf beiden Seiten mit einem Leerzeichen erscheint. Zweitens hat der gematchte String, wenn dieses Muster eine Übereinstimmung findet, vorangestellte und angehängte Leerstellen, und das ist nicht das, was benötigt wird. Anstatt also tatsächliche Leerzeichen mit `\s` zu vergleichen, vergleichen (oder verankern) wir statt dessen Wortgrenzen mit `\b`. Der entsprechende Ausdruck ist `/\bJava\b/`. Das Element `\B` verankert die Übereinstimmung an einem Ort, der keine Wortgrenze ist. Somit entspricht das Muster `/\B[Ss]cript/` »JavaScript« und »postscript«, aber nicht »script« oder »Scripting«.

In JavaScript 1.5 (aber nicht in JavaScript 1.2) können Sie auch beliebige reguläre Ausdrücke als Ankerbedingungen verwenden. Wenn Sie einen Ausdruck zwischen den Zeichen `(?=`\- und `)` einfügen, so ist dies eine vorausschauende Zusicherung: Sie besagt, dass die eingeschlossenen Zeichen übereinstimmen müssen, überprüft diese aber nicht. Um zum Beispiel nach dem Namen einer beliebten Programmiersprache zu suchen, aber nur, wenn er von einem Doppelpunkt gefolgt wird, können Sie `/[Jj]ava([Ss]cript)?(?=\:)/` verwenden. Dieses Muster stimmt mit dem Wort »JavaScript« in »JavaScript: Das umfassende Referenzwerk« überein, aber nicht mit »Java« in »Java in a Nutshell«, da es nicht von einem Doppelpunkt gefolgt wird.

Wenn Sie stattdessen eine Zusicherung mit `(?` beginnen, handelt es sich um eine negative vorausschauende Zusicherung, die besagt, dass die folgenden Zeichen nicht übereinstimmen dürfen. Zum Beispiel entspricht `/Java(?!Script)([A-Z]\w*)/` dem String »Java«, gefolgt von einem Großbuchstaben und einer beliebigen Zahl zusätzlicher ASCII-Wortzeichen, solange auf »Java« nicht »Script« folgt. Es stimmt mit »JavaBeans« überein, aber nicht mit »Javanese«, und es stimmt mit »JavaScrip« überein, aber nicht mit »JavaScript« oder »JavaScripter«.

Tabelle 11-5 fasst die Anker für reguläre Ausdrücke zusammen.

Tabelle 11-5. Ankerzeichen für reguläre Ausdrücke

Zeichen | Bedeutung

---|---

`^` | Stimmt mit dem Beginn eines Strings und bei mehrzeiligen Suchvorgängen mit dem Beginn einer Zeile überein.

`$` | Stimmt mit dem Ende eines Strings und bei mehrzeiligen Suchvorgängen mit dem Ende einer Zeile überein.

`\b` | Stimmt mit einer Wortgrenze überein. Das bedeutet, dass es mit der Position zwischen einem `\w`-Zeichen und einem `\W`-Zeichen oder zwischen einem `\w`-Zeichen und dem Beginn oder Ende eines Strings übereinstimmt. (Beachten Sie aber, dass `[\b]` einem Backspace entspricht.)

`\B` | Stimmt mit einer Position überein, die keine Wortgrenze ist.

`(?=` _`p`_`)` | Eine positive vorausschauende Zusicherung. Macht es erforderlich, dass die folgenden Zeichen dem Muster _`p`_ entsprechen. Diese Zeichen sind aber nicht in der Übereinstimmung enthalten.

`(?` _`p`_`)` | Eine negative vorausschauende Zusicherung. Macht es erforderlich, dass die folgenden Zeichen dem Muster _`p`_ nicht entsprechen.

## 11.1.5 Flags

Es gibt noch ein letztes Element der Grammatik von regulären Ausdrücken. Die Flags von regulären Ausdrücken legen sehr allgemeine Mustervergleichsregeln fest. Im Gegensatz zur restlichen Syntax von regulären Ausdrücken werden Flags außerhalb der `/`-Zeichen angegeben. Anstatt innerhalb der Schrägstriche zu erscheinen, erscheinen sie hinter dem zweiten Schrägstrich. JavaScript 1.2 unterstützt zwei Flags: Das Flag `i` gibt an, dass der Mustervergleich nicht auf Groß- und Kleinschreibung achten soll, und das Flag `g` gibt an, dass der Mustervergleich global sein soll, was bedeutet, dass alle Übereinstimmungen innerhalb des Such-Strings gefunden werden sollen. Beide Flags können kombiniert werden, um einen globalen Vergleich zu erzielen, der nicht auf die Groß- und Kleinschreibung achtet.

Um zum Beispiel eine Suche nach dem ersten Auftreten des Wortes »java« durchzuführen (oder nach »Java«, »JAVA« etc.), bei der nicht auf Groß- und Kleinschreibung geachtet wird, können Sie den regulären Ausdruck `/\bjava\b/i` verwenden. Und um alle Vorkommen eines Wortes in einem String zu finden, können Sie das Flag `g` hinzufügen: `/\bjava\b/gi.`

JavaScript 1.5 unterstützt außerdem das Flag `m`, das einen Mustervergleich im Mehrzeilen-Modus durchführt. In diesem Modus entsprechen die Anker `^` und `$` dem Beginn bzw. dem Ende einer Zeile und außerdem dem Beginn bzw. dem Ende eines Strings, wenn der zu durchsuchende String Zeilenumbrüche enthält. Zum Beispiel entspricht das Muster `/Java$/im »` java« ebenso wie »Java\nmacht Spaß«.

Tabelle 11-6 fasst diese Flags für reguläre Ausdrücke zusammen. Beachten Sie, dass Sie später in diesem Kapitel, wenn die String- und RegExp-Methoden eingesetzt werden, um tatsächlich Vergleiche durchzuführen, mehr vom `g`-Flag sehen werden.

Tabelle 11-6. Flags für reguläre Ausdrücke

Zeichen | Bedeutung

---|---

`i` | Führe Vergleich durch, ohne auf die Groß- und Kleinschreibung zu achten.

`g` | Führe einen globalen Vergleich durch. Das bedeutet, dass alle Übereinstimmungen gefunden werden und nicht nach der ersten Übereinstimmung aufgehört wird.

`m` | Mehrzeilen-Modus. `^` entspricht dem Beginn einer Zeile oder eines Strings, und `$` entspricht dem Ende einer Zeile oder eines Strings.

## 11.1.6 Perl-Funktionen von RegExp, die in JavaScript nicht unterstützt werden

ECMAScript v3 unterstützt eine recht vollständige Teilmenge der Möglichkeiten von regulären Ausdrücken von Perl 5. Zu den fortgeschrittenen Perl-Funktionen, die von ECMAScript nicht unterstützt werden, gehören die folgenden:

  * die Flags `s` (Single-Line-Modus) und `x` (erweiterte Syntax)

  * die Escape-Sequenzen `\a`, `\e`, `\l`, `\u`, `\L`, `\U`, `\E`, `\Q`, `\A`, `\Z`, `\z` und `\G`

  * der positive zurückblickende Anker `(?<=` und der negative zurückblickende Anker `(?<`

  * der Kommentar `(?#` und die andere erweiterte `(?`-Syntax

* * *

[40] Der Begriff »regulärer Ausdruck« ist ein obskurer Begriff, der vor vielen Jahren geprägt wurde. Die Syntax, mit der ein Textmuster beschrieben wird, ist in der Tat eine Art Ausdruck. Auf den ersten Blick fällt es einem aber oft schwer, das Reguläre an einem solchen Ausdruck zu erkennen! Ein regulärer Ausdruck wird manchmal auch »regexp« oder »RE« genannt, was sich auf die englische Bezeichnung »regular expression« stützt.

[41] Außer innerhalb einer Zeichenklasse (eckige Klammern), wo `\b` dem Backspace-Zeichen entspricht.

# 11.2 String-Methoden für den Mustervergleich

Bisher haben wir zwar die Grammatik erörtert, mit der reguläre Ausdrücke erzeugt werden, aber nicht, wie diese regulären Ausdrücke tatsächlich in JavaScript-Code verwendet werden können. In diesem Abschnitt besprechen wir Methoden des String-Objekts, die mit regulären Ausdrücken einen Mustervergleich und Suchen-und-Ersetzen-Operationen durchführen. In den darauf folgenden Abschnitten werden wir die Beschreibung des Mustervergleichs mit regulären Ausdrücken in JavaScript fortsetzen, indem wir das Objekt RegExp mit seinen Methoden und Eigenschaften erklären. Beachten Sie, dass die folgenden Abschnitte nur einen Überblick über die verschiedenen Methoden und Eigenschaften geben, die mit regulären Ausdrücken zu tun haben. Wie üblich finden Sie alle Einzelheiten in Teil III.

Strings unterstützen vier Methoden, die reguläre Ausdrücke nutzen. Die einfachste ist `search( )`. Diese Methode nimmt einen regulären Ausdruck als Argument entgegen und gibt entweder die Zeichenposition zurück, an der der erste übereinstimmende Teilstring beginnt, oder −1, wenn es keine Übereinstimmung gibt. Der folgende Aufruf gibt zum Beispiel 4 zurück:

    "JavaScript".search(/script/i);

Wenn das Argument von `search( )` kein regulärer Ausdruck ist, wird es zuerst in einen umgewandelt, indem es an den Konstruktor `RegExp` übergeben wird. `search( )` unterstützt keine globale Suche, sondern ignoriert das Flag `g` seines regulären Ausdrucksarguments.

Die Methode `replace( )` führt eine Suchen-und-Ersetzen-Operation durch. Sie nimmt als erstes Argument einen regulären Ausdruck und als zweites Argument einen Ersetzungsstring entgegen. Die Methode durchsucht den String, auf dem sie aufgerufen wurde, nach Übereinstimmungen mit dem angegebenen Muster. Wenn bei dem regulären Ausdruck das Flag `g` gesetzt ist, ersetzt die Methode `replace( )` alle Übereinstimmungen in dem String durch den Ersetzungsstring. Andernfalls ersetzt sie nur die erste Übereinstimmung, die sie findet. Wenn das erste Argument von `replace( )` statt eines regulären Ausdrucks ein String ist, sucht die Methode wortwörtlich nach diesem String, anstatt ihn mit dem Konstruktor `RegExp( )` in einen regulären Ausdruck umzuwandeln, wie es `search( )` tut. Sie können zum Beispiel `replace( )` wie folgt verwenden, um eine einheitliche Großschreibung des Wortes »JavaScript« in einem Text-String zu erreichen:

    // Ersetze mit der korrekten Groß- und Kleinschreibung,
    // egal wie die Groß-/Kleinschreibung vorher war
    text.replace(/javascript/gi, "JavaScript");

`replace( )` kann aber noch mehr als das. Erinnern Sie sich, dass geklammerte Teilausdrücke eines regulären Ausdrucks von links nach rechts durchnumeriert sind und dass der reguläre Ausdruck sich den Text merkt, mit dem jeder Teilausdruck übereinstimmt. Wenn in dem Ersetzungsstring ein `$` mit einer Ziffer dahinter erscheint, ersetzt `replace( )` diese beiden Zeichen durch den Text, der dem angegebenen Teilausdruck entspricht. Das ist eine sehr nützliche Funktion. Sie können sie zum Beispiel verwenden, um in einem String gerade Anführungszeichen durch typografische zu ersetzen, die mit ASCII-Zeichen simuliert werden:

    // Ein Zitat besteht aus einem Anführungszeichen mit beliebig vielen
    // anderen Zeichen dahinter (diese merken wir uns), mit einem weiteren
    // Anführungszeichen dahinter.
    var zitat = /"([^"]*)"/g;
    // Ersetze die geraden Anführungszeichen durch typografische und lasse
    // den Inhalt des Zitats (in $1 gespeichert) unverändert.
    text.replace(zitat, "``$1''");

Die Methode `replace( )` verfügt auch noch über andere wichtige Funktionen, die im Referenzeintrag `String.replace( )` in Teil III beschrieben werden. Besonders bemerkenswert ist, dass das zweite Argument von `replace( )` eine Funktion sein kann, die den Ersetzungsstring dynamisch berechnet.

Die Methode `match( )` ist die allgemeinste der String-Methoden für reguläre Ausdrücke. Sie nimmt als einziges Argument einen regulären Ausdruck entgegen (oder wandelt ihr Argument in einen regulären Ausdruck um, indem sie es dem Konstruktor `RegExp( )` übergibt) und gibt ein Array zurück, das die Ergebnisse des Vergleichs enthält. Wenn bei dem regulären Ausdruck das Flag `g` gesetzt ist, gibt die Methode ein Array aller Übereinstimmungen zurück, die in dem String erscheinen. Ein Beispiel:

    "1 plus 2 gleich 3".match(/\d+/g) // gibt ["1", "2", "3"] zurück

Wenn bei dem regulären Ausdruck das Flag `g` nicht gesetzt ist, führt `match( )` keine globale Suche durch, sondern sucht nur nach der ersten Übereinstimmung. `match( )` gibt aber sogar dann ein Array zurück, wenn keine globale Suche durchgeführt wurde. In diesem Fall ist das erste Element des Arrays der gefundene String, und eventuell verbleibende Elemente enthalten die geklammerten Teilausdrücke des regulären Ausdrucks. Wenn also `match( )` das Array `a` zurückgibt, dann enthält `a[0]` die vollständige Übereinstimmung, `a[1]` enthält den Teilstring, der dem ersten geklammerten Ausdruck entsprach, usw. Um eine Parallele zu der Methode `replace( )` zu ziehen: `a[` _`n`_`]` enthält den Inhalt von `$` _`n`_.

Stellen Sie sich zum Beispiel vor, dass Sie mit dem folgenden Code eine URL parsen:

    var url = /(\w+):\/\/([\w.]+)\/(\S*)/;
    var text = "Besuchen Sie meinen Blog unter http://www.example.com/~david";
    var ergebnis = text.match(url);
    if (ergebnis != null) {
      var volle_url = result[0];  // Enthält "http://www.example.com/~david"
      var protokoll = result[1];  // Enthält "http"
      var host = result[2];    // Enthält "www.example.com"
      var pfad = result[3];    // Enthält "~david"
    }

Abschließend sollten Sie noch ein weiteres Merkmal der Methode `match()` kennenlernen. Das Array, das sie zurückgibt, hat wie alle Arrays die Eigenschaft `length`. Wenn `match()` auf einem nicht-globalen regulären Ausdruck aufgerufen wird, verfügt das Rückgabe-Array aber auch noch über zwei andere Eigenschaften: über die Eigenschaft `index`, die die Zeichenposition des Strings enthält, an der die Übereinstimmung beginnt, und über die Eigenschaft `input`, die eine Kopie des Zielstrings ist. In dem vorangegangenen Code wäre also der Wert der Eigenschaft `ergebnis.index` 17, da die gesuchte URL an der Zeichenposition 17 im Text beginnt. Die Eigenschaft `ergebnis.input` enthält den gleichen String wie die Variable `text`. Bei einem regulären Ausdruck `r`, für den das Flag `g` nicht gesetzt ist, liefert der Ausdruck `s.match(r)` den gleichen Wert zurück wie `r.exec(s)`. Die Methode `RegExp.exec( )` wird ein wenig später in diesem Kapitel erörtert.

Die letzte Methode für reguläre Ausdrücke des Objekts String ist `split( )`. Diese Methode zerteilt den String, auf dem sie aufgerufen wird, in ein Array aus Teilstrings und verwendet dabei das Argument als Trennzeichen:

    "123,456,789".split(","); // Gibt ["123","456","789"] zurück

Die Methode `split( )` kann auch einen regulären Ausdruck als Argument entgegennehmen. Diese Fähigkeit macht die Methode mächtiger. Sie können nun zum Beispiel ein Trennzeichen angeben, das eine beliebige Menge von Whitespace auf jeder Seite zulässt:

    "1,2, 3, 4, 5".split(/\s*,\s*/); // Gibt ["1","2","3","4","5"] zurück

Die Methode `split( )` hat auch noch andere Merkmale. Ausführliche Einzelheiten finden Sie unter dem Eintrag `String.split( )` in Teil III.

# 11.3 Das RegExp-Objekt

Wie bereits zu Beginn dieses Kapitels gesagt wurde, werden reguläre Ausdrücke als RegExp-Objekte dargestellt. Neben dem Konstruktor `RegExp( )` unterstützten RegExp-Objekte drei Methoden und eine Reihe von Eigenschaften. Ein ungewöhnliches Merkmal der Klasse RegExp ist es, dass sie sowohl Klassen- (oder statische) Eigenschaften als auch Instanzeigenschaften definiert. Das bedeutet, dass sie sowohl globale Eigenschaften definiert, die zu dem Konstruktor `RegExp( )` gehören, als auch andere Eigenschaften, die zu den einzelnen RegExp-Objekten gehören. Die Mustervergleichsmethoden und -eigenschaften von RegExp werden in den nächsten beiden Abschnitten beschrieben.

Der Konstruktor `RegExp( )` nimmt ein oder zwei String-Argumente entgegen und erzeugt ein neues RegExp-Objekt. Das erste Argument diesens Konstruktors ist ein String, der den Body des regulären Ausdrucks enthält, also den Text, der in einem regulären Ausdrucksliteral in Schrägstrichen erscheinen würde. Beachten Sie, dass sowohl String-Literale als auch reguläre Ausdrücke das `\`-Zeichen für Escape-Sequenzen verwenden. Wenn Sie also `RegExp( )` einen regulären Ausdruck als String-Literal übergeben, müssen Sie jedes `\`-Zeichen durch `\\` ersetzen. Das zweite Argument von `RegExp( )` ist optional. Wenn es angegeben wird, zeigt es die Flags des regulären Ausdrucks an. Dabei kann es sich um `g`, `i`, `m` oder um eine Kombination dieser Buchstaben handeln. Zum Beispiel:

    // Suche alle fünfziffrigen Zahlen in einem String. Beachten Sie
    // den Doppel-\\ in diesem Fall.
    var postleitzahl = new RegExp("\\d{5}", "g");

Der Konstruktor `RegExp( )` ist nützlich, wenn ein regulärer Ausdruck dynamisch erzeugt wird und daher nicht mit der Literalsyntax der regulären Ausdrücke dargestellt werden kann. Um zum Beispiel nach einem String zu suchen, der vom Anwender eingegeben wurde, muss ein regulärer Ausdruck mit `RegExp( )` zur Laufzeit erzeugt werden.

## 11.3.1 RegExp-Methoden für den Mustervergleich

RegExp-Objekte definieren zwei Methoden, die Mustervergleichsoperationen durchführen. Diese verhalten sich ähnlich wie die bereits beschriebenen String-Methoden. Die Hauptmethode von RegExp zum Mustervergleich ist `exec( )`. Sie ähnelt der in "11.2 String-Methoden für den Mustervergleich" beschriebenen String-Methode `match( )`, mit dem Unterschied, dass es sich um eine RegExp-Methode handelt, die einen String entgegennimmt, und nicht um eine String-Methode, die einen RegExp entgegennimmt. Die Methode `exec( )` führt auf dem angegebenen String einen regulären Ausdruck aus, was bedeutet, dass sie in dem String nach einer Übereinstimmung sucht. Wenn sie keine findet, gibt sie `null` zurück. Wenn sie aber eine findet, gibt sie das gleiche Array zurück, das auch von der Methode `match( )` für nicht-globale Suchläufe zurückgegeben wird. Das Element 0 des Arrays enthält den String, der mit dem regulären Ausdruck übereinstimmte, und mögliche nachfolgende Array-Elemente enthalten die Teilstrings, die mit vorhandenen geklammerten Teilausdrücken übereinstimmten. Des Weiteren enthält die Eigenschaft `index` die Zeichenposition, an der die Übereinstimmung auftrat, und die Eigenschaft `input` verweist auf den String, der durchsucht wurde.

Im Gegensatz zu der Methode `match( )` gibt `exec( )` die gleiche Art Array zurück, egal ob der reguläre Ausdruck über das globale Flag `g` verfügt oder nicht. Erinnern Sie sich daran, dass die Methode `match( )` ein Array mit Übereinstimmungen zurückgibt, wenn ihr ein globaler regulärer Ausdruck übergeben wird. Im Gegensatz dazu gibt `exec( )` immer eine einzelne Übereinstimmung zurück und liefert vollständige Informationen über diese Übereinstimmung. Wenn `exec( )` für einen regulären Ausdruck aufgerufen wird, der das Flag `g` hat, setzt die Methode die Eigenschaft `lastIndex` des Objekts des regulären Ausdrucks auf die Zeichenposition, die unmittelbar auf den übereinstimmenden Teilstring folgt. Wenn `exec( )` für den gleichen regulären Ausdruck ein zweites Mal aufgerufen wird, beginnt sie ihre Suche an der Zeichenposition, die von der Eigenschaft `lastIndex` angezeigt wird. Wenn `exec( )` keine Übereinstimmung findet, setzt sie `lastIndex` auf 0 zurück. (Sie können `lastIndex` auch jederzeit selbst auf 0 setzen, was Sie immer dann tun sollten, wenn Sie eine Suche abbrechen, ehe Sie die letzte Übereinstimmung in einem String finden und damit beginnen, einen anderen String mit dem gleichen RegExp-Objekt zu durchsuchen.) Dieses besondere Verhalten ermöglicht es Ihnen, `exec( )` wiederholt aufzurufen, um alle Übereinstimmungen des regulären Ausdrucks in einem String mit einer Schleife zu durchlaufen. Ein Beispiel:

    var muster = /Java/g;
    var text = "JavaScript macht mehr Spaß als Java!";
    var ergebnis;
    while((ergebnis = muster.exec(text)) != null) {
      alert("Übereinstimmung `" + result[0] + "'" +
        " an Position " + ergebnis.index +
        "; nächste Suche beginnt bei " + muster.lastIndex);
    }

Die andere Methode von RegExp, `test( )`, ist viel einfacher als `exec( )`. Sie nimmt einen String entgegen und gibt `true` zurück, wenn der String dem regulären Ausdruck entspricht:

    var muster = /java/i;
    muster.test("JavaScript"); // Gibt true zurück

`test( )` aufzurufen ist dasselbe wie `exec( )` aufzurufen und `true` zurückzugeben, wenn der Rückgabewert von `exec( )` nicht `null` ist. Durch diese Äquivalenz verhält sich die Methode `test( )` genau wie die Methode `exec( )`, wenn sie für einen globalen regulären Ausdruck aufgerufen wird: Sie beginnt damit, den angegebenen String von der Position `lastIndex` an zu durchsuchen, und wenn sie eine Übereinstimmung findet, setzt sie `lastIndex` auf die Position des Zeichens, das unmittelbar auf die Übereinstimmung folgt. So können Sie mit der Methode `test( )` ebenso wie mit der Methode `exec( )` den String durchlaufen.

Die String-Methoden `search( )`, `replace( )` und `match( )` verwenden die Eigenschaft `lastIndex` nicht so, wie `exec( )` und `test( )` es tun. Tatsächlich setzen die String-Methoden `lastIndex` einfach auf 0 zurück. Wenn Sie `exec( )` oder `test( )` auf einem Muster verwenden, bei dem das Flag `g` gesetzt ist, und Sie nach mehreren Strings suchen, müssen Sie entweder alle Übereinstimmungen in jedem String finden, sodass `lastIndex` automatisch auf null zurückgesetzt wird (das geschieht, wenn die letzte Suche fehlschlägt), oder Sie müssen die Eigenschaft `lastIndex` selbst explizit auf 0 setzen. Wenn Sie das vergessen, beginnen Sie die Suche nach einem neuen String möglicherweise irgendwo mitten im String statt am Anfang. Denken Sie daran, dass dieses besondere Verhalten von `lastIndex` nur bei regulären Ausdrücken mit dem Flag `g` auftritt. `exec( )` und `test( )` ignorieren die Eigenschaft `lastIndex` von RegExp-Objekten, die nicht über das Flag `g` verfügen.

## 11.3.2 Instanzeigenschaften von RegExp

Jedes RegExp-Objekt verfügt über fünf Eigenschaften. Die Eigenschaft `source` ist ein schreibgeschützter String, der den Text des regulären Ausdrucks enthält. Die Eigenschaft `global` ist ein schreibgeschützter Boolescher Wert, der angibt, ob der reguläre Ausdruck über das Flag `g` verfügt. Die Eigenschaft `ignoreCase` ist ein schreibgeschützter Boolescher Wert, der angibt, ob der reguläre Ausdruck über das Flag `i` verfügt. Die Eigenschaft `multiline` ist ein schreibgeschützter Boolescher Wert, der angibt, ob der reguläre Ausdruck über das Flag `m` verfügt. Die letzte Eigenschaft ist `lastIndex`, ein les- und schreibbarer Integer. Bei Mustern mit dem Flag `g` speichert diese Eigenschaft die String-Position, an der die nächste Suche beginnen soll. Sie wird von den Methoden `exec( )` und `test( )` verwendet, wie es im vorherigen Abschnitt beschrieben wurde.

# Kapitel 12. Java skripten

Trotz ihres Namens ist die Programmiersprache JavaScript nicht mit der Programmiersprache Java verwandt. Es gibt eine oberflächliche syntaktische Ähnlichkeit, weil beide die Syntax der Programmiersprache C verwenden, aber darunter sind sie sehr verschieden. Trotzdem hat sich JavaScript entwickelt und kann jetzt tatsächlich verwendet werden, um Java zu skripten.[42] Java 6 erkennt das an und wird gebündelt mit einem JavaScript-Interpreter ausgeliefert, damit die Scripting-Fähigkeit leicht in beliebige Java-Anwendungen eingebettet werden kann. Außerdem unterstützen einige JavaScript-Interpreter (wie der mit Java 6 gebündelte) eine Gruppe von Features, die es JavaScript ermöglichen, mit Java-Objekten zu interagieren, Felder zu setzen und abzufragen und Methoden aufzurufen.

Dieses Kapitel erklärt, wie man einen JavaScript-Interpreter in eine Java 6-Anwendung einbettet und wie man JavaScript-Skripten von dieser Anwendung aus ausführt. Und dann kehrt es die Sache um und führt vor, wie diese Skripten direkt Java-Objekte skripten können.

Zum Thema Java-Scripting werde ich noch einmal in Kapitel 23 zurückkehren, das Java-Applets und das Java-Plug-in im Kontext eines Webbrowsers behandelt.

# 12.1 JavaScript einbetten

Wir nähern uns dem Ende des Kern-JavaScript-Abschnitts dieses Buchs. Teil II ist vollständig JavaScript gewidmet, wie es in Webbrowsern verwendet wird. Bevor wir diese Betrachtung beginnen, wollen wir uns kurz ansehen, wie JavaScript in andere Anwendungen eingebettet werden kann. Das Motiv dafür, JavaScript in Anwendungen einzubetten, besteht üblicherweise in dem Wunsch, es den Benutzern zu ermöglichen, die Anwendung mit Skripten anzupassen. Beispielsweise nutzt der Firefox-Webbrowser JavaScript-Skripten, um das Verhalten der Benutzerschnittstelle zu steuern. Viele andere hochkonfigurierbare Anwendungen verwenden Skriptsprachen anderer Art.

Es sind zwei Open Source-JavaScript-Interpreter vom Mozilla-Projekt verfügbar. _SpiderMonkey_ ist der ursprüngliche JavaScript-Interpreter, der in C geschrieben ist. _Rhino_ ist eine Implementierung in Java. Beide haben APIs für das Einbetten. Wenn Sie einer C-Anwendung Scripting hinzufügen möchten, können Sie SpiderMonkey verwenden. Wenn Sie einer Java-Anwendung Scripting hinzufügen möchten, können Sie Rhino verwenden. Mehr zur Verwendung dieser Interpreter in Ihren eigenen Anwendungen können Sie unter <http://www.mozilla.org/js/spidermonkey> und <http://www.mozilla.org/rhino> erfahren.

Mit dem Aufkommen von Java 6.0 wird es besonders leicht, Ihren Java-Anwendungen JavaScript-Scripting-Funktionalitäten hinzuzufügen. Deswegen wird es hier besprochen. Java 6 führt das neue Package `javax.script` ein, das eine allgemeine Schnittstelle für Skriptsprachen bietet, und ist mit einer Version von Rhino gebündelt, die das neue Package verwendet.[43]

Beispiel 12-1 zeigt die grundlegende Verwendung des Packages `javax.script`: Es verschafft sich ein `ScriptEngine`-Objekt, das eine Instanz eines JavaScript-Interpreters repräsentiert, und ein `Bindings`-Objekt, das JavaScript-Variablen festhält. Dann führt es ein Skript aus, das in einer externen Datei gespeichert ist, indem es einen `java.io.Reader`-Stream und die Bindungen an die `eval( )`-Methode der `ScriptEngine` übergibt. Die Methode `eval( )` liefert das Ergebnis des Skripts zurück oder löst eine `ScriptException` aus, wenn etwas schiefgeht.

Beispiel 12-1. Ein Java-Programm zur Ausführung von JavaScript-Skripten

    import javax.script.*;
    import java.io.*;

    // Eine Datei mit JavaScript auswerten und das Ergebnis ausgeben
    public class RunScript {
      public static void main(String[] args) throws IOException {
        // Einen Interpreter oder eine "ScriptEngine" anfordern, um das Skript auszuführen.
        ScriptEngineManager scriptManager = new ScriptEngineManager( );
        ScriptEngine js = scriptManager.getEngineByExtension("js");

        // Die Skriptdatei, die wir ausführen werden.
        String dateiname = null;

        // Ein Bindings-Objekt ist eine Symboltabelle oder ein Namensraum für die
        // Script-Engine. Es verknüpft Namen und Werte und macht sie für das Skript
        // verfügbar.
        Bindings bindings = js.createBindings( );

        // Die Argumente verarbeiten. Sie können eine beliebige Anzahl von
        // -Dname=wert-Argumenten enthalten, die die Variablen für das Skript definieren.
        // Jedes Argument, das nicht mit -D beginnt, wird als Dateiname behandelt.
        for(int i = 0; i < args.length; i++) {
          String arg = args[i];
          if (arg.startsWith("-D")) {
            int pos = arg.indexOf('=');
            if (pos == −1) usage( );
            String name = arg.substring(2, pos);
            String wert = arg.substring(pos+1);
            // Beachten Sie, dass alle Variablen, die wir definieren Strings sind.
            // Skripten können sie nach Bedarf in andere Typen umwandeln.
            // Wir könnten auch ein java.lang.Number-, ein java.lang.Boolean-
            // oder jedes beliebige andere Java-Objekt oder null übergeben.
            bindings.put(name, wert);
          }
          else {
            if (dateiname != null) usage( ); // bitte nur eine Datei
            dateiname = arg;
          }
        }
        // Sicherstellen, dass wir über die Argumente einen Dateinamen erhalten haben.
        if (dateiname == null) usage( );

        // Unter Verwendung eines speziellen reservierten Variablennamens eine weitere
        // Bindung hinzufügen, um der Script-Engine den Namen der auszuführenden Datei
        // mitzuteilen. Das ermöglicht es, bessere Fehlermeldungen anzubieten.
        bindings.put(ScriptEngine.FILENAME, dateiname);

        // Einen Stream zum Lesen des Skripts erzeugen.
        Reader in = new FileReader(dateiname);

        try {
          // Das Skript unter Verwendung der Bindungen ausführen und das Ergebnis abrufen.
          Object ergebnis = js.eval(in, bindings);
          // Das Ergebnis anzeigen.
          System.out.println(ergebnis);
        }
        catch(ScriptException ex) {
          // Oder eine Fehlermeldung anzeigen.
          System.out.println(ex);
        }
      }

      static void usage( ) {
        System.err.println(
             "Verwendung: java RunScript [-Dname=wert...] script.js");
        System.exit(1);
      }
    }

Das `Bindings`-Objekt, das in diesem Codefragment erzeugt wurde, ist nicht statisch: Es werden auch alle Variablen gespeichert, die vom JavaScript-Skript gespeichert werden. Beispiel 12-2 ist ein realistischeres Beispiel für das Scripting von Java. Es speichert sein `Bindings`-Objekt in einem `ScriptContext`-Objekt in einem höheren Geltungsbereich, damit diese Variablen gelesen werden können, es werden aber keine neuen Variablen im `Bindings`-Objekt gespeichert. Das Beispiel implementiert ein einfaches Werkzeug für Konfigurationsdateien: Es wird eine Textdatei verwendet, um Name/Wert-Paare zu definieren, die über die hier definierte Klasse `Configuration` abgefragt werden können. Werte können Strings, Zahlen oder Boolesche Werte sein, und wenn ein Wert in geschweifte Klammern eingeschlossen ist, wird er zur Ausführung an einen JavaScript-Interpreter übergeben. Interessanterweise wird das `java.util.Map`-Objekt, das die Name/Wert-Paare festhält, in ein `SimpleBindings`-Objekt eingepackt, damit der JavaScript-Interpreter ebenfalls auf die Werte anderer Variablen zugreifen kann, die in der gleichen Datei definiert werden.[44]

Beispiel 12-2. Ein Java-Konfigurationsdatei-Werkzeug, das JavaScript-Ausdrücke auswertet

    import javax.script.*;
    import java.util.*;
    import java.io.*;

    /**
     * Diese Klasse ist wie java.util.Properties, ermöglicht es aber, Property-Werte über
     * die Auswertung von JavaScript-Ausdrücken zu ermitteln.
     */
    public class Configuration {
      // Hier speichern wir die Name/Wert-Paare der Defaults.
      Map<String,Object> defaults = new HashMap<String,Object>( );

      // Zugriffsmethoden zum Abrufen und Setzen von Werten in der Map.
      public Object get(String key) { return defaults.get(key); }
      public void put(String key, Object wert) { defaults.put(key, wert); }

      // Den Inhalt der Map aus einer Datei mit Name/Wert-Paaren initialisieren. Wenn ein
      // Wert in geschweifte Klammern eingeschlossen ist, wird er als JavaScript ausgewertet.
      public void load(String dateiname) throws IOException, ScriptException {
        // Einen JavaScript-Interpreter anfordern.
        ScriptEngineManager manager = new ScriptEngineManager( );
        ScriptEngine engine = manager.getEngineByExtension("js");

        // Unsere eigenen Name/Wert-Paare als JavaScript-Variablen verwenden.
        Bindings bindings = new SimpleBindings(defaults);

        // Einen Kontext für die Ausführung von Skripten erzeugen.
        ScriptContext context = new SimpleScriptContext( );

        // Diese Bindings im Kontext setzen, damit sie von den Skripten gelesen werden
        // können, dass aber die Variablen, die von den Skripten erzeugt werden, nicht in
        // unser Map-Objekt gesteckt werden.
        context.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);

        BufferedReader in = new BufferedReader(new FileReader(dateiname));
        String zeile;
        while((zeile = in.readLine( )) != null) {
          zeile = zeile.trim( ); // führende und folgende Leerzeichen entfernen
          if (zeile.length( ) == 0) continue;  // leere Zeilen überspringen
          if (zeile.charAt(0) == '#') continue; // Kommentare überspringen

          int pos = zeile.indexOf(":");
          if (pos == −1)
            throw new IllegalArgumentException("Syntax: " + zeile);

          String name = zeile.substring(0, pos).trim( );
          String wert = zeile.substring(pos+1).trim( );
          char ersteszeichen = wert.charAt(0);
          int l = wert.length( );
          char letzteszeichen = wert.charAt(l-1);

          if (ersteszeichen == '"' && letzteszeichen == '"') {
            // Werte in doppelten Anführungszeichen sind Strings.
            defaults.put(name, wert.substring(1, l-1));
          }
          else if (Character.isDigit(ersteszeichen)) {
            // Beginnt der Wert mit einer Ziffer, wird versucht, ihn als Zahl zu parsen.
            try {
              double d = Double.parseDouble(wert);
              defaults.put(name, d);
            }
            catch(NumberFormatException e) {
              // Hoppla. Keine Zahl. Wert wird als String gespeichert.
              defaults.put(name, wert);
            }
          }
          else if (wert.equals("true"))     // Boolesche Werte verarbeiten
            defaults.put(name, Boolean.TRUE);
          else if (wert.equals("false"))
            defaults.put(name, Boolean.FALSE);
          else if (wert.equals("null"))
            defaults.put(name, null);
          else if (ersteszeichen == '{' && letzteszeichen == '}') {
            // Steht der Wert in geschweiften Klammern, wird er als JavaScript-Code
            // ausgewertet.
            String script = wert.substring(1, l-1);
            Object ergebnis = engine.eval(script, context);
            defaults.put(name, ergebnis);
          }
          else {
            // Im Default-Fall wird der Wert einfach als String gespeichert.
            defaults.put(name, wert);
          }
        }
      }

      // Ein einfaches Testprogramm für die Klasse.
      public static void main(String[] args) throws IOException, ScriptException
      {
        Configuration defaults = new Configuration( );
        defaults.load(args[0]);
        Set<Map.Entry<String,Object>> entryset = defaults.defaults.entrySet( );
        for(Map.Entry<String,Object> entry : entryset) {
          System.out.printf("%s: %s%n", entry.getKey( ), entry.getValue( ));
        }
      }
    }

## 12.1.1 Typumwandlungen mit javax.script

Immer wenn eine Sprache von einer anderen aufgerufen wird, müssen Sie sich darüber Gedanken machen, wie die Typen der einen Sprache auf die Typen der anderen Sprache abgebildet werden. Nehmen Sie an, Sie binden ein `java.lang.String`\- und ein `java.lang. Integer`-Objekt an Variablen in einem `Bindings`-Objekt. Welche Typen sieht ein JavaScript-Skript dann für die Werte, wenn es diese Variablen verwendet? Und, wenn das Ergebnis der Auswertung des Skripts ein Boolescher JavaScript-Wert ist, welchen Wert hat dann das Objekt, das von der Methode `eval( )` zurückgeliefert wird?

Bei Java und JavaScript ist die Antwort ziemlich einfach. Wenn Sie ein Java-Objekt (es gibt keine Möglichkeit, elementare Werte zu speichern) in einem `Bindings`-Objekt speichern, wird es in JavaScript folgendermaßen konvertiert:

  * Boolean-Objekte werden in Boolesche JavaScript-Werte konvertiert.

  * Alle `java.lang.Number`-Objekte werden in JavaScript-Zahlen konvertiert.

  * Java-`Character`\- und -`String`-Objekte werden in JavaScript-Strings konvertiert.

  * Der Java-`null`-Wert wird in den JavaScript-`null`-Wert konvertiert.

  * Alle anderen Java-Objekte werden einfach in ein JavaScript-JavaObject-Objekt eingehüllt. Mehr über den Typ JavaObject werden Sie erfahren, wenn ich Ihnen weiter unten in diesem Kapitel erkläre, wie JavaScript Java skripten kann.

Zu den Zahlumwandlungen gibt es noch ein paar zusätzliche Details, die man verstehen sollte. Alle Java-Zahlen werden in JavaScript-Zahlen umgewandelt. Das schließt `Byte`, `Short`, `Integer`, `Long`, `Float`, `Double`, aber auch `java.math.BigInteger` und `java.math. BigDouble` ein. Besondere Gleitkommawerte wie `Infinity` und `NaN` werden von beiden Sprachen unterstützt und werden von der einen in die andere umgewandelt. Beachten Sie, dass der Zahltyp von JavaScript auf einem 64-Bit-Gleitkommawert basiert, der mit Javas `double`-Typ verwandt ist. In einem `double` können nicht alle `long`-Werte genau repräsentiert werden. Wenn Sie einen Java-`long`-Wert an JavaScript übergeben, kann es also sein, dass Sie Daten verlieren. Die gleiche Warnung gilt für die Verwendung von `BigInteger` und `BigDecimal`: Überzählige Stellen können verloren gehen, wenn der Java-Wert eine größere Genauigkeit hat, als JavaScript darstellen kann. Oder wenn der Java-Wert größer ist als `Double.MAX_VALUE`, wird er in den JavaScript-Wert `Infinity` umgewandelt.

Umwandlungen in die andere Richtung sind ähnlich einfach. Wenn JavaScript einen Wert in einer Variablen speichert (und deswegen in einem `Bindings`-Objekt) oder wenn ein JavaScript-Ausdruck ausgewertet wird, werden JavaScript-Werte folgendermaßen in Java-Werte umgewandelt:

  * Boolesche JavaScript-Werte werden in Java-`Boolean`-Objekte umgewandelt.

  * JavaScript-Stringwerte werden in Java-`String`-Objekte umgewandelt.

  * JavaScript-Zahlen werden in Java-`Double`-Objekte umgewandelt. `Infinity` und `NaN` werden entsprechend umgewandelt.

  * JavaScript-`null`\- und -`undefined`-Werte werden in den Java-Wert `null` umgewandelt.

  * JavaScript-Objekte und -Arrays werden in Java-Objekte eines opaken Typs umgewandelt. Diese Werte können wieder an JavaScript zurückgegeben werden, haben aber eine nicht-veröffentlichte API, die nicht zur Verwendung in Java-Programmen gedacht ist. Beachten Sie, dass die JavaScript-Wrapper-Objekte der Typen String, Boolean und Number ebenfalls in opake Java-Objekte und nicht in die entsprechenden Java-Typen umgewandelt werden.

## 12.1.2 Skripten kompilieren

Wenn Sie dasselbe Skript mehrfach ausführen wollen (und dabei wahrscheinlich jedes Mal andere Bindungen verwenden wollen), ist es effizienter, wenn Sie die Skripten erst kompilieren und dann die kompilierte Version aufrufen. Das können Sie mit Code wie dem folgenden tun:

    // Das ist der Text des Skripts, das wir kompilieren wollen.
    String scripttext = "x * x";

    // Die Script-Engine erzeugen.
    ScriptEngineManager scriptManager = new ScriptEngineManager( );
    ScriptEngine js = scriptManager.getEngineByExtension("js");

    // Sie auf das Compilable-Interface casten, um die Kompilierungsfunktionalität
    // zu erhalten.
    Compilable compiler = (Compilable)js;

    // Das Skript in eine Form kompilieren, die wir wiederholt ausführen können.
    CompiledScript script = compiler.compile(scripttext);

    // Jetzt das Skript fünfmal ausführen und dabei jedes Mal einen anderen Wert
    // für die Variable x verwenden.
    Bindings bindings = js.createBindings( );
    for(int i = 0; i < 5; i++) {
      bindings.put("x", i);
      Object ergebnis = script.eval(bindings);
      System.out.printf("f(%d) = %s%n", i, ergebnis);
    }

## 12.1.3 JavaScript-Funktionen aufrufen

Das Package `javax.script` ermöglicht es Ihnen auch, ein Skript einmal auszuführen und dann wiederholt die Funktionen aufzurufen, die vom Skript definiert werden. Das können Sie folgendermaßen tun:

    // Einen Interpreter oder eine "ScriptEngine" zur Ausführung des Skripts erzeugen.
    ScriptEngineManager scriptManager = new ScriptEngineManager( );
    ScriptEngine js = scriptManager.getEngineByExtension("js");

    // Das Skript auswerten. Wir verwerfen das Ergebnis, da wir uns nur für die
    // Funktionsdefinition interessieren.
    js.eval("function f(x) { return x*x; }");

    // Jetzt eine Funktion aufrufen, die vom Skript definiert wird.
    try {
      // Die ScriptEngine auf das Invokable-Interface casten, um Zugriff
      // auf die Aufruffunktionalität zu erhalten.
      Invocable invocable = (Invocable) js;
      for(int i = 0; i < 5; i++) {
        Object ergebnis = invocable.invoke("f", i);   // f(i) berechnen
        System.out.printf("f(%d) = %s%n", i, ergebnis); // das Ergebnis ausgeben
      }
    }
    catch(NoSuchMethodException e) {
      // Das passiert, wenn das Skript keine Funktion namens "f" definiert.
      System.out.println(e);
    }

## 12.1.4 In JavaScript Interfaces implementieren

Das im vorangehenden Abschnitt demonstrierte Interface `Invocable` bietet auch die Möglichkeit, Interfaces in JavaScript zu implementieren. Beispiel 12-3 nutzt den JavaScript-Code in der Datei _listener.js_ , um das Interface `java.awt.event.KeyListener` zu implementieren:

Beispiel 12-3. Ein Java-Interface mit JavaScript-Code implementieren

    import javax.script.*;
    import java.io.*;
    import java.awt.event.*;
    import javax.swing.*;

    public class Keys {
      public static void main(String[] args) throws ScriptException, IOException
      {
        // Einen Interpreter oder eine "ScriptEngine" zur Ausführung des Skripts erzeugen.
        ScriptEngineManager scriptManager = new ScriptEngineManager( );
        ScriptEngine js = scriptManager.getEngineByExtension("js");

        // Das Skript auswerten. Wir verwerfen das Ergebnis, weil wir uns nur für die
        // Funktionsdefinitionen darin interessieren.
        js.eval(new FileReader("listener.js"));

        // Auf Invocable casten und ein Objekt erhalten, das KeyListener implementiert.
        Invocable invocable = (Invocable) js;
        KeyListener listener = invocable.getInterface(KeyListener.class);

        // Jetzt diesen KeyListener in einem sehr einfachen GUI verwenden.
        JFrame frame = new JFrame("Keys Demo");
        frame.addKeyListener(listener);
        frame.setSize(200, 200);
        frame.setVisible(true);
      }
    }

Die Implementierung eines Interfaces in JavaScript bedeutet einfach, für jede Methode, die vom Interface definiert wird, eine Funktion gleichen Namens zu erzeugen. Hier ist beispielsweise ein einfaches Skript, das `KeyListener` implementiert:

    function keyPressed(e) {
      print("Taste gedrückt: " + String.fromCharCode(e.getKeyChar( )));
    }
    function keyReleased(e) { /* tut nichts */ }
    function keyTyped(e) { /* tut nichts */ }

Beachten Sie, dass die hier definierte JavaScript-Methode `keyPressed( )` ein `java.awt. event.KeyEvent`-Objekt als Argument akzeptiert und eigentlich eine Methode auf diesem Java-Objekt aufruft. Der nächste Abschnitt erklärt, wie das gemacht wird.

* * *

[42] Dieses Kapitel ist für Java-Programmierer gedacht, und viele der Beispiele in diesem Kapitel sind teilweise oder vollständig in Java geschrieben. Wenn Sie nicht bereits wissen, wie man in Java programmiert, sollten Sie dieses Kapitel überspringen.

[43] Als dies geschrieben wurde, befand sich Java 6 noch in der Entwicklung. Das Package `javax.script` scheint so stabil zu sein, dass es hier dokumentiert werden kann. Es besteht aber die Möglichkeit, dass sich die hier dokumentierten APIs vor der endgültigen Veröffentlichung noch verändern.

[44] Wie später in diesem Kapitel gezeigt wird, kann JavaScript-Code alle öffentlichen Member jeder öffentlichen Klasse verwenden. Aus diesem Grund werden Sie Java-Code, der ein benutzerdefiniertes Skript ausführt, mit einer eingeschränkten Menge von Sicherheitsberechtigungen ausführen. Eine Beschreibung des Java-Sicherheits-Frameworks geht allerdings über den Horizont dieses Buchs hinaus.

# 12.2 Java skripten

JavaScript-Interpreter schließen häufig ein Feature ein, das es JavaScript-Code ermöglicht, die Felder von Java-Objekten abzufragen und zu setzen und ihre Methoden aufzurufen. Wenn ein Skript über ein Methodenargument oder ein `Bindings`-Objekt Zugriff auf ein Objekt hat, kann es auf diesem Java-Objekt fast so operieren, als wäre es ein JavaScript-Objekt. Und wenn einem Skript keine Referenzen auf Java-Objekte übergeben werden, kann es seine eigenen erzeugen. Die Mitarbeiter von Netscape waren die ersten, die Java-Scripting mit JavaScript implementierten, als sie ihren Spidermonkey-Interpreter dazu in die Lage versetzten, Applets in Webbrowsern zu skripten. Netscape nannte seine Technologie _LiveConnect_. Der Rhino-Interpreter und Microsofts JScript-Interpreter haben die LiveConnect-Syntax übernommen, und der Name LiveConnect wird im gesamten Kapitel verwendet, um auf eine beliebige JavaScript/Java-Scripting-Implementierung zu verweisen.

Beginnen wir diesen Abschnitt mit einem Überblick über die LiveConnect-Features. Auf diesen Überblick folgt ein Unterabschnitt, der LiveConnect ausführlich erklärt.

Beachten Sie, dass Rhino und Spidermonkey etwas unterschiedliche Versionen von LiveConnect implementieren. Die hier beschriebenen Features sind Rhino-Features, die in Skripten verwendet werden können, die in Java 6 eingebettet werden. Spidermonkey implementiert eine Untermenge dieser Features und wird in Kapitel 23 besprochen.

Wenn ein Java-Objekt über ein `Bindings`-Objekt an ein JavaScript-Skript übergeben wird oder an eine JavaScript-Funktion übergeben wird, kann JavaScript dieses Objekt fast so manipulieren, als wäre es ein natives JavaScript-Objekt. Alle öffentlichen Felder und Methoden des Java-Objekts werden als Eigenschaften des JavaScript-Wrapper-Objekts veröffentlicht. Nehmen Sie beispielsweise an, einem Skript wird eine Referenz auf ein Java-Objekt übergeben, das Diagramme zeichnet. Nehmen Sie jetzt an, dass dieses Objekt ein Feld namens `zeilenFarbe` definiert, dessen Typ `String` ist, und dass das JavaScript-Skript seine Referenz auf das Diagramm-Objekt in einer Variablen namens `chart` speichert. JavaScript-Code kann das Feld dann mit Code wie diesem setzen und abfragen:

    var diagrammFarbe = diagramm.zeilenFarbe; // Ein Java-Feld lesen.
    diagramm.zeilenFarbe = "#ff00ff";     // Ein Java-Feld setzen.

JavaScript kann sogar die Werte von Feldern setzen und abrufen, die Arrays sind. Nehmen wir an, dass das Diagramm-Objekt zwei Felder definiert, die folgendermaßen deklariert sind (Java-Code):

    public int anzahlPunkte;
    public double[] punkte;

Ein JavaScript-Programm könnte diese Felder mit Code wie diesem nutzen:

    for(var i = 0; i < diagramm.anzahlPunkte; i++)
      diagramm.punkte[i] = i*i;

Zusätzlich zum Abfragen und Setzen von Feldern eines Java-Objekts kann JavaScript auch die Methoden eines Objekt aufrufen. Nehmen wir beispielsweise an, dass das Diagramm-Objekt eine Methode namens `neuZeichnen( )` definiert. Diese Methode erwartet keine Argumente und sagt dem Objekt einfach, dass sein `punkte[]`-Array geändert wurde und dass es sich selbst neu zeichnen soll. JavaScript kann diese Methode einfach aufrufen, als wäre es eine JavaScript-Methode:

    diagramm.neuZeichnen( );

JavaScript kann auch Methoden aufrufen, die Argumente erwarten und Werte zurückliefern. Typumwandlungen werden nach Bedarf für die Methodenargumente und Rückgabewerte durchgeführt. Nehmen wir an, dass das Diagramm-Objekt Java-Methoden wie diese definiert:

    public void setDomain(double xmin, double xmax);
    public void setDiagrammTitel(String titel);
    public String getXAchsenBeschriftung( );

JavaScript kann diese Methoden mit Code wie diesem aufrufen:

    chart.setDomain(0, 20);
    chart.setDiagrammTitle("y = x*x");
    var label = chart.getXAchsenBeschriftung( );

Beachten Sie schließlich, dass Java-Methoden Java-Objekte als ihre Rückgabewerte zurückliefern können und dass JavaScript auch die öffentlichen Felder dieser Objekte lesen und schreiben und ihre öffentlichen Methoden aufrufen kann. JavaScript kann auch Java-Objekte als Argumente für Java-Methoden verwenden. Nehmen wir an, dass das Diagramm-Objekt eine Methode namens `getXAchse( )` definiert, die ein weiteres Java-Objekt zurückliefert, dass eine Instanz einer Klasse namens `Achse` ist. Nehmen wir außerdem an, dass das Diagramm-Objekt auch eine Methode namens `setYAchse( )` definiert, die ein `Achse`-Argument erwartet. Nehmen wir jetzt weiter an, dass `Achse` eine Methode namens `setTitel( )` hat. Dann könnten Sie diese Methoden mit JavaScript-Code wie diesem verwenden:

    var xachse = chart.getXAchse( );   // Ein Axis-Objekt abrufen.
    var neueyachse = xachse.clone( );   // Eine Kopie davon anlegen.
    neueyachse.setTitel("Y");      // Eine Methode darauf aufrufen...
    diagramm.setYAchse(neueyachse);   // ...und es einer anderen Methode übergeben

LiveConnect ermöglicht es JavaScript-Code außerdem, eigene Java-Objekte zu erzeugen. So kann JavaScript Java auch dann skripten, wenn ihm keine Java-Objekte übergeben werden.

Das globale Symbol `Packages` bietet Zugriff auf alle Java-Packages, die der JavaScript-Interpreter kennt. Der Ausdruck `Packages.java.lang` verweist auf das Package _java.lang_ , und der Ausdruck `Packages.java.lang.System` verweist auf die Klasse _java.lang.System_. Der Bequemlichkeit halber ist das globale Symbol `java` eine Kurzform für `Packages.java`. JavaScript-Code könnte folgendermaßen eine statische Methode dieser Klasse _java.lang. System_ aufrufen:

    // Die statische Java-Methode System.getProperty( ) aufrufen
    var javaVersion = java.lang.System.getProperty("java.version");

Der Einsatz von LiveConnect ist nicht auf die Systemklassen beschränkt, weil LiveConnect Ihnen erlaubt, den `new`-Operator von JavaScript zu verwenden, um neue Instanzen von Java-Klassen zu erzeugen. Betrachten Sie als Beispiel den folgenden JavaScript-Code, der Java Swing-GUI-Komponenten erzeugt und anzeigt:

    // Eine Kurzform für die javax.*-Package-Hierarchie erzeugen.
    var javax = Packages.javax;

    // Ein paar Java-Objekte erzeugen.
    var frame = new javax.swing.JFrame("Hallo Welt");
    var button = new javax.swing.JButton("Hallo Welt");
    var font = new java.awt.Font("SansSerif", java.awt.Font.BOLD, 24);

    // Auf den neuen Objekten Methoden aufrufen.
    frame.add(button);
    button.setFont(font);
    frame.setSize(200, 200);
    frame.setVisible(true);

Um zu verstehen, wie LiveConnect vorgeht, um JavaScript mit Java zu verbinden, müssen Sie die JavaScript-Datentypen verstehen, die LiveConnect nutzt. Die folgenden Abschnitte erklären diese JavaScript-Datentypen.

## 12.2.1 Die Klasse JavaPackage

Ein _Package_ ist in Java eine Sammlung verwandter Java-Klassen. Die Klasse JavaPackage ist ein JavaScript-Datentyp, der ein Java-Package repräsentiert. Die Eigenschaften eines JavaPackage sind die Klassen, die das Package enthält (Klassen werden von der Klasse JavaClass repräsentiert, die Sie in Kürze sehen werden), sowie eventuelle weitere Packages, die das Package enthält. Die Klassen in einem JavaPackage sind nicht aufzählbar. Sie können also keine `for/in`-Schleife verwenden, um Package-Inhalte zu inspizieren.

Alle JavaPackage-Objekte sind in einem Eltern-JavaPackage enthalten. Die globale Eigenschaft namens `Packages` ist ein Top-level-JavaPackage, das als Wurzel der Package-Hierarchie dient. Sie hat Eigenschaften wie `java` und `javax`, die JavaPackage-Objekte sind, die die verschiedenen Hierarchien von Java-Klassen repräsentieren, die für den Interpreter verfügbar sind. Beispielsweise enthält das JavaPackage `Packages.java` das JavaPackage `Packages.java.awt`. Der Bequemlichkeit halber hat das globale Objekt außerdem eine `java`-Eigenschaft, die eine Kurzform für `Packages.java` ist. Statt `Packages.java.awt` können Sie also auch einfach `java.awt` eintippen.

Um mit dem Beispiel fortzufahren: `java.awt` ist ein JavaPackage-Objekt, das JavaClass-Objekte wie `java.awt.Button` enthält, das die Klasse _java.awt.Button_ repräsentiert. Aber es enthält außerdem ein weiteres JavaPackage-Objekt, `java.awt.image`, das das _java.awt. image_ -Package in Java repräsentiert.

Die Klasse JavaPackage hat allerdings ein paar Mängel. LiveConnect hat keine Möglichkeit, im Voraus zu sagen, ob eine Eigenschaft eines JavaPackage auf eine Java-Klasse oder auf ein weiteres Java-Package verweist. JavaScript nimmt also an, dass es eine Klasse ist, und versucht, eine Klasse zu laden. Wenn Sie einen Ausdruck wie `java.awt` verwenden, sucht LiveConnect also erst nach einer Klasse dieses Namens. Wenn LiveConnect keine Klasse findet, nimmt es an, dass die Eigenschaft auf ein Package verweist, hat aber keine Möglichkeit sicherzustellen, dass das Package tatsächlich existiert und echte Klassen enthält. Das verursacht einen zweiten Mangel: Wenn Sie einen Klassennamen falsch schreiben, behandelt LiveConnect ihn einfach als einen Package-Namen, anstatt Ihnen zu sagen, dass die Klasse, die Sie zu laden versuchen, nicht existiert.

## 12.2.2 Die Klasse JavaClass

Die Klasse JavaClass ist ein JavaScript-Datentyp, der eine Java-Klasse repräsentiert. Ein JavaClass-Objekt hat keine eigenen Eigenschaften: Alle seine Eigenschaften repräsentieren (und haben die gleichen Namen wie) die öffentlichen statischen Felder und Methoden der repräsentierten Java-Klasse. Diese öffentlichen statischen Felder und Methoden werden manchmal _Klassenfelder_ und _Klassenmethoden_ genannt, um anzuzeigen, dass sie mit einer Klasse statt mit einer Objektinstanz verknüpft sind. Anders als die Klasse JavaPackage ermöglicht JavaClass die Verwendung von `for/in`-Schleifen zur Aufzählung ihrer Eigenschaften. Beachten Sie, dass JavaClass-Objekte keine Eigenschaften haben, die die Instanzfelder und -methoden einer Java-Klasse repräsentieren. Individuelle Instanzen einer Java-Klasse werden durch die Klasse JavaObject repräsentiert, die im nächsten Abschnitt dokumentiert wird.

Wie weiter oben gezeigt wurde, sind JavaClass-Objekte in JavaPackage-Objekten enthalten. Beispielsweise ist `java.lang` ein JavaPackage, das eine `System`-Eigenschaft enthält. So ist `java.lang.System` ein JavaClass-Objekt, das die Java-Klasse _java.lang.System_ repräsentiert. Dieses JavaClass-Objekt hat seinerseits Eigenschaften wie `out` und `in`, die statische Felder der Klasse _java.lang.System_ repräsentieren. Sie können JavaScript nutzen, um auf diese Weise auf eine beliebige der Standard-Java-Systemklassen zu verweisen. Die Klasse _java.lang.Double_ heißt beispielsweise `java.lang.Double` (oder `Packages.java.lang.Double`), und die Klasse _javax.swing.JButton_ ist `Packages.javax.swing.JButton`.

Eine andere Möglichkeit, in JavaScript ein JavaClass-Objekt zu erhalten, ist die Verwendung der Funktion `getClass( )`. Wenn Sie ein beliebiges JavaObject-Objekt haben, können Sie ein JavaClass-Objekt erhalten, das die Klasse dieses Java-Objekts repräsentiert, indem Sie das JavaObject an `getClass( )` übergeben.[45]

Wenn Sie ein JavaClass-Objekt haben, können Sie damit verschiedene Dinge machen. Die Klasse JavaClass implementiert die LiveConnect-Funktionalität, die es JavaScript-Programmen erlaubt, die öffentlichen statischen Felder von Java-Klassen zu lesen und zu schreiben und die öffentlichen statischen Methoden von Java-Klassen aufzurufen. Beispielsweise ist `java.lang.System` eine JavaClass. Sie können den Wert eines statischen Felds von `java.lang.System` auf diese Weise lesen:

    var java_console = java.lang.System.out;

Gleichermaßen können Sie eine statische Methode von `java.lang.System` mit einer Zeile wie dieser aufrufen:

    var java_version = java.lang.System.getProperty("java.version");

Erinnern Sie sich, dass Java eine typisierte Sprache ist: Alle Felder und Methodenargumente haben Typen. Wenn Sie versuchen, ein Feld auf einen Wert des falschen Typs zu setzen oder ein Argument des falschen Typs zu übergeben, wird eine Exception ausgelöst.

Die Klasse JavaClass hat ein weiteres wichtiges Feature. Sie können JavaClass-Objekte mit dem `new`-Operator von JavaScript verwenden, um neue Instanzen von Java-Klassen zu erzeugen – d.h., um JavaObject-Objekte zu erzeugen. Die Syntax dafür ist genau so wie in JavaScript (und genau so wie in Java):

    var d = new java.lang.Double(1.23);

Nachdem Sie auf diese Weise ein JavaObject erzeugt haben, können wir zur Funktion `getClass( )` zurückkehren und ein Beispiel für ihre Verwendung zeigen:

    var d = new java.lang.Double(1.23);  // Ein JavaObject erzeugen.
    var d_class = getClass(d);      // Die JavaClass des JavaObject abrufen.
    if (d_class == java.lang.Double) ...; // Dieser Vergleich ergibt true.

Anstatt auf eine JavaClass mit einem umständlichen Ausdruck wie `java.lang.Double` zu verweisen, können Sie eine Variable definieren, die als Kurzform dient:

    var Double = java.lang.Double;

Das ahmt die Java-Anweisung `import` nach und kann die Effizienz Ihres Programms verbessern, weil LiveConnect nicht die `lang`-Eigenschaft von `java` und die `Double`-Eigenschaft von `java.lang` nachschlagen muss.

## 12.2.3 Packages und Klassen importieren

Die Rhino-Version von LiveConnect definiert globale Funktionen zum Importieren von Java-Packages und Klassen. Um ein Package zu importieren, übergeben Sie ein JavaPackage-Objekt an `importPackage( )`. Um eine Klasse zu importieren, übergeben Sie ein JavaClass-Objekt an `importClass( )`:

    importPackage(java.util);
    importClass(java.awt.List);

`importClass( )` kopiert ein einzelnes JavaClass-Objekt aus seinem JavaPackage-Objekt in das globale Objekt. Der `importClass( )`-Aufruf von oben ist mit diesem Code äquivalent:

    var List = java.awt.List;

`importPackage( )` kopiert nicht wirklich alle `JavaClass`-Objekte aus einem `JavaPackage` in das globale Objekt. Stattdessen (und mit fast der gleichen Wirkung) fügt es das Package der internen Liste von Packages hinzu, die durchsucht werden sollen, wenn nicht aufgelöste Bezeichner angetroffen werden, und kopiert nur die JavaClass-Objekte, die tatsächlich verwendet werden. Nachdem Sie den `importPackage( )`-Aufruf von oben ausgeführt haben, könnten Sie beispielsweise den JavaScript-Bezeichner `Map` verwenden. Wenn das nicht der Name einer deklarierten Variablen oder Funktion ist, wird er zu dem JavaClass-Objekt `java.util.Map` aufgelöst und dann in einer neu definierten `Map`-Eigenschaft des globalen Objekts gespeichert.

Beachten Sie, dass es kein guter Gedanke ist, `importPackage( )` auf dem Package `java.lang` aufzurufen, weil `java.lang` eine Reihe von Klassen definiert, die den gleichen Namen haben wie eingebaute JavaScript-Konstruktoren und -Umwandlungsfunktionen. Als Alternative zum Importieren von Packages können Sie das JavaPackage-Objekt einfach an einen praktischeren Platz kopieren:

    var swing = Packages.javax.swing;

Die Funktionen `importPackage( )` und `importClass( )` sind in Spidermonkey nicht verfügbar, aber das Importieren einzelner Klassen lässt sich leicht simulieren und ist sicherer, als den globalen Namensraum mit importierten Packages vollzustopfen.

## 12.2.4 Die Klasse JavaObject

Die Klasse JavaObject ist ein JavaScript-Datentyp, der ein Java-Objekt repräsentiert. Die Klasse JavaObject entspricht auf vielerlei Weise der Klasse JavaClass. Wie bei JavaClass hat ein JavaObject keine eigenen Eigenschaften. All seine Eigenschaften repräsentieren (und haben die gleichen Namen wie) die öffentlichen Instanzfelder und öffentlichen Instanzmethoden des Java-Objekts, die es repräsentiert. Wie bei JavaClass können Sie eine JavaScript-`for/in`-Schleife verwenden, um alle Eigenschaften eines JavaObject-Objekts aufzuzählen. Die Klasse JavaObject implementiert die LiveConnect-Funktionalität, die es Ihnen ermöglicht, die öffentlichen Instanzfelder eines Java-Objekts zu lesen und zu schreiben und die öffentlichen Instanzmethoden eines Java-Objekts aufzurufen.

Wenn `d` ein JavaObject ist, das eine Instanz der Klasse _java.lang.Double_ repräsentiert, können Sie eine Methode dieses Java-Objekts beispielsweise mit JavaScript-Code wie diesem aufrufen:

    n = d.doubleValue( );

Wie zuvor gezeigt, hat die Klasse _java.lang.System_ auch das statische Feld `out`. Dieses Feld verweist auf ein Java-Objekt der Klasse _java.io.PrintStream_. In JavaScript wird auf das entsprechende JavaObject folgendermaßen verwiesen:

    java.lang.System.out

Und eine Methode dieses Objekts kann so aufgerufen werden:

    java.lang.System.out.println("Hallo Welt!");

Ein JavaObject-Objekt ermöglicht es Ihnen außerdem, die öfffentlichen Instanzfelder des Java-Objekts aufzurufen, das es repräsentiert. Weder die Klasse _java.lang.Double_ noch die Klasse _java.io.PrintStream_ , die in den vorangehenden Beispielen verwendet wurden, haben irgendwelche öffentlichen Instanzfelder. Aber nehmen Sie an, Sie nutzen JavaScript, um eine Instanz der Klasse _java.awt.Rectangle_ zu erzeugen:

    r = new java.awt.Rectangle( );

Jetzt können Sie seine öffentlichen Instanzfelder mit JavaScript-Code wie diesem lesen und schreiben:

    r.x = r.y = 0;
    r.width = 4;
    r.height = 5;
    var umfang = 2*r.width + 2*r.height;

Das Schön an LiveConnect ist, dass es ermöglicht, ein Java-Objekt, `r`, so zu verwenden, als wäre es ein JavaScript-Objekt. Eine gewisse Vorsicht ist aber erforderlich: `r` ist ein JavaObject und verhält sich nicht genauso wie gewöhnliche JavaScript-Objekte. Die Unterschiede werden später genauer ausgeführt. Denken Sie auch daran, dass die Felder und Methodenargumente von Java-Objekten anders als bei JavaScript typisiert sind. Wenn Sie keine JavaScript-Werte der erforderlichen Typen angeben, verursachen Sie JavaScript-Exceptions.

## 12.2.5 Java-Methoden

Weil LiveConnect Java-Methoden über JavaScript-Eigenschaften zugreifbar macht, können Sie diese Methoden wie Datenwerte behandeln, genauso wie Sie es mit JavaScript-Funktionen machen können. Beachten Sie allerdings, dass Java-Instanzmethoden tatsächlich Methoden und keine Funktionen sind und über ein Java-Objekt aufgerufen werden müssen. Statische Java-Methoden können jedoch wie JavaScript-Funktionen behandelt und der Bequemlichkeit halber in den globalen Namensraum importiert werden:

    var isDigit = java.lang.Character.isDigit;

### 12.2.5.1 Zugriffsmethoden für Properties

Wenn ein Java-Objekt Instanzmethoden hat, die wie die Zugriffsmethoden für Properties (Getter-/Setter-Methoden) gemäß den JavaBeans-Namenskonventionen aussehen, macht die Rhino-Version von LiveConnect das durch diese Methoden veröffentlichte Property direkt als JavaScript-Eigenschaft verfügbar. Betrachten Sie beispielsweise die bereits vorgestellten `javax.swing.JFrame`\- und `javax.swing.JButton`-Objekte. `JButton` hat die Methoden `setFont( )` und `getFont( )`, und `JFrame` hat die Methoden `setVisible( )` und `isVisible( )`. LiveConnect macht diese Methoden verfügbar, definiert für `JButton`-Objekte aber auch eine `font`-Eigenschaft und für `JFrame`-Objekte eine `visible`-Eigenschaft. Die Folge ist, dass Sie nicht gezwungen sind, Codezeilen wie diese zu verwenden:

    button.setFont(font);
    frame.setVisible(true);

Stattdessen können Sie auch die folgenden Zeilen verwenden:

    button.font = font;
    frame.visible = true;

### 12.2.5.2 Überladene Methoden

Java-Klassen können mehrere Methoden mit dem gleichen Namen definieren. Wenn Sie die Eigenschaften eines JavaObject-Objekts aufzählen, das überladene Methoden hat, sehen Sie nur eine einzige Eigenschaft mit dem überladenen Namen. In der Regel ruft LiveConnect auf Basis der Typen der von Ihnen angegebenen Argumente die richtige Methode für Sie auf.

Gelegentlich müssen Sie aber vielleicht explizit auf eine bestimmte Überladung einer Methode verweisen. JavaObject und JavaClass machen überladene Methoden über spezielle Eigenschaften verfügbar, die den Methodennamen und die Methodenargumente einschließen. Nehmen wir an, Sie haben ein JavaObject `o`, das zwei Methoden namens `f` hat. Die eine akzeptiert ein `int`-Argument und die andere ein `boolean`-Argument. `o.f` ist eine Funktion, die die Java-Methode aufruft, die dem übergebenen Argument am ehesten entspricht. Sie können die beiden Methoden mit Code wie diesem aber auch explizit auseinanderhalten:

    var f = o['f'];             // eine der Methoden
    var bool_funktion = o['f(boolean)'];       // die boolean-Methode
    var int_funktion = o['f(int)'];           // die int-Methode

Wenn Sie als Teil des Eigenschaftsnamens Klammern angeben, können Sie nicht die gewöhnliche ».«-Notation einsetzen, um auf die Eigenschaft zuzugreifen, sondern müssen ihren Namen als String in eckigen Klammern schreiben.

Beachten Sie, dass der Typ JavaClass auch zwischen überschriebenen statischen Methoden unterscheiden kann.

## 12.2.6 Die Klasse JavaArray

Der letzte LiveConnect-Datentyp für JavaScript ist die Klasse JavaArray. Wie man erwarten könnte, repräsentieren Instanzen dieser Klasse Java-Arrays und bieten die LiveConnect-Funktionalität, die es JavaScript ermöglicht, die Elemente von Java-Arrays zu lesen. Wie JavaScript-Arrays (und wie Java-Arrays) hat ein JavaArray-Objekt eine `length`-Eigenschaft, die die Anzahl von Elementen angibt, die es enthält. Die Elemente eines JavaArray-Objekts werden mit dem üblichen JavaScript-Indexoperator für Arrays, `[]`, gelesen. Sie können außerdem in einer `for/in`-Scheife aufgezählt werden. Sie können JavaArray-Objekte einsetzen, um auf mehrdimensionale Arrays (eigentlich Arrays von Arrays) zuzugreifen, genau wie in JavaScript oder Java.

Lassen Sie uns als Beispiel eine Instanz der Klasse _java.awt.Polygon_ erzeugen:

    p = new java.awt.Polygon( );

Das JavaObject `p` hat die Eigenschaften `xpoints` und `ypoints`, die JavaArray-Objekte sind, die Java-Arrays mit Integern repräsentieren. (Wenn Sie die Namen und Typen dieser Eigenschaften in Erfahrung bringen wollen, schlagen Sie in einem Java-Handbuch die Dokumentation für _java.awt.Polygon_ nach.) Sie können diese JavaArray-Objekte verwenden, um das Java-Polygon mit Code wie diesem zufällig zu initialisieren:

    for(var i = 0; i < p.xpoints.length; i++)
      p.xpoints[i] = Math.round(Math.random( )*100);
    for(var i = 0; i < p.ypoints.length; i++)
      p.ypoints[i] = Math.round(Math.random( )*100);

## 12.2.7 Java-Arrays erzeugen

LiveConnect besitzt keine eingebaute Syntax für die Erzeugung von Java-Arrays oder die Umwandlung von JavaScript-Arrays in Java-Arrays. Wenn Sie ein Java-Array erzeugen müssen, müssen Sie das explizit über das Package `java.lang.reflect` machen:

    var p = new java.awt.Polygon( );
    p.xpoints = java.lang.reflect.Array.newInstance(java.lang.Integer.TYPE,5);
    p.ypoints = java.lang.reflect.Array.newInstance(java.lang.Integer.TYPE,5);
    for(var i = 0; i < p.xpoints.length; i++) {
      p.xpoints[i] = i;
      p.ypoints[i] = i * i;
    }

## 12.2.8 Mit LiveConnect Interfaces implementieren

Rhino LiveConnect ermöglicht es JavaScript-Skripten, Java-Interfaces über eine einfache Syntax zu implementieren: Behandeln Sie einfach das JavaClass-Objekt des Interfaces als Konstruktor, und übergeben Sie für jede der Methoden des Interfaces ein JavaScript-Objekt, das keine Eigenschaften hat. Sie können dieses Feature beispielsweise verwenden, um dem zuvor gezeigten Code zur GUI-Erstellung einen Event-Handler hinzuzufügen:

    // Das Zeug importieren, das wir brauchen.
    importClass(Packages.javax.swing.JFrame);
    importClass(Packages.javax.swing.JButton);
    importClass(java.awt.event.ActionListener);

    // Ein paar Java-Objekte erzeugen.
    var frame = new JFrame("Hallo Welt!");
    var button = new JButton("Hallo Welt!");

    // Das Interface ActionListener implementieren.
    var listener = new ActionListener({
        actionPerformed: function(e) { print("Hallo!"); }
      });

    // Dem Button sagen, was er tun soll, wenn er angeklickt wird.
    button.addActionListener(listener);

    // Den Button in den Frame stecken und diesen anzeigen.
    frame.add(button);
    frame.setSize(200, 200);
    frame.setVisible(true);

## 12.2.9 LiveConnect-Datenumwandlung

Java ist eine streng typisierte Sprache mit einer relativ großen Anzahl von Datentypen, während JavaScript eine nicht-typisierte Sprache mit einer relativ kleinen Anzahl von Datentypen ist. Weil das ein erheblicher struktureller Unterschied zwischen den beiden Sprachen ist, ist die Datenumwandlung eine der zentralen Aufgaben von LiveConnect. Wenn JavaScript ein Java-Feld setzt oder einer Java-Methode ein Argument übergibt, muss ein JavaScript-Wert in einen äquivalenten Java-Wert umgewandelt werden. Und wenn JavaScript ein Java-Feld liest oder den Rückgabewert einer Java-Methode erhält, muss dieser Java-Wert in einen kompatiblen JavaScript-Typ umgewandelt werden. Unglücklicherweise erfolgt die LiveConnect-Datenumwandlung etwas anders als die, die vom Package `javax.script` durchgeführt wird.

Die Abbildung 12-1 und Abbildung 12-2 illustrieren, wie die Datenumwandlung durchgeführt wird, wenn JavaScript Java-Werte schreibt bzw. liest.

Beachten Sie die folgenden Punkte zu den Datenumwandlungen, die in Abbildung 12-1 illustriert werden:

  * Abbildung 12-1 zeigt nicht alle möglichen Umwandlungen von JavaScript-Typen in Java-Typen. Das liegt daran, dass JavaScript-in-JavaScript-Typumwandlungen stattfinden können, bevor die JavaScript-in-Java-Umwandlungen stattfinden. Wenn Sie eine JavaScript-Zahl an eine Java-Methode übergeben, die ein _java.lang.String_ -Argument erwartet, wandelt JavaScript diese Zahl beispielsweise erst in einen JavaScript-String um, der dann in einen Java-String umgewandelt werden kann.

  * Eine JavaScript-Zahl kann in jeden der elementaren numerischen Java-Typen umgewandelt werden. Die tatsächlich durchgeführte Umwandlung ist natürlich davon abhängig, welchen Typ das zu setzende Java-Feld oder das zu übergebende Methodenargument hat. Beachten Sie, dass Sie dabei Genauigkeit verlieren können, wenn Sie eine große Zahl an ein Java-Feld des Typs `short` übergeben oder wenn Sie einen Gleitkommawert an einen integralen Java-Typ übergeben.

Abbildung 12-1. Die Datenumwandlungen, die durchgeführt werden, wenn JavaScript Java-Werte schreibt

  * Eine JavaScript-Zahl kann auch in eine Instanz der Java-Klasse _java.lang.Double_ konvertiert werden, aber nicht in eine Instanz einer verwandten Klasse wie _java. lang.Integer_ oder _java.lang.Float_.

  * JavaScript bietet keine Darstellungsmöglichkeit für Zeichendaten. Eine JavaScript-Zahl kann also auch in den elementaren Java-Typ `char` konvertiert werden.

  * Ein JavaObject in JavaScript wird »enthüllt«, wenn es an Java übergeben wird – das heißt, dass es in das Java-Objekt umgewandelt wird, das es repräsentiert. Beachten Sie allerdings, dass JavaClass-Objekte, anders als man erwarten könnte, nicht in Instanzen von _java.lang.Class_ konvertiert werden.

  * JavaScript-Arrays werden nicht in Java-Arrays konvertiert. JavaScript-Objekte, -Arrays und -Funktionen werden in Java-Objekte konvertiert, die keine standardisierte API haben und normalerweise als opak betrachtet werden.

Beachten Sie auch die folgenden Punkte zu den Umwandlungen, die in Abbildung 12-2 illustriert werden:

  * Da JavaScript keinen Typ für Zeichendaten besitzt, wird der elementare Java-Typ `char` in eine JavaScript-Zahl umgewandelt, nicht in einen String, wie man erwarten könnte.

Abbildung 12-2. Die Datenumwandlungen, die durchgeführt werden, wenn JavaScript Java-Werte liest

  * Eine Java-Instanz von _java.lang.Double_ , _java.lang.Integer_ oder einer ähnlichen Klasse wird nicht in eine JavaScript-Zahl konvertiert. Wie jedes andere Java-Objekt wird sie in JavaScript in ein JavaObject-Objekt konvertiert.

  * Ein Java-String ist eine Instanz von _java.lang.String_ und wird deswegen wie jedes andere Java-Objekt in ein JavaObject-Objekt statt in einen richtigen JavaScript-String umgewandelt.

  * Jede Art von Java-Array wird in JavaScript in ein JavaArray-Objekt konvertiert.

### 12.2.9.1 JavaScript-Umwandlungen von JavaObject-Objekten

Beachten Sie in Abbildung 12-2, dass ziemlich viele Java-Datentypen einschließlich Java-Strings (Instanzen von _java.lang.String_ ) in JavaScript in JavaObject-Objekte konvertiert werden anstatt in die tatsächlichen elementaren JavaScript-Typen wie Strings. Das bedeutet, dass Sie oft mit JavaObject-Objekten arbeiten, wenn Sie LiveConnect verwenden. JavaObject-Objekte verhalten sich anders als andere JavaScript-Objekt und Sie sollten sich einiger verbreiteter Fallstricke bewusst sein.

Erstens ist es nicht ungewöhnlich, dass man mit einem JavaObject arbeiten muss, das einer Instanz von _java.lang.Double_ oder einem anderen numerischen Objekt entspricht. In vielerlei Weise verhält sich ein solches JavaObject wie ein elementarer Zahlwert. Aber passen Sie auf, wenn Sie den `+`-Operator verwenden. Wenn Sie ein JavaObject (oder ein beliebiges JavaScript-Objekt) mit `+` verwenden, erzeugen Sie einen String-Kontext. Das Objekt wird also für eine Stringverkettung in einen String umgewandelt statt für eine Addition in eine Zahl. Um die Umwandlung explizit zu machen, übergeben Sie das JavaObject an die JavaScript-Umwandlungsfunktion `Number( )`.

Um ein JavaObject in einen JavaScript-String umzuwandeln, verwenden Sie die JavaScript-Umwandlungsfunktion `String( )` und rufen nicht `toString( )` auf. Alle Java-Klassen definieren oder erben eine Java-`toString( )`-Methode. Wenn Sie auf einem JavaObject `toString( )` aufrufen, rufen Sie also eine Java-Methode auf, die ein anderes JavaObject zurückliefert, das einen `java.lang.String` einhüllt, wie der folgende Code zeigt:

    var d = new java.lang.Double(1.234);
    var s = d.toString( ); // Wandelt in einen java.lang.String um, nicht in einen
                          // JavaScript-String
    print(typeof s);    // Gibt "object" aus, da es ein JavaObject ist
    s = String(d);     // Jetzt in einen JavaScript-String umwandeln
    print(typeof s);   // Zeigt "string" an

Beachten Sie auch, dass JavaScript-Strings eine `length`-Eigenschaft haben, die eine Zahl ist. Ein JavaObject andererseits, das einen `java.lang.String` umhüllt, hat eine `length`-Eigenschaft, die die `length( )`-Methode des Java-Strings repräsentiert.

Ein weiterer seltsamer Fall ist das JavaObject `java.lang.Boolean.FALSE`. Wird der Wert in einem Stringkontext verwendet, wird er in `false` umgewandelt. Wird er aber in einem Booleschen Kontext verwendet, wird er in `true` umgewandelt! Das liegt daran, dass das JavaObject nicht-`null` ist. Der Wert, den das Objekt enthält, spielt für diese Umwandlung einfach keine Rolle.

* * *

[45] Verwechseln Sie die JavaScript-Funktion `getClass( )`, die ein JavaClass-Objekt zurückliefert, nicht mit der Java-Methode `getClass( )`, die ein `java.lang.Class`-Objekt zurückliefert.

# Teil II. Clientseitiges JavaScript

Der zweite Teil des Buchs, der die Kapitel 13 bis 23 beinhaltet, dokumentiert JavaScript, wie es in Webbrowsern implementiert wird. Die Kapitel stellen eine Vielzahl von Objekten vor, die sich per Skript steuern lassen und die im Webbrowser bzw. in HTML- und XML-Dokumenten dargestellt werden können:

Kapitel 13, _JavaScript in Webbrowsern_
---
Kapitel 14, _Browserfenster skripten_
Kapitel 15, _Dokumente skripten_
Kapitel 16, _Cascading Style Sheets und dynamisches HTML_
Kapitel 17, _Events und Event-Handling_
Kapitel 18, _Formulare und Formularelemente_
Kapitel 19, _Cookies und Persistenz auf der Clientseite_
Kapitel 20, _HTTP skripten_
Kapitel 21, _JavaScript und XML_
Kapitel 22, _Skriptgesteuerte Grafiken auf der Clientseite_
Kapitel 23, _Java-Applets und Flash-Movies skripten_

# Kapitel 13. JavaScript in Webbrowsern

Im ersten Teil dieses Buchs wurde der Sprachkern von JavaScript beschrieben. Teil II wendet sich den Aspekten der Sprache zu, die in Webbrowsern verwendet werden, dem sogenannten _clientseitigen JavaScript_.[46] Die meisten der bislang betrachteten Beispiele waren zwar korrekte JavaScript-Programme, besaßen jedoch keinerlei Kontext. Dieses Kapitel stellt nun den Kontext bereit. Es beginnt mit einem Überblick über die Programmierumgebung, die der Webbrowser darstellt. Anschließend beschreibt es, wie man mit `<script>`-Tags, HTML-Event-Handler-Attributen und JavaScript-URLs tatsächlich JavaScript-Code in HTML-Dokumente einbettet. Auf diese Abschnitte über das Einbetten von JavaScript folgt ein Abschnitt, der das Ausführungsmodell für clientseitigen JavaScript-Code erklärt: Wie und wann der Webbrowser JavaScript-Code ausführt. Die dann folgenden Abschnitte behandeln drei wichtige Themen der JavaScript-Programmierung: Kompatibilität, Zugänglichkeit und Sicherheit. Das Kapitel schließt mit einer kurzen Beschreibung anderer webbezogener Einbettungen der Sprache JavaScript neben clientseitigem JavaScript.

Wenn JavaScript in einen Webbrowser eingebettet ist, bietet der Browser einen mächtigen und vielfältigen Satz von Funktionalitäten an und ermöglicht es, diese zu skripten. Die auf Kapitel 13 folgenden Kapitel konzentrieren sich jeweils auf einen wichtigeren Bereich der clientseitigen JavaScript-Funktionalität:

  * Kapitel 14, _Browserfenster skripten_ , erklärt, wie JavaScript die Fenster von Webbrowsern skripten kann, um beispielsweise Fenster zu öffnen und zu schließen, Dialogfenster anzuzeigen, Fenster zu veranlassen, bestimmte URLs zu laden, oder Fenster zu veranlassen, im Browserverlauf vor oder zurück zu gehen. Dieses Kapitel behandelt außerdem verschiedene andere Features von clientseitigem JavaScript, die in clientseitigem JavaScript mit dem Window-Objekt verknüpft sind.

  * Kapitel 15, _Dokumente skripten_ , erklärt, wie JavaScript mit dem Dokumentinhalt interagieren kann, der innerhalb eines Webbrowserfensters angezeigt wird, und wie es Inhalte in einem Dokument finden, einfügen, löschen oder ändern kann.

  * Kapitel 16, _Cascading Style Sheets und dynamisches HTML_ , behandelt die Interaktion von JavaScript und CSS und zeigt, wie JavaScript-Code die Darstellung eines Dokuments ändern kann, indem es CSS-Styles, -Klassen und -Stylesheets skriptet. Eine besonders mächtige Kombination von Scripting und CSS ist Dynamic HTML (oder DHTML), in dem HTML-Inhalt verborgen, angezeigt, bewegt und sogar animiert werden kann.

  * Kapitel 17, _Events und Event-Handling_ , erklärt Events und Event-Handling und zeigt, wie JavaScript Webseiten Interaktivität hinzufügt, indem es ermöglicht, auf Benutzereingaben zu reagieren.

  * Kapitel 18, _Formulare und Formularelemente_ , behandelt Formulare in HTML-Dokumenten und zeigt, wie JavaScript Benutzereingaben in Formularen sammeln, validieren, verarbeiten und absenden kann.

  * Kapitel 19, _Cookies und Persistenz auf der Clientseite_ , zeigt, wie JavaScript-Skripten mit HTTP-Cookies Daten persistent speichern können.

  * Kapitel 20, _HTTP skripten_ , führt HTTP-Scripting (allgemein als Ajax bekannt) ein und zeigt, wie JavaScript mit Webservern kommunizieren kann.

  * Kapitel 21, _JavaScript und XML_ , zeigt, wie JavaScript XML-Dokumentinformationen erzeugen, laden, parsen, transformieren, abfragen, serialisieren und extrahieren kann.

  * Kapitel 22, _Skriptgesteuerte Grafiken auf der Clientseite_ , zeigt verbreitete JavaScript-Techniken zur Grafikmanipulation, die in Webseiten Image-Rollover und Animationen erzeugen können. Es zeigt außerdem verschiedene Techniken, um dynamisch JavaScript-gesteuerte Vektorgrafiken zu zeichnen.

  * Kapitel 23, _Java-Applets und Flash-Movies skripten_ , erklärt, wie JavaScript mit Java-Applets und Flash-Movies interagieren kann, die in eine Webseite eingebettet sind.

# 13.1 Die Webbrowser-Umgebung

Um clientseitiges JavaScript verstehen zu können, muss man die Programmierumgebung verstehen, die von Webbrowsern bereitgestellt wird. Die folgenden Abschnitte beschreiben drei wichtige Eigenschaften dieser Programmierumgebung:

  * das Fensterobjekt (Window), das als globales Objekt und als globaler Ausführungskontext für clientseitiges JavaScript dient

  * die clientseitige Objekthierarchie und das Document Object Model als einen Teil dieser Hierarchie

  * das Event-gesteuerte Programmiermodell

Auf diese Abschnitte folgt eine Betrachtung der eigentlichen Rolle von JavaScript in der Entwicklung von Webanwendungen.

## 13.1.1 Das Fenster als globaler Ausführungskontext

Die Hauptaufgabe eines Webbrowsers besteht darin, HTML-Dokumente in einem Fenster anzuzeigen. In clientseitigem JavaScript stellt das Document-Objekt ein HTML-Dokument dar, und das Window-Objekt stellt das Browserfenster (oder den Frame) dar, in dem das Dokument angezeigt wird. Diese Objekte sind beide für die Programmierung mit clientseitigem JavaScript sehr wichtig, das Window-Objekt ist jedoch von zentraler Bedeutung, da es das globale Objekt in der clientseitigen Programmierung ist.

Wie in Kapitel 4 beschrieben wurde, gibt es in jeder Implementierung von JavaScript ein globales Objekt, das das obere Ende der Geltungsbereichskette bildet; die Eigenschaften dieses globalen Objekts sind globale Variablen. In clientseitigem JavaScript ist das Window-Objekt dieses globale Objekt. Es definiert eine Reihe von Eigenschaften und Methoden, die es Ihnen ermöglichen, auf das Fenster des Webbrowsers zuzugreifen. Außerdem definiert es Eigenschaften, die sich auf andere wichtige Objekte beziehen, wie beispielsweise die Eigenschaft `document` für das Document-Objekt. Darüber hinaus besitzt das Window-Objekt zwei Eigenschaften, die auf es selbst verweisen: `window` und `self`. Mit jeder dieser globalen Variablen können Sie direkt auf das Window-Objekt zugreifen.

Da das Window-Objekt das globale Objekt in clientseitigem JavaScript ist, sind alle globalen Variablen als Eigenschaften dieses Objekts definiert. Beispielsweise bewirken die beiden folgenden Programmzeilen das Gleiche:

    var antwort = 42;   // globale Variable deklarieren und initialisieren
    window.antwort = 42; // neue Eigenschaft des Window-Objekts erzeugen

Das Window-Objekt stellt das Fenster eines Webbrowsers oder einen Frame innerhalb eines Fensters dar. In clientseitigem JavaScript sind Browserfenster und Frames im Wesentlichen äquivalent. Man kann Anwendungen schreiben, die mehrere Fenster (oder Frames) verwenden. Jedes Fenster, das in einem Programm verwendet wird, besitzt ein eigenes Window-Objekt und definiert einen eigenen Ausführungskontext für clientseitigen JavaScript-Code. Daher ist eine globale Variable, die von JavaScript-Anweisungen in einem Fenster deklariert wurde, nicht in dem zweiten Fenster als globale Variable deklariert. JavaScript-Code im zweiten Fenster _kann_ jedoch auf eine globale Variable im ersten Fenster zugreifen, was bestimmten Sicherheitseinschränkungen unterliegt. Diese Themen werden in Kapitel 14 ausführlich betrachtet.

## 13.1.2 Die clientseitige Objekt-Hierarchie und das Document Object Model

Das Window-Objekt ist das zentrale Objekt in clientseitigem JavaScript. Alle anderen clientseitigen Objekte sind mit diesem Objekt verbunden. Beispielsweise definiert jedes Window-Objekt eine Eigenschaft `document`, die auf das Document-Objekt verweist, das zu diesem Fenster gehört, und eine Eigenschaft `location`, die auf das Location-Objekt des Fensters verweist. Wenn ein Webbrowser ein Dokument mit Frames anzeigt, enthält das `frames[]`-Array des Toplevel-Window-Objekts Referenzen auf das Window-Objekt, das die Frames repräsentiert. In clientseitigem JavaScript verweist der Ausdruck `document` also auf das Document-Objekt des aktuellen Fensters. Der Ausdruck `frames[1].document` verweist auf das Document-Objekt des zweiten Frames des aktuellen Fensters.

Das Document-Objekt (und andere clientseitige JavaScript-Objekte) hat also Eigenschaften, die auf andere Objekte verweisen. Beispielsweise besitzt jedes Document-Objekt ein Array `forms[]`, das die in ihm enthaltenen Formulare darstellt. Mit der folgenden Programmzeile können Sie auf eines von ihnen zugreifen:

    window.document.forms[0]

Jedes dieser Form-Objekte besitzt wiederum ein Array `elements[]`, das die im Formular verwendeten Formularelemente von HTML (Eingabefelder, Buttons usw.) als Objekte enthält. Als Extremfall wäre eine Programmzeile wie die folgende denkbar:

    parent.frames[0].document.forms[0].elements[3].options[2].text

Abbildung 13-1. Die clientseitige Objekthierarchie und das Level 0 DOM

Wie bereits gezeigt wurde, ist das Window-Objekt das globale Objekt am oberen Ende der Geltungsbereichskette. Auf alle anderen clientseitigen Objekte in JavaScript können Sie als Eigenschaften anderer Objekte zugreifen. Somit gibt es eine Hierarchie von JavaScript-Objekten, deren Wurzel das Window-Objekt ist. Abbildung 13-1 veranschaulicht diese Hierarchie.

Beachten Sie, dass in Abbildung 13-1 nur die Objekteigenschaften aufgeführt sind, die auf andere Objekte verweisen. Die meisten der abgebildeten Objekte besitzen mehr Methoden und Eigenschaften, als hier dargestellt sind.

Viele der dargestellten Objekte sind Nachfahren des Document-Objekts. Dieser Teilbaum der umfassenderen clientseitigen Objekt-Hierarchie ist als _Document Object Model_ (DOM) bekannt. Dieses ist besonders interessant, da es im Mittelpunkt eines Standardisierungsprojekts stand. Abbildung 13-1 zeigt die Document-Objekte, die _de facto_ zum Standard gehören, da sie in allen wichtigeren Browsern einheitlich implementiert sind. Gemeinsam sind sie als das _Level 0 DOM_ bekannt, da sie eine Basisebene an Funktionen für Dokumente bilden, die von allen Browsern unterstützt werden. Diese grundlegenden Document-Objekte werden in Kapitel 15 behandelt, das außerdem ein fortgeschritteneres Dokumentobjektmodell erklärt, das vom W3C standardisiert wurde. HTML-Formulare sind Teil des DOMs, sind aber so speziell, dass sie in einem eigenen Kapitel (siehe Kapitel 18) behandelt werden.

## 13.1.3 Event-gesteuertes Programmieren

In den Anfängen der Computerei, vor gar nicht allzu langer Zeit, wurden die meisten Programme im _Stapelbetrieb_ ausgeführt: Sie lasen einen (Lochkarten-)Stapel von Daten ein, führten einige Berechnungen mit diesen Daten aus und gaben die Ergebnisse aus. Mit dem Aufkommen von _Time-Sharing_ -Systemen und textbasierten Terminals wurde eine beschränkte Art von Interaktion möglich: Das Programm konnte Eingaben vom Benutzer anfordern und der Benutzer Daten eingeben. Diese konnten dann vom Computer verarbeitet werden, und die Ergebnisse wurden auf dem Bildschirm angezeigt.

Heutzutage, mit grafischen Benutzeroberflächen und Mäusen, stellt sich die Situation anders dar. Programme sind im Allgemeinen Event-gesteuert und reagieren auf asynchrone Eingaben des Benutzers in Form von Mausklicks und Tastatureingaben in Abhängigkeit von der Position des Mauszeigers. Ein Webbrowser ist eine solche grafische Benutzeroberfläche. Ein HTML-Dokument enthält eine eingebettete Benutzerschnittstelle (engl. _graphical user interface_ , kurz GUI) – daher unterstützt JavaScript die Event-gesteuerte Programmierung.

Es ist ohne weiteres möglich, ein statisches JavaScript-Programm zu schreiben, das keine Eingaben vom Benutzer entgegennimmt und jedes Mal exakt das Gleiche tut, wenn es ausgeführt wird. Manchmal sind derartige Programme nützlich. Meistens jedoch werden Sie dynamische Programme schreiben wollen, die mit dem Benutzer interagieren. Dazu müssen sie auf Eingaben des Benutzers reagieren können.

In clientseitigem JavaScript erzeugt der Webbrowser _Events_ (Ereignisse), wenn der Benutzer etwas eingibt, und benachrichtigt das Programm. Es gibt verschiedene Arten von Events, wie beispielsweise Tastatur- und Maus-Events. Wenn ein Event auftritt, versucht der Webbrowser, eine entsprechende Funktion, einen _Event-Handler_ , aufzurufen, um das Event zu verarbeiten. Um dynamische, interaktive JavaScript-Programme zu schreiben, müssen Sie daher geeignete Event-Handler definieren und im System registrieren, die der Browser dann bei Bedarf aufrufen kann.

Wenn Sie nicht bereits mit Event-gesteuerter Programmierung vertraut sind, kann es ein Weilchen dauern, bis Sie sich an sie gewöhnt haben. Im alten Modell schrieben Sie Ihr Programm als einen einzelnen großen Block, der einem wohldefinierten Ablauf folgte und von Anfang bis Ende ablief. Die Event-gesteuerte Programmierung stellt dieses Modell auf den Kopf. Hier schreiben Sie verschiedene, voneinander unabhängige (aber miteinander interagierende) Event-Handler. Sie rufen diese Event-Handler nicht direkt auf, sondern ermöglichen es dem System, sie zu geeigneten Zeitpunkten aufzurufen. Da sie durch die Eingaben des Benutzers ausgelöst werden, erfolgt der Aufruf asynchron zu unvorhersagbaren Zeitpunkten. Die meiste Zeit läuft das Programm gar nicht, sondern wartet lediglich darauf, dass das System einen der Event-Handler des Programms aufruft.

In den folgenden Abschnitten wird erläutert, wie JavaScript-Anweisungen in HTML-Dateien eingebettet werden. Er zeigt sowohl, wie wir statische Programme definieren können, die von Anfang bis Ende ablaufen, als auch, wie wir Event-Handler definieren und registrieren, die asynchron vom System aufgerufen werden. Events und Event-Handling werden in Kapitel 15 erneut behandelt, und später werden Events ausführlich in Kapitel 17 beschrieben.

## 13.1.4 Die Rolle von JavaScript im Web

Die Einführung in dieses Kapitel enthielt eine Liste mit den Webbrowser-Fähigkeiten, die mit clientseitigem JavaScript geskriptet werden können. Beachten Sie allerdings, dass eine Erklärung dessen, für was JavaScript verwendet werden _kann_ , nicht das Gleiche wie eine Erklärung dessen ist, wofür JavaScript verwendet werden _sollte_. Dieser Abschnitt versucht, die richtige Rolle von JavaScript in der Entwicklung von Webanwendungen zu beschreiben.

Webbrowser zeigen mit HTML strukturierten Text mit CSS-Stylesheets gestylt an. HTML definiert den Inhalt, und CSS bietet die Darstellung. Richtig verwendet, fügt JavaScript Inhalt und Darstellung _Verhalten_ hinzu. Die Rolle von JavaScript ist es, das Surferlebnis eines Benutzers zu erweitern, indem es es vereinfacht, Informationen zu erhalten oder zu übermitteln. Das Benutzererlebnis sollte nicht von JavaScript abhängig sein, aber JavaScript kann dabei helfen, dieses Erlebnis zu vereinfachen. Das kann JavaScript auf verschiedene Weisen erreichen. Hier sind einige Beispiele:

  * Erzeugen visueller Effekte wie Image-Rollover, die den Benutzer dezent anleiten und die Navigation der Seite erleichtern

  * Sortieren der Spalten einer Tabelle, um es einem Benutzer zu erleichtern, die Daten zu finden, die er benötigt

  * Verbergen bestimmter Inhalte und ausgewähltes Anzeigen von Details, während sich der Benutzer zu diesen Inhalten »vorarbeitet«

  * Glätten des Surferlebnisses, indem direkt mit einem Webserver kommuniziert wird, damit neue Informationen angezeigt werden können, ohne dass ein vollständiges Neuladen der Seite erforderlich wird

## 13.1.5 Barrierefreies JavaScript

Ein neues Paradigma der clientseitigen Programmierung namens _unobtrusive JavaScript_ (barrierefreies JavaScript) hat in der Gemeinschaft der Webentwickler Anhänger gewonnen. Wie der Name andeutet, besagt das Paradigma, dass JavaScript keine Aufmerksamkeit auf sich ziehen sollte – es sollte nicht aufdringlich sein.[47] Es sollte weder die Benutzer stören, die eine Webseite ansehen, noch die Inhaltsautoren, die HTML-Markup erstellen, noch die Webdesigner, die HTML-Vorlagen oder CSS-Stylesheets erstellen.

Es gibt keine festgelegte Formel für das Schreiben von barrierefreiem JavaScript. Es gibt jedoch eine Reihe hilfreicher Verfahrensweisen, die an anderer Stelle dieses Buchs behandelt werden, die Sie auf die richtige Bahn bringen.

Das erste Ziel von barrierefreiem JavaScript ist es, den JavaScript-Code vom HTML-Markup getrennt zu halten. Das trennt den Inhalt auf gleiche Weise vom Verhalten, wie der Inhalt von der Darstellung getrennt wird, indem das CSS in ein externes Stylesheet gepackt wird. Um dieses Ziel zu erreichen, stecken Sie Ihren gesamten JavaScript-Code in externe Dateien und schließen diese Dateien mit `<script src=>`-Tags in Ihr HTML-Markup ein (ausführliche Informationen finden Sie in "13.2.2 Skripten in externen Dateien"). Wenn Sie die Trennung von Inhalt und Verhalten streng handhaben, schließen Sie noch nicht einmal JavaScript-Code in die Event-Handler-Attribute Ihrer HTML-Dateien ein. Stattdessen schreiben Sie (in einer externen Datei) JavaScript-Code, der Event-Handler für die HTML-Elemente registriert, die sie benötigen (in Kapitel 17 erkläre ich, wie man das macht).

Als logische Konsequenz zu diesem Ziel sollten Sie anstreben, Ihre externen Dateien mit JavaScript-Code so modular wie möglich zu machen, indem Sie die Techniken verwenden, die in Kapitel 10 beschrieben wurden. Das ermöglicht es Ihnen, mehrere, voneinander unabhängige Codemodule in eine Webseite einzubinden, ohne dass Sie sich darüber Gedanken machen müssen, dass die Variablen und Funktionen in einem der Module die Variablen und Funktionen anderer Module überschreiben.

Das zweite Ziel von barrierefreiem JavaScript ist, dass es annehmbar scheitern soll. Ihre Skripten sollten als Erweiterungen von HTML-Inhalten konzipiert und entworfen sein. Aber diese Inhalte sollten auch ohne Ihren JavaScript-Code noch verfügbar sein (was beispielsweise passiert, wenn ein Benutzer in seinem Browser JavaScript deaktiviert). Eine wichtige Technik für das annehmbare Scheitern nennt man _Feature Testing_ : Bevor sie irgendwelche Aktionen unternehmen, sollten Ihre JavaScript-Module erst sicherstellen, dass die clientseitigen Features, die sie benötigen, in dem Browser verfügbar sind, in dem der Code ausgeführt wird. Feature Testing ist eine Kompatibilitätstechnik, die in "13.6.3 Feature Testing" ausführlicher beschrieben wird.

Das dritte Ziel von barrierefreiem JavaScript ist es, dass es die Zugänglichkeit der HTML-Seite nicht schmälern soll (und idealerweise die Zugänglichkeit verbessern soll). Wenn das Einschließen von JavaScript-Code die Zugänglichkeit von Webseiten verringert, stört der JavaScript-Code behinderte Benutzer, die auf zugängliche Webseiten angewiesen sind. JavaScript-Zugänglichkeit wird in "13.7 Zugänglichkeit" ausführlicher beschrieben.

Andere Formulierungen von barrierefreiem JavaScript können zusätzlich zu den hier beschriebenen Zielen noch andere Ziele einschließen. Eine grundlegende Quelle, unter der Sie mehr zu barrierefreiem Scripting erfahren können, ist »The JavaScript Manifesto«, das von der DOM Scripting Task Force unter <http://domscripting.webstandards.org/?page_id=2> veröffentlicht wurde.

* * *

[46] Der Begriff »clientseitiges JavaScript« stammt noch aus den Tagen, als JavaScript nur an zwei Orten verwendet wurde: in Webbrowsern (Clients) und Webservern. Da JavaScript immer mehr Verbreitung als Skriptsprache in unterschiedlichen Umgebungen findet, verliert der Begriff »clientseitig« seine Bedeutung, da er nicht angibt, _wessen_ Clientseite gemeint ist. Dennoch werden wir den Begriff in diesem Buch weiterverwenden.

[47] »Obtrude« ist ein obskures Synonym für »intrude.« Das American Heritage-Wörterbuch sagt: »Anderen mit übermäßiger Beharrlichkeit oder ohne Aufforderung etwas auferlegen.«

# 13.2 Skripten in HTML einbetten

Clientseitige JavaScript-Anweisungen können auf verschiedene Arten in HTML-Dokumente eingebettet werden:

  * zwischen den Tags `<script>` und `</script>`

  * aus einer externen Datei, die durch das Attribut `src` eines `<script>`-Tags angegeben wird

  * in einem Event-Handler, der als Wert eines HTML-Attributs wie `onclick` oder `onmouseover` angegeben wird

  * in eine URL, die das spezielle Protokoll `javascript:` verwendet

Dieser Abschnitt behandelt die `<script>`-Tags. Event-Handler und JavaScript-URLs werden später im Kapitel behandelt.

## 13.2.1 Das <script>-Tag

Clientseitige JavaScript-Programme sind Teil einer HTML-Datei und werden üblicherweise in die Tags `<script>` und `</script>` eingeschlossen.

    <script>
    // Hier kommt Ihr JavaScript-Code hin
    </script>

In XHTML wird der Inhalt eines `<script>`-Elements genau wie jeder andere Inhalt behandelt. Wenn Ihr JavaScript-Code die Zeichen `<` oder `&` enthält, werden diese Zeichen als XML-Markup interpretiert. Aus diesem Grund ist es am besten, wenn Sie Ihren gesamten JavaScript-Code in einen CDATA-Abschnitt packen, wenn Sie XHTML verwenden:

    <script><![CDATA[// Hier kommt Ihr JavaScript-Code hin
    ]]></script>

Ein einzelnes HTML-Dokument kann mehrere `<script>`-Elemente enthalten, die sich nicht überlappen dürfen. Diese Skripten werden in der Reihenfolge ausgeführt, in der ihre Anweisungen im Quelltext des Dokuments auftreten (das Attribut `defer`, das im "13.2.4 Das defer-Attribut" beschrieben wird, bietet allerdings eine Ausnahme). Zwar werden die Skripten innerhalb einer einzelnen Datei zu unterschiedlichen Zeiten ausgeführt, sie bilden jedoch ein gemeinsames JavaScript-Programm – in einem Skript definierte Funktionen und Variablen sind für alle folgenden Skripten in derselben Datei verfügbar. Beispielsweise können Sie das folgende Skript im `<head>` einer HTML-Seite haben:

    <script>function quadrat(x) { return x*x; }</script>

Später auf der gleichen HTML-Seite können Sie auf die Funktion `quadrat( )` verweisen, auch wenn das in einem anderen Skriptblock erfolgt. Der Kontext eines Skripts ist also immer die umgebende HTML-Seite, nicht der Skriptabschnitt:[48]

    <script>alert(quadrat(2));</script>

Beispiel 13-1 zeigt ein Beispiel einer HTML-Datei, die ein einfaches JavaScript-Programm enthält. Im Gegensatz zu den Programmfragmenten im ersten Teil dieses Buches ist dieses Beispiel in eine HTML-Seite eingebettet und besitzt damit einen klar definierten Kontext, innerhalb dessen es abläuft. Beachten Sie die Verwendung des Attributs `language` innerhalb des `<script>`-Tags. Es wird in "13.2.3 Die Skriptsprache angeben" erläutert.

Beispiel 13-1. Ein einfaches JavaScript-Programm in einer HTML-Datei

    <html>
    <head>
    <title>Datum und Uhrzeit</title>
    <script language="JavaScript">
    // Definiere eine Funktion für den späteren Gebrauch
    function schreibeDatumUndUhrzeit( ) {
      var d = new Date( );         // lade Datum und Uhrzeit
      document.write(d.toLocaleString( )); // füge sie in das Dokument ein
    }
    </script>
    </head>
    <body>
    Datum und Uhrzeit:<br>
    <script language="JavaScript">
     // Aufruf der oben definierten Funktion
     schreibeDatumUndUhrzeit( );
    </script>
    </body>
    </html>

Beispiel 13-1 führt außerdem die Funktion `document.write( )` vor. Clientseitiger JavaScript-Code kann diese Funktion verwenden, um HTML-Text in das Dokument am Ort des Skripts auszugeben (mehr Informationen zu dieser Methode finden Sie in Kapitel 15). Beachten Sie, dass die Möglichkeit, mit Skripten Ausgaben zu generieren, die in das HTML-Dokument eingefügt werden können, bedeutet, dass der HTML-Parser die JavaScript-Skripten als Teil des Parsing-Vorgangs interpretieren muss. Es ist nicht möglich, einfach den gesamten Skripttext in einem Dokument zu verketten und dieses dann als ein großes Skript auszuführen, nachdem das Dokument geparst wurde, weil jedes Skript in einem Dokument das Dokument ändern kann (siehe die Beschreibung des Attributs `defer` in "13.2.4 Das defer-Attribut").

## 13.2.2 Skripten in externen Dateien

Das `<script>`-Tag unterstützt ein `src`-Attribut, das eine URL einer Datei angibt, die JavaScript-Code enthält. Es wird so verwendet:

    <script src="../../scripts/util.js"></script>

Eine JavaScript-Datei hat üblicherweise die Erweiterung _.js_ und enthält reines JavaScript ohne `<script>`-Tags oder anderes HTML.

Ein `<script>`-Tag, für das das Attribut `src` angegeben ist, verhält sich genau so, als stünde der Inhalt der angegebenen JavaScript-Datei direkt zwischen den `<script>`\- und `</script>`-Tags. Code und Markup, die zwischen diesen Tags stehen, werden ignoriert. Beachten Sie, dass das schließende `</script>`-Tag auch erforderlich ist, wenn das Attribut `src` angegeben wird und es kein JavaScript zwischen den `<script>`\- und `</script>`-Tags gibt.

Die Verwendung des Attributs `src` bietet eine Reihe von Vorteilen:

  * Es vereinfacht Ihre HTML-Dateien, indem es es Ihnen ermöglicht, große Blöcke mit JavaScript-Code aus ihnen zu entfernen – das heißt, es hilft, Inhalt und Verhalten zu trennen. Die Verwendung des Attributs `src` ist einer der Eckpunkte barrierefreier JavaScript-Programmierung. (In "13.1.5 Barrierefreies JavaScript" erfahren Sie mehr zu dieser Programmierphilosophie.)

  * Wenn Sie eine Funktion oder anderen JavaScript-Code haben, die/der von vielen verschiedenen HTML-Dateien verwendet wird, können Sie sie/ihn in einer einzigen Datei vorhalten und in jede HTML-Datei einlesen, die sie/ihn benötigt. Das vereinfacht die Codepflege enorm.

  * Wenn JavaScript-Funktionen von mehreren Seiten verwendet werden, können sie, wenn Sie in eine separate JavaScript-Datei platziert werden, vom Browser gecacht werden und lassen sich deswegen schneller laden. Wenn JavaScript-Code von mehreren Seiten geteilt wird, kann die Zeitersparnis, die das Cachen bietet, die geringe Verzögerung mehr als aufwiegen, die der Browser braucht, um eine separate Netzwerkverbindung aufzubauen, um die JavaScript-Datei herunterzuladen, wenn sie zum ersten Mal angefordert wird.

  * Weil das Attribut `src` eine beliebige URL als Wert annehmen kann, kann ein JavaScript-Programm oder eine Webseite von einem Webserver Code nutzen, der von anderen Webservern exportiert wird. Eine Menge Internet-Werbung beruht auf diesem Umstand.

Dieser letzte Punkt hat wichtige Sicherheitsauswirkungen. Die Same-Origin-Sicherheitsrichtlinie, die in "13.8.2 Die Same Origin Policy" beschrieben wird, verhindert, dass das JavaScript in einem Dokument aus einer Domain mit Inhalten aus einer anderen Domain interagiert. Beachten Sie allerdings, dass die Herkunft des Skripts selbst keine Rolle spielt: Es kommt nur auf die Herkunft des Dokuments an, in das das Skript eingebettet ist. Deswegen ist die Same Origin Policy in diesem Fall nicht wirksam: JavaScript-Code kann mit dem Dokument interagieren, in das es eingebettet ist, selbst wenn der Code eine andere Herkunft hat als das Dokument. Wenn Sie das `src`-Attribut verwenden, um in Ihre Seite ein Skript einzuschließen, geben Sie dem Autor dieses Skripts (und dem Webmaster der Domain, von der das Skript geladen wird) vollständige Kontrolle über Ihre Webseite.

## 13.2.3 Die Skriptsprache angeben

Obwohl JavaScript die ursprüngliche Skriptsprache für das Web war und die am meisten verwendete Skriptsprache bleibt, ist es nicht die einzige. Die HTML-Spezifikation ist sprachneutral, und Browserhersteller können jede Skriptsprache unterstützen, die sie unterstützen wollen. In der Praxis ist die einzige Alternative zu JavaScript Microsofts Visual Basic Scripting Edition,[49] das vom Internet Explorer unterstützt wird.

Da es mehrere mögliche Skriptsprachen gibt, müssen Sie dem Webbrowser sagen, in welcher Sprache Ihre Skripten geschrieben sind. Das versetzt ihn in die Lage, die Skripten richtig zu interpretieren und Skripten zu überspringen, die in Sprachen geschrieben sind, die er nicht interpretieren kann. Sie können die Default-Skriptsprache für eine Datei mit dem HTTP-Header `Content-Script-Type` angeben, und Sie können diesen Header mit dem HTML-`<meta>`-Tag simulieren. Um anzugeben, dass all Ihre Skripten in JavaScript sind (es sei denn, Sie geben etwas anderes an), stecken Sie einfach das folgende Tag in das `<head>`-Element all Ihrer HTML-Dokumente:

    <meta http-equiv="Content-Script-Type" content="text/javascript">

In der Praxis ist es so, dass alle Browser voraussetzen, dass JavaScript die Default-Skriptsprache ist, selbst wenn der Server den `Content-Script-Type`-Header weglässt und Ihre Seiten das `<meta>`-Tag weglassen. Aber wenn Sie keine Default-Skriptsprache angeben oder den Default überschreiben wollen, sollten Sie das `type`-Attribut des `<script>`-Tags verwenden:

    <script type="text/javascript"></script>

Der traditionelle MIME-Typ für JavaScript-Programme ist »text/javascript«. Ein anderer Typ, der verwendet wurde, ist »application/x-javascript« (das »x-«-Präfix zeigt an, dass es sich um einen experimentellen, nicht-standardisierten Typ handelt). RFC 4329 standardisiert den Typ »text/javascript«, weil er allgemein verwendet wird. Da JavaScript-Programme aber eigentlich keine Textdokumente sind, markiert es diesen Typ allerdings als obsolet und empfiehlt stattdessen »application/javascript« (ohne das »x-«). Aktuell wird »application/javascript« aber noch nicht gut unterstützt. Wenn die Unterstützung dafür gereift ist, werden die geeignetsten `<script>`\- und `<meta>`-Tags folgendermaßen aussehen:

    <script type="application/javascript"></script>
    <meta http-equiv="Content-Script-Type" content="application/javascript">

Als das `<script>`-Tag eingeführt wurde, war es eine nicht standardisierte Erweiterung zu HTML und unterstützte kein `type`-Attribut. Stattdessen wurde die Skriptsprache über das Attribut `language` definiert. Dieses Attribut gibt einfach den allgemeinen Namen der Skriptsprache an. Wenn Sie JavaScript-Code schreiben, können Sie das `language`-Attribut folgendermaßen verwenden:

    <script language="JavaScript">
      // hier folgen die JavaScript-Anweisungen
    </script>

Und wenn Sie ein Skript in VBScript schreiben, verwenden Sie das Attribut wie folgt:

    <script language="VBScript">
      ' hier folgen die VBScript-Anweisungen
      ' (' ist ein Kommentarzeichen wie // in JavaScript)
    </script>

Die Spezifikation von HTML 4 standardisierte das `<script>`-Tag, erklärte jedoch das Attribut `language` für veraltet, da es keine standardisierte Menge von Namen für Skriptsprachen gibt. Gelegentlich sehen Sie `<script>`-Tags, die für die Standardkonformität das Attribut `type` verwenden und für die Rückwärtskompatibilität mit älteren Browsern das Attribut `language`:

    <script type="text/javascript" language="JavaScript"></script>

Das Attribut `language` wird gelegentlich verwendet, um mit Tags wie diesen die Version von JavaScript anzugeben, in der ein Skript geschrieben ist:

    <script language="JavaScript1.2"></script>
    <script language="JavaScript1.5"></script>

Theoretisch ignorieren Webbrowser Skripten, die in JavaScript-Versionen geschrieben sind, die sie nicht unterstützen. Das heißt, dass ein alter Browser, der JavaScript 1.5 nicht unterstützt, nicht versucht, ein Skript mit einem `language`-Attribut mit dem Wert »JavaScript1.5« auszuführen. Ältere Webbrowser respektieren diese Versionsnummer. Aber weil der Kern der Sprache JavaScript über eine Reihe von Jahren stabil geblieben ist, ignorieren viele neuere Browser alle Versionsnummern, die mit dem Attribut `language` angegeben wurden.

## 13.2.4 Das defer-Attribut

Wie bereits erwähnt, kann ein Skript die Methode `document.write( )` aufrufen, um einem Dokument dynamisch Inhalte hinzuzufügen. Deswegen muss der HTML-Parser, wenn er auf ein Skript stößt, normalerweise mit dem Parsen des Dokuments aufhören und warten, dass das Skript ausgeführt wurde. Der HTML 4-Standard definiert ein `defer`-Attribut für das `<script>`-Tag, um dieses Problem anzugehen.

Wenn Sie ein Skript schreiben, das keine Ausgabe in Dokumenten erzeugt – beispielsweise ein Skript, das eine Funktion definiert, aber nie `document.write( )` aufruft -, können Sie das Attribut `defer` (dt. _verzögern_ ) im `<script>`-Tag verwenden und dem Browser so mitteilen, dass er den Inhalt des HTML-Dokuments weiter analysieren und die Ausführung des Skripts aufschieben kann, bis ein Skript auftritt, dessen Ausführung nicht aufgeschoben werden kann. Die Aufschiebung der Ausführung eines Skripts ist besonders dann nützlich, wenn ein Skript aus einer externen Datei geladen wird. Wird das nicht aufgeschoben, muss der Browser warten, bis das Skript geladen ist, bevor er mit dem Parsen des Dokuments fortfahren kann. Das Aufschieben kann die Leistung bei Browsern verbessern, die das Attribut `defer` berücksichtigen. In HTML hat das Attribut `defer` keinen Wert. Es muss einfach im Tag vorhanden sein:

    <script defer>
      // Beliebiger JavaScript-Code, der nicht document.write( ) aufruft
    <script>

In XHTML ist jedoch ein Wert erforderlich:

    <script defer="defer"></script>

Aktuell ist der Internet Explorer der einzige Browser, der das Attribut `defer` berücksichtigt. Er macht das, wenn es mit dem Attribut `src` kombiniert wird. Er implementiert es aber nicht ganz richtig, weil aufgeschobene Skripten immer bis zum Ende des Dokuments aufgeschoben werden, anstatt nur so lange aufgeschoben werden, bis das nächste nicht-aufgeschobene Skript angetroffen wird. Das bedeutet, dass aufgeschobene Skripten im IE außerhalb der Reihenfolge ausgeführt werden und keine Funktionen definieren oder Variablen setzen sollen, die von nachfolgenden nicht-aufgeschobenen Skripten benötigt werden.

## 13.2.5 Das <noscript>-Tag

HTML definiert ein `<noscript>`-Element, das Inhalt aufnehmen soll, der nur dargestellt werden soll, wenn im Browser JavaScript deaktiviert wurde. Idealerweise sollten Sie Ihre Webseiten so gestalten, dass JavaScript nur als Verbesserung dient und die Seiten »annehmbar scheitern« und auch ohne JavaScript funktionieren. Sollte das allerdings nicht möglich sein, können Sie `<noscript>` einsetzen, um Benutzer zu informieren, dass JavaScript erforderlich ist, und eventuell einen Link auf alternativen Inhalt anzugeben.

## 13.2.6 Das </script>-Tag

Irgendwann kommen Sie vielleicht an den Punkt, dass Sie ein Skript schreiben, das die Methode `document.write( )` oder die Eigenschaft `innerHTML` verwendet, um (üblicherweise in einem anderen Fenster oder Frame) ein anderes Skript auszugeben. Dazu ist es notwendig, ein `</script>`-Tag auszugeben, mit dem das zu erzeugende Skript beendet wird. Dabei muss jedoch beachtet werden, dass der HTML-Interpreter nicht versucht, Ihren JavaScript-Code zu verstehen. Wenn er also die Zeichenfolge »</script>« in Ihrem Code findet, nimmt er an, dass es sich um das schließende Tag des gerade ausgeführten Skripts handelt – auch wenn die Zeichenfolge in Anführungszeichen eingeschlossen ist. Um dieses Problem zu umgehen, kann das Tag aufgeteilt und beispielsweise mit Hilfe eines Ausdrucks wie `"</" + "script>"` ausgegeben werden:

    <script>
    f1.document.write("<script>");
    f1.document.write("document.write('<h2>Dies ist das auszugebende Skript</h2>')");
    f1.document.write("</" + "script>");
    </script>

Stattdessen kann auch der Schrägstrich in `</script>` durch einen Backslash maskiert werden:

    f1.document.write("<\/script>");

In XHTML werden Skripten in CDATA-Abschnitte eingeschlossen. Das Problem mit den schließenden `</script>`-Tags tritt dort deswegen nicht auf.

## 13.2.7 Skripten vor älteren Browsern verbergen

Als JavaScript neu war, erkannten einige Browser das `<script>`-Tag nicht und haben den Inhalt des entsprechenden Elements deswegen (richtigerweise) als Text dargestellt. Der Benutzer, der die Webseite besuchte, sah den JavaScript-Code dann formatiert als einen großen sinnlosen Abschnitt und als Webseiteninhalt dargestellt! Der Workaround für dieses Problem war ein einfacher Hack, der HTML-Kommentare innerhalb des Skriptelementes verwendet. JavaScript-Programmierer schrieben ihre Skripten üblicherweise wie hier:

    <script language="JavaScript">
    <!-- Beginnt einen HTML-Kommentar, der das Skript verbirgt
        // Hier kommen die JavaScript-Anweisungen hin
        //       .
        //       .
    // Beendet den HTML-Kommentar, der das Skript verbirgt -->
    </script>

Oder etwas kompakter auf diese Weise:

    <script><!--
    // Hier kommt der Skriptrumpf hin
    //--></script>

Damit das funktioniert, modifiziert clientseitiges JavaScript die JavaScript-Kernsprache leicht, damit sich die Zeichenfolge `<!--` am Anfang eines Skripts genau so verhält wie `//`: Sie leitet einen einzeiligen Kommentar ein.

Die Browser, für die dieser Kommentierungs-Hack erforderlich war, sind längst Geschichte, aber auf vorhandenen Webseiten werden Sie wahrscheinlich noch auf diesen Hack treffen.

## 13.2.8 Nicht-standardgemäße <script>-Attribute

Microsoft hat zwei vollständig nicht-standardgemäße Attribute für das `<script>`-Tag definiert, die nur im Internet Explorer funktionieren. Die Attribute `event` und `for` ermöglichen es Ihnen, über das `<script>`-Tag Event-Handler zu definieren. Das Attribut `event` gibt den Namen des zu behandelnden Events an, und das Attribut `for` gibt den Namen oder die ID des Elements an, für das das Event behandelt werden soll. Der Inhalt des Skripts wird ausgeführt, wenn das angegebene Event im angegebenen Element eintritt.

Diese Attribute funktionieren nur im IE, und ihre Funktionalität kann leicht auf anderem Wege erreicht werden. Sie sollten sie nie einsetzen. Sie werden hier nur erwähnt, damit Sie sie kennen, falls Sie in vorhandenen Webseiten einmal auf sie stoßen sollten.

* * *

48] Die hier verwendete `alert( )`-Funktion ist ein einfaches Mittel, um in clientseitigem JavaScript Ausgaben anzuzeigen: Sie wandelt ihr Argument in einen String um und zeigt diesen String in einem Pop-up-Dialog an. Weitere Informationen zu Methode `alert( )` finden Sie in ["14.5 Einfache Dialogfenster", und in Beispiel 15-9 finden Sie eine Alternative zu `alert( )`, die kein Dialogfenster öffnet, das mit einem Mausklick geschlossen werden muss.

[49] Diese Sprache ist auch als VBScript bekannt. Der einzige Browser, der sie unterstützt, ist der Internet Explorer, sodass in VBScript geschriebene Skripten nicht portierbar sind. Die Schnittstelle zwischen VBScript und HTML gleicht der von JavaScript, die Sprache selbst hat jedoch eine andere Syntax. VBScript wird in diesem Buch nicht dokumentiert.

# 13.3 Event-Handler in HTML

JavaScript-Code in einem Skript wird ein einziges Mal ausgeführt: nämlich in dem Moment, wenn die HTML-Datei, in der das Skript enthalten ist, in den Webbrowser gelesen wird. Ein Programm, das nur diese Art von statischen Skripten verwendet, kann nicht dynamisch auf Benutzereingaben reagieren. Dynamischere Programme definieren Event-Handler, die vom Webbrowser automatisch aufgerufen werden, sobald bestimmte Events eintreten – beispielsweise wenn der Benutzer auf einen Button in einem Formular klickt. Da Events in clientseitigem JavaScript von HTML-Objekten (wie Buttons) ausgehen, werden Event-Handler als Attribute dieser Objekte definiert. Um beispielsweise einen Event-Handler zu definieren, der aufgerufen wird, sobald der Benutzer ein Kästchen in einem Formular ankreuzt, werden die Anweisungen des Event-Handlers als Attribut des HTML-Tags angegeben, das die Checkbox definiert:

    <input type="checkbox" name="options" value="geschenkverpackung"
        onclick="geschenkverpackung = this.checked;">

Von Interesse ist in diesem Zusammenhang das Attribut `onclick`. Der Wert des Strings im `onclick`-Attribut kann eine oder mehrere JavaScript-Anweisungen enthalten. Im letzteren Fall müssen die einzelnen Anweisungen durch Semikola voneinander getrennt werden. Sobald das angegebene Event – in diesem Fall ein Mausklick – auf der Checkbox eintritt, werden die JavaScript-Anweisungen innerhalb des Strings ausgeführt.

Auch wenn Sie in eine Event-Handler-Definition so viele JavaScript-Anweisungen einschließen können, wie Sie möchten, ist es eine verbreitete Technik, einfach Event-Handler-Attribute zu verwenden, um Funktionen aufzurufen, die an anderer Stelle in `<script>`-Tags definiert werden. Auf diese Weise wird der größte Teil des JavaScript-Programms in Skripten gehalten, damit JavaScript und HTML möglichst wenig miteinander vermengt werden.

Beachten Sie, dass HTML-Event-Handler-Attribute nicht die einzige Möglichkeit sind, JavaScript-Event-Handler zu definieren. Kapitel 17 zeigt, dass es möglich ist, JavaScript-Event-Handler für HTML-Elemente über JavaScript-Code in einem `<script>`-Element anzugeben. Einige JavaScript-Entwickler meinen, dass HTML-Event-Handler-Attribute niemals verwendet werden sollten – dass wirklich barrierefreies JavaScript eine vollständige Trennung von Inhalt und Verhalten verlangt. Gemäß dieser Art der JavaScript-Programmierung sollte der gesamte JavaScript-Code in externe Dateien gepackt werden, die aus dem HTML über das `src`-Attribut von `<script>`-Tags referenziert werden. Dieser externe JavaScript-Code kann dann alle Event-Handler definieren, die er benötigt, wenn er läuft.

Events und Event-Handler werden in Kapitel 17 noch viel ausführlicher behandelt; sie werden jedoch schon in den nächsten Kapiteln in verschiedenen Beispielen auftreten. Kapitel 17 enthält eine umfassende Liste von Event-Handlern; die am häufigsten benötigten sollen jedoch schon hier kurz erläutert werden:

`onclick`

Dieser Event-Handler wird von allen button-ähnlichen Elementen in Formularen sowie von den Tags `<a>` und `<area>` unterstützt. Er wird angestoßen, wenn der Benutzer auf das entsprechende Element klickt. Wenn ein `onclick`-Event-Handler den Wert `false` zurückgibt, führt der Browser die für den Button oder Link voreingestellte Aktion nicht aus. Beispielsweise wird (bei einem `<a>`-Tag) ein Link nicht verfolgt oder (bei einem **Submit** -Button) ein Formular nicht abgeschickt.

`onmousedown`, `onmouseup`

Diese beiden Event-Handler ähneln `onclick` stark, werden jedoch einzeln ausgelöst, wenn der Benutzer eine Maustaste drückt bzw. loslässt. Die meisten Dokumentelemente unterstützen diese Handler.

`onmouseover`, `onmouseout`

Diese beiden Event-Handler werden angestoßen, wenn der Mauszeiger über ein Dokumentelement bzw. von diesem herunter bewegt wird.

`onchange`

Dieser Event-Handler wird von den Elementen `<input>`, `<select>` und `<textarea>` unterstützt. Er wird angestoßen, wenn der Benutzer den von dem Element angezeigten Wert ändert und dann den Eingabefokus von diesem Element entfernt.

`onload`

Dieser Event-Handler kann im `<body>`-Tag erscheinen und wird ausgelöst, wenn das Dokument und sein externer Inhalt (wie Grafiken) vollständig geladen sind. Der `onload`-Handler wird häufig verwendet, um Code anzustoßen, der den Dokumentinhalt manipuliert, weil er anzeigt, dass das Dokument einen stabilen Zustand erreicht hat und gefahrlos modifiziert werden kann.

Ein einigermaßen realistisches Beispiel für die Verwendung von Event-Handlern gab bereits das Skript zur Berechnung des Einkommens (Beispiel 1-3). Das HTML-Formular dort enthält verschiedene Attribute für Event-Handler. Der Body dieser Event-Handler ist jeweils einfach: Sie rufen die Funktion `calculate( )` auf, die an anderer Stelle in einem `<script>` definiert ist.

# 13.4 JavaScript in URLs

Eine weitere Möglichkeit, JavaScript-Anweisungen auf der Clientseite einzubinden, besteht in der Angabe einer URL mittels des Pseudo-Protokolls `javascript:`. Dieser besondere Protokolltyp gibt an, dass der Inhalt der URL ein beliebiger String mit JavaScript-Code ist, der vom JavaScript-Interpreter ausgeführt werden soll. Er wird als eine einzige Codezeile behandelt. Das bedeutet, dass Anweisungen durch Semikola getrennt werden müssen und `/* */`-Kommentare anstelle von `//`-Kommentaren verwendet werden müssen. Eine JavaScript-URL könnte so aussehen:

    javascript:var jetzt = new Date( ); "<h1>Es ist jetzt:</h1>" + jetzt;

Wenn der Browser eine dieser JavaScript-URLs lädt, führt er den JavaScript-Code aus, der in der URL enthalten ist, und verwendet den in einen String umgewandelten Wert der letzten JavaScript-Anweisung bzw. des letzten JavaScript-Ausdrucks als Inhalt des neu anzuzeigenden Dokuments. Dieser String kann HTML-Tags enthalten und wird wie jedes andere in den Browser geladene Dokument formatiert und angezeigt.

JavaScript-URLs können auch JavaScript-Anweisungen enthalten, die Aktionen ausführen, aber keinen Wert zurückgeben, wie beispielsweise:

    javascript:alert("Hallihallo!")

Wird eine derartige URL geladen, führt der Browser die Anweisungen aus. Da aber kein Wert zurückgegeben wird, ist kein neues Dokument anzuzeigen, und das momentan angezeigte Dokument bleibt unverändert.

Oft wollen Sie eine `javascript:`-URL verwenden, um JavaScript-Code auszuführen, das momentan angezeigte Dokument aber nicht verändern. Dies können Sie beispielsweise erreichen, indem Sie den Operator `void` verwenden, um einen undefinierten Rückgabewert ausdrücklich festzulegen. Verwenden Sie einfach die Anweisung `void 0;` am Ende Ihrer `javascript:`-URL. Die folgende URL öffnet beispielsweise ein neues leeres Browserfenster, ohne dass der Inhalt des aktuellen Fensters geändert wird:

    javascript:window.open("about:blank"); void 0;

Ohne den Operator `void` in dieser URL würde der Rückgabewert der Methode `Window.open( )` in einen String konvertiert und angezeigt werden. Dadurch würde das aktuelle Dokument durch ein neues »Dokument« ähnlich dem folgenden Text überschrieben:

    [object Window]

Sie können eine `javascript:`-URL überall dort verwenden, wo auch eine normale URL auftreten darf. Eine praktische Anwendung besteht in der Möglichkeit, einen solchen Ausdruck direkt in die Adressleiste des Browsers einzugeben. So kann ein beliebiges JavaScript-Programmstück getestet werden, ohne dass ein Editor geöffnet und eine HTML-Datei mit dem entsprechenden Programm erzeugt werden muss.

Das `javascript:`-Pseudoprotokoll kann in HTML-Attributen verwendet werden, deren Werte URLs sein sollten. Das `href`-Attribut eines Hyperlinks ist so ein Fall. Wenn der Benutzer auf einen solchen Link klickt, wird der angegebene JavaScript-Code ausgeführt. In diesem Kontext ist die JavaScript-URL im Wesentlichen ein Ersatz für einen `onclick`-Event-Handler. (Beachten Sie, dass die Verwendung eines `onclick`-Handlers oder einer JavaScript-URL für einen HTML-Link normalerweise eine schlechte Design-Entscheidung ist. Verwenden Sie stattdessen einen Button, und reservieren Sie Links für das Laden neuer Dokumente. Gleichermaßen kann eine JavaScript-URL als Wert des `action`-Attributs eines `<form>`-Tag verwendet werden, damit der JavaScript-Code in der URL ausgeführt wird, wenn der Benutzer das Formular absendet.

JavaScript-URLs können auch an Methoden wie `Window.open( )` (siehe Kapitel 14) verwendet werden, die URL-Argumente erwarten.

## 13.4.1 Bookmarklets

Besonders wichtig ist die Verwendung von `javascript:`-URLs in Lesezeichen. Dort bilden sie nützliche Mini-JavaScript-Programme oder _Bookmarklets_ , die leicht aus einem Menü oder einer Symbolleiste mit Lesezeichen gestartet werden können. Das folgende HTML-Fragment schließt ein `<a>`-Element mit einer `javascript:`-URL als Wert seines `href`-Attributs ein. Ein Klick auf den Link öffnet einen einfachen Evaluator für JavaScript-Ausdrücke, der es Ihnen ermöglicht, im Kontext der Seite Ausdrücke auszuwerten und Anweisungen auszuführen:

    <a href='javascript:
    var e = "", r = ""; /* Der auszuwertende Ausdruck und das Ergebnis */
    do {
      /* Den Ausdruck und das Ergebnis anzeigen und zur Eingabe eines neuen Ausdrucks
        auffordern */
      e = prompt("Ausdruck: " + e + "\n" + r + "\n", e);
      try { r = "Ergebnis: " + eval(e); } /* Versucht, den Ausdruck auszuwerten */
      catch(ex) { r = ex; }       /* Oder hält stattdessen den Fehler fest */
    } while(e); /* Setzt den Vorgang fort, bis keine Ausdrücke mehr eingegeben werden
            oder auf Abbrechen geklickt wird */
    void 0;   /* Das verhindert, dass das aktuelle Dokument überschrieben wird */
    '>
    JavaScript-Evaluator
    </a>

Beachten Sie, dass der HTML-Parser diese JavaScript-URL als eine einzige Zeile behandelt, auch wenn sie über mehrere Zeilen geschrieben wurde, und Ein-Zeilen-Kommentare `//` hier deswegen nicht funktionieren. So sähe der Link aus, wenn Kommentare und Whitespace entfernt würden:

    <a href='javascript:var e="",r="";do{e=prompt("Ausdruck: "+e+"\n"+r+"\n",e);
    try{r="Ergebnis: "+eval(e);}catch(ex){r=ex;}}while(e);void 0;'>JS-Evaluator</a>

Ein Link wie dieser ist nützlich, wenn er fest in eine Seite geschrieben wird, die Sie entwickeln, wird aber noch nützlicher, wenn er als Lesezeichen gespeichert wird, das auf einer beliebigen Seite ausgeführt werden kann. Üblicherweise können Sie ein Lesezeichen speichern, indem Sie mit der rechten Maustaste auf den Link klicken und »Lesezeichen für diesen Link hinzufügen« oder eine ähnliche Option wählen. In Firefox können Sie den Link auch einfach in die Lesezeichen-Symbolleiste oder -Sidebar ziehen.

Die clientseitigen JavaScript-Techniken, die in diesem Buch behandelt werden, sind alle auf die Erstellung von Bookmarklets anwendbar, aber Bookmarklets selbst werden nicht ausführlich behandelt. Wenn Ihnen die Möglichkeiten dieser kleinen Programme gefallen, sollten Sie eine Internetsuche nach »Bookmarklets« durchführen. Sie werden eine Reihe von Sites finden, die viele interessante und nützliche Bookmarklets anbieten.

# 13.5 Ausführung von JavaScript-Programmen

In den vorangegangenen Abschnitten wurde erläutert, wie JavaScript-Programme in eine HTML-Datei eingebettet werden. Wir wollen in den folgenden Abschnitten erklären, wie diese Programme vom JavaScript-Interpreter ausgeführt werden.

## 13.5.1 Skripten ausführen

JavaScript-Anweisungen, die zwischen `<script>`\- und `</script>`-Tags erscheinen, werden in der Reihenfolge ausgeführt, in der sie im Skript stehen. Wenn eine Datei mehrere Skripten enthält, werden die Skripten in der Reihenfolge ausgeführt, in der sie vorkommen (mit Ausnahme von Skripten mit dem Attribut `defer`, die IE außer der Reihe ausführt). Der JavaScript-Code in `<script>`-Tags wird als Teil des Vorgangs ausgeführt, bei dem das Dokument geladen und geparst wird.

Jedes `<script>`-Element, das kein `defer`-Attribut hat, kann die Methode `document.write( )` aufrufen (die in Kapitel 15 ausführlich beschrieben wird). Der Text, der dieser Methode übergeben wird, wird an der Position des Skripts in das Dokument eingefügt. Wenn die Ausführung des Skripts beendet ist, nimmt der HTML-Parser das Parsen des Dokuments bei der Textausgabe des Skripts wieder auf.

Skripten können im `<head>` oder `<body>` eines HTML-Dokuments stehen. Typischerweise definieren Skripten im `<head>` Funktionen, die von anderem Code aufgerufen werden können. Sie können auch Variablen deklarieren und initialisieren, die anderer Code verwendet. Häufig definieren Skripten im `<head>` eines Dokuments eine einzige Funktion und registrieren diese Funktion als einen `onload`-Event-Handler für die spätere Ausführung. Es ist zulässig, aber nicht so verbreitet, `document.write( )` im `<head>` eines Dokuments aufzurufen.

Skripten im `<body>` eines Dokuments können alles tun, was Skripten im `<head>` tun können. In solchen Skripten sieht man allerdings häufiger `document.write( )`-Aufrufe. Skripten im `<body>` eines Dokuments haben (unter Verwendung von Techniken, die in Kapitel 15 beschrieben werden) auch auf Zugriff auf Dokumentelemente und Dokumentinhalte, die vor dem Skript erscheinen, und können diese manipulieren. Wie später in diesem Kapitel beschrieben wird, wird allerdings nicht garantiert, dass Dokumentelemente verfügbar und stabil sind, wenn die Skripten im `<body>` ausgeführt werden. Wenn ein Skript nur Funktionen und Variablen für die spätere Verwendung definiert und `document.write( )` nicht aufruft oder versucht, den Dokumentinhalt auf andere Weise zu verändern, verlangen die Konventionen, dass es im `<head>` des Dokuments statt im `<body>` stehen soll.

Wie bereits erwähnt wurde, führt der IE Skripten mit dem Attribut `defer` außer der Reihe aus. Diese Skripten werden ausgeführt, nachdem alle nicht aufgeschobenen Skripten ausgeführt wurden und das Dokument vollständig geladen wurde, aber bevor der `onload`-Event-Handler ausgeführt wird.

## 13.5.2 Der onload-Event-Handler

Nachdem das Dokument geparst wurde, alle Skripten ausgeführt wurden und alle zusätzlichen Inhalte (wie Grafiken) geladen wurden, setzt der Browser das `onload`-Event ab und führt JavaScript-Code aus, der beim Window-Objekt als `onload`-Event-Handler registriert wurde. Ein `onload`-Handler kann registriert werden, indem das `onload`-Attribut des `<body>`-Tags gesetzt wird. Auch separate Module mit JavaScript-Code können (mit in Kapitel 17 gezeigten Techniken) ihre eigenen `onload`-Event-Handler registrieren. Wenn mehrere `onload`-Handler registriert sind, ruft der Browser alle Handler auf. Es wird aber nicht garantiert, in welcher Reihenfolge sie aufgerufen werden.

Wenn der `onload`-Handler ausgelöst wird, wurde das Dokument vollständig geladen und geparst. Beliebige Dokumentelemente können dann von JavaScript-Code manipuliert werden. Aus diesem Grund enthalten JavaScript-Module, die Dokumentinhalte modifizieren, üblicherweise eine Funktion, die die Modifikation durchführt, und Code für die Event-Registrierung, der veranlasst, dass diese Funktion aufgerufen wird, wenn das Dokument vollständig geladen wurde.

Weil `onload`-Event-Handler aufgerufen werden, nachdem das Parsen des Dokuments abgeschlossen wurde, dürfen sie `document.write( )` nicht aufrufen. Anstatt etwas an das aktuelle Dokument anzuhängen, würde ein solcher Aufruf ein neues Dokument beginnen und das aktuelle Dokument überschreiben, bevor der Benutzer überhaupt die Chance hat, es anzusehen.

## 13.5.3 Event-Handler und JavaScript-URLs

Wenn das Laden und Parsen eines Dokuments beendet wird, wird der `onload`-Handler ausgelöst. Die JavaScript-Ausführung tritt dann in ihre Event-gesteuerte Phase. Während dieser Phase werden Event-Handler asynchron in Reaktion auf Benutzereingaben wie Mausbewegungen, Maus-Klicks und Tastendrücke ausgeführt. Während dieser Phase können außerdem JavaScript-URLs asynchron ausgeführt werden, beispielsweise wenn der Benutzer auf einen Link klickt, dessen `href`-Attribut das `javascript:`-Pseudoprotokoll verwendet.

`<script>`-Elemente werden üblicherweise verwendet, um Funktionen zu definieren, und Event-Handler werden üblicherweise verwendet, um diese Funktionen in Reaktion zu Benutzereingaben aufzurufen. Event-Handler können natürlich Funktionen definieren, aber das wird nicht häufig gemacht (und ist auch nicht sehr nützlich).

Wenn ein Event-Handler auf dem Dokument, zu dem er gehört, `document.write( )` aufruft, überschreibt er dieses Dokument und beginnt ein neues. Das ist fast nie das, was man beabsichtigt. Eine Faustregel ist deswegen, dass Event-Handler diese Funktion nie aufrufen sollten. Und sie sollten auch keine Funktionen aufrufen, die diese Methode aufrufen. Ausnahmen sind jedoch mehrfenstrige Anwendungen, in denen ein Event-Handler auf einem Fenster die `write( )`-Methode des Dokuments in einem anderen Fenster aufruft. (Siehe "14.8 Mehrere Fenster und Frames" zu mehrfenstrigen JavaScript-Anwendungen.)

## 13.5.4 Der onunload-Event-Handler

Wenn der Benutzer von einer Webseite wegnavigiert, löst der Browser den `onunload`-Event-Handler aus und gibt dem JavaScript-Code auf der Seite eine letzte Chance, ausgeführt zu werden. Sie können einen `onunload`-Handler definieren, indem Sie das `onunload`-Attribut des `<body>`-Tags setzen oder indem Sie eine der Techniken zur Registrierung von Event-Handlern verwenden, die in Kapitel 17 beschrieben werden.

Das `onunload`-Event ermöglicht es Ihnen, die Effekte rückgängig zu machen, die Ihr `onload`-Handler oder andere Skripten in Ihrer Webseite haben. Wenn Ihre Anwendung ein sekundäres Browserfenster öffnet, bietet der `onunload`-Handler beispielsweise eine Möglichkeit, dieses Fenster zu schließen, wenn der Benutzer Ihre Hauptseite verlässt. Der `onunload`-Handler sollte keine zeitaufwändigen Operationen durchführen oder Dialogfenster einblenden. Er dient nur dazu, schnelle Aufräumoperationen durchzuführen. Seine Ausführung sollte den Wechsel des Benutzers zu einer neuen Seite nicht verlangsamen oder behindern.

## 13.5.5 Das Window-Objekt als Ausführungskontext

Alle Skripten, Event-Handler und JavaScript-URLs in einem Dokument teilen das gleiche Window-Objekt als ihr globales Objekt. JavaScript-Variablen und Funktionen sind nichts anderes als Eigenschaften des globalen Objekts. Das bedeutet, dass eine Funktion, die in einem `<script>` definiert ist, vom Code in einem beliebigen nachfolgenden `<script>` aufgerufen werden kann.

Da das `onload`-Event erst abgesetzt wird, nachdem alle Skripten ausgeführt worden sind, hat jeder `onload`-Event-Handler Zugriff auf alle Funktionen und Variablen, die von allen Skripten im Dokument definiert bzw. deklariert werden.

Immer wenn ein neues Dokument in ein Fenster geladen wird, wird das Window-Objekt für dieses Fenster in seinen Default-Zustand zurückversetzt: Alle Eigenschaften und Funktionen, die von einem Skript im vorangehenden Dokument definiert wurden, werden gelöscht, und alle Standard-Systemeigenschaften, die geändert oder überschrieben worden sein können, werden wiederhergestellt. Jedes Dokument beginnt mit einer leeren Seite. Ihre Skripten können sich darauf verlassen. Sie erben keine beschädigte Umgebung vom vorangehenden Dokument. Das bedeutet auch, dass alle Variablen und Funktionen Ihres Skripts nur so lange bestehen, bis Ihr Dokument durch ein neues Dokument ersetzt wird.

Die Eigenschaften eines Window-Objekts haben die gleiche Lebensdauer wie das Dokument, das den JavaScript-Code enthält, der diese Eigenschaften definiert. Das Window-Objekt selbst hat eine längere Lebensdauer. Es besteht so lange wie das Fenster besteht, das es repräsentiert. Die Gültigkeit einer Referenz auf ein Window-Objekt ist nicht davon abhängig, wie viele Webseiten in diesem Fenster geladen und entladen werden. Das ist aber nur für Webanwendungen relevant, die mehrere Fenster oder Frames verwenden. In diesem Fall kann JavaScript-Code in einem Fenster oder Frame eine Referenz auf ein anderes Fenster oder einen anderen Frame festhalten. Diese Referenz bleibt auch dann gültig, wenn das andere Fenster oder der andere Frame ein neues Dokument lädt.

## 13.5.6 Das Threading-Modell von clientseitigem JavaScript

Die JavaScript-Kernsprache enthält keinen Threading-Mechanismus, und clientseitiges JavaScript fügt keinen hinzu. Clientseitiges JavaScript ist (oder verhält sich so, als wäre es) single-threaded. Das Parsen von Dokumenten wird angehalten, wenn Skripten geladen und ausgeführt werden, und Webbrowser reagieren nicht mehr auf Benutzereingaben, wenn Event-Handler ausgeführt werden.

Die Ausführung in einem Thread vereinfacht das Scripting: Wenn Sie Code schreiben, haben Sie die Sicherheit, dass zwei Event-Handler nie zur gleichen Zeit ausgeführt werden. Sie können Ihren Dokumentinhalt in dem Bewusstsein manipulieren, dass zur gleichen Zeit kein anderer Thread versucht, ihn zu ändern.

Die Ausführung in einem Thread erlegt JavaScript-Programmierern aber auch eine Last auf: Sie bedeutet, dass JavaScript-Skripten und -Event-Handler nicht zu lange laufen sollten. Wenn ein Skript eine berechnungsintensive Aufgabe ausführt, führt das zu einer Verzögerung beim Laden des Dokuments. Der Benutzer sieht den Dokumentinhalt erst, wenn das Skript fertig ist. Wenn ein Event-Handler eine berechnungsintensive Aufgabe ausführt, kann es passieren, dass der Browser nicht mehr reagiert, und der Benutzer wird wahrscheinlich denken, dass der Browser abgestürzt ist.[50]

Wenn Ihre Anwendung Berechnungen in einem Umfang durchführen muss, die eine bemerkbare Verzögerung verursachen, sollten Sie das Dokument vollständig laden, bevor Sie diese Berechnung ausführen. Außerdem sollten Sie den Benutzer informieren, dass eine Berechnung erfolgt und der Browser sich nicht aufgehängt hat. Wenn es möglich ist, Ihre Berechnung in separate Unteraufgaben aufzuspalten, können Sie Methoden wie `setTimeout( )` und `setInterval( )` (siehe Kapitel 14) verwenden, um die Unteraufgaben im Hintergrund auszuführen, während Sie eine Fortschrittsanzeige aktualisieren, die dem Benutzer eine Rückmeldung anzeigt.

## 13.5.7 Das Dokument während des Ladens manipulieren

Während ein Dokument geladen und geparst wird, kann JavaScript-Code in einem `<script>`-Element mit `document.write( )` Inhalte in das Dokument einfügen. Andere Arten der Dokumentmanipulation mit den DOM-Scripting-Techniken aus Kapitel 15 können in `<script>`-Tags erlaubt sein oder nicht.

Die meisten Browser scheinen es zu erlauben, alle Dokumentelemente zu manipulieren, die vor dem `<script>`-Tag erscheinen. Einige JavaScript-Programmierer tun das routinemäßig. Allerdings verlangt kein Standard, dass das funktioniert. Und einige erfahrene JavaScript-Programmierer hegen den hartnäckigen, wenn auch unbestimmten Verdacht, dass es zu Problemen führen kann, wenn man Code zur Dokumentmanipulation in `<script>`-Tags verwendet (vielleicht nur gelegentlich, nur bei manchen Browsern oder wenn ein Dokument neu geladen oder mit dem **Zurück** -Button des Browsers erneut besucht wird).

Der einzige Konsens, den es in dieser Grauzone gibt, besagt, dass es sicher ist, das Dokument zu manipulieren, nachdem das `onload`-Event abgesetzt wurde. Und das ist auch, was die meisten JavaScript-Anwendungen tun: Sie nutzen den `onload`-Handler, um alle Dokumentmanipulationen auszulösen. In Beispiel 17-7 stelle ich eine Hilfsroutine für die Registrierung von `onload`-Event-Handlern vor.

Bei Dokumenten, die große oder viele Grafiken enthalten, kann es sein, dass das Hauptdokument bereits eine ganze Weile geparst ist, bevor die Grafiken geladen werden und das `onload`-Event abgesetzt wird. In solchen Fällen möchten Sie die Manipulation des Dokuments vielleicht schon vor dem `onload`-Event beginnen. Eine Technik (über deren Sicherheit diskutiert wird) ist es, den Manipulationscode ans Ende des Dokuments zu stellen. Eine IE-spezifische Technik ist es, den Code zur Dokumentmanipulation in ein `<script>` zu stecken, das `defer`\- und `src`-Attribute hat. Eine Firefox-spezifische Technik ist es, den Code zur Dokumentmanipulation zu einem Event-Handler für das nicht dokumentierte DOMContentLoaded-Event zu machen, das abgesetzt wird, wenn das Dokument geparst ist, aber bevor externe Objekte wie Grafiken vollständig geladen sind.

Eine weitere Grauzone im JavaScript-Ausführungsmodell ist die Frage, ob Event-Handler aufgerufen werden können, bevor das Dokument vollständig geladen ist. Unsere bisherige Betrachtung des JavaScript-Ausführungsmodells hat festgestellt, dass alle Event-Handler immer ausgelöst werden, nachdem alle Skripten ausgeführt worden sind. Auch wenn das üblicherweise so passiert, wird das von keinem Standard verlangt. Wenn ein Dokument sehr lang ist oder über eine langsame Netzwerkverbindung geladen wird, kann der Browser das Dokument teilweise darstellen und dem Benutzer erlauben, die Interaktion mit ihm zu beginnen (und Event-Handler auszulösen), bevor alle Skripten und `onload`-Handler gelaufen sind. Wenn so ein Event-Handler eine Funktion aufruft, die noch nicht definiert ist, schlägt er fehl. (Das ist ein Argument dafür, alle Funktionen in Skripten im `<head>` eines Dokuments zu definieren.) Und wenn so ein Event-Handler versucht, einen Teil des Dokuments zu manipulieren, der noch nicht geparst wurde, schlägt es fehl. In der Praxis tritt dieses Szenario nicht häufig auf, und es ist in der Regel nicht die zusätzliche Programmierarbeit wert, sich aggresiv dagegen zu schützen.

* * *

[50] Einige Browser wie Firefox schützen vor Denial-of-Service-Angriffen und versehentlichen Endlosschleifen und benachrichtigen den Benutzer, wenn ein Skript oder ein Event-Handler zu lange braucht. Das gibt dem Benutzer eine Möglichkeit, ein außer Kontrolle geratenes Skript abzubrechen.

# 13.6 Clientseitige Kompatibilität

Der Webbrowser ist eine universelle Plattform zum Hosting von Anwendungen, und JavaScript ist die Sprache, in der diese Anwendungen entwickelt werden. Glücklicherweise ist die Sprache JavaScript standardisiert und wird gut unterstützt: Alle modernen Webbrowser unterstützen ECMAScript v3. Für die Plattform selbst kann nicht das Gleiche gesagt werden. Natürlich zeigen alle Webbrowser HTML an. Aber sie unterscheiden sich in ihrer Unterstützung von anderen Standards wie CSS (Cascading Style Sheets) und DOM. Und obwohl alle modernen Browser einen standardkonformen JavaScript-Interpreter enthalten, unterscheiden sie sich in Bezug auf die APIs, die sie clientseitigem JavaScript-Code verfügbar machen.

Kompatibilitätsfragen sind einfach ein unangenehmer Lebensumstand für clientseitige JavaScript-Programmierer. Der JavaScript-Code, den Sie schreiben und verteilen, kann in den unterschiedlichsten Versionen der unterschiedlichsten Browser auf den unterschiedlichsten Betriebssystemen ausgeführt werden. Denken Sie nur an die Permutationen beliebter Betriebssysteme und Browser: Internet Explorer auf Windows und Mac OS;[51] Firefox auf Windows, Mac OS und Linux; Safari auf Mac OS; Opera auf Windows, Mac OS und Linux. Wenn Sie die aktuelle Version jedes Browsers und zusätzlich jeweils zwei vorangehende Versionen unterstützen möchten, müssen Sie diese neun Browser/OS-Paare mit drei multiplizieren und erhalten eine Gesamtzahl von 27 Browser/Version/OS-Kombinationen. Es gibt nur eine Möglichkeit, vollkommen sicherzustellen, dass Ihre Webanwendung auf allen 27 Kombinationen läuft: sie auf jeder zu testen. Das ist eine entmutigende Aufgabe, und in der Praxis werden diese Tests oft von den Benutzern ausgeführt, nachdem die Anwendung verteilt wurde

Bevor Sie die Testphase der Anwendungsentwicklung erreichen, müssen Sie den Code schreiben. Wenn man in JavaScript programmiert, ist die Kenntnis der Inkompatibilitäten unter Browsern für die Entwicklung von kompatiblem Code entscheidend. Unglücklicherweise wäre es eine enorme Aufgabe, eine abschließende Liste aller bekannten Hersteller-, Versions- und Plattform-Inkompatibilitäten zu erstellen. Sie geht über den Horizont und die Absicht dieses Buchs hinaus, und meines Wissens nach wurde noch nie eine umfassende clientseitige JavaScript-Test-Suite entwickelt. Sie können Browser-Kompatibilitätsinformationen online finden. Hier sind zwei Seiten, die mir geholfen haben:

<http://www.quirksmode.org/dom/>

Das ist die Website des freiberuflichen Webentwicklers Peter-Paul Koch. Seine DOM-Kompatibilitätstabellen zeigen die Kompatibilität der verschiedenen Browser mit dem W3C DOM.

<http://webdevout.net/browser_support.php>

Diese Site von David Hammond ähnelt quirksmode.org, aber ihre Kompatibilitätstabellen sind umfassender und waren (als dies geschrieben wurde) etwas aktueller. Neben der DOM-Kompatibilität beurteilt die Site außerdem die Konformität mit den HTML-, CSS- und ECMAScript-Standards.

Natürlich ist es nur der erste Schritt, sich bewusst zu sein, dass Inkompabilitäten existieren. Die folgenden Unterabschnitte zeigen Techniken, die Sie einsetzen können, um die Inkompatibilitäten zu umgehen, auf die Sie stoßen.

## 13.6.1 Die Geschichte der Inkompatibilität

Bei der clientseitigen JavaScript-Programmierung ging es immer darum, mit Inkompatibilitäten klarzukommen. Eine Kenntnis der Geschichte liefert einige nützliche Hintergründe. Die Anfangstage der Webprogrammierung wurden durch die »Browser-Kriege« zwischen Netscape und Microsoft gekennzeichnet. Das war ein intensiver Entwicklungsschub – häufig in inkompatible Richtungen – für die Browserumgebung und die clientseitigen JavaScript-APIs. Zu diesem Zeitpunkt waren die Inkompatibilitätsprobleme am schlimmsten. Einige Websites gaben einfach auf und sagten ihren Besuchern, welcher Browser erforderlich war, um auf die Site zuzugreifen.

Die Browser-Kriege endeten damit, dass Microsoft den Markt dominierte und Webstandards wie DOM und CSS begannen, sich durchzusetzen. Es folgte eine Stabilitätsphase (oder Stagnationsphase), in der sich der Netscape langsam in den Firefox-Browser verwandelte und Microsoft nach und nach nur kleinere Verbesserungen an seinem Browser vornahm. Die Standard-Unterstützung beider Browser war gut oder zumindest gut genug, um kompatible Webanwendungen zu schreiben.

Aktuell scheinen wir uns am Anfang eines neuen Schwungs von Browser-Innovationen zu befinden. Beispielsweise unterstützen jetzt alle Browser geskriptete HTTP-Anfragen (HTTP-Requests), die einer der Eckpunkte der neuen Ajax-Webanwendungsarchitektur sind (siehe Kapitel 20). Microsoft arbeitet am Internet Explorer 7, der eine Vielzahl lange bestehender Sicherheits- und CSS-Kompatibilitätsprobleme angehen wird. IE 7 bringt viele für den Benutzer sichtbare Änderungen mit sich, wird Webentwicklern anscheinend aber keine neuen Möglichkeiten eröffnen. Aber andere Browser bieten neue Möglichkeiten. Beispielsweise unterstützen Safari und Firefox ein `<canvas>`-Element für geskriptete clientseitige Grafiken (siehe Kapitel 22). Ein Konsortium von Browserherstellern (unter auffälliger Abwesenheit von Microsoft), das als WHATWG (whatwg.org) bekannt ist, arbeitet an einer Standardisierung des `<canvas>`-Tags und vieler weiterer Erweiterungen für HTML und DOM.

## 13.6.2 Ein Wort zu »modernen Browsern«

Clientseitiges JavaScript ist ein bewegliches Ziel, insbesondere wenn wir tatsächlich in eine Periode schneller Entwicklungen eintreten sollten. Aus diesem Grund schrecke ich in diesem Buch davor zurück, einschränkende Bemerkungen zu bestimmten Versionen bestimmter Browser zu machen. Bei allen derartigen Aussagen ist es wahrscheinlich, dass sie längst veraltet sind, bevor ich eine neue Version dieses Buchs schreibe. Ein gedrucktes Buch kann einfach nicht so oft aktualisiert werden, wie es erforderlich ist, um ein hilfreicher Führer für die Kompatibilitätsprobleme zu sein, die die aktuellen Browserbestand betreffen.

Sie werden deswegen bemerken, dass ich meine Aussagen mit absichtlich vagen Formulierungen wie »alle modernen Browser« (oder manchmal »alle modernen Browser außer dem IE«) schütze. Als dies geschrieben wurde, schloss die lose Gruppe der »modernen Browser« Firefox 1.0, Firefox 1.5, Firefox 2.0, IE 5.5, IE 6.0, IE 7, Safari 2.0, Opera 8 und Opera 8.5 ein. Es gibt keine Garantie, dass jede Aussage, die dieses Buch zu »modernen Browsern« macht, für jeden dieser Browser gilt. Sie ermöglicht aber, dass Sie wissen, welche Browser die aktuelle Technologie darstellten, als dieses Buch geschrieben wurde.

## 13.6.3 Feature Testing

Feature Testing (das manchmal als _Funktionalitätsprüfung_ bezeichnet wird) ist eine mächtige Technologie zur Bewältigung von Inkompatibilitäten. Wenn Sie ein Feature oder eine Funktionalität einsetzen möchten, das bzw. die eventuell nicht von allen Browsern unterstützt wird, sollten Sie in Ihre Skripten Code einschließen, der prüft, ob dieses Feature unterstützt wird. Wenn das gewünschte Feature auf der aktuellen Plattform nicht unterstützt wird, verwenden Sie es entweder auf dieser Plattform nicht oder bieten alternativen Code, der auf allen Plattformen funktioniert.

Feature Testing werden Sie in den folgenden Kapiteln immer wieder sehen. Beispielsweise gibt es in Kapitel 17 Code, der so aussieht:

    if (element.addEventListener) { // Auf diese W3C-Methode prüfen, bevor wir sie verwenden
      element.addEventListener("keydown", handler, false);
      element.addEventListener("keypress", handler, false);
    }
    else if (element.attachEvent) { // Auf diese IE-Methode prüfen, bevor wir sie verwenden
      element.attachEvent("onkeydown", handler);
      element.attachEvent("onkeypress", handler);
    }
    else { // Andernfalls auf eine allgemein unterstützte Technik ausweichen
      element.onkeydown = element.onkeypress = handler;
    }

Kapitel 20 beschreibt noch einen weiteren Ansatz für das Feature Testing: Versuchen Sie so lange Alternativen, bis Sie auf eine stoßen, die keine Exception auslöst! Und wenn Sie eine Alternative finden, die funktioniert, merken Sie sie sich für die spätere Verwendung. Hier ist ein Ausblick auf Code aus Beispiel 20-1 Utility"):

    // Das ist eine Liste der zu probierenden Funktionen zur XMLHttpRequest-Erstellung
    HTTP._factories = [
      function( ) { return new XMLHttpRequest( ); },
      function( ) { return new ActiveXObject("Msxml2.XMLHTTP"); },
      function( ) { return new ActiveXObject("Microsoft.XMLHTTP"); }
    ];

    // Wenn wir eine Factory finden, die funktioniert, speichern wir sie hier
    HTTP._factory = null;

    // Ein neues XMLHttpRequest-Objekt erzeugen und zurückliefern.
    //
    // Beim ersten Aufruf die Liste der Factory-Methoden probieren, bis wir eine finden,
    // die einen Nicht-null-Wert zurückliefert und keine Exception auslöst. Wenn wir
    // eine funktionierende Factory finden, merken wir sie uns für die spätere Verwendung.
    HTTP.newRequest = function( ) { /* Funktionsrumpf weggelassen */ }

Ein verbreitetes, aber veraltetes Beispiel für das Feature Testing, auf das Sie in existierendem Code noch stoßen können, wird verwendet, um zu ermitteln, welches DOM ein Browser unterstützt. Es taucht oft in DHTML-Code auf und sieht normalerweise ungefähr so aus:

    if (document.getElementById) { // Wenn die W3C DOM API unterstützt wird,
      // führen wir unser DHTML mit der W3C DOM API aus
    }
    else if (document.all) {    // Wenn die IE 4 API unterstützt wird,
      // führen wir unser DHTML mit der IE 4 API aus
    }
    else if (document.layers) {   // Wenn die Netscape 4 API unterstützt wird,
      // führen wir den DHTML-Effekt (so gut wie möglich) mit der Netscape 4 API aus
    }
    else {             // Andernfalls wird DHTML nicht unterstützt,
      // und wir bieten eine statische Alternative zu DHTML
    }

Code wie dieser ist veraltet, weil fast alle heute verbreiteten Browser das W3C DOM und seine `document.getElementById( )`-Funktion unterstützen.

Das Wichtige an dieser Feature Testing-Technik ist, dass sie zu Code führt, der nicht an eine bestimmte Liste von Browserherstellern und Browser-Versionsnummern gebunden ist. Sie funktioniert mit der Gruppe der heute existierenden Browser und sollte auch mit zukünftigen Browsern weiterhin funktionieren, egal welche Feature-Sammlungen sie unterstützen. Beachten Sie allerdings, dass sie erfordert, dass Browserhersteller eine Eigenschaft oder eine Methode erst dann definieren, wenn die Eigenschaft oder Methode vollständig funktioniert. Würde Microsoft eine `addEventHandler( )`-Methode definieren, die die W3C-Spezifikation nur teilweise implementiert, würde das eine Menge Code zerstören, der Feature Testing einsetzt, bevor er `addEventHandler( )` aufruft.

Die in diesem Beispiel gezeigte Eigenschaft `document.all` verdient eine besondere Erwähnung. Das Array `document.all[]` wurde von Microsoft im IE 4 eingeführt. Es ermöglichte es JavaScript-Code, auf alle Elemente eines Dokuments zu verweisen, und hat eine neue Ära der clientseitigen Programmierung angestoßen. Es wurde nie standardisiert und durch `document.getElementById( )` überholt. Es wird in bestehendem Code immer noch verwendet und wurde (fälschlicherweise) oft eingesetzt, um zu ermitteln, ob ein Skript im IE läuft. Dazu wurde Code wie dieser verwendet:

    if (document.all) {
      // Das Skript läuft im IE
    }
    else {
      // Das Skript läuft in irgendeinem anderen Browser
    }

Weil es immer noch eine Menge bestehenden Codes gibt, der `document.all` verwendet, wurde in den Firefox-Browser eine Unterstützung eingebaut, damit Firefox mit Seiten arbeiten kann, die zuvor IE-abhängig waren. Weil das Vorhandensein der Eigenschaft `all` oft für die Browserermittlung eingesetzt wird, behauptet Firefox, dass er die Eigenschaft nicht unterstützt. Obwohl Firefox `document.all` unterstützt, verhält sich die `if`-Anweisung im nachfolgenden Skript, als würde die Eigenschaft `all` nicht existieren. Das Skript zeigt also einen Dialog an, der den Text »Firefox« enthält:

    if (document.all) alert("IE"); else alert("Firefox");

Dieses Beispiel illustriert, dass das Feature Testing-Verfahren nicht funktioniert, wenn der Browser Sie aktiv belügt! Es zeigt auch, dass Webentwickler nicht die einzigen sind, die von Inkompatibilitäten geplagt werden. Auch Browserhersteller müssen sich im Dienst der Kompatibilität verbiegen.

## 13.6.4 Browserermittlung

Feature Testing ist gut geeignet, um die Unterstützung für große Funktionalitätsbereiche zu prüfen. Sie können es beispielsweise einsetzen, um zu ermitteln, ob ein Browser das W3C-Event-Handling-Modell oder das IE-Event-Handling-Modell unterstützt. Andererseits müssen Sie gelegentlich einzelne Fehler oder Eigenarten eines bestimmten Browsers umschiffen. Und es kann sein, dass es keinen einfachen Weg gibt, auf die Existenz dieses Fehlers zu prüfen. In diesem Fall müssen Sie einen plattformspezifischen Workaround erstellen, der an einen bestimmten Browserhersteller, eine bestimmte Browserversion oder ein bestimmtes Betriebssystem (oder eine Kombination der drei) gebunden ist.

In clientseitigem JavaScript macht man das über das Navigator-Objekt, das Sie in Kapitel 14 kennenlernen werden. Code, der den Hersteller und die Version des aktuellen Browsers ermittelt, wird häufig _Browser Sniffer_ oder _Client Sniffer_ genannt. Ein einfaches Beispiel wird in Beispiel 14-3 gezeigt. In den Anfangstagen des Webs, als die Netscape- und IE-Plattformen inkompatibel und voneinander abweichend waren, war das Client Sniffing eine verbreitete clientseitige Programmiertechnik. Jetzt, wo sich die Kompatibilitätssituation stabilisiert hat, ist das Client Sniffing in Ungnade gefallen und sollte nur verwendet werden, wenn es unbedingt erforderlich ist.

Beachten Sie, dass Client Sniffing auch auf der Serverseite durchgeführt werden kann, indem der Webserver auf Basis dessen, wie sich der Browser in seinem `User-Agent`-Header identifiziert, entscheidet, welchen JavaScript-Code er zurücksendet.

## 13.6.5 Bedingte Kommentare im Internet Explorer

In der Praxis werden Sie feststellen, dass viele Inkompatibilitäten in der clientseitigen JavaScript-Programmierung IE-spezifisch sind. Das heißt, dass Sie den Code auf eine Weise für den IE und auf eine andere Weise für alle anderen Browser schreiben müssen. Obwohl Sie browserspezifische Erweiterungen, deren Standardisierung unwahrscheinlich ist, normalerweise vermeiden sollten, unterstützt der IE in HTML und JavaScript bedingte Kommentare, die nützlich sein können.

So könnten bedingte Kommentare in HTML aussehen. Achten Sie auf die Tricks, die für den schließenden Begrenzer des HTML-Kommentars angewendet werden:

    <!--[if IE]>
    Dieser Inhalt steht eigentlich in einem HTML-Kommentar.
    Er wird nur im IE angezeigt.
    <![endif]-->

    <!--[if gte IE 6]>
    Dieser Code wird nur in IE 6 und höher angezeigt.
    <![endif]-->

    <!--[if !IE]> <-->
    Das ist der normale HTML-Inhalt. Aber der IE zeigt ihn wegen
    der Kommentare darüber und darunter nicht an.
    <!--> <![endif]-->

    Das ist normaler Inhalt, der von allen Browsern angezeigt wird.

Bedingte Kommentare werden auch im JavaScript-Interpreter des IE unterstützt, und für C- und C++-Programmierer werden sie Ähnlichkeit mit der `#ifdef/#endif`-Funktionalität des C-Präprozessors haben. Im IE beginnt ein bedingter JavaScript-Kommentar mit dem Text `/*@cc_on` und endet mit dem Text `@*/`. (Das `cc` in `cc_on` steht für bedingte Kompilierung, engl. _conditional compilation_.) Der folgende bedingte Kommentar schließt Code ein, der nur im IE ausgeführt werden soll:

    /*@cc_on
     @if (@_jscript)
       // Dieser Code befindet sich in einem JS-Kommentar, wird vom IE aber ausgeführt.
      alert("In IE");

     @end
     @*/

Innerhalb eines bedingten Kommentars begrenzen die Schlüsselwörter `@if`, `@else` und `@end` den Code, der vom JavaScript-Interpreter des IE bedingt ausgeführt werden soll. Meist brauchen Sie nur die oben gezeigte einfache Bedingung: `@if (@_jscript)`. JScript ist Microsofts Name für den eigenen JavaScript-Interpreter, und die Variable `@_jscript` ist im IE immer `true`.

Durch eine geschickte Vermischung bedingter Kommentare und gewöhnlicher JavaScript-Kommentare können Sie einen Codeblock so einrichten, dass er im IE läuft, und einen anderen so, dass er in allen anderen Browsern läuft:

    /*@cc_on
     @if (@_jscript)
      // Code in einem bedingten Kommentar, der auch ein gewöhnlicher
      // JavaScript-Kommentar ist. Der IE führt ihn aus, andere Browser ignorieren ihn.
      alert('Sie nutzen den Internet Explorer);
     @else*/
      // Dieser Code befindet sich nicht mehr in einem JavaScript-Kommentar,
      // aber immer noch im bedingten IE-Kommentar. Das heißt, dass alle Browser außer
      // dem IE diesen Code ausführen.
      alert('Sie nutzen nicht den Internet Explorer');
    /*@end
    @*/

Bedingte Kommentare sind sowohl in ihrer HTML- als auch ihrer JavaScript-Form vollkommen standardfremd. Aber gelegentlich können sie nützliche Mittel sein, um Kompatibilität mit dem IE zu erzielen.

* * *

[51] Der IE für den Mac läuft aus, und das ist ein Segen, weil er sich grundlegend vom IE für Windows unterscheidet.

# 13.7 Zugänglichkeit

Das Web ist ein wunderbares Werkzeug für die Verbreitung von Informationen, und JavaScript-Programme können den Zugriff auf diese Informationen verbessern. JavaScript-Programmierer müssen allerdings aufpassen: Es passiert schnell, dass man JavaScript-Code schreibt, der Benutzern mit visuellen oder körperlichen Behinderungen unbeabsichtigterweise Informationen verweigert.

Blinde Benutzer können eine Form von »Hilfstechnologie« einsetzen, die als Screenreader (auch Bildschirmleseprogramm) bezeichnet wird, um geschriebene Wörter in gesprochene Wörter umzuwandeln. Einige Screenreader berücksichtigen JavaScript, andere funktionieren am besten, wenn JavaScript abgeschaltet ist. Wenn Sie eine Website entwerfen, die zur Anzeige ihrer Informationen JavaScript benötigt, schließen Sie die Nutzer dieser Screenreader aus. (Und Sie haben auch jeden ausgeschlossen, der mit einem Mobilgerät wie einem Handy im Web ist, das keine JavaScript-Unterstützung bietet, und auch alle, die JavaScript in ihren Browsern absichtlich deaktivieren.) Die eigentliche Rolle von JavaScript ist es, die Darstellung der Informationen zu erweitern, und nicht, die Darstellung der Informationen zu übernehmen. Eine Grundregel der JavaScript-Zugänglichkeit ist es, Ihren Code so zu entwerfen, dass die Webseite, auf der er verwendet wird, immer noch (zumindest in gewisser Weise) funktioniert, wenn der JavaScript-Interpreter abgeschaltet ist.

Weitere wichtige Zugänglichkeitsbedenken betreffen Benutzer, die die Tastatur, aber kein Zeigegerät wie eine Maus verwenden können (oder wollen). Wenn Sie JavaScript-Code schreiben, der von mausspezifischen Events abhängig ist, schließen Sie Benutzer aus, die die Maus nicht verwenden. Webbrowser ermöglichen eine Durchquerung und Aktivierung von Webseiten mit der Tastatur, und Ihr JavaScript-Code sollte das auch tun. Aber gleichermaßen sollten Sie auch keinen Code schreiben, der eine Tastatureingabe verlangt, oder Sie schließen Benutzer aus, die keine Tastatur verwenden können, sowie Benutzer vieler Tablet-PC- und Handy-Browser. Wie in Kapitel 17 gezeigt wird, unterstützt JavaScript geräteunabhängige Events wie `onfocus` und `onchange` neben geräteabhängigen Events wie `onmouseover` und `onmousedown`. Der Zugänglichkeit halber sollten Sie, wenn das möglich ist, die geräteunabhängigen Events vorziehen.

Zugängliche Webseiten zu erstellen ist kein triviales Problem, für das es eindeutige Lösungen gibt. Aktuell dauern die Diskussionen an, wie man JavaScript am besten einsetzt, um die Zugänglichkeit zu befördern, anstatt sie zu beeinträchtigen. Eine vollständige Diskussion von JavaScript und der Zugänglichkeit von Webseiten geht über den Horizont dieses Buchs hinaus. Eine Internetsuche liefert Ihnen viele Informationen zu diesem Thema, von denen viele als Empfehlungen autoritativer Quellen verfasst sind. Denken Sie aber daran, dass sich die Praktiken der clientseitigen JavaScript-Programmierung und Hilfstechnologien in der Entwicklung befinden und Zugänglichkeitsrichtlinien damit nicht immer mithalten.

# 13.8 JavaScript-Sicherheit

Die Internet-Sicherheit ist ein weites und komplexes Feld. Dieser Abschnitt konzentriert sich auf Fragen der clientseitigen JavaScript-Sicherheit.

## 13.8.1 Was JavaScript nicht machen kann

Die Einführung von JavaScript-Interpretern in Webbrowser bedeutet, dass das Laden einer Webseite bewirken kann, dass auf Ihrem Rechner beliebiger JavaScript-Code ausgeführt wird. Sichere Webbrowser – und verbreitet verwendete moderne Browser, die relativ sicher zu sein scheinen – schränken Skripten auf verschiedene Weise ein, um bösartigen Code davon abzuhalten, vertrauliche Daten zu lesen, Ihre Daten zu verändern oder Ihre Privatsphäre zu kompromittieren.

JavaScripts erste Verteidigungslinie gegen bösartigen Code ist, dass die Sprache bestimmte Funktionalitäten einfach nicht unterstützt. Beispielsweise bietet JavaScript keine Möglichkeit, Dateien oder Verzeichnisse auf dem Clientcomputer zu lesen, zu schreiben oder zu löschen. Ohne ein File-Objekt und ohne Funktionen für den Dateizugriff kann ein JavaScript-Programm die Daten eines Benutzers nicht löschen oder Viren in das System eines Benutzers einschleusen.

Die zweite Verteidigungslinie ist, dass JavaScript bestimmten Features, die es unterstützt, Einschränkungen auferlegt. Beispielsweise kann clientseitiges JavaScript das HTTP-Protokoll skripten, um Daten mit einem Webserver auszutauschen, und kann sogar Daten von FTP-Servern oder anderen Servern herunterladen. Aber JavaScript bietet keine allgemeinen Netzwerk-Grundfunktionen und kann kein Socket für einen anderen Host öffnen oder eine Verbindung von einem anderen Host annehmen.

Die folgende Liste enthält Features, die eingeschränkt werden können. Beachten Sie, dass diese Liste nicht vollständig ist. Unterschiedliche Browser haben unterschiedliche Einschränkungen, und viele von diesen Einschränkungen können vom Benutzer konfiguriert werden:

  * Ein JavaScript-Programm kann neue Browserfenster öffnen, aber um Pop-up-Missbrauch durch Werbung zu verhindern, schränken viele Browser dieses Feature so ein, dass das nur in Reaktion auf ein vom Benutzer initiiertes Event wie einen Maus-Klick passieren kann.

  * Ein JavaScript-Programm kann Browserfenster schließen, die es selbst geöffnet hat, aber es darf ohne Benutzerbestätigung keine anderen Fenster schließen. Das verhindert, dass bösartige Skripten `self.close( )` aufrufen, um das Browserfenster des Benutzers zu schließen und damit zu bewirken, dass das Programm beendet wird.

  * Ein JavaScript-Programm kann das Ziel eines Links nicht verschleiern, indem es den Statusleistentext verändert, wenn die Maus über dem Link schwebt. (In der Vergangenheit war es verbreitet, in der Statusleiste zusätzliche Informationen zu einem Link anzuzeigen. Missbrauch durch Phishing-Versuche hat viele Browserhersteller veranlasst, die Funktionalität zu deaktivieren.)

  * Ein Skript kann kein Fenster öffnen, das zu klein ist (üblicherweise weniger als 100 Pixel auf einer Seite), oder ein Fenster zu stark verkleinern. Gleichermaßen kann ein Skript kein Fenster aus dem Bildschirm schieben oder ein Fenster erzeugen, das größer als der Bildschirm ist. Das hält Skripten davon ab, Fenster zu öffnen, die der Benutzer nicht sehen oder leicht übersehen kann. Ein solches Fenster könnte Skripten enthalten, die weiterlaufen, nachdem der Benutzer denkt, dass sie beendet wurden. Außerdem kann ein Skript kein Browserfenster ohne Titelleiste oder Statusleiste erzeugen, weil ein solches Fenster ein Betriebssystemfenster vortäuschen und den Benutzer so austricksen könnte, dass er beispielsweise ein geheimes Passwort eingibt.

  * Die `value`-Eigenschaft von HTML-FileUpload-Elementen kann nicht gesetzt werden. Könnte diese Eigenschaft gesetzt werden, könnte ein Skript einen gewünschten Dateinamen einsetzen und das Formular veranlassen, den Inhalt einer beliebigen angegebenen Datei (wie einer Passwortdatei) auf den Server hochzuladen.

  * Ein Skript kann den Inhalt von Dokumenten nicht lesen, die von anderen Servern geladen wurden als das Dokument, das das Skript enthält. Gleichermaßen kann ein Skript keine Event-Listener auf Dokumenten von anderen Servern registrieren. Das verhindert, dass Skripten Benutzereingaben (wie die Tastendrücke, die ein Passwort bilden) für andere Seiten ausspioniert. Diese Einschränkung ist unter dem Namen _Same Origin Policy_ bekannt und wird im folgenden Abschnitt ausführlicher beschrieben.

## 13.8.2 Die Same Origin Policy

Die _Same Origin Policy_ (Gleiche-Herkunft-Richtlinie) ist eine durchschlagende Sicherheitsbeschränkung für die Inhalte, mit denen JavaScript-Code interagieren kann. Üblicherweise kommt sie zum Tragen, wenn eine Webseite mehrere Frames verwendet, `<iframe>`-Tags enthält oder andere Browserfenster öffnet. Dann steuert die Same Origin Policy die Interaktion von JavaScript-Code in einem Fenster oder Frame mit anderen Fenstern oder Frames. Sie bewirkt, dass ein Skript nur Eigenschaften von Fenstern und Dokumenten lesen kann, die die gleiche Herkunft wie das Dokument haben, das das Skript enthält (wie Sie JavaScript mit mehreren Fenstern oder Frames verwenden, erfahren Sie in "14.8 Mehrere Fenster und Frames").

Die Same Origin Policy spielt auch eine Rolle, wenn HTTP mit dem XMLHttpRequest-Objekt geskriptet wird. Dieses Objekt ermöglicht es clientseitigem JavaScript-Code, beliebige HTTP-Anfragen durchzuführen, allerdings nur an den Webserver, von dem das Dokument geladen wurde, das das Skript enthält (mehr Informationen zum XMLHttpRequest-Objekt finden Sie in Kapitel 20).

Die _Herkunft_ eines Dokuments wird über das Protokoll, den Host und den Port der URL definiert, von der das Dokument geladen wurde. Dokumente, die von unterschiedlichen Webservern geladen werden, sind unterschiedlicher Herkunft. Dokumente, die über unterschiedliche Ports auf dem gleichen Host geladen wurden, sind unterschiedlicher Herkunft. Und ein Dokument, das mit dem `http:`-Protokoll geladen wurde, hat eine andere Herkunft als ein Dokument, das mit dem `https:`-Protokoll geladen wurde, selbst wenn beide Dokumente vom gleichen Webserver geladen wurden.

Es ist wichtig, dass man versteht, dass die Herkunft des Skripts selbst für die Same Origin Policy irrelevant ist: Es geht nur um die Herkunft des Dokuments, in das das Skript eingebettet ist. Nehmen wir beispielsweise an, dass ein Skript von Domain A (über das `src`-Attribut des `<script>`-Tag) in eine Webseite von Domain B eingeschlossen ist. Das Skript hat vollständigen Zugriff auf den Inhalt des Dokuments, das es enthält. Wenn das Skript ein neues Fenster öffnet und ein zweites Dokument von Domain B lädt, hat das Skript auch vollständigen Zugriff auf den Inhalt des zweiten Dokuments. Aber wenn das Skript ein drittes Fenster öffnet und darin ein Dokument von Domain C (oder sogar von Domain A) lädt, tritt die Same Origin Policy auf den Plan und verhindert, dass das Skript auf dieses Dokument zugreift.

Die Same Origin Policy gilt eigentlich nicht für alle Eigenschaften aller Objekte in einem Fenster von einer anderen Domain. Aber sie gilt für viele von ihnen und insbesondere für fast alle Eigenschaften des Document-Objekts (siehe Kapitel 15). Außerdem implementieren die einzelnen Browserhersteller diese Sicherheitsrichtlinie etwas unterschiedlich. (Beispielsweise erlaubt Firefox 1.0 einem Skript auf Fenstern mit anderer Herkunft `history.back( )` aufzurufen, der IE 6 aber nicht.) Fenster, die Dokumente von einem anderen Server enthalten, sollten für Ihre Skripten also in jeder Hinsicht tabu sein. Wenn Ihr Skript ein Fenster geöffnet hat, kann Ihr Skript es wieder schließen, aber es kann auf keine Weise in es »hineinblicken«.

Die Same Origin Policy ist erforderlich, um zu verhindern, dass Skripten persönliche Informationen stehlen. Bestände diese Einschränkung nicht, könnte ein bösartiges Skript (das durch eine Firewall hindurch in einen Browser in einem sicheren Unternehmensnetzwerk geladen wird) in der Hoffnung ein leeres Fenster öffnen, dass der Benutzer dieses Fenster verwendet, um Dateien aus dem Unternehmensnetzwerk zu lesen. Das bösartige Skript würde dann den Inhalt dieses Fensters lesen und an seinen eigenen Server zurücksenden. Die Same Origin Policy verhindert derartiges Verhalten.

Unter bestimmten Umständen ist die Same Origin Policy zu streng. Insbesondere stellt sie größere Websites vor Probleme, die mehrere Server nutzen. Beispielsweise könnte ein Skript von home.example.com legitimerweise Eigenschaften eines von developer.example.com geladenen Dokuments lesen wollen oder könnten Skripten von orders.example.com Eigenschaften von einem Dokument aus catalog.example.com lesen müssen. Um große Websites dieser Art zu unterstützen, können Sie die `domain`-Eigenschaft des Document-Objekts einsetzen. Per Default enthält die Eigenschaft `domain` den Hostnamen des Servers, von dem das Dokument geladen wurde. Sie können diese Eigenschaft setzen, allerdings nur auf einen String, der selbst ein gültiges Domain-Suffix ist. Wenn `domain` ursprünglich der String »home.example.com« ist, können Sie es also auf den String »example.com« setzen, aber nicht auf »home.example« oder »ample.com«. Außerdem muss der `domain`-Wert mindestens einen Punkt enthalten. Sie können ihn nicht auf »com« oder eine andere Toplevel-Domain setzen.

Wenn zwei Fenster (oder Frames) Skripten enthalten, deren `domain` auf den gleichen Wert gesetzt ist, wird die Same Origin Policy für diese beiden Fenster gelockert: Die beiden Fenster können dann miteinander interagieren. Beispielsweise können kooperierende Skripten, die von orders.example.com und catalog.example.com geladen wurden, ihre `document.domain`-Eigenschaften auf »example.com« setzen und es so scheinen lassen, dass sie die gleiche Herkunft haben, und es damit den beiden Dokumenten ermöglichen, die Eigenschaften des jeweils anderen zu lesen.

## 13.8.3 Plug-ins und ActiveX-Steuerelemente skripten

Obwohl der JavaScript-Kernsprache und dem grundlegenden clientseitigen Objektsystem die Dateisystem- und Netzwerkfunktionalitäten fehlen, die bösartiger Code in der Regel benötigt, ist die Situation nicht so einfach, wie sie erscheint. In vielen Webbrowsern wird JavaScript als »Script-Engine« für andere Software-Komponenten wie die ActiveX-Steuerelemente des Internet Explorers und die Plug-ins anderer Browser eingesetzt. Das eröffnet clientseitigen Skripten wichtige und mächtige Features. In Kapitel 20 werden Sie Beispiele sehen, in denen ein ActiveX-Steuerelement verwendet wird, um HTTP zu skripten. Ähnliche Beispiele sehen Sie in den Kapitel 19 und Kapitel 22, in denen die Java- und Flash-Plug-ins verwendet werden, um Daten persistent zu machen und fortgeschrittene clientseitige Grafiken zu erstellen.

Die Möglichkeit, ActiveX-Steuerelemente und Plug-ins zu skripten, hat Auswirkungen auf die Sicherheit. Beispielsweise haben Java-Applets Zugriff auf Lowlevel-Netzwerkfunktionalitäten. Die Java-Sicherheits-»Sandbox« verhindert, dass Applets mit einem anderen Server als dem Server kommunizieren, von dem sie geladen wurden. Das öffnet also keine Sicherheitslücke. Es zeigt aber ein grundlegendes Problem: Wenn Plug-ins geskriptet werden können, müssen Sie nicht nur der Sicherheitsarchitektur des Webbrowsers, sondern auch der Sicherheitsarchitektur des Plug-ins vertrauen. In der Praxis scheint es, als hätten die Java- und Flash-Plug-ins eine robuste Sicherheit, und sie scheinen in clientseitiges JavaScript keine Sicherheitsprobleme einzuführen. ActiveX-Scripting hat allerdings eine kontroversere Vergangenheit. Der IE-Browser hat Zugriff auf eine Vielzahl skriptbarer ActiveX-Steuerelemente, die Teil des Windows-Betriebssystems sind, und in der Vergangenheit hatten einige dieser skriptbaren Steuerelemente ausnutzbare Sicherheitslücken. Aktuell scheint es jedoch so, als wären diese Probleme gelöst worden.

## 13.8.4 Cross Site Scripting

 _Cross Site Scripting_ oder XSS ist ein Begriff für eine Kategorie von Sicherheitsproblemen, bei denen ein Angreifer HTML-Tags oder Skripten in eine Ziel-Website einschleust. Der Schutz vor XSS-Angriffen ist normalerweise Aufgabe der serverseitigen Entwickler. Allerdings müssen sich clientseitige JavaScript-Programmierer des Problems Cross Site Scripting bewusst sein und ihren Code davor schützen.

Eine Webseite ist für Cross Site Scripting empfänglich, wenn sie dynamisch Dokumentinhalte erzeugt und diese Inhalte auf Daten basieren, die von Benutzern eingegeben wurden und verwendet werden, ohne dass sie zuvor »gesäubert« wurden, indem alle eventuell darin enthaltenen HTML-Tags entfernt werden. Betrachten Sie als Beispiel die folgende Webseite, die JavaScript einsetzt, um den Benutzer mit seinem Namen zu begrüßen:

    <script>
    var name = decodeURIComponent(window.location.search.substring(6)) || "";
    document.write("Hallo " + name);
    </script>

Dieses zweizeilige Skript verwendet `window.location.search`, um einen Teil der eigenen URL zu erhalten, der mit ? beginnt. Dann nutzt es `document.write( )`, um dem Dokument dynamisch generierten Inhalt hinzuzufügen. Die Seite soll mit einer URL wie dieser aufgerufen werden:

    http://www.example.com/greet.html?name=David

Wenn sie auf diese Weise verwendet wird, zeigt sie den Text »Hallo David« an. Aber überlegen Sie, wenn sie mit dieser URL aufgerufen wurde:

    http://www.example.com/greet.html?name=%3Cscript%3Ealert('David')%3C/script%3E

Mit dieser URL generiert das Skript ein weiteres Skript (`%3C` und `%3E` sind die Codes für die spitzen Klammern)! In diesem Fall zeigt das eingeschleuste Skript nur ein Dialogfenster an. Das ist relativ unkritisch. Aber sehen Sie sich folgenden Fall an:

    http://siteA/greet.html?name=%3Cscript src=siteB/evil.js%3E%3C/script%3E

Cross Site Scripting-Angriffe werden so genannt, weil mehrere Sites beteiligt sind. Die Site B (oder auch Site C) enthält einen besonders aufgebauten Link (wie den oben) auf Site A, der in Site A ein Skript von Site B einschleust. Das Skript _evil.js_ befindet sich auf der bösartigen Site B, ist jetzt aber in Site A eingebettet und kann mit dem Inhalt von Site A absolut alles machen, was es machen will. Es könnte die Seite verunstalten oder bewirken, dass sie nicht mehr funktioniert (indem beispielsweise einer der Denial-of-Service-Angriffe gestartet wird, die im nächsten Abschnitt beschrieben werden). Das wäre schlecht für die Kundenbeziehungen von Site A. Gefährlicher ist, dass ein bösartiges Skript Cookies lesen kann, die von Site A geschrieben wurden (die vielleicht Kontonummern und andere persönliche zuschreibbare Informationen enthalten), und diese Daten an Site B zurücksenden kann. Das eingeschleuste Skript kann sogar die Tastenbetätigungen des Benutzers aufzeichnen und diese Daten an Site B zurücksenden.

Das Mittel zur Verhinderung von XSS-Angriffen ist in der Regel die Entfernung von HTML-Tags aus nicht vertrauenswürdigen Daten, bevor diese verwendet werden, um dynamische Dokumentinhalte zu erzeugen. Die zuvor gezeigte Datei _greet.html_ können Sie reparieren, indem Sie die folgende Codezeile hinzufügen, um die spitzen Klammern zu entfernen, die das `<script>`-Tag umgeben.

    name = name.replace(/</g, "<").replace(/>/g, ">");

Cross Site Scripting ermöglicht eine schädliche Schwachstelle, deren Wurzeln tief in die Architektur des Webs reichen. Es lohnt sich, diese Schwachstelle gründlich zu verstehen. Eine weitergehende Erörterung würde über den Horizont dieses Buchs hinausgehen. Es gibt viele Online-Quellen, die Ihnen helfen, sich vor Cross Site Scripting zu schützen. Eine wichtige Primärquelle ist das CERT Advisory zu diesem Problem: <http://www.cert.org/advisories/CA-2000-02.html>.

## 13.8.5 Denial-of-Service-Angriffe

Die Same Origin Policy und andere hier beschriebene Sicherheitsbeschränkungen leisten gute Arbeit, um zu verhindern, dass bösartiger Code versucht, Ihre Daten zu beschädigen oder Ihre Privatssphäre zu verletzen. Allerdings schützen Sie sie nicht vor Denial-of-Service-Angriffen mit brutaler Gewalt. Wenn Sie bei aktiviertem JavaScript eine bösartige Webseite besuchen, kann diese Website Ihren Browser mit einer Endlosschleife mit `alert( )`-Dialogfenstern lahmlegen und Sie beispielsweise zwingen, den Unix-Befehl `kill` oder den Windows Task Manager einzusetzen, um Ihren Browser zu schließen.

Eine bösartige Website kann auch versuchen, Ihre CPU mit einer Endlosschleife oder sinnlosen Berechnungen lahmzulegen. Einige Browser (wie Firefox) entdecken lange laufende Skripten und geben Benutzern eine Möglichkeit, diese abzubrechen. Das verhindert versehentliche Endlosschleifen, aber bösartiger Code kann Techniken wie den Befehl `window.setInterval( )` einsetzen, um zu verhindern, dass er heruntergefahren wird. Ein ähnlicher Angriff legt Ihr System lahm, indem er Massen an Speicherplatz reserviert.

Es gibt kein Patentrezept dafür, wie Webbrowser derartige ungeschickte Angriffe verhindern können. In der Praxis ist das im Web aber kein verbreitetes Problem, weil niemand zu einer Site zurückkehrt, die derartigen Skriptmissbrauch treibt

# 13.9 Andere webbezogene JavaScript-Einbettungen

Neben clientseitigem JavaScript besitzt die Sprache JavaScript weitere webbezogene Einbettungen. Diese weiteren Einbettungen werden in diesem Buch nicht behandelt. Aber Sie sollten sie kennen, damit Sie sie nicht mit clientseitigem JavaScript verwechseln:

 _Benutzer-Scripting_

Benutzer-Scripting ist eine Neuerung, bei der benutzerdefinierte Skripten auf HTML-Dokumente angewendet werden, bevor sie im Webbrowser dargestellt werden. Anstatt allein vom Seitenautor kontrolliert zu werden, können Webseiten jetzt auch vom Besucher der Seite gesteuert werden. Das bekannteste Beispiel für Benutzer-Scripting wird durch die Greasemonkey-Erweiterung für den Firefox-Webbrowser (<http://greasemonkey.mozdev.org>) ermöglicht. Die Benutzer-Skripten zur Verfügung gestellte Programmierumgebung ähnelt der Programmierumgebung von clientseitigem JavaScript, ist aber nicht mit ihr identisch. Dieses Buch wird Ihnen nicht zeigen, wie Sie Greasemonkey-Benutzer-Skripten schreiben, aber das Lernen von clientseitiger JavaScript-Programmierung kann als Voraussetzung für das Lernen von Benutzer-Scripting betrachtet werden.

 _SVG_

SVG (Scalable Vector Graphics) ist ein XML-basiertes Grafikformat, das die Einbettung von JavaScript-Skripten ermöglicht. Clientseitiges JavaScript kann das HTML-Dokument skripten, in das es eingebettet ist, und der JavaScript-Code, der in eine SVG-Datei eingebettet ist, kann die XML-Elemente dieses Dokuments skripten. Das Material der Kapitel 15 und 17 ist auf SVG-Scripting übertragbar, reicht aber nicht aus: Das DOM für SVG unterscheidet sich grundlegend vom HTML DOM. Die SVG-Spezifikation finden Sie unter <http://www.w3.org/TR/SVG>. Appendix B dieser Spezifikation definiert das SVG DOM. Kapitel 22 nutzt clientseitiges JavaScript, das in ein HTML-Dokument eingebettet ist, um ein SVG-Dokument zu erzeugen, das in ein HTML-Dokument eingebettet ist. Da sich der JavaScript-Code nicht im SVG-Dokument befindet, ist das eher ein Beispiel für gewöhnliches clientseitiges JavaScript statt ein Beispiel für die SVG-Einbettung von JavaScript.

 _XUL_

XUL ist eine XML-basierte Grammatik für die Beschreibung von Benutzerschnittstellen. Das GUI des Firefox-Webbrowsers wird über XUL-Dokumente definiert. Wie SVG gestattet die XUL-Grammatik die Verwendung von JavaScript. Wie bei SVG ist das Material in den Kapitel 15 und Kapitel 17 für die XUL-Programmierung relevant. Allerdings hat der JavaScript-Code in einem XUL-Dokument Zugriff auf andere Objekte und APIs und ist außerdem einem anderen Sicherheitsmodell unterworfen als clientseitiger JavaScript-Code. Mehr über XUL erfahren Sie unter <http://www.mozilla.org/projects/xul> und <http://www.xulplanet.com>.

 _ActionScript_

ActionScript ist eine JavaScript-artige Sprache (die von der gleichen ECMAScript-Spezifikation abgeleitet ist, aber in eine objektorientierte Richtung entwickelt wurde), die in Flash-Filmen verwendet wird. Der größte Teil des JavaScript-Materials in Teil I dieses Buchs ist für die ActionScript-Programmierung relevant. Flash ist nicht XML- oder HTML-basiert, und die von Flash veröffentlichten APIs sind nicht mit denen verwandt, die in diesem Buch behandelt werden. Dieses Buch enthält in den Kapitel 19, Kapitel 22 und Kapitel 23 Beispiele dazu, wie clientseitiges JavaScript Flash-Movies skripten kann. Diese Skripten werden notwendigerweise einige Fragmente von ActionScript-Code enthalten, aber der eigentliche Fokus liegt darauf, wie man gewöhnliches clientseitiges JavaScript verwendet, um mit diesem Code zu interagieren.

# Kapitel 14. Browserfenster skripten

In Kapitel 13 habe ich das Window-Objekt und die zentrale Rolle beschrieben, die es in clientseitigem JavaScript spielt: Es ist das globale Objekt für die clientseitige JavaScript-Programmierung. In diesem Kapitel untersuchen wir die Eigenschaften und Methoden des Window-Objekts, die es Ihnen ermöglichen, den Browser und seine Fenster und Frames zu steuern.

Sie werden hier lernen, wie Sie Folgendes tun:

  * JavaScript-Code registrieren, damit er später entweder einmal oder öfter ausgeführt wird

  * Die URL des Dokuments abrufen, das in einem Fenster angezeigt wird, und die Abfrageargumente aus dieser URL herausziehen

  * Den Browser dazu bringen, ein neues Dokument zu laden und anzuzeigen

  * Dem Browser sagen, dass er im Verlauf vor- oder zurückgehen und andere Browserfunktionen wie das Drucken aufrufen soll

  * Neue Browserfenster öffnen, manipulieren und schließen

  * Einfache Dialogfenster anzeigen

  * Ermitteln, in welchem Browser Ihr JavaScript-Code ausgeführt wird, und andere Informationen zur clientseitigen Umgebung erhalten

  * In der Statusleiste eines Browserfensters beliebigen Text anzeigen

  * Unbehandelte JavaScript-Fehler behandeln, die in einem Fenster auftreten

  * JavaScript-Code schreiben, der mit mehreren Fenstern oder Frames interagiert

Ihnen wird aufgefallen sein, dass es in diesem Kapitel nur um die Manipulation von Browserfenstern geht und nichts zum _Inhalt_ gesagt wird, der in diesen Fenstern angezeigt wird. Als JavaScript neu war, konnte der Dokumentinhalt nur auf sehr eingeschränkte Weise geskriptet werden, während die in diesem Kapitel beschriebenen Scripting-Techniken für Fenster aufregend und frisch waren. Heutzutage, mit vollständig skriptbaren Dokumenten (siehe Kapitel 15), ist das Scripting des Browsers nicht mehr so topaktuell. Außerdem werden einige der in diesem Kapitel gezeigten Techniken durch Sicherheitsbeschränkungen behindert und funktionieren nicht mehr so gut, wie sie einmal funktioniert haben. Andere Techniken funktionieren noch, sind bei Webdesignern aber nicht mehr so beliebt und werden nicht mehr häufig verwendet.

Obwohl dieses Kapitel heute weniger relevant ist, ist es nicht vollkommen irrelevant. Deswegen rate ich Ihnen nicht, es zu überspringen. Das Kapitel ist so aufgebaut, dass (der größte Teil) des wichtigsten Materials am Anfang kommt. Darauf folgen weniger wichtige und weniger häufig verwendete Techniken. Einen wichtigeren, aber komplizierteren Abschnitt dazu, wie man JavaScript verwendet, um mit mehreren Fenstern und Frames zu interagieren, spare ich mir bis zum Ende des Kapitels auf, und das Kapitel schließt mit einem nützlichen Beispiel.

# 14.1 Timer

Ein wichtiges Feature jeder Programmierumgebung ist die Fähigkeit, Code zu terminieren, dass er zu einem späteren Zeitpunkt ausgeführt wird. Die JavaScript-Kernsprache bietet eine solche Funktionalität nicht, aber clientseitiges JavaScript bietet es in der Form der globalen Funktionen `setTimeout( )`, `clearTimeout( )`, `setInterval( )` und `clearInterval( )`. Eigentlich haben diese Funktionen nichts mit dem Window-Objekt zu tun, aber sie werden in diesem Kapitel dokumentiert, weil das Window-Objekt das globale Objekt von clientseitigem JavaScript ist und diese globalen Methoden deswegen Methoden dieses Objekts sind.

Die `setTimeout( )`-Methode des Window-Objekts terminiert eine Funktion so, dass sie ausgeführt wird, nachdem eine bestimmte Anzahl von Sekunden verstrichen ist. `setTimeout( )` liefert einen opaken Wert zurück, der an die Funktion `clearTimeout( )` übergeben werden kann, um die Ausführung der terminierten Funktion abzubrechen.

`setInterval( )` ist wie `setTimeout( )`, bewirkt aber, dass die angegebene Funktion mehrfach im Abstand der angegebenen Anzahl von Millisekunden aufgerufen wird. Wie `setTimeout( )` liefert `setInterval( )` einen opaken Wert zurück, der an `clearInterval( )` übergeben werden kann, um weitere Aufrufe der terminierten Funktion abzubrechen.

Obwohl empfohlen wird, `setTimeout( )` und `setInterval( )` so aufzurufen, dass eine Funktion als erstes Argument übergeben wird, ist es auch erlaubt, stattdessen einen String mit JavaScript-Code zu übergeben. Wenn Sie das tun, wird der String (einmal oder mehrfach) aufgerufen, nachdem die angegebene Zeit verstrichen ist. In älteren Browsern wie dem IE 4 werden keine Funktionen unterstützt. In ihnen müssen Sie diese Methode mit einem String als erstem Argument aufrufen.

`setTimeout( )` und `setInterval( )` sind in einer Reihe von Situationen nützlich. Wenn Sie einen Tooltip anzeigen wollen, wenn der Benutzer die Maus länger als eine halbe Sekunde über einem Dokumentelement schweben lässt, können Sie `setTimeout( )` verwenden, um den Code zur Anzeige des Tooltips zu terminieren. Wenn sich die Maus wegbewegt, bevor der Code ausgelöst wird, können Sie `clearTimeout( )` verwenden, um den terminierten Code abzubrechen. `setTimeout( )` wird später in Beispiel 14-7 vorgeführt. Wenn Sie irgendwelche Animationen durchführen, verwenden Sie üblicherweise `setInterval( )`, um den Code zu terminieren, der die Animation durchführt. Anwendungen davon werden Sie in den Beispiel 14-4 und Beispiel 14-6 sehen.

Ein nützlicher Trick mit `setTimeout( )` ist die Registrierung einer Funktion, die nach einer Verzögerung von 0 Millisekunden aufgerufen wird. Der Code wird nicht sofort aufgerufen, sondern »so bald wie möglich«. In der Praxis heißt das, dass `setTimeout( )` dem Browser sagt, dass er die Funktion aufrufen soll, wenn die Ausführung der Event-Handler für die aktuell ausstehenden Events und die Aktualisierung des aktuellen Zustands des Dokuments abgeschlossen ist. Event-Handler (siehe Kapitel 17), die Dokumentinhalt abfragen oder verändern (siehe Kapitel 15), müssen gelegentlich zu diesem Trick greifen, um die Ausführung Ihres Codes hinauszuzögern, bis sich das Dokument wieder in einem stabilen Zustand befindet.

Referenzinformationen zu diesen Timer-Funktionen des Window-Objekts finden Sie in Teil IV.

# 14.2 Die Location- und History-Objekte

Dieser Abschnitt betrachtet die Location- und History-Objekte eines Fensters. Diese Objekte bieten Zugriff auf die URL des aktuell angezeigten Objekts und ermöglichen es Ihnen, neue Dokumente zu laden oder den Browser dazu zu bringen, zurück (oder vor) zu einem zuvor betrachteten Dokument zu gehen.

## 14.2.1 URLs parsen

Die `location`-Eigenschaft eines Fensters (oder eines Frames) ist eine Referenz auf ein Location-Objekt. Es repräsentiert die URL des Dokuments, das aktuell in diesem Fenster angezeigt wird. Die `href`-Eigenschaft des Location-Objekts ist ein String, der den vollständigen Text der URL enthält. Die `toString( )`-Methode des Location-Objekts liefert den Wert der `href`-Eigenschaft zurück. Statt `location.href` können Sie also auch `location` verwenden.

Andere Eigenschaften dieses Objekts – wie `protocol`, `host`, `pathname` und `search` – geben die verschiedenen einzelnen Bestandteile der URL an (vollständige Informationen finden Sie beim Location-Objekt in Teil IV).

Die `search`-Eigenschaft des Location-Objekts ist interessant. Sie enthält einen eventuellen Teil einer URL, der auf ein Fragezeichen folgt (und dieses einschließt). Häufig ist das eine Art Abfragestring. Im Allgemeinen ist die Fragezeichensyntax in einer URL eine Technik, über die in eine URL Argumente eingebettet werden können. Während diese Argumente in der Regel für Skripten gedacht sind, die auf einem Server laufen, gibt es keinen Grund dafür, warum sie nicht auch in JavaScript-gesteuerten Webseiten verwendet werden sollen. Beispiel 14-1 zeigt die Definition einer allgemeinen `getArgs( )`-Funktion, die verwendet werden kann, um Argumente aus der `search`-Eigenschaft einer URL hinauszuziehen.

Beispiel 14-1. Argumente aus einer URL herausziehen

    /*
     * Diese Funktion parst durch Ampersands getrennte Name=Wert-Argumentpaare aus dem
     * Abfragestring der URL. Sie speichert die Name=Wert-Paare in den Eigenschaften
     * eines Objekts und liefert das Objekt zurück. Verwenden Sie sie folgendermaßen:
     *
     * var args = getArgs( ); // Argumente aus der URL parsen
     * var q = args.q || ""; // Argument verwenden, wenn es definiert ist, sonst einen
                 // Default-Wert
     * var n = args.n ? parseInt(args.n) : 10;
     */
    function getArgs( ) {
      var args = new Object( );
      var abfrage = location.search.substring(1);  // Abfragestring abrufen
      var paare = abfrage.split("&");        // Bei Ampersand-Zeichen zerlegen
      for(var i = 0; i < paare.length; i++) {
        var pos = paare[i].indexOf('=');     // Nach "Name=Wert" suchen
        if (pos == −1) continue;         // Wird keiner gefunden, überspringen
        var argname = paare[i].substring(0,pos); // Den Namen herausziehen
        var wert = paare[i].substring(pos+1);   // Den Wert herausziehen
        value = decodeURIComponent(wert);     // Den Wert, falls erforderlich, dekodieren
        args[argname] = wert;           // Name/Wert-Paar als Eigenschaft speichern
      }
      return args;                 // Das Objekt zurückliefern
    }

## 14.2.2 Neue Dokumente laden

Obwohl die `location`-Eigenschaft eines Fensters auf ein Location-Objekt verweist, können Sie der Eigenschaft einen Stringwert zuweisen. Wenn Sie das tun, interpretiert der Browser diesen String als eine URL und versucht, sie zu laden und das Dokument unter dieser URL anzuzeigen. Beispielsweise könnten Sie der Eigenschaft `location` folgendermaßen eine URL zuweisen:

    // Wenn der Browser die Funktion Document.getElementById nicht unterstützt,
    // zu einer statischen Seite umleiten, die diese Funktion nicht benötigt.
    if (!document.getElementById) location = "staticpage.html";

Beachten Sie, dass die der Eigenschaft `location` zugewiesene URL in diesem Beispiel eine relative URL ist. Relative URLs werden in Bezug auf die Seite interpretiert, in der sie erscheinen, genauso als würden sie in einem Hyperlink verwendet.

Beispiel 14-7 am Ende dieses Kapitels nutzt die Eigenschaft `location` ebenfalls, um ein neues Dokument zu laden.

Es ist überraschend, dass das Window-Objekt keine Methode besitzt, die den Browser veranlasst, eine neue Seite zu laden und anzuzeigen. Früher war die Zuweisung einer URL zur `location`-Eigenschaft eines Fensters die unterstützte Technik zum Laden neuer Seiten. Das Location-Objekt hat allerdings zwei Methoden für verwandte Zwecke. Die Methode `reload( )` lädt die aktuell angezeigte Webseite neu vom Webserver. Die Methode `replace( )` lädt eine von Ihnen angegebene URL und zeigt sie an. Es hat allerdings andere Auswirkungen, wenn Sie die zweite Methode für eine bestimmte URL aufrufen, als wenn Sie der `location`-Eigenschaft eines Fensters eine neue URL zuweisen. Wenn Sie `replace( )` aufrufen, ersetzt die angegebene URL die aktuelle URL im Verlauf des Browsers, anstatt einen neuen Eintrag in dieser Liste zu erzeugen. Wenn Sie `replace( )` einsetzen, um ein Dokument mit einem anderen zu überschreiben, führt der **Zurück** -Button den Benutzer deswegen nicht zum ursprünglichen Dokument zurück, wie er es macht, wenn Sie ein neues Dokument laden, indem Sie der Eigenschaft `location` eine URL zuweisen. Bei Webseiten, die Frames einsetzen und viele (vielleicht von einem Serverskript generierte) temporäre Seiten laden, ist `replace( )` oft nützlich. Da temporäre Seiten nicht im Verlauf gespeichert werden, ist der **Zurück** -Button für den Benutzer nützlicher.

Verwechseln Sie außerdem die `location`-Eigenschaft des Window-Objekts, die auf ein Location-Objekt verweist, nicht mit der `location`-Eigenschaft des Document-Objekts, die einfach ein schreibgeschützter String ist, der keine der besonderen Funktionalitäten des Location-Objekts besitzt. `document.location` ist ein Synonym für `document.URL`, dem vorgezogenen Namen für diese Eigenschaft (weil er mögliche Verwirrungen vermeidet). In den meisten Fällen ist `document.location` mit `location.href` identisch. Aber wenn es eine Server-Umleitung gibt, enthält `document.location` die geladene URL, während `location.href` die URL enthält, die ursprünglich angefordert wurde.

## 14.2.3 Das History-Objekt

Die `history`-Eigenschaft des Window-Objekts verweist auf das History-Objekt für das Fenster. Ursprünglich wurde das History-Objekt entworfen, um den Surfverlauf in einem Fenster als ein Array der zuletzt besuchten URLs zu modellieren. Es hat sich allerdings herausgestellt, dass das eine schlechte Entwurfsentscheidung war. Aus Gründen der Sicherheit und Privatsphäre ist es fast nie passend, einem Skript Zugriff auf die Liste der Webseiten zu geben, die der Benutzer zuvor besucht hat. Deswegen kann auf die Array-Elemente des History-Objekts über Skripten tatsächlich nie zugegriffen werden.

Obwohl auf seine Array-Elemente nicht zugegriffen werden kann, unterstützt das History-Objekt drei Methoden. Die Methoden `back( )` und `forward( )` gehen im Verlauf eines Fensters (oder Frames) zurück und vor und ersetzen das aktuell angezeigte Dokument durch ein zuvor betrachtetes. Das ähnelt dem, was passiert, wenn der Benutzer auf die **Zurück** \- und **Vor** -Buttons des Browsers klickt. Die dritte Methode, `go( )`, erwartet ein ganzzahliges Argument und kann im Verlauf eine beliebige Anzahl von Seiten vor (bei einem positiven Argument) oder zurück (bei einem negative Argument) gehen. Beispiel 14-7 am Ende dieses Kapitels führt die Verwendung der `back( )`\- und `forward( )`-Methoden des History-Objekts vor.

Netscape- und Mozilla-basierte Browser unterstützen `back( )`\- und `forward( )`-Methoden auf dem Window-Objekt selbst. Diese nicht-portierbaren Methoden führen die gleichen Aktionen durch wie die **Zurück** \- und **Vor** -Buttons des Browsers. Wenn Frames verwendet werden, kann es sein, dass `window.back( )` eine andere Aktion durchführt als `history.back( )`.

# 14.3 Window-, Screen- und Browser-Informationen abrufen

Skripten müssen sich manchmal auch Informationen zum Fenster, Desktop oder Browser verschaffen, in dem sie laufen. Dieser Abschnitt beschreibt Eigenschaften der Window-, Screen- und Navigator-Objekte, die es Ihnen ermöglichen, Dinge wie die Größe des Browserfensters, die Größe des Desktops und die Versionsnummer des Webbrowsers zu ermitteln. Diese Informationen ermöglichen es einem Skript, sein Verhalten auf Basis seiner Umgebung anzupassen.

## 14.3.1 Window-Geometrie

Die meisten Browser (die bemerkenswerteste Ausnahme ist der Internet Explorer) unterstützen einen einfachen Satz von Eigenschaften auf dem Window-Objekt, die Informationen zur Größe und Position des Fensters enthalten:

    // Die Gesamtgröße des Browserfensters auf dem Desktop
    var windowWidth = window.outerWidth;
    var windowHeight = window.outerHeight;

    // Die Position des Browserfensters auf dem Desktop
    var windowX = window.screenX
    var windowY = window.screenY

    // Die Größe des Inhaltsbereichs, in dem das HMTL-Dokument angezeigt wird
    // Das ist die Fenstergröße abzüglich der Menü-, Werkzeug- und Bildlaufleisten usw.
    var viewportWidth = window.innerWidth;
    var viewportHeight = window.innerWidth;

    // Diese Werte geben die Positionen der horizontalen und vertikalen Bildlaufleisten an.
    // Sie werden zur Umwandlung zwischen Dokument- und Fensterkoordinaten verwendet.
    // Diese Werte geben an, welcher Bereich des Dokuments in der linken oberen Ecke
    // des Bildschirms erscheint.
    var horizontalScroll = window.pageXOffset;
    var verticalScroll = window.pageYOffset;

Beachten Sie, dass diese Eigenschaften schreibgeschützt sind. Methoden zur Fenstermanipulation, die es Ihnen ermöglichen, das Fenster zu verschieben, die Fenstergröße zu ändern oder das Fenster zu scrollen, werden weiter unten in diesem Kapitel beschrieben. Beachten Sie auch, dass es unterschiedliche Koordinatensysteme gibt, derer Sie sich bewusst sein müssen. _Bildschirmkoordinaten_ beschreiben die Position des Browserfensters auf dem Desktop. Sie werden in Bezug auf die linke obere Ecke des Desktops gemessen. _Fensterkoordinaten_ beschreiben eine Position im Inhaltsbereich des Webbrowsers. Sie werden in Bezug auf die linke obere Ecke des Inhaltsbereichs gemessen. _Dokumentkoordinaten_ beschreiben die Position innerhalb eines HTML-Dokuments. Sie werden in Bezug auf die linke obere Ecke des Dokuments gemessen. Wenn das Dokument länger oder breiter als der Inhaltsbereich ist (was bei Webseiten häufig der Fall ist), sind Dokumentkoordinaten und Fensterkoordinaten nicht identisch. Dann müssen Sie die Position der Bildlaufleisten berücksichtigen, wenn Sie zwischen diesen beiden Koordinatensystemen konvertieren. Mehr zu Dokumentkoordinaten erfahren Sie in den Kapitel 15 und Kapitel 16.

Wie bereits gesagt wurde, sind die hier aufgeführten Eigenschaften des Window-Objekts im Internet Explorer nicht definiert. Aus irgendeinem Grund packt der IE die Eigenschaften für die Fenstergeometrie in den `<body>` eines HTML-Dokuments. Und der IE 6 packt diese Eigenschaften, um die Sache noch etwas verwirrender zu machen, in das Element `document.documentElement` statt in `document.body`, wenn er ein Dokument mit einer `<!DOCTYPE>`-Deklaration anzeigt.

Beispiel 14-2 liefert ausführliche Informationen dazu. Es definiert ein Geometry-Objekt mit Methoden, die eine portable Abfrage der Größe des Inhaltsbereichs, der Position der Bildlaufleiste und der Bildschirmposition ermöglichen.

Beispiel 14-2. Portables Abfragen der Fenstergeometrie

    /**
     * Geometry.js: Portable Funktionen für die Geometrie von Fenster und Dokument.
     *
     * Dieses Modul definiert Funktionen für die Abfrage der Geometrie von Fenster und Dokument.
     *
     * getWindowX/Y( ): liefern die Position des Fensters auf dem Bildschirm zurück
     * getViewportWidth/Height( ): liefern die Größe des Inhaltsbereichs des Browser
     * getDocumentWidth/Height( ): liefern die Größe des Dokuments
     * getHorizontalScroll( ): liefert die Position der horizontalen Bildlaufleiste
     * getVerticalScroll( ): liefert die Position der vertikalen Bildlaufleiste
     *
     * Beachten Sie, dass es keine portable Möglichkeit gibt, die Gesamtgröße des Browserfensters
     * abzufragen. Deswegen gibt es keine getWindowWidth/Height( )-Funktionen.
     *
     * ACHTUNG: Dieses Modul muss in den <body> eines Dokuments eingeschlossen werden,
     * nicht in den <head> des Dokuments.
     */
    svar Geometry = {};

    if (window.screenLeft) { // IE und andere
        Geometry.getWindowX = function( ) { return window.screenLeft; };
        Geometry.getWindowY = function( ) { return window.screenTop; };
    }
    else if (window.screenX) { // Firefox und andere
        Geometry.getWindowX = function( ) { return window.screenX; };
        Geometry.getWindowY = function( ) { return window.screenY; };
    }

    if (window.innerWidth) { // Alle Browsers außer dem IE
        Geometry.getViewportWidth = function( ) { return window.innerWidth; };
        Geometry.getViewportHeight = function( ) { return window.innerHeight; };
        Geometry.getHorizontalScroll = function( ) { return window.pageXOffset; };
        Geometry.getVerticalScroll = function( ) { return window.pageYOffset; };
    }
    else if (document.documentElement && document.documentElement.clientWidth) {
        // Funktionen für den IE 6, wenn es einen DOCTYPE gibt
        Geometry.getViewportWidth =
            function( ) { return document.documentElement.clientWidth; };
        Geometry.getViewportHeight =
            function( ) { return document.documentElement.clientHeight; };
        Geometry.getHorizontalScroll =
            function( ) { return document.documentElement.scrollLeft; };
        Geometry.getVerticalScroll = function( ) { return document.documentElement.scrollTop; };
    }
    else if (document.body.clientWidth) {
        // Funktionen für IE4, IE5 und IE6 ohne DOCTYPE
        Geometry.getViewportWidth =
            function( ) { return document.body.clientWidth; };
        Geometry.getViewportHeight =
            function( ) { return document.body.clientHeight; };
        Geometry.getHorizontalScroll =
            function( ) { return document.body.scrollLeft; };
        Geometry.getVerticalScroll =
            function( ) { return document.body.scrollTop; };
    }

    // Diese Funktionen liefern die Größe des Dokuments zurück. Sie sind nicht fensterbezogen,
    // sind aber trotzdem nützlich.
    if (document.documentElement && document.documentElemnet.scrollWidth) {
        Geometry.getDocumentWidth =
            function( ) { return document.documentElement.scrollWidth; };
        Geometry.getDocumentHeight =
            function( ) { return document.documentElement.scrollHeight; };
    }
    else if (document.body.scrollWidth) {
        Geometry.getDocumentWidth =
            function( ) { return document.body.scrollWidth; };
        Geometry.getDocumentHeight =
            function( ) { return document.body.scrollHeight; };
    }

## 14.3.2 Das Screen-Objekt

Die `screen`-Eigenschaft eines Window-Objekts verweist auf ein Screen-Objekt, das Informationen zur Größe der Anzeige des Benutzers und der auf ihr verfügbaren Farben enthält. Die Eigenschaften `width` und `height` geben die Größe der Anzeige in Pixeln an. Sie könnten diese Eigenschaften beispielsweise nutzen, um zu entscheiden, welche Größe die Bilder haben sollen, die in ein Dokument eingeschlossen werden sollen.

Die Eigenschaften `availWidth` und `availHeight` geben die tatsächlich verfügbare Anzeigegröße an. Sie schließen den Raum aus, der von Elementen wie einer Desktop-Taskleiste eingenommen wird. Firefox und verwandte Browser (aber nicht der IE) definieren außerdem `availLeft`\- und `availTop`-Eigenschaften für das Screen-Objekt. Diese Eigenschaften geben die Koordinaten der ersten verfügbaren Position auf dem Bildschirm an. Wenn Sie ein Skript schreiben, das ein neues Browserfenster öffnet (wie Sie das tun, werden Sie später in diesem Kapitel erfahren), könnten Sie diese Eigenschaften verwenden, um es leichter auf dem Bildschirm zu zentrieren.

Beispiel 14-4 weiter unten in diesem Kapitel führt die Verwendung des Screen-Objekts vor.

## 14.3.3 Das Navigator-Objekt

Die `navigator`-Eigenschaft eines Window-Objekts verweist auf ein Navigator-Objekt, das Informationen zu einem Webbrowser als Ganzem enthält. Beispiele sind die Version und die Liste der Datenformate, die er anzeigen kann. Das Navigator-Objekt hat seinen Namen vom Netscape Navigator, wird aber auch von allen anderen Browsern unterstützt. (Als herstellerneutrales Synonym für `navigator` unterstützt der IE außerdem `clientInformation`. Leider haben andere Browser diese vernünftiger benannte Eigenschaft nicht übernommen.)

In der Vergangenheit wurde das Navigator-Objekt von Skripten verbreitet verwendet, um zu ermitteln, ob sie im Internet Explorer oder in Netscape laufen. Dieses Verfahren zur Browserermittlung ist problematisch, weil es eine kontinuierliche Anpassung erfordert, wenn neue Browser oder neue Versionen vorhandener Browser eingeführt werden. Heutzutage wird ein Verfahren vorgezogen, das auf einer _Funktionalitätsprüfung_ basiert. Anstatt Annahmen in Bezug auf bestimmte Browser und ihre Funktionalitäten zu machen, prüfen Sie einfach auf die Funktionalität (d.h. die Methode), die Sie benötigen. Als Beispiel sehen Sie hier, wie Sie ein Funktionalitätsprüfungsverfahren für die Methoden zur Event-Handler-Registrierung verwenden (die in Kapitel 17 betrachtet werden):

    if (window.addEventListener) {
        // Wenn die Methode addEventListener( ) unterstützt wird, wird diese verwendet.
        // Das deckt den Fall standardkonformer Browser wie Netscape/Mozilla/Firefox ab.
    }
    else if (window.attachEvent) {
        // Andernfalls wird die Methode attachEvent( ) verwendet, wenn sie existiert.
        // Das deckt den IE und alle nicht-standardmäßigen Browser ab, die ihn emulieren.
    }
    else {
        // Andernfalls ist keine der Methoden verfügbar.
        // Das passiert bei älteren Browsern, die kein DHTML unterstützen.
    }

Browserermittlung ist manchmal aber trotzdem nützlich. Das ist beispielsweise der Fall, wenn Sie einen bestimmten Fehler umgehen wollen, der in einer bestimmten Version eines bestimmten Browsers auftritt. Das ermöglicht Ihnen das Navigator-Objekt.

Das Navigator-Objekt hat fünf Eigenschaften, die Versionsinformationen zu dem Browser liefern, der läuft:

`appName`

Der einfache Name des Webbrowsers. Beim IE ist das »Microsoft Internet Explorer«. Bei Firefox und anderen Browsern, die von der Netscape-Codebasis abgeleitet sind (wie Mozilla und Netscape selbst), ist das »Netscape«.

`appVersion`

Die Versionsnummer und/oder andere Versionsinformationen zu dem Browser. Beachten Sie, dass das als interne Versionsnummer betrachtet werden sollte, da sie nicht immer mit der Versionsnummer übereinstimmt, die für den Benutzer angezeigt wird. Beispielsweise melden Netscape 6 und alle nachfolgenden Versionen von Mozilla und Firefox die Versionsnummer 5.0. Gleichermaßen melden die IE-Versionen 4 bis 6 alle die Versionsnummer 4.0, um eine Kompatibilität mit der Basisfunktionalität dieser vierten Browsergeneration anzuzeigen.

`userAgent`

Der String, den der Browser in seinem `USER-AGENT`-HTTP-Header sendet. Diese Eigenschaft enthält üblicherweise alle Informationen in `appName` und `appVersion` und kann oft noch weitere Informationen enthalten. Es gibt allerdings keine Standardformatierung für diese Informationen. Es ist also nicht möglich, sie auf browserunabhängige Weise zu parsen.

`appCodeName`

Der Codename des Browsers. Netscape nutzt den Codenamen »Mozilla« als Wert dieser Eigenschaft. Aus Kompatibilitätsgründen macht der IE das Gleiche.

`platform`

Die Hardware-Plattform, auf der der Browser läuft. Diese Eigenschaft wurde in JavaScript 1.2 eingeführt.

Die folgenden Zeilen JavaScript-Code zeigen alle Eigenschaften des Navigator-Objekts in einem Dialogfenster an:

    var browser = "BROWSER-INFORMATIONEN:\n";
    for(var eigenschaft in navigator) {
        browser += eigenschaft + ": " + navigator[eigenschaft] + "\n"
    }
    alert(browser);

Abbildung 14-1 zeigt das Dialogfenster, das angezeigt wird, wenn der Code in Firefox 1.5 ausgeführt wird.

Abbildung 14-1. Die Eigenschaften des Navigator-Objekts

Wie Sie auf Abbildung 14-1 sehen können, haben die Eigenschaften des Navigator-Objekts Werte, die manchmal komplexer als das sind, was Sie benötigen. Vielleicht interessieren Sie sich beispielsweise nur für die erste Ziffer der Eigenschaft `appVersion`. Wenn Sie das Navigator-Objekt verwenden, um Browserinformationen zu prüfen, können Sie Methoden wie `parseInt( )` und `String.indexOf( )` einsetzen, um nur die Informationen herauszuziehen, die Sie benötigen. Beispiel 14-3 zeigt etwas Code, der das macht: Er verarbeitet die Eigenschaften des Navigator-Objekts und speichert sie in einem Objekt namens `browser`.

In ihrer verarbeiteten Form sind diese Eigenschaften leichter zu verwenden als die rohen `navigator`-Eigenschaften. Der allgemeine Begriff für Code wie diesen ist _Client-Sniffer_. Komplexeren und allgemeineren Sniffer-Code können Sie im Internet finden. (Siehe beispielsweise <http://www.mozilla.org/docs/web-developer/sniffer/browser_type.html>.) Aber für viele Zwecke ist Code, der so einfach ist wie der in Beispiel 14-3 gezeigte, vollkommen ausreichend.

Beispiel 14-3. Browserhersteller und -version ermitteln

    /**
     * browser.js: Ein einfacher Client-Sniffer
     *
     * Dieses Modul definiert ein Objekt namens "browser", das sich leichter verwenden
     * lässt als das "navigator"-Objekt.
    */
    var browser = {
        version: parseInt(navigator.appVersion),
        isNetscape: navigator.appName.indexOf("Netscape") != −1,
        isMicrosoft: navigator.appName.indexOf("Microsoft") != −1
    };

Ein wichtiger Punkt, den Sie aus diesem Abschnitt mitnehmen sollten, ist, dass die Eigenschaften des Navigator-Objekts keine zuverlässige Browserbeschreibung liefern. Firefox 1.0, zum Beispiel, hat den `appName` »Netscape« und eine `appVersion`, die mit »5.0« beginnt. Safari, der nicht auf Mozilla-Code basiert, liefert die gleichen Werte zurück! Und der IE 6.0 hat den `appCodeName` »Mozilla« und eine `appVersion`, die mit der Zahl »4.0« beginnt. Der Grund dafür ist, dass früher in alten, bestehenden Webseiten so viel Browser-Sniffing-Code verwendet wurde, dass die Browserhersteller es sich nicht leisten können, die Rückwärtskompatibilität zu brechen, indem sie diese Eigenschaften aktualisieren. Das ist einer der Gründe dafür, dass das Browser-Sniffing weniger hilfreich geworden ist und vom Feature Testing überholt wurde.

# 14.4 Fenster öffnen und manipulieren

Das Window-Objekt definiert eine Reihe von Methoden, die eine hochgradige Steuerung des Fensters selbst ermöglichen. Die folgenden Abschnitte untersuchen, wie diese Methoden es Ihnen ermöglichen, Fenster zu öffnen und zu schließen, die Fenstergröße und -position zu steuern, den Tastaturfokus anzufordern und freizugeben und den Inhalt eines Fensters zu scrollen. Dieser Abschnitt schließt mit einem Beispiel, das einige dieser Features vorführt.

## 14.4.1 Fenster öffnen

Ein neues Webbrowserfenster können Sie mit der `open( )`-Methode des Window-Objekts öffnen.

`Window.open( )` ist die Methode, die verwendet wird, um über oder unter dem Browserfenster neue Fenster zu öffnen, während Sie im Web surfen. Aufgrund dieser Flut nervender Pop-ups haben die meisten Browserhersteller irgendeine Art Pop-up-Blocker integriert. Üblicherweise sind Aufrufe der `open( )`-Methode nur als Antwort auf eine Benutzeraktion erfolgreich, beispielsweise wenn der Benutzer auf einen Button oder einen Link klickt. JavaScript-Code, der versucht ein Pop-up-Fenster zu öffnen, wenn der Browser eine Seite zum ersten Mal lädt (oder entlädt), schlägt fehl.

`Window.open( )` erwartet vier optionale Argumente und liefert ein Window-Objekt zurück, das das neu geöffnete Fenster repräsentiert. Das erste Argument für `open( )` ist die URL des Dokuments, das in dem neuen Fenster angezeigt werden soll. Wird dieses Argument weggelassen (oder ist es `null` oder der leere String), bleibt das Fenster leer.

Das zweite Argument für `open( )` ist der Name des Fensters. Wie in diesem Kapitel bereits besprochen wurde, kann dieser Wert als Name des `target`-Attributs eines `<form>`\- oder `<a>`-Tags nützlich sein. Wenn Sie den Namen eines Fensters angeben, das bereits geöffnet ist, liefert `open( )` einfach eine Referenz auf das bestehende Fenster zurück und öffnet kein neues.

Das dritte Argument für `open( )` ist eine Liste mit Features, die die Fenstergröße und GUI-Dekorationen angeben. Wenn Sie dieses Argument weglassen, erhält das neue Fenster die Default-Größe und einen vollständigen Satz von Standard-Features: eine Menüleiste, eine Statusleiste, Werkzeugleisten und so weiter. Aber wenn Sie dieses Argument angeben, können Sie explizit die Größe des Fensters und die Features angeben, die es einschließen soll. Beispielsweise können Sie die folgende Zeile JavaScript verwenden, um ein kleines, größenveränderbares Fenster mit einer Statusleiste, aber ohne Menüleiste oder Adressleiste zu erzeugen:

    var w = window.open("smallwin.html", "Kleines Fenster",
                        "width=400,height=350,status=yes,resizable=yes");

Beachten Sie, dass alle Features weggelassen werden, die Sie nicht explizit angeben, wenn Sie ein drittes Argument angeben. Bei `Window.open( )` in Teil IV finden Sie eine vollständige Liste der verfügbaren Features und ihrer Namen. Aus unterschiedlichen Sicherheitsgründen besitzen Browser verschiedene Einschränkungen für die Features, die Sie angeben können. Üblicherweise wird Ihnen beispielsweise nicht gestattet, ein Fenster anzugeben, das zu klein ist oder außerhalb des Bildschirms positioniert wird. Und einige Browser erlauben Ihnen nicht, ein Fenster ohne Statusleiste zu erzeugen. Während Spammer, Phisher und andere Protagonisten der dunklen Seite des Webs immer wieder neue Möglichkeiten finden, Benutzer auszuspionieren, erlegen Browserhersteller der Verwendung der `open( )`-Methode mehr und mehr Einschränkungen auf.

Das vierte Argument für `open( )` ist nur dann nützlich, wenn das zweite Argument ein bestehendes Fenster angibt. Dieses vierte Argument ist ein Boolescher Wert, der angibt, ob die URL, die als erstes Argument angegeben wurde, den aktuellen Eintrag im Verlauf des Browserfensters ersetzen soll (`true`) oder ob ein neuer Eintrag im Verlauf des Browserfensters erzeugt werden soll (`false`). Das zweite Verhalten ist das Default-Verhalten.

Der Rückgabewert der `open( )`-Methode ist ein Window-Objekt, das das neu erzeugte Fenster repräsentiert. Sie können dieses Window-Objekt in Ihrem JavaScript-Code verwenden, um das neue Fenster zu referenzieren, wie Sie das implizite Window-Objekt `window` verwenden, um das Fenster zu referenzieren, in dem Ihr Code läuft. Aber wie ist das mit der umgekehrten Situation? Was ist, wenn JavaScript-Code im neuen Fenster das Fenster referenzieren will, durch das er geöffnet wurde? Die `opener`-Eigenschaft eines Fensters verweist auf das Fenster, durch das es geöffnet wurde. Wenn das Fenster vom Benutzer statt von JavaScript-Code geöffnet wurde, ist der Wert der `opener`-Eigenschaft `null`.

## 14.4.2 Fenster schließen

Genau wie die Methode `open( )` ein neues Fenster öffnet, schließt die Methode `close( )` ein Fenster. Wenn Sie ein Window-Objekt `w` erzeugt haben, können Sie es folgendermaßen schließen:

    w.close( );

JavaScript-Code, der in diesem Fenster selbst läuft, kann es folgendermaßen schließen:

    window.close( );

Beachten Sie die explizite Verwendung des Bezeichners `window`, um die `close( )`-Methode des Window-Objekts von der `close( )`-Methode des Document-Objekts zu unterscheiden.

Die meisten Browser ermöglichen es Ihnen, nur die Fenster automatisch zu schließen, die Ihr eigener JavaScript-Code erzeugt hat. Wenn Sie versuchen, ein anderes Fenster zu schließen, schlägt die Anforderung entweder fehl oder es wird dem Benutzer ein Dialogfenster präsentiert, das ihn auffordert, die Anforderung, das Fenster zu schließen, zu bestätigen (oder abzubrechen). Diese Vorsichtsmaßnahme verhindert, dass unvorsichtige Skripter Code schreiben, der das Haupt-Surffenster schließt.

Ein Window-Objekt besteht weiter, nachdem das Fenster geschlossen wurde, das es repräsentiert. Sie sollten allerdings nicht versuchen, eine seiner Eigenschaften zu verwenden. Nur die Eigenschaft `closed` können Sie prüfen. Diese Eigenschaft ist `true`, wenn das Fenster geschlossen wurde. Denken Sie daran, dass der Benutzer jedes Fenster jederzeit schließen kann. Es empfiehlt sich, regelmäßig zu prüfen, ob ein Fenster, das Sie zu verwenden versuchen, immer noch geöffnet ist.

## 14.4.3 Fenster manipulieren

Das Window-Objekt definiert Methoden, die die Größe eines Fensters ändern und das Fenster bewegen. Die Verwendung dieser Methoden wird üblicherweise als sehr schlechter Stil betrachtet: Die Kontrolle über die Größe und Position aller Fenster auf dem Desktop sollte ausschließlich dem Benutzer überlassen bleiben. Moderne Browser haben üblicherweise eine Option, die verhindert, dass JavaScript Fenster bewegt oder ihre Größe ändert, und Sie sollten davon ausgehen, dass diese Option bei einem großen Prozentsatz der Browser aktiviert ist. Außerdem schränken Browser üblicherweise Ihre Möglichkeiten ein, Fenster aus dem Bildschirm zu schieben oder zu klein zu machen, um bösartigen Skripten entgegenzuwirken, die sich auf Code stützen, der in kleinen Fenstern oder Fenstern außerhalb des Bildschirms läuft, die vom Benutzer nicht bemerkt werden. Lesen Sie weiter, wenn Sie, nach all diesen Vorbehalten, immer noch die Größe von Fenstern ändern oder Fenster verschieben wollen.

`moveTo( )` verschiebt die linke obere Ecke des Fensters an die angegebenen Koordinaten. Gleichermaßen verschiebt `moveBy( )` das Fenster um die angegebene Anzahl von Pixeln nach links oder rechts und nach oben oder unten. `resizeTo( )` und `resizeBy( )` ändern die Fenstergröße um einen absoluten oder relativen Betrag. Ausführliche Informationen finden Sie in Teil IV.

## 14.4.4 Tastaturfokus und Sichtbarkeit

Die Methoden `focus( )` und `blur( )` bieten ebenfalls hochgradige Steuerungsmöglichkeiten über ein Fenster. Ein Aufruf von `focus( )` fordert, dass das System dem Fenster den Tastaturfokus gibt, und ein Aufruf von `blur( )` gibt den Tastaturfokus frei. Außerdem sichert die Methode `focus( )`, dass das Fenster sichtbar ist, indem es in der Stapelreihenfolge nach oben verschoben wird. Wenn Sie die Methode `Window.open( )` verwenden, um ein neues Fenster zu erzeugen, erzeugt der Browser dieses Fenster automatisch oben auf dem Stapel. Aber wenn das zweite Argument den Namen eines Fensters angibt, das bereits besteht, macht die Methode `open( )` dieses nicht automatisch sichtbar. Deswegen ist es eine verbreitete Praxis, auf Aufrufe von `open( )` einen Aufruf von `focus( )` folgen zu lassen.

## 14.4.5 Scrollen

Das Window-Objekt enthält außerdem Methoden, die das Dokument in einem Fenster oder Frame scrollen. `scrollBy( )` scrollt das Dokument, das im Fenster angezeigt wird, um die angegebene Anzahl von Pixeln nach links oder rechts und nach oben oder unten. `scrollTo( )` scrollt das Dokument zu einer absoluten Position. Es bewegt das Dokument so, dass sich die angegebenen Dokumentkoordinaten in der linken oberen Ecke des Dokumentbereichs des Fensters befinden.

In modernen Browsern haben die HTML-Elemente eines Dokuments (siehe Kapitel 15) `offsetLeft`\- und `offsetTop`-Eigenschaften, die die X- und Y-Koordinaten des Elements angeben (Methoden zur Ermittlung der Position aller Elemente werden in "16.2.3 Abfrage der Position und Abmessungen von Elementen" behandelt). Nachdem Sie die Position eines Elements ermittelt haben, können Sie die Methode `scrollTo( )` verwenden, um ein Fenster so zu scrollen, dass sich ein beliebiges angegebenes Element in der linken oberen Ecke des Fensters befindet.

Eine andere Herangehensweise für das Scrolling ist es, die `focus( )`-Methode von Dokumentelementen (wie Formularfeldern und Buttons) aufzurufen, die den Tastaturfokus annehmen können. Wenn der Fokus auf das Element übertragen wird, wird das Dokument auch gescrollt, um das Element sichtbar zu machen. Beachten Sie, dass das das Element nicht notwendigerweise in die linke obere Ecke des Fensters bringt. Aber es sorgt dafür, dass das Element irgendwo im Fenster sichtbar ist.

Die meisten modernen Browser haben eine weitere Scrolling-Methode: Auf einem beliebigen HTML-Dokument kann `scrollIntoView( )` aufgerufen werden, um es sichtbar zu machen. Diese Methode versucht, das Element oben im Fenster zu positionieren, aber kann das natürlich nicht, wenn sich das Element in der Nähe des Dokumentendes befindet. `scrollIntoView( )` ist nicht so weit verbreitet implementiert wie die Methode `focus( )`, funktioniert aber auf allen HTML-Elementen, nicht nur auf denen, die den Tastaturfokus annehmen. Mehr zu dieser Methode erfahren Sie im HTMLElement-Abschnitt von Teil IV.

Eine letzte Möglichkeit, ein Fenster mithilfe der Skriptsteuerung zu scrollen, besteht darin, an allen Orten, zu denen Sie das Dokument scrollen wollen, mit `<a name=>`-Tags Anker zu definieren. Dann verwenden Sie die Ankernamen mit der `hash`-Eigenschaft des Location-Objekts. Wenn Sie am Anfang Ihres Dokuments einen Anker mit dem Namen »top« definieren, können Sie beispielsweise mit Code wie diesem wieder an den Anfang des Dokuments zurückspringen:

    window.location.hash = "#top";

Diese Technik ist eine wirksame Anwendung der Fähigkeit von HTML, über benannte Anker in einem Dokument zu navigieren. Sie macht die aktuelle Dokumentposition in der Adressleiste des Browsers sichtbar, ermöglicht es, Lesezeichen für diese Position zu setzen, und ermöglicht es dem Benutzer, mit dem **Zurück** -Button zu seiner vorangehenden Position zurückzugehen, was ein ansprechendes Feature sein kann.

Andererseits kann es unter manchen Umständen als Ärgernis betrachtet werden, wenn man den Browserverlauf des Benutzers mit benannten Ankern verstopft, die von einem Skript generiert wurden. Um zu einem benannten Anker zu scrollen, ohne (in den meisten Browsern) einen neuen Verlaufseintrag zu erzeugen, verwenden Sie stattdessen die Methode `Location.replace( )`:

    window.location.replace("#top");

## 14.4.6 Beispiel zu Window-Methoden

Beispiel 14-4 führt die `open( )`-, `close( )`\- und `moveTo( )`-Methoden des Window-Objekts und eine Reihe weiterer Programmiertechniken für Fenster vor, die in diesem Kapitel behandelt wurden. Es erzeugt ein neues Fenster und verwendet dann `setInterval( )`, um wiederholt eine Funktion aufzurufen, die es über den Bildschirm bewegt. Es ermittelt über das Screen-Objekt die Größe des Bildschirms und verwendet diese Informationen dann, um das Fenster zurückspringen zu lassen, wenn es einen der Ränder des Bildschirms erreicht.

Beispiel 14-4. Fenster erzeugen und manipulieren

    <script>
    var abprallen = {
        x:0, y:0, w:200, h:200,  // Fensterposition und Größe
        dx:5, dy:5,              // Fenstergeschwindigkeit
        intervall: 100,          // Millisekunden zwischen Aktualisierungen
        fenster: null,           // Das Fenster, das wir erzeugen werden
        timer: null,             // Der Rückgabewert von setInterval( )

        // Die Animation beginnen.
        start: function( ) {
             // Mit dem Fenster im Zentrum des Bildschirms beginnen.
             abprallen.x = (screen.width – abprallen.w)/2;
             abprallen.y = (screen.height – abprallen.h)/2;

             // Das Fenster erzeugen, das wir herumbewegen werden.
             // Die javascript:-URL ist einfach ein Mittel, um ein kurzes Dokument anzuzeigen.
             // Das letzte Argument gibt die Fenstergröße an.
             abprallen.fenster = window.open('javascript:"<h1>ABPRALLEN!</h1>"', "",
                                      "left=" + abprallen.x + ",top=" + abprallen.y +
                                      ",width=" + abprallen.w + ",height=" +abprallen.h+
                                      ",status=yes");
             // setInterval( ) verwenden, um die Methode naechsterFrame( ) alle intervall
             // Millisekunden aufzurufen. Den Rückgabewert speichern, damit die Animation
             // angehalten werden kann, indem er an clearInterval( ) übergeben wird.
             abprallen.timer = setInterval(abprallen.naechsterFrame, abprallen.intervall);
        },

        // Die Animation anhalten.
        stop: function( ) {
             clearInterval(abprallen.timer);  // Timer abbrechen
             if (!abprallen.fenster.closed) abprallen.fenster.close( ); // Fenster schließen
        },

        // Den nächsten Frame der Animation anzeigen. Wird von setInterval( ) aufgerufen.
        naechsterFrame: function( ) {
             // Wenn der Benutzer das Fenster geschlossen hat, wird die Animation beendet.
             if (abprallen.fenster.closed) {
                 clearInterval(abprallen.timer);
                 return;
             }

             // Zurückspringen, wenn der rechte oder linke Rand erreicht wurde.
             if ((abprallen.x+abprallen.dx > (screen.availWidth – abprallen.w)) ||
                 (abprallen.x+abprallen.dx < 0)) abprallen.dx = -abprallen.dx;

             // Zurückspringen, wenn der obere oder untere Rand erreicht wurde.
             if ((abprallen.y+abprallen.dy > (screen.availHeight – abprallen.h)) ||
                 (abprallen.y+abprallen.dy < 0)) abprallen.dy = -abprallen.dy;

             // Die aktuelle Position des Fensters aktualisieren.
             abprallen.x += abprallen.dx;
             abprallen.y += abprallen.dy;

             // Schließlich das Fenster in die neue Position bewegen.
             abprallen.fenster.moveTo(abprallen.x,abprallen.y);

             // Die aktuelle Position in der Statusleiste des Fensters anzeigen.
             abprallen.fenster.defaultStatus = "(" + abprallen.x + "," + abprallen.y + ")";
        }
    }
    </script>
    <button onclick="abprallen.start( )">Start</button>
    <button onclick="abprallen.stop( )">Stop</button>

# 14.5 Einfache Dialogfenster

Das Window-Objekt bietet drei Methoden, um dem Benutzer einfache Dialogfenster anzuzeigen. `alert( )` zeigt dem Benutzer eine Meldung an und wartet, dass der Benutzer das Dialogfenster schließt. `confirm( )` fordert den Benutzer auf, auf einen **OK** \- oder einen **Abbrechen** -Button zu klicken, um eine Operation zu bestätigen bzw. abzubrechen. Und `prompt( )` fordert den Benutzer zur Eingabe eines Strings auf.

Obwohl diese Methoden sehr einfach und leicht zu verwenden sind, verlangt gutes Design, dass Sie sie spärlich – am besten überhaupt nicht – einsetzen. Dialogfenster wie diese sind kein gewöhnliches Feature des Webparadigmas und werden mittlerweile immer seltener eingesetzt, da fähige Webbrowser das Scripting des Dokumentinhaltes selbst unterstützen. Die meisten Benutzer werden die Dialogfenster, die von den Methoden `alert( )`, `confirm( )` und `prompt( )` erzeugt werden, als Störung ihres Surferlebnisses empfinden. Heutzutage werden diese Methoden oft nur noch für das Debugging verwendet: JavaScript-Progammierer fügen in Code, der nicht funktioniert, häuft `alert( )`-Methoden ein, um zu versuchen, das Problem zu diagnostizieren. (In Beispiel 15-9 finden Sie eine alternative Technik für das Debugging.)

Beachten Sie, dass der Text, der von diesen Dialogfenstern angezeigt wird, Klartext ist, und nicht HTML-formatierter Text. Sie können den Text in diesen Dialogfenstern nur mit Leerzeichen, Zeilenumbrüchen und verschiedenen Interpunktionszeichen steuern.

Einige Browser zeigen in der Titelleiste oder der linken oberen Ecke aller Dialogfenster, die mit `alert( )`, `confirm( )` und `prompt( )` erzeugt wurden, das Wort »JavaScript« an. Obwohl Designer das nervend finden, sollte das als Feature, nicht als Fehler betrachtet werden: Es dient dazu, dem Benutzer die Herkunft des Dialogfensters klarzumachen und verhindert, dass Hacker Trojaner-Code schreiben, der System-Dialogfenster vortäuscht und den Benutzer so austrickst, dass dieser sein Passwort eingibt oder andere Dinge tut, die er nicht tun sollte.

Die Methoden `confirm( )` und `prompt( )` _blockieren_ – das heißt, dass diese Methoden erst zurückkehren, wenn der Benutzer das Dialogfenster schließt, das von ihnen angezeigt wird. Das bedeutet, dass Ihr Code nicht mehr weiterläuft, wenn eines dieser Dialogfenster geöffnet wird, und dass das Laden des aktuell geladenen Dokuments (wenn es ein solches gibt) angehalten wird, bis der Benutzer die angeforderte Eingabe liefert. Diese Methoden müssen blockieren, weil ihr Rückgabewert die Eingabe des Benutzers ist und die Methoden deswegen auf den Benutzer warten müssen, bevor sie zurückkehren können. In den meisten Browsern blockiert auch die `alert( )`-Methode und wartet darauf, dass der Benutzer das Dialogfenster schließt.

Beispiel 14-5-Methode verwenden") zeigt eine mögliche Verwendung der Methode `confirm( )`, die das in Abbildung 14-2-Dialogfenster") gezeigte Dialogfenster hervorbringt.

Beispiel 14-5. Die confirm( )-Methode verwenden

    function sendeAbfrage( ) {
        // Das wollen wir den Benutzer fragen. Eine eingeschränkte Formatierung
        // kann mit Unterstrichen und Zeilenumbrüchen erreicht werden.
        var meldung = "\n\n\n\n" +
            "________________________________________________________\n\n" +
            "Bitte beachten Sie, dass komplexe Abfragen wie Ihre eine\n"   +
            "Minute oder mehr an Suchzeit in Anspruch nehmen können.\n"  +
            "________________________________________________________\n\n\n"  +
            "Bestätigen Sie mit OK oder brechen Sie mit Abbrechen ab.";

        // Um Bestätigung bitten und abbrechen, wenn diese nicht erteilt wird.
        if (!confirm(meldung)) return;

        /* Hier kämme der Code zur Ausführung der Abfrage hin. */
    }

Abbildung 14-2. Ein confirm( )-Dialogfenster

# 14.6 Die Statusleiste skripten

Webbrowser zeigen am unteren Rand jedes Fensters üblicherweise eine _Statusleiste_ an, in der der Browser Meldungen für den Benutzer anzeigen kann. Wenn der Benutzer die Maus über einen Hyperlink bewegt, zeigt ein Browser beispielsweise üblicherweise die URL an, auf die der Link zeigt.

In alten Browsern können Sie die Eigenschaft `status` setzen, um Text anzugeben, den der Browser vorübergehend in der Statusleiste anzeigen soll. Die Eigenschaft wurde üblicherweise verwendet, wenn der Benutzer die Maus über einem Hyperlink schweben lies, um anstelle der für Maschinen gedachten URL des Dokuments eine für Menschen lesbare Beschreibung des verlinkten Elements anzuzeigen. Code, der das gemacht hat, sah üblicherweise so aus:

    Verwirrt? Versuchen Sie die
    <a href="help.html" onmouseover="status='Klicken Sie hier, wenn Sie Hilfe brauchen!';
    return true;">
    Online-Hilfe</a>.

Wenn sich die Maus über diesen Hyperlink bewegt, wird der JavaScript-Code im `onmouseover`-Attribut ausgewertet. Er setzt die `status`-Eigenschaft des Fensters und liefert dann `true` zurück, um dem Browser zu sagen, dass dieser seine Aktion nicht durchführen soll (d.h., nicht die URL des Hyperlinks anzeigen soll).

Dieses Codefragment funktioniert nicht mehr. Häufig wurde Code wie dieser eingesetzt, um Benutzer vorsätzlich über das Ziel eines Links zu täuschen (beim Phishing-Betrug beispielsweise). Moderne Browser haben deswegen die Funktionalität zum Setzen der `status`-Eigenschaft deaktiviert.

Das Window-Objekt definiert außerdem eine `defaultStatus`-Eigenschaft, die Text angibt, der in der Statusleiste angezeigt werden soll, wenn der Browser nichts anderes (wie Hyperlink-URLs) hat, die dort angezeigt werden sollen. In einigen Browsern funktioniert `defaultStatus` noch (aber nicht in allen: Firefox 1.0 deaktiviert die Eigenschaft `defaultStatus` beispielsweise gemeinsam mit der Eigenschaft `status`).

Früher wurde die Eigenschaft `defaultStatus` beispielsweise verwendet, um Statusleisten-Animationen zu erzeugen. In der Vergangenheit, als Dokumentinhalte noch nicht skriptbar waren, aber die Eigenschaft `defaultStatus` und die Methode `setInterval( )` verfügbar waren, konnten Webentwickler der Versuchung nicht wiederstehen, eine Vielzahl von ablenkenden und grellen Marquee-Animationen zu erzeugen, die eine Meldung über die Statusleiste gescrollt haben. Glücklicherweise sind diese Zeiten vorbei. Allerdings gibt es gelegentlich immer noch Gründe dafür, die Statusleiste zu verwenden, und sogar dafür, sie mit `setInterval( )` zu verwenden, wie Beispiel 14-6 zeigt.

Beispiel 14-6. Eine geschmackvolle Statusleisten-Animation

    <script>
    var VergeudeteZeit = {
        start: new Date( ),  // Die Anfangszeit festhalten
        zeigeVerstricheneZeit: function( ) {
            var jetzt = new Date( ); // Wie spät ist es jetzt?
            // die verstrichenen Minuten berechnen
            var verstrichen = Math.round((jetzt – VergeudeteZeit.start)/60000);
            // und versuchen, das in der Statusleiste anzuzeigen.
            window.defaultStatus = "Sie haben " + verstrichen + " Minuten vergeudet.";
        }
    }
    // Die Statusleiste minütlich aktualisieren.
    setInterval(VergeudeteZeit.zeigeVerstricheneZeit, 60000);
    </script>

# 14.7 Fehlerbehandlung

Die `onerror`-Eigenschaft eines Window-Objekts ist eine besondere Eigenschaft. Wenn Sie dieser Eigenschaft eine Funktion zuweisen, wird diese Funktion jedes Mal aufgerufen, wenn in diesem Fenster ein JavaScript-Fehler auftritt: Die Funktion, die Sie zuweisen, wird zu einem Error-Handler für das Fenster. (Beachten Sie, dass Sie auch einfach eine globale Funktion namens `onerror( )` definieren können sollten, da das mit der Zuweisung einer Funktion zur `onerror`-Eigenschaft eines Window-Objekts äquivalent sein müsste. Im IE funktioniert die Definition einer globalen `onerror( )`-Funktion allerdings nicht.)

Wenn ein JavaScript-Fehler auftritt, werden einem Error-Handler drei Argumente übergeben. Das erste ist eine Nachricht, die den Fehler beschreibt. Das könnte etwas sein wie »missing operator in expression«, »self is read-only« oder »meinname is not defined«. Das zweite Argument ist ein String, der die URL des Dokuments enthält, das den JavaScript-Code enthält, der den Fehler verursacht hat. Das dritte Argument ist die Nummer der Zeile im Dokument, auf der der Fehler aufgetreten ist. Ein Error-Handler kann diese Argumente für beliebige Zwecke verwenden. Ein typischer Error-Handler könnte dem Benutzer die Fehlermeldung anzeigen, den Fehler irgendwo protokollieren oder es erzwingen, dass der Fehler ignoriert wird. Vor JavaScript 1.5 kann der `onerror`-Handler auch als recht armseliger Ersatz für das `try/catch`-Exception-Handling dienen (siehe Kapitel 6).

Neben diesen drei Argumenten ist der Rückgabewert des `onerror`-Handlers wichtig. Wenn der `onerror`-Handler `true` zurückliefert, sagt er dem Browser, dass er den Fehler behandelt hat und keine weiteren Handlungen mehr erforderlich sind – anders gesagt, der Browser soll seine eigene Fehlermeldung nicht mehr anzeigen.

Im Lauf der Zeit haben Browser die Art geändert, wie sie mit JavaScript-Fehlern umgehen. Als JavaScript neu und Webbrowser jung waren, haben Browser üblicherweise jedes Mal ein Dialogfenster aufspringen lassen, wenn ein JavaScript-Fehler aufgetreten ist. Für Entwickler sind diese Dialoge hilfreich, für Endbenutzer sind sie störend. Um zu verhindern, dass Endbenutzer bei einer Produktions-Website jemals einen Fehlerdialog sehen (viele Websites verursachen JavaScript-Fehler, zumindest in einigen Browsern), können Sie einfach einen Error-Handler schreiben, der alle Fehler stillschweigend ignoriert:

    // Den Benutzer nicht mit Fehlermeldungen belästigen
    window.onerror = function( ) { return true; }

Als schlecht geschriebener und inkompatibler JavaScript-Code im Web zu wuchern begann und JavaScript-Fehler alltäglich wurden, begannen Webbrowser, Fehler unaufdringlicher zu protokollieren. Das ist für Endbenutzer besser, aber schwerer für JavaScript-Entwickler, die (in Firefox beispielsweise) ein JavaScript-Konsolenfenster öffnen müssen, um zu sehen, welche Fehler aufgetreten sind. Um das während der Codeentwicklung zu vereinfachen, können Sie einen Error-Handler wie diesen definieren:

    // Fehlermeldungen in einem Dialogfenster anzeigen, aber nie mehr als 3.
    window.onerror = function(msg, url, line) {
        if (onerror.num++ < onerror.max) {
            alert("FEHLER: " + msg + "\n" + url + ":" + line);
            return true;
        }
    }
    onerror.max = 3;
    onerror.num = 0;

# 14.8 Mehrere Fenster und Frames

Die meisten Webanwendungen laufen in einem einzelnen Fenster oder öffnen vielleicht kleine Zusatzfenster, um eine Hilfe anzuzeigen. Aber es ist auch möglich, Anwendungen zu erstellen, die zwei oder mehr Frames oder Fenster verwenden und JavaScript-Code einsetzen, um diese Fenster oder Frames miteinander interagieren zu lassen. Dieser Abschnitt erklärt, wie das gemacht wird.[52]

Bevor wir in die Betrachtung von mehreren Fenstern und Frames einsteigen, macht es Sinn, dass wir uns noch einmal die Implikationen der Same-Origin-Sicherheitsrichtlinie ansehen, die in "13.8.2 Die Same Origin Policy" betrachtet wurde. Gemäß dieser Sicherheitsrichtlinie kann JavaScript-Code nur mit Inhalten von dem gleichen Webserver interagieren, von dem das Dokument stammt, in das der JavaScript-Code eingebettet ist. Jeder Versuch, Inhalte oder Eigenschaften eines Dokuments von einem anderen Webserver zu lesen, schlägt fehl. Das bedeutet beispielsweise, dass Sie ein JavaScript-Programm schreiben können, das seine eigene Website indiziert, um eine Liste aller Links zu erzeugen, die auf der Site erscheinen. Aber Sie können das Programm nicht so erweitern, dass es diesen Links folgt und die anderen Sites indiziert: Ihr Versuch, eine Liste der Links in einem externen Dokument zu erhalten, schlägt fehl. Später werden Sie in Beispiel 14-7 Code sehen, der aufgrund der Same Origin Policy nicht funktioniert.

## 14.8.1 Beziehungen zwischen Frames

Sie werden sich sicher daran erinnern, dass die `open( )`-Methode des Window-Objekts ein neues Window-Objekt zurückliefert, das das neu erzeugte Fenster repräsentiert, und dass dieses neue Fenster eine `opener`-Eigenschaft hat, die auf das ursprüngliche Fenster zurückweist. Auf diese Weise können zwei Fenster aufeinander verweisen und jeweils die Eigenschaften des anderen lesen und die Methoden des anderen aufrufen. Das Gleiche ist mit Frames möglich. Jeder Frame in einem Fenster kann auf alle anderen Frames über die `frames`-, `parent`\- und `top`-Eigenschaften des Window-Objekts zugreifen.

Der JavaScript-Code in einem beliebigen Fenster oder Frame kann auf sein eigenes Fenster oder seinen eigenen Frame mit `window` oder `self` verweisen. Da jedes Fenster und jeder Frame das globale Objekt für den eigenen Code ist, ist eine Verwendung von `window` oder `self` nur dann erforderlich, wenn Sie auf das globale Objekt selbst verweisen müssen. Wenn Sie auf eine Methode oder eine Eigenschaft in diesem Fenster oder Frame verweisen möchten, ist es nicht erforderlich (aber in stilistischer Hinsicht manchmal hilfreich), dass Sie dem Eigenschafts- oder Methodennamen `window` oder `self` voranstellen.

Jedes Fenster hat eine `frames`-Eigenschaft. Diese Eigenschaft verweist auf ein Array mit Window-Objekten, die jeweils einen Frame repräsentieren, der in dem Fenster enthalten ist. (Wenn ein Fenster keine Frames hat, ist das Array `frames[]` leer und `frames.length` gleich null.) Ein Fenster (oder ein Frame) kann seinen ersten Frame also mit `frames[0]` referenzieren, seinen zweiten Frame mit `frames[1]` und so weiter. Gleichermaßen kann JavaScript-Code, der in einem Fenster läuft, auf den dritten Subframe seines zweiten Frames folgendermaßen verweisen:

    frames[1].frames[2]

Jedes Window-Objekt hat außerdem eine `parent`-Eigenschaft, die auf das Window-Objekt verweist, in dem es enthalten ist. Der erste Frame in einem Fenster kann auf einen Geschwisterframe (hier den zweiten Frame im Fenster) also so verweisen:

    parent.frames[1]

Wenn ein Window-Objekt ein Toplevel-Fenster und kein Frame ist, verweist `parent` einfach auf das Fenster selbst:

    parent == self; // Gilt für jedes Toplevel-Fenster

Wenn ein Frame in einem anderen Frame enthalten ist, der in einem Toplevel-Fenster enthalten ist, kann dieser Frame auf das Toplevel-Fenster über `parent.parent` verweisen. Die Eigenschaft `top` ist allerdings eine allgemeine Abkürzung: Egal wie tief ein Frame einbettet ist, verweist seine `top`-Eigenschaft auf das einschließende Fenster auf der obersten Ebene. Wenn ein Window-Objekt ein Toplevel-Fenster repräsentiert, verweist `top` einfach auf dieses Fenster selbst. Bei Frames, die unmittelbare Kinder eines Toplevel-Fensters sind, ist die Eigenschaft `top` mit der Eigenschaft `parent` identisch.

Frames werden üblicherweise mit `<frameset>`\- und `<frame>`-Elementen erzeugt. In HTML 4 kann auch das `<iframe>`-Element verwendet werden, um in einem Dokument einen _Inline-Frame_ zu erzeugen. Aus JavaScript-Sicht sind Frames, die mit `<iframe>` erzeugt wurden, mit Frames identisch, die mit `<frameset>` und `<frame>` erzeugt wurden. Alles, was hier gesagt wird, gilt für beide Arten von Frames.

Abbildung 14-3 illustriert diese Beziehungen zwischen Frames und zeigt, wie Code, der in einem beliebigen Frame läuft, auf jeden anderen Frame über die Eigenschaften `frames`, `parent` und `top` verweisen kann. Die Abbildung zeigt ein Browserfenster, das zwei Frames enthält, die aufeinandergestapelt sind. Der zweite Frame (der untere, größere) enthält seinerseits drei nebeneinanderliegende Subframes.

## 14.8.2 Fensternamen und Frame-Namen

Das zweite, optionale Argument für die Methode `Window.open( )`, über das wir bereits gesprochen haben, ist ein Name für das neu erzeugte Fenster. Wenn Sie mit dem HTML-Element `<frame>` einen neuen Frame erzeugen, können Sie einen Namen mit dem Attribut `name` erzeugen. Ein wichtiger Grund, Namen für Fenster und Frames anzugeben, ist, dass diese Namen als Wert des `target`-Attributs von `<a>`\- und `<form>`-Elementen verwendet werden können, um dem Browser zu sagen, wo das Ergebnis einer Link-Aktivierung oder einer Formular-Übermittlung angezeigt werden soll.

Wenn Sie zwei Fenster haben, von denen das eine `inhaltsverzeichnis` und das andere `hauptfenster` heißt, könnten Sie im `inhaltsverzeichnis`-Fenster HTML wie das folgende haben:

    <a href="chapter01.html" target="hauptfenster">[Kapitel 1](ch01.html "Kapitel 1. Einführung in JavaScript"), Einführung</a>

Der Browser lädt die angegebene URL, wenn der Benutzer auf diesen Hyperlink klickt, aber anstatt die URL im gleichen Fenster wie den Link anzuzeigen, zeigt er sie in dem Fenster mit dem Namen `hauptfenster` an. Wenn es kein Fenster mit dem Namen `hauptfenster` gibt, erzeugt ein Klick auf den Link ein Fenster mit diesem Namen und lädt darin die angegebene URL.

Abbildung 14-3. Beziehungen zwischen Frames

Die Attribute `target` und `name` sind Teil von HTML und funktionieren ohne die Intervention von JavaScript. Aber es gibt auch JavaScript-bezogene Gründe, Ihren Frames Namen zu geben. Erinnern Sie sich daran, dass jedes Window-Objekt ein `frames[]`-Array hat, das Referenzen auf jeden seiner Frames enthält. Dieses Array enthält alle Frames in einem Fenster (oder Frame), egal ob sie Namen haben oder nicht. Wenn Sie einem Frame einen Namen geben, wird jedoch auch eine Referenz auf diesen Frame in einer neuen Eigenschaft des Eltern-Window-Objekts gespeichert. Der Name der neuen Eigenschaft entspricht dem Namen des Frames. Deswegen können Sie einen Frame mit HTML wie diesem erzeugen:

    <frame name="inhaltsverzeichnis" src="toc.html">

Jetzt können Sie auf diesen Frame so aus einem anderen Geschwister-Frame verweisen:

    parent.inhaltsverzeichnis

Das macht Ihren Code besser lesbar und verstehbar als die Verwendung (und das Verlassen auf) hartcodierte Array-Indizes, wie Sie es bei einem unbenannten Frame machen müssten:

    parent.frames[1]

Beispiel 14-7 am Ende dieses Kapitels verwendet diese Technik, um Frames zu referenzieren.

## 14.8.3 JavaScript in interagierenden Fenstern

In Kapitel 13 haben Sie gelernt, dass das Window-Objekt als das globale Objekt für clientseitigen JavaScript-Code dient und dass das Fenster als Ausführungskontext für den gesamten JavaScript-Code dient, den es enthält. Das gilt auch für Frames: Jeder Frame ist ein unabhängiger JavaScript-Ausführungskontext. Weil jedes Window-Objekt sein eigenes globales Objekt ist, definiert jedes Fenster seinen eigenen Namensraum und seine eigene Menge globaler Variablen. Betrachtet man sie aus der Perspektive mehrerer Frames oder Fenster, scheinen globale Variablen dann aber doch nicht mehr so global zu sein

Obwohl jedes Fenster und jeder Frame einen unabhängigen JavaScript-Ausführungskontext definiert, bedeutet das nicht, dass der JavaScript-Code, der in einem Fenster läuft, von Code isoliert ist, der in anderen Fenstern läuft. Code, der in dem einen Frame läuft, hat am Anfang seiner Geltungsbereichskette ein anderes Window-Objekt als Code, der in einem anderen Frame läuft. Aber der Code beider Frames wird vom gleichen JavaScript-Interpreter in der gleichen JavaScript-Umgebung ausgeführt. Wie Sie gesehen haben, kann ein Frame auf andere Frames über die Eigenschaften `frames`, `parent` und `top` verweisen. Obwohl der JavaScript-Code in separaten Frames also mit unterschiedlichen Geltungsbereichsketten ausgeführt wird, kann der Code in einem Frame trotzdem auf die Variablen und Funktionen zugreifen, die von Code in einem anderen Frame definiert werden.

Nehmen Sie beispielsweise an, dass Code in Frame A eine Variable namens `i` definiert:

    var i = 3;

Diese Variable ist nichts anderes als eine Eigenschaft des globalen Objekts – eine Eigenschaft des Window-Objekts. Code in Frame A kann auf die Variable explizit mit einem der folgenden Ausdrücke verweisen:

    window.i
    self.i

Nehmen Sie jetzt an, dass Frame A einen Geschwister-Frame B hat, der den Wert der Variablen `i` setzen möchte, die vom Code in Frame A definiert wurde. Wenn Frame B einfach eine Variable `i` setzt, führt das einfach nur dazu, dass er eine neue Eigenschaft in seinem eigenen Window-Objekt erzeugt. Stattdessen muss er also explizit mit Code wie diesem auf die Eigenschaft `i` in seinem Geschwister-Frame verweisen:

    parent.frames[0].i = 4;

Erinnern Sie sich daran, dass das Schlüsselwort `function`, das Funktionen definiert, genau wie das Schlüsselwort `var` nur eine Variable deklariert. Wenn JavaScript-Code in Frame A eine Funktion `f` deklariert, ist diese Funktion nur in Frame A definiert. Code in Frame A kann `f` folgendermaßen aufrufen:

    f( );

Aber Code in Frame B muss auf `f` als Eigenschaft des Window-Objekts von Frame A verweisen:

    parent.frames[0].f( );

Wenn der Code in Frame B diese Funktion häufig verwenden muss, könnte er die Funktion einer Variablen in Frame B zuweisen, damit er etwas bequemer auf sie verweisen kann:

    var f = parent.frames[0].f;

Jetzt kann Code in Frame B die Funktion als `f( )` aufrufen, genau wie es der Code in Frame A macht.

Wenn Sie Funktionen auf diese Weise zwischen Frames oder Fenstern teilen, ist es sehr wichtig, die Regeln der lexikalischen Geltung im Kopf zu behalten. Eine Funktion wird in dem Geltungsbereich ausgeführt, in dem sie definiert wurde, nicht in dem, in dem sie aufgerufen wurde. Unter den Voraussetzungen des vorangehenden Beispiels heißt das also, dass globale Variablen, wenn die Funktion `f` auf sie verweist, als Eigenschaften von Frame A nachgeschlagen werden, selbst wenn die Funktion aus Frame B aufgerufen wird.

Wenn Sie darauf nicht sorgfältig achten, kann das dazu führen, dass sich Ihre Programme auf unerwartete und verwirrende Weise verhalten. Nehmen Sie beispielsweise an, Sie definieren die folgende Funktion im `<head>`-Abschnitt eines Dokuments mit mehreren Frames und wollen sie zur Debugging-Unterstützung einsetzen:

    function debug(msg) {
      alert("Debugging-Meldung von Frame: " + name + "\n" + msg);
    }

Der JavaScript-Code in jedem Ihrer Frames kann auf diese Funktion über `top.debug( )` verweisen. Aber jedes Mal, wenn diese Funktion aufgerufen wird, schlägt sie die Variable `name` im Kontext des Toplevel-Fensters nach, in dem die Funktion definiert wurde, und nicht im Kontext des Frames, in dem sie aufgerufen wurde. Deswegen hat die Debugging-Meldung immer den Namen des Toplevel-Fensters statt den Namen des Frames, der die Meldung gesendet hat, wie es beabsichtigt war.

Denken Sie daran, dass Konstruktoren auch Funktionen sind. Wenn Sie also mit einer Konstruktorfunktion und einem damit verknüpften Prototyp-Objekt eine Klasse von Objekten definieren, ist diese Klasse nur für ein einziges Fenster definiert. Erinnern Sie sich an die Klasse Komplex, die in Kapitel 9 definiert wurde, und betrachten Sie folgendes HTML-Dokument mit mehreren Frames:

    <head>
    <script src="Komplex.js"></script>
    </head>
    <frameset rows="50%,50%">
      <frame name="frame1" src="frame1.html">
      <frame name="frame2" src="frame2.html">
    </frameset>

JavaScript-Code in den Dateien _frame1.html_ und _frame2.html_ kann Komplex-Objekte nicht mit einem Ausdruck wie diesem erzeugen:

    var c = new Komplex(1,2); // Funktioniert in keinem der Frames

Stattdessen muss Code in diesen Daten die Konstruktorfunktion explizit referenzieren:

    var c = new top.Komplex(3,4);

Alternativ kann Code in beiden Frames eine eigene Variable definieren, um bequemer auf die Konstruktorfunktion verweisen zu können:

    var Komplex = top.Komplex;
    var c = new Komplex(1,2);

Im Unterschied zu benutzerdefinierten Konstruktoren sind vordefinierte Konstruktoren automatisch in allen Fenstern vordefiniert. Beachten Sie allerdings, dass jedes Fenster eine separate Kopie des Konstruktors und eine separate Kopie des Prototyp-Objekts des Konstruktors besitzt. Beispielsweise hat jedes Fenster eine eigene Kopie des `String( )`-Konstruktors und des `String.prototype`-Objekts. Wenn Sie eine neue Methode für die Manipulation von JavaScript-Strings schreiben und diese dann zu einer Methode der Klasse String machen, indem Sie sie dem `String.prototype`-Objekt des aktuellen Fensters zuweisen, können alle Strings in diesem Fenster die neue Methode verwenden. Aber Strings, die in anderen Fenstern definiert werden, können nicht auf die neue Methode zugreifen. Beachten Sie allerdings, dass es keine Rolle spielt, welches Fenster die Referenz auf den String hält. Es zählt nur das Fenster, in dem der String tatsächlich erzeugt wurde.

* * *

[52] In den Anfangstagen von JavaScript waren Webanwendungen mit mehreren Frames oder Fenstern weit verbreitet. Mittlerweile hat sich der Webdesign-Konsens stark gegen die Verwendung von Frames (aber nicht von Inline-Frames, d.h. _IFrames_ ) gewandt, und man sieht auch nicht mehr so häufig Websites, die interagierende Fenster verwenden.

# 14.9 Beispiel: Eine Navigationsleiste in einem Frame

Dieses Kapitel schließt mit einem Beispiel, das viele der wichtigeren der hier beschriebenen Scripting-Techniken illustriert:

  * Die aktuelle URL mit `location.href` abfragen und neue Dokumente laden, indem die Eigenschaft `location` gesetzt wird

  * Die `back( )`\- und `forward( )`-Methoden des History-Objekts verwenden

  * `setTimeout( )` verwenden, um den Aufruf einer Funktion hinauszuschieben

  * Mit `window.open( )` ein neues Browserfenster öffnen

  * JavaScript in einem Frame verwenden, um mit einem anderen Frame zu interagieren

  * Mit den Einschränkungen klarkommen, die die Same Origin Policy mit sich bringt

Beispiel 14-7 ist ein Skript und ein einfaches HTML-Formular, das für die Verwendung in einem Dokument mit Frames gedacht ist. Es erzeugt eine einfache Navigationsleiste in einem Frame und verwendet diese Leiste, um den Inhalt eines anderen Frames zu steuern. Die Navigationsleiste schließt einen **Zurück** -Button, einen **Vor** -Button und ein Textfeld ein, in das der Benutzer eine URL eingeben kann. Sie können die Navigationsleiste am unteren Rand von Abbildung 14-4 sehen.

Abbildung 14-4. Eine Navigationsleiste

Beispiel 14-7 definiert JavaScript-Funktionen in einem `<script>`-Element und Buttons und ein Textfeld für die URL-Eingabe in einem `<form>`-Element. Es nutzt HTML-Event-Handler, um diese Funktionen aufzurufen, wenn die Buttons angeklickt werden. Event-Handler und HTML-Formulare habe ich noch nicht ausführlich behandelt. Aber diese Aspekte des Codes sind hier nicht wichtig. In diesem Beispiel werden Sie Anwendungen der Location- und History-Objekte sowie der Funktionen `setTimeout( )` und `Window.open( )` sehen. Sie werden sehen, wie der JavaScript-Code im Frame mit der Navigationsleiste über den Namen auf den anderen Frame verweist, und werden außerdem die Verwendung von `try/catch`-Blöcken an Orten sehen, wo die Same Origin Policy dafür verantwortlich sein könnte, dass der Code fehlschlägt.

Beispiel 14-7. Eine Navigationsleiste

    <!--
     Diese Datei implementiert eine Navigationsleiste, die in einen Frame gesteckt
     werden soll. Schließen Sie sie folgendermaßen in ein Frameset ein:

      <frameset rows="*,75">
        <frame src="about:blank" name="main">
        <frame src="navigation.html">
      </frameset>

     Der Code in dieser Datei steuert den Inhalt im Frame namens "main".
    -->
    <script>
    // Diese Funktion wird vom Zurück-Button der Navigationsleiste aufgerufen.
    function zurueck( ) {
        // Erst das Eingabefeld für die URL im Formular leeren.
        document.navbar.url.value = "";

        // Dann das History-Objekt des Haupt-Frames verwenden, um zurückzugehen.
        // Es sei denn, die Same Origin Policy kommt uns in die Quere.
        try { parent.main.history.back( ); }
        catch(e) { alert("Same Origin Policy blockiert History.back( ): " + e.message); }

        // Die URL des Dokuments anzeigen, zu dem wir zurückgegangen sind, wenn das möglich ist.
        // Dieser Aufruf von updateURL( ) muss hinausgezögert werden, damit er funktioniert.
        setTimeout(updateURL, 1000);
    }

    // Diese Funktion wird vom Vor-Button der Navigationsleiste aufgerufen.
    function vor( ) {
        document.navbar.url.value = "";
        try { parent.main.history.forward( ); }
        catch(e) { alert("Same Origin Policy blockiert History.forward( ): "+e.message);}
        setTimeout(updateURL, 1000);
    }

    // Diese private Funktion wird von back( ) und forward( ) eingesetzt, um das URL-Textfeld
    // des Formulars zu aktualisieren. In der Regel verhindert die Same Origin Policy, dass
    // wir die location-Eigenschaft des Haupt-Frames abfragen.
    function aktualisiereURL( ) {
        try { document.navbar.url.value = parent.main.location.href; }
        catch(e) {
            document.navbar.url.value = "<Same Origin Policy verhindert URL-Zugriff>";
        }
    }
    // Hilfsfunktion: Wenn die URL nicht mit "http://" beginnt, fügen wir das hinzu.
    function repariere(url) {
        if (url.substring(0,7) != "http://") url = "http://" + url;
        return url;
    }

    // Diese Funktion wird vom Gehe-Button der Navigationsleiste aufgerufen und auch,
    // wenn der Benutzer das Formular abschickt.
    function gehe( ) {
        // Die angegebene URL in den Haupt-Frame laden.
        parent.main.location = repariere(document.navbar.url.value);
    }

    // Ein neues Fenster anzeigen und die vom Benutzer angegebene URL in ihm anzeigen.
    function zeigeInNeuemFenster( ) {
        // Wir öffnen ein gewöhnliches, unbenanntes, vollständiges Fenster, deswegen müssen
        // wir nur das URL-Argument angeben. Nachdem das Fenster geöffnet wurde, hat unsere
        // Navigationsleiste keine Kontrolle mehr darüber.
        window.open(repariere(document.navbar.url.value));
    }
    </script>

    <!-- Hier ist das Formular mit den Event-Handlern, die die oben definierten Funktionen aufrufen. -->
    <form name="navbar" onsubmit="gehe( ); return false;">
         <input type="button" value="Zurück" onclick="zurueck( );">
         <input type="button" value="Vor" onclick="vor( );">
         URL: <input type="text" name="url" size="50">
         <input type="button" value="Gehe" onclick="gehe( );">
         <input type="button" value="In neuem Fenster öffnen" onclick="zeigeInNeuemFenster( );">
    </form>

# Kapitel 15. Dokumente skripten

Clientseitiges JavaScript dient dazu, statische HTML-Dokumente in interaktive Webanwendungen zu verwandeln. Das Scripting des Inhalts von Webseiten ist die Raison d'Être von JavaScript. Dieses Kapitel – das wichtigste in Teil II – erklärt, wie man das macht.

Jedes Fenster (oder jeder Frame) eines Webbrowsers zeigt ein HTML-Dokument an. Das Window-Objekt, das dieses Fenster repräsentiert, hat eine `document`-Eigenschaft, die auf ein Document-Objekt verweist. Dieses Document-Objekt ist der Gegenstand dieses Kapitels, das mit einer Betrachtung der Eigenschaften und Methoden des Document-Objekts selbst beginnt. Diese sind interessant, aber sie sind nur der Anfang.

Interessanter als das Document-Objekt selbst sind die Objekte, die den _Inhalt_ des Dokuments repräsentieren. HTML-Dokumente können Text, Bilder, Hyperlinks, Formularelemente und so weiter enthalten. JavaScript-Code kann auf die Objekte zugreifen, die die einzelnen Dokumentelemente repräsentieren, und sie manipulieren. Direkt auf die Objekte zugreifen zu können, die den Inhalt eines Dokuments repräsentieren, eröffnet sehr viele Möglichkeiten, aber das ist auch der Punkt, an dem die Sache beginnt, kompliziert zu werden.

Ein Document Object Model (Dokumentobjektmodell) oder DOM ist eine API, die definiert, wie auf die Objekte zugegriffen wird, aus denen ein Dokument besteht. Das W3C definiert ein Standard-DOM, das von allen modernen Webbrowsern hinreichend gut unterstützt wird. Unglücklicherweise war das nicht immer der Fall. Die Geschichte der clientseitigen JavaScript-Programmierung ist eigentlich die Geschichte der (manchmal auf inkompatible Weise erfolgenden) Evolution des DOMs. In den Anfangstagen des Webs war Netscape der führende Browserhersteller und definierte die APIs für das clientseitige Scripting. Netscape 2 und 3 unterstützten ein einfaches DOM, das nur Zugriff auf besondere Dokumentelemente wie Links, Grafiken und Formularelemente bot. Dieses Alt-DOM wurde von allen Browser-Herstellern übernommen und als »Level 0 DOM« formell in den W3C-Standard integriert. Das Alt-DOM funktioniert immer noch in allen Browsern, und es ist das, das ich zuerst behandeln werde.

Mit dem Internet Explorer 4 übernahm Microsoft die Kontrolle des Webs. IE 4 hatte ein revolutionäres neues DOM: Es bot Zugriff auf alle Elemente in einem Dokument und ermöglichte es Ihnen, diese auf interessante Weise zu skripten. Es ermöglicht Ihnen sogar, den Text eines Dokuments zu ändern, und lässt die Absätze des Dokuments neu einfließen, wenn das erforderlich ist. Microsofts API ist als das IE 4 DOM bekannt. Es wurde nie standardisiert, und der IE 5 und spätere Versionen übernahmen das W3C DOM, bewahrten aber Unterstützung für das IE 4 DOM. Teile des IE 4 DOMs wurden von anderen Browsern übernommen und werden im Web immer noch verwendet. Ich werde es am Ende des Kapitels erklären, nachdem ich die standardmäßige Alternative behandelt habe.

Netscape 4 wählte einen ganz anderen Ansatz für das DOM, der auf dynamisch positionierbaren skriptbaren Elementen basierte, die als _Layer_ bezeichnet wurden. Dieses Netscape 4 DOM war eine Evolutionssackgasse. Es wurde nur in Netscape 4 unterstützt und in den Mozilla- und Firefox-Browsern fallengelassen, die sich aus der Netscape-Codebasis entwickelten. Die Behandlung des Netscape 4 DOMs wurde in dieser Auflage aus dem Buch entfernt.

Der Hauptteil dieses Kapitels behandelt den W3C DOM-Standard. Beachten Sie allerdings, dass ich hier nur die Kernteile des Standards vorstelle. Das Scripting von Dokumentinhalten ist der Hauptgrund für clientseitiges JavaScript, und die meisten nachfolgenden Kapitel dieses Buchs sind eigentlich Fortsetzungen dieses Kapitels. Kapitel 16 behandelt den W3C DOM-Standard für die Arbeit mit CSS-Styles und -Stylesheets. Kapitel 17 behandelt den W3C DOM-Standard für das Event-Handling (sowie die alten und IE-spezifischen Techniken, diese Dinge zu erledigen). Kapitel 18 erklärt das DOM für die Interaktion mit HTML-Formularelementen, und Kapitel 22 erklärt, wie man die `<img>`-Elemente eines HTML-Dokuments skriptet und clientseitigen Webseiten geskriptete Grafiken hinzufügt.

Das Level 0 DOM definiert nur eine einzige Document-Klasse, und dieses Kapitel verweist häufig informell als Document-Objekt darauf. Das W3C DOM definiert jedoch eine Document API, die eine allgemeine Dokumentfunktionalität stellt, die auf HTML- und XML-Dokumente anwendbar ist, und definiert daneben eine spezialisierte HTMLDocument API, die HTML-spezifische Eigenschaften und Methoden hinzufügt. Das Referenzmaterial in Teil IV dieses Buchs folgt der W3C-Konvention: Wenn Sie nach HTML-spezifischen Document-Features suchen, schauen Sie unter HTMLDocument nach. Die meisten der Features von Level 0 DOM sind HTML-spezifisch und müssen unter HTMLDocument nachgeschlagen werden, auch wenn dieses Kapitel auf sie als Eigenschaften und Methoden von Document verweist.

# 15.1 Dynamischer Dokumentinhalt

Beginnen wir die Erforschung des Document-Objekts mit seiner `write( )`-Methode, die es Ihnen ermöglicht, Inhalt in das Dokument zu schreiben. Diese Methode ist Teil des Alt-DOMs und existiert seit den frühesten Versionen von JavaScript. `document.write( )` kann auf zweierlei Weise verwendet werden. Die erste und einfachste Möglichkeit ist die Verwendung in einem Skript, um HTML in das Dokument auszugeben, das gerade geparst wird. Betrachten Sie den folgenden Code, der `write( )` einsetzt, um einem ansonsten statischen HTML-Dokument das aktuelle Datum hinzuzufügen:

    <script>
     var heute = new Date( );
     document.write("<p>Dokumentzugriff um: " + heute.toString( ));
    </script>

Achten Sie jedoch darauf, dass Sie mit der Methode `write( )` nur HTML in das aktuelle Dokument ausgeben können, während das Dokument geparst wird. Das heißt, dass Sie `document.write( )` nur aus Toplevel-Code in `<script>`-Tags aufrufen können, weil diese Skripten als Teil des Parsing-Vorgangs für das Dokument ausgeführt werden. Wenn Sie einen `document.write( )`-Aufruf in eine Funktionsdefinition packen und diese Funktion dann von einem Event-Handler aus ausrufen, funktioniert das nicht wie erwartet – stattdessen löschen Sie das aktuelle Dokument und das Skript, das es enthält! (Warum das passiert, werden Sie in Kürze sehen.)

`document.write( )` fügt an der Stelle Text ein HTML-Dokument ein, an der sich das `<script>`-Tag befindet, das den Aufruf der Methode enthält. Wenn ein `<script>`-Tag das Attribut `defer` trägt, darf es überhaupt keine Aufrufe von `document.write( )` enthalten. Das Attribut `defer` sagt dem Webbrowser, dass es sicher ist, die Ausführung des Skripts so lange aufzuschieben, bis das Dokument vollständig geladen wurde. Und wenn das passiert ist, ist es zu spät, um mit `document.write( )` Inhalt in das Dokument einzufügen, während es geparst wird.

Mit der Methode `write( )` Dokumentinhalt zu generieren, während das Dokument geparst wird, war eine sehr verbreitete JavaScript-Programmiertechnik. Das W3C DOM ermöglicht es Ihnen jetzt (unter Einsatz von Techniken, die Sie später kennenlernen werden), in einen beliebigen Teil des Dokuments Inhalte einzufügen, nachdem es geparst wurde. Trotzdem ist die Verwendung von `document.write( )` immer noch sehr verbreitet.

Sie können die `write( )`-Methode (in Verbindung mit den `open( )`\- und `close( )`-Methoden) des Document-Objekts auch verwenden, um vollständig neue Dokumente in anderen Fenstern oder Frames zu erzeugen. Obwohl Sie aus einem Event-Handler heraus nicht brauchbar in das aktuelle Dokument schreiben können, gibt es keinen Grund, nicht in andere Fenster oder Frames schreiben zu können. Beispielsweise könnten Sie ein Pop-up-Fenster erzeugen und mit Code wie dem folgenden etwas HTML in es schreiben:

    // Diese Funktion öffnet ein Pop-up-Fenster. Rufen Sie sie aus einem Event-Handler
    // auf, da das Pop-up ansonsten wahrscheinlich blockiert wird.
    function hallo( ) {
        var w = window.open( );                       // Ein neues Fenster ohne Inhalt erzeugen
        var d = w.document;                           // Sein Document-Objekt abrufen
        d.open( );                                    // Ein neues Dokument beginnen (optional)
        d.write("<h1>Hallo Welt!</h1>");  // Den Dokumentinhalt ausgeben
        d.close( );                                   // Das Dokument beenden
    }

Um ein neues Dokument zu erzeugen, rufen Sie zunächst die `open( )`-Methode des Document-Objekts auf. Dann rufen Sie beliebig oft `write( )` auf, um den Inhalt des Dokuments zu erzeugen. Abschließend rufen Sie die `close( )`-Methode des Document-Objekts auf, um anzuzeigen, dass Sie fertig sind. Dieser letzte Schritt ist wichtig. Wenn Sie vergessen, das Dokument zu schließen, beendet der Browser die Animation nicht, die er anzeigt, wenn er ein Dokument lädt. Außerdem kann es sein, dass der Browser das HTML puffert, das Sie geschrieben haben. Es ist nicht erforderlich, dass die gepufferte Ausgabe angezeigt wird, bis Sie das Dokument explizit beenden, indem Sie `close( )` aufrufen.

Im Unterschied zum `close( )`-Aufruf, der erforderlich ist, ist der `open( )`-Aufruf optional. Wenn Sie die Methode `write( )` auf einem Dokument aufrufen, das bereits geschlossen wurde, öffnet JavaScript implizit ein neues HTML-Dokument, als hätten Sie die Methode `open( )` aufgerufen. Das erklärt, was passiert, wenn Sie `document.write( )` aus einem Event-Handler im gleichen Dokument aufrufen: JavaScript öffnet ein neues Dokument. Bei diesem Vorgang wird das aktuelle Dokument (und sein Inhalt einschließlich Skripten und Event-Handlern) aber verworfen. Eine allgemeine Faustregel ist deswegen, dass ein Dokument auf sich selbst nie `write( )` aus einem Event-Handler aufrufen soll.

Ein paar abschließende Bemerkungen zur `write( )`-Methode. Erstens wissen viele Leute nicht, dass die Methode `write( )` mehrere Argumente akzeptiert. Wenn Sie mehrere Argumente übergeben, werden diese nacheinander ausgegeben – so, als wären sie verkettet worden. Statt

    document.write("Hallo, " + benutzer + " Willkommen bei meinem Blog!");

könnten Sie auch Folgendes schreiben:

    var begruessung = "Hallo, ";
    var willkommen = " Willkommen bei meinem Blog!";
    document.write(begruessung, benutzer, willkommen);

Der zweite beachtenswerte Punkt zur Methode `write( )` ist, dass das Document-Objekt auch eine `writeln( )`-Methode unterstützt, die mit der `write( )`-Methode in jeder Hinsicht identisch ist, allerdings nach Ausgabe der Argumente einen Zeilenumbruch anhängt. Das kann beispielsweise hilfreich sein, wenn man in einem `<pre>`-Element vorformatierten Text ausgibt.

Bei HTMLDocument in Teil IV finden Sie vollständige Informationen zu den Methoden `write( )`, `writeln( )`, `open( )` und `close( )`.

# 15.2 Document-Eigenschaften

Nachdem wir uns die Alt-Methoden des Document-Objekts angesehen haben, wenden wir uns jetzt seinen Alt-Eigenschaften zu:

`bgColor`

Die Hintergrundfarbe des Dokuments. Diese Eigenschaft entspricht dem (veralteten) `bgcolor`-Attribut des `<body>`-Tags.

`cookie`

Eine besondere Eigenschaft, die es JavaScript-Programmen ermöglicht, HTTP-Cookies zu lesen und zu schreiben. Diese Eigenschaft ist Gegenstand eines eigenen Kapitels, Kapitel 19.

`domain`

Eine Eigenschaft, die es sich gegenseitig vertrauenden Webservern in der gleichen Internet-Domain ermöglicht, gemeinsam die Same Origin Policy-Sicherheitseinschränkung für Interaktionen zwischen ihren Webseiten zu lockern (siehe "13.8.2 Die Same Origin Policy").

`lastModified`

Ein String, der das Veränderungsdatum des Dokuments enthält.

`location`

Ein veraltetes Synonym für die Eigenschaft `URL`.

`referrer`

Die URL des Dokuments, das einen eventuellen Link enthielt, über den der Browser zum aktuellen Dokument gelangt ist.

`title`

Der Text zwischen den `<title>`\- und `</title>`-Tags dieses Dokuments.

`URL`

Ein String, der die URL angibt, von der das Dokument geladen wurde. Der Wert dieser Eigenschaft entspricht dem Wert der `location.href`-Eigenschaft des Window-Objekts, es sei denn, es fand eine Serverumleitung statt.

Einige dieser Eigenschaften bieten Informationen zum Dokument als Ganzem. Sie können Code wie den folgenden an das Ende jeder Ihrer Webseiten setzen, um eine nützliche automatische Fußzeile zu erzeugen, die es Benutzern ermöglicht zu beurteilen, wie aktuell (oder veraltet) ein Dokument ist, wenn es gedruckt wird:

    <hr><font size="1">
     Dokument: <i><script>document.write(document.title);</script></i><br>
     URL: <i><script>document.write(document.URL);</script></i><br>
     Letzte Aktualisierung: <i><script>document.write(document.lastModified);</script></i>
    </font>

`referrer` ist eine weitere interessante Eigenschaft: Sie enthält die URL des Dokuments, von dem der Benutzer zum aktuellen Dokument gelangt ist. Sie können diese Eigenschaft nutzen, um tiefe Links in Ihre Site zu verhindern. Wenn Sie wollen, dass all Ihre Besucher über Ihre Homepage ankommen, können Sie sie umleiten, indem Sie an den Anfang aller Seiten außer der Homepage folgenden Code stellen:

    <script>
    // Bei einer Verlinkung durch eine externe Seite erst zur Homepage gehen
    if (document.referrer == "" || document.referrer.indexOf("meinesite.com") == −1)
        window.location = "http://home.meinesite.com";
    </script>

Sie dürfen diesen Trick natürlich nicht als eine Sicherheitsmaßnahme betrachten. Offensichtlicherweise funktioniert er bei Benutzern nicht, die in ihren Webbrowsern JavaScript deaktiviert haben.

Eine letzte interessante Document-Eigenschaft ist `bgColor`. Diese Eigenschaft entspricht einem HTML-Attribut, dessen Verwendung als veraltet gilt, hier aus historischen Gründen aber erwähnt wird. Die Änderung der Hintergrundfarbe eines Dokuments war eine der ersten Anwendungen von clientseitigem JavaScript. Selbst sehr, sehr alte Webbrowser ändern die Hintergrundfarbe eines Dokuments, wenn Sie `document.bgColor` auf einen HTML-Farbstring wie »pink« oder »#FFAAAA« setzen.

Bei HTMLDocument in Teil IV finden Sie vollständige Informationen zu den Alt-Eigenschaften des Document-Objekts.

Das Document-Objekt hat noch weitere wichtige Eigenschaften, deren Werte Arrays mit Dokumentobjekten sind. Diese Document-Sammlungen sind Gegenstand des nächsten Abschnitts.

# 15.3 Alt-DOM: Dokumentobjekt-Sammlungen

Die Liste der Eigenschaften des Document-Objekts im vorangehenden Abschnitt hat eine wichtige Kategorie von Eigenschaften ausgespart: Dokumentobjekt-Sammlungen. Die Eigenschaften mit Array-Werten sind das Herz des Alt-DOMs. Das sind die Eigenschaften, die Ihnen Zugriff auf besondere Elemente im Dokument geben:

`anchors[]`

Ein Array mit Anchor-Objekten, die die Anker in einem Dokument repräsentieren. (Ein _Anker_ ist eine benannte Position im Dokument. Sie wird mit einem `<a>`-Tag erzeugt, das ein `name`-Attribut anstelle eines `href`-Attributs hat.) Die `name`-Eigenschaft eines Anchor-Objekts enthält den Wert des `name`-Attributs. In Teil IV finden Sie vollständige Informationen zum Anchor-Objekt.

`applets[]`

Ein Array mit Applet-Objekten, die die Java-Applets im Dokument repräsentieren. Applets werden in Kapitel 23 betrachtet.

`forms[]`

Ein Array mit Form-Objekten, die die `<form>`-Elemente im Dokument repräsentieren. Jedes Form-Objekt hat eine eigene Sammlungseigenschaft namens `elements]`, die Objekte enthält, die die Formularelemente repräsentieren, die im Formular enthalten sind. Form-Objekte lösen einen `onsubmit`-Event-Handler aus, bevor das Formular abgeschickt wird. Dieser Handler kann eine clientseitige Formularvalidierung durchführen: Liefert er `false` zurück, schickt der Browser das Formular nicht ab. Die `form[]`-Sammlung ist mit Sicherheit die wichtigste des Alt-DOMs, und Formulare und Formularelemente sind Gegenstand von [Kapitel 18.

`images[]`

Ein Array mit Image-Objekten, die die `<img>`-Elemente im Dokument repräsentieren. Die `src`-Eigenschaft eines Images kann gelesen und geschrieben werden. Wird dieser Eigenschaft eine URL zugewiesen, veranlasst das den Browser, diese neue Grafik zu lesen und anzuzeigen (in älteren Browsern muss die neue Grafik die gleiche Größe haben wie die alte). Das Scripting der `src`-Eigenschaft eines Image-Objekts ermöglicht Rollover-Effekte und einfache Grafiken. Sie werden in Kapitel 22 behandelt.

`links[]`

Ein Array mit Link-Objekten, die die Hyperlinks im Dokument repräsentieren. Hyperlinks werden in HTML mit `<a>`-Tags und gelegentlich mit `<area>`-Tags in clientseitigen Imagemaps erzeugt. Die `href`-Eigenschaft eines Link-Objekts entspricht dem `href`-Attribut des `<a>`-Tags: Sie enthält die URL des Links. Link-Objekte machen über verschiedene Eigenschaften wie `protocol`, `hostname` und `pathname` auch die verschiedenen Teile einer URL verfügbar. In dieser Hinsicht entsprechen Link-Objekte dem Location-Objekt, das in Kapitel 14 behandelt wurde. Ein Link-Objekt löst einen `onmouseover`-Event-Handler aus, wenn der Mauszeiger über ihm schwebt, und einen `onmouseout`-Handler, wenn sich der Mauszeiger von ihm wegbewegt. Es löst einen `onclick`-Handler aus, wenn über einem Link die Maus geklickt wird. Wenn der Event-Handler `false` zurückliefert, folgt der Browser dem Link nicht. Vollständige Informationen zu Link-Objekten finden Sie in Teil IV.

Wie ihre Namen anzeigen, sind diese Eigenschaften Sammlungen aller Links, Grafiken, Formulare usw., die in einem Dokument erscheinen. Ihre Elemente stehen in der gleichen Reihenfolge wie im Quellcode des Dokuments. `document.forms[0]` verweist zum Beispiel auf das erste `<form>`-Element in einem Dokument und `document.images[4]` auf das fünfte `<img>`-Element.

Die Objekte, die in diesen Alt-DOM-Sammlungen enthalten sind, können geskriptet werden. Aber es ist wichtig, dass man versteht, dass keines von ihnen es ermöglicht, die _Struktur_ des Dokuments zu ändern. Sie können Linkziele untersuchen und ändern, die Werte von Formularelementen lesen und schreiben und sogar eine Grafik gegen eine andere austauschen, aber den Text des Dokuments können Sie nicht ändern. Ältere Browser wie Netscape 2, 3 und 4 und IE 3 waren nicht dazu in der Lage, ein Dokument neu einfließen zu lassen (oder neu aufzubauen), nachdem es einmal geparst und angezeigt wurde. Aus diesem Grund ließ (und lässt) das Alt-DOM keine Dokumentänderungen zu, die einen Neuaufbau des Dokuments erforderlich machen. Beispielsweise schließt das Alt-DOM eine API für das Hinzufügen neuer `<option>`-Elemente zu einem `<select>`-Element ein. Das kann es, weil HTML-Formulare `<select>`-Elemente als Aufklappmenüs anzeigen und sich der Aufbau des restlichen Formulars nicht ändert, wenn solchen Menüs neue Elemente hinzugefügt werden. Es gibt im Alt-DOM aber keine API, die es ermöglicht, einem Formular einen neuen Radiobutton oder einer Tabelle eine neue Zeile hinzuzufügen, weil derartige Änderungen einen Neuaufbau des Dokuments erforderlich machen würden.

## 15.3.1 Dokumentobjekten Namen geben

Das Problem mit numerisch indizierten Dokumentobjekt-Sammlungen ist, dass ihre positionsbasierten Indizes etwas zerbrechlich sind: Kleine Dokumentänderungen, die die Ordnung der Elemente im Dokument ändern, können Code zerbrechen, der sich auf ihre Ordnung stützt. Eine robustere Lösung ist es, wichtigen Dokumentelementen Namen zuzuweisen und auf diese Elemente über den Namen zu verweisen. Im Alt-DOM können Sie das über die `name`-Attribute von Formularen, Formularelementen, Grafiken, Applets und Links machen.

Wenn das Attribut `name` vorhanden ist, wird sein Wert verwendet, um das entsprechende Objekt über den Namen zu veröffentlichen. Nehmen Sie beispielsweise an, ein HTML-Dokument enthält das folgende Formular:

    <form name="f1"><input type="button" value="Klick mich"></form>

Wenn wir hier annehmen, dass das `<form>` das erste im Dokument ist, kann Ihr JavaScript-Code auf das resultierende Form-Objekt mit jedem der drei folgenden Ausdrücke verweisen:

    document.forms[0]   // Auf das Formular über die Position im Dokument verweisen
    document.forms.f1   // Auf das Formular über den Namen als Eigenschaft verweisen
    document.forms["f1"] // Auf das Formular über den Namen als Array-Index verweisen

Tatsächlich ist es sogar so, dass ein Form-, Image- oder Applet-Objekt (aber nicht Link- oder Anchor-Objekt) als benannte Eigenschaft des Dokuments selbst verfügbar ist, wenn die `name`-Eigenschaft des entsprechenden `<form>`-, `<img>`\- oder `<applet>`-Objekts gesetzt wird. Sie können auf das Formular also auch so verweisen:

    document.f1

Den Elementen in einem Formular können ebenfalls Namen gegeben werden. Wenn Sie das `name`-Attribut eines Formularelements setzen, wird das Objekt, das es repräsentiert, als Eigenschaft des Form-Objekts selbst verfügbar gemacht. Nehmen Sie an, Sie haben ein Formular, das so aussieht:

    <form name="lieferung">
     ...
     <input type="text" name="plz">
     ...
    </form>

Auf das Texteingabefeld-Element können Sie dann mit dieser intuitiven Syntax verweisen:

    document.lieferung.plz

Eine abschließende Bemerkung zur Bennenung von Dokumentelementen ist unter dem Alt-DOM erforderlich: Was passiert, wenn zwei Dokumentelemente `name`-Attribute mit dem gleichen Wert haben? Wenn ein `<form>`\- und `<img>`-Tab beispielsweise beide den `name`-Wert »n« haben, wird die Eigenschaft `document.n` zu einem Array, das Referenzen auf beide Elemente enthält.

Üblicherweise sollten Sie sich Mühe geben sicherzustellen, dass Ihre `name`-Attribute eindeutig sind, damit diese Situation nicht aufkommen kann. Sie ist in einem Fall aber verbreitet. Bei HTML-Formularen verlangt die Konvention, dass verwandte Gruppen von Radiobuttons und Checkboxes den gleichen Namen haben. Wenn Sie das tun, wird dieser Name zu einer Eigenschaft des Form-Objekts, das diese Elemente enthält, und der Wert dieser Eigenschaft ist ein Array, das Referenzen auf die verschiedenen Radiobutton- oder Checkbox-Objekte enthält. Mehr dazu werden Sie in Kapitel 18 lernen.

## 15.3.2 Event-Handler auf Document-Objekten

Um interaktiv zu sein, müssen ein HTML-Dokument und die Elemente in ihm auf Benutzerereignisse reagieren. In Kapitel 13 haben Sie schon etwas zu Event-Handlern und Events gelernt, und Sie haben eine Reihe von Beispielen gesehen, die einfache Event-Handler verwenden. In diesem Kapitel gibt es viele weitere Beispiele von Event-Handlern, weil sie die Verknüpfung zwischen Dokumentelementen und JavaScript-Code sind.

Eine vollständige Diskussion von Events und Event-Handlern hebe ich mir bis Kapitel 17 auf. Denken Sie jetzt nur daran, dass Event-Handler durch Attribute von HTML-Elementen wie `onclick` und `onmouseover` definiert werden. Die Werte dieser Attribute sollten Strings mit JavaScript-Code sein. Dieser Code wird ausgeführt, wenn das angegebene Event auf dem HTML-Element auftritt.

Dokumentobjekte, auf die über Sammlungen wie `document.links` zugegriffen wird, haben Eigenschaften, die den Attributen des HTML-Tags entsprechen. Ein Link-Objekt hat beispielsweise eine `href`-Eigenschaft, die das `href`-Attribut des `<a>`-Tag widerspiegelt. Das gilt auch für Event-Handler. Sie können einen `onclick`-Event-Handler für einen Hyperlink definieren, indem Sie das `onclick`-Attribut auf dem `<a>`-Tag setzen oder indem Sie die `onclick`-Eigenschaft des Link-Objekts definieren. Betrachten Sie als weiteres Beispiel das `onsubmit`-Attribut des `<form>`-Elements. In JavaScript hat ein Form-Objekt eine entsprechende `onsubmit`-Eigenschaft. (Denken Sie daran, dass HTML keine Groß-/Kleinschreibung berücksichtigt und Attribute in Großbuchstaben, Kleinbuchstaben oder einer Mischung von beidem geschrieben werden können. In JavaScript müssen alle Event-Handler-Eigenschaften in Kleinbuchstaben geschrieben werden.)

In HTML werden Event-Handler definiert, indem einem Event-Handler-Attribut ein String mit JavaScript-Code zugewiesen wird. Aber in JavaScript werden sie definiert, indem einer Event-Handler-Eigenschaft eine Funktion zugewiesen wird. Betrachten Sie das folgende `<form>` und seinen `onsubmit`-Event-Handler:

    <form name="meinformular" onsubmit="return validiereFormular( );">...</form>

Anstatt einen einfachen String mit JavaScript-Code zu verwenden, der eine Funktion aufruft und ihr Ergebnis zurückliefert, weisen Sie in JavaScript die Funktion direkt folgendermaßen der Event-Handler-Eigenschaft zu:

    document.meinformular.onsubmit = validiereFormular;

Beachten Sie, dass auf den Funktionsnamen keine Klammern folgen. Das liegt daran, dass Sie die Funktion hier nicht aufrufen wollen. Sie wollen hier nur eine Referenz darauf zuweisen.

Eine vollständige Betrachtung von Event-Handlern finden Sie in Kapitel 17.

## 15.3.3 Alt-DOM-Beispiele

Beispiel 15-1 zeigt eine `listeAnkerAuf( )`-Funktion, die ein neues Fenster öffnet und `document.write( )` einsetzt, um eine Liste mit allen Ankern im Ausgangsdokument auszugeben. Jeder Eintrag in der Liste ist ein Link mit einem Event-Handler, der bewirkt, dass das Fenster zum Ort des angegebenen Ankers scrollt. Der Code in diesem Beispiel ist besonders nützlich, wenn Sie Ihre HTML-Dokumente so schreiben, dass alle Abschnittsüberschriften in Anker wie diesen eingeschlossen sind:

    <a name="sect14.6"><h2>Das Anchor-Objekt</h2></a>

Beachten Sie, dass die Funktion `listeAnkerAuf( )` die Methode `Window.open( )` verwendet. Wie in Kapitel 14 gezeigt wurde, blockieren Browser üblicherweise Pop-up-Fenster, es sei denn, sie werden als Antwort auf eine Benutzeraktion geöffnet. Rufen Sie `listeAnkerAuf( )` deswegen auf, wenn der Benutzer auf einen Link oder einen Button klickt. Aber tun Sie es nicht automatisch, wenn Ihre Webseite geladen wird.

Beispiel 15-1. Alle Anker auflisten

    /*
     * listeAnkerAuf.js: Ein einfaches Inhaltsverzeichnis mit document.anchors[] erstellen.
     *
     * Der Funktion listeAnkerAuf( ) wird als Argument ein Dokument übergeben, und sie öffnet
     * ein neues Fenster, das als "Navigationsfenster" für dieses Dokument dient. Das neue
     * Fenster zeigt eine Liste aller Anker des Dokuments an. Klicken Sie auf einen Anker
     * in der Liste, scrollt das Dokument zu diesem Anker.
     */
    function listeAnkerAuf(d) {
        // Ein neues Fenster öffnen.
        var neuesfenster = window.open("", "navfenster",
                                 "menubar=yes,scrollbars=yes,resizable=yes," +
                                 "width=500,height=300");

        // Ihm einen Titel geben.
        neuesfenster.document.write("<h1>Navigationsfenster: " + d.title + "</h1>");

        // Alle Anker auflisten.
        for(var i = 0; i < d.anchors.length; i++) {
            // Für jedes Anchor-Objekt den anzuzeigenden Titel ermitteln.
            // Erst versuchen, den Text zwischen <a> und </a> über eine browserunabhängige
            // Eigenschaft abzurufen. Wird so kein Titel erhalten, wird stattdessen der Name
            // verwendet.
            var a = d.anchors[i];
            var text = null;
            if (a.text) text = a.text;                      // Netscape 4
            else if (a.innerText) text = a.innerText;       // IE 4+
            if ((text == null) || (text == '')) text = a.name;  // Default

            // Jetzt diesen Text als Hyperlink ausgeben. Die href-Eigenschaft des Links
            // wird nie verwendet: Der onclick-Handler erledigt die Arbeit, indem er die
            // location.hash-Eigenschaft das Ausgangsfensters so setzt, dass dieses
            // Fenster den angegebenen Anker anzeigt. Siehe Window.opener,
            // Window.location und Location.hash und Link.onclick.
            neuesfenster.document.write('<a href="#' + a.name + '"' +
                                  ' onclick="opener.location.hash=\'' + a.name +
                                  '\'; return false;">');
            neuesfenster.document.write(text);
            neuesfenster.document.write('</a><br>');
      }
      neuesfenster.document.close( ); // Vergessen Sie nie, das Dokument zu schließen!
    }

# 15.4 Überblick über das W3C DOM

Nachdem wir uns das einfache Alt-DOM angesehen haben, können wir uns jetzt dem mächtigeren und standardisierten W3C DOM zuwenden, das es erweitert und ersetzt. Die API des W3C DOMs ist nicht sonderlich kompliziert, aber bevor wir uns ansehen, wie man mit dem DOM programmiert, sollten wir einige Punkte erwähnen, die Sie in Bezug auf die DOM-Architektur verstehen müssen.

## 15.4.1 Dokumente als Bäume darstellen

HTML-Dokumente haben eine hierarchische Struktur eingebetteter Tags, die im DOM als ein Baum von Objekten dargestellt werden. Die Baumdarstellung eines HTML-Dokuments enthält Knoten, die HTML-Tags oder -Elemente wie `<body>` und `<p>` repräsentieren, und Knoten, die Textstrings repräsentieren. Ein HTML-Dokument kann auch Knoten enthalten, die HTML-Kommentare repräsentieren.[53] Betrachten Sie folgendes einfaches HTML-Dokument:

    <html>
     <head>
      <title>Beispieldokument</title>
     </head>
     <body>
      <h1>Ein HTML-Dokument</h1>
      <p>Dies ist ein <i>einfaches</i> Dokument.
     </body>
    </html>

Die DOM-Darstellung dieses Dokuments entspricht dem Baum, der in Abbildung 15-1 gezeigt wird.

Abbildung 15-1. Die Baumdarstellung eines HTML-Dokuments

Wenn Sie mit der Baumstruktur in der Computer-Programmierung noch nicht vertraut sind, ist es hilfreich zu wissen, dass sie ihre Terminologie von Stammbäumen ausleiht. Der Knoten direkt über einem Knoten ist der _Elternknoten_ dieses Knotens. Die Knoten auf einer Stufe direkt unter einem anderen Knoten sind die _Kindknoten_ dieses Knotens. Knoten auf der gleichen Ebene und mit dem gleichen Elternknoten sind _Geschwister_. Die Menge der Knoten, die sich auf irgendeiner Ebene unterhalb eines anderen Knotens befinden, sind die _Nachfahren_ dieses Knotens. Und der Elternknoten, die Großelternknoten und alle anderen Knoten über einem Knoten sind die _Vorfahren_ dieses Knotens.

## 15.4.2 Knoten

Die DOM-Baumstruktur, die in Abbildung 15-1 illustriert wird, wird als ein Baum unterschiedlicher Typen von Node-Objekten dargestellt. Die Node-Schnittstelle[54] definiert Eigenschaften und Methoden für die Durchquerung und Manipulation des Baums. Die `childNodes`-Eigenschaft eines Node-Objekts liefert die Liste der Kindknoten des Knotens zurück, und die Eigenschaften `firstChild`, `lastChild`, `nextSibling`, `previousSibling` und `parentNode` bieten eine Möglichkeit, den Knotenbaum zu durchqueren. Methoden wie `appendChild( )`, `removeChild( )`, `replaceChild( )` und `insertBefore( )` ermöglichen es Ihnen, dem Dokumentbaum Knoten hinzuzufügen oder Knoten aus dem Dokumentbaum zu entfernen. Beispiele für die Verwendung dieser Eigenschaften und Methoden sehen Sie später in diesem Kapitel.

### 15.4.2.1 Typen von Knoten

Verschiedene Typen von Knoten im Dokumentbaum werden durch spezifische Subinterfaces von Node repräsentiert. Jedes Node-Objekt hat eine `nodeType`-Eigenschaft, die angibt, um was für eine Art von Knoten es sich handelt. Wenn die `nodeType`-Eigenschaft eines Knotens der Konstanten `Node.ELEMENT_NODE` entspricht, wissen Sie beispielsweise, dass das Node-Objekt auch ein Element-Objekt ist, und können auf ihm alle Methoden und Eigenschaften verwenden, die das Interface Element definiert. Tabelle 15-1 führt alle Knotentypen auf, auf die man häufiger in HTML-Dokumenten stößt, und gibt für jeden den `nodeType`-Wert an.

Tabelle 15-1. Häufige Knotentypen

Interface | nodeType-Konstante | nodeType-Wert

---|---|---

Element | `Node.ELEMENT_NODE` | 1

Text | `Node.TEXT_NODE` | 3

Document | `Node.DOCUMENT_NODE` | 9

Comment | `Node.COMMENT_NODE` | 8

DocumentFragment | `Node.DOCUMENT_FRAGMENT_NODE` | 11

Attr | `Node.ATTRIBUTE_NODE` | 2

Das Node-Objekt an der Wurzel des DOM-Baums ist ein Document-Objekt. Die `documentElement`-Eigenschaft dieses Objekts verweist auf ein Element-Objekt, das das Wurzelelement des Dokuments repräsentiert. Bei HTML-Dokumenten ist das das `<html>`-Element, das entweder explizit oder implizit im Dokument enthalten ist. (Der Document-Knoten kann neben dem Wurzelelement noch weitere Kinder wie Comment-Knoten enthalten.) In HTML-Dokumenten interessieren Sie sich üblicherweise mehr für das `<body>`-Element als für das `<html>`-Element. Als Abkürzung können Sie `document.body` verwenden, um auf dieses Element zu verweisen.

In einem DOM-Baum gibt es nur ein Document-Objekt. Die meisten Knoten im Baum sind Element-Objekte, die durch Tags wie `<html>` und `<i>` repräsentiert werden, und Text-Objekte, die Strings mit Text repräsentieren. Wenn der Dokument-Parser Kommentare bewahrt, werden diese Kommentare im DOM-Baum durch Comment-Objekte repräsentiert. Abbildung 15-2 zeigt eine partielle Klassenhierarchie für diese und andere DOM-Kern-Interfaces.

Abbildung 15-2. Eine partielle Klassenhierarchie der Core DOM API

### 15.4.2.2 Attribute

Die Attribute eines Elements (wie die `src`\- und `width`-Attribute eines `<img>`-Tags) können mit den Methoden `getAttribute( )`, `setAttribute( )` und `removeAttribute( )` des Element-Interfaces abgefragt, gesetzt und entfernt werden. Wie später gezeigt wird, sind die Standard-Attribute von HTML auch als Eigenschaften der Element-Knoten verfügbar, die diese Tags repräsentieren.

Ein anderer, etwas unbequemerer Weg, mit Attributen zu arbeiten, ist die Methode `getAttributeNode( )`, die ein Attr-Objekt zurückliefert, das ein Attribut und seinen Wert repräsentiert. (Ein Grund dafür, diese etwas unbequemere Technik zu verwenden, besteht darin, dass das Attr-Interface eine `specified`-Eigenschaft definiert, mit der Sie ermitteln können, ob das Attribut literal im Dokument angegeben wurde oder ob sein Wert ein Default-Wert ist.) Das Attr-Interface erscheint in Abbildung 15-2 und ist ein Knotentyp. Beachten Sie allerdings, dass Attr-Objekte nicht im `childNodes[]`-Array eines Elements erscheinen und nicht auf dieselbe Weise direkt Teil des Dokumentbaums sind, wie es Element- und Text-Knoten sind. Die DOM-Spezifikation erlaubt, dass auf Attr-Knoten über das `attributes[]`-Array des Node-Interfaces zugegriffen wird, aber der Internet Explorer definiert ein anderes und inkompatibles `attributes[]`-Array, das es unmöglich macht, dieses Feature portabel zu verwenden.

## 15.4.3 Die DOM HTML API

Der DOM-Standard wurde gleichermaßen für die Verwendung mit XML- und HTML-Dokumenten entwickelt. Die Core DOM API – die Interfaces Node, Element, Document und weitere Interfaces – ist relativ allgemein und auf beide Typen von Elementen anwendbar. Der DOM-Standard schließt auch Interfaces ein, die spezifisch für HTML-Dokumente sind. Wie Sie in Abbildung 15-2 sehen können, ist HTMLDocument ein HTML-spezifisches Subinterface von Document und HTMLElement ein HTML-spezifisches Subinterface von Element. Weiterhin definiert das DOM tag-spezifische Interfaces für viele HTML-Elemente. Die tag-spezifischen Interfaces wie HTMLBodyElement und HTMLTitleElement definieren üblicherweise einen Satz von Eigenschaften, die die Attribute des HTML-Tags widerspiegeln.

Das HTMLDocument-Interface definiert verschiedene Dokumenteigenschaften und -methoden, die von Browsern vor der W3C-Standardisierung unterstützt wurden. Dazu zählen die `location`-Eigenschaft, das `forms[]`-Array und die `write( )`-Methode, die weiter oben in diesem Kapitel beschrieben wurden.

Das HTMLElement-Interface definiert die Eigenschaften `id`, `style`, `title`, `lang`, `dir` und `className`. Diese Eigenschaften ermöglichen einen bequemen Zugriff auf die Werte der Attribute `id`, `style`, `title`, `lang`, `dir` und `class`, die in HTML-Tags zulässig sind. (»class« ist in JavaScript ein reserviertes Wort; das HTML-Attribut `class` wird in JavaScript also zur Eigenschaft `className`.) Einige, in Tabelle 15-2 aufgeführte HTML-Tags akzeptieren keine anderen Attribute als diese sechs und werden durch das Interface HTMLElement deswegen vollständig dargestellt.

Tabelle 15-2. Einfache HTML-Tags

`<abbr>` | `<acronym>` | `<address>` | `<b>` | `<bdo>`

---|---|---|---|---

`<big>` | `<center>` | `<cite>` | `<code>` | `<dd>`

`<dfn>` | `<dt>` | `<em>` | `<i>` | `<kbd>`

`<noframes>` | `<noscript>` | `<s>` | `<samp>` | `<small>`

`<span>` | `<strike>` | `<strong>` | `<sub>` | `<sup>`

`<tt>` | `<u>` | `<var>`

|  |

Alle anderen HTML-Tags haben eigene Interfaces, die vom HTML-Teil der DOM-Spezifikation definiert werden. Für viele HTML-Tags definieren diese Interfaces nur eine Menge von Eigenschaften, die ihre HTML-Attribute widerspiegeln. Beispielsweise gibt es für das `<ul>`-Tag ein entsprechendes HTMLUListElement-Interface und für das `<body>`-Tag ein entsprechendes HTMLBodyElement-Interface. Weil diese Interfaces einfach nur Eigenschaften definieren, die Teil des HTML-Standards sind, werden sie in diesem Buch nicht ausführlich dokumentiert. Sie können sicher voraussetzen, dass das HTMLElement-Objekt, das ein bestimmtes HTML-Tag repräsentiert, Eigenschaften für jedes Standard-Attribut dieses Tags besitzt (aber beachten Sie die Namenskonventionen, die im folgenden Abschnitt beschrieben werden). Beim Eintrag HTMLElement in Teil IV finden Sie eine Tabelle der HTML-Tags und die ihnen entsprechenden JavaScript-Eigenschaften.

Beachten Sie, dass der DOM-Standard die Eigenschaften für HTML-Attribute als Hilfsmittel für Skriptautoren definiert. Der allgemeine Weg, Attributwerte abzufragen und zu setzen, sind die `getAttribute( )`\- und `setAttribute( )`-Methoden des Element-Objekts, und Sie müssen diese Methoden verwenden, wenn Sie mit Attributen arbeiten, die nicht Teil des HTML-Standards sind.

Einige der Interfaces, die vom HTML DOM spezifiziert werden, definieren Eigenschaften oder Methoden, die nicht einfach HTML-Attributwerte widerspiegeln. Beispielsweise definiert das Interface HTMLInputElement die Methoden `focus( )` und `blur( )` und eine `form`-Eigenschaft, und das Interface HTMLFormElement definiert die Methoden `submit( )` und `reset( )` und eine `length`-Eigenschaft. Wenn die JavaScript-Darstellung eines HTML-Elements Eigenschaften und Methoden einschließt, die mehr tun, als einfach nur HTML-Attribute widerzuspiegeln, werden diese Elemente in Teil IV dokumentiert. Beachten Sie allerdings, dass der Referenzabschnitt nicht die umfangreichen Interface-Namen verwendet, die vom DOM definiert werden. Stattdessen verweist er auf sie der Einfachheit (und der Rückwärtskompatibilität mit der früheren Verwendung) halber über einfachere Namen. Schlagen Sie beispielsweise in den Referenzeinträgen für Anchor, Image, Input, Form, Link, Option, Select, Table und Textarea nach.

### 15.4.3.1 HTML-Namenskonventionen

Bei der Arbeit mit den HTML-spezifischen Teilen des DOM-Standards sollten Sie mit einigen einfachen Namenskonventionen vertraut sein. Denken Sie zunächst daran, dass HTML Groß-/Kleinschreibung zwar nicht berücksichtigt, JavaScript aber schon. Eigenschaften der HTML-spezifischen Interfaces beginnen mit Kleinbuchstaben. Wenn der Eigenschaftsname aus mehreren Wörtern besteht, wird der erste Buchstabe des zweiten Worts und aller nachfolgenden Wörter großgeschrieben. Das `maxlength`-Attribut des `<input>`-Tags wird also zur `maxLength`-Eigenschaft von HTMLInputElement.

Wenn ein HTML-Attributname mit einem JavaScript-Schlüsselwort in Konflikt tritt, wird ihm der String »html« vorangestellt, um einen Konflikt zu vermeiden. Das `for`-Attribut des `<label>`-Tags wird also zur `htmlFor`-Eigenschaft von HTMLLabelElement. Eine Ausnahme zu dieser Regel ist das `class`-Attribut (das für jedes HTML-Element angegeben werden kann). Es wird zur `className`-Eigenschaft von HTMLElement.[55]

## 15.4.4 DOM Level und Features

Es gibt zwei Versionen oder »Level« des DOM-Standards. DOM Level 1 wurde im Oktober 1998 standardisiert. Es definiert die Core DOM-Interfaces wie Node, Element, Attr und Document und daneben verschiedene HTML-spezifische Interfaces. DOM Level 2 wurde im November 2000 standardisiert. Neben einigen Aktualisierungen der Kern-Interfaces wurde diese neue Version des DOMs umfassend erweitert, um Standard-APIs für die Arbeit mit Dokument-Events und CSS-Stylesheets zu definieren und zusätzliche Werkzeuge für die Arbeit mit Bereichen von Dokumenten bereitzustellen.

Mit Level 2 wurde der DOM-Standard »modularisiert.« Das Core-Modul, das die grundlegende Baumstruktur eines Dokuments mit (unter anderem) den Interfaces Document, Node, Element und Text definiert, ist das einzige erforderliche Modul. Alle anderen Module sind optional und können, je nach Anforderungen der Implementierung unterstützt werden oder nicht. Die DOM-Implementierung eines Webbrowsers würde offensichtlicherweise das HTML-Modul unterstützen, weil Webdokumente in HTML geschrieben sind. Browser, die CSS-Stylesheets unterstützen, unterstützen überlicherweise auch die StyleSheets- und CSS-Module, weil (wie in Kapitel 16 gezeigt wird) CSS-Styles in der Dynamic HTML-Programmierung eine entscheidende Rolle spielen. Da fast die gesamte interessante clientseitige JavaScript-Programmierung Event-Handling-Fähigkeiten erfordert, würde man gleichermaßen erwarten, dass alle Webbrowser das Events-Modul der DOM-Spezifikation unterstützen. Leider hat Microsoft das DOM Events-Modul nie für den Internet Explorer implementiert. Events werden deswegen, wie in Kapitel 17 beschrieben, im Alt-DOM, dem W3C DOM und dem IE DOM jeweils unterschiedlich behandelt.

Dieses Buch dokumentiert DOM Level 1 und 2. Das entsprechende Referenzmaterial finden Sie in Teil IV.

Das W3C ist fortgefahren, den DOM-Standard zu verbessern und zu erweitern und hat Spezifikationen für verschiedene Level 3-Module veröffentlicht, einschließlich einer Level 3-Version des Core-Moduls. Level 3-Features werden in Webbrowsern noch nicht so umfassend unterstützt (obwohl Firefox eine partielle Unterstützung bietet) und werden in dieser Auflage dieses Buchs nicht dokumentiert.

Neben Verweisen auf DOM Level 1, 2 und 3 stoßen Sie gelegentlich vielleicht auch auf Verweise auf DOM Level 0. Dieser Begriff verweist auf keinen formellen Standard, sondern verweist informell auf die gemeinsamen Features der HTML-Objektmodelle, die Netscape und der Internet Explorer vor der W3C-Standardisierung implementiert haben. Das heißt, »DOM Level 0« ist ein Synonym für »Alt-DOM.«

## 15.4.5 DOM-Konformität

Als dies geschrieben wurde, boten alle aktuellen Versionen moderner Browser wie Firefox, Safari und Opera gute bis ausgezeichnete Unterstützung des DOM Level 2-Standards. Internet Explorer 6 ist Level 1 DOM-konform und unterstützt Level 2 DOM nicht annähernd so gut. Zusätzlich zu einer unvollständigen Unterstützung von Level 2 Core, bietet er auch überhaupt keine Unterstützung für das Level 2 Events-Modul, das Gegenstand von Kapitel 17 ist. Internet Explorer 5 und 5.5 haben substanzielle Mängel in ihrer Level 1-Konformität, unterstützen aber die wichtigsten DOM Level 1-Methoden gut genug, um die meisten der Beispiele in diesem Kapitel auszuführen.

Die Anzahl der verfügbaren Browser ist so groß geworden und die Geschwindigkeit der Veränderungen in diesem Bereich der Standardunterstützung hat so zugenommen, dass dieses Buch nicht einmal mehr versucht, definitive Aussagen dazu zu machen, welche Browser welches bestimmte DOM-Feature unterstützen. Deswegen müssen Sie sich auf andere Informationsquellen verlassen, wenn Sie herausfinden möchten, wie konform die DOM-Implementierung eines bestimmten Webbrowsers ist.

Eine Quelle für Konformitätsinformationen ist die Implementierung selbst. In einer konformen Implementierung verweist die `implementation`-Eigenschaft des Document-Objekts auf ein DOMImplementation-Objekt, das eine Methode namens `hasFeature( )` definiert. Diese Methode können Sie einsetzen (falls sie existiert), um die Implementierung zu fragen, ob sie ein bestimmtes Feature (oder Modul) des DOM-Standards unterstützt. Um zu ermitteln, ob die DOM-Implementierung in einem Webbrowser die grundlegenden DOM Level 1-Interfaces für die Arbeit mit HTML-Dokumenten unterstützt, können Sie beispielsweise den folgenden Code verwenden:

    if (document.implementation &&
        document.implementation.hasFeature &&
        document.implementation.hasFeature("html", "1.0")) {
        // Der Browser behauptet, dass er die Level 1 Core- und HTML-Interfaces unterstützt
    }

Die Methode `hasFeature( )` erwartet zwei Argumente: den Namen des zu prüfenden Features und eine Versionsnummer, die beide als String ausgedrückt werden. Sie liefert `true`, wenn die angegebene Version des angegebenen Features unterstützt wird. Tabelle 15-3 getestet werden können") führt die Feature-Name/Versionsnummer-Paare auf, die von den DOM Level 1- und Level 2-Standards definiert werden. Beachten Sie, dass bei den Feature-Namen keine Groß-/Kleinschreibung berücksichtigt wird: Sie können sie in dieser Hinsicht schreiben, wie Sie möchten. Die vierte Spalte der Tabelle gibt an, welche anderen Features für die Unterstützung eines Features erforderlich sind und somit durch den Rückgabewert `true` impliziert werden. Wenn `hasFeature( )` beispielsweise anzeigt, dass das Modul Mouse-Events unterstützt wird, impliziert das, dass UIEvents ebenfalls unterstützt wird, und das impliziert seinerseits, dass die Module Events, Views und Core unterstützt werden.

Tabelle 15-3. Features, die mit hasFeature( ) getestet werden können

Feature-Name | Version | Beschreibung | Impliziert

---|---|---|---

HTML | 1.0 | Level 1 Core- und HTML-Interfaces

|

XML | 1.0 | Level 1 Core- und XML-Interfaces

|

Core | 2.0 | Level 2 Core-Interfaces

|

HTML | 2.0 | Level 2 HTML-Interfaces | Core

XML | 2.0 | Level 2 XML-spezifische Interfaces | Core

Views | 2.0 | AbstractView-Interface | Core

StyleSheets | 2.0 | Generische Stylesheet-Durchquerung | Core

CSS | 2.0 | CSS-Styles | Core, Views

CSS2 | 2.0 | CSS2Properties-Interface | CSS

Events | 2.0 | Event-Handling-Infrastruktur | Core

UIEvents | 2.0 | Benutzerschnittstellen-Events (plus Events und Views) | Events, Views

MouseEvents | 2.0 | Mouse-Events | UIEvents

HTMLEvents | 2.0 | HTML-Events | Events

Im Internet Explorer 6 liefert `hasFeature( )` nur für das HTML-Feature und die Version 1.0 `true` zurück. Er meldet keine Konformität zu einem der anderen Features, die in Tabelle 15-3 getestet werden können") aufgeführt werden (obwohl er, wie in Kapitel 16 gezeigt wird, die wichtigsten Verwendungen des CSS2-Moduls unterstützt).

Dieses Buch dokumentiert die Interfaces, die alle DOM-Module bilden, die in Tabelle 15-3 getestet werden können") aufgeführt werden. Die Core- und HTML-Module werden in diesem Kapitel behandelt. Die StyleSheets-, CSS- und CSS2-Module werden in Kapitel 16 behandelt und die verschiedenen Event-Module in Kapitel 17. Teil IV schließt eine vollständige Behandlung aller Module ein.

Die Methode `hasFeature( )` ist nicht immer absolut zuverlässig. Wie bereits gesagt, meldet der IE 6 Level 1-Konformität zu den HTML-Features, obwohl es bei seiner Standardkonformität einige Probleme gibt. Andererseits meldete Netscape 6.1 Nicht-Konformität zu den Level 2 Core-Features, obwohl er überwiegend konform war. In beiden Fällen benötigen Sie feinstufigere Informationen dazu, was genau konform ist und was nicht. Das sind aber genau die Informationen, die zu umfangreich und unbeständig sind, um sie in diesem Buch abzudrucken.

Wenn Sie ein aktiver Webentwickler sind, kennen oder entdecken Sie zweifelslos selbst viele browserspezifische Unterstützungsdetails. Aber auch im Web gibt es Ressourcen, die Sie dabei unterstützen können. Das W3C hat unter <http://www.w3c.org/DOM/Test/> eine partielle Test-Suite für bestimmte DOM-Module veröffentlicht. Leider hat es keine Ergebnisse der Anwendung dieser Suite auf gebräuchliche Webbrowser veröffentlicht.

Der beste Ort, um Kompatibilitäts- und Konformitätsinformationen zu finden, sind vielleicht einige unabhängige Sites im Web. Eine beachtenswerte Site ist <http://www.quirksmode.org> von Peter-Paul Koch. Sie enthält die Ergebnisse von intensiven Untersuchungen der Browser-Kompatibilität mit den DOM- und CSS-Standards, die ihr Autor durchgeführt hat. Eine andere Site ist <http://webdevout.net/browser_support.php> von David Hammond.

### 15.4.5.1 DOM-Konformität im Internet Explorer

Weil der IE der am häufigsten verwendete Webbrowser ist, sind hier einige besondere Hinweise zu seiner Konformität mit den DOM-Spezifikationen angebracht. IE 5 und später unterstützen die Level 1 Core- und HTML-Features gut genug, um die Beispiele in diesem Kapitel auszuführen. Außerdem unterstützen sie die wichtigsten Level 2 CSS-Features gut genug, um die meisten der Beispiele in Kapitel 16 auszuführen. Leider bieten IE 5, 5.5 und 6 keine Unterstützung des DOM Level 2 Events-Moduls, obwohl Microsoft selbst an der Definition dieses Moduls beteiligt war und hinreichend Zeit hatte, es für den IE 6 zu implementieren. Die mangelnde Unterstützung des Standard-Event-Modells durch den IE hemmt die Entwicklung fortgeschrittener clientseitiger Webanwendungen.

Obwohl der IE 6 (über seine `hasFeature( )`-Methode) behauptet, dass er die Core- und HTML-Interfaces des DOM Level 1-Standards unterstützt, ist diese Unterstützung eigentlich unvollständig. Das ungeheuerlichste Problem und das, auf das Sie am wahrscheinlichsten stoßen werden, ist klein, aber nervig: Der IE bietet keine Unterstützung für die Knotentypkonstanten, die vom Node-Interface definiert werden. Sie werden sich erinnern, dass jeder Knoten in einem Dokument eine `nodeType`-Eigenschaft hat, die angibt, um was für eine Art von Knoten es sich handelt. Die DOM-Spezifikation sagt auch, dass das Node-Interface Konstanten definiert, die jeden definierten Knotentyp repräsentieren. Beispielsweise repräsentiert die Konstante `Node.ELEMENT_NODE` einen Element-Knoten. In IE (zumindest bis Version 6) existieren diese Konstanten einfach nicht.

Die Beispiele in diesem Kapitel wurden so angepasst, dass sie dieses Problem umgehen, indem sie Integer-Literale anstelle der entsprechenden symbolischen Konstanten verwenden. Beispielsweise werden Sie Code wie diesen sehen:

    if (n.nodeType == 1 /*Node.ELEMENT_NODE*/) // Prüfen, ob n ein Element ist

Es ist guter Programmierstil, statt festen Integer-Literalen in Code Konstanten zu verwenden. Wenn Sie das portabel machen möchten, können Sie den folgenden Code in Ihre Programme einschließen, um diese Konstanten zu definieren, wenn sie fehlen sollten:

    if (!window.Node) {
        var Node = {           // Wenn es kein Node-Objekt gibt, eines mit
            ELEMENT_NODE: 1,   // den folgenden Eigenschaften und Werten definieren.
            ATTRIBUTE_NODE: 2, // Beachten Sie, dass das nur HTML-Knotentypen sind.
            TEXT_NODE: 3,      // Für XML-spezifische Knoten müssen Sie hier
            COMMENT_NODE: 8,   // andere Konstanten hinzufügen.
            DOCUMENT_NODE: 9,
            DOCUMENT_FRAGMENT_NODE: 11
        };
    }

## 15.4.6 Sprachunabhängige DOM-Interfaces

Obwohl der DOM-Standard aus dem Wunsch entsprang, eine allgemeine API für die dynamische HTML-Programmierung zur Verfügung zu haben, ist das DOM nicht nur für Webprogrammierer interessant. Tatsächlich wird der Standard zurzeit am intensivsten von serverseitigen Java- und C++-Programmen verwendet, die XML-Dokumente parsen und manipulieren. Aufgrund seiner vielen Verwendungsmöglichkeiten wurde der DOM-Standard sprachunabhängig definiert. Dieses Buch beschreibt nur die JavaScript-Bindungen der DOM API, aber Sie sollten sich einiger weiterer Punkte bewusst sein. Erstens sollten Sie beachten, dass die Objekteigenschaften in den JavaScript-Bindungen in anderen Sprachen üblicherweise auf Paare von Getter/Setter-Methoden abgebildet sind. Wenn ein Java-Programmierer Sie nach der `getFirstChild( )`-Methode des Node-Interfaces fragt, müssen Sie deswegen wissen, dass die JavaScript-Bindung der Node API keine `getFirstChild( )`-Methode definiert. Stattdessen definiert sie einfach eine `firstChild`-Eigenschaft. Das Lesen dieser Eigenschaft ist das JavaScript-Äquivalent zum `getFirstChild( )`-Aufruf in Java.

Ein weiteres wichtiges Feature der JavaScript-Bindung der DOM API ist, dass sich bestimmte DOM-Objekte wie JavaScript-Arrays verhalten. Wenn ein Interface eine Methode namens `item( )` definiert, verhalten sich Objekte, die dieses Interface implementieren, wie schreibgeschützte numerische Arrays. Nehmen Sie beispielsweise an, Sie haben ein NodeList-Objekt erhalten, indem Sie die `childNodes`-Eigenschaft eines Knotens gelesen haben. Sie können die einzelnen Node-Objekte in der Liste erhalten, indem Sie die gewünschte Knotennummer an die Methode `item( )` übergeben oder, noch einfacher, indem Sie das NodeList-Objekt einfach als ein Array behandeln und es direkt indizieren. Der folgende Code illustriert diese beiden Optionen:

    var n = document.documentElement; // Dies ist ein Node-Objekt.
    var children = n.childNodes;      // Dies ist ein NodeList-Objekt.
    var head = children.item(0);      // Hier ist ein Weg, eine NodeList zu verwenden.
    var body = children[1];           // Aber dieser Weg ist einfacher!

Wenn ein DOM-Objekt eine `namedItem( )`-Methode besitzt, hat es quasi die gleichen Auswirkungen, wenn Sie dieser Methode einen String übergeben oder wenn Sie den String als Array-Index für das Objekt verwenden. Beispielsweise sind die folgenden Codezeilen äquivalente Möglichkeiten, auf ein Formularelement zuzugreifen:

    var f = document.forms.namedItem("meinformular");
    var g = document.forms["meinformular"];
    var h = document.forms.meinformular;

Obwohl Sie die Array-Notation verwenden können, um auf ein Element einer NodeList zuzugreifen, ist es wichtig, dass Sie daran denken, dass NodeList einfach ein Array-artiges Objekt ist (siehe "7.7 Array-artige Objekte") und kein echtes Array. Eine NodeList hat beispielsweise keine `sort( )`-Methode.

Weil der DOM-Standard auf unterschiedliche Weise verwendet werden kann, haben die Architekten des Standards darauf geachtet, dass die DOM API auf eine Weise definiert ist, dass sie nicht die Möglichkeiten anderer einschränkt, die API ihren eigenen Vorstellungen entsprechend zu implementieren. Insbesondere definiert der DOM-Standard Interfaces anstelle von Klassen. In der objektorientierten Programmierung ist eine Klasse ein festgelegter Datentyp, der genau so implementiert werden muss, wie er vorgegeben wurde. Ein Interface ist andererseits eine Sammlung von Methoden und Eigenschaften, die gemeinsam implementiert werden müssen. Also kann eine Implementierung des DOM nach eigenem Geschmack Klassen definieren. Aber diese Klassen müssen die Methoden und Eigenschaften der verschiedenen DOM-Interfaces definieren.

Diese Architektur hat eine Reihe wichtiger Implikationen. Erstens müssen die Klassennamen, die in einer Implementierung verwendet werden, nicht direkt mit den Interface-Namen übereinstimmen, die im DOM-Standard verwendet werden. Zweitens kann eine Klasse mehrere Interfaces implementieren. Betrachten Sie beispielsweise das Document-Objekt. Dieses Objekt ist eine Instanz einer Klasse, die von der Webbrowser-Implementierung definiert wird. Wir wissen nicht genau, welche Klasse das ist, aber wir wissen, dass sie das DOM Document-Interface implementiert – das heißt, alle Methoden und Eigenschaften, die von Document definiert werden, sind für uns über das Document-Objekt verfügbar. Da Webbrowser mit HTML-Dokumenten arbeiten, wissen wir auch, dass das Document-Objekt das HTMLDocument-Interface implementiert und dass uns auch alle Methoden und Eigenschaften zur Verfügung stehen, die von diesem Interface definiert werden. Da Webbrowser außerdem CSS unterstützen, implementiert das Document-Objekt auch noch die DOM-Interfaces DocumentStyle und DocumentCSS. Und wenn der Webbrowser die DOM Events- und Views-Module unterstützt, implementiert Document ebenfalls die Interfaces DocumentEvent und DocumentView.

Im Allgemeinen konzentriert sich Teil IV auf die Objekte, mit denen clientseitige JavaScript-Programmierer tatsächlich arbeiten, anstatt auf die abstrakteren Interfaces, die die API für diese Objekte vorgeben. Deswegen finden Sie im Referenzabschnitt Einträge für Document und HTMLDocument, aber nicht für die kleineren Zusatz-Interfaces wie DocumentCSS und DocumentView. Die von diesen Interfaces definierten Methoden werden einfach in den Eintrag für das Document-Objekt integriert.

Eine andere wichtige Sache, die Sie verstehen müssen, ist, dass der DOM-Standard, weil er statt Klassen Interfaces definiert, keine Konstruktormethoden definiert. Wenn Sie ein neues Text-Objekt erzeugen möchten, um es in ein Dokument einzufügen, können Sie beispielsweise nicht Folgendes schreiben:

    var t = new Text("Dies ist ein Textknoten"); // Den Konstruktor gibt es nicht

Da er keine Konstruktoren definieren kann, definiert der DOM-Standard stattdessen im Document-Interface eine Reihe nützlicher _Factory-Methoden_. Um einen neuen Textknoten für ein Dokument zu erstellen, würden Sie deswegen Folgendes schreiben:

    var t = document.createTextNode("Dies ein neue Textknoten");

Die vom DOM definierten Factory-Methoden haben Namen, die mit dem Wort »create« beginnen. Zusätzlich zu den von Document definierten Factory-Methoden werden noch einige weitere von DOMImplementation definiert und sind über `document.implementation` verfügbar.

* * *

53] Das DOM kann auch verwendet werden, um XML-Dokumente darzustellen, die eine komplexere Syntax als HTML-Dokumente haben, und die Baumdarstellung eines solchen Dokuments kann Knoten enthalten, die XML-Entity-Referenzen, Verarbeitungsanweisungen, CDATA-Abschnitte und so weiter repräsentieren. Mehr zur Verwendung des DOMs mit XML finden Sie in [Kapitel 21.

[54] Der DOM-Standard definiert Interfaces, keine Klassen. Wenn Sie mit dem Begriff _Interface_ in der objektorientierten Programmierung noch nicht vertraut sind, stellen Sie sich das als eine Art abstrakte Klasse vor. Später in diesem DOM-Überblick werde ich diesen Unterschied ausführlicher beschreiben.

[55] Der Begriff `className` ist irreführend, weil mit dieser Eigenschaft (und dem HTML-Attribut, das sie repräsentiert) nicht nur ein einzelner Klassenname angegeben werden kann, sondern auch eine durch Leerzeichen getrennte Liste mit Klassennamen.

# 15.5 Ein Dokument durchqueren

Mit dem vorangehenden Überblick über das W3C DOM im Gepäck sind Sie jetzt in der Lage, mit der Verwendung der DOM API zu beginnen. Dieser Abschnitt und die auf ihn folgenden Abschnitte zeigen, wie man DOM-Bäume durchquert, in einem Dokument bestimmte Elemente findet, den Dokumentinhalt verändert und einem Dokument neuen Inhalt hinzufügt.

Wie bereits erwähnt, repräsentiert das DOM ein HTML-Dokument als einen Baum von Node-Objekten. Bei jeder Baumstruktur ist die Durchquerung des Baums und eine Untersuchung der einzelnen Knoten eine der häufigsten Aufgaben. Beispiel 15-2 zeigt eine Möglichkeit, das zu tun. Es ist eine JavaScript-Funktion, die rekursiv einen Knoten und all seine Kinder untersucht und die Anzahl der HTML-Tags (d.h. Element-Knoten) zählt, auf die sie bei der Durchquerung stößt. Beachten Sie die Verwendung der `childNodes`-Eigenschaft eines Knotens. Der Wert dieser Eigenschaft ist ein NodeList-Objekt, das sich (in JavaScript) wie ein Array von Node-Objekten verhält. Die Funktion kann also alle Kinder eines gegebenen Knotens aufzählen, indem sie eine Schleife über die Elemente des `childNodes[]`-Arrays durchläuft. Durch Rekursion zählt die Funktion nicht nur die Kinder eines gegebenen Knotens auf, sondern alle Knoten im Knotenbaum. Beachten Sie, dass diese Funktion auch zeigt, wie man die Eigenschaft `nodeType` einsetzt, um den Typ der einzelnen Knoten zu ermitteln.

Beispiel 15-2. Die Knoten eines Dokuments durchqueren

    <head>
    <script>
    // Dieser Funktion wird ein DOM-Node-Objekt übergeben, und sie prüft, ob dieser Knoten ein
    // HTML-Tag repräsentiert – d.h., ob der Knoten ein Element-Objekt ist. Sie ruft sich selbst
    // rekursiv auf allen Kindern des Knotens auf und prüft diese auf die gleiche Weise.
    // Sie liefert die Gesamtzahl von Element-Objekten zurück, auf die sie stößt.
    // Wenn Sie diese Funktion aufrufen, indem Sie ihr das Document-Objekt übergeben,
    // durchquert sie den vollständigen DOM-Baum.
    function zaehleTags(n) {                        // n ist ein Node-Objekt
        var anz_tags = 0;                           // Den Tag-Zähler initialisieren
        if (n.nodeType == 1 /*Node.ELEMENT_NODE*/)  // Prüfen, ob n ein Element ist
            anz_tags++;                             // Gegebenenfalls den Zähler erhöhen
        var kinder = n.childNodes;                  // Jetzt alle Kinder abrufen
        for(var i=0; i < kinder.length; i++) {   // Die Kinder durchlaufen
            anz_tags += zaehleTags(kinder[i]);      // Rekursion auf allen Kindern
        }
        return anz_tags;                            // Gesamtzahl zurückliefern
    }
    </script>
    </head>
    <!-- Hier ist ein Beispiel dafür, wie zaehleTags( ) verwendet werden könnte. -->
    <body onload="alert('Dieses Dokument hat ' + zaehle(document) + ' Tags')">
    Dies ist ein <i>Beispiel</i> für ein Dokument.
    </body>

Ein weiterer Punkt, der zu Beispiel 15-2 zu beachten ist, besteht darin, dass die Funktion `zaehleTags( )`, die es definiert, aus dem `onload`-Event-Handler aufgerufen wird, damit sie nicht aufgerufen wird, bevor das Dokument vollständig geladen wurde. Das ist eine allgemeine Anforderung bei der Arbeit mit dem DOM: Sie können den Dokumentbaum nicht durchqueren oder manipulieren, bevor das Dokument vollständig geladen wurde. (In "13.5.7 Das Dokument während des Ladens manipulieren" finden Sie weitere Informationen zu dieser Anforderung. Sehen Sie sich auch Beispiel 17-7 an, das eine Hilfsfunktion bietet, die es mehreren Modulen ermöglicht, `onload`-Event-Handler zu registrieren.)

Zusätzlich zu der Eigenschaft `childNodes` definiert das Interface Node ein paar weitere nützliche Eigenschaften. `firstChild` und `lastChild` verweisen auf das erste und das letzte Kind eines Knotens, und `nextSibling` und `previousSibling` verweisen auf benachbarte Geschwister eines Knotens. (Zwei Knoten sind Geschwister, wenn sie den gleichen Elternknoten haben.) Diese Eigenschaften bieten eine weitere Möglichkeit, die Kinder eines Knotens zu durchlaufen, die in Beispiel 15-3 vorgeführt wird. Dieses Beispiel definiert eine Funktion namens `getText( )`, die alle Text-Knoten bei oder unter einem angegebenen Knoten findet. Es zieht den Textinhalt der Knoten heraus, verknüpft sie und liefert sie als einen JavaScript-String zurück. In der DOM-Programmierung besteht überraschend häufig Bedarf nach einer Hilfsfunktion wie dieser.

Beispiel 15-3. Den gesamten Text unterhalb eines DOM-Knotens erhalten

    /**
     * getText(n): Alle Text-Knoten bei oder unter dem Knoten n finden,
     * ihren Inhalt aneinanderknüpfen und als String zurückliefern.
     */
    function getText(n) {
        // Wiederholte Stringverkettungen können ineffizient sein. Wir sammeln
        // deswegen die Werte aller Textknoten in einem Array und verketten die
        // Elemente dieses Arrays dann auf einmal.
        var strings = [];
        getStrings(n, strings);
        return strings.join("");

        // Diese rekursive Funktion findet alle Textknoten und hängt ihren
        // Text an das Array an.
        function getStrings(n, strings) {
            if (n.nodeType == 3 /* Node.TEXT_NODE */)
                strings.push(n.data);
            else if (n.nodeType == 1 /* Node.ELEMENT_NODE */) {
                // Achten Sie auf die Iteration mit firstChild/nextSibling
                for(var m = n.firstChild; m != null; m = m.nextSibling) {
                    getStrings(m, strings);
                }
            }
        }
    }

# 15.6 Elemente in einem Dokument finden

Die Fähigkeit, alle Knoten in einem Dokument durchqueren zu können, bietet Ihnen die Möglichkeit, bestimmte Knoten zu finden. Wenn man mit der DOM API programmiert, braucht man häufig einen bestimmten Knoten aus dem Dokument oder eine Liste von Knoten bestimmter Typen im Dokument. Glücklicherweise bietet die DOM API Funktionen, die das einfach machen.

Das Document-Objekt ist die Wurzel jedes DOM-Baums, repräsentiert aber kein HTML-Element in diesem Baum. Die Eigenschaft `document.documentElement` verweist auf das `<html>`-Tag, das als Wurzelelement des Dokuments dient. Und die Eigenschaft `document. body` verweist auf das `<body>`-Tag, das häufiger nützlich ist als sein `<html>`-Elternknoten.

Die `body`-Eigenschaft des HTMLDocument-Objekts ist eine praktische Sondereigenschaft. Gäbe es sie nicht, könnten Sie dennoch folgendermaßen auf das `<body>`-Tag verweisen:

    document.getElementsByTagName("body")[0]

Dieser Ausdruck ruft die `getElementsByTagName( )`-Methode des Document-Objekts auf und wählt das erste Element des zurückgelieferten Arrays aus. Der Aufruf von `getElementsByTagName( )` liefert ein Array aller `<body>`-Elemente im Dokument. Da HTML-Dokumente nur ein `<body>`-Element haben können, interessieren Sie sich nur für das erste Element des zurückgelieferten Arrays.[56]

Sie können `getElementsByTagName( )` verwenden, um eine Liste eines beliebigen Typs von HTML-Element zu erhalten. Beispielsweise können Sie Folgendes tun, um alle Tabellen in einem Dokument zu finden:

    var tabellen = document.getElementsByTagName("table");
    alert("Dieses Dokument enthält " + tabellen.length + " Tabellen");

Beachten Sie, dass die Strings, die `getElementsByTagName( )` übergeben werden, da HTML-Tags keine Groß-/Kleinschreibung berücksichtigen, ebenfalls keine Groß-/Kleinschreibung berücksichtigen. Das heißt, dass der vorangehende Code `<table>`-Tags auch dann findet, wenn sie als `<TABLE>` geschrieben sind. `getElementsByTagName( )` liefert Elemente in der Reihenfolge zurück, in der sie im Dokument erscheinen. Und wenn Sie `getElementsByTagName( )` den besonderen String »*« übergeben, liefert die Methode eine Liste aller Elemente im Dokument zurück und bewahrt dabei die Reihenfolge, in der sie im Dokument erscheinen. (Diese besondere Verwendung wird von IE 5 und IE 5.5 nicht unterstützt. Siehe stattdessen das IE-spezifische `HTMLDocument.all]`-Array in [Teil IV.)

Gelegentlich möchten Sie keine Liste von Elementen, sondern möchten auf einem bestimmten Element eines Dokuments operieren. Wenn Sie viel über die Struktur eines Dokuments wissen, können Sie dazu vielleicht immer noch `getElementsByTagName( )` verwenden. Wenn Sie etwas mit dem vierten Absatz eines Dokuments machen möchten, könnten Sie beispielsweise diesen Code verwenden:

    var meinAbsatz = document.getElementsByTagName("p")[3];

Das ist normalerweise jedoch nicht die beste (und auch nicht effizienteste) Technik, weil sie stark von der Struktur des Dokuments abhängig ist. Würde am Anfang des Dokuments ein neuer Absatz eingefügt, würde das den Code zerstören. Wenn Sie bestimmte Elemente eines Dokuments manipulieren möchten, sollten Sie diesen Elementen stattdessen ein `id`-Attribut geben, das einen (innerhalb des Dokuments) eindeutigen Namen für das Element angibt. Dann können Sie das gewünschte Element über seine ID nachschlagen. Beispielsweise könnten Sie den besonderen vierten Absatz Ihres Dokuments mit einem Tag wie diesem schreiben:

    <p id="besondererAbsatz">

Den Knoten für diesen Absatz können Sie dann mit JavaScript-Code wie diesem suchen:

    var meinAbsatz = document.getElementById("besondererAbsatz");

Beachten Sie, dass die Methode `getElementById( )` nicht wie `getElementsByTagName( )` ein Array von Elementen zurückliefert. Weil der Wert jedes `id`-Attributs einzigartig ist (oder sein sollte), liefert `getElementById( )` nur das eine Element mit dem passenden `id`-Attribut zurück.

`getElementById( )` ist eine wichtige Methode, und ihr Einsatz ist in der DOM-Programmierung sehr gebräuchlich. Sie wird so häufig verwendet, dass Sie dafür vielleicht eine Hilfsfunktion mit einem kürzeren Namen definieren möchten. Zum Beispiel:

    // Wenn x ein String ist, wird angenommen, dass es eine Element-ID ist,
    // und das benannte Element gesucht. Andernfalls wird angenommen, dass
    // x bereits ein Element ist, das dann einfach zurückgeliefert wird.
    function id(x) {
      if (typeof x == "string") return document.getElementById(x);
      return x;
    }

Wenn Sie eine solche Funktion definiert haben, können Ihre DOM-Manipulationsmethoden so geschrieben werden, dass sie Elemente oder Element-IDs als Argumente akzeptieren. Vor der Verwendung eines solchen Arguments `x` müssen Sie dann einfach nur einmal `x=id(x)` schreiben. Ein bekanntes clientseitiges JavaScript-Toolkit[57] definiert eine derartige Hilfsmethode und gibt ihr einen sogar noch kürzeren Namen: `$( )`.

`getElementById( )` und `getElementsByTagName( )` sind beides Methoden des Document-Objekts. Element-Objekte definieren jedoch ebenfalls eine `getElementsByTagName( )`-Methode. Diese Methode des Element-Objekts verhält sich genauso wie die Methode des Document-Objekts, liefert allerdings nur Elemente zurück, die Nachfahren des Elements sind, auf dem sie aufgerufen wurde. Anstatt das vollständige Dokument nach einem bestimmten Typen zu durchsuchen, sucht sie nur innerhalb des vorgegebenen Elements. Das ermöglicht es beispielsweise, `getElementById( )` zu verwenden, um ein bestimmtes Element zu finden, und dann `getElementsByTagName( )` zu finden, um alle Nachfahren eines bestimmten Typs in diesem Element zu finden. Zum Beispiel:

    // Ein bestimmtes Table-Element in einem Dokument finden und seine Zeilen zählen
    var inhaltsverzeichnis = document.getElementById("TOC");
    var zeilen = tableOfContents.getElementsByTagName("tr");
    var zeilenanzahl = rows.length;

Beachten Sie schließlich, dass das HTMLDocument-Objekt für HTML-Dokumente auch eine `getElementsByName( )`-Methode definiert. Diese Methode verhält sich wie `getElementById( )`, betrachtet aber das `name`-Attribut von Elementen anstelle des `id`-Attributs. Und weil beim `name`-Attribut nicht erwartet wird, dass es in einem Dokument eindeutig ist (beispielsweise haben Radiobuttons in HTML-Formularen üblicherweise gleiche `name`-Werte), liefert `getElementsByName( )` ein Array mit Elementen anstatt eines einzelnen Elements zurück. Zum Beispiel:

    // <a name="oben"> suchen
    var link = document.getElementsByName("oben")[0];
    // Alle <input type="radio" name="lieferungsart">-Elemente finden
    var auswahl = document.getElementsByName("lieferungsart");

Zusätzlich zur Auswahl von Dokumentelementen über den Tagnamen oder die ID ist es auch oft nützlich, Elemente auszuwählen, die Mitglieder einer Klasse sind. Das HTML-Attribut `class` und die entsprechende JavaScript-Eigenschaft `className` weisen einem Element eine oder mehrere benannte (und durch Leerzeichen getrennte) Klassen zu. Diese Klassen sind für die Verwendung mit CSS-Stylesheets (siehe Kapitel 16) gedacht, aber das muss nicht ihr einziger Zweck sein. Nehmen Sie an, Sie schreiben wichtige Hinweise in einem HTML-Dokument folgendermaßen:

    <div class="warnung">
    Dies ist ein Warnhinweis
    </div>

Sie können dann ein CSS-Stylesheet einsetzen, um die Farben, Abstände, Rahmen und anderen Darstellungsattribute für diese Hinweise anzugeben. Aber was ist, wenn Sie auch JavaScript-Code schreiben wollen, der `<div>`-Tags finden und bearbeiten kann, die Mitglieder der Klasse »warnung« sind? Beispiel 15-4 bietet eine Lösung. Dieses Beispiel definiert eine `getElemente( )`-Methode, die es Ihnen ermöglicht, Elemente anhand der Klasse und/oder des Tagnamens auszuwählen. Beachten Sie, dass die Eigenschaft `className` verzwickt ist, weil ein HTML-Element Mitglied mehrerer Klassen sein kann. `getElemente( )` schließt eine eingebettete Methode `isMitglied( )` ein, die prüft, ob ein angegebenes HTML-Element Mitglied einer angegebenen Klasse ist.

Beispiel 15-4. HTML-Elemente nach Klasse und Tag-Namen auswählen

    /**
     * getElemente(klassenname, tagname, wurzel):
     * Liefert ein Array mit DOM-Elementen zurück, die Mitglied der angegebenen Klasse
     * sind, den angegebenen Tagnamen haben und Nachfahren der angegebenen Wurzel sind.
     *
     * Wird kein klassenname angegeben, werden die Elemente unabhängig von der Klasse
     * zurückgeliefert.
     * Wird kein tagname angegeben, werden die Elemente unabhängig vom tagname zurückgeliefert.
     * Wird keine Wurzel angegeben, wird das document-Objekt verwendet. Wenn die angegebene
     * Wurzel ein String ist, wird sie als Element-ID verwendet und das Wurzelelement
     * über getElementsById( ) nachgeschlagen.
     */
    function getElemente(klassenname, tagname, wurzel) {
        // Wurde keine Wurzel angegeben, wird das vollständige Dokument verwendet.
        // Wurde ein String angegeben, wird er nachgeschlagen.
        if (!wurzel) wurzel = document;
        else if (typeof wurzel == "string") wurzel = document.getElementById(root);

        // Wurde kein Tagname angegeben, werden alle Tags verwendet.
        if (!tagname) tagname = "*";
        // Alle Nachfahren der angegebenen Wurzel mit dem angegebenen Tagnamen finden.
        var alle = wurzel.getElementsByTagName(tagname);

        // Wurde kein klassenname angegeben, werden alle Tags zurückgeliefert
        if (!klassenname) return alle;

        // Andernfalls filtern wir die Elemente über klassenname
        var elemente = []; // Wir beginnen mit einem leeren Array
        for(var i = 0; i < alle.length; i++) {
            var element = alle[i];
            if (isMitglied(element, klassenname)) // isMitglied( ) wird unten definiert
                elemente.push(element);           // Klassenmitglieder dem Array hinzufügen
        }

        // Beachten Sie, dass wir immer ein Array zurückliefern, auch wenn es leer ist.
        return elemente;

        // Ermitteln, ob das angegebene Element Mitglied der angegebenen Klasse ist.
        // Diese Funktion ist für den häufigen Fall optimiert, dass die Eigenschaft
        // className nur einen Klassennamen enthält. Aber sie behandelt auch den Fall
        // einer durch Leerzeichen getrennten Liste von Klassennamen.
        function isMitglied(element, klassenname) {
            var klassen = element.className; // Eine Liste der Klassen abrufen
            if (!klassen) return false;            // Keine Klassen definiert
            if (klassen == klassenname) return true; // Genauer Treffer

            // Kein genauer Treffer. Wenn es keine Leerzeichen gibt, ist dieses Element
            // also kein Mitglied der Klasse.
            var whitespace = /\s+/;
            if (!whitespace.test(klassen)) return false;

            // Wenn wir hier hinkommen, ist das Element Mitglied mehrerer Klassen, die
            // wir deswegen einzeln prüfen müssen.
            var c = klassen.split(whitespace);     // Auf dem Leerraum-Begrenzer aufspalten
            for(var i = 0; i < c.length; i++) { // Die Klassen durchlaufen
                if (c[i] == klassenname) return true;    // und auf Treffer prüfen
            }

            return false; // Wenn keine der Klassen passt
        }
    }

* * *

[56] Technisch gesehen liefert `getElementsByTagName( )` eine NodeList zurück, die wie ein Array-artiges Objekt ist. In diesem Buch verwende ich die Array-Notation, um sie zu indizieren, und werde auf sie deswegen informell als »Array« verweisen.

[57] Die Prototype-Bibliothek von Sam Stephenson, die unter <http://prototype.conio.net> verfügbar ist.

# 15.7 Ein Dokument modifizieren

Die Durchquerung der Knoten eines Dokuments kann nützlich sein, aber die wahre Macht der Core DOM API liegt in den Features, des es Ihnen ermöglichen, JavaScript einzusetzen, um Dokumente dynamisch zu modifizieren. Die beiden folgenden Beispiele demonstrieren die grundlegenden Techniken der Modifikation von Dokumenten und illustrieren einige der Möglichkeiten.

Beispiel 15-5 schließt eine JavaScript-Funktion namens `sortiereKinder( )`, ein Beispieldokument und einen HTML-Button ein, der, wenn er angeklickt wird, die Funktion `sortiereKinder( )` aufruft und ihr die ID eines `<ul>`-Tags übergibt. Die Funktion `sortiereKinder( )` findet die Kindelemente des angegebenen Knotens, sortiert sie auf Grundlages des Texts, den sie enthalten, und ordnet sie im Dokument (mit Hilfe von `appendChild( )`) neu, damit sie in alphabetischer Reihenfolge erscheinen.

Beispiel 15-5. Die Elemente einer Liste alphabetisch sortieren

    <script>
    function sortiereKinder(e) {
       // Das ist das Element, dessen Kinder wir sortieren werden.
       if (typeof e == "string") e = document.getElementById(e);

       // Die Elementkinder (aber nicht die Textknotenkinder) von e in ein echtes Array
       // übertragen.
       var kinder = [];
       for(var x = e.firstChild; x != null; x = x.nextSibling)
         if (x.nodeType == 1 /* Node.ELEMENT_NODE */) kinder.push(x);

       // Jetzt das Array auf Grundlage des Textinhalts der Kinder sortieren.
       // Voraussetzen, dass jedes Kind nur ein einziges Kind hat, das ein Textknoten ist.
       kinder.sort(function(n, m) { // Das ist die Vergleichsfunktion für das Sortieren
              var s = n.firstChild.data; // Text des Knotens n
              var t = m.firstChild.data; // Text des Knotens m
              if (s < t) return −1;   // n kommt vor m
              else if (s > t) return 1; // n kommt nach m
              else return 0;       // n und m sind gleich
            });

       // Jetzt die Kinder in sortierter Reihenfolge wieder an das Elternelement anhängen.
       // Wird ein Knoten eingefügt, der bereits Teil des Dokuments ist, wird er automatisch
       // von seiner aktuellen Position entfernt. Das Wiedereinfügen dieser Knoten
       // verschiebt sie automatisch von ihrer alten Position. Beachten Sie, dass
       // alle Textknoten, die wir übersprungen haben, allerdings zurückgelassen werden.
       for(var i = 0; i < kinder.length; i++) e.appendChild(kinder[i]);
    }
    </script>

    <ul id="liste"> <!-- Das ist die Liste, die wir sortieren werden -->
    <li>eins<li>zwei<li>drei<li>vier <!-- die Elemente sind nicht in alphabetischer Ordnung -->
    </ul>
    <!-- Das ist der Button, der die Liste sortiert -->
    <button onclick="sortiereKinder('liste')">Sortiere Liste</button>

Das in Abbildung 15-3 gezeigte Ergebnis von Beispiel 15-5 ist, dass die Elemente der Liste alphabetisch sortiert werden, wenn der Benutzer auf den Button klickt.

Abbildung 15-3. Eine Liste vor und nach Sortierung

Beachten Sie, dass Beispiel 15-5 die zu sortierenden Knoten in ein separates Array kopiert hat. Das ermöglicht es, das Array leicht zu sortieren, hat aber auch noch andere Vorteile. Die NodeList-Objekte, die der Wert der Eigenschaft `childNodes` sind und von `getElementsByTagName( )` zurückgeliefert werden, sind »live«: Alle Änderungen am Dokument werden unmittelbar in die NodeList übernommen. Das kann eine Problemursache sein, wenn Sie Knoten in die Liste einfügen oder Knoten aus der Liste entfernen, während Sie die Liste durchlaufen. Es kann deswegen häufig am sichersten sein, eine »Momentaufnahme« der Knoten zu machen, indem Sie die Knoten in ein echtes Array übertragen, bevor Sie sie durchlaufen.

Beispiel 15-5 hat die Struktur des Dokuments geändert, indem die Elemente neu geordnet wurden. Beispiel 15-6 ändert den Inhalt eines Dokuments, indem es seinen Text ändert. Dieses Beispiel definiert eine Funktion `grossbuchstaben( )`, die rekursiv von einem angegebenen Node-Objekt aus absteigt und den Inhalt aller Textknoten, die sie findet, in Großbuchstaben umwandelt.

Beispiel 15-6. Ein Dokument in Großbuchstaben umwandeln

    // Diese Funktion durchsucht einen Node n und seine Nachfahren rekursiv und
    // wandelt alle Textknotendaten in Großbuchstaben um.
    function grossbuchstaben(n) {
        if (n.nodeType == 3 /*Node.TEXT_NODE*/) {
            // Wenn der Knoten ein Textknoten ist, Text in Großbuchstaben ändern.
            n.data = n.data.toUpperCase( );
        }
        else {
             // Wenn der Knoten kein Textknoten ist, die Kinder durchlaufen und die
            // Funktion rekursiv auf allen Kindern aufrufen.
            var kinder = n.childNodes;
            for(var i = 0; i < kinder.length; i++) grossbuchstaben(kinder[i]);
        }
    }

Beispiel 15-6 setzt einfach die `data`-Eigenschaft aller Textknoten, auf die es trifft. Es ist auch möglich, in einem Textknoten Text anzuhängen, einzufügen, zu löschen oder zu ersetzen. Dazu dienen die Methoden `appendData( )`, `insertData( )`, `deleteData( )` und `replaceData( )`. Diese Methoden werden nicht direkt vom Text-Interface definiert, sondern von Text von CharacterData geerbt. Mehr Informationen zu ihnen können Sie unter CharacterData in Teil IV finden.

In Beispiel 15-5 haben Sie die Dokumentelemente neu geordnet, sie aber unter dem gleichen Elternknoten belassen. Beachten Sie allerdings, dass die DOM API es erlaubt, Knoten aus dem Dokumentbaum im Baum frei zu bewegen (jedoch nur im gleichen Dokument). Beispiel 15-7 zeigt dies, indem es eine Funktion namens `machFett( )` definiert, die einen angegebenen Knoten durch ein neues Element ersetzt (das mit der `createElement( )`-Methode des Document-Objekts erzeugt wurde), das ein HTML-`<b>`-Tag repräsentiert und den ursprünglichen Knoten zum Kind des neuen `<b>`-Knotens macht und ihm damit einen neuen Elternknoten gibt. In einem HTML-Dokument bewirkt das, dass der gesamte Text in diesem Knoten und seinen Nachfahren fett dargestellt wird.

Beispiel 15-7. Einem Knoten mit einem <b>-Element einen neuen Elternknoten geben

    <script>
    // Diese Funktion erwartet einen Node n, ersetzt ihn im Baum durch einen Elementknoten,
    // der ein HTML-<b>-Tag repräsentiert, und macht den ursprünglichen Knoten dann zu einem
    // Kind des neuen <b>-Elements.
    function machFett(n) {
      if (typeof n == "string") n = document.getElementById(n); // Knoten suchen
      var b = document.createElement("b"); // Ein neues <b>-Element erzeugen
      var elternknoten = n.parentNode;   // Den Elternknoten des Knotens holen
      elternknoten.replaceChild(b, n);   // Den Knoten durch das <b>-Tag ersetzen
      b.appendChild(n);          // Den Knoten zu einem Kind des <b>-Elements machen
    }
    </script>

    <!-- Ein paar Beispielabsätze -->
    <p id="p1">Das <i>ist</i> Absatz #1.</p>
    <p id="p2">Das <i>ist</i> Absatz #2.</p>

    <!-- Ein Button, der die Funktion machFett( ) auf dem Element p1 aufruft. -->
    <button onclick="machFett('p1');">Fett machen</button>

## 15.7.1 Attribute verändern

Neben der Modifikation von Dokumenten durch die Veränderung von Text oder die Umordnung oder Zuweisung zu anderen Elternknoten von Elementen können auch substanzielle Änderungen an einem Dokument durchgeführt werden, indem man einfach Attributwerte auf Dokumentelementen setzt. Eine Möglichkeit, das zu tun, bietet die Methode `element.setAttribute( )`. Zum Beispiel:

    var ueberschrift = document.getElementById("ueberschrift"); // Ein benanntes Element
                                   // suchen
    ueberschrift.setAttribute("align", "center");        // align='center' setzen

Die DOM-Elemente, die HTML-Attribute repräsentieren, definieren JavaScript-Eigenschaften, die jedem ihrer Standard-Attribute entsprechen (selbst veralteten wie `align`). Die gleiche Wirkung können Sie also auch mit diesm Code erreichen:

    var ueberschrift = document.getElementById("ueberschrift");
    ueberschrift.align = "center"; // Die Eigenschaft align setzen.

Wie in Kapitel 16 gezeigt wird, können Sie ein ganzes Universum nützlicher Effekte erreichen, indem Sie die CSS-Style-Eigenschaften von HTML-Elementen auf diese Weise ändern. Wird das gemacht, wird nicht die Struktur oder der Inhalt des Dokuments geändert, sondern stattdessen seine _Darstellung_.

## 15.7.2 Mit Dokumentfragmenten arbeiten

Ein DocumentFragment ist ein besonderer Typ von Knoten, der nicht in einem Dokument selbst erscheint, sondern als temporärer Container für eine sequenzielle Sammlung von Knoten dient und es ermöglicht, diese Knoten als zusammenhängendes Objekt zu manipulieren. Wenn ein DocumentFragment in ein Dokument eingefügt wird (mit einer der Node-Methoden `appendChild( )`, `insertBefore( )` oder `replaceChild( )`), ist es nicht das DocumentFragment, das eingefügt wird, sondern alle seine Kinder.

Ein DocumentFragment können Sie mit `document.createDocumentFragment( )` erzeugen. Mit `appendChild( )` oder einer verwandten Node-Methode können Sie einem DocumentFragment Knoten hinzufügen. Und wenn Sie dann bereit sind, diese Knoten dem Dokument hinzuzufügen, fügen Sie ihm das DocumentFragment selbst hinzu. Nachdem Sie das getan haben, ist das Fragment leer und kann erst wieder verwendet werden, wenn ihm neue Kinder hinzugefügt wurden. Beispiel 15-8 zeigt diesen Vorgang. Es definiert eine `umkehren( )`-Funktion, die ein DocumentFragment als temporären Container verwendet, während sie die Reihenfolge der Kinder eines Node-Objekts umkehrt.

Beispiel 15-8. Ein DocumentFragment einsetzen

    // Die Reihenfolge der Kinder des Node-Objekts n umkehren.
    function umkehren(n) {
      // Ein leeres DocumentFragment als temporären Container erzeugen.
      var f = document.createDocumentFragment( );

      // Jetzt die Kinder rückwärts durchlaufen und jedes in das Fragment verschieben.
      // Das letzte Kind von n wird das erste Kind von f und umgekehrt. Beachten Sie,
      // dass ein Kind automatisch aus n entfernt wird, wenn es f hinzugefügt wird
      while(n.lastChild) f.appendChild(n.lastChild);

      // Schließlich die Kinder von f alle auf einmal zurückschieben.
      n.appendChild(f);
    }

# 15.8 Einem Dokument Inhalt hinzufügen

Die Methoden `Document.createElement( )` und `Document.createTextNode( )` erzeugen neue Element- und Text-Knoten, und die Methoden `Node.appendChild( )`, `Node.insertBefore( )` und `Node.replaceChild( )` können verwendet werden, um sie einem Dokument hinzuzufügen. Mit diesen Methoden können Sie einen DOM-Baum beliebiger Dokumentinhalte aufbauen.

Beispiel 15-9 ist ein ausführlicheres Beispiel, das eine `log( )`-Funktion für die Protokollierung von Nachrichten und Objekten definiert. Dieses Beispiel schließt außerdem eine `log.debug( )`-Hilfsfunktion ein, die eine nützliche Alternative zur Einfügung von `alert( )`-Aufrufen sein kann, wenn man JavaScript-Code debuggen muss. Die »Nachricht«, die an die Funktion `log( )` übergeben wird, kann entweder ein String mit Klartext sein oder ein JavaScript-Objekt. Wenn ein String protokolliert wird, wird er einfach angezeigt, wie er ist. Wenn ein Objekt protokolliert wird, wird es als eine Tabelle mit Eigenschaftsnamen und -werten angezeigt. In beiden Fällen erzeugen die Funktionen `createElement( )` und `createTextNode( )` den neuen Inhalt.

Mit einem geeigneten CSS-Stylesheet (das im Beispiel gezeigt wird) erzeugt die `log( )`-Funktion von Beispiel 15-9 Ausgaben wie die, die in Abbildung 15-4-Funktion") gezeigt werden.

Beispiel 15-9 ist lang, aber es ist gut kommentiert und verdient eine sorgfältige Betrachtung. Achten Sie insbesondere auf die Aufrufe von `createElement( )`, `createTextNode( )` und `appendChild( )`. Die private Funktion `log.makeTable( )` zeigt, wie diese Funktionen verwendet werden, um die relativ komplexe Struktur einer HTML-Tabelle zu erzeugen.

Beispiel 15-9. Eine Logging-Einrichtung für clientseitiges JavaScript

    /*
     * Log.js: Eine unaufdringliche Logging-Einrichtung
     *
     * Dieses Modul definiert ein globales Symbol: eine Funktion namens log( ).
     * Protokollieren Sie eine Nachricht, indem Sie die Funktion mit 2 oder 3 Argumenten aufrufen:
     *
     *  category: der Typ der Nachricht. Er ist erforderlich, damit Nachrichten
     *   unterschiedlicher Typen ausgewählt aktiviert oder deaktiviert werden können und
     *   damit sie unabhängig gestylt werden können. Siehe unten.
     *  message: der zu protokollierende Text. Kann leer sein, wenn ein Objekt angegeben wird
     *
     *  object: ein zu protokollierendes Objekt. Dieses Argument ist optional. Wird es
     *   übergeben, werden die Eigenschaften des Objekts selbst in Form einer Tabelle
     *   protokolliert. Jede Eigenschaft, deren Wert selbst ein Objekt ist, kann rekursiv
     *   protokolliert werden.
     *
     * Hilfsfunktionen:
     *
     *  Die Funktionen log.debug( ) und log.warn( ) sind Hilfsfunktionen, die einfach die
     *  Funktion log( ) mit den festen Kategorien "debug" und "warning" aufrufen. Es ist
     *  trivial, eine Hilfsfunktion zu definieren, die die eingebaute alert()-Methode durch
     *  eine Methode ersetzt, die log( ) aufruft.
     *
     * Logging aktivieren
     *
     *  Log-Nachrichten werden per Default *nicht* angezeigt. Sie können die Anzeige
     *  einer bestimmten Kategorie auf zwei verschiedene Arten aktivieren. Die erste ist,
     *  ein <div> oder ein anderes Container-Element mit einer ID der Form "<kategorie>_log"
     *  zu erzeugen. Für Nachrichten mit der Kategorie "debug" könnten Sie Folgendes in das
     *  Dokument einfügen:
     *
     *   <div id="debug_log"></div>
     *
     *  In diesem Fall werden alle Nachrichten in der angegebenen Kategorie an diesen
     *  Container angehängt, der gestylt werden kann, wie es Ihnen gefällt.
     *
     *  Die zweite Möglichkeit, Nachrichten für eine besimmte Kategorie zu aktivieren, ist,
     *  eine geeignete Logging-Option zu setzen. Um die Kategorie "debug" zu aktivieren,
     *  würden Sie log.options.debugEnabled = true setzen. Wenn Sie das tun, wird ein
     *  <div class="log"> für die Log-Nachrichten erzeugt. Wenn Sie die Protokollierung
     *  von Log-Nachrichten deaktivieren wollen, auch wenn ein geeigneter Container
     *  existiert, setzen Sie eine andere Option: log.options.debugDisabled=true.
     *  Setzen Sie diese Option wieder auf false, um die Protokollierung von Nachrichten
     *  für diese Kategorie wieder zu aktivieren.
     *
     * Log-Nachrichten stylen
     *
     *  Zusätzlich zum Styling des Containers können Sie CSS einsetzen, um die Anzeige der
     *  einzelnen Log-Meldungen zu stylen. Jede Log-Meldung wird in ein <div>-Tag
     *  platziert, und ihr wird eine CSS-Klasse der Form <Kategorie>_message gegeben.
     *  Debugging-Meldungen hätten die Klasse "debug_message".
     *
     * Log-Optionen
     *
     *  Das Logging-Verhalten kann geändert werden, indem Eigenschaften des log.options-
     *  Objekt gesetzt werden, wie die Optionen, die beschrieben wurden, um das Logging
     *  für bestimmte Kategorien zu aktivieren oder zu deaktivieren. Es sind ein paar weitere
     *  Optionen verfügbar:
     *
     *   log.options.timestamp: Wenn diese Eigenschaft true ist, wird jeder Log-Meldung
     *    das Datum und die Uhrzeit hinzugefügt.
     *
     *   log.options.maxRecursion: Ein Integer, der die maximale Anzahl von eingebetteten
     *    Tabellen angibt, die beim Logging von Objekten angezeigt werden sollen. Setzen
     *    Sie diese Option auf 0, wenn Sie nie eine Tabelle in einer Tabelle haben wollen.
     *
     *   log.options.filter: Eine Funktion, die beim Logging eines Objekts Eigenschaften
     *    herausfiltert. Einer Filterfunktion wird der Name und der Wert einer Eigenschaft
     *    übergeben, und sie liefert true, wenn die Eigenschaft in der Objekt-Tabelle
     *    erscheinen soll, und false andernfalls.
     */
    function log(category, message, object) {
      // Wenn diese Kategorie explizit deaktiviert wird, wird nichts getan.
      if (log.options[category + "Disabled"]) return;

      // Den Container finden.
      var id = category + "_log";
      var c = document.getElementById(id);

      // Wenn es keinen Container gibt, das Logging dieser Kategorie aber aktiviert ist,
      // wird der Container erzeugt.
      if (!c && log.options[category + "Enabled"]) {
        c = document.createElement("div");
        c.id = id;
        c.className = "log";
        document.body.appendChild(c);
      }

      // Wenn es immer noch keinen Container gibt, wird die Meldung ignoriert.
      if (!c) return;

      // Wenn Zeitstempel aktiviert sind, fügen wir den Zeitstempel hinzu.
      if (log.options.timestamp)
        message = new Date( ) + ": " + (message?message:"");

      // Ein <div>-Element für die Aufnahme des Log-Eintrags erzeugen.
      var entry = document.createElement("div");
      entry.className = category + "_message";

      if (message) {
        // Dem Element die Meldung hinzufügen.
        entry.appendChild(document.createTextNode(message));
      }

      if (object && typeof object == "object") {
        entry.appendChild(log.makeTable(object, 0));
      }

      // Schließlich den Eintrag dem Logging-Container hinzufügen.
      c.appendChild(entry);
    }

    // Eine Tabelle für die Anzeige der Eigenschaften des angegebenen Objekts erzeugen.
    log.makeTable = function(object, level) {
      // Wurde die maximale Rekursionstiefe erreicht, wird stattdessen ein Textknoten
      // zurückgeliefert.
      if (level > log.options.maxRecursion)
        return document.createTextNode(object.toString( ));

      // Die Tabelle erzeugen, die zurückgeliefert werden soll.
      var table = document.createElement("table");
      table.border = 1;

      // Der Tabelle einen Name|Typ|Wert-Header hinzufügen.
      var header = document.createElement("tr");
      var headerName = document.createElement("th");
      var headerType = document.createElement("th");
      var headerValue = document.createElement("th");
      headerName.appendChild(document.createTextNode("Name"));
      headerType.appendChild(document.createTextNode("Typ"));
      headerValue.appendChild(document.createTextNode("Wert"));
      header.appendChild(headerName);
      header.appendChild(headerType);
      header.appendChild(headerValue);
      table.appendChild(header);

      // Die Eigenschaftsnamen des Objekts abrufen und alphabetisch sortieren.
      var names = [];
      for(var name in object) names.push(name);
      names.sort( );

      // Jetzt die Eigenschaften durchlaufen.
      for(var i = 0; i < names.length; i++) {
        var name, value, type;
        name = names[i];
        try {
          value = object[name];
          type = typeof value;
        }
        catch(e) { // Das sollte nicht passieren, kann in Firefox aber vorkommen.
          value = "<unknown value>";
          type = "unknown";
        };

        // Diese Eigenschaft überspringen, wenn sie von einem Filter verworfen wird.
        if (log.options.filter && !log.options.filter(name, value)) continue;

        // Nie Funktionsquellcode anzeigen: Er braucht zu viel Platz.
        if (type == "function") value = "{/*Quellcode unterdrückt*/}";

        // Eine Tabellenzeile für die Anzeige von Name, Typ und Wert für die Eigenschaft
        // erzeugen.
        var row = document.createElement("tr");
        row.vAlign = "top";
        var rowName = document.createElement("td");
        var rowType = document.createElement("td");
        var rowValue = document.createElement("td");
        rowName.appendChild(document.createTextNode(name));
        rowType.appendChild(document.createTextNode(type));

        // Objekte rekursiv verarbeiten, um sie als Tabellen anzuzeigen.
        if (type == "object")
          rowValue.appendChild(log.makeTable(value, level+1));
        else
          rowValue.appendChild(document.createTextNode(value));

        // Der Zeile die Zellen hinzufügen und die Zeile der Tabelle.
        row.appendChild(rowName);
        row.appendChild(rowType);
        row.appendChild(rowValue);

        table.appendChild(row);
      }

      // Zum Abschluss die Tabelle zurückliefern.
      return table;
    }

    // Ein leeres Optionsobjekt erzeugen.
    log.options = {};

    // Hilfsversionen der Funktion mit vorgegebenen Kategorien
    log.debug = function(message, object) { log("debug", message, object); };
    log.warn = function(message, object) { log("warning", message, object); };

    // Kommentieren Sie die folgende Zeile aus, um alert( )-Dialoge in Log-Meldungen umzuwandeln.
    // function alert(msg) { log("alert", msg); }

Die Debugging-Meldungen, die in Abbildung 15-4-Funktion") erscheinen, wurden mit dem folgenden einfachen Code erzeugt:

    <head>
    <script src="Log.js"></script>             <!-- log( ) einschließen -->
    <link rel="stylesheet" type="text/css" href="log.css">
        <!-- Styles einschließen -->
    </head>
    <body>
    <script>
    function macheRechteck(x, y, w, h) { // Das ist die zu debuggende Funktion
      log.debug("Eintritt in macheRechteck");  // Eine Meldung protokollieren

Abbildung 15-4. Ausgabe der log( )-Funktion

    var r = {x:x, y:y, size: { w:w, h:h }};
      log.debug("Neues Rechteck", r);       // Ein Objekt protokollieren
      log.debug("Austritt aus macheRechteck");   // Eine weitere Meldung protokollieren
      return r;
    }
    </script>
    <!-- Dieser Button ruft die Funktion auf, die wir debuggen möchten. -->
    <button onclick="macheRechteck(1,2,3,4);">Mache Rechteck</button>
    <!-- Hier werden die Log-Meldungen ausgegeben. -->
    <!-- Wir aktivieren das Logging, indem wir dieses <div> in das Dokument einfügen. -->
    <div id="debug_log" class="log"></div>
    </body>

Das Erscheinungsbild der Log-Meldungen in Abbildung 15-4-Funktion") wurde mit CSS-Styles erzeugt, die mit einem `<link>`-Tag in den Code importiert wurden. Die Styles, die zur Erzeugung der Abbildung verwendet wurden, sind folgende:

    #debug_log { /* Styles für den Container mit den Debug-Meldungen */
      background-color: #aaa;  /* grauer Hintergrund */
      border: solid black 2px;  /* schwarzer Rahmen */
      overflow: auto;      /* Bildlaufleisten */
      width: 75%;        /* nicht so breit wie das vollständige Fenster */
      height: 300px;       /* nicht zu viel vertikalen Raum einnehmen */
    }

    #debug_log:before { /* Unserem Logging-Bereich einen Titel geben */
      content: "Debugging-Meldungen";
      display: block;
      text-align: center;
      font: bold 18pt sans-serif ;
    }

    .debug_message { /* Zwischen den Debug-Meldungen eine dünne schwarze Linie ausgeben */
      border-bottom: solid black 1px;
    }

Mehr zu CSS werden Sie in Kapitel 16 lernen. Es ist nicht wichtig, dass Sie hier die CSS-Details verstehen. Das CSS wurde hier eingeschlossen, damit Sie sehen können, wie mit dem dynamisch generierten Inhalt, der von der Funktion `log( )` erzeugt wurde, Styles verknüpft werden können.

## 15.8.1 Hilfsmethoden zur Erstellung von Knoten

Beim Lesen von Beispiel 15-9 ist Ihnen vielleicht aufgefallen, dass die API zur Erstellung von Dokumentinhalten ziemlich ausufernd ist: Erst erzeugen Sie ein Element, dann setzen Sie seine Attribute, und dann erzeugen Sie einen Textknoten und fügen ihn dem Element hinzu. Als Nächstes fügen Sie das Element seinem Elternelement hinzu. Und so weiter. Allein die Erstellung eines `<table>`-Elements, das Setzen eines Attributs und die Hinzufügung einer Kopfzeile hat in Beispiel 15-9 13 Codezeilen in Anspruch genommen. Beispiel 15-10 definiert eine Hilfsfunktion zur Element-Erstellung, die diese Art repetitiver DOM-Programmierung vereinfacht.

Beispiel 15-10 definiert eine einzige Funktion namens `make( )`. `make( )` erzeugt ein Element mit dem angegebenen Tag-Namen, setzt auf ihm Attribute und fügt ihm Kinder hinzu. Attribute werden als Eigenschaften eines Objekts angegeben und Kinder werden in einem Array übergeben. Die Elemente dieses Arrays können Strings sein, die in Textknoten umgewandelt werden, oder andere Element-Objekte, die üblicherweise mit eingebetteten `make( )`-Aufrufen erzeugt werden.

`make( )` hatt eine flexible Aufrufsyntax, die zwei Kurzformen ermöglicht. Erstens kann das Attribut-Argument weggelassen werden, wenn keine Attribute angegeben werden, und an seiner Stelle das Kindelemente-Argument übergeben werden. Wenn es nur ein Kindelement gibt, kann dieses zweitens direkt übergeben werden und muss nicht in ein Array mit nur einem Element gepackt werden. Der einzige Nachteil ist, dass diese beiden Kurzformen nicht kombiniert werden können, es sei denn, das einzige Kind ist ein Textknoten, der als String übergeben wird.

Unter Verwendung von `make( )` können die 13 Zeilen Code zur Erstellung einer `<table>` und ihrer Kopfzeile aus Beispiel 15-9 zu Folgendem verkürzt werden:

    var table = make("table", {border:1}, make("tr",
                       [make("th", "Name"),
                             make("th", "Typ"),
                             make("th", "Wert")]));

Aber das geht noch besser. In Beispiel 15-10 folgt auf die Funktion `make( )` eine weitere Funktion namens `maker( )`. Übergeben Sie `maker( )` einen Tag-Namen, liefert die Funktion eine eingebettete Funktion zurück, die `make( )` unter Vorgabe des von Ihnen angegebenen Tag-Namens aufruft. Wenn Sie viele Tabellen erstellen möchten, können Sie folgendermaßen Funktionen für häufig verwendete Tabellen-Tags definieren:

    var table = maker("table"), tr = maker("tr"), th = maker("th");

Sind diese Erstellungsfunktionen definiert, reduziert sich der Code für die Tabellen- und die Kopfzeilenerstellung auf eine einzige Zeile:

    var meineTabelle = table({border:1}, tr([th("Name"), th("Typ"), th("Wert")]));

Beispiel 15-10. Hilfsfunktion zur Elementerstellung

    /**
     * make(tagname, attributes, children):
     *  Erzeugt ein HTML-Element mit dem angegebenen Tag-Namen, Attributen und Kindern.
     *
     * Das Argument attributes ist ein JavaScript-Objekt: Die Namen und Werte seiner
     * Eigenschaften werden als die Namen und Werte der zu setzenden Attribute behandelt.
     * Wenn attributes null ist und children ein Array oder ein String ist, kann attributes
     * vollständig weggelassen werden und children als zweites Argument übergeben werden.
     *
     * Das Argument children ist normalerweise ein Array mit Kindelementen, die dem erzeugten
     * Element hinzugefügt werden sollen. Wenn es keine Kinder gibt, kann dieses Argument
     * weggelassen werden. Wenn es nur ein Kind gibt, kann es direkt übergeben werden und
     * muss nicht in ein Array eingeschlossen werden. (Aber wenn das Kind kein String
     * ist und keine Attribute angegeben werden, muss ein Array verwendet werden.)
     *
     * Beispiel: make("p", ["Dies ist ein ", make("b", "fettes"), " Wort."]);
     *
     * Inspiriert von der Bibliothek MochiKit (http://mochikit.com) von Bob Ippolito
     */
    function make(tagname, attributes, children) {

      // Wenn bei einem Aufruf mit zwei Argumenten das Argument attributes ein Array oder
      // ein String ist, sollte es eigentlich das Argument children sein.
      if (arguments.length == 2 &&
        (attributes instanceof Array || typeof attributes == "string")) {
        children = attributes;
        attributes = null;
      }

      // Das Element erzeugen.
      var e = document.createElement(tagname);

      // Die Attribute setzen.
      if (attributes) {
        for(var name in attributes) e.setAttribute(name, attributes[name]);
      }
      // Kinder hinzufügen, wenn welche angegeben wurden.
      if (children != null) {
        if (children instanceof Array) { // Wenn es tatsächlich ein Array ist
          for(var i = 0; i < children.length; i++) { // die Kinder durchlaufen.
            var child = children[i];
            if (typeof child == "string")     // Textknoten verarbeiten
              child = document.createTextNode(child);
            e.appendChild(child); // Annehmen, dass alles andere ein Node ist
          }
        }
        else if (typeof children == "string") // Ein einzelnes Textkind verarbeiten
          e.appendChild(document.createTextNode(children));
        else e.appendChild(children);     // Alle anderen einzelnen Kinder verarbeiten
      }

      // Schließlich das Element zurückliefern.
      return e;
    }

    /**
     * maker(tagname): Liefert eine Funktion zurück, die make( ) für das angegebene Tag aufruft.
     * Beispiel: var table = maker("table"), tr = maker("tr"), td = maker("td");
     */
    function maker(tag) {
      return function(attrs, children) {
        if (arguments.length == 1) return make(tag, attrs);
        else return make(tag, attrs, children);
      }
    }

## 15.8.2 Die innerHTML-Eigenschaft

Auch wenn sie vom W3C nie als ein offizieller Teil des DOMs sanktioniert wurde, ist die `innerHTML`-Eigenschaft von HTMLElement-Knoten eine wichtige und mächtige Eigenschaft. Wenn Sie den Wert dieser Eigenschaft für ein HTML-Element abfragen, erhalten Sie einen String mit HTML-Text, der die Kinder dieses Elements repräsentiert. Wenn Sie diese Eigenschaft setzen, ruft der Browser seinen HTML-Parser auf, um Ihren String zu parsen und die Kinder des Elements durch das zu ersetzen, was der Browser zurückliefert.

Ein HTML-Dokument als einen String mit HTML-Text zu beschreiben, ist in der Regel bequemer und kompakter, als eine Beschreibung über eine Folge von `createElement( )` und `appendChild( )` aufzurufen. Betrachten Sie noch einmal den Code aus Beispiel 15-9, der ein neues `<table>`-Element erzeugt und ihm eine Kopfzeile hinzugefügt hat. Wenn Sie `innerHTML` verwenden, können Sie diesen ziemlich ausufernden Code folgendermaßen neu schreiben:

    var table = document.createElement("table"); // Das <table>-Element erzeugen
    table.border = 1;               // Ein Attribut setzen
    // Der Tabelle die Name|Typ|Wert-Kopfzeile hinzufügen.
    table.innerHTML = "<tr><th>Name</th><th>Typ</th><th>Wert</th></tr>";

Webbrowser sind, fast schon definitionsgemäß, sehr gut beim Parsen von HTML. Es stellt sich heraus, dass die Verwendung von `innerHTML` hinreichend effizient ist, insbesondere, wenn Sie mit großen Stücken von HTML-Text arbeiten, der geparst werden muss. Beachten Sie allerdings, dass es in der Regel nicht effizient ist, der Eigenschaft `innerHTML` mit dem `+=`-Operator Textstückchen anzufügen, da dazu sowohl ein Serialisierungs- als auch ein Parsing-Schritt erforderlich ist.

`innerHTML` wurde von Microsoft im IE 4 eingeführt. Es ist die wichtigste und am häufigsten verwendete Eigenschaft aus einem Quartett miteinander verwandter Eigenschaften. Die anderen drei Eigenschaften, `outerHTML`, `innerText` und `outerText`, werden von Firefox und verwandten Browsern nicht unterstützt. Sie werden am Ende dieses Kapitels in "15.11 Das IE 4 DOM" beschrieben.

# 15.9 Beispiel: Ein dynamisch erzeugtes Inhaltsverzeichnis

Die vorangehenden Abschnitte haben Ihnen gezeigt, wie Sie die Core DOM API einsetzen können, um ein Dokument zu durchqueren, Elemente aus einem Dokument auszuwählen und Dokumentinhalte zu ändern und hinzuzufügen. Beispiel 15-11 bringt all diese Teile zusammen, um automatisch ein Inhaltsverzeichnis für ein HTML-Dokument zu erstellen.

Das Beispiel definiert eine einzige Funktion namens `maketoc( )` und registriert einen `onload`-Event-Handler, damit die Funktion automatisch ausgeführt wird, wenn das Laden des Dokuments abgeschlossen ist. Wenn `maketoc( )` ausgeführt wird, durchquert sie das Dokument und sucht nach den Tags `<h1>, <h2>, <h3>, <h4>, <h5> und <h6`, bei denen es davon ausgeht, dass sie den Anfang von wichtigen Abschnitten im Dokument markieren. `maketoc( )` sucht nach einem Element mit der ID »toc« und baut in diesem Element ein Inhaltsverzeichnis auf. Als Teil des Vorgangs fügt `maketoc( )` dem Titel jedes Abschnitts eine Abschnittsnummer hinzu und fügt vor jedem Abschnitt einen benannten Anker ein. Dann fügt es am Anfang jedes Abschnitts einen Link ein, der zum Inhaltsverzeichnis zurückführt. Abbildung 15-5 zeigt, wie ein Inhaltsverzeichnis aussieht, das von der Funktion `maketoc( )` erstellt wurde.

Wenn Sie lange Dokumente pflegen und überarbeiten, die mit `<h1>`, `<h2>` und verwandten Tags in Abschnitte eingeteilt werden, könnte die Funktion `maketoc( )` für Sie interessant sein. Inhaltsverzeichnisse sind in langen Dokumenten ziemlich nützlich. Aber wenn Sie ein Dokument häufig überarbeiten, kann es schwierig werden, das Inhaltsverzeichnis mit dem Dokument selbst synchron zu halten. Der Code in Beispiel 15-11 wurde so geschrieben, dass er barrierefrei ist. Schließen Sie einfach das Modul in Ihr HTML-Dokument ein, und stellen Sie `maketoc( )` ein Container-Element zur Verfügung, in das das Inhaltsverzeichnis eingefügt werden soll. Optional können Sie das Inhaltsverzeichnis noch mit CSS stylen. Sie könnten die Funktion so verwenden:

Abbildung 15-5. Ein dynamisch erzeugtes Inhaltsverzeichnis

    <script src="TOC.js"></script> <!-- Die Funktion maketoc( ) laden -->
    <style>
    #toc { /* Diese Styles werden auf den Container toc angewendet */
      background: #ddd;       /* hellgrauer Hintergrund */
      border: solid black 1px;    /* einfacher Rahmen */
      margin: 10px; padding: 10px;  /* Einrückung */
    }
    .TOCEntry { font-family: sans-serif; } /* Verzeichniseinträge in Sans-Serif */
    .TOCEntry a { text-decoration: none; } /* Verzeichnis-Links nicht unterstrichen */
    .TOCLevel1 { font-size: 16pt; font-weight: bold; } /* Ebene 1 groß und fett */
    .TOCLevel2 { font-size: 12pt; margin-left: .5in; } /* Ebene 2 eingerückt */
    .TOCBackLink { display: block; }    /* die Links zurück auf einer eigenen Zeile */
    .TOCSectNum:after { content: ":"; }  /* nach den Abschnittsnummern einen Doppelpunkt
                        einfügen */
    </style>
    <body>
    <div id="toc"><h1>Inhaltsverzeichnis</h1></div> <!-- hier kommt das Inhaltsverzeichnis hin -->
    <!--
      ... hier kommt der Rest des Dokuments hin ...
    -->

Der Code für das Modul _TOC.js_ folgt. Beispiel 15-11 ist lang, aber es ist gut dokumentiert und verwendet Techniken, die bereits gezeigt wurden. Es lohnt sich, ein praktisches Beispiel für die Macht des W3C DOMs zu untersuchen.

Beispiel 15-11. Inhaltsverzeichnisse automatisch generieren

    /**
     * TOC.js: Ein Inhaltsverzeichnis für ein Dokument erzeugen.
     *
     * Dieses Modul definiert die Funktion maketoc( ) und registriert einen onload-
     * Event-Handler, damit die Funktion automatisch aufgerufen wird, wenn das Laden des
     * Dokuments abgeschlossen ist. Wenn maketoc( ) läuft, sucht die Funktion erst nach
     * einem Dokument-Element mit der ID "toc". Wenn es kein derartiges Element gibt, macht
     * maketoc( ) nichts. Wenn es ein derartiges Element gibt, durchquert maketoc( ) das
     * Dokument, um alle <h1>- bis <h6>-Tags zu suchen, und erzeugt ein Inhaltsverzeichnis,
     * das es an das "toc"-Element anhängt. maketoc( ) fügt jeder Abschnittsüberschrift
     * eine Abschnittsnummer hinzu und fügt vor jeder Überschrift einen Link zurück zum
     * Inhaltsverzeichnis ein. maketoc( ) generiert Links und Anker mit Namen, die mit
     * "TOC" beginnen. Sie sollten dieses Präfix in Ihrem HTML also vermeiden.
     *
     * Die Einträge im generierten TOC können mit CSS gestylt werden. Alle Einträge
     * haben die Klasse "TOCEntry". Einträge haben außerdem eine Klasse, die der Ebene
     * der Abschnittsüberschrift entspricht. <h1>-Tags generieren Einträge mit der Klasse
     * "TOCLevel1". <h2>-Tags generieren Einträge mit der Klasse "TOCLevel2" und so weiter.
     * Abschnittsnummern, die in Überschriften eingefügt werden, haben die Klasse "TOCSectNum",
     * und die generierten Links zurück zum TOC haben die Klasse "TOCBackLink".
     *
     * Standardmäßig lauten die generierten Links zurück zum TOC "Inhalt".
     * Überschreiben Sie diesen Default (z.B. für Internationalisierungszwecke), indem Sie
     * die Eigenschaft maketoc.backlinkText auf den gewünschten Text setzen.
     **/
    function maketoc( ) {
      // Den Container suchen. Wenn es keinen gibt, still zurückkehren.
      var container = document.getElementById('toc');
      if (!container) return;

      // Das Dokument durchqueren und alle <h1>...<h6>-Tags einem Array hinzufügen
      var sections = [];
      findSections(document, sections);

      // Vor dem Container-Element einen Anker einfügen, damit wir darauf linken können.
      var anchor = document.createElement("a"); // Ein <a>-Knoten erzeugen
      anchor.name = "TOCtop";          // Ihm einen Namen geben
      anchor.id = "TOCtop";           // Und eine id (der IE braucht das)
      container.parentNode.insertBefore(anchor, container); // Vor dem toc einfügen

      // Ein Array initialisieren, das die Abschnittsnummern nachhält
      var sectionNumbers = [0,0,0,0,0,0];

      // Jetzt alle Abschnittsüberschriftselemente durchlaufen, die gefunden wurden.
      for(var s = 0; s < sections.length; s++) {
        var section = sections[s];

        // Ermitteln, welche Überschriftsebene es ist.
        var level = parseInt(section.tagName.charAt(1));
        if (isNaN(level) || level < 1 || level > 6) continue;

        // Die Abschnittsnummer für diese Überschriftsebene inkrementieren.
        // Und alle kleineren Ebenennummern auf null zurücksetzen
        sectionNumbers[level-1]++;
        for(var i = level; i < 6; i++) sectionNumbers[i] = 0;

        // Jetzt die Abschnittsnummern für alle Überschriftsebenen kombinieren,
        // um Abschnittsnummern wie 2.3.1 zu erzeugen.
        var sectionNumber = "";
        for(i = 0; i < level; i++) {
          sectionNumber += sectionNumbers[i];
          if (i < level-1) sectionNumber += ".";
        }

        // Der Abschnittsüberschrift die Abschnittsnummer und ein Leerzeichen hinzufügen.
        // Wir platzieren die Nummer in ein <span>, damit wir sie stylen können.
        var frag = document.createDocumentFragment( ); // Zur Aufnahme des Span und des
                               // Leerzeichens
        var span = document.createElement("span");  // Span zur Aufnahme der Nummer
        span.className = "TOCSectNum";        // Das Span stylebar machen
        span.appendChild(document.createTextNode(sectionNumber)); // sect# hinzufügen
        frag.appendChild(span);             // Das Span dem Fragment hinzufügen
        frag.appendChild(document.createTextNode(" ")); // Dann ein Leerzeichen hinzufügen
        section.insertBefore(frag, section.firstChild); // Beides der Überschrift hinzufügen

        // Einen Anker zur Markierung des Anfangs des Abschnitts erzeugen.
        var anchor = document.createElement("a");
        anchor.name = "TOC"+sectionNumber; // Dem Anker für das Linken einen Namen geben
        anchor.id = "TOC"+sectionNumber;  // Im IE brauchen generierte Anker eine id

        // Den Anker in einen Link zurück zum Inhalt einhüllen.
        var link = document.createElement("a");
        link.href = "#TOCtop";
        link.className = "TOCBackLink";
        link.appendChild(document.createTextNode(maketoc.backlinkText));
        anchor.appendChild(link);

        // Anker und Link unmittelbar vor der Abschnittsüberschrift einfügen.
        section.parentNode.insertBefore(anchor, section);
        // Jetzt einen Link auf diesen Abschnitt erzeugen.
        var link = document.createElement("a");
        link.href = "#TOC" + sectionNumber;  // Das Link-Ziel setzen
        link.innerHTML = section.innerHTML;  // Den Link auf die Überschrift setzen

        // Den Link in ein div platzieren, das auf Basis der Ebene gestylt werden kann.
        var entry = document.createElement("div");
        entry.className = "TOCEntry TOCLevel" + level; // Für das CSS-Styling
        entry.appendChild(link);

        // Und dann das Div dem TOC-Container hinzufügen.
        container.appendChild(entry);
      }

      // Diese Methode durchquert rekursiv den Baum mit der Wurzel n, sucht nach
      // den Tags <h1> bis <h6> und hängt sie an das Array sections an.
      function findSections(n, sects) {
        // Alle Kinder von n durchlaufen.
        for(var m = n.firstChild; m != null; m = m.nextSibling) {
          // Alle Knoten überspringen, die keine Elemente sind.
          if (m.nodeType != 1 /* Node.Element_NODE */) continue;
          // Das Container-Element überspringen, da es eine eigene Überschrift haben kann.
          if (m == container) continue;
          // Als Optimierung <p>-Tags überspringen, da in Absätzen keine Überschriften
          // erscheinen sollen. (Wir könnten auch Listen <pre>-Tags usw. überspringen,
          // aber <p> ist das am häufigsten vorkommende.)
          if (m.tagName == "P") continue; // Optimierung

          // Wird der Kindknoten nicht übersprungen, wird geprüft, ob er eine
          // Überschrift ist. Ist das der Fall, wird er dem Array hinzugefügt.
          // Andernfalls wird in ihn hinabgestiegen. Beachten Sie, dass das DOM
          // interface-basiert ist und nicht klassenbasiert. Wir können also nicht
          // einfach Folgendes prüfen (m instanceof HTMLHeadingElement).
          if (m.tagName.length==2 && m.tagName.charAt(0)=="H") sects.push(m);
          else findSections(m, sects);
        }
      }
    }

    // Das ist der Default-Text für die Links zurück zum Inhalt.
    maketoc.backlinkText = "Inhalt";

    // maketoc( ) registieren, damit die Funktion automatisch ausgeführt wird, wenn
    // das Laden des Dokuments abgeschlossen ist.
    if (window.addEventListener) window.addEventListener("load", maketoc, false);
    else if (window.attachEvent) window.attachEvent("onload", maketoc);

# 15.10 Ausgewählten Text abfragen

Manchmal ist es nützlich, wenn man ermitteln kann, welchen Text der Benutzer in einem Dokument ausgewählt hat. Das ist ein Bereich, in dem wenige Standardisierungen zur Verfügung stehen, aber es ist in allen modernen Browsern möglich, den ausgewählten Text abzufragen. Beispiel 15-12 zeigt, wie das geht.

Beispiel 15-12. Den aktuell ausgewählten Text abfragen

    function getSelectedText( ) {
      if (window.getSelection) {
        // Diese Technik wird am ehesten standardisiert werden.
        // getSelection( ) ruft ein Selection-Objekt ab, das wir nicht dokumentieren.
        return window.getSelection( ).toString( );
      }
      else if (document.getSelection) {
        // Das ist eine ältere Technik, die einen String zurückliefert
        return document.getSelection( );
      }
      else if (document.selection) {
        // Das ist eine IE-spezifische Technik.
        // Wir dokumentieren die IE-Eigenschaft selection oder TextRange-Objekte nicht.
        return document.selection.createRange( ).text;
      }
    }

Dem Code in diesem Beispiel müssen Sie in gewissem Maße einfach vertrauen. Die in diesem Beispiel verwendeten Selection- und TextRange-Objekte werden in diesem Buch nicht dokumentiert. Als dies geschrieben wurde, waren ihre APIs einfach zu komplex und zu wenig standardisiert, um sie hier zu behandeln. Aber da die Abfrage des ausgewählten Texts eine häufige und recht einfache Operation ist, ist es nützlich, sie hier zu illustrieren. Sie können sie einsetzen, um Bookmarklets zu erzeugen (siehe "13.4.1 Bookmarklets"), die auf dem ausgewählten Text operieren, indem sie ein Wort in einer Suchmaschen oder auf einer Referenz-Site nachschlagen. Der folgende HTML-Link schlägt beispielsweise den aktuellen Text in Wikipedia nach. Wird für ihn ein Lesezeichen erzeugt, werden der Link und die darin enthaltene JavaScript-URL zu einem Bookmarklet:

    <a href="javascript:
      var q;
      if (window.getSelection) q = window.getSelection( ).toString( );
      else if (document.getSelection) q = document.getSelection( );
      else if (document.selection) q = document.selection.createRange( ).text;
      void window.open('http://en.wikipedia.org/wiki/' + q);
    ">
    Ausgewählten Text in Wikipedia nachschlagen
    </a>

Es gibt in Beispiel 15-12 eine kleinere Inkompatibilität. Die `getSelection( )`-Methoden der Window- und Document-Objekte liefern den ausgewählten Text nicht zurück, wenn er sich in einem `<input>`\- oder `<textarea>`-Formularelement befindet: Sie liefern nur ausgewählten Text aus dem Dokument selbst zurück. Die IE-Eigenschaft `document.selection` liefert jedoch ausgewählten Text von allen Stellen des Dokuments zurück.

In Firefox definieren Texteingabeelemente die Eigenschaften `selectionStart` und `selectionEnd`, die Sie einsetzen können, um den ausgewählten Text abzufragen (oder zu setzen). Zum Beispiel:

    function getTextFieldSelection(e) {
       if (e.selectionStart != undefined && e.selectionEnd != undefined) {
         var start = e.selectionStart;
         var end = e.selectionEnd;
         return e.value.substring(start, end);
       }
       else return ""; // In diesem Browser nicht unterstützt
    }

# 15.11 Das IE 4 DOM

Obwohl der IE 4 das W3C DOM nicht implementiert, unterstützt er eine API, die viele Fähigkeiten bietet, die denen des Kern-W3C DOMs entsprechen. IE 5 und höher unterstützen das IE 4 DOM, und einige andere Browser bieten zumindest eine partielle Kompatibilität. Als dies geschrieben wurde, war der IE 4-Browser nicht mehr weit verbreitet. Neu geschriebener JavaScript-Code muss normalerweise nicht mit Kompatibilität zum IE 4 geschrieben werden. Deswegen wurden große Teile des Referenzmaterials für das IE 4 DOM in dieser Auflage des Buchs aus Teil IV gelöscht. Trotzdem gibt es immer noch eine große Menge bestehenden Codes, der immer noch das IE 4 DOM verwendet. Deswegen ist es nützlich, wenn man mit dieser API zumindest in ihren Grundzügen vertraut ist.

## 15.11.1 Ein Dokument durchqueren

Das W3C DOM gibt vor, dass alle Node-Objekte, das Document-Objekt und alle Element-Objekte eingeschlossen, ein `childNodes]`-Array haben, das die Kinder des Knotens enthält. Der IE 4 bietet keine Unterstützung für `childNodes[]`, bietet aber ein ähnliches `children[]`-Array auf seinen Document- und HTMLElement-Objekten. Deswegen ist es leicht, eine rekursive Funktion wie die in [Beispiel 15-2 gezeigte zu schreiben, um die vollständige Menge von HTML-Elementen in einem IE 4-Dokument zu durchqueren.

Es gibt allerdings einen substanziellen Unterschied zwischen dem `children[]`-Array des IE 4 und dem `childNodes[]`-Array des W3C DOMs. Der IE 4 kennt keinen Text-Knotentyp und betrachtet Textstrings nicht als Kinder. Ein `<p>`-Tag, das nur Klartext ohne Markup enthält, hat im IE 4 also ein leeres `children[]`-Array. Wie Sie in Kürze sehen werden, steht der Textinhalt eines `<p>`-Tags über die IE 4-Eigenschaft `innerText` zur Verfügung.

## 15.11.2 Dokumentelemente finden

IE 4 bietet keine Unterstützung für die `getElementById( )`\- und `getElementsByTagName( )`-Methoden des Document-Objekts. Stattdessen haben das Document-Objekt und alle Dokumentelemente eine Array-Eigenschaft namens `all[]`. Wie der Name andeutet, repräsentiert dieses Array _alle_ Elemente in einem Dokument oder alle Elemente, die in einem anderen Element enthalten sind. Beachten Sie, dass `all[]` nicht einfach die Kinder des Document-Objekts oder eines Elements repräsentiert. Es repräsentiert alle Nachfahren, egal wie tief sie eingebettet sind.

Das Array `all[]` kann auf mehrere Weisen verwendet werden. Wenn Sie es mit einem Integer _n_ indizieren, liefert es das _n+1_. Element des Dokuments oder Elternelements zurück. Zum Beispiel:

    var e1 = document.all[0]; // Das erste Element des Dokuments
    var e2 = e1.all[4];    // Das fünfte Element von Element 1

Elemente werden in der Reihenfolge nummeriert, in der sie in der Dokumentquelle erscheinen. Beachten Sie einen wichtigen Unterschied zwischen der IE 4 API und dem DOM-Standard: Der IE kennt keine Text-Knoten. Das Array `all[]` enthält also nur Dokumentelemente, nicht den Text, der zwischen ihnen erscheint.

In der Regel ist es viel praktischer, auf Dokumentelemente über einen Namen statt über eine Nummer zu verweisen. Das IE 4-Äquivalent zu `getElementbyId( )` ist es, das Array `all[]` mit einem String statt mit einer Zahl zu indizieren. Wenn Sie das tun, liefert der IE 4 das Element zurück, dessen `id`\- oder `name`-Attribut den angegebenen Wert hat. Wenn es mehrere derartige Elemente gibt (was passieren kann, weil es häufig vorkommt, dass mehrere Formularelemente wie Radiobutton das gleiche `name`-Attribut haben), ist das Ergebnis ein Array mit diesen Elementen. Zum Beispiel:

    var besondererAbsatz = document.all["besondererAbsatz"];
    var buttons = form.all["lieferungsart"]; // Kann ein Array zurückliefern

JavaScript erlaubt Ihnen, diese Ausdrücke so zu schreiben, dass der Array-Index als Eigenschaftsname ausgedrückt wird:

    var besondererAbsatz = document.all.besondererAbsatz;
    var buttons = form.all.lieferungsart;

Wird das Array `all[]` auf diese Weise verwendet, stellt es die gleiche Grundfunktionalität wie `getElementById( )` und `getElementsByName( )` zur Verfügung steht. Der Hauptunterschied ist, dass das Array `all[]` die Features dieser beiden Methoden kombiniert. Das kann zu Problemen führen, wenn Sie aus Versehen die gleichen Werte für die `id`\- und `name`-Attribute nicht verwandter Elemente verwenden.

Das Array `all[]` hat eine ungewöhnliche Eigenart: Es kann eine `tags( )`-Methode verwenden, um ein Array mit Elementen mit diesem Tag-Namen zu erhalten. Zum Beispiel:

    var liste = document.all.tags("UL"); // Alle <ul>-Tags im Dokument finden
    var elemente = lists[0].all.tags("LI"); // Alle <li>-Tags im ersten <ul> finden

Diese IE 4-Syntax bietet im Wesentlichen die gleiche Funktionalität wie die standardmäßige `getElementsByTagName( )`-Methode der Interfaces Document und Element. Beachten Sie allerdings, dass der Tag-Name im IE 4 vollständig in Großbuchstaben angegeben werden muss.

## 15.11.3 Dokumente modifizieren

Wie das W3C DOM veröffentlicht der IE 4 die Attribute von HTML-Tags als Eigenschaften der entsprechenden HTMLElement-Objekte. Deswegen ist es möglich, ein im IE 4 angezeigtes Element zu modifizieren, indem dynamisch seine HTML-Attribute geändert werden. Wenn eine Attributänderung die Größe eines Elements ändert, wird das Dokument neu aufgebaut, um die neue Größe des Elements aufzunehmen. Das HTMLElement-Objekt des IE 4 definiert ebenfalls die Methoden `setAttribute( )`, `getAttribute( )` und `removeAttribute( )`. Diese ähneln den Methoden gleichen Namens, die vom Element-Objekt in der Standard-DOM API definiert werden.

Das W3C DOM definiert eine API, die es ermöglicht, neue Knoten zu erzeugen, Knoten in den Dokumentbaum einzufügen, Knoten neue Elternknoten zu geben und Knoten im Baum zu verschieben. Der IE 4 kann das nicht. Stattdessen definieren alle HTMLElement-Objekte im IE 4 eine `innerHTML`-Eigenschaft. Dass Sie diese Eigenschaft auf einen String mit HTML-Text setzen können, ermöglicht Ihnen, den Inhalt eines Elements beliebig zu ändern. Weil diese `innerHTML`-Eigenschaft so mächtig ist, wurde sie von allen modernen Browsern implementiert. Und es scheint wahrscheinlich, dass sie in eine zukünftige Version des DOM-Standards integriert wird. `innerHTML` wurde in "15.8.2 Die innerHTML-Eigenschaft" beschrieben und eingesetzt.

IE 4 definiert außerdem einige verwandte Eigenschaften und Methoden. Die Eigenschaft `outerHTML` ersetzt den Inhalt eines Elements und das Element selbst durch einen angegebenen String mit HTML-Text. Die Eigenschaften `innerText` und `outerText` ähneln `innerHTML` und `outerHTML`, behandeln den String aber als Klartext und parsen ihn nicht als HTML. Die Methoden `insertAdjacentHTML( )` und `insertAdjacentText( )` lassen schließlich den Inhalt eines Elements unverändert, fügen aber bei ihm (vor oder nach ihm, in ihm oder außerhalb von ihm) neues HTML oder neuen Klartext ein. Diese Eigenschaften und Funktionen werden nicht so verbreitet verwendet wie `innerHTML` und wurden von Firefox nicht implementiert.

# Kapitel 16. Cascading Style Sheets und dynamisches HTML

Cascading Style Sheets (CSS) sind ein Standard, mit dem sich die Präsentation von HTML- oder XML-Dokumenten festlegen lässt. Theoretisch sollten Sie HTML-Markierungen verwenden, um die Struktur Ihres Dokuments festzulegen. Widerstehen Sie der Versuchung, veraltete HTML-Tags wie `<font>` zu verwenden, um das Aussehen Ihres Dokuments anzugeben! Verwenden Sie stattdessen CSS zur Definition eines Stylesheets, das angibt, wie die strukturierten Elemente Ihres Dokuments angezeigt werden sollen. Beispielsweise können Sie CSS verwenden, um festzulegen, dass die durch `<h1>`-Tags definierten Überschriften erster Kategorie in fetter serifenloser Schrift, zentriert und in Großbuchstaben von 24 Punkt Größe angezeigt werden sollen.

Cascading Style Sheets stellen eine Technologie bereit, die zur Verwendung durch Grafikdesigner und andere Leute gedacht ist, die die visuelle Darstellung von HTML-Dokumenten exakt kontrollieren wollen. Für die Programmierung clientseitigen JavaScripts sind sie interessant, weil sich die Styles, die auf die einzelnen Elemente eines Dokuments angewendet werden, über das Document Object Model per Skript steuern lassen. In Verbindung mit CSS ermöglicht JavaScript eine Vielzahl von visuellen Effekten, die auch gemeinhin als dynamisches HTML (DHTML)[58] bezeichnet werden.

Durch die Möglichkeit, mit Skripten auf Stylesheets zuzugreifen, können Sie Farben, Schriftarten usw. dynamisch ändern. Wichtiger noch: Sie können die Position von Elementen angeben und ändern und sie sogar anzeigen und verbergen. Sie können also DHTML-Techniken zur Erzeugung animierter Übergänge verwenden, bei denen der Dokumentinhalt beispielsweise von rechts »eingeschoben« wird, oder zur Erzeugung einer erweiterbaren und verkleinerbaren Umrissliste, bei der der Benutzer den angezeigten Informationsumfang bestimmen kann.

Dieses Kapitel beginnt mit einer Übersicht über CSS. Dann werden die wichtigsten CSS-Styles zur Erzeugung verbreiteter DHTML-Effekte erklärt. Als Nächstes beschreibt das Kapitel unterschiedliche Arten der CSS-Skriptsteuerung. Die am häufigsten gebrauchte und wichtigste Technik ist die Skriptsteuerung der Styles einzelner Dokumentelemente über die `style`-Eigenschaft. Eine verwandte Technik modifiziert indirekt den Stil eines Elements, indem sie die CSS-Klassen ändert, die auf dieses Element angewendet werden. Das wird durch das Setzen oder Ändern der `className`-Eigenschaft des Elements erreicht. Stylesheets können auch direkt skriptgesteuert werden. Das Kapitel schließt mit einer Erläuterung der Aktivierung und Deaktivierung von Stylesheets und dem Abfragen, Hinzufügen und Entfernen von Stylesheet-Regeln.

# 16.1 Überblick über CSS

In CSS werden Styles als eine Liste von Name-Wert-Paaren angegeben, wobei jedem Name-Wert-Paar ein Semikolon als Trennzeichen folgt. In jedem Paar sind Name und Wert durch einen Doppelpunkt getrennt. Beispielsweise gibt der folgende Style einen fetten, blauen, unterstrichenen Text an:

    font-weight: bold; color: blue; text-decoration: underline;

Der CSS-Standard definiert viele Eigenschaften. Tabelle 16-1 führt die wichtigsten Eigenschaften auf, mit Ausnahme einiger schlecht unterstützter. Sie müssen diese Eigenschaften und ihre Werte und Bedeutungen nicht alle kennen oder verstehen. Wenn Sie CSS jedoch häufiger in Ihren Dokumenten und Skripten benutzen, kann Ihnen diese Tabelle als einfache Kurzreferenz nützlich sein. Eine ausführlichere Dokumentation zu CSS finden Sie in _CSS: Das umfassende Handbuch_ von Eric Meyer (O'Reilly) und in _Dynamic HTML: The Definitive Reference_ von Danny Goodman (O'Reilly). Sie können auch die Spezifikation selbst lesen, die unter <http://www.w3c.org/TR/CSS21/> steht.

Die rechte Spalte von Tabelle 16-1 gibt die Werte an, die für die einzelnen Eigenschaften erlaubt sind. Sie verwendet die Grammatik, die von der CSS-Spezifikation verwendet wird. In `Festbreitenschrift` angegebene Elemente sind Schlüsselwörter, die genau wie gezeigt angegeben werden müssen. In _kursiver Schrift_ angegebene Elemente geben einen Datentyp wie String oder eine Länge an. Beachten Sie, dass der Typ `length` eine Zahl ist, an die sich eine Einheitsangabe, wie z.B. `px` (für Pixel), anschließt. In einer CSS-Referenz finden Sie weitere Details zu den anderen Typen. Elemente in einer `kursiven Festbreitenschrift` geben die Menge der erlaubten Werte an, die eine andere CSS-Eigenschaft besitzen kann. Zusätzlich zu den in der Tabelle aufgeführten Werten kann jede Eigenschaft den Wert »inherit« enthalten, der angibt, dass sie den Wert ihrer übergeordneten Eigenschaft erben soll.

Durch `|` getrennte Werte stellen Alternativen dar; Sie müssen genau eine von ihnen angeben. Durch `||` getrennte Werte sind Optionen, von denen Sie mindestens eine angeben müssen; Sie dürfen jedoch auch mehrere Werte in beliebiger Reihenfolge angeben. Eckige Klammern `]` gruppieren Werte. Ein Stern (`*`) gibt an, dass der vorhergehende Wert bzw. die vorhergehende Gruppe nullmal oder öfter auftreten darf; ein Plus-Zeichen (`+`) gibt an, dass der vorhergehende Wert bzw. die vorhergehende Gruppe einmal oder öfter auftreten darf; ein Fragezeichen (`?`) gibt an, dass das vorhergehende Element optional ist und null- oder einmal auftreten darf. Zahlen in geschweiften Klammern geben eine Anzahl von Wiederholungen an. Beispielsweise legt {2} fest, dass das vorhergehende Element zweimal wiederholt werden muss. {1,4} gibt an, dass das vorhergehende Element mindestens einmal und nicht häufiger als viermal auftreten darf. (Diese Syntax für die Angabe von Wiederholungen könnte Ihnen bekannt vorkommen: Sie gleicht der in [Kapitel 11 vorgestellten Syntax, die von regulären Ausdrücken in JavaScript verwendet wird.)

Tabelle 16-1. CSS 2.1-Eigenschaften und ihre Werte

Name | Wert

---|---

`background` | [ _`background-color`_ || _`background-image`_ || _`background-repeat`_ || _`background-attachment`_ || _`background-position`_ ]

`background-attachment` | `scroll` | `fixed`

`background-color` |  _color_ `| transparent`

`background-image` | `url(` _url_`) | none`

`background-position` | [ [percentage | length ]{1,2} | [ [`top` | `center` | `bottom`] || [`left` | `center` | `right`] ] ]

`background-repeat` | `repeat` | `repeat-x` | `repeat-y` | `no-repeat`

`border` | [ _`border-width`_ || _`border-style`_ || _color_ ]

`border-collapse` | `collapse` | `separate`

`border-color` |  _color_`{1,4} | transparent`

`border-spacing` |  _length length_?

`border-style` | [`none` | `hidden` | `dotted` | `dashed` | `solid` | `double` | `groove` | `ridge` | `inset` | `outset`]{1,4}

`border-top`

`border-right`

`border-bottom`

`border-left` | [ _`border-top-width`_ || _`border-style`_ || [ _color_ | `transparent` ] ]

`border-top-color`

`border-right-color`

`border-bottom-color`

`border-left-color` |  _color_ `| transparent`

`border-top-style`

`border-right-style`

`border-bottom-style`

`border-left-style` | `none` | `hidden` | `dotted` | `dashed` | `solid` | `double` | `groove` | `ridge` | `inset` | `outset`

`border-top-width`

`border-right-width`

`border-bottom-width`

`border-left-width` | `thin | medium | thick |` _length_

`border-width` | [`thin` | `medium` | `thick` | length]{1,4}

`bottom` |  _length_ | _percentage_ | `auto`

`caption-side` | `top` | `bottom`

`clear` | `none` | `left` | `right` | `both`

`clip` | [`rect(` [ _length_ | `auto`]{4} `)`] | `auto`

`color` |  _color_

`content` | [ _string_ | `url(` _url_ `)` | _counter_ | `attr(` _`attribute-name`_`)` | `open-quote` |

`close-quote` | `no-open-quote` | `no-close-quote` ]+ | `normal`

`counter-increment` | [ _identifier integer_? ]+ | `none`

`counter-reset` | [ _identifier integer_? ]+ | `none`

`cursor` | [ [ `url(` _url_`)`,]* [ `auto` | `crosshair` | `default` | `pointer` | `progress` | `move` | `e-resize` | `ne-resize` | `nw-resize` | `n-resize` | `se-resize` | `sw-resize` | `s-resize` | `w-resize` | `text` | `wait` | `help` ] ]

`direction` | `ltr` | `rtl`

`display` | `inline` | `block` | `inline-block` | `list-item` | `run-in` | `table` | `inline-table` | `table-row-group` | `table-header-group` | `table-footer-group` |

`table-row` | `table-column-group` | `table-column` | `table-cell` |

`table-caption` | `none`

`empty-cells` | `show` | `hide`

`float` | `left` | `right` | `none`

`font` | [ [ _`font-style`_ || _`font-variant`_ || _`font-weight`_ ]? _`font-size`_ [ / _`line-height`_ ]? _`font-family`_ ] | `caption` | `icon` | `menu` | `message-box` |

`small-caption` | `status-bar`

`font-family` | [[ family-name | `serif` | `sans-serif` | `monospace` | `cursive` | `fantasy`],]+

`font-size` | `xx-small` | `x-small` | `small` | `medium` | `large` | `x-large` | `xx-large` | `smaller` | `larger` | _length_ | _percentage_

`font-style` | `normal` | `italic` | `oblique`

`font-variant` | `normal` | `small-caps`

`font-weight` | `normal` | `bold` | `bolder` | `lighter` | `100` | `200` | `300` | `400` | `500` | `600` | `700` | `800` | `900`

`height` |  _length_ | _percentage_ | `auto`

`left` |  _length_ | _percentage_ | `auto`

`letter-spacing` | `normal` | _length_

`line-height` | `normal` | _number_ | _length_ | _percentage_

`list-style` | [ _`list-style-type`_ || _`list-style-position`_ || _`list-style-image`_ ]

`list-style-image` | `url(` _url_`)` | `none`

`list-style-position` | `inside` | `outside`

`list-style-type` | `disc` | `circle` | `square` | `decimal` | `decimal-leading-zero` | `lower-roman` | `upper-roman` | `lower-greek` | `lower-alpha` | `lower-latin` | `upper-alpha` | `upper-latin` | `hebrew` | `armenian` | `georgian` | `cjk-ideographic` | `hiragana` | `katakana` | `hiragana-iroha` | `katakana-iroha` | `none`

`margin` | [ _length_ | _percentage_ | `auto`]{1,4}

`margin-top`

`margin-right`

`margin-bottom`

`margin-left` |  _length_ | _percentage_ | `auto`

`marker-offset` |  _length_ | `auto`

`max-height` |  _length_ | _percentage_ | `none`

`max-width` |  _length_ | _percentage_ | `none`

`min-height` |  _length_ | _percentage_

`min-width` |  _length_ | _percentage_

`outline` | [ _`outline-color`_ || _`outline-style`_ || _`outline-width`_ ]

`outline-color` |  _color_ | `invert`

`outline-style` | `none` | `hidden` | `dotted` | `dashed` | `solid` | `double` | `groove` | `ridge` | `inset` | `outset`

`outline-width` | `thin` | `medium` | `thick` | _length_

`overflow` | `visible` | `hidden` | `scroll` | `auto`

`padding` | [ _length_ | _percentage_ ]{1,4}

`padding-top`

`padding-right`

`padding-bottom`

`padding-left` |  _length_ | _percentage_

`page-break-after` | `auto` | `always` | `avoid` | `left` | `right`

`page-break-before` | `auto` | `always` | `avoid` | `left` | `right`

`page-break-inside` | `avoid` | `auto`

`position` | `static` | `relative` | `absolute` | `fixed`

`quotes` | [ _string string_ ]+ | `none`

`right` |  _length_ | _percentage_ | `auto`

`table-layout` | `auto` | `fixed`

`text-align` | `left` | `right` | `center` | `justify`

`text-decoration` | `none` | `[underline || overline || line-through || blink ]`

`text-indent` |  _length_ | _percentage_

`text-transform` | `capitalize` | `uppercase` | `lowercase` | `none`

`top` |  _length_ | _percentage_ | `auto`

`unicode-bidi` | `normal` | `embed` | `bidi-override`

`vertical-align` | `baseline` | `sub` | `super` | `top` | `text-top` | `middle` | `bottom` | `text-bottom` |

percentage | length

`visibility` | `visible` | `hidden` | `collapse`

`white-space` | `normal` | `pre` | `nowrap` | `pre-wrap` | `pre-line`

`width` |  _length_ | _percentage_ | `auto`

`word-spacing` | `normal` | _length_

`z-index` | `auto` | _integer_

Der CSS-Standard erlaubt es, häufig gemeinsam verwendete Eigenschaften mit Hilfe spezieller Kurzschrift-Eigenschaften zusammenzufassen. Beispielsweise können Sie die Eigenschaften `font-family`, `font-size`, `font-style` und `font-weight` gleichzeitig mit der einen Eigenschaft `font` setzen:

    font: bold italic 24pt helvetica;

Die Kurzschrift-Eigenschaften `margin` und `padding` sind Abkürzungen für Eigenschaften, die Außen- und Innenabstände für die einzelnen Seiten eines Elements festlegen. Anstelle von `margin` können Sie `margin-left`, `margin-right`, `margin-top` und `margin-bottom` verwenden; analog gilt dies für `padding`.

## 16.1.1 Stilregeln auf Elemente eines Dokuments anwenden

Sie können Eigenschaften auf Elemente eines Dokuments auf verschiedene Weise anwenden. Eine Möglichkeit besteht darin, sie im Attribut `style` eines HTML-Tags zu verwenden. Um beispielsweise die Rahmen eines einzelnen Absatzes festzulegen, können Sie das folgende Tag verwenden:

    <p style="margin-left: 1in; margin-right: 1in;">

Eines der wesentlichen Ziele von CSS besteht in der Trennung der Struktur von der Darstellung eines Dokuments. Die Angabe eines `style`-Attributs für einzelne HTML-Tags leistet dies nicht (auch wenn es eine nützliche Technik für DHTML sein kann). Um die Trennung von Struktur und Darstellung zu erreichen, verwenden wir _Stylesheets_ , die alle Stilinformationen an einer einzelnen Stelle sammeln. Ein CSS-Stylesheet besteht aus einem Satz von Stilregeln. Jede Regel beginnt mit einem Selektor, der das oder die Elemente eines Dokuments angibt, für das bzw. die die Regel gilt. Darauf folgt eine Reihe von Eigenschaften und ihren Werten in geschweiften Klammern. Die einfachste Art solcher Regeln definiert Styles für einen oder mehrere bestimmte Tag-Namen. Beispielsweise legt die folgende Regel die Ränder und die Hintergrundfarbe des `<body>`-Tags fest:

    body { margin-left: 30px; margin-right: 15px; background-color: #ffffff }

Die folgende Regel legt fest, dass Text in den Überschriften `<h1>` und `<h2>` zentriert werden soll:

    h1, h2 { text-align: center; }

Beachten Sie, wie im letzten Beispiel ein Komma verwendet wurde, um die Tag-Namen, für die die Styles angewendet werden sollen, voneinander zu trennen. Wenn das Komma weggelassen wird, gibt der Selektor eine Kontextregel an, die nur angewandt wird, wenn ein Tag in einem anderen eingeschlossen ist. Die folgenden Regeln legen beispielsweise fest, dass Text in `<blockquote>`-Tags in kursiver Schrift angezeigt werden soll, Text innerhalb eines `<i>`-Tags in einem `<blockquote>` jedoch in normaler, nicht-kursiver Schrift:

    blockquote { font-style: italic; }
    blockquote i { font-style: normal; }

Eine weitere Art von Stylesheet-Regel verwendet einen anderen Selektor, um eine _Klasse_ von Elementen anzugeben, auf die ihre Styles angewandt werden sollen. Die Klasse eines Elements wird durch das `class`-Attribut des HTML-Tags definiert. Beispielsweise legt die folgende Regel fest, dass jedes Tag mit dem Attribut `class="attention"` in fetter Schrift angezeigt werden soll:

    .attention { font-weight: bold; }

Klassenselektoren können mit den Selektoren für Tag-Namen kombiniert werden. Die folgende Regel legt fest, dass ein `<p>`-Tag, das das Attribut `class="attention"` trägt, nicht nur in fetter Schrift (wie von der vorhergehenden Regel festgelegt), sondern zusätzlich in Rot angezeigt werden soll:

    p.attention { color: red; }

Schließlich können Stylesheets Regeln enthalten, die nur auf einzelne Elemente angewandt werden, für die ein `id`-Attribut angegeben ist. Die folgende Regel legt fest, dass das Element, dessen Attribut `id` den Wert »p1« trägt, nicht angezeigt werden soll:

    #p1 { visibility: hidden; }

Das `id`-Attribut haben wir bereits im Zusammenhang mit der Document-Methode `getElementById( )` kennengelernt, die einzelne Elemente eines Dokuments zurückgibt. Wie Sie sich vorstellen können, ist diese Art elementspezifischer Regel nützlich, wenn der Stil eines einzelnen Elements geändert werden soll. Wurde die obige Regel definiert, kann ein Skript beispielsweise den Wert des Attributs `visibility` von `hidden` in `visible` ändern und so das Element dynamisch erscheinen lassen. Sie werden weiter unten in diesem Kapitel sehen, wie das funktioniert.

Der CSS-Standard definiert eine Anzahl weiterer Selektoren außer den einfachen hier gezeigten, und einige werden durch moderne Browser gut unterstützt. In den CSS-Spezifikationen oder einer CSS-Referenz finden Sie weitere Details.

## 16.1.2 Stylesheets zu Dokumenten zuordnen

Sie können ein Stylesheet in ein HTML-Dokument einbeziehen, indem Sie es zwischen `<style>` und `</style>`-Tags im `<head>` des Dokuments stellen. Beispielsweise:

    <html>
    <head><title>Testdokument</title>
    <style type="text/css">
    body { margin-left: 30px; margin-right: 15px; background-color: #ffffff }
    p { font-size: 24px; }
    </style>
    </head>
    <body><p>Test, test</p></body>
    </html>

Wenn ein Stylesheet von mehr als einer Seite auf einer Website verwendet werden soll, ist es meist besser, es in einer eigenen Datei unterzubringen, ohne umgebende HTML-Tags. Diese CSS-Datei kann dann in die HTML-Seite einbezogen werden. Anders als das `<script>`-Tag hat das `<style>`-Tag aber kein `src`-Attribut. Um ein Stylesheet in eine HTML-Seite einzubinden, verwenden Sie stattdessen das `<link>`-Tag:

    <html>
    <head><title>Testdokument</title>
    <link rel="stylesheet" href="meinestyles.css" type="text/css">
    </head>
    <body><p>Test, test</p></body>
    </html>

Sie können ein `<link>`-Tag auch zur Angabe eines alternativen Stylesheets verwenden. Einige Browser (wie z.B. Firefox) lassen den Benutzer eine der von Ihnen angegebenen Alternativen auswählen (über **Ansicht** → **Seitenstil** ). So können Sie beispielsweise ein alternatives Stylesheet für Besucher Ihrer Site bereitstellen, die große Schriftarten und Farben mit hohem Kontrast vorziehen:

    <link rel="alternate" href="grosseschrift.css" type="text/css">
       title="Große Schrift"> <!-- Titel wird im Menü angezeigt -->

Wenn Ihre Webseite ein seitenspezifisches Stylesheet mit einem `<style>`-Tag enthält, können Sie die CSS-Direktive-`@import` zur Einbeziehung einer gemeinsam benutzten CSS-Datei in das seitenspezifische Stylesheet verwenden:

    <html>
    <head><title>Testdokument</title>
    <style type="text/css">
    @import "meinestyles.css"; /* gemeinsames Stylesheet importieren */
    p { font-size: 48px; } /* importierte Styles überschreiben */
    </style>
    </head>
    <body><p>Test, test</p></body>
    </html>

## 16.1.3 Die Kaskade

Der Buchstabe C in CSS steht für _cascading_ (fallend). Mit diesem Begriff soll angedeutet werden, dass die auf ein beliebiges Element anzuwendenden Stilregeln aus einer Kaskade verschiedener Quellen stammen können. Jeder Webbrowser besitzt üblicherweise seine eigenen voreingestellten Styles für alle HTML-Elemente; er kann es dem Benutzer erlauben, diese durch ein Benutzer-Stylesheet zu modifizieren. Der Autor eines Dokuments kann Stylesheets innerhalb der `<style>`-Tags oder in externen Dateien definieren, die in andere Stylesheets eingebunden oder importiert werden können. Der Autor kann außerdem Styles für einzelne Elemente im HTML-Code mit dem Attribut `style` festlegen.

Die Spezifikation von CSS enthält einen vollständigen Satz von Regeln, der festlegt, welche Regeln der Kaskade Vorrang vor welchen anderen Regeln haben. In aller Kürze besagen die wichtigsten Regeln, dass das Stylesheet des Benutzers Vorrang vor dem Standard-Stylesheet des Browsers hat, dass Stylesheets des Autors die Stylesheets des Benutzers überschreiben und dass eingebettete Styles alles andere überschreiben. Die Ausnahme von dieser allgemeinen Regel besteht darin, dass Style-Attribute des Benutzers, deren Werte den Modifikator `!important` enthalten, Autoren-Styles überschreiben. Wenn innerhalb eines Stylesheets mehrere Regeln auf ein Dokument zutreffen, überschreiben die Styles spezifischerer Regeln immer die Styles weniger spezifischer Regeln. Regeln, die ein Element `id` angeben, sind am spezifischsten, dann folgen Regeln, die eine `class` angeben. Am wenigsten spezifisch sind Regeln, die nur einen Tag-Namen angeben, wobei hier diejenigen Regeln, die mehrfach geschachtelte Tag-Namen angeben, spezifischer sind als solche, die nur einen einfachen Tag-Namen angeben.

## 16.1.4 Versionen von CSS

CSS ist ein vergleichsweise alter Standard. CSS1 wurde im Dezember 1996 verabschiedet; in ihm werden die Eigenschaften für die Angabe von Farben, Schriftarten, Außenabständen, Rahmen und andere grundlegende Styles festgelegt. Sogar so alte Browser wie Netscape 4 und Internet Explorer 4 unterstützen einen signifikanten Teil von CSS1. Die zweite Version des Standards, CSS2, wurde im Mai 1998 verabschiedet; in ihm wurden viele erweiterte Merkmale definiert, insbesondere solche zur Unterstützung einer absoluten Positionierung von Elementen. Als dieses Buch geschrieben wurde, wurden CSS2-Features in den aktuellen Browsern halbwegs gut unterstützt. Die wichtigen Merkmale zur Positionierung wurden schon im Rahmen des CSS-Positioning (CSS-P) standardisiert, sodass diese Merkmale zur Unterstützung von DHTML in praktisch allen heute installierten Browsern verfügbar sind. (Diese wichtigen Styles zur Positionierung werden später in diesem Kapitel behandelt).

Die Arbeit an CSS geht weiter. Als dieses Buch geschrieben wurde, war die Spezifikation von CSS 2.1 fast komplett. Sie klärt die CSS2-Spezifikation, behebt Fehler und entfernt einige CSS2-Styles, die nie irgendwo in einem Browser implementiert wurden. Für Version 3 wurde die CSS-Spezifikation in mehrere spezialisierte Module aufgeteilt, die den Standardisierungsprozess separat durchlaufen. Einige CSS3-Module sind fast komplett, und einige Browser fangen an, ausgewählte CSS3-Features zu implementieren, wie z.B. die `opacity`-Eigenschaft. Die Spezifikationen der einzelnen Versionen und die Arbeitsentwürfe können Sie unter <http://www.w3.org/Style/CSS/> finden.

## 16.1.5 Ein CSS-Beispiel

Beispiel 16-1 besteht aus einer HTML-Datei, die ein Stylesheet definiert und verwendet. Es zeigt die oben beschriebenen Stilregeln, die auf Tag-Namen, Klassen und IDs basieren. Außerdem enthält es ein Beispiel eines eingebetteten Styles, der mit dem Attribut `style` definiert wird. Abbildung 16-1 zeigt, wie dieses Beispiel in einem Browser wiedergegeben wird. Dieses Beispiel gibt nur einen Überblick über die Syntax und die Fähigkeiten von CSS. Eine umfassende Betrachtung von CSS würde den Rahmen dieses Buchs sprengen.

Abbildung 16-1. Eine mit CSS gestaltete Webseite

Beispiel 16-1. Definition und Verwendung von Cascading Style Sheets

    <head>
    <style type="text/css">
    /* Überschriften sollen in blauer, kursiver Schrift angezeigt werden. */
    h1, h2 { color: blue; font-style: italic }

    /*
     * Elemente der Klasse class="WARNUNG" werden in großem, fettem Text mit breiten Rändern,
     * gelbem Hintergrund und breitem, rotem Rahmen angezeigt.
     */
    .WARNUNG {
         font-weight: bold;
         font-size: 150%;
         margin: 0 1in 0 1in; /* oben rechts unten links */
         background-color: yellow;
         border: solid red 8px;
         padding: 10px;    /* 10 Pixel auf allen vier Seiten */
    }

    /*
     * Text in einer Überschrift des Typs h1 oder h2 in einem Element der
     * class="WARNUNG" wird zentriert (zusätzlich zur blauen, kursiven Schrift).
     */
    .WARNUNG h1, .WARNUNG h2 { text-align: center }

    /* Das Element mit id="P23" wird zentriert in Großbuchstaben angezeigt. */
    #P23 {
       text-align: center;
       text-transform: uppercase;
    }
    </style>
    </head>
    <body>
    <h1>Demonstration von Cascading Style Sheets</h1>

    <div class="WARNUNG">
    <h2>Warnung</h2>
    Dies ist eine Warnung!
    Sie zieht mit ihrem fetten Text und den kräftigen Farben Ihre Aufmerksamkeit auf sich.
    Die Überschrift ist zentriert und erscheint in blauer, kursiver Schrift.
    </div>

    <p id="P23">
    Dieser Absatz ist zentriert<br>
    und erscheint in Großbuchstaben.<br>
    <span style="text-transform: none">
    Hier verwenden wir einen eingebetteten Style, um die Großbuchstaben zu deaktivieren.
    </span>
    </p>
    </body>

* * *

58] Viele fortgeschrittene DHTML-Effekte beziehen auch die in [Kapitel 17 gezeigten Techniken zum Umgang mit Events ein.

# 16.2 CSS für DHTML

Für Entwickler von DHTML-Inhalten ist das wichtigste Merkmal von CSS die Fähigkeit, mit gewöhnlichen CSS-Eigenschaften die Sichtbarkeit, die Größe und die exakte Position einzelner Elemente eines Dokuments festlegen zu können. Mit anderen CSS-Styles können Sie die Stapelordnung, Transparenz, Clipping-Region, Außen- und Innenabstände, Rahmen und Farben angeben. Um in DHTML programmieren zu können, ist es wichtig zu verstehen, wie diese Eigenschaften funktionieren. Sie sind in Tabelle 16-2 zusammengestellt und werden in den folgenden Abschnitten ausführlicher dokumentiert.

Tabelle 16-2. CSS-Eigenschaften für Positionierung und Sichtbarkeit

Eigenschaft(en) | Beschreibung

---|---

`position` | gibt den Typ der auf ein Element angewandten Positionierung an

`top`, `left` | gibt die Position der oberen und linken Kante eines Elements an

`bottom`, `right` | gibt die Position der unteren und rechten Kante eines Elements an

`width`, `height` | gibt die Größe eines Elements an

`z-index` | gibt die »Höhe« eines Elements relativ zu überlappenden Elementen an; sie definiert eine dritte Dimension für die Positionierung von Elementen

`display` | gibt an, ob und wie ein Element angezeigt wird

`visibility` | gibt an, ob ein Element sichtbar ist

`clip` | definiert eine »Clipping-Region« für ein Element; nur innerhalb dieser Region liegende Teile des Elements werden angezeigt

`overflow` | gibt an, was zu tun ist, wenn ein Element größer ist als der für es vorgesehene Platz

`margin`, `border`, `padding` | geben die Abstände und Rahmen für ein Element an

`background` | gibt Hintergrundfarbe oder -bild eines Elements an

`opacity` | gibt an, wie deckend (oder lichtdurchlässig) ein Element ist; dabei handelt es sich um eine CSS3-Eigenschaft, das von ein paar Browsern unterstützt wird. Eine funktionsfähige Alternative für IE existiert.

## 16.2.1 Der Schlüssel zu DHTML: Absolute Positionierung

Die CSS-Eigenschaft `position` gibt an, welche Art von Positionierung auf das Element angewandt werden soll. Die vier möglichen Werte dieser Eigenschaft sind:

`static`

Dies ist der voreingestellte Wert. Er gibt an, dass das Element der normalen Abfolge der Dokumenteninhalte entsprechend positioniert werden soll (also bei den meisten westlichen Sprachen von links nach rechts und von oben nach unten). Statisch positionierte Elemente sind keine DHTML-Elemente; sie können nicht mit `top`, `left` oder anderen Eigenschaften positioniert werden. Um die Positionierungstechniken von DHTML anwenden zu können, müssen Sie zunächst die Eigenschaft `position` eines Elements auf einen der anderen drei Werte setzen.

`absolute`

Dieser Wert ermöglicht die Angabe einer Position eines Elements relativ zu dem umgebenden Element. Absolut positionierte Elemente werden unabhängig von allen anderen Elementen positioniert und sind nicht Teil der Abfolge, in der statisch positionierte Elemente angeordnet werden. Ein absolut positioniertes Element wird entweder relativ zum `<body>` des Dokuments oder, wenn es in einem anderen positionierten Element enthalten ist, relativ zu diesem Element angeordnet. Dies ist der am häufigsten verwendete Positionierungstyp in DHTML. IE 4 unterstützt nur die absolute Positionierung bestimmter Elemente. Wenn Sie diesen alten Browser unterstützen wollen, stellen Sie sicher, dass Sie Ihre absolut positionierten Elemente in `<div>`\- oder `<span>`-Tags verpacken.

`fixed`

Mit diesem Wert können Sie die Position eines Elements relativ zum Browserfenster angeben. Elemente mit der Positionierung `fixed` sind immer sichtbar und bewegen sich nicht, wenn das Dokument gescrollt wird. Wie absolut positionierte Elemente sind auch Elemente mit fester Position unabhängig von allen anderen und damit nicht Teil der Dokumentabfolge. Feste Positionierung wird in den meisten modernen Browsern unterstützt, mit der bemerkenswerten Ausnahme von IE 6.

`relative`

Wenn die Eigenschaft `position` auf den Wert `relative` gesetzt ist, wird das Element entsprechend der normalen Dokumentabfolge positioniert. Die Position wird relativ zur Position im normalen Fluss angepasst. Der für das Element vorgesehene Platz in der normalen Dokumentabfolge bleibt reserviert, sodass seitlich angezeigte Elemente weder den leeren Platz auffüllen noch durch die neue Positionierung des Elements verdrängt werden.

Nachdem Sie die Eigenschaft `position` eines Elements auf einen anderen Wert als `static` gesetzt haben, können Sie die Position dieses Elements mit einer Kombination der Eigenschaften `left`, `top`, `right` und `bottom` festlegen. Die am häufigsten verwendete Technik besteht in der Angabe der Eigenschaften `left` und `top`, die den Abstand vom linken Rand des umgebenden Elements (dies ist meist das Dokument selbst) zum linken Rand des Elements bzw. vom oberen Rand des umgebenden Elements zum oberen Rand des Elements festlegen. Um beispielsweise ein Element 100 Pixel von links und 100 Pixel vom oberen Rand des Dokuments entfernt zu platzieren, können Sie CSS-Styles in einem `style`-Attribut folgendermaßen festlegen:

    <div style="position: absolute; left: 100px; top: 100px;">

Das umgebende Element, relativ zu dem ein dynamisches Element positioniert wird, ist nicht unbedingt dasselbe wie das umgebende Element, in dem das dynamische Element im Quelltext des Dokuments definiert ist. Da dynamische Elemente nicht Teil der normalen Dokumentabfolge sind, werden ihre Positionen nicht relativ zu dem statischen Container angegeben, in dem sie definiert sind. Die meisten dynamischen Elemente werden relativ zum Dokument selbst (dem `<body>`-Tag) positioniert. Eine Ausnahme stellen dynamische Elemente dar, die in anderen dynamischen Elementen definiert sind. In diesem Fall wird das eingebettete dynamische Element relativ zum nächsten dynamischen Vorfahren positioniert. Wenn Sie ein Element relativ zu einem Container positionieren wollen, der Teil der normalen Dokumentabfolge ist, verwenden Sie `position:relative` für den Container und geben eine `top`\- und `left`-Position von `0px` an. Das positioniert den Container dynamisch, belässt ihn aber an seinem normalen Platz in der Dokumentabfolge. Etwaige absolut positionierte Kinder werden dann relativ zum Container positioniert.

Obwohl es üblich ist, die Position der oberen linken Ecke eines Elements mit `left` und `top` festzulegen, können Sie auch `right` und `bottom` verwenden, um die Position der unteren und der rechten Kante eines Elements relativ zur unteren und rechten Kante des umgebenden Elements festzulegen. Um beispielsweise ein Element so zu positionieren, dass die untere rechte Ecke in der unteren rechten Ecke des Dokuments liegt, können Sie die folgenden Styles verwenden (wobei wir annehmen, dass das Element nicht in einem anderen dynamischen Element enthalten ist):

    position: absolute; right: 0px; bottom: 0px;

Um ein Element so zu positionieren, dass seine obere Kante 10 Pixel vom oberen Rand und seine rechte Kante 10 Pixel vom rechten Rand des Fensters entfernt ist und es nicht mit dem Dokument scrollt, können Sie die folgenden Styles verwenden:

    position: fixed; right: 10px; top: 10px;

Außer der Position von Elementen können Sie mit CSS auch deren Größe angeben. Dies geschieht meist durch Angabe von Werten für die Eigenschaften `width` und `height`. Beispielsweise erzeugt der folgende HTML-Code ein absolut positioniertes Element ohne Inhalt. Durch die Eigenschaften `width`, `height` und `background-color` erscheint es als kleines, blaues Viereck.

    <div style="position:absolute; left:10px; top:10px;
                   width: 10px; height: 10px; background-color: blue">
    </div>

Sie können die Breite eines Elements auch durch die Angabe von Werten für die Eigenschaften `left` und `right` festlegen sowie die Höhe durch Angabe von Werten für die Attribute `top` und `bottom` bestimmen. Wenn Sie allerdings Werte für `left`, `right` und `width` angeben, hat `width` Vorrang vor dem `right`; analog dazu hat `height` Vorrang vor `bottom`, falls sich die Höhenangaben widersprechen.

Denken Sie daran, dass es nicht notwendig ist, die Größe jedes dynamischen Elements festzulegen. Einige Elemente, wie beispielsweise Bilder, besitzen von sich aus bereits eine Größe. Auch für dynamische Elemente, die Text oder anderen fließenden Inhalt enthalten, reicht oft die Angabe der gewünschten Breite aus; die Höhe ergibt sich dann automatisch aus dem Layout des Elementinhalts.

In den letzten Beispielen zur Positionierung wurden die Werte für die Position und Größe mit dem Suffix »px« angegeben, was für Pixel steht. Der CSS-Standard erlaubt die Angabe von Maßen in verschiedenen Einheiten; dazu gehören Zoll (»in«), Zentimeter (»cm«), Punkt (»pt«) und »em« (ein Maß für die Breite des Buchstabens »m« in der gerade verwendeten Schriftart). Pixel werden bei der DHTML-Programmierung am häufigsten verwendet. Beachten Sie, dass der CSS-Standard die Angabe einer Einheit vorschreibt. Manche Browser nehmen an, dass Sie Pixel meinen, wenn Sie keine Einheit angeben; darauf verlassen sollten Sie sich jedoch nicht.

Statt absoluter Positionen und Größen in den oben genannten Einheiten können Sie bei CSS auch Prozentangaben verwenden, die sich auf die Größe des umgebenden Elements beziehen. Beispielsweise erzeugt der folgende HTML-Code ein leeres Element mit einem schwarzen Rahmen, der halb so breit und halb so hoch wie das umgebende Element (oder das Browserfenster) und in diesem zentriert ist:

    <div style="position: absolute; left: 25%; top: 25%; width: 50%; height: 50%;
                   border: 2px solid black">
    </div>

## 16.2.2 CSS-Positionierungsbeispiel: Text mit Schatten

Die CSS2-Spezifikation enthielt eine `text-shadow`-Eigenschaft zum Erzeugen von Schattenwurfeffekten unter Text. Diese Eigenschaft wurde vom Safari-Browser implementiert, aber von keinem anderen wichtigen Browserhersteller aufgegriffen und aus CSS2.1 entfernt, zur Neubegutachtung unter CSS3. Sie können Schatteneffekte bei Text allerdings auch ohne `text-shadow` erreichen. Sie müssen einfach nur die CSS-Positionierung verwenden und willens sein, den gewünschten Text zu wiederholen: einmal für den eigentlichen Text und einmal (oder mehr als einmal) für den oder die Schatten. Der folgende Code produziert die in Abbildung 16-2 gezeigte Ausgabe:

    <div style="font: bold 32pt sans-serif;"> <!--Schatten sieht bei großem Text am besten aus-->
    <!-- Text mit Schatten muss relativ positioniert werden, damit wir die Schatten -->
    <!-- von seiner normalen Position in der Folge absetzen können -->
    <span style="position:relative;">
    <!-- Hier sind 3 Schatten mit verschiedenen Farben unter Verwendung absoluter Positionierung, -->
    <!-- die sie um verschiedene Beträge vom normalen Text absetzt -->
    <span style="position:absolute; top:5px; left:5px; color: #ccc">Schatten</span>
    <span style="position:absolute; top:3px; left:3888px; color: #ccc">Schatten</span>
    <span style="position:absolute; top:1px; left:1px; color: #ccc">Schatten</span>
    <!-- Dies ist der JavaScript-Code, der den Schatten wirft. Wir verwenden relative -->
    <!-- Positionierung, sodass er über seinen Schatten erscheint -->
    <span style="position:relative">Schatten</span>
    </span>
    | Kein Schatten <!-- Zum Vergleich ist hier etwas Text ohne Schatten -->
    </div>

Abbildung 16-2. Schattierter Text mit CSS-Positionierung

Das hier gezeigte manuelle Hinzufügen von Schatten ist umständlich und verletzt das Prinzip der Trennung von Inhalt und Präsentation. Sie können dieses Problem durch etwas unauffälliges JavaScript beheben. Beispiel 16-2 ist ein JavaScript-Modul namens _Schatten.js_. Es definiert eine Funktion `Schatten.addAll( )`, die das Dokument (oder einen Teil desselben) nach Tags durchsucht, die ein `schatten`-Attribut haben. Wenn es ein solches Tag findet, parst es den Wert des `schatten`-Attributs und verwendet DOM und Skriptsteuerung, um dem Text im Tag Schatten hinzuzufügen. Als Beispiel können Sie dieses Modul verwenden, um die in Abbildung 16-2 gezeigte Ausgabe zu produzieren:

    <head><script src="Schatten.js"></script></head> <!-- Modul einbeziehen -->
    <body onload="Schatten.addAll( );">                                <!-- beim Laden Schatten hinzufügen -->
    <div style="font: bold 32pt sans-serif;">                          <!-- große Schrift verwenden -->
    <!-- Beachten Sie das schatten-Attribut hier -->
    <span schatten='5px 5px #ccc 3px 3px #888 1px 1px #444'>Schatten</span> | Kein Schatten
    </div>
    </body>

Der Code für das _Schatten.js_ -Modul folgt. Beachten Sie, dass es sich dabei zum größten Teil um ein Beispiel für Skriptsteuerung des DOM handelt, das zufällig CSS auf eine interessante Weise verwendet. Bis auf eine Zeile steuert dieses Beispiel CSS nicht selbst: Es setzt einfach nur CSS-Eigenschaften an den Dokumentelementen, die es erzeugt. Später in diesem Kapitel zeige ich detailliertere Techniken zum Skripten von CSS.

Beispiel 16-2. Schattierten Text mit unauffälligem JavaScript erzeugen

    /**
     * Schatten.js: schattierter Text mit CSS.
     *
     * Dieses Modul definiert ein einziges globales Objekt namens Schatten.
     * Die Eigenschaften dieses Objekts sind zwei Utility-Funktionen.
     *
     * Schatten.add(element, schatten):
     *  Füge den angegebenen Schatten zum angegebenen Element hinzu. Das erste Argument
     *  ist ein Dokumentelement oder eine Element-id. Dieses Element muss einen einzigen
     *  Textknoten als Kind haben. Dieses Kind wird schattiert.
     *  Schatten werden über ein Stringargument angegeben, dessen Syntax unten
     *  erklärt wird.
     *
     * Schatten.addAll(root, tagname):
     *  Finde alle Abkömmlinge des angegebenen Wurzelelements, die den
     *  angegebenen Tagnamen haben. Wenn eines dieser Elemente ein Attribut namens
     *  schatten hat, rufe Schatten.add( ) für das Element und den Wert seines
     *  schatten-Attributs auf. Wenn tagname nicht angegeben ist, werden alle Elemente geprüft.
     *  Wenn root nicht angegeben ist, wird das Document-Objekt verwendet. Diese Funktion sollte
     *  nur einmal aufgerufen werden, und zwar nach dem ersten Laden eines Dokuments.
     *
     * Schatten-Syntax
     *
     * Schatten werden als String im Format [x y farbe]+ angegeben, d.h. als ein
     * oder mehrere Triplets, die einen x- und y-Offset und eine Farbe angeben. Jeder
     * dieser Werte muss in gültigem CSS-Format sein. Wenn mehr als ein Schatten
     * angegeben ist, wird der erste angebene Schatten der unterste, über dem
     * die anderen Schatten liegen, z.B.: "4px 4px #ccc 2px 2px #aaa"
     */
    var Schatten = {};

    // Füge Schatten an ein einzelnes angegebenes Element an
    Schatten.add = function(element, schatten) {
      if (typeof element == "string")
        element = document.getElementById(element);

      // Brich den Schattenstring an Whitespace auseinander, nach Entfernen
      // von Whitespace an Anfang und Ende.
      schatten = schatten.replace(/^\s+/, "").replace(/\s+$/, "");
      var args = schatten.split(/\s+/);

      // Finde den Textknoten, der zu schattieren ist.
      // Dieses Modul wäre robuster, wenn wir alle Kinder schattieren würden.
      // Aus Gründen der Einfachheit belassen wir es bei einem.
      var textnode = element.firstChild;

      // Gib dem Containerelement relative Positionierung, damit
      // der Schatten relativ dazu positioniert werden kann.
      // Wir lernen noch, wie man die style-Eigenschaft so skriptsteuern kann.
      element.style.position = "relative";

      // Erzeuge den Schatten
      var numschatten = args.length/3;      // Wie viele Schatten?
      for(var i = 0; i < numschatten; i++) {   // für jeden
        var schattenX = args[i*3];       // X-Offset ermitteln
        var schattenY = args[i*31];       // Y-Offset ermitteln
        var schattenFarbe = args[i*3 + 2];   // und die Farbargumente

        // Erzeuge ein neues <span> zur Aufnahme des Schattens
        var schatten = document.createElement("span");
        // Verwende sein style-Attribut zur Angabe von Offset und Farbe
        schatten.setAttribute("style", "position:absolute; " +
                  "left:" + schattenX + "; " +
                  "top:" + schattenY + "; " +
                  "color:" + schattenFarbe + ";");

        // Füge dem schatten-Span eine Kopie des Textknotens hinzu
        schatten.appendChild(textnode.cloneNode(false));
        // und füge das Span in den Container ein
        element.appendChild(schatten);
    }

      // Jetzt setzen wir den Text über den Schatten. Erzeuge zuerst ein <span>
      var text = document.createElement("span");
      text.setAttribute("style", "position: relative"); // positioniere es
      text.appendChild(textnode); // Verschiebe den Original-Textknoten in dieses Span
      element.appendChild(text); // und füge es in den Container ein
    };

    // Durchsuche den Dokumentenbaum am und unter dem angegebenen Wurzelelement
    // nach Elementen mit dem angegebenen Tag-Namen. Wenn eines davon ein schatten-Attribut hat,
    // übergib es an die Schatten.add( )-Methode oben zur Erzeugung des Schattens.
    // Wenn root nicht angegeben ist, verwende das Document-
    // Objekt. Wenn tagname nicht angegeben ist, durchsuche alle Tags.
    Schatten.addAll = function(root, tagname) {
      if (!root) root = document;  // Verwende ganzes Dokument, wenn root fehlt
      if (!tagname) tagname = '*'; // Alle Tags nehmen, wenn tagname fehlt

      var elemente = root.getElementsByTagName(tagname);    // Finde alle Tags
      for(var i = 0; i < elemente.length; i++) {        // Für jedes Tag:
        var schatten = elemente[i].getAttribute("schatten"); // Hat es einen Schatten?
        if (schatten) Schatten.add(elemente[i], schatten);  // Erzeuge den Schatten
      }
    };

## 16.2.3 Abfrage der Position und Abmessungen von Elementen

Nachdem Sie jetzt wissen, wie die Position und Größe von HTML-Elementen mit CSS gesetzt wird, stellt sich natürlich die Frage, wie Sie die Position und Größe eines Elements abfragen können. Vielleicht möchten Sie die CSS-Positionierung verwenden, um ein DHTML-Popup-»Fenster« über einem anderen HTML-Element zu zentrieren. Dazu müssen Sie die Position und Abmessungen des Elements kennen.

In modernen Browsern geben die Eigenschaften `offsetLeft` und `offsetTop` eines Elements die X- und Y-Koordinate des Elements zurück. Ebenso geben die Eigenschaften `offsetWidth` und `offsetHeight` die Breite und Höhe zurück. Diese Eigenschaften sind schreibgeschützt und geben Pixelwerte als Zahlen zurück (nicht als CSS-Strings mit den angefügten »px«-Einheiten). Sie reflektieren die CSS-Eigenschaften `left`, `top`, `width` und `height`, sind aber nicht Teil des CSS-Standards. Überhaupt gehören sie zu keinem Standard: Sie wurden zum ersten Mal von Microsoft in IE 4 verwendet und dann von anderen Browsern übernommen.

Leider sind die Eigenschaften `offsetLeft` und `offsetTop` allein meistens nicht ausreichend. Die Eigenschaften geben die X- und Y-Koordinaten eines Elements bezogen auf ein anderes Element an. Das andere Element ist der Wert der `offsetParent`-Eigenschaft. Bei positionierten Elementen ist `offsetParent` normalerweise entweder das `<body>`-Tag, das `<html>`-Tag (dessen `offsetParent` den Wert `null` hat) oder ein positionierter Vorfahre des positionierten Elements. Bei nicht positionierten Elementen handhaben verschiedene Browser `offsetParent` unterschiedlich. Unter IE werden beispielsweise Tabellenzeilen in Bezug auf die Tabelle positioniert, die die Zeilen enthält. Grundsätzlich besteht daher die portable Vorgehensweise zur Bestimmung der Position eines Elements im Verfolgen der `offsetParent`-Referenzen und in der Akkumulation der Offsets. Sie können zum Beispiel folgenden Code verwenden:

    // Hole die X-Koordinate des Elements e.
    function getX(e) {
        var x = 0;                // Beginne mit 0
      while(e) {                  // Beginne beim Element e
        x += e.offsetLeft;        // Summiere den Offset hinzu
        e = e.offsetParent;       // und gehe zum offsetParent weiter
      }
      return x;                   // Gesamtwert für offsetLeft ausgeben
    }

Eine `getY( )`-Funktion lässt sich schreiben, indem man einfach `offsetLeft` durch `offsetTop` ersetzt.

Beachten Sie, dass die Werte, die im vorherigen Code von Funktionen wie z.B. `getX( )` zurückgegeben werden, Dokumentkoordinaten darstellen. Sie sind mit den CSS-Koordinaten kompatibel und werden nicht von der Position der Browser-Bildlaufleiste beeinflusst. In Kapitel 17 erfahren Sie, dass die mit Maus-Events assoziierten Koordinaten Fensterkoordinaten sind und zur Bildlaufleistenposition hinzuaddiert werden müssen, um sie zu Dokumentkoordinaten zu konvertieren.

Die oben gezeigte `getX( )`-Funktion hat ein Manko. Weiter unten erfahren Sie, dass die CSS-Eigenschaft `overflow` verwendet werden kann, um gescrollte Regionen in einem Dokument zu erzeugen. Wenn ein Element in einer solchen gescrollten Region erscheint, reflektieren seine Offset-Werte die Bildlaufleistenposition dieser Region nicht. Wenn Sie `overflow` in Ihrer Webseite verwenden, müssen Sie eventuell eine intelligentere Funktion zur Offsetberechnung verwenden, wie z.B. die folgende:

    function getY(element) {
        var y = 0;
        for(var e = element; e; e = e.offsetParent) // Iteriere durch die offsetParents
            y += e.offsetTop;                       // Summiere die offsetTop-Werte

       // Durchlaufe jetzt die Vorfahren des Elements, und suche nach allen,
       // bei denen scrollTop gesetzt ist. Subtrahiere diese Scrollwerte vom
       // Gesamtoffset. Wir müssen aber sicherstellen, dass der Durchlauf vor
       // document.body endet, sonst berücksichtigen wir auch das Dokumentscrollen
      // und konvertieren im Ende unseren Offset zu Fensterkoordinaten.
      for(e = element.parentNode; e && e != document.body; e = e.parentNode)
          if (e.scrollTop) y -= e.scrollTop; // subtrahiere Bildlaufleistenwerte

      // Das ist die Y-Koordinate unter Beachtung des dokumentinternen Scrolling.
      return y;
    }

## 16.2.4 Die dritte Dimension: z-index

Sie haben gesehen, dass die Eigenschaften `left`, `top`, `right` und `bottom` verwendet werden können, um die X- und Y-Koordinaten eines Elements innerhalb eines umgebenden Elements in der Ebene festzulegen. Die Eigenschaft `z-index` definiert eine Art dritter Dimension: Sie gibt an, in welcher Reihenfolge überlappende Elemente übereinander angeordnet, also übereinandergezeichnet werden. Die Eigenschaft `z-index` ist eine ganze Zahl, deren voreingestellter Wert null ist. Die Standardeinstellung ist null, aber Sie können positive oder negative Werte angeben. Wenn zwei Elemente übereinanderliegen – eventuell auch nur teilweise -, werden sie in der Reihenfolge vom niedrigsten zum höchsten `z-index` gezeichnet, sodass das Element mit dem höchsten `z-index` oben auf allen anderen liegt. Wenn überlappende Elemente den gleichen `z-index` besitzen, werden sie in der Reihenfolge gezeichnet, in der sie im Dokument erscheinen, sodass das letzte Element oben liegt.

Beachten Sie, dass sich die Eigenschaft `z-index` nur auf benachbarte Elemente bezieht, also auf Elemente, die Kinder des gleichen Containers sind. Wenn sich zwei Elemente überlappen, die keine Nachbarn sind, kann durch die Angabe einzelner Werte für die Eigenschaft `z-index` nicht festgelegt werden, welches oben erscheint. Stattdessen müssen Sie `z-index` für die benachbarten Container festlegen, die die beiden überlappenden Elemente enthalten.

Nicht-positionierte Elemente (d.h. Elemente mit der Standardpositionierung `position: static`) haben immer eine Anordnung, die Überlappungen vermeidet. Dadurch findet die Eigenschaft `z-index` bei diesen Elementen keine Anwendung. Dennoch besitzen sie einen voreingestellten `z-index` vom Wert null, sodass positionierte Elemente mit einem positiven `z-index` über dem normalen Dokumentenfluss liegen, positionierte Elemente mit einem negativen `z-index` dagegen unterhalb.

Einige Browser werten die Eigenschaft `z-index` nicht aus, wenn sie auf `<iframe>`-Tags angewandt wird, sodass solche Inline-Frames oberhalb von anderen Elementen schweben können, obwohl Sie eine andere Reihenfolge festgelegt haben. Das gleiche Problem kann Ihnen bei anderen fensterartigen Elementen begegnen, wie beispielsweise bei `<select>`-Menüs. Alte Browser zeigen mitunter alle Formularelemente unabhängig von den Einstellungen des `z-index` oberhalb von absolut positionierten Elementen an.

## 16.2.5 Anzeige und Sichtbarkeit von Elementen

Zwei CSS-Eigenschaften beeinflussen die Sichtbarkeit eines Dokumentelements: `visibility` und `display`. Die Eigenschaft `visibility` ist sehr einfach: Wenn sie auf den Wert `hidden` gesetzt wird, wird das Element nicht angezeigt; wird sie auf den Wert `visible` gesetzt, wird das Element angezeigt. Die Eigenschaft `display` ist allgemeiner und wird verwendet, um den Typ der Anzeige anzugeben, den ein Element erhält. Sie gibt an, ob ein Element ein Blockelement, ein Inline-Element, ein Listenelement oder was auch immer ist. Wenn `display` auf den Wert `none` gesetzt wird, wird das betreffende Element gar nicht angezeigt oder im Layout berücksichtigt.

Der Unterschied zwischen den Eigenschaften `visibility` und `display` hängt damit zusammen, wie sie sich auf Elemente auswirken, die nicht dynamisch positioniert werden. Bei einem Element, das im normalen Layout erscheint (dessen Eigenschaft `position` den Wert `static` oder `relative` besitzt), wird das Element unsichtbar, wenn Sie `visibility` auf den Wert `hidden` setzen, jedoch wird im Layout Platz für das Element reserviert. Ein solches Element kann wiederholt verborgen und angezeigt werden, ohne dass sich das Layout des Dokuments ändert. Wird dagegen für ein solches Element die Eigenschaft `display` auf den Wert `none` gesetzt, wird kein Platz für das Element im Layout des Dokuments reserviert – beiderseits werden andere Elemente so nah aneinandergelegt, als wäre das nicht angezeigte Element gar nicht vorhanden. (`visibility` und `display` verhalten sich bei absolut oder fest positionierten Elementen identisch, da diese ohnehin niemals in das Layout des Dokuments integriert sind.) Üblicherweise werden Sie `visibility` verwenden, wenn Sie mit dynamisch positionierten Elementen arbeiten. `display` ist hilfreich, wenn Sie beispielsweise Listen erzeugen möchten, die man expandieren und wieder schließen kann.

Beachten Sie, dass es nicht sehr sinnvoll ist, `visibility` oder `display` zum Verbergen eines Elements zu benutzen, wenn Sie diese Eigenschaften nicht mit Hilfe von JavaScript dynamisch setzen und damit das Element irgendwann sichtbar machen! Ich zeige Ihnen weiter unten im Kapitel, wie das funktioniert.

## 16.2.6 Das CSS-Boxmodell und Positionierungsdetails

Mit CSS lassen sich Rahmen, Innen- und Außenabstände für jedes Element festlegen. Das erschwert die CSS-Positionierung, da Sie wissen müssen, wie die Eigenschaften `width`, `height`, `top` und `left` berechnet werden, wenn Rahmen und Abstände vorhanden sind. Das _Boxmodell_ in CSS gibt eine präzise Antwort darauf. Sie wird in den folgenden Abschnitten ausführlich beschrieben und ist in Abbildung 16-3 bildlich dargestellt.

Abbildung 16-3. Das CSS-Boxmodell: Rahmen, Innenabstände und Positionierungsattribute

Fangen wir mit der Besprechung der Eigenschaften `border`, `margin` und `padding` an. Der Rahmen eines Elements ist ein Rechteck, das (teilweise) um das Element gezeichnet wird. Mit CSS-Eigenschaften können Sie die Art, Farbe und Breite des Rahmens festlegen:

    border: solid black 1px; /* Der Rahmen ist massiv, schwarz und 1 Pixel breit */
    border: 3px dotted red; /* Der Rahmen ist rot und 3 Pixel breit */

Es ist möglich, die Breite, den Stil und die Farbe des Rahmens über einzelne CSS-Eigenschaften festzulegen, und es kann auch nur der Rahmen für einzelne Seiten eines Elements angegeben werden. Um beispielsweise eine Linie unter einem Element zu zeichnen, legen Sie einfach dessen `border-bottom`-Eigenschaft fest. Es kann sogar die Breite, der Stil und die Farbe einer einzelnen Seite eines Elements angegeben werden. Abbildung 16-3 zeigt das: Sie enthält Rahmeneigenschaften wie `border-top-width` und `border-left-width`.

Die Eigenschaften `margin` und `padding` geben beide Leerraum um ein Element herum an. Der (wichtige) Unterschied ist, dass `margin` den Raum außerhalb des Rahmens angibt, also zwischen dem Rahmen und angrenzenden Elementen. `padding` gibt den Raum innerhalb des Rahmens an, also zwischen dem Rahmen und dem Elementinhalt. In der normalen Dokumentabfolge erzeugt der Außenabstand (margin) optisch einen Raum zwischen einem (eventuell umrahmten) Element und dessen Nachbarn. Der Innenabstand (padding) trennt den Elementinhalt optisch vom Elementrahmen. Hat ein Element keinen Rahmen, so ist normalerweise kein Innenabstand erforderlich. Ist ein Element dynamisch positioniert und nicht Teil der normalen Dokumentabfolge, so sind seine Außenabstände bedeutungslos. (Aus diesem Grund enthält Abbildung 16-3 keine CSS-Margin-Attribute).

Der Außen- und Innenabstand eines Elements lassen sich mit den Eigenschaften `margin` und `padding` festlegen:

    margin: 5px; padding: 5px;

Außen- und Innenabstände lassen sich auch für einzelne Seiten eines Elements festlegen:

    margin-left: 25px;
    padding-bottom: 5px;

Sie können auch Werte für die Außen- und Innenabstände aller vier Kanten eines Elements mit `margin`\- und `padding` festlegen. Die `top`-Werte werden zuerst angegeben und dann geht es im Uhrzeigersinn weiter: `top`, `right`, `bottom` und `left`. Der folgende Beispielcode zeigt zwei äquivalente Vorgehensweisen beim Setzen unterschiedlicher Innenabstände für jede der vier Seiten:

    padding: 1px 2px 3px 4px;
    /* Die vorhergehende Zeile ist äquivalent zu den folgenden Zeilen. */
    padding-top: 1px;
    padding-right: 2px;
    padding-bottom: 3px;
    padding-left: 4px;

Die Eigenschaft `margin` funktioniert auf dieselbe Weise.

Jetzt verstehen wir die Außen- und Innenabstände und Rahmen und können uns damit ein paar wichtige Einzelheiten der CSS-Positionierungsattribute ansehen. Zunächst legen `width` und `height` nur die Größe des Bereichs fest, in dem der Inhalt des Elements erscheinen soll, nicht aber den zusätzlichen Platz für Rahmen, Innen- und Außenabstände. Um die Gesamtgröße eines Elements mit einem Rahmen zu bestimmen, müssen Sie den linken und rechten Innenabstand sowie die Breite des linken und rechten Rahmens zur Breite des Elements und den oberen und unteren Innenabstand sowie die Breite des oberen und unteren Rahmens zur Höhe des Elements addieren.

Da `width` und `height` nur die für den Inhalt des Elements verwendete Fläche beschreiben, könnte man denken, dass auch `left` und `top` (sowie `right` und `bottom`) relativ zur Inhaltsfläche des umgebenden Elements gemessen würden. Das ist aber nicht der Fall. Der CSS-Standard legt fest, dass diese Werte relativ zur äußeren Kante des Innenabstands des umgebenden Elements gemessen werden (diese entspricht der inneren Kante des Elementrahmens).

Das wird alles in Abbildung 16-3 dargestellt, aber sehen wir uns trotzdem ein Beispiel an, um die Sache verständlicher zu machen. Nehmen wir an, Sie haben ein dynamisch positioniertes Container-Element erzeugt, das rings um seine Inhaltsfläche einen Innenabstand von 10 Pixeln Breite sowie einen Rahmen von 5 Pixeln Breite besitzt. Nehmen wir nun an, Sie wollten in diesem Container ein Kindelement dynamisch positionieren. Wenn Sie das Attribut `left` des Kindes auf den Wert »0 px« setzen, werden Sie feststellen, dass die linke Kante des Kindes direkt am inneren Rand der Containerrahmens klebt. Das Kind belegt also den Raum im Innenabstand, der vermutlich frei bleiben sollte (denn dies ist der Sinn eines Innenabstands). Wenn Sie das Kindelement in der oberen linken Ecke der Inhaltsfläche des Containers positionieren möchten, müssen Sie daher den Eigenschaften `left` und `top` jeweils den Wert »10px« zuweisen.

### 16.2.6.1 Eigenheiten des Internet Explorers

Nachdem Sie nun wissen, dass `width` und `height` nur die Größe der Inhaltsfläche angeben und `left`, `top`, `right` und `bottom` relativ zu den Innenabständen des umgebenden Elements gemessen werden, ist ein weiteres wichtiges Detail zu beachten: In den Versionen 4 bis 5.5 des Internet Explorers für Windows (nicht aber in Internet Explorer 5 auf Macintosh-Computern) sind die Eigenschaften `width` und `height` fehlerhaft implementiert: Sie enthalten dort auch den Rahmen und den Innenabstand, nicht aber den Außenabstand des Elements. Wenn Sie beispielsweise die Breite eines Elements auf 100 Pixel setzen und einen Innenabstand von 10 Pixeln sowie einen Rahmen von 5 Pixeln Breite links und rechts festlegen, ist die Inhaltsfläche des Elements bei diesen fehlerhaften Versionen des Internet Explorers nur 70 Pixel breit.

In Internet Explorer 6 funktionieren die CSS-Eigenschaften für die Position und Größe richtig, wenn der Browser im Standardmodus arbeitet, aber nicht richtig (wenn auch kompatibel zu älteren Versionen), wenn der Browser im Kompatibilitätsmodus arbeitet. Der Standardmodus – und damit die korrekte Implementierung des CSS-Modells für die Schachtelung von Elementen – wird durch die Anwesenheit eines Tags `<!DOCTYPE>` am Anfang des Dokuments aktiviert, wenn dort bekannt gegeben wird, dass das Dokument dem Standard HTML 4.0 (oder neuer) oder einer Version des XHTML-Standards entspricht. Die folgenden drei Deklarationen des HTML-Dokumenttyps sind Beispiele, die Internet Explorer 6 in den Standardmodus versetzen:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Strict//EN">
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
      "http://www.w3.org/TR/html4/loose.dtd">

Die Unterscheidung zwischen Standardmodus und Kompatibilitätsmodus (manchmal auch als _Quirks-Modus_ bezeichnet) gibt es nicht nur in Internet Explorer. Andere Browser brauchen ebenfalls `<!DOCTYPE>`-Deklarationen, um eine strikte Übereinstimmung mit dem Standard zu erzwingen und nehmen ohne diese Deklaration Rückwärtskompatibilität als Standardverhalten an. Allerdings hat nur IE ein so ungeheuerliches Kompatibilitätsproblem.

## 16.2.7 Farbe, Transparenz und Durchsichtigkeit

Die Erörterung der Rahmen enthielt Beispiele, in denen Rahmenfarben anhand der englischen umgangssprachlichen Namen »red« und »black« angegeben wurden. Die allgemeinere Schreibweise zur Festlegung von Farben in CSS ist die Verwendung von Hexadezimalzahlen. Damit werden die Farbkomponenten Rot, Grün und Blau gesetzt. Für jede Komponente können Sie eine oder zwei Zahlen verwenden, beispielsweise:

    #000000    /* Schwarz */
    #fff       /* Weiß */
    #f00       /* leuchtendes Rot */
    #404080    /* ungesättigtes Dunkelblau */
    #ccc       /* Hellgrau */

Zusätzlich zur Angabe von Rahmenfarben in dieser Farbnotation lassen sich auch Textfarben festlegen, und zwar mit der CSS-Eigenschaft `color`. Die Hintergrundfarbe jedes beliebigen Elements kann mit der `background-color`-Eigenschaft gesetzt werden. In CSS kann die genaue Position, Größe, Hintergrundfarbe und Rahmenfarbe von Elementen festgelegt werden. Das gibt Ihnen eine rudimentäre Grafikfähigkeit zum Zeichnen von Rechtecken und horizontalen und vertikalen Linien (durch Reduzierung der Breite bzw. Höhe). Auf dieses Thema komme ich noch einmal in Kapitel 22 zu sprechen, das die Verwendung der CSS-Positionierung und DOM-Skriptsteuerung zum Zeichnen von Säulendiagrammen erläutert.

Zusätzlich zur `background-color`-Eigenschaft können Sie auch Bilder angeben, die als Hintergrund für ein Element verwendet werden sollen. Die `background-image`-Eigenschaft legt das zu verwendende Bild fest, und `background-attachment`, `background-position` und `background-repeat` geben weitere Einzelheiten darüber an, wie dieses Bild gezeichnet werden soll. Mit der Kurzschrift-Eigenschaft `background` können Sie diese Eigenschaften in einem Schritt festlegen. Sie können diese Hintergrundbilder zum Erzeugen interessanter optischer Effekte verwenden, die jedoch den Umfang dieses Buchs überschreiten.

Es ist wichtig zu verstehen, dass der Hintergrund des Elements normalerweise transparent ist, wenn Sie für ein Element keine Hintergrundfarbe und kein Bild angeben. Wenn Sie beispielsweise ein `<div>`-Element über einen bereits vorhandenen Text in der normalen Dokumentabfolge absolut positionieren, ist dieser Text normalerweise hinter dem `<div>`-Element sichtbar. Wenn das `<div>` seinen eigenen Text enthält, können sich die Buchstaben überlappen und unleserlichen Wirrwarr ergeben. Allerdings sind nicht alle Elemente in der Standardeinstellung transparent. Zum Beispiel sehen Formularelemente mit einem transparenten Hintergrund nicht richtig aus, weswegen beispielsweise das `<button>`-Tag eine Standardhintergrundfarbe hat. Mit der Eigenschaft `background-color` können Sie diese Standardeinstellung ändern und sie sogar ausdrücklich auf »transparent« setzen.

Die bisher besprochene Transparenz zeigt die Extreme: Entweder hat ein Element einen völlig transparenten oder einen völlig deckenden Hintergrund. Es ist auch möglich, ein Element (sowohl seinen Hintergrund als auch seinen Inhalt im Vordergrund) als durchsichtig anzugeben. (Abbildung 16-4 enthält hierzu ein Beispiel.) Das machen Sie über die CSS3-Eigenschaft `opacity`. Der Wert dieser Eigenschaft ist eine Zahl zwischen 0 und 1, wobei 1 für 100% deckend (Standardeinstellung) und 0 für 0% deckend (bzw. 100% transparent) steht. `Opacity` wird vom Firefox-Browser unterstützt. Frühe Versionen von Mozilla unterstützten eine experimentelle Eigenschaft namens `-moz-opacity`. IE stellt mit seinem IE-spezifischer `filter`-Eigenschaft eine funktionsgleiche Alternative bereit. Um für ein Element eine Deckung von 75 Prozent zu erreichen, können Sie die folgenden CSS-Styles verwenden:

    opacity: .75;        /* standardmäßiger CSS3-Style für Transparenz */
    -moz-opacity: .75;     /* Transparenz für ältere Mozillas */
    filter: alpha(opacity=75); /* Transparenz für IE; beachten Sie den fehlenden
                  Dezimalpunkt */

## 16.2.8 Partielle Sichtbarkeit: overflow und clip

Mit der Eigenschaft `visibility` können Sie ein Element als Ganzes verbergen. Die Eigenschaften `overflow` und `clip` ermöglichen es, nur einen Teil eines Elements anzuzeigen. `overflow` gibt an, was passieren soll, wenn der Inhalt eines Elements über den dafür vorgesehenen Platz (der beispielsweise durch `width` und `height` festgelegt wurde) hinausragt. Es kann die folgenden Werte annehmen:

`visible`

Der Inhalt kann über den Platz hinausragen und wird dort gegebenenfalls angezeigt. Dies ist die Voreinstellung.

`hidden`

Der über den Platz hinausragende Inhalt wird abgeschnitten und verborgen, sodass außerhalb des Platzes, der durch die Größe und die Position festgelegt wurde, keinerlei Inhalt sichtbar ist.

`scroll`

Der Kasten, der das Element umgibt, wird mit dauerhaften senk- und waagerechten Scrollleisten versehen. Wenn der Inhalt über den Kasten hinausragt, ermöglichen die Leisten dem Benutzer, den zusätzlichen Inhalt so zu scrollen, dass er sichtbar wird. Dieser Wert wird nur berücksichtigt, wenn das Dokument auf einem Bildschirm angezeigt wird. Wenn das Dokument ausgedruckt wird, ergeben die Scrollleisten offensichtlich nur wenig Sinn.

`auto`

Scrollleisten werden nicht dauerhaft, sondern nur dann angezeigt, wenn der Inhalt mehr Platz benötigt, als für das Element festgelegt wurde.

Während Sie mit der Eigenschaft `overflow` festlegen können, was passiert, wenn der Inhalt des Elements größer ist als der umgebende Kasten, können Sie mit `clip` genau festlegen, welcher Teil eines Elements angezeigt werden soll – unabhängig davon, ob das Element in den dafür vorgesehenen Platz passt oder nicht. Diese Eigenschaft ist besonders nützlich für skriptmäßig gesteuerte DHTML-Effekte, bei denen ein Element allmählich angezeigt oder aufgedeckt wird.

Der Wert der Eigenschaft `clip` gibt die sogenannte Clipping-Region des Elements an. Im CSS2-Standard sind dies Rechtecke, die Syntax von `clip` lässt jedoch für zukünftige Versionen des Standards auch andere Formen zu. Die Syntax von `clip` sieht folgendermaßen aus:

    rect(oben rechts unten links)

Die Werte _`oben`_ , _`rechts`_ , _`unten`_ und _`links`_ geben die Begrenzungen des Clipping-Rechtecks relativ zur oberen linken Ecke des Kastens um das Element an. Um beispielsweise nur einen 100 × 100 Pixel großen Teil eines Elements anzuzeigen, können Sie dem Element diese `style`-Eigenschaft zuweisen:

    style="clip: rect(0px 100px 100px 0px);"

Beachten Sie, dass die vier in den Klammern angegebenen Werte Längeneinheiten sind und daher eine Maßeinheit wie `px` für Pixel tragen müssen. Prozentzahlen sind hier nicht erlaubt. Es können auch negative Werte angegeben werden, die angeben, dass die Clipping-Region über den für das Element festgelegten Kasten hinausragt. Sie können für jeden der vier Werte auch das Schlüsselwort `auto` angeben, um festzulegen, dass diese Kante der Clipping-Region mit der entsprechenden Kante des Kastens um das Element übereinstimmt. Beispielsweise können Sie von einem Element nur die linken 100 Pixel anzeigen lassen, indem Sie den folgenden Code verwenden:

    style="clip: rect(auto 100px auto auto);"

Beachten Sie, dass zwischen den Werten keine Kommata stehen und dass die Kanten der Clipping-Region im Uhrzeigersinn von der oberen Kante ausgehend angegeben werden.

## 16.2.9 Beispiel: Überlappende durchsichtige Fenster

Dieser Abschnitt schließt mit einem Beispiel, das viele der hier besprochenen CSS-Eigenschaften veranschaulicht. Beispiel 16-3 verwendet CSS, um einen visuellen Effekt mit scrollenden, überlappenden und durchsichtigen Fenstern im Browserfenster zu erzeugen. Abbildung 16-4 zeigt, wie das aussieht. Das Beispiel enthält weder JavaScript-Code noch Event-Handler, sodass man mit den Fenstern nicht interagieren kann (außer sie zu scrollen), es ist aber eine nützliche Demonstration der Leistungsfähigkeit von CSS-Effekten.

Beispiel 16-3. Anzeige von Fenstern mit CSS

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
    <head>
    <style type="text/css">
    /**
     * Dieses Stylesheet definiert drei Stilregeln, die wir im Body des
     * Dokuments verwenden, um visuelle "Fenster" zu erzeugen.
     * Die Regeln verwenden Positionierungseigenschaften, um die Gesamtgröße des Fensters
     * und die Position der Komponenten festzulegen. Eine Änderung der Fenstergröße erfordert
     * eine sorgfälitge Anpassung der Positionierungseigenschaften in allen drei Regeln.
     **/
    div.window { /* Legt Größe und Rahmen des Fensters fest */
      position: absolute;                            /* Die Position wird anderswo festgelegt */
      width: 300px; height: 200px;                   /* Fenstergröße ohne Rahmen */
      border: outset gray 3px;                       /* 3D-Rahmeneffekt "outset" */
    }

    div.titlebar { /* Legt Position, Größe und Stil der Titelleiste fest */
      position: absolute;                          /* Das Element wird positioniert */
      top: 0px; height: 18px;                      /* Höhe: 18px + Innenabstand und Rahmen */
      width: 290px;                                /* 290 + 5px Innenabstand links und rechts = 300 */
      background-color: ActiveCaption;             /* Systemfarbe für die Titelleiste */
      border-bottom: groove gray 2px;              /* Die Titelleiste hat nur unten einen Rahmen */
      padding: 3px 5px 2px 5px;                    /* Werte im Uhrzeigersinn: oben, rechts, unten, links */
      font: bold 11pt sans-serif;                  /* Titelschriftart */
    }

    div.content { /* Legt Position, Größe und Scrollleisten für den Inhalt fest */
      position: absolute;                          /* Das Element wird positioniert */
      top: 25px;                                   /* 18px Titel + 2px Rahmen + 3px + 2px Innenabstand */
      height: 165px;                               /* 200px gesamt – 25px Titelleiste – 10px Innenabstand */
      width: 290px;                                /* 300px Breite – 10px Innenabstand */
      padding: 5px;                                /* Etwas Platz auf allen Seiten */
      overflow: auto;                              /* Scrollleisten, wenn wir sie brauchen */
      background-color: #ffffff;                   /* Als Standard: weißer Hintergrund */
    }

    div.translucent {                              /* Diese Klasse macht ein Fenster teilweise transparent */
      opacity: .75;                                /* standardmäßiger CSS3-Style für Transparenz */
      -moz-opacity: .75;                           /* Transparenz für ältere Mozillas */
      filter: alpha(opacity=75);                   /* Transparenz für IE */
    }
    </style>
    </head>

    <body>
    <!-- So definieren wir ein Fenster: ein "window"-div mit divs für Titelleiste -->
    <!-- und Inhalt darin. Die Position wird mit einer Eigenschaft festgelegt, -->
    <!-- die die Styles des Stylesheets ergänzt. -->
    <div class="window" style="left: 10px; top: 10px; z-index: 10;">
    <div class="titlebar">Test-Fenster</div>
    <div class="content">
    1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>0<br><!-- Viele Zeilen, um -->
    1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>0<br><!-- Scrollleisten zu zeigen -->
    </div>
    </div>

    <!-- Und noch ein Fenster mit anderer Position, Farbe und in fetter Schrift -->
    <div class="window" style="left: 75px; top: 110px; z-index: 20;">
    <div class="titlebar">Noch ein Fenster</div>
    <div class="content translucent"
       style="background-color:#d0d0d0; font-weight:bold;">
    Dies ist ein weiteres Fenster. Sein <tt>z-index</tt> sorgt dafür, dass es über dem anderen angezeigt wird.
    CSS-Styles machen seinen Inhaltsbereich in Browsern durchsichtig, die das unterstützen.
    </div>
    </div>
    </body>

Die größte Beschränkung dieses Beispiels besteht darin, dass das Stylesheet eine feste Größe für alle Fenster vorschreibt. Da die Titelleiste und der Inhaltsteil des Fensters exakt in dem umgebenden Fenster positioniert werden müssen, erfordert eine Änderung der Größe des Fensters die Anpassung verschiedener Positionierungseigenschaften in allen drei von dem Stylesheet definierten Regeln. In einem statischen HTML-Dokument kann man dies nicht leicht erreichen; wenn wir aber mit einem Skript auf alle benötigten Eigenschaften zugreifen könnten, wäre es nicht so schwierig. Diesem Thema werden wir uns im nächsten Abschnitt zuwenden.

# 16.3 Eingebettete Styles per Skript steuern

DHTML ist deswegen schwierig, weil Sie die Eigenschaften, die auf einzelne Elemente eines Dokuments angewandt werden, mit JavaScript dynamisch ändern können. Der DOM Level 2-Standard definiert eine API, die dies recht einfach macht. In Kapitel 15 haben Sie gesehen, wie man mit der DOM API Referenzen auf Elemente eines Dokuments erhält, indem man entweder den Namen des Tags oder die ID verwendet oder indem das Dokument rekursiv durchlaufen wird. Wenn Sie eine Referenz auf das Element, dessen Stil Sie verändern möchten, beschafft haben, verwenden Sie dessen Eigenschaft `style`, um ein CSS2Properties-Objekt für dieses Element zu erhalten. Dieses JavaScript-Objekt besitzt JavaScript-Eigenschaften, die sämtlichen Eigenschaften von CSS1 und CSS2 entsprechen. Wenn Sie diesen Eigenschaften einen Wert zuweisen, hat dies die gleiche Auswirkung wie die Zuweisung eines entsprechenden Stils in einer `style`-Eigenschaft des Elements. Durch das Auslesen dieser Eigenschaften erhalten Sie den Wert der CSS-Eigenschaft, falls in der `style`-Eigenschaft des Elements ein solcher angegeben wurde. Das CSS2Properties-Objekt wird in Teil IV dokumentiert.

Abbildung 16-4. Mit CSS erzeugte Fenster

Dabei ist zu beachten, dass das CSS2Properties-Objekt, das Sie mit Hilfe der Eigenschaft `style` eines Elements erhalten, nur die in einem Dokument eingebetteten Styles eines Elements angibt. Sie können das CSS2Properties-Objekt nicht verwenden, um Informationen über die durch ein Stylesheet angegebenen Styles zu erhalten, die auf das Element angewandt werden. Indem Sie Eigenschaften dieses Objekts setzen, definieren Sie eingebettete Styles, die die von Stylesheets angegebenen überschreiben.

Betrachten Sie beispielsweise das folgende Skript. Es sucht alle `<img>`-Elemente im Dokument und sucht (auf Basis ihrer Größe) diejenigen heraus, die Werbung in Form von Bannern zu enthalten scheinen. Wenn das Skript ein Werbebild entdeckt, verwendet es die Eigenschaft `style.visibility`, um die CSS-Eigenschaft `visibility` auf den Wert `hidden` zu setzen und so die Werbung auszublenden:

    var bilder = document.getElementsByTagName("img"); // Suche alle Bilder
    for(var i = 0; i < bilder.length; i++) {      // Durchlaufe sie
      var bild=bilder[i];
      if (bild.width == 468 && bild.height == 60)  // Wenn es ein 468x60-Banner ist ...
        bild.style.visibility = "hidden";     // Verbirg es!
    }

Nebenbei bemerkt: Sie können dieses einfache Skript auch in Form einer `javascript:`-URL als Lesezeichen in Ihrem Browser benutzen (siehe "13.4.1 Bookmarklets").

## 16.3.1 Namenskonventionen: CSS-Eigenschaften in JavaScript

Viele CSS-Eigenschaften, wie beispielsweise `font-family`, enthalten in ihrem Namen einen Bindestrich. In JavaScript wird der Bindestrich jedoch als Minuszeichen interpretiert, sodass ein Ausdruck wie der folgende nicht verwendet werden kann:

    element.style.font-family = "sans-serif";

Aus diesem Grund unterscheiden sich die Namen der Eigenschaften des CSS2Properties-Objekts leicht von den Namen der eigentlichen CSS-Eigenschaften. Wenn eine CSS-Eigenschaft einen oder mehrere Bindestriche enthält, wird der Name der entsprechenden Eigenschaft des CSS2Properties-Objekts gebildet, indem die Bindestriche weggelassen und die auf die Bindestriche folgenden Buchstaben großgeschrieben werden. Die Eigenschaft `border-left-width` wird daher mit der Eigenschaft `borderLeftWidth` angesprochen; auf `font-family` können Sie beispielsweise folgendermaßen zugreifen:

    element.style.fontFamily = "sans-serif";

Es gibt einen weiteren Unterschied zwischen der Benennung von CSS-Eigenschaften und JavaScript-Eigenschaften des Objekts CSS2Properties. Das Wort »float« ist ein Schlüsselwort in Java und anderen Sprachen; es wird zwar derzeit in JavaScript nicht verwendet, ist aber für zukünftige Erweiterungen reserviert. Daher kann das CSS2Properties-Objekt keine Eigenschaft namens `float` besitzen, die der CSS-Eigenschaft `float` entspricht. Dieses Problem wird gelöst, indem der CSS-Eigenschaft `float` der String »css« vorangestellt wird, sodass die Eigenschaft den Namen `cssFloat` besitzt. Um also den Wert der Eigenschaft `float` eines Elements abzufragen oder zu setzen, verwenden Sie die Eigenschaft `cssFloat` des CSS2Properties-Objekts.

## 16.3.2 Arbeiten mit Stileigenschaften

Wenn Sie die Stileigenschaften des CSS2Properties-Objekts benutzen, müssen Sie daran denken, alle Werte als Strings anzugeben. In einem Stylesheet oder einem `style`-Attribut können Sie Folgendes schreiben:

    position: absolute; font-family: sans-serif; background-color: #ffffff;

Um das Gleiche für ein Element `e` mit Hilfe von JavaScript zu erreichen, müssen Sie alle Werte in Anführungszeichen einschließen:

    e.style.position = "absolute";
    e.style.fontFamily = "sans-serif";
    e.style.backgroundColor = "#ffffff";

Beachten Sie, dass die Semikola außerhalb der Strings stehen. Es handelt sich bei ihnen um ganz gewöhnliche Semikola in JavaScript; die in CSS verwendeten Semikola werden in den Stringwerten, die Sie in JavaScript angeben, nicht benötigt.

Denken Sie außerdem daran, dass alle Positionierungseigenschaften mit einer Längeneinheit versehen werden müssen. Den Wert der Eigenschaft `left` können Sie daher nicht wie folgt setzen:

    e.style.left = 300;  // falsch: dies ist eine Zahl, kein String
    e.style.left = "300"; // falsch: die Längeneinheit fehlt

Einheiten werden benötigt, wenn Stileigenschaften in JavaScript ein Wert zugewiesen werden soll – genau wie bei der Angabe von Stileigenschaften in Stylesheets. Um der Eigenschaft `left` eines Elements `e` den Wert 300 Pixel zuzuweisen, müssen Sie die folgende Anweisung verwenden:

    e.style.left = "300px";

Wenn Sie der Eigenschaft `left` einen berechneten Wert zuweisen möchten, müssen Sie am Ende der Berechnung die Maßeinheit anhängen:

    e.style.left = (x0 + left_margin + left_border + left_padding) + "px";

Durch das Anhängen von Einheiten, das ja in Form eines Strings erfolgt, wird der berechnete Wert von einer Zahl in einen String umgewandelt.

Sie können das CSS2Properties-Objekt auch verwenden, um die Werte der CSS-Eigenschaften abzufragen, die ausdrücklich im `style`-Attribut eines Elements gesetzt wurden, oder um einen Wert eines eingebetteten Styles zu lesen, der zuvor von einer JavaScript-Anweisung gesetzt wurde. Sie müssen jedoch wiederum daran denken, dass die von diesen Eigenschaften zurückgegebenen Werte Strings und keine Zahlen sind. Die folgenden Anweisungen (die davon ausgehen, dass die Rahmen des Elements `e` durch eingebettete Styles angegeben wurden) könnten daher nicht den von Ihnen beabsichtigten Effekt haben:

    var gesamtRandbreite = e.style.marginLeft + e.style.marginRight;

Stattdessen sollten Sie eine Anweisung wie die folgende verwenden:

    var totalMarginWidth = parseInt(e.style.marginLeft) +
      parseInt(e.style.marginRight);

Dieser Ausdruck entfernt einfach die angegebenen Einheiten am Ende der beiden zurückgegebenen Strings. Er nimmt an, dass sowohl `marginLeft` als auch `marginRight` mit denselben Einheiten angegeben wurden. Wenn Sie ausschließlich die Einheit Pixel in Ihren eingebetteten Styles verwenden, können Sie üblicherweise in dieser einfachen Weise die Einheiten abschneiden.

Wie bereits gesagt, sind einige CSS-Eigenschaften wie `margin` Abkürzungen für andere Eigenschaften wie `margin-top`, `margin-right`, `margin-bottom` und `margin-left`. Das CSS2Properties-Objekt besitzt Eigenschaften, die diesen Abkürzungen entsprechen. Beispielsweise können Sie die Eigenschaft `margin` folgendermaßen setzen:

    e.style.margin = topMargin + "px " + rightMargin + "px " +
             bottomMargin + "px " + leftMargin + "px";

Es ist jedoch einfacher, die vier Abstandseigenschaften separat zu setzen:

    e.style.marginTop = topMargin + "px";
    e.style.marginRight = rightMargin + "px";
    e.style.marginBottom = bottomMargin + "px";
    e.style.marginLeft = leftMargin + "px";

Sie können auch die Werte von zusammengefassten Eigenschaften abfragen; meist lohnt sich das jedoch nicht, da Sie den zurückgegebenen Wert analysieren und in die einzelnen Bestandteile zerlegen müssen. Dies ist oft recht kompliziert, sodass es einfacher ist, die Eigenschaften einzeln abzufragen.

Lassen Sie mich zum Abschluss noch einmal betonen, dass die Eigenschaften eines CSS2Properties-Objekts, das Sie aus der Eigenschaft `style` eines HTMLElement-Objekts gewonnen haben, die Werte der eingebetteten Stilattribute des Elements darstellen. Eine dieser Eigenschaften zu setzen ist daher das Gleiche, wie eine CSS-Eigenschaft im `style`-Attribut des Elements zu setzen: Nur das eine Element wird davon beeinflusst, und der so festgelegte Wert hat Vorrang vor allen anderen Quellen der CSS-Kaskade. Wenn Sie Java-Script verwenden, um DHTML-Effekte zu erzeugen, ist die genaue Steuerung einzelner Elemente genau das, was Sie wünschen.

Wenn Sie andererseits die Werte dieser CSS2Properties-Eigenschaften lesen, erhalten Sie nur dann sinnvolle Werte, wenn Ihr JavaScript-Code oder das HTML-Element, das Sie verwenden, eine eingebettete `style`-Eigenschaft besitzt, das den gewünschten Eigenschaften einen Wert zuweist. Beispielsweise kann Ihr Dokument ein Stylesheet enthalten, das den linken Außenabstand für alle Absätze auf 30 Pixel setzt. Wenn Sie die Eigenschaft `marginLeft` eines Ihrer Absatzelemente abfragen, erhalten Sie jedoch einen leeren String, sofern der Absatz nicht eine eigene `style`-Eigenschaft besitzt, die die Einstellung des Stylesheets überschreibt. Obwohl das CSS2Properties-Objekt also zum Setzen von Styles nützlich ist, die alle anderen Styles überschreiben, bietet es uns keine Möglichkeit, die CSS-Kaskade abzufragen und eine vollständige Übersicht über alle Styles für ein gegebenes Element zu erhalten. "16.4 Berechnete Styles per Skript steuern" geht kurz auf die `getComputedStyle( )`-Methode und die entsprechende IE-Eigenschaft `currentStyle` ein, die diese Fähigkeit zur Verfügung stellen.

## 16.3.3 Beispiel: CSS-Tooltips

Beispiel 16-4 ist ein Modul mit JavaScript-Code zur Anzeige einfacher DHTML-Tooltips, wie des in Abbildung 16-5 abgebildeten.

Abbildung 16-5. Ein CSS-Tooltip

Die Tooltips werden durch zwei ineinander verschachtelte `<div>`-Elemente angezeigt. Das äußere `<div>` ist absolut positioniert und hat einen Hintergrund, der als Tooltip-Schatten dient. Das innere `<div>` ist relativ positioniert in Bezug auf den Schatten und zeigt den Inhalt des Tooltips an. Der Tooltip bekommt Styles aus drei unterschiedlichen Quellen. Als erste Quelle gibt ein statisches Stylesheet den Schatten, die Hintergrundfarbe, den Rahmen und die Schriftart des Tooltips an. Als zweite Quelle werden eingebettete Styles (wie z.B. `position:absolute`) angegeben, wenn die Tooltip-`<div>`-Elemente im `Tooltip( )`-Konstruktor erzeugt werden. Die dritte Quelle sind die `top`-, `left`\- und `visibility`-Styles, die gesetzt werden, wenn der Tooltip mit der `Tooltip.show( )`-Methode angezeigt wird.

Beachten Sie, dass Beispiel 16-4 ein einfaches Tooltip-Modul ist, das einfach Tooltips anzeigt und wieder versteckt. Dieses Beispiel wird in Beispiel 17-3 zu einem etwas praktischeren erweitert, das Unterstützung für die Anzeige von Tooltips in Reaktion auf Mouseover-Events hinzufügt.

Beispiel 16-4. Tooltips

    /**
     * Tooltip.js: Einfache CSS-Tooltips mit Schattenwurf.
     *
     * Dieses Modul definiert eine Tooltip-Klasse. Erzeuge ein Tooltip-Objekt mit dem
     * Tooltip( )-Konstruktor. Dann mache ihn mit der show( )-Methode sichtbar.
     * Wenn wir fertig sind, verstecke es mit hide( ).
     *
     * Beachten Sie, dass dieses Modul mit entsprechenden CSS-Klassendefinitionen
     * verwendet werden muss, damit es richtig angezeigt wird. Das hier sind Beispiele:
     *  .tooltipSchatten {
     *   background: url(shadow.png); /* durchsichtiger Schatten * /
     *  }
     *
     *  .tooltipContent {
     *   left: −4px; top: −4px;    /* wie viel des Schattens ist sichtbar? * /
     *   background-color: #ff0;    /* gelber Hintergrund * /
     *   border: solid black 1px;   /* dünner schwarzer Rahmen * /
     *   padding: 5px;         /* Abstand zwischen Text und Rahmen * /
     *   font: bold 10pt sans-serif;  /* kleine fette Schriftart * /
     *  }
     *
     * In Browsern, die durchsichtige PNG-Grafiken unterstützen, kann man
     * durchsichtige Schatten werfen. Andere Browser müssen Deckfarbe verwenden oder
     * Transparenz mit einem gesprenkelten GIF simulieren, das zwischen deckenden
     * und transparenten Pixeln abwechselt.
     */
    function Tooltip( ) { // Die Konstruktorfunktion für die Tooltip-Klasse
      this.tooltip = document.createElement("div");  // div für Schatten
      this.tooltip.style.position = "absolute";    // absolut positioniert
      this.tooltip.style.visibility = "hidden";    // anfangs unsichtbar
      this.tooltip.className = "tooltipSchatten";   // um Styles zu setzen

      this.inhalt = document.createElement("div"); // div für Inhalt
      this.inhalt.style.position = "relative";   // relativ positioniert
      this.inhalt.className = "tooltipContent";   // um Styles zu setzen

      this.tooltip.appendChild(this.inhalt);    // Inhalt zu Schatten hinzufügen
    }

    // Inhalt und Position des Tooltips setzen und ihn anzeigen
    Tooltip.prototype.show = function(text, x, y) {
      this.inhalt.innerHTML = text;       // Text des Tooltips setzen.
      this.tooltip.style.left = x + "px";    // Position setzen.
      this.tooltip.style.top = y + "px";
      this.tooltip.style.visibility = "visible"; // Sichtbar machen

      // Tooltip ins Dokument einfügen, falls das noch nicht passiert ist
      if (this.tooltip.parentNode != document.body)
        document.body.appendChild(this.tooltip);
    };

    // Tooltip verstecken
    Tooltip.prototype.hide = function( ) {
      this.tooltip.style.visibility = "hidden"; // Unsichtbar machen.
    };

## 16.3.4 DHTML-Animationen

Animationen zählen zu den leistungsfähigsten DHTML-Techniken, die Sie mit JavaScript und CSS nutzen können. Eigentlich sind DHTML-Animationen nichts Besonderes: Sie müssen lediglich in periodischen Abständen eine oder mehrere Stileigenschaften eines oder mehrerer Elemente ändern. Um beispielsweise eine Grafik von links an ihren Platz zu schieben, erhöhen Sie die `style.left`-Eigenschaft des Bilds wiederholt, bis es die gewünschte Position erreicht hat. Sie können auch die Eigenschaft `style.clip` wiederholt ändern, um ein Bild Pixel für Pixel aufzudecken.

Beispiel 16-5 zeigt eine einfache HTML-Datei, die ein zu animierendes `div`-Element sowie ein kurzes Skript definiert, das die Rahmenfarbe des Elements alle 500 Millisekunden ändert. Beachten Sie, dass die Farbänderung einfach dadurch erfolgt, dass einer CSS-Eigenschaft ein Wert zugewiesen wird. Eine Animation wird daraus, indem wir die Farbe mit Hilfe der Funktion `setInterval( )` des Window-Objekts wiederholt ändern. (Sie müssen `setInterval( )` oder `setTimeout( )` für alle DHTML-Animationen benutzen; schlagen Sie in der Referenz in Teil IV nach, wenn Sie sich nicht mehr an die Einzelheiten der Methoden erinnern). Beachten Sie außerdem, wie wir den Modulo-Operator (`%`) zur Bestimmung des Rests bei einer ganzzahligen Division verwenden, um die Farben zyklisch zu durchlaufen. Falls Sie vergessen haben, wie dieser Operator funktioniert, können Sie dies in Kapitel 5 nachschlagen.

Beispiel 16-5. Eine einfache Animation mit Farbänderungen

    <!-- Dieses div-Element wird animiert -->
    <div id="dringend"><h1>Alarmstufe Rot!</h1>Der Webserver wird angegriffen!</div>

    <script>
    var e = document.getElementById("dringend");    // Element-Objekt beschaffen
    e.style.border = "solid black 5px";        // Gib ihm einen Rahmen
    e.style.padding = "50px";             // und etwas Innenabstand
    var farben = ["white", "yellow", "orange", "red"] // die zu durchlaufenden Farben
    var naechsteFarbe = 0;               // Position im Farbzyklus
    // Rufe die folgende Funktion alle 500 ms zur Animierung der Rahmenfarbe auf
    setInterval(function( ) {
            e.style.borderColor=farben[naechsteFarbe++%farben.length];
          }, 500);
    </script>

Beispiel 16-5 erzeugt eine sehr einfache Animation. In der Praxis werden bei den meisten CSS-Animationen zwei oder mehr Stileigenschaften gleichzeitig verändert (wie beispielsweise `top`, `left` und `clip`). Komplexere Animationen können mit der in Beispiel 16-5 gezeigten Technik schnell recht kompliziert werden. Um außerdem den Benutzer nicht zu belästigen, sollten Animationen normalerweise nur eine kurze Zeit laufen und anschließend anhalten – anders als bei der Animation in Beispiel 16-5.

Beispiel 16-6 zeigt eine JavaScript-Datei, die eine Funktion für eine CSS-Animation definiert, mit der auch komplexere Animationen erzeugt werden können.

Beispiel 16-6. Ein Framework für CSS-basierte Animationen

    /**
     * AnimiereCSS.js:
     * Diese Datei definiert eine Funktion animiereCSS( ), die als Framework für
     * die Erzeugung CSS-basierter Animationen dient. Die Argumente für diese Funktion sind:
     *
     *   element: Das zu animierende HTML-Element.
     *   anzBilder: Die Anzahl der Einzelbilder in der Animation.
     *   zeitProBild: Die Anzahl der Millisekunden zur Anzeige der Einzelbilder.
     *   animation: Ein Objekt, das die Animation definiert; s.u.
     *   wennFertig: Eine optionale Funktion, die nach Ende der Animation aufgerufen wird.
     *         Wenn angegeben, wird dieser Funktion element als Argument übergeben.
     *
     * Die Funktion animiereCSS( ) definiert einfach ein Framework für Animationen.
     * Die Eigenschaften des Animationsobjekts legen die durchzuführende Animation fest.
     * Jede Eigenschaft sollte den gleichen Namen wie eine CSS-Stileigenschaft * besitzen.
     * Der Wert jeder Eigenschaft muss eine Funktion sein, die gültige Werte für diese
     * Stileigenschaft zurückgibt. Jeder Funktion wird die Nummer des Einzelbilds
     * und die bereits verstrichene Zeit übergeben, die verwendet werden können,
     * um den zurückzugebenden Stilwert zu berechnen. Um beispielsweise ein Bild so zu animieren,
     * dass es von links oben hereingleitet, können Sie animiereCSS wie folgt aufrufen:
     *
     * animiereCSS(bild, 25, 50, // Animiere das Bild in 25 Einzelbildern mit je 50ms Standzeit
     *       { // Setze die Eigenschaften top und left für jedes Einzelbild:
     *        top: function(einzelbild,zeit) { return einzelbild*8 + "px"; },
     *        left: function(einzelbild,zeit) { return einzelbild*8 + "px"; }
     *       });
     *
     **/
    function animiereCSS(element, anzBilder, zeitProBild, animation, wennFertig) {
      var einzelbild = 0; // Speichere die aktuelle Nummer des Einzelbilds
      var zeit = 0;    // Speichere die verstrichene Zeit

      // Bereite den Aufruf von zeigeNaechstesBild( ) alle zeitProBild Millisekunden vor.
      // Dadurch werden alle Einzelbilder der Animation angezeigt.
      var intervallId = setInterval(zeigeNaechstesBild, zeitProBild);

      // Der Aufruf von animiereCSS( ) ist nun beendet, aber durch die obige Zeile
      // ist sichergestellt, dass die im Folgenden definierte innere Funktion
      // einmal für jedes Einzelbild der Animation aufgerufen wird.
      function zeigeNaechstesBild( ) {
        if (einzelbild >= anzBilder) {       // Prüfe, ob wir fertig sind
          clearInterval(intervallId);       // Wenn ja, rufen wir uns nicht                               // mehr selbst auf
          if (wennFertig) wennFertig(element);  // Rufe wennFertig auf,
          return;                 // und wir sind fertig
        }

        // Durchlaufe nun alle im Animationsobjekt definierten Eigenschaften
        for(var cssEigenschaft in animation) {
          // Rufe die Animationsfunktion jeder Eigenschaft auf, und übergib
          // die Einzelbildnummer und die verstrichene Zeit. Verwende den
          // Rückgabewert der Funktion als neuen Wert der entsprechenden Stileigenschaft
          // des übergebenen Elements. Verwende try/catch, um Exceptions
          // abzufangen, die durch ungültige Rückgabewerte erzeugt werden.
          try {
            element.style[cssEigenschaft] = animation[cssEigenschaft]
            (einzelbild, zeit);
          } catch(e) {}
        }

        einzelbild++;     // Erhöhe die Einzelbildnummer
        zeit += zeitProBild; // Erhöhe die verstrichene Zeit
      }
    }

Der in diesem Beispiel definierten Funktion `animateCSS( )` werden fünf Argumente übergeben. Das erste gibt das zu animierende HTMLElement-Objekt an. Das zweite und dritte Argument geben die Anzahl der Einzelbilder der Animation sowie die Standzeit jedes Einzelbildes an. Das vierte Argument ist ein JavaScript-Objekt, das die durchzuführende Animation festlegt. Als fünftes Argument kann optional eine Funktion übergeben werden, die einmal ausgeführt werden soll, wenn die Animation beendet ist.

Das vierte an `animateCSS( )` übergebene Argument ist das entscheidende. Jede Eigenschaft des JavaScript-Objekts muss den gleichen Namen haben wie eine CSS-Eigenschaft; die Werte aller Eigenschaften müssen jeweils eine Funktion sein, die einen gültigen Wert für den angegebenen Stil zurückgibt. Sobald ein neues Einzelbild der Animation angezeigt wird, wird jede Funktion aufgerufen, um einen neuen Wert für jede der Stileigenschaften zu bestimmen. Jeder Funktion wird die Nummer des Einzelbilds sowie die insgesamt verstrichene Zeit übergeben, sodass diese Werte zur Berechnung des neuen Werts herangezogen werden können.

Der Code in Beispiel 16-6 ist recht einfach; wie Sie bald sehen werden, ist die eigentliche Komplexität in den Eigenschaften des Animationsobjekts eingebettet, das Sie an `animiereCSS( )` übergeben. `animiereCSS( )` definiert eine innere Funktion namens `zeigeNaechstesBild( )` und macht nicht viel mehr, als die Methode `setInterval( )` zu verwenden, um `zeigeNaechstesBild( )` wiederholt aufzurufen. `zeigeNaechstesBild( )` durchläuft die Eigenschaften des Animationsobjekts und ruft die einzelnen Funktionen auf, um die neuen Werte für die Stileigenschaften zu berechnen.

Da `zeigeNaechstesBild( )` innerhalb von `animiereCSS( )` definiert wird, kann diese Funktion auf die Argumente und lokalen Variablen von `animiereCSS( )` zugreifen, auch wenn `zeigeNaechstesBild( )` aufgerufen wird, nachdem `animiereCSS( )` bereits beendet ist! (Wenn Sie nicht verstehen, warum dies funktioniert, können Sie noch einmal "8.8 Funktionsgeltung und Closures" studieren).

Durch ein Beispiel wird die Verwendung von `animiereCSS( )` viel leichter verständlich. Der folgende Code bewegt ein Element auf dem Bildschirm nach oben, während es allmählich durch das Vergrößern der Clipping-Region aufgedeckt wird.

    // Animiere das Element mit dem Bezeichner "title" in 40 Einzelbildern,
    // die je 50 Millisekunden Standzeit haben
    animiereCSS(document.getElementById("title"), 40, 50,
          { // Setze die Stileigenschaften top und clip jedes Einzelbildes wie folgt:
            top: function(f,t) { return 300-f*5 + "px"; },
            clip: function(f,t) {return "rect(auto "+f*10+"px auto auto)";}
          });

Das folgende Code-Fragment verwendet `animiereCSS( )`, um ein Button-Objekt im Kreis zu bewegen. Das optionale fünfte Argument wird verwendet, um den Button-Text in »Fertig« zu ändern, wenn die Animation beendet ist. Beachten Sie, dass das animierte Element als Argument an die Funktion übergeben wird, die das fünfte Argument bildet.

    // Bewege einen Button im Kreis, und ändere zum Schluss den von ihm angezeigten Text
    animiereCSS(document.forms[0].elements[0], 40, 50, // Button, 40 Bilder, 50ms
          { // Diese Berechnung definiert einen Kreis mit einem Radius von 100 an der
           // Stelle (200,200):
            left: function(b,t){ return 200 + 100*Math.cos(b/8) + "px"},
            top: function(b,t){ return 200 + 100*Math.sin(b/8) + "px"}
          },
    function(button) { button.value = "Fertig"; });

Die JavaScript-Bibliothek Scriptaculous enthält ein hochentwickeltes Animationsframework mit vielen leistungsfähigen vordefinierten Animationseffekten. Besuchen Sie die einschlägig benannte Website <http://script.aculo.us/,> um mehr darüber zu lernen.

# 16.4 Berechnete Styles per Skript steuern

Die `style`-Eigenschaft eines HTML-Elements entspricht dem `style`-Attribut in HTML, und das CSS2Properties-Objekt, das der Wert der `style`-Eigenschaft ist, enthält nur Informationen über die eingebetteten Stile für dieses eine Element. Es enthält keine Styles aus anderen Teilen der CSS-Kaskade.

Manchmal wollen Sie die genauen Stile eines Elements wissen, und zwar unabhängig davon, wo sie in der Kaskade festgelegt wurden. In diesem Fall suchen Sie nach dem berechneten Stil für das Element. Leider ist die Bedeutung des Namens _berechneter Stil_ nicht offensichtlich; er bezieht sich auf die Berechnung, die durchgeführt wird, bevor das Element vom Webbrowser angezeigt wird: Die Regeln aller Stylesheets werden getestet, um herauszufinden, welche auf das Element angewendet werden, und die Stile der betreffenden Regeln werden mit gegebenenfalls vorhandenen eingebetteten Styles für das Element kombiniert. Mit dieser aggregierten Stilinformation kann das Element im Browserfenster korrekt wiedergegeben werden.

Die Standard-W3C API zur Bestimmung des berechneten Stils eines Elements ist die `getComputedStyle( )`-Methode des Window-Objekts. Das erste Argument dieser Methode ist das Element, dessen berechneter Stil ermittelt werden soll. Das zweite Argument ist ein beliebiges CSS-Pseudoelement, wie z.B. `:before` oder `:after`, dessen Stil ermittelt werden soll. Sie haben wahrscheinlich kein Interesse an Pseudoelementen, aber in der Mozilla- und Firefox-Implementierung dieser Methode ist das zweite Argument vorgeschrieben und kann nicht weggelassen werden. Daher wird `getComputedStyle( )` normalerweise mit dem Wert `null` als zweites Argument aufgerufen.

Der Rückgabewert von `getComputedStyle( )` ist ein CSS2Properties-Objekt, das alle Styles repräsentiert, die auf das angegebene Element oder Pseudoelement Anwendung finden. Im Gegensatz zu dem CSS2Properties-Objekt, das Informationen über die eingebetteten Styles enthält, ist das von `getComputedStyle( )` zurückgegebene Objekt schreibgeschützt.

IE unterstützt die `getComputedStyle( )`-Methode nicht, stellt aber eine einfachere Alternative zur Verfügung: Jedes HTML-Element hat eine `currentStyle`-Eigenschaft, die seinen berechneten Style enthält. Der einzige Mangel der IE API ist, dass sie keine Möglichkeit zur Abfrage der Stile von Pseudoelementen bietet.

Als Beispiel für berechnete Styles könnten Sie plattformübergreifenden Code wie den folgenden verwenden, um zu bestimmen, in welcher Schriftart ein Element angezeigt wird:

    var p = document.getElementsByTagName("p")[0];  // Hole ersten Absatz des Dok.
    var schriftart = "";                            // Wir wollen seine Schriftart
    if (p.currentStyle)                             // Erst einfache IE API probieren
      schriftart = p.currentStyle.fontFamily;
    else if (window.getComputedStyle)               // ansonsten W3C API verwenden
      schriftart = window.getComputedStyle(p, null).fontFamily;

Berechnete Styles sind eigenwillig, und ihre Abfrage resultiert nicht immer in den Informationen, die Sie haben wollen. Nehmen Sie beispielsweise das eben gezeigte Beispiel mit der Schriftart. Die CSS-Eigenschaft `font-family` nimmt eine durch Kommata getrennte Liste gewünschter Schriftartenfamilien, um plattformübergreifende Portabilität sicherzustellen. Wenn Sie die `fontFamily`-Eigenschaft eines berechneten Styles abfragen, bekommen Sie einfach den Wert des spezifischen `font-family`-Styles, der auf das Element zutrifft. Damit können Sie einen Wert wie »arial,helvetica,sans-serif« zurückbekommen, der Ihnen nicht sagt, welche Schriftart denn nun eigentlich gerade benutzt wird. Ebenso führt der Versuch, die Position und Abmessungen eines nicht absolut positionierten Elements über die `top`\- und `left`-Eigenschaften seines berechneten Styles ermitteln, oft zu einem Rückgabewert von »auto«. Das ist ein völlig ordnungsgemäßer CSS-Wert, aber vermutlich nicht der, nach dem Sie gesucht haben.

# 16.5 CSS-Klassen per Skript steuern

Eine Alternative zur Skriptsteuerung individueller CSS-Styles über die `style`-Eigenschaft besteht darin, in HTML den Werts des `class`-Attributs zu ändern, und zwar per Skript über die `className`-Eigenschaft eines beliebigen HTML-Elements. Das dynamische Setzen einer Elementklasse kann die Styles, die auf das Element angewendet werden, dramatisch ändern – vorausgesetzt natürlich, dass die von Ihnen verwendete Klasse entsprechend in einem Stylesheet definiert ist. Diese Technik kommt in Beispiel 18-3 zur Anwendung, einem Formularüberprüfungsbeispiel, das noch einmal in diesem Buch vorkommt. Der JavaScript-Code in diesem Beispiel setzt den `className` von Formularelementen auf »valid« bzw. »invalid«, abhängig davon, ob die Benutzereingaben gültig waren oder nicht. Beispiel 18-2 enthält ein einfaches Stylesheet, das die Klassen »valid« und »invalid« so definiert, dass sie die Hintergrundfarbe des Eingabeelements in einem Formular ändern.

Bei der Verwendung des HTML-Attributs `class` und der ihm entsprechenden `className`-Eigenschaft ist es wichtig, daran zu denken, dass es mehr als eine Klasse auflisten kann. Bei der Skriptsteuerung der Eigenschaft `className` ist es daher im Allgemeinen nicht sinnvoll, diesen Wert einfach so zu setzen und abzufragen, als enthalte er nur einen Klassennamen (obwohl das aus Gründen der Einfachheit in Kapitel 18 der Fall ist). Vielmehr brauchen Sie eine Funktion zum Testen, ob ein Element ein Mitglied einer Klasse ist, und Funktionen, um Klassen zur Elementeigenschaft `className` hinzuzufügen oder aus ihr zu entfernen. Beispiel 16-7 zeigt, wie diese Funktionen definiert werden. Der Code ist einfach, stützt sich aber stark auf reguläre Ausdrücke.

Beispiel 16-7. Utility-Funktion zur Manipulation von className

    /**
     * CSSClass.js: Utilities zur Manipulation der CSS-Klasse eines HTML-Elements.
     *
     * Dieses Modul definiert ein einziges globales Symbol namens CSSClass. Dieses Objekt
     * enthält Utility-Funktionen zum Umgang mit dem class-Attribut (className-
     * Eigenschaft) von HTML-Elementen. Alle Funktionen haben zwei Argumente: das Element,
     * das zu testen/manipulieren ist, und die CSS-Klasse k, die zu testen,
     * hinzuzufügen oder zu entfernen ist. Ist Element e ein String, dann wird er als Element-
     * id betrachtet und an document.getElementById( ) übergeben.
     */
    var CSSClass = {}; // Erzeuge unser namespace-Objekt

    // Gib true zurück, wenn e ein Mitglied der Klasse k ist; ansonsten false
    CSSClass.ist = function(e, k) {
      if (typeof e == "string") e = document.getElementById(e); // Element-id

      // Vor der Regexp-Suche optimieren wir den Code für einige häufige Fälle.
      var klassen = e.className;
      if (!klassen) return false;  // Kein Mitglied einer Klasse
      if (klassen == k) return true; // Nur Mitglied dieser einen Klasse

      // Ansonsten suchen wir per regulärem Ausdruck nach c als eigenständigem Wort
      // \b in regulären Ausdrücken verlangt Übereinstimmung an einer Wortgrenze.
      return e.className.search("\\b" + k + "\\b") != −1;
    };

    // Füge Klasse k an den className von e an, wenn er sie nicht bereits enthält.
    CSSClass.anfuegen = function(e, k) {
      if (typeof e == "string") e = document.getElementById(e); // Element-id
      if (CSSClass.ist(e, k)) return; // Ist k bereits Mitglied, nichts machen
      if (e.className) k = " " + k;  // Leerzeichen zum Trennen, falls nötig
      e.className += k;        // Füge die neue Klasse ans Ende an
    };

    // Entferne Klasse k (falls vorhanden, ggf. auch mehrfach) aus dem className von e
    CSSClass.remove = function(e, k) {
      if (typeof e == "string") e = document.getElementById(e); // Element-id
      // Durchsuche den className nach k, und ersetze sie jeweils durch "".
      // \s* passt auf eine beliebige Anzahl von Whitespace-Zeichen.
      // mit "g" im regulären Ausdruck darf k beliebig oft vorkommen
      e.className = e.className.replace(new RegExp("\\b"+ k +"\\b\\s*", "g"), "");
    };

# 16.6 Stylesheets per Skript steuern

Die vorangegangenen Abschnitte beschreiben zwei Techniken der CSS-Skriptsteuerung: die Änderung des eingebetteten Styles eines Elements und die Änderung der Klasse eines Elements. Wie die folgenden Unterabschnitte zeigen, können auch die Stylesheets selbst über Skripten gesteuert werden.

## 16.6.1 Stylesheets aktivieren und deaktivieren

Die einfachste Technik für die Stylesheet-Steuerung über Skripten ist gleichzeitig die portabelste und die robusteste. Der HTML DOM Level 2-Standard definiert eine `disabled`-Eigenschaft für sowohl das `<link>`\- als auch das `<script>`-Element. Für die HTML-Tags existiert kein entsprechendes `disabled`-Attribut, aber es gibt eine Eigenschaft, die in JavaScript gesetzt und abgefragt werden kann. Wenn die `disabled`-Eigenschaft auf `true` steht, ist das Stylesheet, das sich auf das `<link>`\- oder `<style>`-Element bezieht, deaktiviert und wird vom Browser ignoriert.

Das wird in Beispiel 16-8 veranschaulicht. Es besteht aus einer HTML-Seite, die vier Stylesheets enthält. Sie zeigt vier Checkboxen an, über die der Benutzer jedes der vier Stylesheets einzeln (de-)aktivieren kann.

Beispiel 16-8. Stylesheets aktivieren und deaktivieren

    <head>
    <!-- In diesem Beispiel definieren wir mit den Tags <link> und <style> vier Stylesheets. -->
    <!-- Zwei der ge<link>ten Sheets sind austauschbar und daher standardmäßig deaktiviert. -->
    <!-- Alle haben ein id-Attribut, und wir können über ihre Namen auf sie zugreifen. -->
    <link rel="stylesheet" type="text/css" href="ss0.css" id="ss0">
    <link rel="alternate stylesheet" type="text/css" href="ss1.css"
       id="ss1" title="Riesenschrift">
    <link rel="alternate stylesheet" type="text/css" href="ss2.css"
       id="ss2" title="Viel Kontrast">
    <style id="ss3" title="Sans Serif">
    body { font-family: sans-serif; }
    </style>

    <script>
    // Diese Funktion (de)aktiviert ein über id angegebenes Stylesheet.
    // Das funktioniert für <link>- und <style>-Elemente.
    function enableSS(sheetid, aktiviert) {
      document.getElementById(sheetid).disabled = !aktiviert;
    }
    </script>
    </head>
    <body>

    <!-- Ein einfaches HTML-Formular zur (De-)Aktivierung von Stylesheets -->
    <!-- Das Formular hat die Sheets dieses Dokuments fest einprogrammiert. Es könnte -->
    <!-- aber auch dynamisch über die Stylesheet-Titel erzeugt werden. -->
    <form>
    <input type="checkbox" onclick="enableSS('ss0', this.checked)" checked>Einfach
    <br><input type="checkbox" onclick="enableSS('ss1', this.checked)">Riesenschrift
    <br><input type="checkbox" onclick="enableSS('ss2', this.checked)">Kontrast
    <br><input type="checkbox" onclick="enableSS('ss3', this.checked)" checked>
    Sans Serif
    </form>
    </body>

## 16.6.2 Stylesheet-Objekte und Stylesheet-Regeln

Das Level 2 DOM ermöglicht nicht nur das Aktivieren und Deaktivieren der `<link>`\- und `<style>`-Tags, die sich auf die Stylesheets beziehen, es definiert auch eine vollständige API zum Abfragen, Manipulieren und Navigieren in Stylesheets selbst. Als dieses Buch geschrieben wurde, war Firefox der einzige Browser, der einen erheblichen Teil dieser standardisierten API zu Navigation in Stylesheets unterstützte. IE 5 definiert eine andere API, und andere Browser haben nur eingeschränkte (oder keine) Unterstützung für das direkte Arbeiten mit Stylesheets.

Im Allgemeinen ist die direkte Manipulation von Stylesheets nicht sinnvoll. Statt beispielsweise einem Stylesheet neue Regeln hinzuzufügen, ist es normalerweise besser, das Stylesheet statisch zu belassen und die `className`-Eigenschaft Ihres Elements per Skript zu setzen. Wenn Sie dem Benutzer aber freie Bahn bei der Auswahl der auf Ihrer Seite benutzten Stile lassen wollen, müssen Sie ein Stylesheet eventuell dynamisch verändern können (vielleicht durch das Speichern der vom Benutzer gewünschten Stile in einem Cookie). Wenn Sie sich dafür entscheiden, Stylesheets direkt per Skript zu steuern, funktioniert der in diesem Abschnitt vorgestellte Code unter Firefox und IE, aber eventuell nicht in anderen Browsern.

Die auf ein Dokument angewandten Stylesheets werden im `styleSheets[]`-Array des Document-Objekts gespeichert. Hat ein Dokument nur ein Stylesheet, können Sie wie folgt auf es verweisen:

    var ss = document.styleSheets[0]

Die Elemente in diesem Array sind CSSStyleSheet-Objekte. Beachten Sie, dass diese Objekte sich von den `<link>`\- und `<style>`-Tags unterscheiden, die auf das Stylesheet verweisen oder es enthalten. CSSStyleSheet-Objekte besitzen ein Array `cssRules[]`, das die Regeln des Stylesheets enthält.

    var ersteRegel = document.styleSheets[0].cssRules[0];

IE unterstützt zwar die `cssRules`-Eigenschaft nicht, hat aber eine gleichwertige Eigenschaft namens `rules`.

Die Elemente der `cssRules[]`\- und `rules[]`-Arrays sind CSSRule-Objekte. Im W3C-Standard kann ein CSSRule-Objekt jede beliebige CSS-Regel darstellen, einschließlich der _at-rules_ , wie z.B. die Anweisungen `@import` und `@page`. In IE stellt das CSSRule-Objekt jedoch nur die derzeitigen Stilregeln des Stylesheets dar.

CSSRule-Objekte besitzen zwei Eigenschaften, die plattformübergreifend verwendet werden können. (Im W3C DOM sind diese Eigenschaften einer Regel nicht definiert, wenn diese keine Stilregel ist. Beim Navigieren durch das Stylesheet werden Sie diese Regel wahrscheinlich überspringen wollen). `selectorText` ist der CSS-Selektor für die Regel, und `style` bezieht sich auf ein CSS2Properties-Objekt, das die mit diesem Selektor assoziierten Styles beschreibt. Rufen wir uns ins Gedächtnis, dass CSS2Properties das gleiche Interface ist und zur Repräsentation der eingebetteten Styles eines HTML-Elements über die `style`-Eigenschaft verwendet wird. Sie können dieses CSS2Properties-Objekt zum Abfragen der Stilwerte oder zum Setzen neuer Styles für die Regel verwenden. Wenn Sie durch ein Stylesheet navigieren, haben Sie oft mehr Interesse am Text der Regel als an ihrer geparsten Darstellung. In diesem Fall verwenden Sie die `cssText`-Eigenschaft des CSS2Properties-Objekts, was Ihnen eine Textdarstellung der Regeln liefert.

Der folgende Code durchläuft die Regeln eines Stylesheets und zeigt, was sich mit ihnen machen lässt:

    // Hole das erste Stylesheet des Dokuments
    var ss = document.styleSheets[0];

    // Hole das rules-Array über W3C oder IE API
    var regeln = ss.cssRules?ss.cssRules:ss.rules;

    // Iteriere durch diese Regeln
    for(var i = 0; i < regeln.length; i++) {
      var regel = regeln[i];

      // Überspringe @import und andere Regeln, die keine Styles sind
      if (!regel.selectorText) continue;

      // Das ist die Textform der Regel
      var regelText = regel.selectorText + " { " + regel.style.cssText + " }";

      // Wenn die Regel einen Rand angibt, gehen wir davon aus, dass er in Pixeln ist, und   // verdoppeln ihn
      var rand = parseInt(regel.style.margin);
      if (rand) regel.style.margin = (rand*2) + "px";
    }

Zusätzlich zum Abfragen und Ändern bereits bestehender Regeln eines Stylesheetskönnen Sie Regeln zu einem Stylesheet hinzufügen und aus ihm entfernen. Das W3C CSSStyleSheet-Interface definiert die Methoden `insertRule( )` und `deleteRule( )` zum Hinzufügen und Entfernen von Regeln:

    document.styleSheets[0].insertRule("H1 { text-weight: bold; }", 0);

IE unterstützt `insertRule( )` und `deleteRule( )` nicht, definiert aber die weitgehend äquivalenten Funktionen `addRule( )` und `removeRule( )`. Der einzige Unterschied (abgesehen von den Namen) ist, dass bei `addRule( )` der Selektortext und der Stiltext als zwei getrennte Argumente übergeben werden müssen. Beispiel 16-9 definiert eine Stylesheet-Utility-Klasse, die sowohl die W3C- als auch die IE API zum Hinzufügen und Entfernen von Regeln enthält.

Beispiel 16-9. Stylesheet-Utility-Methoden

    /**
     * Stylesheet.js: Utility-Methoden zur Skriptsteuerung von Stylesheets.
     *
     * Dieses Modul definiert eine Stylesheet-Klasse, die ein einfacher Wrapper
     * um ein Element des Arrays document.styleSheets[] ist. Sie definiert nützliche
     * plattformübergreifende Methoden zur Abfrage und Änderung des Stylesheets.
     **/

    // Erzeuge ein neues Stylesheet-Objekt, das das angegebene CSSStylesheet verpackt.
    // Wenn ss eine Zahl ist, dann suche das Stylesheet im styleSheet[]-Array.
    function Stylesheet(ss) {
      if (typeof ss == "number") ss = document.styleSheets[ss];
      this.ss = ss;
    }

    // Gib das rules-Array für dieses Stylesheet zurück.
    Stylesheet.prototype.holeRegeln = function( ) {
      // Verwende die W3C-Eigenschaft, wenn sie definiert ist; ansonsten die IE-Eigenschaft
      return this.ss.cssRules?this.ss.cssRules:this.ss.rules;
    }

    // Gib eine Regel des Stylesheets zurück. Wenn s eine Zahl ist, geben wir die Regel
    // mit diesem Index zurück. Sonst nehmen wir an, dass s ein Selektor ist, und suchen nach
    // einer Regel, die mit diesem Selektor übereinstimmt.
    Stylesheet.prototype.holeRegel = function(s) {
      var regeln = this.holeRegeln( );
      if (!regeln) return null;
      if (typeof s == "number") return regeln[s];

      // Gehe davon aus, dass s ein Selektor ist.
      // Durchlaufe die Regeln rückwärts. Bei mehreren auf s passenden Regeln
      // finden wir dadurch die mit der höchsten Priorität.
      s = s.toLowerCase( );
      for(var i = regeln.length-1; i >= 0; i--) {
        if (regeln[i].selectorText.toLowerCase( ) == s) return regeln[i];
      }
      return null;
    };

    // Gib das CSS2Properties-Objekt für die angegebene Regel zurück.
    // Regeln können über eine Zahl oder einen Selektor angegeben werden.
    Stylesheet.prototype.holeStyles = function(s) {
      var regel = this.holeRegel(s);
      if (regel && regel.style) return regel.style;
      else return null;
    };

    // Gib den Stiltext der angegebenen Regel zurück.
    Stylesheet.prototype.holeStyleText = function(s) {
      var regel = this.holeRegel(s);
      if (regel && regel.style && regel.style.cssText) return regel.style.cssText;
      else return "";
    };

    // Füge eine Regel in ein Stylesheet ein.
    // Die Regel besteht aus dem angegebenen Selektor und Style-Strings.
    // Sie wird am Index n eingefügt. Wenn n fehlt, wird sie ans Ende angefügt.
    Stylesheet.prototype.regelEinfügen = function(selector, styles, n) {
      if (n == undefined) {
        var regeln = this.holeRegeln( );
        n = regeln.length;
      }
      if (this.ss.insertRule)  // Versuche es zuerst mit der W3C API
        this.ss.insertRule(selector + "{" + styles + "}", n);
      else if (this.ss.addRule) // Verwende ansonsten die IE API
        this.ss.addRule(selector, styles, n);
    };

    // Entferne die Regel von der angegebenen Position im Stylesheet.
    // Wenn s eine Zahl ist, lösche die Regel an dieser Position.
    // Wenn s ein String ist, lösche die Regel mit diesem Selektor.
    // Wenn n nicht angegeben ist, lösche die letzte Regel im Stylesheet.
    Stylesheet.prototype.loescheRegel = function(s) {
      // Wenn s nicht definiert ist, nimm den Index der letzten Regel
      if (s == undefined) {
        var regeln = this.holeRegeln( );
        s = regeln.length-1;
      }

      // Wenn s keine Zahl ist, suche nach einer passenden Regel, und hole ihren Index.
      if (typeof s != "number") {
        s = s.toLowerCase( );  // Konvertiere zu Kleinbuchstaben
        var regeln = this.holeRegeln( );
        for(var i = regeln.length-1; i >= 0; i--) {
          if (regeln[i].selectorText.toLowerCase( ) == s) {
            s = i; // Speichere den Index der zu löschenden Regel,
            break; // und beende die Suche
          }
        }

        // Haben wir keine Übereinstimmung gefunden, geben wir auf.
        if (i == −1) return;
      }

      // An dieser Stelle ist s eine Zahl.
      // Versuche es zuerst mit der W3C API und dann mit der IE API
      if (this.ss.deleteRule) this.ss.deleteRule(s);
      else if (this.ss.removeRule) this.ss.removeRule(s);
    };

# Kapitel 17. Events und Event-Handling

Wie ich in Kapitel 13 erklärt habe, verwenden interaktive JavaScript-Programme ein Event-gesteuertes Programmiermodell. Bei dieser Art der Programmierung erzeugt der Webbrowser ein _Event_ , sobald mit dem Dokument oder einem seiner Elemente etwas Interessantes passiert. Beispielsweise erzeugt der Webbrowser ein Event, wenn ein Dokument vollständig geladen wurde, wenn der Benutzer die Maus bewegt oder auf einen Button eines Formulars klickt. Wenn sich eine JavaScript-Anwendung für eine bestimmte Art von Events interessiert, die an einem bestimmten Dokumentelement eintreten, kann sie einen _Event-Handler_ – eine JavaScript-Funktion oder ein kurzes Stück Code – für diesen Event-Typ bei dem Element registrieren. Wenn dann ein entsprechendes Event eintritt, ruft der Browser diesen Handler auf. Anwendungen mit grafischen Benutzeroberflächen sind immer in dieser Art programmiert: Sie warten darauf, dass der Benutzer etwas tut, was sie interessiert (d.h., sie warten darauf, dass Events eintreten), und reagieren dann.

Nebenbei bemerkt hängen Timer und Error-Handler zur Fehlerbehandlung (die beide in Kapitel 14 beschrieben sind) mit dem Event-gesteuerten Programmiermodell zusammen. Wie die in diesem Kapitel beschriebenen Event-Handler funktionieren auch Timer und Error-Handler, indem sie eine Funktion beim Browser registrieren und ihm erlauben, diese Funktion aufzurufen, wenn das entsprechende Event eintritt. In diesen Fällen ist das interessante Ereignis, dass eine bestimmte Zeitspanne verstrichen oder ein JavaScript-Fehler aufgetreten ist. Obwohl Timer und Error-Handler in diesem Kapitel nicht behandelt werden, ist es hilfreich, sich die Verwandtschaft mit der Behandlung von Events vor Augen zu halten. Ich möchte Sie daher dazu anregen, "14.1 Timer" und "14.7 Fehlerbehandlung" im Zusammenhang mit diesem Kapitel noch einmal zu lesen.

Die meisten nicht-trivialen JavaScript-Programme verwenden Event-Handler intensiv. In den vorhergehenden Kapiteln haben wir bereits einige JavaScript-Beispiele gesehen, die einfache Event-Handler verwenden. Dieses Kapitel erläutert all die Details, die wir zu den Themen Events und Event-Handling bislang ausgespart haben. Leider sind diese Details komplexer, als sie sein müssten, da derzeit drei verschiedene und zueinander inkompatible Event-Handling-Modelle verwendet werden[59], und zwar:

 _Das ursprüngliche Event-Modell:_

Dies ist das einfache Schema zum Event-Handling, das wir bislang in diesem Buch verwendet haben (ohne es gründlich zu dokumentieren). Es wurde – in beschränktem Umfang – im Standard HTML 4 festgelegt und wird informell als Teil der API des DOM Level 0 angesehen. Auch wenn es nur einen begrenzten Funktionsumfang bietet, wird es von allen JavaScript-fähigen Webbrowsern unterstützt und ist daher portabel.

 _Das Standard-Event-Modell:_

Dieses leistungsfähige Event-Modell mit allen Features wurde im DOM Level 2 standardisiert. Es wird von allen modernen Browsern außer Internet Explorer unterstützt.

 _Das Event-Modell des Internet Explorers_

Dieses Event-Modell wurde erstmals in IE 4 verwendet und in IE 5 erweitert. Es hat einige, aber nicht alle der fortgeschrittenen Merkmale des Standard-Event-Modells. Obwohl Microsoft an der Entwicklung des Event-Modells des DOM Level 2 beteiligt war und genügend Zeit hatte, dieses Standard-Event-Modell in IE 5.5, IE 6 und IE 7 zu implementieren, ist Microsoft bei seinem Event-Modell geblieben. Daher müssen JavaScript-Programmierer, die fortgeschrittene Funktionen zum Event-Handling verwenden wollen, speziellen Code für die Versionen des Internet Explorers programmieren.

Dieses Kapitel dokumentiert die einzelnen Event-Modelle. Nach der Beschreibung der drei Event-Modelle folgen drei Abschnitte, die ausführliche Beispiele zur Handhabung von Maus-, Tastatur- und Onload-Events enthalten. Das Kapitel endet mit einer kurzen Einführung in das Erzeugen und Auslösen synthetischer Events.

# 17.1 Grundlegendes Event-Handling

In den in diesem Buch bisher vorgestellten Programmen wurden Event-Handler als Strings mit JavaScript-Anweisungen angegeben, die als Werte bestimmter HTML-Attribute wie `onclick` verwendet wurden. Auch wenn dies den Schlüssel zum ursprünglichen Event-Modell darstellt, gibt es eine Reihe weiterer Einzelheiten, die Sie wissen sollten. Diese werden in den folgenden Abschnitten erläutert.

## 17.1.1 Events und Event-Typen

Verschiedene Arten von Vorkommnissen erzeugen unterschiedliche Typen von Events. Wenn der Benutzer die Maus über einen Hypertext-Verweis bewegt, tritt ein anderer Typ von Event ein, als wenn der Benutzer mit der Maus auf den Hyperlink klickt. Auch kann dasselbe Vorkommnis je nach Kontext verschiedene Typen von Events auslösen: Wenn der Benutzer mit der Maus beispielsweise auf einen **Submit** -Button klickt, wird ein anderes Event erzeugt, als wenn er auf den **Reset** -Button eines Formulars klickt.

Im ursprünglichen Event-Modell ist ein Event eine interne Abstraktion des Webbrowsers; JavaScript-Anweisungen können ein Event nicht unmittelbar bearbeiten. Wenn wir im ursprünglichen Event-Modell von einem _Event-Typ_ sprechen, meinen wir eigentlich den Namen des Event-Handlers, der als Reaktion auf dieses Event aufgerufen wird. Bei diesem Modell werden die Anweisungen zum Event-Handling mit Hilfe der Attribute von HTML-Elementen angegeben (und den entsprechenden Eigenschaften der sie repräsentierenden JavaScript-Objekte). Wenn Ihre Anwendung daher mitbekommen soll, dass der Benutzer die Maus über einem bestimmten Hypertext-Verweis bewegt, müssen Sie das Attribut `onmouseover` des `<a>`-Tags verwenden, das diesen Verweis definiert. Wenn Ihre Anwendung mitbekommen soll, dass der Benutzer den **Submit** -Button angeklickt hat, verwenden Sie das Attribut `onclick` des `<input>`-Tags, das den Button definiert, oder das Attribut `onsubmit` des `<form>`-Elements, das den Button enthält.

Es gibt eine ganze Anzahl verschiedener Event-Handler-Attribute, die Sie im ursprünglichen Event-Modell verwenden können. Sie sind in Tabelle 17-1 aufgeführt. Dort wird auch angegeben, wann diese Event-Handler ausgelöst werden und welche HTML-Elemente die Handler-Attribute unterstützen.

Mit der Weiterentwicklung von clientseitiger JavaScript-Programmierung wurde auch das Event-Modell erweitert. Jeder neuen Browserversion wurden neue Event-Handler-Attribute hinzugefügt. Die Spezifikation von HTML 4 hat schließlich eine Standardmenge von Event-Handler-Attributen für HTML-Tags festgelegt. Die dritte Spalte in Tabelle 17-1 gibt an, welche HTML-Elemente das jeweilige Event-Handler-Attribut unterstützen. Bei den Maus-Event-Handlern gibt Spalte drei an, dass das Handler-Attribut von den »meisten Elementen« unterstützt wird. Die HTML-Elemente, die diese Event-Handler nicht unterstützen, sind üblicherweise solche Elemente, die sich im `<head>`-Teil eines Dokuments befinden oder keine eigene grafische Darstellung besitzen. Die folgenden Tags unterstützen diese annähernd universellen Attribute von Maus-Event-Handlern nicht: `<applet>`, `<bdo>`, `<br>`, `<font>`, `<frame>`, `<frameset>`, `<head>`, `<html>`, `<iframe>`, `<isindex>`, `<meta>` und `<style>`.

Tabelle 17-1. Event-Handler und sie unterstützende HTML-Elemente

Handler | Ausgelöst, wenn | Unterstützt von

---|---|---

`onabort` | das Laden des Bildes abgebrochen wurde. | `<img>`

`onblur` | das Element den Eingabefokus verliert. | `<button>`, `<input>`, `<label>`, `<select>`, `<textarea>`, `<body>`

`onchange` | die Auswahl in einem `<select>`-Element oder in einem anderen Formularelement den Fokus verliert und sein Wert sich geändert hat, seit das Element den Fokus erhalten hat. | `<input>`, `<select>`, `<textarea>`

`onclick` | die Maus gedrückt und losgelassen wurde; folgt auf ein `mouseup`-Event. Wird `false` zurückgegeben, wird die Standardaktion nicht durchgeführt (d.h. der Link verfolgt, das Formular abgeschickt, ...). | den meisten Elementen

`ondblclick` | ein Doppelklick ausgeführt wurde. | den meisten Elementen

`onerror` | ein Fehler beim Laden des Bildes auftrat. | `<img>`

`onfocus` | das Element den Eingabefokus erhält. | `<button>`, `<input>`, `<label>`, `<select>`, `<textarea>`, `<body>`

`onkeydown` | eine Taste heruntergedrückt wurde. Wird `false` zurückgegeben, wird die Eingabe nicht angezeigt. | Formularelementen und `<body>`

`onkeypress` | eine Taste heruntergedrückt wurde; folgt `keydown`. Wird `false` zurückgegeben, wird die Eingabe nicht angezeigt. | Formularelementen und `<body>`

`onkeyup` | eine Taste losgelassen wird; folgt `keypress`. | Formularelementen und `<body>`

`onload` | das Dokument vollständig geladen wurde. | `<body>`, `<frameset>`, `<img>`

`onmousedown` | die Maustaste gedrückt wird. | den meisten Elementen

`onmousemove` | die Maus bewegt wurde. | den meisten Elementen

`onmouseout` | die Maus von einem Element wegbewegt wurde. | den meisten Elementen

`onmouseover` | die Maus über ein Element bewegt wurde. | den meisten Elementen

`onmouseup` | die Maustaste losgelassen wird. | den meisten Elementen

`onreset` | das Rücksetzen eines Formulars angefordert wurde. Wird `false` zurückgegeben, wird das Formular nicht zurückgesetzt. | `<form>`

`onresize` | die Fenstergröße geändert wurde. | `<body>`, `<frameset>`

`onselect` | Text ausgewählt wurde. | `<input>`, `<textarea>`

`onsubmit` | das Abschicken eines Formulars angefordert wurde. Wird `false` zurückgegeben, wird das Formular nicht abgeschickt. | `<form>`

`onunload` | das Dokument oder Frameset aus dem Browser entfernt wird. | `<body>`, `<frameset>`

### 17.1.1.1 Geräteabhängige und -unabhängige Events

Wenn Sie die verschiedenen Event-Handler-Attribute in Tabelle 17-1 genauer betrachten, können Sie zwei grobe Kategorien von Events unterscheiden. Die eine ist die Kategorie der _rohen Events_ oder _Eingabe-Events_. Diese Events werden erzeugt, wenn der Benutzer die Maus bewegt oder mit ihr klickt oder wenn der Benutzer eine Taste auf der Tastatur drückt. Diese elementaren Events beschreiben einfach eine Aktion des Benutzers und haben keine weitergehende Bedeutung. Die zweite Kategorie von Events sind _semantische Events_. Sie haben eine komplexere Bedeutung und treten üblicherweise nur in bestimmten Kontexten auf – beispielsweise wenn der Browser ein Dokument vollständig geladen hat oder ein Formular abgeschickt werden soll. Ein semantisches Event tritt häufig als Nebeneffekt eines elementaren Events auf. Wenn beispielsweise der Benutzer mit der Maus einen **Submit** -Button anklickt, werden drei der Button-Eingabe-Handler ausgelöst: `onmousedown`, `onmouseup` und `onclick`. Als Resultat dieses Mausklicks erzeugt dann außerdem das HTML-Formular, das den Button enthält, ein `onsubmit`-Event.

Eine andere wichtige Unterscheidung teilt Events in _geräteabhängige Events_ , die speziell mit der Maus oder der Tastatur zusammenhängen, und _geräteunabhängige Events_ ein, die auf mehr als eine Art und Weise ausgelöst werden können. Diese Unterscheidung ist besonders für die Barrierefreiheit wichtig (siehe "13.7 Zugänglichkeit"), weil manche Benutzer eine Maus, aber keine Tastatur verwenden können. Andere können vielleicht eine Tastatur verwenden, aber keine Maus. Semantische Events, wie beispielsweise `onsubmit` und `onchange`, sind fast immer geräteunabhängige Events: Alle modernen Browser ermöglichen es Benutzern, durch HTML-Formulare sowohl per Maus als auch per Tastatur zu navigieren. Die Events, die die Wörter »mouse« oder »key« enthalten, sind ganz klar geräteabhängige Events. Wenn Sie sie verwenden, sollten Sie sie gegebenenfalls in Paaren verwenden, sodass Sie Handler sowohl für Mausgesten als auch eine Tastaturalternative zur Verfügung stellen. Beachten Sie, dass das `onclick`-Event als geräteunabhängiges Event gelten kann. Es ist nicht mausabhängig, da die Aktivierung von Formelementen und Hyperlinks durch die Tastatur ebenfalls dieses Event auslöst.

## 17.1.2 Event-Handler als Attribute

Wie wir bereits in den vergangenen Kapiteln an mehreren Beispielen gesehen haben, werden Event-Handler (im ursprünglichen Event-Modell) als Strings mit JavaScript-Anweisungen angegeben, die als Werte von HTML-Attributen verwendet werden. Um daher beispielsweise JavaScript-Code auszuführen, wenn der Benutzer einen Button anklickt, geben Sie diesen Code als Wert des Attributs `onclick` des `<input>`\- oder `<button>`-Tags an:

    <input type="button" value="Drück mich" onclick="alert('Danke!');">

Der Wert eines Event-Handler-Attributs ist ein beliebiger String mit JavaScript-Anweisungen. Wenn der Handler aus mehreren JavaScript-Anweisungen besteht, _müssen_ diese durch Semikola voneinander getrennt sein – beispielsweise:

    <input type="button" value="Klicke hier"
        onclick="if (window.anzClicks) anzClicks++; else anzClicks=1;
            this.value='Klick No. ' + anzClicks;">

Wenn ein Event-Handler mehrere Anweisungen benötigt, ist es meist einfacher, diese im Body einer Funktion zu definieren und anschließend das Event-Handler-Attribut für den Aufruf dieser Funktion zu verwenden. Wenn Sie beispielsweise die Eingaben eines Benutzers in einem Formular überprüfen wollen, bevor das Formular abgeschickt wird, können Sie das Attribut `onsubmit` des `<form>`-Tags verwenden.[60] Für die Überprüfung eines Formulars sind meist mehrere oder sogar recht viele Programmzeilen notwendig. Anstatt alle diese Anweisungen in einen einzelnen langen Attributwert zu packen, ist es sinnvoller, eine Funktion für die Überprüfung des Formulars zu definieren und einfach das `onclick`-Attribut für den Aufruf der Funktion zu verwenden. Wenn Sie beispielsweise eine Funktion namens `pruefeFormular( )` definiert haben, die die Überprüfung vornimmt, können Sie sie aus einem Event-Handler folgendermaßen aufrufen:

    <form action="verarbeiteFormular.cgi" onsubmit="return pruefeFormular( );">

Denken Sie daran, dass HTML nicht zwischen Groß- und Kleinschreibung unterscheidet, sodass Sie das Event-Handler-Attribut schreiben können, wie Sie möchten. Es hat sich eingebürgert, eine gemischte Schreibweise zu verwenden, bei der das initiale »on« kleingeschrieben wird: `onClick`, `onLoad`, `onMouseOut` und so fort. In diesem Buch verwende ich durchgängig kleingeschriebene Attribute, um so kompatibel zu XHTML zu sein, das zwischen Groß- und Kleinschreibung unterscheidet.

Der JavaScript-Code in einem Event-Handler-Attribut darf eine `return`-Anweisung enthalten. Dabei kann der Rückgabewert eine besondere Bedeutung für den Browser besitzen. Wir werden uns dies bald ansehen. Beachten Sie außerdem, dass der JavaScript-Code eines Event-Handlers in einem anderen Geltungsbereich (siehe Kapitel 4) ausgeführt werden kann als globaler JavaScript-Code. Auch dies wird weiter unten in diesem Abschnitt besprochen.

## 17.1.3 Event-Handler als Eigenschaften

Kapitel 15 zeigt, dass jedes HTML-Element in einem Dokument ein ihm entsprechendes DOM-Element im Dokumentenbaum besitzt und dass die Eigenschaften dieses JavaScript-Objekts den Attributen des HTML-Elements entsprechen. Das trifft für Event-Handler-Attribute ebenfalls zu. Wenn also ein `<input>`-Tag ein `onclick`-Attribut besitzt, kann auf den in diesem Attribut enthaltenen Event-Handler mit der `onclick`-Eigenschaft des Formularelement-Objekts zugegriffen werden. (JavaScript unterscheidet zwischen Groß- und Kleinschreibung; unabhängig von der im HTML-Attribut verwendeten Schreibweise muss die JavaScript-Eigenschaft vollständig kleingeschrieben werden.)

Da der Wert eines Event-Handler-Attributs ein String mit JavaScript-Anweisungen ist, erwarten Sie vielleicht, dass der Wert der entsprechenden JavaScript-Eigenschaft ebenfalls ein String ist. Dies ist jedoch nicht der Fall: Beim Zugriff von JavaScript sind Event-Handler-Eigenschaften Funktionen. Sie können dies mit einem einfachen Beispiel überprüfen:

    <input type="button" value="Klicke hier" onclick="alert(typeof this.onclick);">

Wenn Sie den Button anklicken, wird ein Dialog angezeigt, der das Wort »function«, nicht das Wort »string« enthält. (Beachten Sie, dass sich bei Event-Handlern das Schlüsselwort `this` auf das Objekt bezieht, bei dem das Event eingetreten ist. Wir werden uns das Schlüsselwort `this` gleich genauer ansehen.)

Um in JavaScript einem Dokumentelement einen Event-Handler zuzuweisen, weisen Sie der Event-Handler-Eigenschaft einfach die gewünschte Funktion zu. Betrachten Sie beispielsweise das folgende HTML-Formular:

    <form name="f1">
    <input name="b1" type="button" value="Drück mich">
    </form>

Auf den Button in diesem Formular können Sie mit `document.f1.b1` zugreifen, sodass mit einer JavaScript-Zeile wie der folgenden ein Event-Handler zugewiesen werden kann:

    document.f1.b1.onclick=function( ) { alert('Danke!'); };

Ein Event-Handler kann auch folgendermaßen zugewiesen werden:

    function bitten( ) { document.f1.b1.value += " bitte!"; }
    document.f1.b1.onmouseover = bitten;

Beachten Sie besonders die letzte Zeile: Auf den Namen der Funktion folgen dort keine Klammern! Um einen Event-Handler zu definieren, weisen Sie der Event-Handler-Eigenschaft die Funktion selbst zu, nicht das Ergebnis eines Funktionsaufrufs. Hier stolpern unerfahrene JavaScript-Programmierer oft.

Wenn man Event-Handler als JavaScript-Eigenschaften ausdrückt, ergeben sich verschiedene Vorteile. Der erste und wichtigste Vorteil ist die geringere Vermischung von HTML- und JavaScript, was die Modularität und saubereren und einfacher zu wartenden Code fördert. Zweitens können Event-Handler-Funktionen somit dynamisch werden. Im Gegensatz zu HTML-Attributen, die einen statischen Teil des Dokuments darstellen, können JavaScript-Eigenschaften jederzeit geändert werden. In komplexen interaktiven Programmen kann es hilfreich sein, die für HTML-Elemente registrierten Event-Handler dynamisch zu ändern.

Ein kleiner Nachteil bei der Definition von Event-Handlern in JavaScript besteht darin, dass der Handler von dem Element getrennt wird, zu dem er gehört. Wenn der Benutzer mit einem Dokumentelement interagiert, bevor das Dokument vollständig geladen wurde (und bevor alle seine Skripten ausgeführt wurden), kann es sein, dass der Event-Handler für das Dokumentelement noch nicht definiert ist.

Beispiel 17-1 zeigt, wie Sie eine einzige Funktion als Event-Handler für viele Dokumentelemente angeben. Das Beispiel ist eine einfache Funktion, die einen `onclick`-Event-Handler für jeden Link in einem Dokument definiert. Der Event-Handler bittet den Benutzer um Bestätigung, bevor der Browser den Hyperlink verfolgt, auf den der Benutzer soeben geklickt hat. Die Event-Handler-Funktion gibt `false` zurück, wenn der Benutzer die Nachfrage nicht bestätigt; dadurch verfolgt der Browser den Link nicht. Die Rückgabewerte von Event-Handlern werden wir uns gleich anschauen.

Beispiel 17-1. Eine Funktion, viele Event-Handler

    // Diese Funktion kann als onclick-Event-Handler für <a>- und <area>-Elemente
    // verwendet werden. Sie verwendet das Schlüsselwort this, um auf das Dokumentelement
    // zuzugreifen, und kann false zurückgeben, damit der Browser dem Link nicht folgt.
    function bestaetigeLink( ) {
      return confirm("Möchten Sie wirklich die Seite " + this.href + " besuchen?");
    }

    // Diese Funktion durchläuft alle Hyperlinks in einem Dokument und weist jeweils
    // die Funktion bestaetigeLink als Event-Handler zu. Sie darf erst aufgerufen
    // werden, wenn das Dokument analysiert wurde und alle Links definiert sind.
    // Am besten wird sie vom onload-Event-Handler eines <body>-Tags aufgerufen.
    function bestaetigeAlleLinks( ) {
      for(var i = 0; i < document.links.length; i++) {
        document.links[i].onclick = bestaetigeLink;
      }
    }

## 17.1.4 Event-Handler explizit aufrufen

Da die Werte von Event-Handler-Eigenschaften in JavaScript Funktionen sind, können Sie JavaScript verwenden, um Event-Handler-Funktionen direkt aufzurufen. Wenn Sie beispielsweise das `onsubmit`-Attribut eines `<form>`-Tags verwenden, um eine Funktion zur Überprüfung eines Formulars zu definieren, und Sie dieses Formular bereits prüfen möchten, bevor der Benutzer versucht, es abzuschicken, können Sie die `onsubmit`-Eigenschaft des Form-Objekts verwenden, um die Event-Handler-Funktion aufzurufen. Die Anweisung dazu kann folgendermaßen aussehen:

    document.meinFormular.onsubmit( );

Beachten Sie jedoch, dass der Aufruf eines Event-Handlers nicht das Eintreten des entsprechenden Events simuliert. Wenn Sie beispielsweise die `onclick( )`-Methode eines Link-Objekts aufrufen, folgt der Browser dem Link nicht, und es wird kein neues Dokument geladen. Er führt lediglich die von Ihnen als Wert dieser Eigenschaft definierte Funktion aus. Damit der Browser ein neues Dokument lädt, setzen Sie die `location`-Eigenschaft des Window-Objekts (wie in Kapitel 14 gezeigt). Das Gleiche gilt für die `onsubmit( )`-Methode eines Form-Objekts oder die `onclick( )`-Methode eines Submit-Objekts: Der Aufruf der Methode führt die Event-Handler-Funktion aus, sendet aber das Formular nicht ab. Um das Formular tatsächlich abzuschicken, rufen Sie die `submit( )`-Methode des Form-Objekts auf.

Eine Event-Handler-Funktion rufen Sie z.B. dann explizit auf, wenn Sie JavaScript verwenden möchten, um einen Event-Handler, der eventuell bereits im HTML-Text definiert wurde, um zusätzliche Funktionen zu erweitern. Nehmen wir an, Sie wollten eine bestimmte Aktion ausführen, wenn der Benutzer einen Button anklickt, aber einen bereits im HTML-Dokument definierten `onclick`-Event-Handler nicht außer Kraft setzen. (Dies ist eines der Probleme, die wir in Beispiel 17-1 haben: Durch die Definition eines Handlers für jeden Hyperlink werden bereits für die Links definierte `onclick`-Handler überschrieben.) Sie können dies mit Programmzeilen wie den folgenden erreichen:

    var b = document.meinFormular.meinButton; // Der Button, der uns interessiert
    var alterHandler = b.onclick;             // Sichere den HTML-Event-Handler
    function neuerHandler( ) { /* Hier folgt mein Code zum Event-Handling */ }
    // Nun weisen wir einen neuen Event-Handler zu, der sowohl den alten als auch den neuen
    // Handler aufruft
    b.onclick = function() { alterHandler(); neuerHandler( ); }

## 17.1.5 Rückgabewerte von Event-Handlern

In vielen Fällen verwenden Event-Handler (unabhängig davon, ob sie als HTML-Attribut oder als JavaScript-Eigenschaft angegeben wurden) ihren Rückgabewert, um die Art des Events anzuzeigen. Sie könnten beispielsweise den Event-Handler `onsubmit` eines Form-Objekts benutzen, um die Überprüfung eines Formulars durchzuführen. Wenn Sie dabei feststellen, dass der Benutzer nicht alle Felder ausgefüllt hat, kann der Handler den Wert `false` zurückgeben, um zu verhindern, dass das Formular abgeschickt wird. Um ein leeres Formular nicht abzuschicken, können Sie den folgenden Code verwenden:

    <form action="suche.cgi"
       onsubmit="if (this.elements[0].value.length == 0) return false;">
    <input type="text">
    </form>

Im Allgemeinen können Sie den Wert `false` zurückgeben, wenn der Webbrowser als Antwort auf ein Event eine voreingestellte Aktion nicht ausführen soll. Außer dem Event-Handler `onsubmit` können auch `onclick`, `onkeydown`, `onkeypress`, `onmousedown`, `onmouseup` und `onreset` den Wert `false` zurückgeben, um eine vordefinierte Aktion nicht auszuführen. Die zweite Spalte von Tabelle 17-1 zeigt, was passiert, wenn Sie `false` zurückgeben.

Von der Regel, `false` für den Abbruch einer Aktion zurückzugeben, gibt es eine Ausnahme: Wenn der Benutzer die Maus auf einen Hyperlink bewegt, besteht die vordefinierte Aktion des Browsers darin, die URL des Links in der Statuszeile anzuzeigen. Wenn Sie dies verhindern möchten, muss Ihr Event-Handler `onmouseover` den Wert `true` zurückgeben. Beispielsweise können Sie versuchen, eine von einer URL verschiedene Mitteilung mit der folgenden Zeile in der Statuszeile anzuzeigen:

    <a href="hilfe.htm" onmouseover="window.status='Hilfe!!'; return true;">Hilfe</a>

Für diese Ausnahme gibt es keinen guten Grund, außer dass Browser sich schon immer so verhalten haben. Wie in Kapitel 14 bereits erwähnt wurde, betrachten die meisten Browser die Fähigkeit, die Ziel-URL eines Links nicht anzuzeigen, allerdings als Sicherheitslücke und haben sie daher deaktiviert. Deshalb wurde die einzige Ausnahme zur Regel »false zum Abbruch einer Aktion zurückgeben« irrelevant.

Beachten Sie, dass Event-Handler keinen Wert zurückgeben müssen. Wenn Sie keinen Wert zurückgeben, wird die vordefinierte Aktion ausgeführt.

## 17.1.6 Event-Handler und das Schlüsselwort this

Egal ob Sie einen Event-Handler durch ein HTML-Attribut oder durch eine JavaScript-Eigenschaft definieren: Sie weisen einer Eigenschaft eines Dokumentelements eine Funktion zu. Anders gesagt, definieren Sie eine neue Methode des Dokumentelements. Wenn Ihr Event-Handler aufgerufen wird, wird er als eine Methode des Elements aufgerufen, an dem das Event eingetreten ist. Das Schlüsselwort `this` verweist daher auf dieses Zielelement – nützlicherweise und wenig überraschend.

Beachten Sie jedoch, was dies bedeutet! Nehmen wir an, Sie hätten ein Objekt `o` mit einer Methode `meineMethode` definiert. Sie könnten einen Event-Handler dann folgendermaßen registrieren:

    button.onclick= o.meineMethode;

Durch diese Anweisung verweist `button.onclick` auf die gleiche Funktion wie `o.meineMethode`. Diese Funktion ist jetzt eine Methode von sowohl `o` als auch `button`. Wenn der Browser diesen Event-Handler auslöst, ruft er die Funktion als Methode des `button`-Objekts auf und nicht als Methode von `o`. Das Schlüsselwort `this` bezieht sich auf das Button-Objekt und nicht auf Ihr `o`-Objekt. Machen Sie nicht den Fehler zu glauben, Sie könnten den Browser dazu bringen, einen Event-Handler als Methode eines anderen Objekts aufzurufen. Wenn Sie dies tun möchten, müssen Sie explizit eine eigene Funktion definieren:

    button.onclick = function( ) { o.meineMethode( ); }

## 17.1.7 Geltungsbereich von Event-Handlern

Wie Sie in "8.8 Funktionsgeltung und Closures" gesehen haben, besitzen Funktionen in JavaScript einen lexikalischen Geltungsbereich. Sie werden daher in dem Geltungsbereich ausgeführt, in dem sie definiert wurden, und nicht in dem Geltungsbereich, aus dem heraus sie aufgerufen werden. Wenn Sie einen Event-Handler definieren, indem Sie den Wert eines HTML-Attributs auf einen String von JavaScript-Code setzen, definieren Sie damit automatisch eine JavaScript-Funktion. Es ist wichtig zu verstehen, dass der Geltungsbereich einer auf diese Weise definierten Event-Handler-Funktion nicht das Gleiche ist wie der Geltungsbereich anderer normal definierter globaler JavaScript-Funktionen. Als HTML-Attribute definierte Event-Handler werden in einem anderen Geltungsbereich ausgeführt als andere Funktionen.[61]

Sie haben in Kapitel 4 gesehen, dass der Geltungsbereich einer Funktion durch eine Geltungsbereichskette definiert ist – eine Liste von Objekten, die nacheinander nach Variablendefinitionen durchsucht werden. Wenn in einer normalen Funktion nach einer Variablen `x` gesucht wird, schaut JavaScript zunächst nach einer lokalen Variablen oder einem Argument, indem das Aufrufobjekt der Funktion nach einer Eigenschaft dieses Namens durchsucht wird. Wenn keine solche Eigenschaft gefunden wird, fährt JavaScript mit dem nächsten Objekt in der Geltungsbereichskette fort: mit dem globalen Objekt. Die Eigenschaften des globalen Objekts werden geprüft, um herauszufinden, ob die Variable eine globale Variable ist.

Als HTML-Attribute definierte Event-Handler besitzen eine komplexere Geltungsbereichskette. Der Anfang der Geltungsbereichskette ist das Aufrufobjekt. Alle an den Event-Handler übergebenen Argumente sind hier definiert (weiter unten in diesem Kapitel werden Sie sehen, dass in einigen der fortgeschrittenen Event-Modelle Event-Handler ein Argument erhalten) – genauso wie alle lokalen Variablen, die im Body des Event-Handlers definiert sind. Das nächste Objekt in der Geltungsbereichskette des Event-Handlers ist jedoch nicht das globale Objekt, sondern das Objekt, das den Event-Handler ausgelöst hat. Nehmen wir beispielsweise an, Sie verwenden ein `<input>`-Tag, um ein Button-Objekt in einem HTML-Formular zu definieren, und benutzen das Attribut `onclick`, um einen Event-Handler zu definieren. Wenn die Anweisungen des Event-Handlers eine Variable namens `form` verwenden, wird diese Variable so aufgelöst, dass sie sich auf die `form`-Eigenschaft des Button-Objekts bezieht. Dies kann eine nützliche Abkürzung sein, wenn Sie Event-Handler als HTML-Attribute schreiben, beispielsweise:

    <form>
     <!-- In Event-Handlern bezieht sich "this" auf das Zielelements des Events -->
     <!-- Also können wir uns wie folgt auf ein Element im selben Formular beziehen -->
     <input id="b1" type="button" value="Button 1"
         onclick="alert(this.form.b2.value);">
     <!-- Das Zielelement ist auch in der Geltungsbereichskette, -->
     <!-- sodass "this" wegfallen kann -->
     <input id="b2" type="button" value="Button 2"
         onclick="alert(form.b1.value);">
     <!-- Das <form>-Tag ist in der Geltungsbereichskette, -->
     <!-- sodass "form" wegfallen kann. -->
     <input id="b3" type="button" value="Button 3"
         onclick="alert(b4.value);">
     <!-- Document ist in der Geltungsbereichskette, und wir können -->
     <!-- seine Methoden verwenden, -->
     <!-- ohne das Präfix "document" anzuhängen. Das ist allerdings schlechter -->
     <!-- Programmierstil. -->
     <input id="b4" type="button" value="Button 4"
         onclick="alert(getElementById('b3').value);">
    </form>

Wie Sie an diesem Beispielcode sehen, endet die Geltungsbereichskette eines Event-Handlers nicht bei dem Objekt, das den Handler definiert: Sie wird entlang der Hierarchie fortgeführt, in der die einzelnen Elemente enthalten sind. Sie enthält mindestens das HTML-Element `<form>`, das wiederum die Schaltfläche und das Document-Objekt mit dem Formular enthält.[62] Das letzte Objekt in der Geltungsbereichskette ist, wie immer in clientseitigem JavaScript, das Window-Objekt.

Eine andere Möglichkeit, die erweiterte Geltungsbereichskette der Event-Handler zu sehen, ist die Übersetzung des JavaScript-Texts im HTML-Event-Handler-Attribut in eine JavaScript-Funktion. Nehmen wir die folgenden Zeilen aus dem letzten Beispiel:

    <input id="b3" type="button" value="Button 3"
        onclick="alert(b4.value);">

Der entsprechende JavaScript-Code wäre der folgende:

    var b3 = document.getElementById('b3'); // Hole den Button, der uns interessiert
    b3.onclick = function( ) {
      with (document) {
        with(this.form) {
          with(this) {
            alert(b4.value);
          }
        }
      }
    }

Die sich wiederholenden `with`-Anweisungen erzeugen eine erweiterte Geltungsbereichskette. Wenn Sie sich an diese selten genutzte Anweisung nicht mehr erinnern können, sollten Sie in "6.18 with" nachschlagen.

Die Anwesenheit des Zielobjekts in der Geltungsbereichskette eines Event-Handlers ist eine nützliche Abkürzung. Dagegen kann eine erweiterte Geltungsbereichskette, die auch andere Dokumentelemente umfasst, ein Ärgernis sein. Beispielsweise definieren sowohl Window- als auch Document-Objekte Methoden namens `open( )`. Wenn Sie den Bezeichner `open` ohne weitere Angaben verwenden, greifen Sie meistens auf die Methode `window.open( )` zu. In einem als HTML-Attribut definierten Event-Handler allerdings steht das Document-Objekt in der Geltungsbereichskette vor dem Window-Objekt. Wenn Sie `open` ohne weiteren Zusatz verwenden, rufen Sie daher die Methode `document.open( )` auf. Bedenken Sie auch, was geschieht, wenn Sie in einem Form-Objekt eine Eigenschaft namens `window` definieren (oder ein Eingabefeld mit dem Attribut `name="window"`). Wenn Sie dann einen Event-Handler in diesem Formular definieren, der den Ausdruck `window.open( )` verwendet, wird der Bezeichner `window` so aufgelöst, dass er auf die Eigenschaft des Form-Objekts statt auf das globale Window-Objekt verweist. Event-Handler in diesem Formular können daher nicht auf einfache Weise auf das globale Window-Objekt zugreifen oder die Methode `window.open( )` aufrufen

Daher müssen Sie vorsichtig sein, wenn Sie Event-Handler als HTML-Attribute definieren. Das sicherste Verfahren besteht darin, die Handler sehr einfach zu halten. Im Idealfall sollten sie einfach eine globale Funktion aufrufen, die andernorts definiert wurde und gegebenenfalls einen Wert zurückgibt:

    <script>function validateForm( ) { /* Formularüberprüfungscode steht hier */ }</script>
    <form onsubmit="return validateForm( );">...</form>

Ein einfacher Event-Handler wie dieser wird nichtsdestotrotz mit Hilfe einer ungewöhnlichen Geltungsbereichskette ausgeführt. Da aber der Code kurz gehalten wird, minimieren Sie die Gefahr, über eine lange Geltungsbereichskette zu stolpern. Denken Sie wieder daran, dass bei der Ausführung von Funktionen der Geltungsbereich verwendet wird, in dem die Funktion definiert wurde, nicht der Geltungsbereich, in dem sie aufgerufen wird. Selbst wenn also unsere Beispielmethode `pruefeFormular( )` von einem ungewöhnlichen Geltungsbereich aus aufgerufen wird, wird sie in ihrem eigenen globalen Geltungsbereich ausgeführt, sodass keine Verwirrungen auftreten können.

Da es keinen Standard für die genaue Zusammensetzung der Geltungsbereichskette eines Event-Handlers gibt, ist es am sichersten anzunehmen, dass sie nur aus dem Zielelement und dem globalen Window-Objekt besteht. Verwenden Sie beispielsweise `this`, um auf das Zielelement zu verweisen; und wenn das Ziel ein `<input>`-Element ist, dürfen Sie auch `form` statt `this.form` verwenden, um auf das umgebende Form-Objekt zuzugreifen. Verlassen Sie sich aber nicht darauf, dass die Form- oder Document-Objekte in der Geltungsbereichkette sind. Verwenden Sie zum Beispiel nicht `action statt form.action` oder `getElementById statt document.getElementById`.

Denken Sie schließlich daran, dass alles, was hier zum Thema Geltungsbereich von Event-Handlern gesagt wurde, nur für Event-Handler gilt, die als HTML-Attribute definiert sind. Wenn Sie einen Event-Handler durch Zuweisung einer Funktion an eine entsprechende Event-Handler-Eigenschaft in JavaScript angeben, wird keine besondere Geltungsbereichskette gebildet. Ihre Funktion wird dann in dem Geltungsbereich ausgeführt, in dem sie definiert wurde. Dies ist fast immer der globale Geltungsbereich – außer wir haben es mit einer inneren Funktion zu tun; dann kann die Geltungsbereichskette wieder interessant werden

* * *

[59] Netscape 4 hatte auch ein eigenes und inkompatibles Event-Modell. Dieser Browser ist allerdings kaum mehr in Gebrauch, und die Dokumentation seines Event-Modells ist aus diesem Buch entfernt worden.

60] [Kapitel 18 beschreibt HTML-Formulare ausführlich und enthält ein Beispiel zur Formularüberprüfung.

[61] Es ist wichtig, dies zu verstehen. Die folgende Darstellung ist sehr interessant, aber auch sehr kompakt. Wenn Sie das Buch erstmals lesen, können Sie dieses Kapitel auch überspringen und es später studieren.

[62] Die genaue Zusammensetzung der Geltungsbereichskette ist nie standardisiert worden und kann von der jeweiligen Implementierung abhängen.

# 17.2 Fortgeschrittenes Event-Handling mit dem DOM Level 2

Die Techniken zum Event-Handling, die wir in diesem Kapitel bislang betrachtet haben, sind Teil des Level 0 des DOM: der De-facto-Standard für die API, der von jedem JavaScript-fähigen Browser unterstützt wird. DOM Level 2 definiert eine erweiterte API für das Event-Handling, die sich grundlegend von der API des Level 0 unterscheidet (und ein gutes Stück mächtiger ist). Der Standard des Level 2 übernimmt die vorhandene API nicht in das Standard-DOM; dennoch besteht keine Gefahr, dass die API des Level 0 nicht mehr unterstützt wird. Für einfache Aufgaben des Event-Handlings können Sie die einfache API weiterhin verwenden.

Das DOM Level 2 Event-Modell wird von allen modernen Browsern außer Internet Explorer unterstützt.

## 17.2.1 Event-Propagation

Im Event-Modell des Level 0 verteilt der Browser eintretende Events an die Dokumentelemente, bei denen sie eingetreten sind. Wenn das betreffende Objekt einen entsprechenden Event-Handler besitzt, wird dieser ausgeführt. Das ist alles. Die Situation in DOM Level 2 ist etwas komplexer. Wenn in diesem erweiterten Event-Modell ein Event bei einem Dokumentknoten (dem _Ziel_ des Events) auftritt, werden der bzw. die Event-Handler des Zielobjekts ausgelöst; zusätzlich aber erhalten die Vorfahren des Zielobjekts im Dokumentenbaum jeweils ein oder zwei Möglichkeiten, das Event zu behandeln. Die Verbreitung von Events (die sogenannte _Event-Propagation_ ) erfolgt in drei Phasen. Zunächst wird das Event _abgefangen_ (engl. _capturing_ ); dabei werden Events vom Document-Objekt nach unten durch den Dokumentenbaum zum Zielknoten verbreitet. Wenn einer der Vorfahren des Ziels (nicht aber das Ziel selbst) einen zu diesem Zweck registrierten abfangenden Event-Handler definiert hat, werden diese Handler in dieser Phase der Event-Verbreitung ausgeführt. (Wie normale und abfangende Event-Handler registriert werden, werden Sie gleich herausfinden.)

Die nächste Phase der Verbreitung von Events betrifft den _Zielknoten selbst:_ Alle für das Event direkt am Zielobjekt registrierten Event-Handler werden ausgeführt. Dies ähnelt dem Event-Handling, das vom Event-Modell im Level 0 durchgeführt wurde.

In der dritten Phase _steigt_ das Event wie ein Bläschen durch die Dokument-Hierarchie vom Zielelement bis zum Document-Objekt nach oben (engl. _bubbling_ ). Zwar durchlaufen alle Events die erste Phase der Event-Verbreitung, aber nicht alle Typen von Events steigen auf: Beispielsweise ist es wenig sinnvoll, ein Event, mit dem ein Formular abgeschickt wird, oberhalb des betroffenen `<form>`-Elements weiter durch das Dokument zu verbreiten. Andererseits können generische Events wie das Drücken einer Maustaste für jedes Element im Dokument von Interesse sein, sodass diese durch die Dokument-Hierarchie aufsteigen und dabei alle angemessen erscheinenden Event-Handler bei jedem der Vorfahren des Zielelements auslösen. Im Allgemeinen steigen Eingabe-Events auf, semantische Events dagegen nicht. (In Tabelle 17-3 finden Sie eine Liste dazu, welche Events aufsteigen und welche nicht.)

Während der Event-Propagation kann jeder Event-Handler die weitere Verbreitung des Events durch den Aufruf der Methode `stopPropagation( )` des Event-Objekts beenden, das das Event repräsentiert. Das Event-Objekt und seine Methode `stopPropagation( )` werden weiter unten in diesem Kapitel näher besprochen.

Einige Events veranlassen den Webbrowser, eine voreingestellte Aktion durchzuführen. Wenn beispielsweise ein Mausklick als Event bei einem `<a>`-Tag auftritt, besteht die vordefinierte Aktion des Browsers darin, dem Verweis zu folgen. Solche vordefinierten Aktionen werden erst ausgeführt, nachdem alle drei Phasen der Event-Propagation abgeschlossen sind. Während der Verbreitung der Events hat jeder der dabei aufgerufenen Handler die Gelegenheit, die Ausführung der voreingestellten Aktion zu verhindern, indem er die Methode `preventDefault( )` des Event-Objekts aufruft.

Diese Art der Event-Propagation mag zwar etwas gewunden aussehen; sie kann Ihnen aber bei der Zentralisierung Ihres Event-Handling-Codes helfen. DOM Level 1 legt alle Dokumentelemente offen und erlaubt das Eintreten von Events (wie beispielsweise `mouseover`-Events) für beliebige dieser Elemente. Daher gibt es sehr, sehr viel mehr Orte, an denen Event-Handler registriert werden können, als im alten Event-Modell des Level 0. Nehmen wir an, Sie wollten einen Event-Handler immer dann auslösen, wenn der Benutzer die Maus über ein `<p>`-Element in Ihrem Dokument bewegt. Anstatt einen `onmouseover`-Event-Handler für jedes `<p>`-Tag zu registrieren, können Sie einen einzigen Event-Handler für das Document-Objekt registrieren und diese Events während der Phase des Abfangens oder Aufsteigens der Events behandeln.

Es gibt einen weiteren wichtigen Aspekt der Event-Propagation. Im Modell des Level 0 können Sie nur einen einzelnen Event-Handler für jeden Typ von Event an einem gegebenen Objekt registrieren. Im Modell des Level 2 dagegen können Sie beliebig viele Handler-Funktionen für einen gegebenen Event-Typ an einem gegebenen Objekt registrieren. Dies gilt auch für Vorfahren des Event-Ziels, deren Handler-Funktion oder -Funktionen während der Phasen des Abfangens oder Aufsteigens der Events aufgerufen werden.

## 17.2.2 Registrierung von Event-Handlern

In der API des Level 0 registrieren Sie einen Event-Handler, indem Sie einem Attribut in Ihrem HTML-Quelltext oder einer Objekteigenschaft in Ihrem JavaScript-Code einen Wert zuweisen. Im Event-Modell des Level 2 registrieren Sie einen Event-Handler für ein gegebenes Element, indem Sie die Methode `addEventListener( )` dieses Objekts aufrufen. (Der DOM-Standard verwendet den Begriff »Listener« in seiner API; ich werde dennoch weiterhin das Synonym »Handler« in diesen Erörterungen verwenden.) Diese Methode nimmt drei Argumente entgegen. Das erste ist der Name des Event-Typs, für den der Handler registriert wird. Der Event-Typ sollte als String angegeben werden, der den kleingeschriebenen Namen des HTML-Handler-Attributs ohne die Vorsilbe »on« enthält. Wenn Sie also im Modell des Level 0 ein HTML-Attribut `onmousedown` oder eine Eigenschaft `onmousedown` verwenden würden, benutzen Sie im Event-Modell des Level 2 den String »mousedown«.

Das zweite an `addEventListener( )` übergebene Argument ist die Handler- (oder Listener-) Funktion, die aufgerufen werden soll, wenn der angegebene Event-Typ eintritt. Wenn Ihre Funktion aufgerufen wird, wird ihr als einziges Argument ein Event-Objekt übergeben. Dieses Objekt enthält genauere Informationen über das Event (beispielsweise, welche Maustaste gedrückt wurde). Außerdem definiert das Event-Objekt Methoden wie `stopPropagation( )`. Das Event-Interface und seine Sub-Interfaces werden später in diesem Kapitel ausführlicher beschrieben.

Das letzte an `addEventListener( )` übergebene Argument ist ein Boolescher Wert. Ist dieser `true`, fängt der angegebene Event-Handler Events während der ersten Phase der Event-Propagation ein. Wenn das Argument den Wert `false` besitzt, ist der Event-Handler ganz normal und wird nur ausgelöst, wenn das Event direkt an dem Objekt oder einem Nachfolger des Elements auftritt und anschließend zu dem Element aufsteigt.

Beispielsweise können Sie mit `addEventListener( )` einen Handler für Submit-Events eines `<form>`-Elements folgendermaßen registrieren:

    document.meinFormular.addEventListener("submit",
                     function(e) { pruefe(e.target); }
                     false);

Wenn Sie alle `mousedown`-Events abfangen möchten, die in einem bestimmten benannten `<div>`-Element eintreten, können Sie `addEventListener( )` wie folgt verwenden:

    var meinDiv = document.getElementById("meinDiv");
    meinDiv.addEventListener("mousedown", behandleMouseDown, true);

Beachten Sie, dass in diesen Beispielen angenommen wird, dass Sie die Funktionen namens `pruefe( )` und `behandleMouseDown( )` andernorts in Ihrem JavaScript-Code definiert haben.

Mit `addEventListener( )` registrierte Event-Handler werden in dem Geltungsbereich ausgeführt, in dem sie definiert sind. Sie werden nicht mit der in "17.1.6 Event-Handler und das Schlüsselwort this" beschriebenen erweiterten Geltungsbereichskette aufgerufen.

Da Event-Handler im Modell des Level 2 durch den Aufruf einer Methode statt durch Zuweisung eines Attributs oder einer Eigenschaft registriert werden, können Sie mehrere Event-Handler für einen gegebenen Event-Typ an einem gegebenen Objekt registrieren. Wenn Sie `addEventListener( )` mehrfach aufrufen, um mehrere Handler-Funktionen für den gleichen Event-Typ beim gleichen Objekt zu registrieren, werden alle von Ihnen registrierten Funktionen aufgerufen, wenn ein Event dieses Typs an diesem Objekt eintritt (oder zu ihm aufsteigt oder von ihm abgefangen wird). Beachten Sie, dass der DOM-Standard keine Garantie dafür gibt, dass die Handler-Funktionen eines einzelnen Objekts in einer bestimmten Reihenfolge aufgerufen werden. Daher sollten Sie sich nicht darauf verlassen, dass die Handler-Funktionen in der Reihenfolge aufgerufen werden, in der Sie sie registriert haben. Wenn Sie dieselbe Handler-Funktion mehrfach für das gleiche Element registrieren, werden alle Registrierungen nach der ersten ignoriert.

Warum sollten Sie mehrere Handler-Funktionen für das gleiche Event an demselben Objekt registrieren wollen? Dies kann bei einer Modularisierung Ihrer Software sehr nützlich sein. Nehmen Sie beispielsweise an, dass Sie ein wiederverwendbares JavaScript-Modul geschrieben haben, das `mouseover`-Events an Bildern verwendet, um Rollover-Effekte zu erzielen. Stellen Sie sich vor, Sie haben ein anderes Modul, das die gleichen `mouseover`-Events verwenden will, um zusätzliche Informationen über das Bild in einem DHTML-Popup oder -Tooltip anzuzeigen. In der API des Level 0 müssten Sie diese beiden Module zu einem zusammenfassen, so dass beide die eine `onmouseover`-Eigenschaft des Image-Objekts gemeinsam nutzen können. In der API des Level 2 dagegen kann jedes Modul den von ihm benötigten Event-Handler registrieren, ohne etwas über das andere Modul zu wissen oder diesem ins Gehege zu kommen.

Das Gegenstück zu `addEventListener( )` ist die Methode `removeEventListener( )`, die dieselben drei Argumente entgegennimmt, aber eine Event-Handler-Funktion von einem Objekt entfernt, anstatt eine Funktion hinzuzufügen. Oft ist es hilfreich, einen Event-Handler nur zeitweilig zu registrieren und ihn bald danach wieder zu entfernen. Wenn Sie beispielsweise ein `mousedown`-Event erhalten, können Sie vorübergehend abfangende Event-Handler für `mousemove`\- und `mouseup`-Events registrieren, um festzustellen, ob der Benutzer die Maus zieht. Sie können diese Handler sofort deregistrieren, wenn das `mouseup`-Event eintritt. In einem solchen Fall können Ihre Anweisungen zur Entfernung der Event-Handler folgendermaßen aussehen:

    document.removeEventListener("mousemove", behandleMouseMove, true);
    document.removeEventListener("mouseup", behandleMouseUp, true);

Die Methoden `addEventListener( )` und `removeEventListener( )` werden vom Interface EventTarget definiert. Bei Webbrowsern, die das DOM Level 2-Event-Modul unterstützen, implementieren die Element- und Dokumentknoten dieses Interface und stellen diese Event-Registrierungsmethoden zur Verfügung.63] [Teil IV dokumentiert diese Methoden unter Document und Element, hat aber keinen Eintrag für das Interface EventTarget selbst.

## 17.2.3 addEventListener( ) und das Schlüsselwort this

Wenn im ursprünglichen Event-Modell des Level 0 eine Funktion als Event-Handler für ein Dokumentelement registriert wird, wird sie zu einer Methode des Dokumentelements (wie oben in "17.1.5 Rückgabewerte von Event-Handlern" dargestellt). Wenn der Event-Handler aufgerufen wird, wird er als eine Methode des Elements aufgerufen; innerhalb der Funktion verweist das Schlüsselwort `this` auf das Element, an dem das Event eintrat.

DOM Level 2 ist sprachunabhängig geschrieben und schreibt vor, dass Event-Listener keine einfachen Funktionen, sondern Objekte sind. Die JavaScript-Bindung des DOM macht JavaScript-Funktionen zu Event-Handlern, anstatt die Verwendung eines JavaScript-Objekts zu verlangen. Leider `sagt` die Bindung nicht, wie die Handler-Funktion aufgerufen wird, und gibt den Wert des `this`-Schlüsselworts nicht an.

Trotz der fehlenden Standardisierung rufen alle bekannten Implementierungen die über `addEventListener( )` registrierten Handler so auf, als wären sie Methoden des Zielelements. Wenn der Handler aufgerufen wird, bezieht sich das `this`-Schlüsselwort also auf das Objekt, auf dem der Handler registriert wurde. Wenn Sie sich lieber nicht auf dieses nicht festgeschriebene Verhalten verlassen wollen, können Sie die `currentTarget`-Eigenschaft des Event-Objekts verwenden, das an Ihre Handler-Funktionen übergeben wird. Bei der Besprechung des Event-Objekts weiter unten in diesem Kapitel sehen Sie, dass sich die `currentTarget`-Eigenschaft auf das Objekt bezieht, auf dem der Event-Handler registriert wurde.

## 17.2.4 Registrierung von Objekten als Event-Handler

Mit der Methode `addEventListener( )` können Sie Event-Handler-Funktionen registrieren. In der objektorientierten Programmierung werden Sie es vielleicht vorziehen, Event-Handler als Methoden eines speziellen Objekts zu definieren, sodass diese als Methoden des Objekts aufgerufen werden. Für Java-Programmierer erlaubt der DOM-Standard genau dies: Er legt fest, dass Event-Handler Objekte sind, die das EventListener-Interface und eine Methode namens `handleEvent( )` implementieren. Wenn Sie in Java einen Event-Handler registrieren, übergeben Sie ein Objekt an `addEventListener( )`, keine Funktion. Der Einfachheit halber verlangt die JavaScript-Bindung der DOM API von Ihnen nicht, dass Sie ein EventListener-Interface implementieren. Stattdessen können Sie direkt Funktionsreferenzen an `addEventListener( )` übergeben.

Wenn Sie ein objektorientiertes JavaScript-Programm schreiben und lieber Objekte als Event-Handler verwenden, können Sie eine Funktion wie die folgende für die Registrierung benutzen:

    function registerObjectEventHandler(element, eventtype, listener, captures) {
      element.addEventListener(eventtype,
                               function(event) { listener.handleEvent(event); }
                               captures);
    }

Mit dieser Funktion kann jedes Objekt als Event-Listener registriert werden, sofern es eine Methode namens `handleEvent( )` definiert. Diese Methode wird als eine Methode des Listener-Objekts aufgerufen. Das Schlüsselwort `this` verweist dann auf das Listener-Objekt, nicht auf das Dokumentelement, das das Event ausgelöst hat.

Über die DOM-Spezifikation hinausgehend erlaubt Firefox (und andere Browser, die auf dem Mozilla-Code aufbauen), dass Event-Listener-Objekte, die eine `handleEvent( )`-Methode definieren, anstelle einer Funktionsreferenz direkt an `addEvent-Listener( )` übergeben werden können. Für diese Browser ist keine spezielle Registrierungsfunktion wie die gerade gezeigte notwendig.

## 17.2.5 Module und Typen von Events

Wie ich zuvor angemerkt habe, ist das Level 2 DOM modular aufgebaut, sodass eine Implementierung es in Teilen unterstützen kann, andere Teile aber nicht zu unterstützen braucht. Die Events-API ist ein solches Modul. Ob ein Browser dieses Modul unterstützt, können Sie mit der folgenden Programmzeile feststellen:

    document.implementation.hasFeature("Events", "2.0")

Das Events-Modul enthält jedoch nur die API für die grundlegende Infrastruktur zum Event-Handling. Die Unterstützung bestimmter Arten von Events ist in Untermodule ausgelagert. Jedes Untermodul unterstützt jeweils eine Gruppe miteinander verwandter Arten von Events und definiert einen an Event-Handler übergebenen Event-Typ für jede Art von Event. Beispielsweise stellt das Untermodul mit dem Namen MouseEvents eine Unterstützung für die Event-Typen `mousedown`, `mouseup`, `click` usw. bereit. Es definiert außerdem das MouseEvent-Interface. Für jeden von diesem Modul unterstützten Event-Typ wird ein Objekt, das dieses Interface implementiert, an die Handler-Funktion übergeben.

Tabelle 17-2 führt die einzelnen Event-Module, die von ihnen definierten Event-Interfaces sowie die unterstützten Event-Typen auf. Beachten Sie, dass das Level 2 DOM keine Typen von Tastatur-Events standardisiert, sodass hier keine Module für Tastatur-Events aufgeführt werden. Die derzeitigen Browser unterstützen allerdings Tasten-Events, über die Sie später in diesem Kapitel mehr erfahren. Tabelle 17-2 und der Rest des Buches übergehen die Beschreibung der MutationEvents-Module. Mutation-Events werden ausgelöst, wenn die Struktur eines Dokuments verändert wird. Für Anwendungen wie z.B. HTML-Editoren sind sie nützlich, sie werden aber im Allgemeinen von Webbrowsern nicht implementiert und von Webprogrammierern nicht verwendet.

Tabelle 17-2. Event-Module, Interfaces und Typen

Modulname | Event-Interface | Event-Typen

---|---|---

HTMLEvents | Event | abort, blur, change, error, focus, load, reset, resize, scroll, select, submit, unload

MouseEvents | MouseEvent | click, mousedown, mousemove, mouseout, mouseover, mouseup

UIEvents | UIEvent | DOMActivate, DOMFocusIn, DOMFocusOut

Wie Sie in Tabelle 17-2 sehen können, definieren die Module HTMLEvents und MouseEvents Event-Typen, die uns aus dem Event-Modul des Level 0 vertraut sind. Das Modul UIEvents definiert Event-Typen, die den von HTML-Formularelementen unterstützten Events `focus`, `blur` und `click` ähneln. Sie sind jedoch insofern verallgemeinert, als dass sie von beliebigen Dokumentelementen erzeugt werden können, die den Eingabefokus erhalten oder in anderer Weise aktiviert werden können.

Wie bereits gesagt, wird beim Eintreten eines Events seinem Handler ein Objekt übergeben, das das mit diesem Event-Typ assoziierte Event-Interface implementiert. Die Eigenschaften dieses Objekts enthalten für den Handler nützliche Informationen über das Event. Tabelle 17-3 führt die Standard-Events erneut auf. Dort sind sie nach dem Event-Typ statt nach dem Modul sortiert. Für jeden Event-Typ gibt die Tabelle die Art des an den Handler übergebenen Event-Objekts an. Außerdem wird angegeben, ob der Event-Typ die Dokument-Hierarchie im Zuge der Event-Propagation aufsteigt (die Spalte »B« steht für »bubbling«) und ob das Event eine voreingestellte Aktion besitzt, deren Aufruf mit der Methode `preventDefault( )` verhindert werden kann (die Spalte »C« steht für »cancel«). Für Events des Moduls HTMLEvents gibt die fünfte Spalte an, welche HTML-Elemente das Event erzeugen können. Für alle anderen Event-Typen gibt die fünfte Spalte an, welche Eigenschaften des Event-Objekts sinnvolle Informationen über das Event enthalten (diese Eigenschaften werden im nächsten Abschnitt beschrieben). Beachten Sie, dass unter den in dieser Spalte aufgeführten Eigenschaften nicht die Eigenschaften zu finden sind, die vom grundlegenden Interface Event definiert werden. Diese enthalten sinnvolle Werte für alle Event-Typen.

Es ist hilfreich, Tabelle 17-3 mit Tabelle 17-1 zu vergleichen, wo alle von HTML 4 definierten Event-Handler des Level 0 aufgeführt sind. Die von den beiden Modellen unterstützten Event-Typen entsprechen sich weitgehend (abgesehen vom UIEvents-Modul). Der DOM Level 2 Standard unterstützt zusätzlich die Event-Typen `abort`, `error`, `resize` und `scroll`, die von HTML 4 nicht standardisiert wurden; aber er unterstützt die im HTML 4-Standard definierten Tasten- und `dblclick`-Eventtypen nicht. (Wie Sie bald sehen werden, gibt die Eigenschaft `detail` des an einen `click`-Event-Handler übergebenen Objekts die Anzahl der aufgetretenen Klicks an.)

Tabelle 17-3. Event-Typen

Event-Typ | Interface | B | C | Unterstützt von/Detail-Eigenschaften

---|---|---|---|---

abort | Event | ja | nein | `<img>`, `<object>`

blur | Event | nein | nein | `<a>`, `<area>`, `<button>`, `<input>`, `<label>`, `<select>`, `<textarea>`

change | Event | ja | nein | `<input>`, `<select>`, <`textarea>`

click | MouseEvent | ja | ja | `screenX`, `screenY`, `clientX`, `clientY`, `altKey`, `ctrlKey`, `shiftKey`, `metaKey`, `button`, `detail`

error | Event | ja | nein | `<body>`, `<frameset>`, `<img>`, `<object>`

focus | Event | nein | nein | `<a>`, `<area>`, `<button>`, `<input>`, `<label>`, `<select>`, `<textarea>`

load | Event | nein | nein | `<body>`, `<frameset>`, `<iframe>`, `<img>`, `<object>`

mousedown | MouseEvent | ja | ja | `screenX`, `screenY`, `clientX`, `clientY`, `altKey`, `ctrlKey`, `shiftKey`, `metaKey`, `button`, `detail`

mousemove | MouseEvent | ja | nein | `screenX`, `screenY`, `clientX`, `clientY`, `altKey`, `ctrlKey`, `shiftKey`, `metaKey`

mouseout | MouseEvent | ja | ja | `screenX`, `screenY`, `clientX`, `clientY`, `altKey`, `ctrlKey`, `shiftKey`, `metaKey`, `relatedTarget`

mouseover | MouseEvent | ja | ja | `screenX`, `screenY`, `clientX`, `clientY`, `altKey`, `ctrlKey`, `shiftKey`, `metaKey`, `relatedTarget`

mouseup | MouseEvent | ja | ja | `screenX`, `screenY`, `clientX`, `clientY`, `altKey`, `ctrlKey`, `shiftKey`, `metaKey`, `button`, `detail`

reset | Event | ja | nein | `<form>`

resize | Event | ja | nein | `<body>`, `<frameset>`, `<iframe>`

scroll | Event | ja | nein | `<body>`

select | Event | ja | nein | `<input>`, `<textarea>`

submit | Event | ja | ja | `<form>`

unload | Event | nein | nein | `<body>`, `<frameset>`

DOMActivate | UIEvent | ja | ja | `detail`

DOMFocusIn | UIEvent | ja | nein | keine

DOMFocusOut | UIEvent | ja | nein | keine

### 17.2.5.1 Event-Interfaces und Event-Informationen

Wenn ein Event eintritt, stellt die API des DOM Level 2 zusätzliche Informationen über das Event (beispielsweise, wann und wo das Event eintrat) als Eigenschaften eines an den Event-Handler übergebenen Objekts bereit. Jedes Event-Modul besitzt ein mit ihm assoziiertes Event-Interface, das für den jeweiligen Event-Typ genauere Informationen angibt. Tabelle 17-2 führte drei verschiedene Event-Module und drei verschiedene Event-Interfaces auf.

Diese drei Interfaces hängen tatsächlich miteinander zusammen und bilden eine Hierarchie. An der Wurzel befindet sich als grundlegendes Event-Interface das Interface Event, das von allen Event-Objekten implementiert wird. UIEvent ist ein Unterinterface von Event: Jedes Event-Objekt, das UIEvent implementiert, implementiert auch alle Methoden und Eigenschaften von Event. Das Event-Interface Mouse ist ein Unterinterface von UIEvent. Daher implementiert beispielsweise das Event-Objekt, das für ein Mausklick-Event an einen Event-Handler übergeben wird, alle von den Interfaces MouseEvent, UIEvent und Event definierten Methoden und Eigenschaften.

Die folgenden Abschnitte beschreiben jedes einzelne Event-Interface und erläutern ihre wichtigsten Eigenschaften und Methoden. Teil IV enthält alle Einzelheiten über jedes Interface.

### 17.2.5.2 Event

Die vom Modul HTMLEvents definierten Event-Typen verwenden das Interface Event. Alle anderen Event-Typen verwenden Unterinterfaces dieses Interfaces, sodass Event von allen Event-Objekten implementiert wird und Informationen für alle Event-Typen bereitstellt. Das Event-Interface definiert die folgenden Eigenschaften (beachten Sie, dass diese Eigenschaften und die Eigenschaften aller Unterinterfaces von Event nur gelesen werden können):

`type`

Der Typ des eingetretenen Events. Der Wert dieser Eigenschaft ist der Name des Event-Typs; sein Wert ist derselbe String, der bei der Registrierung des Event-Handlers verwendet wurde (z.B. »click« oder »mouseover«).

`target`

Der Knoten, an dem das Event eingetreten ist; dies muss nicht dasselbe sein wie `currentTarget`.

`currentTarget`

Der Knoten, an dem das Event derzeit verarbeitet wird (d.h. der Knoten, dessen Event-Handler derzeit ausgeführt wird). Wenn das Event während des Abfangens oder Aufsteigens bei der Event-Propagation verarbeitet wird, unterscheidet sich der Wert dieser Eigenschaft vom Wert der Eigenschaft `target`. Wie bereits gesagt wurde, können Sie diese Eigenschaft anstelle des Schlüsselworts `this` in Ihren Event-Handler-Funktionen verwenden.

`eventPhase`

Diese Zahl gibt an, in welcher Phase der Event-Propagation wir uns derzeit befinden. Der Wert ist eine der Konstanten `Event.CAPTURING_PHASE`, `Event.AT_TARGET` oder `Event.BUBBLING_PHASE`.

`timeStamp`

Dieses Date-Objekt gibt an, wann das Event aufgetreten ist.

`bubbles`

Dieser Boolesche Wert gibt an, ob dieses Event (und Events dieses Typs) im Dokumentenbaum aufsteigen.

`cancelable`

Dieser Boolesche Wert gibt an, ob mit dem Event eine vordefinierte Aktion verknüpft ist, deren Ausführung mit der Methode `preventDefault( )` verhindert werden kann.

Zusätzlich zu diesen sieben Eigenschaften definiert das Event-Interface zwei Methoden, die ebenfalls von allen Event-Objekten implementiert werden: `stopPropagation( )` und `preventDefault( )`. Durch einen Aufruf von `stopPropagation( )` kann ein Event-Handler verhindern, dass das Event über den Knoten, an dem es derzeit verarbeitet wird, weiterverbreitet wird. Durch einen Aufruf von `preventDefault( )` kann ein Event-Handler den Browser daran hindern, eine mit dem Event assoziierte vordefinierte Aktion auszuführen. Ein Aufruf von `preventDefault( )` in der API des DOM Level 2 entspricht der Rückgabe des Werts `false` im Event-Modell des Level 0.

### 17.2.5.3 UIEvent

Das Interface UIEvent ist ein Unterinterface von Event. Es definiert den Typ von Event-Objekten, die an Events der Typen DOMFocusIn, DOMFocusOut und DOMActivate übergeben werden. Diese Event-Typen werden selten verwendet; das UIEvent-Interface ist insbesondere deshalb wichtig, weil es das Eltern-Interface von MouseEvent ist. UIEvent definiert zusätzlich zu den von Event definierten Eigenschaften zwei weitere:

`view`

Das Window-Objekt (in der DOM-Terminologie als »view« bezeichnet), in dem das Event eingetreten ist.

`detail`

Eine Zahl, die zusätzliche Informationen über das Event bereitstellen kann. Bei `click`\- sowie `mousedown`\- und `mouseup`-Events gibt dieses Feld die Anzahl der Klicks an: 1 bei einem einfachen Mausklick, 2 bei einem Doppelklick und 3 bei einem Dreifachklick. (Beachten Sie, dass jeder Klick ein Event erzeugt; wenn aber Mehrfachklicks schnell genug aufeinanderfolgen, zeigt der Wert `detail` dies an. Einem Maus-Event, dessen Eigenschaft `detail` den Wert 2 enthält, geht immer ein Maus-Event voraus, dessen Eigenschaft `detail` den Wert 1 enthält). Bei DOMActivate-Events enthält dieses Feld den Wert 1 bei normaler Aktivierung und den Wert 2 bei einer Hyperaktivierung wie einem Doppelklick oder einer **Umschalt-Eingabe** -Kombination (Shift-Enter).

### 17.2.5.4 MouseEvent

Das Interface MouseEvent erbt die Eigenschaften und Methoden von Event und UIEvent und definiert die folgenden zusätzlichen Eigenschaften:

`button`

Eine Zahl, die angibt, welche Maustaste während eines `mousedown`-, `mouseup`\- oder `click`-Events ihren Zustand geändert hat. Der Wert 0 verweist auf die linke Taste, 1 auf die mittlere und 2 auf die rechte. Diese Eigenschaft wird nur verwendet, wenn eine Maustaste ihren Zustand ändert; sie wird nicht verwendet, wenn beispielsweise eine Taste während eines `mousemove`-Events gedrückt gehalten wird. Beachten Sie außerdem, dass Netscape 6 hier abweichend vom Standard die Werte 1, 2 und 3 anstelle von 0, 1 und 2 verwendet. Dieses Problem ist in Netscape 6.1 behoben.

`altKey`, `ctrlKey`, `metaKey`, `shiftKey`

Diese vier Booleschen Felder zeigen an, ob die Tasten **Alt** , **Strg** ( **Ctrl** ), **Meta** oder **Umschalt (Shift)** bei Eintreten eines Maus-Events gedrückt waren. Im Gegensatz zur Eigenschaft `button` enthalten diese Tasten-Eigenschaften für jeden Typ von Maus-Event einen gültigen Wert.

`clientX`, `clientY`

Diese beiden Eigenschaften geben die X- und Y-Koordinaten des Mauszeigers relativ zur Anzeigefläche bzw. zum Browserfenster an. Beachten Sie, dass diese Koordinaten das Scrollen eines Dokuments nicht berücksichtigen: Wenn ein Event am oberen Rand des Fensters eintritt, ist der Wert von `clientY` immer 0, wie weit auch immer das Dokument im Fenster gescrollt wurde. Leider bietet das Level 2-DOM keine standardisierte Möglichkeit, diese Fensterkoordinaten in Dokumentkoordinaten umzuwandeln. In Browsern außer IE können Sie `window.pageXOffset` und `window.pageYOffset` hinzufügen. ("14.3.1 Window-Geometrie" enthält Einzelheiten dazu).

`screenX`, `screenY`

Diese beiden Eigenschaften geben die X- und Y-Koordinaten des Mauszeigers relativ zur oberen linken Ecke des Bildschirms an. Diese Werte sind nützlich, wenn Sie ein neues Browserfenster an der Stelle des Maus-Events oder in dessen Nähe öffnen möchten.

`relatedTarget`

Diese Eigenschaft verweist auf einen Knoten, der mit dem Zielknoten des Events zusammenhängt. Bei `mouseover`-Events ist dies der Knoten, den die Maus verlassen hat, als sie über den neuen Zielknoten bewegt wurde. Bei `mouseout`-Events ist es der Knoten, zu dem die Maus bewegt wurde, als sie den Zielknoten verlassen hat. Bei anderen Eventtypen wird sie nicht verwendet.

## 17.2.6 Mischen von Event-Modellen

Bislang habe ich das traditionelle Event-Modell des Level 0 und das neue des Standards DOM Level 2 vorgestellt. Um abwärtskompatibel zu sein, unterstützen Browser, die das Modell des Level 2 unterstützen, auch weiterhin das Event-Modell des Level 0. Das bedeutet, Sie können Event-Modelle innerhalb eines Dokuments mischen.

Sie sollten wissen, dass Webbrowser, die das Event-Modell des Level 2 unterstützen, immer ein Event-Objekt an die Event-Handler übergeben – auch wenn die Handler durch Angabe eines HTML-Attributs oder einer JavaScript-Eigenschaft mit dem Modell des Level 0 registriert wurden. Wenn ein Event-Handler mit einem HTML-Attribut definiert wurde, wird er implizit in eine Funktion umgewandelt, die ein Argument namens `event` entgegennimmt. Ein solcher Event-Handler kann daher den Bezeichner `event` verwenden, um auf das Event-Objekt zuzugreifen. (Später werden Sie sehen, dass die Verwendung des `event`-Bezeichners in einem HTML-Attribut auch mit dem IE-Event-Modell kompatibel ist).

Der Standard erkennt an, dass das Event-Modell des Level 0 auch weiterhin verwendet wird. Er legt daher fest, dass Implementierungen, die das Modell des Level 0 unterstützen, nach diesem Modell registrierte Handler behandeln, als wären sie mit der Funktion `addEventListener( )` registriert worden. Wenn Sie also der Eigenschaft `onclick` eines Dokumentelements `e` eine Funktion `f` zuweisen (oder dem entsprechenden HTML-Attribut `onclick`), ist dies äquivalent zur Registrierung dieser Funktion in der folgenden Weise:

    e.addEventListener("click", f, false);

Wenn `f( )` aufgerufen wird, wird der Funktion ein Event-Objekt als Argument übergeben, auch wenn sie mit dem Modell des Level 0 registriert wurde.

* * *

[63] Genau genommen sagt DOM, dass alle Knoten eines Dokuments (einschließlich z.B. Textknoten) das EventTarget-Interface implementieren. In der Praxis unterstützen Webbrowser allerdings die Event-Handler-Registrierung nur an Element- und Document-Knoten sowie am Window-Objekt, obwohl dieses außerhalb des DOM-Geltungsbereichs liegt.

# 17.3 Das Event-Modell des Internet Explorers

Das von den Browsern Internet Explorer 4, 5, 5.5 und 6 unterstützte Event-Modell stellt eine Zwischenstufe zwischen dem ursprünglichen Modell des Level 0 und dem standardisierten Modell des DOM Level 2 dar. Das Event-Modell des IE besitzt ein Event-Objekt, das nähere Informationen über eingetretene Events bereitstellt. Die Übergabe des Event-Objekts an Event-Handler-Funktionen scheint zwar zumindest in einigen Fällen als Kopie stattzufinden, ist aber schlecht dokumentiert.[64] Traditionell wird das Event-Objekt in IE stattdessen als eine Eigenschaft des Window-Objekts verfügbar gemacht. Das Modell des IE unterstützt Event-Propagation durch Aufsteigen, nicht aber durch Abfangen, wie das DOM-Modell dies tut (obwohl IE 5 und höhere Versionen eine spezielle Funktionalität haben, um Maus-Events abzufangen). In IE 4 werden Event-Handler in der gleichen Weise registriert wie im ursprünglichen Modell des Level 0. In IE 5 und neueren Versionen dagegen können mehrere Handler mit speziellen (aber nicht standardisierten) Registrierungsfunktionen registriert werden.

Die folgenden Abschnitte geben genauere Auskunft über dieses Event-Modell und vergleichen es mit dem ursprünglichen Event-Modell des Level 0 und dem standardisierten Event-Modell des Level 2. Sie sollten diese beiden Event-Modelle verstanden haben, bevor Sie den folgenden Abschnitt lesen.

## 17.3.1 Das Event-Objekt des IE

Wie das standardisierte Event-Modell des DOM Level 2 stellt auch das Event-Modell des IE Informationen über alle eingetretenen Events in den Eigenschaften eines Event-Objekts bereit. Die im Standardmodell definierten Event-Objekte wurden tatsächlich dem Event-Objekt des IE nachgebildet, sodass Sie eine Vielzahl von Ähnlichkeiten zwischen den Eigenschaften des Event-Objekts des IE und den Eigenschaften der Objekte Event, UIEvent und MouseEvent des DOM bemerken werden.

Die wichtigsten Eigenschaften des Event-Objekts des IE sind:

`type`

Ein String, der den Typ des eingetretenen Events angibt. Der Wert dieser Eigenschaft ist der Name des Event-Handlers ohne die Vorsilbe »on« (z.B. »click« oder »mouseover«). Diese Eigenschaft ist kompatibel zur `type`-Eigenschaft des Event-Objekts des DOM.

`srcElement`

Das Dokumentelement, an dem das Event eingetreten ist. Diese Eigenschaft ist kompatibel zur `target`-Eigenschaft des Event-Objekts des DOM.

`button`

Eine ganze Zahl, die die gedrückte Maustaste angibt. Der Wert 1 bezeichnet die linke Taste, 2 die rechte und 4 die mittlere. Wenn mehrere Tasten gedrückt wurden, werden diese Werte addiert, sodass diese Eigenschaft beispielsweise den Wert 3 besitzt, wenn die linke und die rechte Maustaste gedrückt wurden. Vergleichen Sie dies mit der `button`-Eigenschaft des MouseEvent-Objekts im DOM Level 2, wo derselbe Name für die Eigenschaft verwendet wird, die Interpretation der Eigenschaftswerte sich jedoch unterscheidet.

`clientX`, `clientY`

Diese ganzzahligen Eigenschaften geben die Mauskoordinaten (relativ zur oberen linken Ecke des umgebenden Fensters) zum Zeitpunkt des Events an. Diese Eigenschaften sind zu den gleichnamigen MouseEvent-Eigenschaften des DOM Level 2 kompatibel. Beachten Sie, dass bei Dokumenten, die größer als das Fenster sind, diese Koordinaten nicht der Position im Dokument entsprechen. Um die Fensterkoordinaten zu Dokumentkoordinaten zu konvertieren, müssen Sie den Betrag hinzufügen, um den das Dokument gescrollt wurde. In "14.3.1 Window-Geometrie" wird gezeigt, wie das gemacht wird.

`offsetX`, `offsetY`

Diese ganzzahligen Eigenschaften geben die Position des Mauszeigers relativ zum Quellenelement an. Mit ihnen können Sie beispielsweise feststellen, auf welche Pixel eines Image-Objekts geklickt wurde. Diese Eigenschaften besitzen im Event-Modell des DOM keine Entsprechung.

`altKey`, `ctrlKey`, `shiftKey`

Diese Booleschen Eigenschaften geben an, ob die Tasten **Alt** , **Strg** ( **Ctrl** ) oder **Umschalt (Shift)** gedrückt waren, als das Event eintrat. Diese Eigenschaften sind zu den gleichnamigen Eigenschaften des MouseEvent-Objekts im DOM kompatibel. Beachten Sie jedoch, dass das Event-Objekt des IE keine Eigenschaft `metaKey` besitzt.

`keyCode`

Diese ganzzahlige Eigenschaft gibt den Tastaturcode bei `keydown`\- und `keyup`-Events bzw. das Unicode-Zeichen bei `keypress`-Events an. Mit der Methode `String. fromCharCode( )` können Sie die Zeichencodes in Strings umwandeln. Tasten-Events werden weiter unten in diesem Kapitel ausführlicher beschrieben.

`fromElement`, `toElement`

`fromElement` gibt bei `mouseover`-Events das Dokumentelement an, über dem die Maus sich zuvor befand. `toElement` gibt bei `mouseout`-Events das Dokumentelement an, über dem sich die Maus anschließend befindet. Dies ist mit der Eigenschaft `relatedTarget` des MouseEvent-Objekts im DOM vergleichbar.

`cancelBubble`

Diese Boolesche Eigenschaft verhindert, dass das aktuelle Event die Hierarchie der umgebenden Objekte weiter hinaufsteigt, wenn ihr der Wert `true` zugewiesen wird. Sie ist mit der Methode `stopPropagation( )` des Event-Objekts im DOM vergleichbar.

`returnValue`

Eine Boolesche Eigenschaft, der der Wert `false` zugewiesen werden kann, um den Browser an der Ausführung der vordefinierten Aktion zu hindern, die mit dem Event verbunden ist. Dies ist eine Alternative zur traditionellen Technik, bei der der Event-Handler den Wert `false` zurückgibt. Sie ist mit der `preventDefault( )`-Methode des Event-Objekts im DOM vergleichbar.

Eine vollständige Dokumentation des Event-Objekts in IE finden Sie in Teil IV.

## 17.3.2 Das Event-Objekt des IE als globale Variable

Obwohl das Event-Modell des IE Informationen über das Event in einem Event-Objekt bereitstellt, gibt der IE Event-Objekte nur an Handler weiter, die mit der nicht-standardisierten Methode `attachEvent()` registriert sind (siehe den folgenden Abschnitt »Registrierung von Event-Handlern im IE«). Andere Event-Handler werden ohne Argumente aufgerufen. Anstatt das Event-Objekt als Argument an den Handler weiterzureichen, stellt IE es als die Eigenschaft `event` des globalen Window-Objekts zur Verfügung. Daher kann eine Funktion zum Event-Handling in IE auf dieses Event-Objekt als `window.event` oder einfach als `event` zugreifen. Wenn es auch merkwürdig erscheint, eine globale Variable anstelle eines Funktionsarguments zu verwenden, funktioniert das Schema des IE, da das Event-gesteuerte Programmiermodell implizit davon ausgeht, dass zu jedem Zeitpunkt immer nur ein Event verarbeitet werden muss. Da zwei Events niemals gleichzeitig behandelt werden, kann eine globale Variable für die Speicherung der Informationen über das aktuell zu verarbeitende Event verwendet werden.

Die Tatsache, dass das Event-Objekt als globale Variable implementiert ist, unterscheidet IE vom standardisierten Event-Modell des DOM Level 2. Mit einer einzelnen Programmzeile kann man diese Inkompatibilität jedoch beseitigen. Wenn Sie eine Event-Handler-Funktion schreiben möchten, die mit beiden Event-Modellen funktioniert, schreiben Sie die Funktion derart, dass sie ein Argument entgegennimmt, und initialisieren das Argument mit der globalen Variablen, falls kein Argument übergeben wurde. Beispielsweise:

    function portableEventHandler(e) {
      if (!e) e = window.event; // Beschaffe in IE Informationen über das Event
      // Hier folgt der Body des Event-Handlers
    }

Ein weiterer üblicher Ausdruck, dem Sie vielleicht begegnen werden, verwendet das Zeichen `||` zur Rückgabe seines ersten definierten Arguments:

    function portableEventHandler(event) {
      var e = event || window.event;
      // der Body des Event-Handlers steht hier
    }

## 17.3.3 Registrierung von Event-Handlern im IE

In IE 4 werden Event-Handler in der gleichen Weise wie im ursprünglichen Event-Modell des Level 0 registriert: indem sie als HTML-Attribute angegeben werden oder indem den Event-Handler-Eigenschaften der Dokumentelemente Funktionen zugewiesen werden.

Seit IE 5 gibt es die Methoden `attachEvent( )` und `detachEvent( )`, mit denen mehrere Handler-Funktionen für einen gegebenen Event-Typ bei einem gegebenen Objekt registriert werden können. Ein mit `attachEvent()` registrierter Event-Handler wird mit einer Kopie des globalen `window.event`-Event-Objekts aufgerufen. Sie können mit `attachEvent( )` einen Event-Handler folgendermaßen registrieren:

    function hervorheben( ) { /* Hier folgt der Code des Event-Handlers */ }
    document.getElementById("meinEl").attachEvent("onmouseover", hervorheben);

Die Methoden `attachEvent( )` und `detachEvent( )` funktionieren genau wie `addEventListener( )` und `removeEventListener( )`, mit den folgenden Ausnahmen:

  * Da das Event-Modell des IE das Abfangen von Events nicht unterstützt, erwarten `attachEvent( )` und `detachEvent( )` nur zwei Argumente: den Event-Typ und die Handler-Funktion.

  * Die an die IE-Methode übergebenen Namen der Event-Handler sollten das Präfix »on« enthalten. Verwenden Sie zum Beispiel »onclick« mit `attachEvent( )` statt »click« mit `addEventListener( )`.

  * Funktionen, die mit `attachEvent( )` registriert sind, werden als globale Funktionen aufgerufen und nicht als Methoden des Dokumentelements, an dem das Event aufgetreten ist. Wenn also ein mit `attachEvent( )` registrierter Event-Handler ausgeführt wird, verweist das `this`-Schlüsselwort nicht auf das Zielelement des Events, sondern auf das Window-Objekt.

  * Mit `attachEvent( )` kann die gleiche Event-Handler-Funktion mehrfach registriert werden. Wenn ein Event des angegebenen Typs stattfindet, wird die registrierte Funktion so oft aufgerufen, wie sie registriert wurde.

## 17.3.4 Event-Bubbling im IE

Das Event-Modell des IE kennt im Gegensatz zu dem des DOM Level 2 das Konzept des Capturing von Events nicht. Allerdings steigen Events die Hierarchie der umgebenden Elemente im Modell des IE genau wie in dem Modell des Level 2 hinauf. Wie im Modell des Level 2 steigen nur »rohe« Eingabe-Events auf (in erster Linie Maus- und Tastatur-Events), nicht aber höhere semantische Events. Der Hauptunterschied zwischen dem Bubbling von Events in den Event-Modellen des IE und des DOM Level 2 besteht in der Art, wie das Bubbling abgebrochen wird. Das Event-Objekt des IE besitzt im Gegensatz zu dem des DOM Level 2 keine Methode `stopPropagation( )`. Um zu verhindern, dass ein Event aufsteigt bzw. dass es über den aktuellen Knoten in der Hierarchie weiter aufsteigt, muss ein Event-Handler in IE der Eigenschaft `cancelBubble` des Event-Objekts den Wert `true` zuweisen:

    window.event.cancelBubble = true;

Beachten Sie, dass die Zuweisung an `cancelBubble` sich nur auf das aktuelle Event bezieht. Wenn ein neues Event erzeugt wird, wird `window.event` ein neues Event-Objekt und `cancelBubble` der voreingestellte Wert `false` zugewiesen.

## 17.3.5 Abfangen von Maus-Events

Um eine beliebige Benutzerschnittstelle zu implementieren, in der die Maus gezogen werden soll (wie z.B. bei Pulldown-Menüs oder Drag-and-drop), ist es wichtig, dass Maus-Events abgefangen werden können, damit das Ziehen der Maus richtig durchgeführt werden kann, unabhängig davon, über was der Benutzer hinwegzieht. Im Event-Modell des DOM kann das durch das Abfangen des Event-Handlers erreicht werden. In IE 5 und höher wird das mit den Methoden `setCapture( )` und `releaseCapture( )` erreicht.

Die Methoden `setCapture( )` und `releaseCapture( )` sind bei allen HTML-Elementen vorhanden. Beim Aufruf von `setCapture( )` auf einem Element werden alle anschließenden Maus-Events an dieses Element geleitet. Der Handler dieses Elements kann die Events bearbeiten, bevor sie aufsteigen. Beachten Sie, dass dies nur für Maus-Events zutrifft und auf alle Events, die mit der Maus zu tun haben: mousedown, mouseup, mousemove, mouseover, mouseout, click und dblclick.

Beim Aufruf von `setCapture( )` werden Maus-Events auf spezielle Weise weitergeleitet, bis Sie `releaseCapture( )` aufrufen oder bis das Abfangen unterbrochen wird. Das Abfangen der Maus wird z.B. unterbrochen, wenn Ihr Webbrowser den Fokus verliert, ein `alert( )`-Dialog erscheint oder ein Systemmenü angezeigt wird. Tritt eine dieser Situationen ein, erhält das Element, an dem `setCapture( )` aufgerufen wurde, ein `onlosecapture`-Event, um es darauf hinzuweisen, dass es keine weiteren abgefangenen Maus-Events mehr erhält.

Im üblichsten Szenario wird `setCapture( )` als Antwort auf ein `mousedown`-Event aufgerufen, womit sichergestellt wird, dass nachfolgende `mousemove`-Events vom gleichen Element empfangen werden. Das Element führt seinen Ziehvorgang als Antwort auf `mousemove`-Events aus und ruft `releaseCapture( )` als Antwort auf ein (abgefangenes) `mouseup`-Event auf.

Beispiel 17-4 verwendet `setCapture( )` und `releaseCapture( )`.

## 17.3.6 attachEvent( ) und das Schlüsselwort this

Wie bereits erwähnt, werden Event-Handler, die mit `attachEvent( )` registriert sind, als globale Funktionen aufgerufen und nicht als Methoden des Elements, an dem sie registriert sind. Das bedeutet, dass sich das Schlüsselwort `this` auf das globale Window-Objekt bezieht. An sich ist das kein großes Problem. Es wird allerdings dadurch verschlimmert, dass das Event-Objekt von IE keine Eigenschaft hat, die der `currentTarget`-Eigenschaft von DOM entspricht. `srcElement` gibt das Element an, das das Event erzeugte, aber wenn das Event inzwischen aufgestiegen ist, ist das Element eventuell nicht mehr das, das das Event bearbeitet.

Wenn Sie einen generischen Event-Handler schreiben wollen, der auf einem beliebigen Element registriert werden kann, und dieser Handler wissen muss, auf welchem Event er registeriert ist, können Sie `attachEvent( )` nicht zur Handler-Registrierung verwenden. Sie müssen den Handler entweder über das Level 0-Event-Modell registrieren oder eine Wrapperfunktion um den Handler definieren und diese Wrapperfunktion registrieren:

    // Hier sind ein Event-Handler und ein Element, an dem wir ihn registrieren wollen
    function generischerHandler( ) { /* Code, der this verwendet */ }
    var element = document.getElementById("meinelement");

    // Wir können diesen Handler mit der Level 0 API registrieren
    element.onmouseover = generischerHandler;

    // Oder wir können einen Wrapper verwenden
    element.attachEvent("onmouseover", function( ) {
                // Handler als Methode des Elements aufrufen
                generischerHandler.call(element, event);
              });

Das Problem mit der Level 0 API ist, dass sie keine Registrierung mehrfacher Handlerfunktionen ermöglicht. Das Problem bei der Verwendung von Wrappern ist, dass sie in IE zu Speicherlecks führen. Im nächsten Abschnitt gibt es Details dazu.

## 17.3.7 Event-Handler und Speicherlecks

Wie in "8.8.4.2 Closures und Speicherlöcher im Internet Explorer" bereits gesagt wurde, ist Internet Explorer (bis Version 6, in Version 7 scheint das Problem behoben zu sein) anfällig für eine Klasse von Speicherlecks, die auftreten können, wenn Sie eingebettete Funktionen als Event-Handler verwenden. Sehen Sie sich den folgenden Code an:

    // Einen Validierungs-Event-Handler zu einem Formular hinzufügen
    function addValidationHandler(formular) {
      formular.attachEvent("onsubmit", function( ) { return validate( ); });
    }

Wenn diese Funktion aufgerufen wird, versieht sie das angegebene Formularelement mit einem Event-Handler. Der Handler wird als eingebettete Funktion definiert. Obwohl die Funktion selbst nicht auf irgendwelche Formularelemente verweist, tut das ihr Geltungsbereich, der als Teil des Wrappers mit einbezogen wird. Im Ergebnis verweist ein Formularelement auf ein JavaScript-Funktionsobjekt, und dieses Objekt (über seine Geltungsbereichskette) verweist zurück auf das Formularobjekt. Das ist die Art zirkuläre Referenz, die Speicherlecks in IE verursacht.

Eine Lösung dieses Problem ist, eingebettete Funktionen vollkommen zu vermeiden, wenn man für IE programmiert. Eine andere Lösung besteht darin, sorgfältig alle Event-Handler als Antwort auf ein `onunload( )`-Event zu entfernen. Der im nächsten Abschnitt gezeigte Code geht die zweite Route.

## 17.3.8 Beispiel: Event-Modell-Kompatibilität für IE

Dieser Abschnitt hat eine Anzahl von Inkompatibilitäten zwischen dem IE-Event-Modell und dem standardisierten DOM Level 2-Modell aufgezeigt. Beispiel 17-2 ist ein Code-Modul, das viele dieser Inkompatibilitäten löst. Es definiert zwei Funktionen, `Handler.add( )` und `Handler.remove( )`, zum Hinzufügen und Entfernen von Event-Handlern an einem angegebenen Element. Auf Plattformen, die `addEventListener( )` unterstützen, sind diese Funktionen triviale Wrapper um diese Standardmethoden. Unter IE 5 und höher definiert Beispiel 17-2 diese Methoden aber so, dass sie die folgenden Inkompatibilitäten beseitigen:

  * Event-Handler werden als Methoden des Elements aufgerufen, an dem sie registriert sind.

  * Event-Handlern wird ein simuliertes Event-Objekt übergeben, das so weit wie möglich dem DOM-Standard-Event-Objekt gleicht.

  * Eine zweite Registrierung von Event-Handlern wird ignoriert.

  * Beim Entladen des Dokuments aus dem Browser werden alle Handler deregistriert, um Speicherlecks in IE zu verhindern.

Um Event-Handler mit dem korrekten Wert des `this`-Schlüsselworts aufzurufen und ein simuliertes Event-Objekt zu übergeben, muss Beispiel 17-2 die angegebene Handler-Funktion in einer anderen Funktion verpacken, die sie richtig aufruft. Der schwierigste Teil dieses Beispiels ist der Code, der die an `Handler.add( )` übergebene Handler-Funktion auf die Wrapper-Funktion abbildet, die tatsächlich mit `attachEvent( )` registriert wird. Diese Abbildung muss aufrechterhalten werden, damit `Handler.remove( )` die richtige Wrapper-Funktion entfernen kann und die Handler beim Entladen des Dokuments gelöscht werden können.

Beispiel 17-2. Ein Event-Kompatibilitäts-Layer für IE

    /*
     * Handler.js -- Portable Event-Handler-Registrierungsfunktionen
     *
     * Dieses Modul definiert die Event-Handler-(De-)Registrierungsfunktionen
     * Handler.add( ) und Handler.remove( ). Beide Funktionen nehmen drei Argumente entgegen:
     *  Element: das DOM-Element, Dokument oder Fenster, für das der Handler
     *   hinzugefügt oder entfernt werden soll.
     *
     *  eventType: ein String, der den Event-Typ angibt, für den der
     *   Handler aufgerufen werden soll. DOM-Standard-Typennamen verwenden, die
     *   kein "on"-Präfix enthalten. Beispiele: "click", "load", "mouseover".
     *
     *  Handler: Die aufzurufende Funktion, wenn ein Event des angegebenen Typs
     *   am angegebenen Element auftritt. Diese Funktion wird als
     *   Methode des Elements aufgerufen, an dem sie registriert wurde.
     *   Das "this"-Schlüsselwort bezieht sich auf dieses Element. Der Handler-Funktion wird
     *   ein Event-Objekt als einziges Argument übergeben. Dieses event-Objekt ist
     *   entweder ein DOM-Standard-Event oder ein simuliertes Objekt. Wenn ein
     *   simuliertes Event-Objekt übergeben wird, hat es die folgenden DOM-
     *   konformen Eigenschaften: type, target, currentTarget, relatedTarget,
     *   eventPhase, clientX, clientY, screenX, screenY, altKey, ctrlKey,
     *   shiftKey, charCode, stopPropagation( ) und preventDefault( )
     *
     * Handler.add( ) und Handler.remove( ) haben keinen Rückgabewert.
     *
     * Handler.add( ) ignoriert eine zweite Registrierung des gleichen Handlers bei
     * Übereinstimmung von Event-Typ und -Element. Handler.remove( ) macht nichts, wenn sie zum * Entfernen eines Handlers aufgerufen wird, der nicht registriert ist.
     *
     * Implementierungshinweise:
     *
     * Bei Browsern, die die Event-Registrierungsfunktionen addEventListener( ) und
     * removeEventListener( ) des DOM-Standards unterstützen, rufen Handler.add( ) und
     * Handler.remove( ) einfach diese Funktionen auf und übergeben false als
     * drittes Argument (was bedeutet, dass die Event-Handler nie als
     * abfangende Event-Handler registriert sind).
     *
     * Versionen des Internet Explorers, die attachEvent( ) unterstützen, verwenden Handler.add( )
     * und Handler.remove() die Funktionen attachEvent( ) und detachEvent( ). Um
     * die Handler-Funktion mit dem richtigen this-Schlüsselwort aufzurufen, wird ein Wrapper
     * verwendet. Da diese Art Wrapper in IE Speicherlecks verursachen,
     * registriert Handler.add( ) automatisch einen onunload-Handler, um
     * alle Event-Handler zu deregistrieren, wenn für die Seite entladen wird. Zum Verwalten
     * der registrierten Handler erzeugt Handler.add( ) eine Eigenschaft namens _allHandlers
     * am Window-Objekt und eine namens _handlers an jedem Element, auf
     * dem ein Handler registriert ist.
     */
    var Handler = {};

    // In DOM-standardkonformen Browsern sind unsere Funktionen triviale Wrapper um
    // addEventListener( ) und removeEventListener( ).
    if (document.addEventListener) {
      Handler.add = function(element, eventType, handler) {
        element.addEventListener(eventType, handler, false);
      };

      Handler.remove = function(element, eventType, handler) {
        element.removeEventListener(eventType, handler, false);
      };
    }
    // In IE 5 und neuer verwenden wir attachEvent( ) und detachEvent( ) mit einigen
    // Hacks, um sie addEventListener- und removeEventListener-kompatibel zu machen.
    else if (document.attachEvent) {
      Handler.add = function(element, eventType, handler) {
        // Erlaube keine mehrfachen Handler-Registrierungen
        // _find( ) ist eine private Utility-Funktion, die weiter unten definiert wird.
        if (Handler._find(element, eventType, handler) != −1) return;

        // Um die Handler-Funktion als Methode des Elements
        // aufzurufen, müssen wir diese eingebettete Funktion anstelle der Handler-
        // Funktion selbst definieren und registrieren.
        var wrappedHandler = function(e) {
          if (!e) e = window.event;

          // Erzeuge ein synthetisches Event-Objekt mit teilweiser Kompatibilität
          // mit DOM-Events.
          var event = {
            _event: e,  // Falls wir wirklich das Event-Objekt von IE möchten
            type: e.type,      // Event-Typ
            target: e.srcElement,  // Wo das Event stattfand
            currentTarget: element, // Wo wir es bearbeiten
            relatedTarget: e.fromElement?e.fromElement:e.toElement,
            eventPhase: (e.srcElement==element)?2:3,

            // Mauskoordinaten
            clientX: e.clientX, clientY: e.clientY,
            screenX: e.screenX, screenY: e.screenY,

            // Tasten-Status
            altKey: e.altKey, ctrlKey: e.ctrlKey,
            shiftKey: e.shiftKey, charCode: e.keyCode,

            // Event-Management-Funktionen
            stopPropagation: function( ) {this._event.cancelBubble = true;},
            preventDefault: function( ) {this._event.returnValue = false;}
          }

          // Rufe die Handler-Funktion als Methode des Elements auf und übergib
          // das synthetische Event-Objekt als einziges Argument.
          // Verwende Function.call( ), sofern sie definiert ist; ansonsten einen Hack
          if (Function.prototype.call)
            handler.call(element, event);
          else {
            // Wenn Function.call nicht vorhanden ist, dann täusche sie wie folgt vor.
            element._currentHandler = handler;
            element._currentHandler(event);
            element._currentHandler = null;
          }
        };

        // Registriere jetzt diese eingebettete Funktion als unseren Event-Handler.
        element.attachEvent("on" + eventType, wrappedHandler);

        // Jetzt müssen wir uns merken, dass die vom Benutzer angegebene
        // Handler-Funktion zu der sie aufrufenden eingebetteten Funktion gehört.
        // Das ist für die Deregistrierung des Handlers über die
        // remove( )-Methode und beim Seitenentladen notwendig.

        // Speichere alle Informationen über diesen Handler in einem Objekt.
        var h = {
          element: element,
          eventType: eventType,
          handler: handler,
          wrappedHandler: wrappedHandler
        };

        // Stelle fest, zu welchem Dokument dieser Handler gehört.
        // Wenn das Element keine document-Eigenschaft enthält, ist es
        // kein Window- oder Document-Element. Also muss es das Document-
        // Objekt selbst sein.
        var d = element.document || element;
        // Hole jetzt das Fenster, das zu diesem Dokument gehört.
        var w = d.parentWindow;

        // Wir müssen diesen Handler mit dem Fenster assoziieren,
        // damit wir es entfernen können, wenn das Fenster entladen wird.
        var id = Handler._uid( ); // Eindeutigen Eigenschaftsnamen erzeugen
        if (!w._allHandlers) w._allHandlers = {}; // Erzeuge ein Objekt, falls nötig
        w._allHandlers[id] = h; // Speichere die Handler-Informationen in diesem Objekt

        // id der Handler-Informationen ebenfalls mit diesem Element assoziieren.
        if (!element._handlers) element._handlers = [];
        element._handlers.push(id);

        // Sofern kein onunload-Handler mit dem Fenster assoziiert ist,
        // registriere jetzt einen.
        if (!w._onunloadHandlerRegistered) {
          w._onunloadHandlerRegistered = true;
          w.attachEvent("onunload", Handler._removeAllHandlers);
        }
      };

      Handler.remove = function(element, eventType, handler) {
        // Suche diesen Handler im element._handlers[]-Array.
        var i = Handler._find(element, eventType, handler);
        if (i == −1) return; // Wenn der Handler nicht registriert ist, dann mache nichts

        // Hole das Fenster dieses Elements.
        var d = element.document || element;
        var w = d.parentWindow;

        // Suche die eindeutige id dieses Handlers.
        var handlerId = element._handlers[i];
        // Verwende sie zur Suche nach den Handler-Informationen.
        var h = w._allHandlers[handlerId];
        // Mit diesen Informationen können wir den Handler vom Element lösen.
        element.detachEvent("on" + eventType, h.wrappedHandler);
        // Entferne ein Element aus dem element._handlers-Array.
        element._handlers.splice(i, 1);
        // Lösche die Handler-Informationen aus dem _allHandlers-Objekt des Fensters.
        delete w._allHandlers[handlerId];
      };

      // Eine Utility-Funktion zum Finden eines Handlers im element._handlers-Array
      // Gib einen Array-Index zurück oder −1, wenn kein passender Handler gefunden wurde
      Handler._find = function(element, eventType, handler) {
        var handlers = element._handlers;
        if (!handlers) return −1; // Wenn keine Handler registriert sind, wurde                   // nichts gefunden

        // Hole das Fenster dieses Elements
        var d = element.document || element;
        var w = d.parentWindow;

        // Durchlaufe die Handler, die mit diesem Element assoziiert sind, und suche
        // nach einem mit richtigem Typ und der richtigen Funktion.
        // Wir suchen rückwärts, da der zuletzt registrierte Handler
        // höchstwahrscheinlich als erster entfernt wird.
        for(var i = handlers.length-1; i >= 0; i--) {
          var handlerId = handlers[i];    // hole die Handler-id
          var h = w._allHandlers[handlerId]; // hole die Handler-Informationen
          // Wenn Handler-Info auf Typ und Handler-Funktion passt, haben wir ihn gefunden.
          if (h.eventType == eventType && h.handler == handler)
            return i;
        }
        return −1; // Nichts Passendes gefunden
      };

      Handler._removeAllHandlers = function( ) {
        // Diese Funktion ist als onunload-Handler registriert, mit
        // attachEvent. Das bedeutet, dass sich das this-Schlüsselwort auf das
        // Fenster bezieht, in dem das Event stattfand.
        var w = this;

        // Iteriere durch alle registrierten Handler
        for(id in w._allHandlers) {
          // Hole die Handler-Info für diese Handler-Id
          var h = w._allHandlers[id];
          // Löse den Handler mit Hilfe dieser Informationen
          h.element.detachEvent("on" + h.eventType, h.wrappedHandler);
          // Lösche die Handler-Informationen aus dem Fenster
          delete w._allHandlers[id];
        }
      }

      // Private Utility zur Erzeugung eindeutiger Handler-ids
      Handler._counter = 0;
      Handler._uid = function( ) { return "h" + Handler._counter++; };
    }

* * *

[64] Siehe hierzu David's Blog unter <http://www.davidflanagan.com/blog/2006_10.html.>

# 17.4 Maus-Events

Nachdem wir jetzt die drei Event-Modelle kennen, sehen wir uns etwas praktischen Event-Handling-Code an. Dieser Abschnitt bespricht Maus-Events ausführlicher.

## 17.4.1 Mauskoordinaten konvertieren

Tritt ein Maus-Event auf, so enthalten die Eigenschaften `clientX` und `clientY` des Event-Objekts die Position des Mauszeigers. Diese Position wird in Fensterkoordinaten angegeben: Sie beziehen sich auf die linke obere Ecke des »Sichtfensters« im Browser und berücksichtigen das Scrollen des Dokuments nicht. Sie müssen diese Werte wahrscheinlich oft in Dokumentkoordinaten konvertieren: Um beispielsweise ein Tooltip-Fenster neben dem Mauszeiger anzuzeigen, brauchen Sie die Dokumentkoordinaten zur Positionierung des Tooltips. Beispiel 17-3 erweitert den Tooltip-Code aus Beispiel 16-4. Beispiel 16-4 zeigte einfach, wie Sie ein Tooltip-Fenster an den von Ihnen festgelegten Dokumentkoordinaten anzeigen. Das jetzige Beispiel erweitert dieses Beispiel und fügt die Methode `Tooltip.schedule( )` hinzu, die einen Tooltip an Koordinaten anzeigt, die einem Maus-Event entnommen werden. Da das Maus-Event die Position der Maus in Fensterkoordinaten angibt, konvertiert die `schedule( )`-Methode sie mit Hilfe der in Beispiel 14-2 definierten Geometriemodul-Methoden in Dokumentkoordinaten.

Beispiel 17-3. Tooltips mit Maus-Events positioniert

    // Die folgenden Werte werden von schedule( ) verwendet.
    // Sie werden wie Konstanten verwendet, aber Sie können
    // diese voreingestellten Werte auch überschreiben.
    Tooltip.X_OFFSET = 25; // Pixel rechts vom Mauszeiger
    Tooltip.Y_OFFSET = 15; // Pixel unter dem Mauszeiger
    Tooltip.DELAY = 500;  // Millisekunden nach mouseover

    /**
     * Diese Methode plant die Anzeige eines Tooltips über dem angegebenen
     * Zielelement in Tooltip.DELAY Millisekunden. Das Argument e sollte
     * das Event-Objekt eines mouseover-Events sein. Diese Methode extrahiert die
     * Mauskoordinaten des Events, konvertiert sie von Fenster- in
     * Dokumentkoordinaten und addiert die oben angegebenen Offsets.
     * Sie bestimmt den Text, der im Tooltip angezeigt werden soll, durch Abfragen
     * des tooltip-Attributs im Zielelement. Diese Methode
     * registriert und deregistriert automatisch einen onmouseout-Event-Handler,
     * um den Tooltip zu verstecken oder seine kommende Anzeige abzubrechen.
     */
    Tooltip.prototype.schedule = function(target, e) {
      // Hole den Anzeigetext. Gibt es keinen, machen wir nichts.
      var text = target.getAttribute("tooltip");
      if (!text) return;

      // Das Event-Objekt enthält die Mausposition in Fensterkoordinaten.
      // Mit dem Geometrie-Modul konvertieren wir sie in Dokumentkoordinaten.
      var x = e.clientX + Geometry.getHorizontalScroll( );
      var y = e.clientY + Geometry.getVerticalScroll( );

      // Füge die Offsets hinzu, damit der Tooltip nicht direkt unter der Maus erscheint.
      x += Tooltip.X_OFFSET;
      y += Tooltip.Y_OFFSET;

      // Plane die Anzeige des Tooltips.
      var self = this; // Wird für die eingebetteten Funktionen unten gebraucht
      var timer = window.setTimeout(function( ) { self.show(text, x, y); },
                     Tooltip.DELAY);

      // Einen onmouseout-Handler zum Verstecken eines Tooltips oder Abbrechen
      // der anstehenden Anzeige eines Tooltips registrieren.
      if (target.addEventListener) target.addEventListener("mouseout", mouseout,
                                 false);
      else if (target.attachEvent) target.attachEvent("onmouseout", mouseout);
      else target.onmouseout = mouseout;

      // Hier kommt die Implementierung des Event-Listeners
      function mouseout( ) {
        self.hide( );        // Verstecke den Tooltip, wenn er zu sehen ist,
        window.clearTimeout(timer); // Brich evtl. anstehende Anzeige ab,
        // und entferne uns, damit wir nur einmal aufgerufen werden
        if (target.removeEventListener)
          target.removeEventListener("mouseout", mouseout, false);
        else if (target.detachEvent) target.detachEvent("onmouseout",mouseout);
        else target.onmouseout = null;
      }
    }

    // Definiere ein einziges globales Tooltip-Objekt zur allgemeinen Verwendung
    Tooltip.tooltip = new Tooltip( );

    /*
     * Diese statische Version der schedule( )-Methode verwendet den globalen Tooltip,
     * und zwar so:
     *
     *  <a href="www.davidflanagan.com" tooltip="Gutes Java/JavaScript-Blog"
     *   onmouseover="Tooltip.schedule(this, event)">David Flanagans Blog</a>
     */
    Tooltip.schedule = function(target, e) { Tooltip.tooltip.schedule(target, e); }

## 17.4.2 Beispiel: Ziehen von Dokumentelementen

Jetzt kennen wir sowohl die Event-Propagation als auch die Registrierung von Event-Handlern und die verschiedenen Event-Objekt-Interfaces für DOM Level 2 und für die Event-Modelle von IE. Also ist es jetzt Zeit, das alles in einem praktischen Beispiel zusammenzufügen. Beispiel 17-4 zeigt eine JavaScript-Funktion `drag( )`. Wenn sie von einem `mousedown`-Event-Handler aufgerufen wird, kann ein absolut positioniertes Dokumentelement vom Benutzer gezogen werden. `drag( )` funktioniert sowohl mit DOM als auch den Event-Modellen von IE.

`drag( )` nimmt zwei Argumente entgegen. Das erste ist das zu ziehende Element. Dies kann das Element sein, an dem das `mousedown`-Event eingetreten ist, oder ein umgebendes Element (z.B. könnten Sie dem Benutzer erlauben, die Titelleiste eines Fensters zu ziehen, um das gesamte Fenster zu bewegen). In jedem Fall muss das Argument aber auf ein mit dem CSS-Attribut `position` absolut positioniertes Dokumentelement verweisen. Das zweite Argument ist das mit dem auslösenden `mousedown`-Event assoziierte Event-Objekt.

`drag( )` speichert die Position des `mousedown`-Events und registriert Event-Handler für die folgenden `mousemove`\- und `mouseup`-Events. Der Handler für das `mousemove`-Event ist für das Verschieben des Dokumentelements zuständig, der Handler für das `mouseup`-Event für die Deregistrierung seiner selbst und des `mousemove`-Handlers. Beachten Sie, dass die Handler für die `mousemove`\- und `mouseup`-Events als abfangende Event-Handler registriert sind, da der Benutzer die Maus schneller bewegen könnte, als das Dokumentelement folgen kann, und so einige dieser Events außerhalb des ursprünglichen Zielelements eintreten können. Ohne Abfangen werden die Events eventuell nicht dem richtigen Handler zugeteilt. Beachten Sie außerdem, dass die Funktionen `moveHandler( )` und `upHandler( )`, die zur Behandlung dieser Events registriert werden, als innere Funktionen von `drag( )` definiert werden. Da sie in diesem geschachtelten Geltungsbereich definiert sind, können sie die Argumente und lokalen Variablen von `drag( )` verwenden, wodurch sich die Implementierung dieser Funktionen stark vereinfacht.

Beispiel 17-4. Dokumentelemente ziehen

    /**
     * Drag.js: absolut positionierte HTML-Elemente ziehen.
     *
     * Dieses Modul definiert eine drag( )-Funktion, die von
     * einem onmousedown-Event-Handler aufgerufen wird. Anschließende mousemove-Events
     * verschieben das angegebene Element. Ein mouseup-Event beendet das Ziehen.
     * Wird das Element aus dem Bildschirm gezogen, scrollt das Fenster nicht mit.
     * Diese Implementierung funktioniert sowohl mit dem Event-Modell des DOM Level 2 als auch
     * mit dem Event-Modell von IE.
     *
     * Argumente:
     *
     *  elementToDrag: das Element, das das mousedown-Event empfangen hat, oder
     *   ein umgebendes Element. Es muss absolut positioniert sein. Seine
     *   Werte style.left und style.top ändern sich mit der benutzergesteuerten
     *   Zugbewegung.
     *
     *  event: das Event-Objekt für das mousedown-Event.
     **/
    function drag(elementToDrag, event) {
      // Die Mausposition (in Fensterkoordinaten),
      // an der das Ziehen beginnt
      var startX = event.clientX, startY = event.clientY;

      // Die Originalposition (in Dokumentkoordinaten) des
      // zu ziehenden Elements. Da elementToDrag
      // absolut positioniert ist, gehen wir davon aus, dass sein offsetParent der
      // Document-Body ist.
      var origX = elementToDrag.offsetLeft, origY = elementToDrag.offsetTop;

      // Obwohl die Koordinaten in unterschiedlichen Koordinatensystemen
      // berechnet werden, können wir den Unterschied zwischen ihnen ermitteln
      // und ihn in der moveHandler( )-Funktion verwenden. Das funktioniert, weil
      // sich die Bildlaufleistenposition während des Ziehens nie ändert.
      var deltaX = startX – origX, deltaY = startY – origY;

      // Registriere die Event-Handler, die auf die mousemove-Events und das
      // mouseup-Event reagieren, das diesem mousedown-Event folgt.
      if (document.addEventListener) { // Event-Modell des DOM Level 2
        // Registriere abfangende Event-Handler
        document.addEventListener("mousemove", moveHandler, true);
        document.addEventListener("mouseup", upHandler, true);
      }
      else if (document.attachEvent) { // Event-Modell des IE 5+
        // Im Event-Modell von IE fangen wir Events durch den Aufruf von
        // setCapture( ) am Element ab.
        elementToDrag.setCapture( );
        elementToDrag.attachEvent("onmousemove", moveHandler);
        elementToDrag.attachEvent("onmouseup", upHandler);
        // Behandle Verlust des Maus-Abfangens als mouseup-Event.
        elementToDrag.attachEvent("onlosecapture", upHandler);
      }
      else { // Event-Modell des IE 4
        // In IE 4 funktionieren attachEvent( ) und setCapture( ) nicht, also setzen wir
        // Event-Handler direkt am Document-Objekt und hoffen, dass die
        // benötigten Maus-Events aufsteigen werden.
        var oldmovehandler = document.onmousemove; // von upHandler( ) verwendet
        var olduphandler = document.onmouseup;
        document.onmousemove = moveHandler;
        document.onmouseup = upHandler;
      }

      // Wir haben dieses Ereignis behandelt. Gib es an niemanden weiter.
      if (event.stopPropagation) event.stopPropagation( ); // DOM Level 2
      else event.cancelBubble = true;           // IE

      // Unterbinde vordefinierte Aktionen.
      if (event.preventDefault) event.preventDefault( );  // DOM Level 2
      else event.returnValue = false;           // IE

      /**
       * Dieser Handler fängt die mousemove-Events ab, wenn ein Element
       * gezogen wird. Er ist für das Verschieben des Elements zuständig.
       **/
      function moveHandler(e) {
        if (!e) e = window.event; // Event-Modell des IE

        // Verschiebe das Element an die aktuelle Mausposition, wobei ein ggf.
        // notwendiger Offset des initialen Mausklicks berücksichtigt wird.
        elementToDrag.style.left = (e.clientX – deltaX) + "px";
        elementToDrag.style.top = (e.clientY – deltaY) + "px";

        // Wir zeigen dieses Ereignis niemandem sonst.
        if (e.stopPropagation) e.stopPropagation( ); // DOM Level 2
        else e.cancelBubble = true;         // IE
      }

      /**
       * Dieser Handler fängt das mouseup-Event ab, das am Ende des Ziehens
       * eintritt.
       **/
      function upHandler(e) {
        if (!e) e = window.event; // Event-Modell des IE

        // Deregistriere die abfangenden Event-Handler
        if (document.removeEventListener) { // Event-Modell des DOM
          document.removeEventListener("mouseup", upHandler, true);
          document.removeEventListener("mousemove", moveHandler, true);
        }
        else if (document.detachEvent) { // Event-Modell des IE 5+
          elementToDrag.detachEvent("onlosecapture", upHandler);
          elementToDrag.detachEvent("onmouseup", upHandler);
          elementToDrag.detachEvent("onmousemove", moveHandler);
          elementToDrag.releaseCapture( );
        }
        else { // Event-Modell des IE 4
          // Ursprüngliche Handler, sofern vorhanden, wiederherstellen
          document.onmouseup = olduphandler;
          document.onmousemove = oldmovehandler;
        }

        // Und verteile das Event nicht weiter.
        if (e.stopPropagation) e.stopPropagation( ); // DOM Level 2
        else e.cancelBubble = true;         // IE
      }
    }

Der folgende Code zeigt, wie Sie `drag( )` in einer HTML-Datei verwenden können (es handelt sich um eine vereinfachte Version von Beispiel 16-3, bei der das Ziehen neu hinzugekommen ist):

    <script src="Drag.js"></script> <!-- Binde das Skript Drag.js ein -->
    <!-- Definiere das zu ziehende Element -->
    <div style="position:absolute; left:100px; top:100px; width:250px;">
          background-color: white; border: solid black;">
    <!-- Definiere den "Griff", an dem es gezogen werden kann. Beachten Sie das onmousedown-Attribut. -->
    <div style="background-color: gray; border-bottom: dotted black;
          padding: 3px; font-family: sans-serif; font-weight: bold;"
       onmousedown="drag(this.parentNode, event);">
    Zieh mich <!-- Der Inhalt der "Titelleiste" -->
    </div>
    <!-- Der Inhalt des ziehbaren Elements -->
    <p>Dies ist ein Test. Test, eins, zwei, eins.<p>Dies ist ein Test.<p>Test.
    </div>

Der entscheidende Punkt ist hier das Attribut `onmousedown` des inneren `<div>`-Elements. Obwohl `drag( )` intern die DOM- und IE-Event-Modelle verwendet, wird das Attribut hier aus Bequemlichkeitsgründen unter Verwendung des Level 0-Modells registriert.

Hier ist ein weiteres einfaches Beispiel für die Verwendung von `drag( )`: Es definiert ein Bild, das der Benutzer ziehen kann, wenn er die **Umschalt** -Taste gedrückt hält:

    <script src="Drag.js"></script>
    <img src="draggable.gif" width="20" height="20"
       style="position:absolute; left:0px; top:0px;"
       onmousedown="if (event.shiftKey) drag(this, event);">

# 17.5 Tasten-Events

Wie Sie bereits wissen, sind Events und Event-Handling der Gegenstand vieler Browserinkompatibilitäten. Und Tasten-Events sind noch inkompatibler als die meisten anderen: Sie sind im DOM Level 2 Event-Modul nicht standardisiert, und IE und Mozilla-basierte Browser gehen mit ihnen etwas unterschiedlich um. Leider reflektiert das einfach den Stand der Technik im Umgang mit Tastatureingaben. Die Betriebs- und Fenstersystem-APIs, auf denen die Browser aufgebaut werden, sind üblicherweise komplex und verwirrend. Die Bearbeitung von Texteingaben ist auf vielen Ebenen kompliziert, von Hardware-Tastaturlayouts bis zur Eingabemethodenbearbeitung für Sprachen mit komplexen Schriftzeichen.

Trotz der Schwierigkeiten lassen sich nützliche Skripten zum Umgang mit Tasten-Events schreiben, die zumindest in Firefox und IE funktionieren. Dieser Abschnitt zeigt ein paar einfache Skripten und führt dann eine etwas allgemeinere Keymap-Klasse zum Abbilden von Tastatur-Events auf JavaScript-Handlerfunktionen ein.

## 17.5.1 Tasten-Event-Typen

Es gibt drei Tasten-Event-Typen: keydown, keypress und keyup; sie entsprechen den `onkeydown`-, `onkeypress`\- und `onkeyup`-Event-Handlern. Ein typischer Tastendruck erzeugt alle drei Events: `keydown`, `keypress` und anschließend `keyup`, wenn die Taste losgelassen wird. Wenn eine Taste gedrückt gehalten wird und automatisch das Signal wiederholt, kann es mehrere `keypress`-Events zwischen `keydown` und `keyup` geben, aber das ist betriebssystem- und browserabhängig und man kann sich nicht darauf verlassen.

Von den drei Event-Typen sind die `keypress`-Events am benutzerfreundlichsten: das Event-Objekt, das mit ihnen assoziiert ist, enthält den Code des tatsächlich erzeugten Zeichens. Die `keydown`\- und `keyup`-Events stehen auf einer niederen Ebene; ihre Tasten-Events beinhalten einen »virtuellen Tastencode«, der mit den von der Tastatur erzeugten Hardware-Codes zu tun hat. Für alphanumerische Zeichen im ASCII-Zeichensatz passen diese virtuellen Tastencodes zu den ASCII-Codes, werden aber nicht vollständig bearbeitet. Wenn Sie die **Umschalt-Taste** gedrückt halten und die Taste **2** drücken, sagt Ihnen das `keydown`-Event, dass der Tastendruck »shift-2« aufgetreten ist. Das `keypress`-Event interpretiert das für Sie und lässt Sie wissen, dass dieser Tastendruck das druckbare Zeichen »@« erzeugt hat. (Diese Zuordnung kann bei anderen Tastaturlayouts anders aussehen).

Nicht-druckbare Funktionstasten, wie beispielsweise **Löschen** , **Enter** , **Escape** , die Pfeiltasten und **F1** bis **F12** , erzeugen `keydown`\- und `keyup`-Events. In einigen Browsern erzeugen sie auch `keypress`-Events. In IE treten `keypress`-Events aber nur dann auf, wenn der Tastendruck einen ASCII-Code hat – d.h., wenn es sich um ein druckbares Zeichen oder ein Steuerzeichen handelt. Nicht druckbare Funktionstasten haben ebenso wie druckbare Tasten virtuelle Tastencodes, und diese stehen über das Event-Objekt zur Verfügung, das mit dem `keydown`-Event verbunden ist. Zum Beispiel erzeugt die linke Pfeiltaste einen Tastencode von 37 (zumindest beim standardmäßigen U.S.-Tastaturlayout und ähnlichen Layouts, wie z.B. einer deutschen Tastatur).

Als Faustregel können Sie sich merken, dass das `keydown`-Event bei Funktionstasten am nützlichsten ist, und das `keypress`-Event bei druckbaren Tasten.

## 17.5.2 Tasten-Event-Details

Die Event-Objekte, die an `keydown`-, `keypress`\- und `keyup`-Event-Handler übergeben werden, sind bei allen drei Event-Typen dieselben, aber die Interpretation bestimmter Eigenschaften dieser Objekte hängt vom Event-Typ ab. Die Event-Objekte sind natürlich auch browserabhängig und haben in Firefox und IE unterschiedliche Eigenschaften.

Wenn die **Alt** -, **Ctrl** \- oder **Shift** -Tasten gedrückt gehalten werden, während eine andere Taste gedrückt wird, wird das durch den Zustand der `altKey`-, `ctrlKey`\- und `shiftKey`-Eigenschaften des Event-Objekts angezeigt. Diese Eigenschaften sind übrigens portabel: Sie funktioneren sowohl in Firefox als auch in IE und für alle Typen von Tasten-Events. (Eine Ausnahme bestätigt die Regel: **Alt** -Tastenkombinationen werden von IE als nicht druckbar angesehen und erzeugen daher kein `keypress`-Event).

An den Tastencode oder Zeichencode eines Tasten-Events zu kommen, ist allerdings weniger portabel. Firefox definiert zwei Eigenschaften. `keyCode` enthält den virtuellen Tastencode niederer Ebene einer Taste und wird dem `keydown`-Event mitgegeben. `charCode` enthält die Codierung des druckbaren Zeichens, das durch Drücken dieser Taste erzeugt wird, und wird dem `keypress`-Event beigefügt. In Firefox erzeugen Funktionstasten ein `keypress`-Event; in diesem Fall ist `charCode` 0 und der `keyCode` enthält den virtuellen Tastencode.

In IE gibt es nur die `keyCode`-Eigenschaft, und ihre Interpretation hängt vom Event-Typ ab. Bei `keydown`-Events ist `keyCode` ein virtueller Tastencode; bei `keypress`-Events ist `keyCode` ein Zeichencode.

Zeichencodes können mit der statischen Funktion `String.fromCharCode( )` in Zeichen konvertiert werden. Um Tastencode korrekt zu verarbeiten, müssen Sie nur wissen, welche Tasten welche Tastencodes erzeugen. Beispiel 17-6 am Ende dieses Abschnitts enthält eine Zuordnung von Tastencodes zu den Funktionstasten, die sie repräsentieren (zumindest auf einem standardmäßigen U.S.-Tastaturlayout o.Ä.).

## 17.5.3 Tastatureingaben filtern

Tasten-Event-Handler können mit `<input>`\- und `<textarea>`-Elementen zum Filtern der Benutzereingaben verwendet werden. Nehmen wir zum Beispiel an, Sie möchten die Benutzereingaben zwingend in Großschreibung konvertieren:

    Nachname: <input id="nachname" type="text"
            onkeyup="this.value = this.value.toUpperCase( );">

Ein `<input>`-Element hängt das getippte Zeichen an seine `value`-Eigenschaft an, nachdem das `keypress`-Event auftritt. Bis das `keyup`-Event eintrifft, ist die `value`-Eigenschaft daher auf dem neuesten Stand, und Sie können sie einfach komplett in Großbuchstaben konvertieren. Das funktioniert unabhängig davon, wo der Cursor im Textfeld positioniert ist, und Sie können es mit dem DOM Level 0-Event-Modell erledigen. Sie müssen nicht wissen, welche Taste gedrückt wurde, und müssen deshalb nicht auf das mit dem Event assoziierte Event-Objekt zugreifen. (Beachten Sie, dass der `onkeyup`-Event-Handler nicht ausgelöst wird, wenn der Benutzer Text mit der Maus in das Feld kopiert. Um mit diesem Fall klarzukommen, werden Sie wahrscheinlich auch einen `onchange`-Handler registrieren wollen. Siehe Kapitel 18 für zusätzliche Details zu Formularelementen und ihren Event-Handlern).

Ein komplexeres Beispiel für Tasten-Event-Filterung verwendet den `onkeypress`-Handler, um die Benutzereingabe auf eine bestimmte Untermenge von Zeichen zu beschränken. Beispielsweise möchten Sie einen Benutzer davon abhalten, Buchstaben in ein für Ziffern vorgesehenes Feld einzugeben. Beispiel 17-5 ist ein unaufdringliches Modul mit JavaScript-Code, das genau diese Art Filterung ermöglicht. Es sucht nach `<input type=text>`-Tags, die ein zusätzliches (nicht standardisiertes) Attribut namens `allowed` haben. Das Modul registriert einen `keypress`-Event-Handler auf jedem solchen Textfeld, um die Eingabe auf Zeichen zu beschränken, die im Wert des `allowed`-Attributs vorkommen. Der Eingangskommentar am Anfang von Beispiel 17-5 enthält etwas Beispiel-HTML, das das Modul verwendet.

Beispiel 17-5. Benutzereingaben auf eine bestimmte Zeichenmenge beschränken

    /**
     * InputFilter.js: Unauffälliges Filtern von Tastendrücken für <input>-Tags
     *
     * Dieses Modul findet alle <input type="text">-Elementes im Dokument, die
     * ein nicht standardisiertes Attribut names "allowed" haben. Es registriert einen
     * onkeypress-Event-Handler für alle solchen Elemente, um die Benutzereingabe
     * auf Zeichen im Wert des allowed-Attributs zu beschränken.
     * Wenn das <input>-Element auch noch ein "messageid"-Attribut hat,
     * wird der Wert dieses Attributs als id eines anderen Dokument-
     * elements interpretiert. Wenn der Benutzer ein verbotenes Zeichen tippt, wird das messageid-
     * Element sichtbar gemacht. Wenn der Benutzer ein erlaubtes Zeichen eintippt,
     * wird das messageid-Element versteckt. Dieses messageid-Element soll
     * dem Benutzer eine Erklärung für den verweigerten Tastendruck geben. Es
     * sollte üblicherweise mit CSS so gestylt werden, dass es anfangs unsichtbar ist.
     *
     * Hier ist etwas Beispiel-HTML, das dieses Modul verwendet.
     *  Postleitzahl:
     *  <input id="plz" type="text" allowed="0123456789" messageid="plzwarn">
     *  <span id="plzwarn" style="color:red;visibility:hidden">Nur Ziffern</span>
     *
     * In Browsern wie IE, die addEventListener( ) nicht unterstützen,
     * überschreibt der hier registrierte keypress-Handler alle anderen keypress-Handler,
     * die in HTML definiert werden.
     *
     * Dieses Modul ist völlig unauffällig: es definiert keine Symbole im
     * globalen Namensraum.
     */
    (function( ) { // Das gesamte Modul steht in einer anonymen Funktion
      // Nach dem Laden die unten stehende init( )-Funktion aufrufen
      if (window.addEventListener) window.addEventListener("load", init, false);
      else if (window.attachEvent) window.attachEvent("onload", init);

      // Suche alle <input>-Tags, die einen Event-Handler brauchen
      function init( ) {
        var inputtags = document.getElementsByTagName("input");
        for(var i = 0 ; i < inputtags.length; i++) { // Tags durchlaufen
          var tag = inputtags[i];
          if (tag.type != "text") continue; // Wir wollen nur Textfelder
          var allowed = tag.getAttribute("allowed");
          if (!allowed) continue; // und nur, wenn sie ein allowed-Attribut haben

          // Registriere unsere Event-Handler-Funktion an diesem Input-Tag
          if (tag.addEventListener)
            tag.addEventListener("keypress", filter, false);
          else {
            // Wir verwenden attachEvent nicht, da es die Handler-Funktion
            // nicht mit dem richtigen Wert des this-Schlüsselworts aufruft.
            tag.onkeypress = filter;
          }
        }
      }

      // Das ist der keypress-Handler, der die Benutzereingabe filtert
      function filter(event) {
        // Hole das Event-Objekt und den Zeichencode in einer portablen Form
        var e = event || window.event;     // Tasten-Event-Objekt
        var code = e.charCode || e.keyCode;  // Welche Taste wurde gedrückt?

        // Wenn dieser Tastendruck eine beliebige Funktionstaste ist, nicht filtern
        if (e.charCode == 0) return true;    // Funktionstaste (nur unter Firefox)
        if (e.ctrlKey || e.altKey) return true; // Strg oder Alt gedrückt
        if (code < 32) return true;       // ASCII-Steuerzeichen

        // Suche jetzt die Informationen, die wir von diesem Input-Element brauchen
        var allowed = this.getAttribute("allowed");   // Gültige Zeichen
        var messageElement = null;       // zu versteckende/anzuzeigende Meldung
        var messageid = this.getAttribute("messageid"); // Message-ID, falls vorhanden
        if (messageid) // Element holen, wenn eine Message-ID vorhanden ist
          messageElement = document.getElementById(messageid);

        // Konvertiere den Zeichencode in ein Zeichen
        var c = String.fromCharCode(code);

        // Prüfe, ob das Zeichen in der Menge der erlaubten Zeichen ist
        if (allowed.indexOf(c) != −1) {
          // Wenn c ein gültiges Zeichen ist, dann verstecke ggf. die Meldung
          if (messageElement) messageElement.style.visibility = "hidden";
          return true; // und akzeptiere das Zeichen
        }
        else {
          // Wenn c nicht in der Menge der erlaubten Zeichen ist, Meldung anzeigen
          if (messageElement) messageElement.style.visibility = "visible";
          // und lehne dieses keypress-Event ab
          if (e.preventDefault) e.preventDefault( );
          if (e.returnValue) e.returnValue = false;
          return false;
        }
      }
    })( ); // Beende die anonyme Funktion, und rufe sie auf.

## 17.5.4 Tastenkürzel mit einer Keymap

Grafische Programme, die auf dem Desktop eines Benutzers laufen, definieren in der Regel Tastaturabkürzungen für Befehle, die auch über Ausklappmenüs, Werkzeugleisten, usw. aufgerufen werden können. Webbrowser (und HTML) sind ziemlich mausorientiert, und Webanwendungen unterstützen zumindest standardmäßig keine Tastaturabkürzungen. Das sollten sie aber. Wenn Sie DHTML verwenden, um Ausklappmenüs für Ihre Webanwendung zu simulieren, sollten Sie für diese Menüs auch Tastaturabkürzungen unterstützen. Beispiel 17-6 zeigt, wie Sie das erreichen können. Es definiert eine Keymap-Klasse, die Tastendruck-Bezeichner wie z.B. »Escape«, »Delete«, »Alt_Z« und »alt_ctrl_shift_F5« JavaScript-Funktionen zuordnet, die als Antwort auf diese Tastendrücke aufgerufen werden.

Tastenanbindungen übergeben Sie an den `Keymap( )`-Konstruktor in der Form eines JavaScript-Objekts, dessen Eigenschaftsnamen Tastendruckbezeichner und Eigenschaftswerte Handler-Funktionen sind. Bindungen fügen Sie mit der `bind( )`-Methode hinzu und entfernen sie mit der `unbind( )`-Methode. Eine Keymap installieren Sie an einem HTML-Element (das oft das Document-Objekt ist) mit der `install( )`-Methode. Bei der Installation einer Keymap an einem Element werden sowohl der `onkeydown`-Event-Handler als auch der `onkeypress`-Event-Handler an diesem Element registriert, um sowohl Funktionstasten als auch druckbare Zeichen abzufangen.

Beispiel 17-6 beginnt mit einem langen Kommentar, der das Modul ausführlicher erklärt. Ein besonders relevanter Teil dieses Kommentars ist der Abschnitt »Beschränkungen«.

Beispiel 17-6. Eine Keymap-Klasse für Tastaturabkürzungen

    /*
     * Keymap.js: Tasten-Events an Handler-Funktionen binden.
     *
     * Dieses Modul definiert eine Keymap-Klasse. Eine Instanz dieser Klasse ordnet
     * Tasten-Bezeichnern (unten definiert) Handler-Funktionen zu. Eine
     * Keymap kann an einem HTML-Element installiert werden und dessen keydown- und keypress-
     * Events bearbeiten. Tritt ein solches Event auf, wird über die Zuordnung in der Keymap
     * die passende Handler-Funktion aufgerufen.
     *
     * Bei der Erzeugung einer Keymap übergeben Sie ein JavaScript-Objekt, das die
     * Anfangsanbindungen für die Keymap repräsentiert. Die Eigenschaftsnamen dieses Objekts
     * sind Tasten-Bezeichner und die Eigenschaftswerte sind die Handler-Funktionen.
     *
     * Nach der Erzeugung einer Keymap können Sie neue Anbindungen durch Übergeben
     * von Tasten-Bezeichner, und Handler-Funktion an bind( ) hinzufügen. Eine Bindung
     * entfernen Sie, indem Sie einen Tasten-Bezeichner an die unbind( )-Methode übergeben.
     *
     * Sie verwenden eine Keymap durch Aufruf ihrer install( )-Methode mit einem HTML-Element,
     * wie z.B. dem Document-Objekt. Die install( )-Methode installiert onkeypress- und onkeydown-
     * Event-Handler am angegebenen Objekt und ersetzt alle Handler, die ggf. vorher
     * über dieser Eigenschaften gesetzt wurden. Werden diese Handler aufgerufen, so bestimmen
     * sie den Tastenbezeichner des Tasten-Events und rufen die ggf. vorhandene Handler-Funktion
     * auf, die an diesen Tastenbezeichner gebunden ist. Ist für das Event keine Zuordnung
     * vorhanden, wird die Default-Handler-Funktion (siehe unten) verwendet, falls sie
     * definiert ist. Eine einzige Keymap kann auf mehreren HTML-Elementen installiert werden.
     *
     * Tastenbezeichner
     *
     * Ein Tastenbezeichner ist eine Stringrepräsentation (groß- oder kleingeschrieben) einer
     * Taste mit ggf. gleichzeitig gedrückten Modifikationstasten. Der Tastenname ist der
     * Name der Taste, d.h., oft der Text, der tatsächlich auf der Taste einer
     * englischen Tastatur steht. Gültige Tastennamen sind z.B. "A", "7", "F2", "PageUp",
     * "Left", "Delete", "/", "~". Bei druckbaren Tasten ist der Tastenname einfach das
     * Zeichen, das die Taste erzeugt. Bei nicht-druckbaren Tasten sind die Namen
     * aus den von Firefox definierten KeyEvent.DOM_VK_-Konstanten abgeleitet. Das sind
     * lediglich die Konstantennamen, wobei "DOM_VK_" und ggf. vorhandene Unterstriche
     * entfernt sind. Beispielsweise wird die KeyEvent-Konstante DOM_VK_BACK_SPACE zu
     * BACKSPACE. Das Keymap.keyCodeToFunctionKey-Objekt in diesem Modul führt
     * alle Namen auf.
     *
     * Ein Tastenbezeichner kann auch Modifikationstastenpräfixe enthalten. Diese Präfixe sind
     * Alt_, Ctrl_ und Shift_. Groß- und Kleinschreibung sind hier unwichtig, sind aber mehrere
     * vorhanden, müssen sie in alphabetischer Reihenfolge stehen. Tastenbezeichner, die
     * Modifikationstasten enthalten sind z.B. "Shift_A", "ALT_F2" und "alt_ctrl_delete".
     * Beachten Sie, dass "ctrl_alt_delete" ungültig ist, da die Tasten nicht alphabetisch
     * geordnet sind.
     *
     * Umgeschaltete Sonderzeichentasten werden normalerweise als die entsprechenden
     * Zeichen zurückgegeben. Shift-4 erzeugt beispielsweise den Tastenbezeichner "$". Wenn
     * aber zusätzlich Alt oder Strg gedrückt werden, wird stattdessen das nicht umgeschaltete
     * Zeichen verwendet. Beispielsweise erhalten wir den Tastenbezeichner Strg_Shift_4 statt
     * Strg_$.
     *
     * Handler-Funktionen
     *
     * Wird eine Handler-Funktion aufgerufen, werden ihr drei Argumente übergeben:
     *  1) das HTML-Element, an dem das Tasten-Event stattfand
     *  2) der Tastenbezeichner der Taste, die gedrückt wurde
     *  3) das Event-Objekt für das keydown-Event
     *
     * Default-Handler
     *
     * Der reservierte Tastenname "defaulthandler" kann einer Handler-Funktion zugewiesen werden.
     * Diese Funktion wird aufgerufen, wenn keine andere tastenspezifische Anbindung besteht.
     *
     * Beschränkungen
     *
     * Eine Handler-Funktion kann nicht an alle Tasten gebunden werden. Das Betriebssystem
     * fängt manche Tastenabfolgen (z.B. Alt-F4) ab. Und der Browser
     * selbst fängt eventuell andere ab (z.B. Strg-S). Dieser Code hängt von Browser, OS
     * und Locale ab. Funktionstasten und modifizierte Funktionstasten funktionieren gut.
     * Nicht-modifizierte, druckbare Tasten funktionieren ebenfalls gut. Die Kombination von
     * Strg und Alt mit druckbaren Zeichen – und insbesondere mit Sonderzeichen – ist
     * weniger robust.
     */

    // Das ist die Konstruktorfunktion
    function Keymap(bindings) {
      this.map = {};  // Definiere die Zuweisung Bezeichner->Handler
      if (bindings) {  // Kopiere Anfangsanbindungen, und konvertiere zu Kleinschreibung
        for(name in bindings) this.map[name.toLowerCase( )] = bindings[name];
      }
    }

    // Binde den angegebenen Tastenbezeichner an die angegebene Handler-Funktion
    Keymap.prototype.bind = function(key, func) {
      this.map[key.toLowerCase( )] = func;
    };

    // Lösche die Bindung für den angegebenen Tastenbezeichner
    Keymap.prototype.unbind = function(key) {
      delete this.map[key.toLowerCase( )];
    };

    // Installiere diese Keymap am angegebenen HTML-Element
    Keymap.prototype.install = function(element) {
      // Das ist die Event-Handler-Funktion
      var keymap = this;
      function handler(event) { return keymap.dispatch(event); }

      // Installiere sie jetzt
      if (element.addEventListener) {
        element.addEventListener("keydown", handler, false);
        element.addEventListener("keypress", handler, false);
      }
      else if (element.attachEvent) {
        element.attachEvent("onkeydown", handler);
        element.attachEvent("onkeypress", handler);
      }
      else {
        element.onkeydown = element.onkeypress = handler;
      }
    };

    // Dieses Objekt erzeugt Bezeichner aus keyCode-Werten für normale nicht-druckbare
    // Funktionstasten. IE und Firefox verwenden für diese Tasten i.d.R. kompatible keyCodes.
    // Beachten Sie aber, dass diese keyCodes geräteabhängig sein können und verschiedene
    // Tastatur-Layouts unterschiedliche Werte haben können.
    Keymap.keyCodeToFunctionKey = {
      8:"backspace", 9:"tab", 13:"return", 19:"pause", 27:"escape", 32:"space",
      33:"pageup", 34:"pagedown", 35:"end", 36:"home", 37:"left", 38:"up",
      39:"right", 40:"down", 44:"printscreen", 45:"insert", 46:"delete",
      112:"f1", 113:"f2", 114:"f3", 115:"f4", 116:"f5", 117:"f6", 118:"f7",
      119:"f8", 120:"f9", 121:"f10", 122:"f11", 123:"f12",
      144:"numlock", 145:"scrolllock"
    };

    // Dieses Objekt bildet keydown-keyCode-Werte auf Bezeichner ab, für druckbare
    // Zeichen. Alphanumerische Zeichen haben ihren ASCII-Code, aber
    // Sonderzeichen nicht. Beachten Sie, dass das locale-abhängig sein kann
    // und auf internationalen Tastaturen evtl. nicht richtig funktioniert.
    Keymap.keyCodeToPrintableChar = {
      48:"0", 49:"1", 50:"2", 51:"3", 52:"4", 53:"5", 54:"6", 55:"7", 56:"8",
      57:"9", 59:";", 61:"=", 65:"a", 66:"b", 67:"c", 68:"d",
      69:"e", 70:"f", 71:"g", 72:"h", 73:"i", 74:"j", 75:"k", 76:"l", 77:"m",
      78:"n", 79:"o", 80:"p", 81:"q", 82:"r", 83:"s", 84:"t", 85:"u", 86:"v",
      87:"w", 88:"x", 89:"y", 90:"z", 107:"+", 109:"-", 110:".", 188:",",
      190:".", 191:"/", 192:"`", 219:"[", 220:"\\", 221:"]", 222:"\""
    };

    // Diese Methode leitet Tasten-Events je nach keymap-Anbindung weiter.
    Keymap.prototype.dispatch = function(event) {
      var e = event || window.event; // IE-Event-Modell berücksichtigen

      // Wir beginnen ohne Modifikationstasten und ohne Bezeichner
      var modifiers = ""
      var keyname = null;

      if (e.type == "keydown") {
        var code = e.keyCode;
        // Ignoriere keydown-Events für Shift, Strg und Alt
        if (code == 16 || code == 17 || code == 18) return;

        // Hole den Tastenbezeichner aus unserer Zuordnung
        keyname = Keymap.keyCodeToFunctionKey[code];

        // Wenn das keine Funktionstaste war, aber die strg- oder alt-Tasten gedrückt
        // sind, wollen wir es wie eine Funktiontaste behandeln
        if (!keyname && (e.altKey || e.ctrlKey))
          keyname = Keymap.keyCodeToPrintableChar[code];

        // Hat sich ein Bezeichner für die Taste gefunden, brauchen wir die
        // Modifikationstasten.
        // Wenn nicht, einfach zurückkehren und keydown-Event ignorieren.
        if (keyname) {
          if (e.altKey) modifiers += "alt_";
          if (e.ctrlKey) modifiers += "ctrl_";
          if (e.shiftKey) modifiers += "shift_";
        }
        else return;
      }
      else if (e.type == "keypress") {
        // Wenn strg oder alt gedrückt sind, haben wir die Taste bereits behandelt.
        if (e.altKey || e.ctrlKey) return;

        // Unter Firefox erhalten wir keypress-Events auch für nicht-druckbare Tasten.
        // In diesem Fall einfach zurückkehren und so tun, als wäre nichts passiert
        if (e.charCode != undefined && e.charCode == 0) return;

        // Firefox gibt uns druckbare Tasten in e.charCode, IE in e.keyCode
        var code = e.charCode || e.keyCode;

        // Der Code ist ein ASCII-Code, konvertiere ihn in einen String.
        keyname=String.fromCharCode(code);

        // Großgeschriebene Tastenbezeichner machen wir klein und fügen Shift hinzu
        // Damit können wir mit CAPS LOCK umgehen, das Großbuchstaben sendet,
        // ohne dass dabei der Shift-Modifier gesetzt ist.
        var lowercase = keyname.toLowerCase( );
        if (keyname != lowercase) {
          keyname = lowercase;  // Verwende den kleingeschriebenen Namen,
          modifiers = "shift_";  // und füge den Shift-Modifikator an.
        }
      }

      // Modifikatoren und Tastenbezeichner sind bestimmt – jetzt suchen wir nach
      // einer Handler-Funktion für die Tasten-Modifikatorenkombination
      var func = this.map[modifiers+keyname];

      // Wenn wir keine gefunden haben, Default-Handler verwenden, wenn vorhanden
      if (!func) func = this.map["defaulthandler"];

      if (func) { // Wenn ein Handler für diese Taste vorhanden ist, benutze ihn
        // Bestimme, an welchem Element das Event stattfand
        var target = e.target;       // DOM-Standard-Event-Modell
        if (!target) target = e.srcElement; // IE-Event-Modell

        // Rufe die Handler-Funktion auf
        func(target, modifiers+keyname, e);

        // Halte das Event von der Ausbreitung ab, und unterdrücke die Standardaktion für
        // das Event. Beachte, dass preventDefault normalerweise
        // Browserbefehle der obersten Ebene, z.B. F1 für Hilfe, nicht verhindert.
        if (e.stopPropagation) e.stopPropagation( ); // DOM-Modell
        else e.cancelBubble = true;         // IE-Modell
        if (e.preventDefault) e.preventDefault( );  // DOM
        else e.returnValue = false;         // IE
        return false;                // Legacy-Event-Modell
      }
    };

# 17.6 Das onload-Event

JavaScript-Code, der das Dokument verändert, in dem er enthalten ist, darf normalerweise erst laufen, wenn das Dokument vollständig geladen ist (das wird in "13.5.7 Das Dokument während des Ladens manipulieren" ausführlicher erläutert). Nach dem Abschließen des Ladevorgangs löst der Webbrowser ein `onload`-Event am Window-Objekt aus, und dieses Event wird oft verwendet, um Code auszuführen, der auf das gesamte Dokument zugreifen können muss. Enthält Ihre Webseite mehrere unabhängige Module, die Code als Antwort auf das `onload`-Event laufen lassen müssen, so ist eventuell eine plattformübergreifende Utility-Funktion nützlich für Sie, wie die in Beispiel 17-7 gezeigte.

Beispiel 17-7. Portable Event-Registrierung für onload-Event-Handler

    /*
     * runOnLoad.js: portable Registrierung für onload-Event-Handler.
     *
     * Dieses Modul definiert eine einzige runOnLoad( )-Funktion für das portable Registrieren
     * von Funktionen, die erst nach vollständigem Laden des Dokuments sicher aufgerufen werden
     * können, wenn das DOM vorhanden ist.
     *
     * An Funktionen, die mit runOnLoad( ) registriert sind, werden beim Aufruf keine Argumente
     * übergeben. Sie werden nicht als Methode eines bestimmten Objekts aufgerufen, und
     * this sollte nicht verwendet werden. Funktionen, die mit runOnLoad( ) registriert wurden,
     * werden in der Reihenfolge aufgerufen, in der sie registriert wurden. Es gibt keine
     * Möglichkeit, mit runOnLoad( ) registrierte Funktionen zu deregistrieren.
     *
     * In alten Browsern, die addEventListener( ) und attachEvent( ) nicht unterstützen,
     * braucht diese Funktion die DOM Level 0-window.onload-Eigenschaft und
     * funktioniert nicht richtig, wenn sie in Dokumenten verwendet wird, die das onload-Attribut
     * ihrer <body>- oder <frameset>-Tags setzen.
     */
    function runOnLoad(f) {
      if (runOnLoad.loaded) f( );  // Wenn bereits geladen, rufe f( ) jetzt einfach auf.
      else runOnLoad.funcs.push(f); // Ansonsten speichere sie
    }

    runOnLoad.funcs = []; // Das Array der nach dem Laden aufzurufenden Funktionen
    runOnLoad.loaded = false; // Die Funktionen wurden noch nicht ausgeführt.

    // Alle registrierten Funktionen in der Reihenfolge der Registrierung ausführen.
    // Es ist sicher, runOnLoad.run( ) mehrfach aufzurufen: Aufrufe, die dem ersten Aufruf
    // folgen, haben keine Auswirkung. Eine Initialisierungsfunktion kann
    // runOnLoad( ) zur Registrierung einer weiteren Funktion sicher aufrufen.
    runOnLoad.run = function( ) {
      if (runOnLoad.loaded) return; // Mache nichts, wenn die Ausführung bereits stattfand

      for(var i = 0; i < runOnLoad.funcs.length; i++) {
        try { runOnLoad.funcs[i]( ); }
        catch(e) { /* Eine Ausnahme in einer Funktion sollte den Rest nicht anhalten */ }
      }

      runOnLoad.loaded = true; // Ausführung vermerken.
      delete runOnLoad.funcs; // Die eigentlichen Funktionen nicht speichern,
      delete runOnLoad.run;  // einschließlich dieser Funktion!
    };

    // Registriere runOnLoad.run( ) als den onload-Event-Handler für das Fenster
    if (window.addEventListener)
      window.addEventListener("load", runOnLoad.run, false);
    else if (window.attachEvent) window.attachEvent("onload", runOnLoad.run);
    else window.onload = runOnLoad.run;

# 17.7 Synthetische Events

Sie können sowohl mit dem DOM Level 2-Event-Modell als auch mit dem IE-Event-Modell synthetische Event-Objekte erzeugen und sie an Event-Handler leiten, die an Dokumentelementen registriert sind. Im Grunde dient diese Technik dazu, Browser zum Aufruf der an einem Element registrierten Event-Handler zu bringen (und, im Fall von aufsteigenden Events, die an den übergeordneten Elementen registrierten Handler aufzurufen). Beim Level 0-Event-Modell sind synthetische Events nicht notwendig, da Event-Handler über die verschiedenen Event-Handler-Eigenschaften zur Verfügung stehen. In den fortgeschrittenen Event-Modellen lassen sich aber die Handler, die mit `addEventListener` oder `attachEvent` registriert sind, nicht abfragen. Sie können nur mit den in diesem Abschnitt gezeigten Techniken aufgerufen werden.

Im DOM-Event-Modell erzeugen Sie ein synthetisches Event mit `Document. createEvent( )`, initialisieren das Event mit `Event.initEvent( )`, `UIEvent.initUIEvent( )` oder `MouseEvent.initMouseEvent( )` und leiten das Event dann mit der `dispatchEvent`-Methode des Knotens weiter, an den es gesendet werden soll. Unter IE erzeugen Sie ein neues Event-Objekt mit `Document.createEventObject` und leiten es dann mit der `fireEvent( )`-Methode des Zielelements weiter. Beispiel 17-8 führt diese Methoden vor. Es definiert eine plattformübergreifende Funktion zum Versenden synthetischer `dataavailable`-Events und eine Funktion zur Registrierung von Event-Handlern für Events dieses Typs.

Es ist wichtig zu verstehen, dass synthetische Events, die mit `dispatchEvent( )` und `fireEvent( )` ausgelöst werden, nicht in eine Schlange eingereiht und asynchron bearbeitet werden. Vielmehr werden sie sofort ausgelöst, und ihre Handler werden synchron aufgerufen, bevor der Aufruf von `dispatchEvent( )` oder `fireEvent( )` zurückkehrt. Das bedeutet, dass der Versand von synthetischen Events keine Technik ist, mit der man die Ausführung von Code verzögern kann, bis der Browser alle anstehenden Events ausgeführt hat. Um das zu erreichen, müssen Sie `setTimeout( )` mit einem Timeout-Wert von 0 Millisekunden aufrufen.

Rohe Events auf niedriger Ebene, wie z.B. Maus-Events, können synthetisiert und weitergeleitet werden, aber es ist nicht umfassend geregelt, wie Dokumentelemente auf diese Events reagieren müssen. Meist ist es nützlicher, diese Funktionalität für sematische Events auf höherer Ebene zu verwenden, für die der Browser keine Standardantwort hat. Aus diesem Grund verwendet Beispiel 17-8 den Event-Typ `dataavailable`.

Beispiel 17-8. Versenden synthetischer Events

    /**
     * DataEvent.js: versende und empfange ondataavailable-Events.
     *
     * Dieses Modul definiert zwei Funktionen: DataEvent.send( ) und DataEvent.receive( )
     * zum Versand synthetischer dataavailable-Events und zur Registrierung von Event-
     * Handlern für diese Events. Der Code ist so geschrieben, dass er unter Firefox und anderen
     * DOM-standardkonformen Browsern sowie unter IE funktioniert.
     *
     * Das DOM-Event-Modell lässt synthetische Events aller Art zu, aber das IE-Modell
     * unterstützt nur synthetische Events vordefinierter Typen. dataavailable-Events sind der
     * allgemeinste vordefinierte Typ, den IE unterstützt, und sie werden hier verwendet.
     *
     * Beachten Sie, dass mit DataEvent.send( ) versendete Events nicht in eine Warteschlange
     * kommen, wie das bei echten Events passiert. Stattdessen werden registrierte Handler
     * sofort aufgerufen.
     */
    var DataEvent = {};

    /**
     * Sende ein synthetisches ondataavailable-Event an das angegebene Ziel.
     * Das Event-Objekt enthält Eigenschaften namens datatype und data
     * mit den angegebenen Werten. Es wird erwartet, dass datatype ein String
     * oder ein anderer einfacher Wert (bzw. Null) ist, der den Typ dieser Mitteilung
     * identifiziert, und data kann ein beliebiger JavaScript-Wert sein, auch ein Objekt
     * oder Array.
     */
    DataEvent.send = function(target, datatype, data) {
      if (typeof target == "string") target = document.getElementById(target);

      // Erzeuge ein Event-Objekt. Wenn das nicht klappt, still aufgeben.
      if (document.createEvent) {      // DOM-Event-Modell
        // Erzeuge das Event und gib den Namen des Event-Moduls an.
        // Für ein Maus-Event würden wir "MouseEvents" verwenden.
        var e = document.createEvent("Events");
        // Initialisiere das Event-Objekt mit einer modulspezifischen Init-Methode.
        // Hier geben wir den Event-Typ als aufsteigend und nicht-abbrechbar an.
        // Vergleiche Event.initEvent, MouseEvent.initMouseEvent und UIEvent.initUIEvent
        e.initEvent("dataavailable", true, false);
      }
      else if (document.createEventObject) { // IE-Event-Modell
        // Im IE-Event-Modell rufen wir nur diese einfache Methode auf
        var e = document.createEventObject( );
      }
      else return; // Mache nichts in anderen Browsern

      // Hier fügen wir dem Event-Objekt einige maßgeschneiderte Eigenschaften hinzu.
      // Zusätzlich könnten wir bereits bestehende Eigenschaften setzen.
      e.datatype = datatype;
      e.data = data;

      // Versende das Event an das angegebene Ziel.
      if (target.dispatchEvent) target.dispatchEvent(e); // DOM
      else if (target.fireEvent) target.fireEvent("ondataavailable", e); // IE
    };

    /**
     * Registriere einen Event-Handler für ein ondataavailable-Event am angegebenen
     * Zielelement.
     */
    DataEvent.receive = function(target, handler) {
      if (typeof target == "string") target = document.getElementById(target);
      if (target.addEventListener)
        target.addEventListener("dataavailable", handler, false);
      else if (target.attachEvent)
        target.attachEvent("ondataavailable", handler);
    };

# Kapitel 18. Formulare und Formularelemente

Wie viele Beispiele in diesem Buch zeigen, sind HTML-Formulare ein wichtiger Bestandteil vieler clientseitiger JavaScript-Programme. In diesem Kapitel erläutern wir die Einzelheiten der Programmierung mit Formularen in JavaScript. Es wird vorausgesetzt, dass Sie bereits ein wenig mit der Erstellung von HTML-Formularen und den in ihnen enthaltenen Eingabeelementen vertraut sind. Falls dies nicht der Fall ist, sollten Sie ein gutes Buch über HTML zurate ziehen.[65]

Wenn Sie bereits mit serverseitiger Programmierung im Zusammenhang mit der Benutzung von Formularen vertraut sind, werden Sie feststellen, dass es einige Unterschiede zur Verwendung von Formularen in JavaScript gibt. Im serverseitigen Modell wird ein Formular mit den in ihm enthaltenen Daten auf einen Rutsch abgeschickt, also an den Webserver gesandt. Der Schwerpunkt liegt auf der Bearbeitung eines vollständigen Satzes von Eingabedaten und der dynamischen Erzeugung einer neuen Webseite im Gegenzug. Das Programmiermodell von JavaScript unterscheidet sich davon erheblich. In JavaScript-Pogrammen steht nicht das Abschicken eines Formulars im Vordergrund, sondern das Behandeln von Events. Ein Formular und alle in ihm enthaltenen Eingabeelemente besitzen Event-Handler, die JavaScript verwenden kann, um auf Benutzereingaben im Formular zu reagieren. Wenn der Benutzer beispielsweise auf eine Checkbox klickt, kann ein JavaScript-Programm eine Benachrichtigung durch einen Event-Handler erhalten und als Reaktion z.B. den in einem anderen Element des Formulars angezeigten Wert verändern.

Bei serverseitigen Programmen kann ein HTML-Formular nur sinnvoll verwendet werden, wenn es einen **Submit** -Button enthält (oder nur ein einzelnes Texteingabefeld, bei dem der Benutzer die **Enter** -Taste anstelle eines solchen Buttons zum Abschicken drücken kann). Bei JavaScript ist ein **Submit** -Button nie zwingend notwendig (obwohl er nützlich sein kann). Ein Formular kann in JavaScript eine beliebige Anzahl von Buttons mit Event-Handlern besitzen, die verschiedene Aktionen ausführen, wenn sie gedrückt werden (einschließlich des Abschickens eines Formulars).

Die Beispiele in diesem Buch zeigen auch, dass Event-Handler oft das zentrale Element eines JavaScript-Programms sind. Zu den gebräuchlichsten Event-Handlern zählen diejenigen, die bei Formularen oder Formularelementen verwendet werden. In diesem Kapitel werden das Form-Objekt von JavaScript sowie die verschiedenen JavaScript-Objekte für Formularelemente vorgestellt. Das Kapitel endet mit einem Beispiel, in dem JavaScript benutzt wird, um Eingaben eines Benutzers auf der Clientseite zu überprüfen, bevor das Formular an ein auf dem Webserver laufendes serverseitiges Programm geschickt wird.

# 18.1 Das Form-Objekt

Das Form-Objekt in JavaScript entspricht einem HTML-Formular. Wie in Kapitel 15 erklärt wurde, sind Formularobjekte als Elemente des `forms[]`-Arrays zugänglich, das eine Eigenschaft des Document-Objekts ist. Die Formulare erscheinen in diesem Array in der gleichen Reihenfolge wie im Dokument. `document.forms[0]` verweist also auf das erste Formular in einem Dokument; auf das letzte Formular eines Dokuments können Sie wie folgt zugreifen:

    document.forms[document.forms.length-1]

Die interessanteste Eigenschaft des Form-Objekts ist das Array `elements[]`. Es enthält JavaScript-Objekte unterschiedlichen Typs, die die verschiedenen Eingabeelemente des Formulars darstellen. Auch diese Elemente erscheinen im Array in der gleichen Reihenfolge wie im Dokument. Sie können folgendermaßen auf das dritte Element des zweiten Formulars im Dokument des aktuellen Fensters zugreifen:

    document.forms[1].elements[2]

Die verbleibenden Eigenschaften des Form-Objekts sind weniger wichtig. Die Eigenschaften `action`, `encoding`, `method` und `target` entsprechen unmittelbar den Attributen `action`, `encoding`, `method` und `target` des `<form>`-Tags. Diese Eigenschaften und Attribute werden verwendet, um das Versenden von Daten an den Webserver zu steuern und anzugeben, wo die Ergebnisse angezeigt werden sollen. Sie sind nur von Nutzen, wenn das Formular tatsächlich an ein serverseitiges Programm übergeben wird. Eine ausführliche Beschreibung dieser Attribute finden Sie in Büchern über HTML oder serverseitige Webprogrammierung. Beachtenswert ist, dass alle diese Eigenschaften von Formularen les- und schreibbare Strings sind, sodass ein JavaScript-Programm die Werte dynamisch zuweisen kann, um das Verfahren zu ändern, mit dem das Formular versandt wird.

Früher, bevor es JavaScript gab, wurden Formulare mit einem speziellen **Submit** -Button abgesandt; auf ihren Anfangswert zurückgesetzt wurden die Formularelemente mit einem speziellen **Reset** -Button. Das Form-Objekt in JavaScript unterstützt nun zwei Methoden, `submit( )` und `reset( )`, die demselben Zweck dienen. Durch den Aufruf der `submit( )`-Methode eines Formulars wird dieses abgeschickt, ein Aufruf von `reset( )` setzt die Werte der Formularelemente zurück.

Als Ergänzung der Methoden `submit( )` und `reset( )` stellt das Form-Objekt die Event-Handler `onsubmit` und `onreset` bereit. Letzterer wird vor dem Zurücksetzen der Formularwerte aufgerufen. Der Event-Handler `onsubmit` wird aufgerufen, unmittelbar bevor das Formular abgesandt wird; er kann das Versenden verhindern, indem er den Wert `false` zurückgibt. Dadurch ist es einem JavaScript-Programm möglich, die Eingaben des Benutzers auf Fehler zu überprüfen und zu verhindern, dass unvollständige oder ungültige Daten über das Netz zu einem CGI-Programm geschickt werden. Sie werden ein Beispiel dafür am Ende dieses Abschnitts sehen. Beachten Sie, dass der Event-Handler `onsubmit` nur bei einem Klick auf den Button **Submit** aufgerufen wird. Wenn das Formular durch einen Aufruf der Methode `submit( )` eines Formulars abgeschickt wird, wird der Event-Handler nicht ausgelöst.

Der Event-Handler `onreset` ähnelt `onsubmit`. Er wird aufgerufen, kurz bevor die Formulareinträge gelöscht werden, und kann die Löschung verhindern, indem er den Wert `false` zurückgibt. Dadurch kann ein JavaScript-Programm eine Bestätigung des Löschens verlangen, was insbesondere bei langen oder ausführlichen Formularen eine gute Idee sein kann. Sie können eine derartige Bestätigung mit einem Event-Handler wie dem folgenden verlangen:

    <form...
      onreset="return confirm('Sollen wirklich alle Daten gelöscht werden?')"
    >

Wie der Event-Handler `onsubmit` wird auch `onreset` nur durch den **Reset** -Button ausgelöst, nicht aber bei einem Aufruf der Methode `reset( )`.

* * *

[65] Beispielsweise _HTML – Das umfassende Referenzwerk_ von Chuck Musciano und Bill Kennedy (O'Reilly Verlag).

# 18.2 Definition von Formularelementen

Mit HTML-Formularelementen können Sie einfache Benutzerschnittstellen für Ihre JavaScript-Programme bauen. Abbildung 18-1 zeigt ein komplexes Formular, in dem mindestens eines der grundlegenden Formularelemente enthalten ist. Für den Fall, dass Sie nicht bereits mit HTML-Formularelementen vertraut sind, enthält das Formular eine Legende, in der die einzelnen Elemente benannt sind. Wir beenden diesen Abschnitt mit einem Beispiel (Beispiel 18-1), das den HTML- und den JavaScript-Code enthält, mit dem das Formular in Abbildung 18-1 erzeugt wurde, und in dem die Event-Handler an die Formularelemente gebunden werden.

Abbildung 18-1. HTML-Formularelemente

Tabelle 18-1 ist eine Zusammenstellung aller Formularelemente, die HTML-Designern und JavaScript-Programmierern zur Verfügung stehen. In der ersten Spalte finden Sie den Typ des Formularelements, in der zweiten die HTML-Tags, die zur Definition der Elemente verwendet werden. Die dritte Spalte enthält den Wert des Attributs `type` für jeden Typ von Formularelement. Wie bereits gezeigt, hat jedes Form-Objekt ein `elements[]`-Array mit den Objekten, die die Elemente des Formulars repräsentieren. Jedes Element hat ein `type`-Attribut, um zwischen den verschiedenen Elementtypen zu unterscheiden. Indem es dieses Attribut untersucht, kann das JavaScript-Programm den Typ des Elements ermitteln und damit feststellen, was es mit diesem Element anfangen kann. Die vierte Spalte schließlich beschreibt die einzelnen Elemente kurz und nennt ihre am häufigsten benötigten Event-Handler.

Beachten Sie, dass die Namen Button, Checkbox usw. in der ersten Spalte der Tabelle 18-1 nicht unbedingt einem tatsächlichen JavaScript-Objekt auf der Clientseite entsprechen. Die vollständigen Einzelheiten über die verschiedenen Elementtypen finden Sie in Teil IV unter den Einträgen Input, Option, Select und Textarea. Diese Formularelemente werden weiter unten in diesem Kapitel ausführlicher besprochen.

Tabelle 18-1. HTML-Formularelemente

Objekt | HTML-Tag | type-Attribut | Beschreibung und Events

---|---|---|---

Button | `<input type="button">` oder `<button type="button">` | "button" | Ein Button; `onclick`.

Checkbox | `<input type="checkbox">` | "checkbox" | Ein nicht-exklusiver Schalter; `onclick`.

File | `<input type="file">` | "file" | Ein Eingabefeld für den Namen einer Datei, die auf einen Webserver geladen werden soll; `onchange`.

Hidden | `<input type="hidden">` | "hidden" | Für den Benutzer unsichtbare Daten, die mit dem Formular verschickt werden; keine Event-Handler.

Option | `<option>` | keine | Ein einzelnes Element innerhalb eines Select-Objekts; Event-Handler beziehen sich auf das Select-Objekt, nicht auf einzelne Option-Objekte.

Password | `<input type="password">` | "password" | Ein Eingabefeld für Passwörter – die eingegebenen Zeichen werden nicht angezeigt; `onchange`.

Radio | `<input type="radio">` | "radio" | Ein exklusiver Schalter – nur einer kann ausgewählt werden; `onclick`.

Reset | `<input type="reset">` oder `<button type="reset">` | "reset" | Ein Button, mit dem ein Formular zurückgesetzt werden kann; `onclick`.

Select | `<select>` | "select-one" | Eine Liste oder ein Menü, aus der bzw. dem ein Eintrag ausgewählt werden kann; `onchange`. Siehe auch das Option-Objekt.

Select | `<select multiple>` | "select-multiple" | Eine Liste, aus der mehrere Elemente ausgewählt werden können; `onchange`. Siehe auch das Option-Objekt.

Submit | `<input type="submit">` oder `<button type="submit">` | "submit" | Ein Button, mit dem ein Formular abgeschickt wird; `onclick`.

Text | `<input type="text">` | "text" | Ein einzeiliges Texteingabefeld; `onchange`.

Textarea | `<textarea>` | "textarea" | Ein mehrzeiliges Texteingabefeld; `onchange`.

Nachdem wir uns die verschiedenen Typen von Formularelementen und die zu ihrer Erzeugung verwendeten HTML-Tags angesehen haben, wenden wir uns in Beispiel 18-1 dem HTML-Code zu, der das in Abbildung 18-1 abgebildete Formular erzeugt hat. Das Beispiel besteht hauptsächlich aus HTML-Code, enthält aber auch den JavaScript-Code, mit dem die Event-Handler für jedes Formularelement definiert werden. Beachten Sie, dass die Event-Handler nicht als HTML-Attribute definiert werden, sondern als JavaScript-Funktionen, die den Eigenschaften der Objekte im Array `elements[]` des Formulars zugewiesen werden. Alle Event-Handler rufen die Funktion `protokolliere( )` auf, die Code enthält, der mit den verschiedenen Formularelementen funktioniert. Im nächsten Abschnitt erfahren Sie alles, was Sie wissen müssen, um die Funktionsweise der Funktion `protokolliere( )` zu verstehen.

Beispiel 18-1. Ein HTML-Formular, das sämtliche Formularelemente enthält

    <form name="alles">            <!-- Ein "Von-jedem-eins"-Formular... -->
     <table border="border" cellpadding="5">  <!-- in einer großen HTML-Tabelle -->
      <tr>
       <td>Benutzername:<br>[1]<input type="text" name="benutzername" size="15"></td>
       <td>Passwort:<br>[2]<input type="password" name="passwort" size="15"></td>
       <td rowspan="4">Eingabe-Events[3]<br>
        <textarea name="textarea" rows="20" cols="28"></textarea></td>
       <td rowspan="4" align="center" valign="center">
        [9]<input type="button" name="loeschbutton" value="Löschen"><br>
        [10]<input type="submit" name="submitbutton" value="Abschicken"><br>
        [11]<input type="reset" name="resetbutton" value="Zurücksetzen"></td></tr>
      <tr>
       <td colspan="2">
        Dateiname: [4]<input type="file" name="datei" size="15"></td></tr>
      <tr>
       <td>Angeschlossene Geräte:<br>
        [5]<input type="checkbox" name="extras" value="burner">DVD-Brenner<br>
        [5]<input type="checkbox" name="extras" value="printer">Drucker<br>
        [5]<input type="checkbox" name="extras" value="card">Kartenleser</td>
       <td>Mein Webbrowser:<br>
        [6]<input type="radio" name="browser" value="ff">Firefox<br>
        [6]<input type="radio" name="browser" value="ie">Internet Explorer<br>
        [6]<input type="radio" name="browser" value="anderer">Anderer</td></tr>
      <tr>
       <td>Meine Hobbies:[7]<br>
        <select multiple="multiple" name="hobbies" size="4">
         <option value="programmieren">JavaScript hacken
         <option value="surfen">Im Web surfen
         <option value="koffein">Kaffee trinken
         <option value="nerven">Meine Freunde nerven
        </select></td>
       <td align="center" valign="center">Meine Lieblingsfarbe:<br>[8]
        <select name="farbe">
         <option value="red">Rot     <option value="green">Grün
         <option value="blue">Blau    <option value="white">Weiß
         <option value="violet">Violett <option value="peach">Pfirsich
        </select></td></tr>
     </table>
    </form>

    <div align="center">    <!-- Noch eine Tabelle -- die Legende zur obigen -->
     <table border="4" bgcolor="pink" cellspacing="1" cellpadding="4">
      <tr>
       <td align="center"><b>Formularelemente</b></td>
       <td>[1] Text</td> <td>[2] Password</td> <td>[3] Textarea</td>
       <td>[4] File-Upload</td> <td>[5] Checkbox</td></tr>
      <tr>
       <td>[6] Radio</td> <td>[7] Select (list)</td>
       <td>[8] Select (menu)</td> <td>[9] Button</td>
       <td>[10] Submit</td> <td>[11] Reset</td></tr>
     </table>
    </div>

    <script>
    // Diese generische Funktion protokolliert die Events in der großen Textarea
    // im obigen Formular. Sie wird von verschiedenen Event-Handlern aufgerufen.
    function protokolliere(element, event) {
      if ((element.type == "select-one") || (element.type == "select-multiple")){
        wert = " ";
        for(var i = 0; i < element.options.length; i++)
          if (element.options[i].selected)
            wert += element.options[i].value + " ";
      }
      else if (element.type == "textarea") wert = "...";
      else wert = element.value;
      var msg = event + ": " + element.name + ' (' + value + ')\n';
      var t = element.form.textarea;
      t.value = t.value + msg;
    }

    // Diese Funktion gibt jedem Element des Formulars ein paar Event-Handler.
    // Sie kümmert sich nicht darum, ob das Element den Event-Handler unterstützt.
    // Sie fügt einfach alle hinzu. // Jeder Handler ruft die Funktion protokolliere() auf.
    // Wir definieren die Event-Handler, indem wir den Eigenschaften von
    // JavaScript-Objekten Funktionen zuweisen, anstatt den Attributen von
    // HTML-Elementen Strings zuzuweisen.
    function definiereHandler(f) {
      // Durchlaufe alle Elemente des Formulars
      for(var i = 0; i < f.elements.length; i++) {
        var e = f.elements[i];
        e.onclick = function( ) { protokolliere(this, 'Click'); }
        e.onchange = function( ) { protokolliere(this, 'Change'); }
        e.onfocus = function( ) { protokolliere(this, 'Focus'); }
        e.onblur = function( ) { protokolliere(this, 'Blur'); }
        e.onselect = function( ) { protokolliere(this, 'Select'); }
      }

      // Definiere spezielle Handler für die drei Buttons:
      f.loeschbutton.onclick = function( ) {
        this.form.textarea.value=''; protokolliere(this,'Click');
      }
      f.submitbutton.onclick = function ( ) {
        protokolliere(this, 'Click'); return false;
      }
      f.resetbutton.onclick = function( ) {
        this.form.reset( ); protokolliere(this, 'Click'); return false;
      }
    }
    // Wir aktivieren das Formular, indem wir alle möglichen Handler definieren!
    definiereHandler(document.alles);
    </script>

# 18.3 Formularelemente skripten

Im letzten Abschnitt wurden alle von HTML bereitgestellten Formularelemente beschrieben, und es wurde gezeigt, wie sie in HTML-Dokumente eingebettet werden. Dieser Abschnitt geht einen Schritt weiter und zeigt, wie Sie mit diesen Elementen in Ihren JavaScript-Programmen arbeiten können.

## 18.3.1 Formulare und Formularelemente benennen

Jedes Formularelement besitzt ein `name`-Attribut, das im HTML-Tag definiert werden muss, wenn das Formular an ein serverseitiges Programm übergeben werden soll. Wenn auch das Versenden von Formularen für JavaScript-Programme nicht unbedingt fürchterlich interessant ist, gibt es einen anderen Grund dafür, dieses `name`-Attribut anzugeben, wie wir bald sehen werden.

Das `<form>`-Tag besitzt ebenfalls ein Attribut `name`, das Sie angeben können. Dieses Attribut hat nichts mit dem Verschicken von Formularen zu tun; wie bereits in Kapitel 15 beschrieben, existiert es, um JavaScript-Programmierern die Arbeit zu erleichtern. Wird ein benanntes Formular erzeugt, indem für ein `<form>`-Tag das `name`-Attribut vergeben wird, so wird dieses wie gewöhnlich im Array `forms]` des Document-Objekts gespeichert. Zusätzlich wird jedoch eine neue Eigenschaft des Document-Objekts erzeugt, die den durch das `name`-Attribut angegebenen Namen erhält. In [Beispiel 18-1 wurde beispielsweise ein Formular mit diesem Tag definiert:

    <form name="alles">

Dadurch können Sie wie folgt auf das Formular zugreifen:

    document.alles

Dies ist häufig angenehmer als die Array-Schreibweise:

    document.forms[0]

Außerdem wird Ihr Programm durch die Benennung des Formulars positionsunabhängig: Es funktioniert auch, wenn das Dokument umstrukturiert wird und die enthaltenen Formulare dadurch in eine andere Reihenfolge gebracht werden.

Tags wie `<img>` und `<applet>` besitzen ebenfalls alle ein Attribut `name`, das in der gleichen Weise funktioniert wie das Attribut `name` des `<form>`-Tags. Diese Art der Benennung geht bei Formularen jedoch noch einen Schritt weiter, da alle in Formularen enthaltenen Elemente ein `name`-Attribut besitzen. Wenn Sie einem Formularelement einen Namen zuweisen, erzeugen Sie eine neue Eigenschaft des Form-Objekts, die auf dieses Element verweist. Der Name dieser Eigenschaft entspricht dem Wert des Attributs. Sie können also beispielsweise auf ein Element »plz« in einem Formular namens »adresse« folgendermaßen zugreifen:

    document.adresse.plz

Wenn die Namen vernünftig gewählt werden, ist diese Schreibweise viel eleganter als die Variante, die auf fest eingebaute (und positionsabhängige) Array-Indizes zugreift:

    document.forms[1].elements[4]

Damit in einem HTML-Formular in einer Gruppe von sogenannten _Radio-Buttons_ jeweils nur ein Schalter gedrückt sein kann, müssen diese alle denselben Namen tragen. In Beispiel 18-1 werden beispielsweise drei solcher Schalter definiert, deren Attribut `name` den Wert »browser« trägt. Bei nicht-exklusiven Schaltern, sogenannten Checkboxes, ist eine gleiche Benennung innerhalb einer Gruppe zwar nicht notwendig, aber durchaus üblich. Haben mehrere Elemente in einem Formular das gleiche `name`-Attribut, dann stellt JavaScript diese Elemente einfach in ein Array mit dem angegebenen Namen. Die Elemente stehen in diesem Array in der gleichen Reihenfolge, wie sie im Dokument erscheinen. In Beispiel 18-1 können Sie auf die Radio-Buttons daher wie folgt zugreifen:

    document.alles.browser[0]
    document.alles.browser[1]
    document.alles.browser[2]

## 18.3.2 Eigenschaften von Formularelementen

Alle (oder zumindest die meisten) Formularelemente besitzen die folgenden Eigenschaften. Manche Elemente habe weitere spezielle Eigenschaften, die weiter unten in diesem Kapitel beschrieben werden, genauer gesagt dort, wo die verschiedenen Arten von Formularelementen einzeln behandelt werden:

`type`

Ein nur-lesbarer String, der den Typ des Formularelements angibt. Die dritte Spalte in Tabelle 18-1 führt für jedes Formularelement den Wert dieser Eigenschaft auf.

`form`

Eine nur-lesbare Referenz auf das Form-Objekt, in dem das Element enthalten ist.

`name`

Ein nur-lesbarer String, der durch das HTML-Attribut `name` angegeben wurde.

`value`

Ein les- und schreibbarer String, der den im Formularelement enthaltenen bzw. von ihm dargestellten Wert enthält. Dieser String wird an den Webserver übergeben, wenn das Formular abgeschickt wird; er ist nur manchmal für JavaScript-Programme interessant. Bei Text- und Textarea-Elementen enthält diese Eigenschaft den vom Benutzer eingegebenen Text. Bei Buttons gibt sie den Text an, der auf dem Button angezeigt werden soll; manchmal werden Sie diesen Text durch ein Skript ändern wollen. Bei Radio- und Checkbox-Elementen dagegen wird die `value`-Eigenschaft im Allgemeinen nicht bearbeitet und dem Benutzer auch nicht angezeigt. Sie enthält einfach nur einen String, der durch das HTML-Attribut `value` angegeben wird; dies wird an den Webserver übergeben, wenn das Dokument abgeschickt wird. Die `value`-Eigenschaft wird in den Abschnitten über die unterschiedlichen Kategorien von Formularelementen weiter unten in diesem Kapitel ausführlicher besprochen.

## 18.3.3 Event-Handler von Formularelementen

Die meisten Formularelemente unterstützen fast alle der folgenden Event-Handler:

`onclick`

Wird ausgelöst, wenn der Benutzer das Element mit der Maus anklickt. Dieser Event-Handler ist insbesondere für Buttons und verwandte Formularelemente nützlich.

`onchange`

Wird ausgelöst, wenn der Benutzer den von dem Element dargestellten Wert beispielsweise durch Eingabe von Text oder Auswahl einer Option verändert. Buttons und ähnliche Elemente unterstützen diesen Event-Handler üblicherweise nicht, da sie keinen bearbeitbaren Wert besitzen. Beachten Sie, dass dieser Handler nicht sofort aufgerufen wird, wenn der Benutzer z.B. ein Zeichen in ein Textfeld eingibt. Er wird erst dann ausgelöst, wenn der Benutzer den Wert eines Elements ändert und anschließend den Eingabefokus an ein anderes Formularelement weitergibt. Der Aufruf dieses Event-Handlers signalisiert also eine abgeschlossene Änderung.

`onfocus`

Wird ausgelöst, wenn das Formularelement den Eingabefokus erhält.

`onblur`

Wird ausgelöst, wenn das Formularelement den Eingabefokus verliert.

Beispiel 18-1 definierte Event-Handler für Formularelemente, indem die Event-Handler-Eigenschaft auf eine JavaScript-Funktion gesetzt wurde. Das Beispiel ist so ausgelegt, dass auftretende Events in einem großen Textarea-Element protokolliert werden. Sie können mit diesem Formular experimentieren, um herauszufinden, welches Formularelement wann welchen Event-Handler auslöst.

Wichtig ist, dass sich das Schlüsselwort `this` innerhalb des Codes eines Event-Handlers auf das Element eines Dokuments bezieht, das das Event ausgelöst hat. Da jedes Formularelement eine Eigenschaft `form` besitzt, die sich auf das umgebende Formular bezieht, können die Event-Handler eines Elements immer mit `this.form` auf das Form-Objekt zugreifen. Einen Schritt weiter gedacht bedeutet dies, dass ein Formularelement auf ein benachbartes Formularelement namens `x` mit dem Ausdruck `this.form.x` zugreifen kann.

Beachten Sie, dass die vier genannten Event-Handler für Formularelemente die für diese Elemente wichtigsten sind. Darüber hinaus unterstützen Formularelemente weitere Event-Handler (wie beispielsweise `onmousedown`), die von nahezu allen HTML-Elementen unterstützt werden. In Kapitel 17 werden Events und Event-Handler in allen Einzelheiten beschrieben. Beispiel 17-5 in diesem Kapitel zeigt die Verwendung von Tastatur-Event-Handlern mit Formularelementen.

## 18.3.4 Button-Schaltflächen

Das Button-Formularelement ist eines der am häufigsten verwendeten Elemente, da es dem Benutzer deutlich zeigt, dass er hiermit eine Aktion auslösen kann. Das Button-Element besitzt zunächst kein eigenes vordefiniertes Verhalten und ist daher in einem Formular nur dann von Nutzen, wenn es einen `onclick`-Event-Handler (oder einen anderen) besitzt. Die Eigenschaft `value` eines Button-Elements gibt an, welcher Text auf dem Button erscheinen soll. Sie können dieser Eigenschaft einen Wert (nur einfachen Text, kein HTML) zuweisen, um den Wert zu ändern, der auf dem Button angezeigt werden soll. Dies kann mitunter nützlich sein.

Beachten Sie, dass Hypertext-Verweise ebenfalls den Event-Handler `onclick` unterstützen und jedes Button-Objekt durch einen Verweis ersetzt werden kann, der die gleiche Aktion auslöst, wenn er angeklickt wird. Verwenden Sie einen Button, wenn das Element wie ein solcher aussehen soll; einen Link sollten Sie verwenden, wenn sich der Benutzer die durch den `onclick`-Handler ausgelöste Aktion eher wie das Verfolgen eines Links vorstellen soll.

Die Elemente Submit und Reset gleichen Button-Elementen, besitzen jedoch vordefinierte Aktionen, nämlich das Absenden bzw. Zurücksetzen eines Formulars. Daher können diese Elemente auch ohne einen `onclick`-Event-Handler nützlich sein. Andererseits sind sie genau aus diesem Grund in Formularen, die an einen Webserver geschickt werden sollen, hilfreicher als in reinen clientseitigen JavaScript-Programmen. Wenn der Event-Handler `onclick` den Wert `false` zurückgibt, wird die vordefinierte Aktion nicht ausgeführt. Sie können den `onclick`-Handler eines Submit-Elements verwenden, um ein Formular vor dem Versenden auf Korrektheit zu überprüfen; es ist jedoch üblich, diese Aufgabe vom `onsubmit`-Handler des Form-Objekts erledigen zu lassen.

Sie können Button-Schaltflächen, einschließlich **Submit** \- und **Reset** -Buttons, mit dem Tag `<button>` statt des traditionellen `<input>`-Tags erstellen. Das `<button>`-Tag ist flexibler, da es nicht nur einfachen Text anzeigen kann, der als Wert des Attributs `value` angegeben wird, sondern auch beliebigen HTML-Inhalt (formatierten Text sowie Bilder), der zwischen den Tags `<button>` und `</button>` angegeben wird. Das `<button>`-Tag kann an einer beliebigen Stelle in einem HTML-Dokument verwendet werden und muss nicht in einem `<form>`-Tag stehen.

Die durch das `<button>`-Tag erzeugten Buttons unterscheiden sich technisch von solchen, die durch das Tag `<input>` erzeugt wurden, besitzen jedoch im Feld `type` denselben Wert und verhalten sich auch ansonsten weitestgehend gleich. Der Hauptunterschied besteht darin, dass Sie das Aussehen eines mit dem `<button>`-Tag erzeugten Buttons nicht durch Zuweisung eines Werts an die Eigenschaft `value` ändern können, da das Attribut `value` für das Aussehen des Buttons keine Rolle spielt.

Teil IV enthält keinen Eintrag für Button. Einzelheiten zu allen Formularelement-Schaltflächen, einschließlich derjenigen, die mit dem `<button>`-Tag erzeugt werden, finden Sie unter dem Eintrag Input.

## 18.3.5 Schalter

Die Elemente Checkbox und Radio sind Schalter, die zwei visuell unterscheidbare Zustände besitzen: Sie können ausgewählt sein oder nicht. Der Benutzer kann den Zustand eines solchen Schalters ändern, indem er ihn anklickt. Radio-Elemente sind dazu gedacht, in Gruppen verwandter Elemente benutzt zu werden, deren HTML-Attribut `name` denselben Wert besitzt. Auf diese Weise erzeugte Radio-Elemente schließen sich gegenseitig aus, d.h., es kann immer nur eines von ihnen ausgewählt sein – ähnlich den Stationstasten eines Radios. Checkbox-Elemente werden ebenfalls häufig in Gruppen verwendet, die ein gemeinsames Attribut `name` besitzen. Wenn Sie auf diese Elemente über ihren Namen zugreifen, müssen Sie daran denken, dass Sie auf ein Array gleichnamiger Elemente zugreifen. Beispiel 18-1 enthält drei Checkbox-Elemente mit dem Namen »extras«. Sie können auf ein Array mit diesen Elementen folgendermaßen zugreifen:

    document.alles.extras

Um auf ein einzelnes Checkbox-Element zu verweisen, indizieren Sie das Array:

    document.alles.extras[0] // Das erste Formularelement names "extras"

Sowohl Radio- als auch Checkbox-Elemente definieren eine Eigenschaft `checked`. Dies ist ein les- und schreibbarer Boolescher Wert, der angibt, ob das Element derzeit ausgewählt ist. Die Eigenschaft `defaultChecked` ist ein Boolescher Wert; sie enthält den Wert des HTML-Attributs `checked` und gibt an, ob das Element beim erstmaligen Laden der Seite ausgewählt ist.

Radio- und Checkbox-Elemente zeigen selbst keinen Text an; sie werden meist neben HTML-Text gesetzt (oder mit einem `<label>`-Tag versehen). Daher können Sie das Aussehen eines Checkbox- oder Radio-Elements nicht dadurch ändern, dass Sie seine `value`-Eigenschaft setzen. Das geht nur bei Button-Elementen, die mit einem `<input>`-Tag erzeugt wurden. Sie können `value` einen Wert zuweisen; dadurch ändern Sie jedoch nur den String, der beim Abschicken des Formulars an den Webserver versandt wird.

Wenn der Benutzer einen solchen Schalter anklickt, löst das Radio- oder Checkbox-Element seinen Event-Handler `onclick` aus, um das JavaScript-Programm von der Zustandsänderung zu benachrichtigen. Neuere Webbrowser lösen bei diesen Elementen außerdem den `onchange`-Handler aus. Beide Event-Handler stellen also die gleiche Information bereit; der `onclick`-Handler funktioniert jedoch auch in älteren Browsern.

## 18.3.6 Textfelder

Das Text-Element ist vermutlich das am häufigsten in HTML-Formularen und JavaScript-Programmen verwendete Element. Es ermöglicht dem Benutzer die Eingabe eines kurzen, einzeiligen Texts. Die `value`-Eigenschaft stellt den vom Benutzer eingegebenen Text dar. Sie können dieser Eigenschaft einen Wert zuweisen, der in dem Feld angezeigt werden soll. Der Event-Handler `onchange` wird ausgelöst, wenn der Benutzer neuen Text eingibt oder den vorhandenen Text bearbeitet und das Ende der Bearbeitung dadurch anzeigt, dass er den Eingabefokus aus dem Textfeld entfernt.

Das Element Textarea ähnelt dem Text-Element, ermöglicht dem Benutzer jedoch die Eingabe von Text in mehreren Zeilen (und Ihrem JavaScript-Programm entsprechend die Ausgabe von mehrzeiligem Text). Textarea-Elemente werden mit dem Tag `<textarea>` erzeugt. Die Syntax dieses Tags unterscheidet sich wesentlich von der des `<input>`-Tags, das Text-Elemente erzeugt. (Vergleiche den Textarea-Eintrag in Teil IV.) Trotzdem verhalten sich die beiden Elementtypen recht ähnlich. Sie können die `value`-Eigenschaft und den `onchange`-Event-Handler eines Textarea-Elements genauso verwenden, wie Sie dies bei einem Text-Element tun.

Das Element Password ist ein modifiziertes Text-Element, das Sterne anzeigt, wenn der Benutzer hier etwas eingibt. Wie der Name andeutet, ist dieser Typ nützlich, wenn der Benutzer ein Passwort eingeben soll, ohne dass jemand dieses vom Bildschirm ablesen kann. Beachten Sie, dass das Password-Element die Eingabe des Benutzers vor neugierigen Augen verbirgt; wenn das Formular jedoch verschickt wird, werden diese Daten in keiner Weise verschlüsselt (es sei denn, sie werden über eine sichere HTTPS-Verbindung versandt), sodass sie während der Übertragung ausspioniert werden können.

Das File-Element schließlich dient dazu, den Benutzer den Namen einer Datei eingeben zu lassen, die auf einen Webserver übertragen werden soll. Es handelt sich dabei im Wesentlichen um ein Text-Element, das mit einem Button kombiniert wurde, über den ein Dialog zur Dateiauswahl geöffnet wird. File besitzt wie Text-Elemente einen Event-Handler `onchange`. Im Gegensatz zu Text-Elementen kann die `value`-Eigenschaft eines file-Elements jedoch nur gelesen werden. Dadurch wird verhindert, dass bösartige JavaScript-Programme den Benutzer dazu bringen, Dateien zu übertragen, die nicht für die Öffentlichkeit bestimmt sind.

Das W3C hat bisher noch keinen Standard für Event-Handler oder Event-Objekte für die Tastatureingabe definiert. Trotzdem definieren moderne Browser die Event-Handler `onkeypress`, `onkeydown` und `onkeyup`. Diese Handler können für beliebige Document-Objekte angegeben werden, sind aber am nützlichsten, wenn sie bei Text- und ähnlichen Formularelementen angegeben werden, die tatsächlich Tastatureingaben akzeptieren. Die Event-Handler `onkeypress` und `onkeydown` können den Wert `false` zurückgeben, damit die vom Benutzer gedrückte Taste nicht angezeigt wird. Das kann z.B. dann sinnvoll sein, wenn Sie den Benutzer zwingen wollen, nur Zahlen in ein bestimmtes Texteingabefeld einzugeben. Beispiel 17-5 veranschaulicht diese Technik.

## 18.3.7 Select- und Option-Elemente

Das Select-Element stellt eine (durch Option-Elemente repräsentierte) Auswahlliste dar, aus der sich der Benutzer bedienen kann. Die meisten Browser zeigen Select-Elemente in einem Menü oder einer Listbox an. Ein Select-Element kann eine von zwei sehr unterschiedlichen Funktionen wahrnehmen, die durch den Wert der Eigenschaft `type` festgelegt wird. Wenn das `<select>`-Tag das `multiple`-Attribut besitzt, kann der Benutzer mehrere Optionen auswählen; die `type`-Eigenschaft besitzt in diesem Fall den Wert »select-multiple«. Ist dieses Attribut nicht vergeben, kann nur ein einzelnes Element gewählt werden; die Eigenschaft `type` besitzt dann den Wert »select-one«.

Ein mit »select-multiple« versehenes Element ähnelt einer Gruppe von Checkbox-Elementen, ein mit »select-one« versehenes Element einer Gruppe von Radio-Elementen. Das Select-Element unterscheidet sich von den Schaltern dadurch, dass ein einzelnes Select-Element eine ganze Menge von Optionen darstellt, die in HTML durch das `<option>`-Tag angegeben werden und in JavaScript durch Option-Objekte repräsentiert werden, die im Array `options[]` des Select-Elements gespeichert sind. Da ein Select-Element eine Auswahl zwischen verschiedenen Werten ermöglicht, besitzt es im Gegensatz zu allen anderen Formularelementen keine Eigenschaft `value`. Wie wir gleich sehen werden, definieren vielmehr die im Select-Element enthaltenen Option-Objekte jeweils eine Eigenschaft `value`.

Wenn der Benutzer eine Option auswählt oder abwählt, löst das Select-Element seinen Event-Handler `onchange` aus. Bei mit »select-one« versehenen Elementen gibt die les- und schreibbare `selectedIndex`-Eigenschaft die Nummer der derzeit ausgewählten Option an. Bei mit »select-multiple« versehenen Elementen reicht die einfache Eigenschaft `selectedIndex` nicht aus, um eine ganze Menge von ausgewählten Optionen darzustellen. Um in diesem Fall herausfinden zu können, welche Optionen ausgewählt sind, müssen Sie die Elemente des `options[]`-Arrays durchlaufen und die `selected`-Eigenschaft eines jeden Option-Objekts betrachten.

Zusätzlich zu seiner `selected`-Eigenschaft besitzt das Option-Element eine `text`-Eigenschaft, die den (einfachen) Text angibt, der in dem Select-Element für diese Option angezeigt werden soll. Sie können dieser Eigenschaft einen Wert zuweisen, um den angezeigten Text zu ändern. Die `value`-Eigenschaft ist ebenfalls ein les- und schreibbarer String; er gibt den Text an, der beim Versenden des Formulars an den Webserver geschickt werden soll. Auch wenn Sie ein ausschließlich auf der Clientseite laufendes Programm schreiben und Ihr Formular niemals abgeschickt wird, kann die Eigenschaft `value` (bzw. das ihr entsprechende HTML-Attribut `value`) ein guter Ort für die Speicherung von Daten sein, die Sie benötigen, wenn der Benutzer eine bestimmte Option auswählt. Beachten Sie, dass das Option-Element keine Event-Handler definiert, die sich auf das Formular beziehen; dafür ist der Event-Handler `onchange` des umgebenden Select-Elements zuständig.

Die in einem Select-Element angezeigten Optionen können Sie außer durch Zuweisung eines Werts an die `text`-Eigenschaft eines Option-Objekts auch auf andere Weise dynamisch ändern. Sie können das Array der Option-Elemente kürzen, indem Sie `options. length` auf die gewünschte Anzahl von Optionen setzen, und Sie können alle Option-Objekte entfernen, indem Sie `options.length` auf 0 setzen. Nehmen wir an, Sie haben ein Select-Objekt namens »land« in einem Formular namens »adresse«. Sie können dann alle Optionen dieses Elements mit folgender Programmzeile entfernen:

    document.adresse.land.options.length = 0; // Entferne alle Optionen

Sie können ein einzelnes Option-Objekt aus dem Select-Element entfernen, indem Sie an seine Stelle im Array `options[]` den Wert `null` schreiben. Dadurch wird das Option-Objekt gelöscht; die Optionen, die sich weiter hinten befinden, rutschen nach, um den leeren Platz zu füllen.

    // Entferne ein einzelnes Option-Objekt aus dem Select-Element.
    // Die zuvor in options[11] gespeicherte Option wird nun nach
    // options[10] verschoben ...
    document.adresse.land.options[10] = null;

Vergleichen Sie den Option-Eintrag in Teil IV. Vergleichen Sie ebenfalls `Select.add( )` als Alternative für das Hinzufügen neuer Optionen in DOM Level 2.

Das Option-Element definiert außerdem einen Konstruktor `Option( )`, den Sie verwenden können, um neue Option-Elemente dynamisch zu erzeugen. Diese können Sie an ein Select-Element anhängen, indem Sie sie an das Ende des Arrays `options[]` anfügen: Beispielsweise:

    // Erzeuge ein neues Option-Objekt
    var zaire = new Option("Zaire", // Die Eigenschaft text
                           "zaire", // Die Eigenschaft value
                           false,  // Die Eigenschaft defaultSelected
                           false);  // Die Eigenschaft selected

    // Zeige das Objekt im Select-Element durch Anhängen an das options-Array an:
    var laender = document.adress.land; // Beschaffe das Select-Objekt
    laender.options[laender.options.length] = zaire;

Sie können das Tag `<optgroup>` verwenden, um eine Gruppe zusammenhängender Optionen innerhalb eines Select-Elements zusammenzufassen. Dieses Tag besitzt ein Attribut `label`, das den Text festlegt, der im Select-Element angezeigt werden soll. Obwohl sie angezeigt werden, können `<optgroup>`-Tags nicht vom Benutzer ausgewählt werden; Objekte, die sich auf das `<optgroup>`-Tag beziehen, werden außerdem nicht in das `options[]`-Array eingefügt.

## 18.3.8 Versteckte Elemente

Wie der Name schon sagt, werden versteckte Elemente vom Typ Hidden in Formularen nicht angezeigt. Sie ermöglichen den Versand eines beliebigen Texts an den Server und werden von serverseitigen Programmen dazu verwendet, Zustandsinformationen zu speichern, die sie mit dem Formular zurückgeschickt bekommen. Da versteckte Elemente nicht angezeigt werden, können sie keine Events erzeugen und besitzen keine Event-Handler. Mit der Eigenschaft `value` können Sie den zu einem solchen Element gehörigen Text lesen und schreiben; abgesehen davon werden diese Elemente in der clientseitigen JavaScript-Programmierung jedoch selten verwendet.

## 18.3.9 Fieldset-Elemente

Zusätzlich zu den oben beschriebenen aktiven Formularelementen können HTML-Formulare auch `<fieldset>`\- und `<label>`-Tags beinhalten. Diese Tags können für Webdesigner wichtig sein, sind aber unter Skriptsteuerungsgesichtspunkten nicht so interessant, und clientseitige JavaScript-Programmierer verwenden sie normalerweise nicht. Sie müssen sich mit `<fieldset>`-Tags nur auskennen, weil durch die Aufnahme eines solchen Elements in ein Formular ein entsprechendes Objekt zum `elements[]`-Array des Formulars hinzugefügt wird. (Das geschieht bei `<label>`-Tags allerdings nicht). Im Gegensatz zu allen anderen Objekten im `elements[]`-Array hat das Objekt, das das `<fieldset>`-Tag repräsentiert, keine `type`-Eigenschaft. Das kann bei Code, der eine solche Eigenschaft erwartet, problematisch sein.

# 18.4 Ein Beispiel zur Überprüfung von Formularen

Ich schließe die Besprechung von Formularen mit einem ausführlichen Beispiel, das zeigt, wie Sie unauffälliges clientseitiges JavaScript zur Formularüberprüfung verwenden können.66] [Beispiel 18-3, das uns später begegnet, ist ein Modul mit unauffälligem JavaScript-Code, der eine automatische clientseitige Formularüberprüfung ermöglicht. Um das Beispiel zu verwenden, fügen Sie es einfach in Ihre HTML-Seite ein, definieren einen CSS-Style, um die Formularfelder zu markieren, bei denen die Überprüfung fehlschlägt, und fügen dann zusätzliche Attribute zu Ihren Formularfeldern hinzu. Um den Benutzer zum Ausfüllen eines Felds zu zwingen, geben Sie dem Feld einfach ein `required`-Attribut. Um sicherzustellen, dass die Benutzereingabe einem regulären Ausdruck entspricht, setzen Sie das `pattern`-Attribut auf die gewünschte Regexp. Beispiel 18-2 zeigt, wie Sie dieses Modul verwenden können, und Abbildung 18-2 zeigt, was passiert, wenn Sie versuchen, ein Formular mit einer ungültigen Eingabe einzureichen.

Beispiel 18-2. Eine Formularüberprüfung zu Ihren HTML-Formularen hinzufügen

    <script src="Validate.js"></script> <!-- Formularüberprüfungsmodul einbeziehen -->
    <style>
    /*
     * Validate.js verlangt von uns die Definition von Styles für die "invalid"-Klasse, damit
     * ungültige Felder eine auffällige Erscheinung haben, die der Benutzer erkennt.
     * Optional können wir auch Styles für gültige Felder definieren.
     */
    input.invalid { background: #faa; } /* Rötlicher Hintergrund für ungültige Felder */
    input.valid { background: #afa; }  /* Grünlicher Hintergrund für gültige Felder */
    </style>

    <!--
     Zum Überprüfen von Formularfeldern setzen wir einfach die Attribute required oder pattern.
    -->
    <form>
     <!-- Dieses Feld muss einen Wert haben (alles außer Whitespace) -->
     Name: <input type="text" name="name" required><br>
     <!-- \s* bedeutet optionaler Whitespace. \w+ bedeutet ein oder mehrere alphanumerische  Zeichen-->
     E-mail: <input type="text" name="email" pattern="^\s*\w+@\w+\.\w+\s*$"><br>
     <!-- \d{5} heißt genau fünf Ziffern -->
     Postleitzahl: <input type="text" name="plz" pattern="^\s*\d{5}\s*$"><br>
     <!-- keine Überprüfung für dieses Feld -->
     Nicht validiert: <input type="text"><br>
     <input type="submit" value="Abschicken!">
    </form>

Abbildung 18-2. Ein Formular, das durch die Überprüfung fiel

Beispiel 18-3 ist der Code für das Formularüberprüfungsmodul. Durch die Aufnahme dieses Moduls in eine HTML-Datei werden dem globalen Namensraum keine Symbole hinzugefügt. Das Modul registriert automatisch einen `onload`-Event-Handler, der alle Formulare im Dokument durchläuft, nach den Attributen `required` und `pattern` sucht und je nach Bedarf die Handler `onchange` und `onsubmit` hinzufügt. Diese Event-Handler setzen die `className`-Eigenschaft jedes von ihnen überprüften Formularfelds auf »invalid« oder »valid«. Mit CSS sollten Sie zumindest das Erscheinungsbild der »invalid«-Klasse auffällig gestalten.[67]

Beispiel 18-3. Automatische Formularüberprüfung mit unauffälligem JavaScript

    /**
     * Validate.js: Unauffällige HTML-Formularüberprüfung.
     *
     * Beim Ladevorgang des Dokuments scannt dieses Modul das Dokument nach HTML-Formularen und
     * Text-Formularelementen. Findet es Elemente mit "required"- oder
     * "pattern"-Attributen, so fügt es entsprechende Event-Handler für die
     * Formularüberprüfung auf der Clientseite an.
     *
     * Hat ein Formularelement ein "pattern"-Attribut, dann wird der Wert dieses Attributs
     * in JavaScript als regulärer Ausdruck interpretiert und das Element erhält einen
     * onchange-Event-Handler, der die Benutzereingabe mit dem Muster vergleicht.
     * Falls die Eingabe nicht mit dem Muster übereinstimmt, ändert sich die Hintergrundfarbe des
     * Eingabeelements, damit der Benutzer auf den Fehler aufmerksam wird.
     * Als Default muss der Wert des Textfeldes einen Substring enthalten, der mit
     * dem Muster übereinstimmt. Wenn Sie eine genaue Übereinstimmung des ganzen Werts verlangen,
     * verwenden Sie die Anker ^ und $ am Anfang und Ende des Musters.
     *
     * Ein Formularelement mit einem "required"-Attribut muss einen Wert haben.
     * Das Vorhandensein von "required" ist eine Abkürzung für pattern="\S". Das heißt, es wird
     * verlangt, dass der Wert ein beliebiges Zeichen enthält, das kein Whitespace ist.
     *
     * Wenn ein Formularelement die Überprüfung erfolgreich besteht, wird sein "class"-Attribut * auf "valid" gesetzt. Wenn es die Überprüfung nicht besteht, wird seine Klasse auf
     * "invalid" gesetzt.
     * Damit dieses Modul sinnvoll ist, müssen Sie es in Kombination mit
     * einem CSS-Stylesheet verwenden, das Styles für die "invalid"-Klasse definiert.
     * Beispielsweise:
     *
     *  <!-- auffälliger orangefarbener Hintergrund für ungültige Formularelemente -->
     *  <style>input.invalid { background: #fa0; }</style>
     *
     * Wird ein Formular abgesendet, werden die zu überprüfenden Textfeld-Elemente
     * noch einmal überprüft. Wenn die Überprüfung fehlschlägt, wird die Übermittlung blockiert,
     * und dem Benutzer wird ein Dialogfeld angezeigt, das über die Unvollständigkeit oder
     * Unrichtigkeit des Formulars informiert.
     *
     * Sie können das Modul nicht für die Überprüfung von Formularfeldern oder Formularen
     * verwenden, an denen Sie Ihre eigenen onchange- oder onsubmit-Event-Handler definieren,
     * oder an Feldern, für die Sie ein class-Attribut definieren.
     *
     * Dieses Modul setzt seinen gesamten Code in eine anonyme Funktion und
     * definiert keine Symbole im globalen Namensraum.
     */
    (function( ) { // Mache alles in dieser einen anonymen Funktion
      // Rufe nach dem Laden init( ) auf
      if (window.addEventListener) window.addEventListener("load", init, false);
      else if (window.attachEvent) window.attachEvent("onload", init);

      // Definiere Event-Handler für die Formulare und Formularelemente, die sie brauchen.
      function init( ) {
        // Durchlaufe alle Formulare im Dokument
        for(var i = 0; i < document.forms.length; i++) {
          var f = document.forms[i]; // das Formular, an dem wir gerade arbeiten

          // Nehmen wir zunächst an, dass das Formular keine Überprüfung braucht
          var needsValidation = false;

          // Durchlaufe jetzt die Elemente in unserem Formular
          for(j = 0; j < f.elements.length; j++) {
            var e = f.elements[j]; // das Element, das wir gerade bearbeiten

            // Uns interessieren nur <input type="text">-Textfelder
            if (e.type != "text") continue;

            // Prüfe, ob es Attribute hat, die validiert werden müssen
            var pattern = e.getAttribute("pattern");
            // Wir könnten e.hasAttribute( ) verwenden, aber IE unterstützt das nicht
            var required = e.getAttribute("required") != null;

            // Required ist eine Abkürzung für ein einfaches Muster
            if (required && !pattern) {
              pattern = "\\S";
              e.setAttribute("pattern", pattern);
            }

            // Wenn dieses Element überprüft werden muss,
            if (pattern) {
              // dann überprüfe das Element nach jeder Änderung
              e.onchange = validateOnChange;
              // Denke daran, einen onsubmit-Handler zu diesem Formular hinzuzufügen
              needsValidation = true;
            }
          }

          // Wenn mindestens eines der Formularelemente überprüft werden muss,
          // brauchen wir auch einen onsubmit-Event-Handler für das Formular
          if (needsValidation) f.onsubmit = validateOnSubmit;
        }
      }

      // Diese Funktion ist der onchange-Event-Handler für Textfelder, die
      // überprüft werden müssen. Beachte, dass wir das required-Attribut
      // in init( ) zu einem pattern-Attribut konvertiert haben.
      function validateOnChange( ) {
        var textfield = this;              // das Textfeld
        var pattern = textfield.getAttribute("pattern"); // das Muster
        var value = this.value;             // die Benutzereingabe

        // Stimmt dieser Wert nicht mit dem Muster überein, setze die Klasse zu "invalid".
        if (value.search(pattern) == −1) textfield.className = "invalid";
        else textfield.className = "valid";
      }

      // Diese Funktion ist der onsubmit-Event-Handler für ein beliebiges Formular,
      // das überprüft werden muss.
      function validateOnSubmit( ) {
        // Bei Übermittlung des Formulars überprüfen wir alle Felder im
        // Formular noch einmal und prüfen über ihre classNames, ob sie ungültig sind.
        // Wenn ungültige Felder vorhanden sind, zeige eine Warnung an, und verhindere
        // die Übermittlung des Formulars.
        var invalid = false; // Beginne mit der Annahme, dass alle Felder gültig sind
        // Durchlaufe alle Formularelemente
        for(var i = 0; i < this.elements.length; i++) {
          var e = this.elements[i];
          // Wenn das Element ein Textfeld ist und unseren onchange-Handler hat,
          if (e.type == "text" && e.onchange == validateOnChange) {
            e.onchange( ); // dann rufe den Handler zur erneuten Überprüfung auf
            // Wenn das Element durchfällt, scheitert die gesamte Formularüberprüfung
            if (e.className == "invalid") invalid = true;
          }
        }

        // Bei ungültigem Formular Benutzer warnen und Übermittlung blockieren
        if (invalid) {
          alert("Das Formular ist unvollständig oder falsch ausgefüllt.\n" +
             "Bitte korrigieren Sie die markierten Felder, und versuchen Sie es noch
             einmal.");
          return false;
        }
      }
    })( );

* * *

[66] Denken Sie daran, dass die Formularüberprüfung auf der Clientseite nur der Bequemlichkeit der Benutzer dient: Durch sie können Eingabefehler festgestellt werden, bevor ein Formular an den Server übergeben wird. Es gibt aber keine Garantie dafür, dass die Validierung auf der Clientseite stattfindet, da manche Benutzer JavaScript deaktiviert haben. Außerdem kann ein böswilliger Benutzer die Überprüfung auf der Clientseite auf einfache Art untergraben. Aus diesen Gründen ist die Validierung auf der Clientseite nie ein Ersatz für eine robuste Überprüfung auf der Serverseite.

[67] Als dieses Buch geschrieben wurde, behinderte das AutoFill-Feature der Google-Werkzeugleiste die Verwendung von CSS-Styles zum Setzen des Hintergrunds bestimmter Textfelder. Die Browsererweiterung von Google setzt den Hintergrund des Textfeldes auf Hellgelb, um anzuzeigen, dass Werte eventuell automatisch in das Feld eingegeben werden können.

# Kapitel 19. Cookies und Persistenz auf der Clientseite

Das Document-Objekt besitzt eine Eigenschaft namens `cookie`, die wir in Kapitel 15 nicht betrachtet haben. Oberflächlich gesehen scheint diese Eigenschaft ein einfacher Stringwert zu sein; die `cookie`-Eigenschaft ist aber viel mehr als das: Sie versetzt JavaScript-Code in die Lage, Daten dauerhaft auf der Benutzerfestplatte zu speichern und vorher auf diese Weise gespeicherte Daten wieder abzurufen. Persistenz auf der Clientseite ist eine einfache Möglichkeit, Webanwendungen ein Gedächtnis zu geben: Beispielsweise kann eine Webseite Benutzereinstellungen speichern, die wiederverwendet werden können, wenn der Benutzer die Seite erneut besucht.

Cookies werden außerdem für serverseitige Skripten verwendet und sind eine standardisierte Erweiterung des HTTP-Protokolls. Alle modernen Webbrowser unterstützen Cookies und lassen sie über die `Document.cookie`-Eigenschaft per Skript steuern. Neben den Cookies gibt es weitere Persistenzmechanismen auf der Clientseite, die leistungsfähiger, aber weniger weit verbreitet sind. Diese bespreche ich kurz am Ende dieses Kapitels.

# 19.1 Ein Überblick über Cookies

Ein _Cookie_ ist eine kleine benannte Datenmenge, die vom Webbrowser gespeichert wird und einer bestimmten Webseite oder -site zugeordnet ist.[68] Cookies dienen dazu, dem Webbrowser eine Art Gedächtnis zu geben, sodass Skripten und serverseitige Programme Daten, die auf einer Seite eingegeben wurden, auch auf einer anderen Webseite verwenden können. Sie ermöglichen auch das Speichern und Abrufen von Voreinstellungen des Benutzers oder von anderen Zustandsvariablen, wenn der Benutzer eine Seite verlässt und später zu ihr zurückkehrt. Cookies wurden ursprünglich für die Programmierung auf der Serverseite entwickelt und sind auf der untersten Ebene als Erweiterung des HTTP-Protokolls implementiert. Die Daten eines Cookies werden automatisch zwischen Webbrowser und Webserver ausgetauscht, sodass Skripten auf der Serverseite auf die Werte von Cookies, die auf der Clientseite gespeichert sind, lesend und schreibend zugreifen können. Wie Sie im Folgenden sehen werden, kann auch JavaScript Cookies lesen und schreiben, und zwar über die Eigenschaft `cookie` des Document-Objekts.

`cookie` ist eine String-Eigenschaft, die es Ihnen ermöglicht, Cookies, die sich auf die aktuelle Webseite beziehen, zu lesen, zu erzeugen, zu verändern und zu löschen. Obwohl `cookie` zunächst wie eine normale les- und schreibbare String-Eigenschaft aussieht, ist das Verhalten dieser Eigenschaft tatsächlich komplexer. Wenn Sie den Wert von `cookie` lesen, erhalten Sie einen String, der die Namen und Werte aller Cookies enthält, die das Dokument betreffen. Ein einzelner Cookie wird erzeugt, geändert oder gelöscht, indem der Wert der `cookie`-Eigenschaft mit einer speziellen Syntax gesetzt wird. In den folgenden Abschnitten dieses Kapitels wird genau erklärt, wie dies funktioniert. Um allerdings die Eigenschaft `cookie` verwenden zu können, benötigen Sie zunächst einige Informationen über Cookies und ihre Funktionsweise.

Außer einem Namen und einem Wert besitzt jeder Cookie optionale Attribute, die seine Lebensdauer, seine Sichtbarkeit und den Sicherheitsgrad festlegen. Cookies sind normalerweise vergänglich; die gespeicherten Werte bleiben für die Dauer der Webbrowser-Sitzung erhalten, werden aber gelöscht, sobald der Benutzer den Browser beendet. Wenn Sie einen Cookie über die Dauer einer einzelnen Browsersitzung hinaus erhalten wollen, müssen Sie dem Browser sagen, wie lange er den Cookie aufbewahren soll. Die ursprüngliche Vorgehensweise dafür war das Setzen des `expires`-Attributs auf ein Verfallsdatum in der Zukunft. Das `expires`-Attribut funktioniert zwar weiterhin, wurde aber vom `max-age`-Attribut abgelöst, das die Lebensdauer des Cookies in Sekunden angibt. Durch das Festlegen eines der beiden Attribute speichert der Browser den Cookie in einer lokalen Datei und kann dadurch den Cookie in zukünftigen Browsersitzungen abrufen, wenn der Benutzer die Webseite erneut besucht. Wenn das Verfallsdatum abgelaufen ist oder die `max-age`-Lebensdauer erreicht wird, löscht der Browser den Cookie automatisch aus seiner Cookie-Datei.

Ein weiteres wichtiges Cookie-Attribut ist `path`; durch dieses Attribut wird angegeben, mit welchen Webseiten der Cookie verknüpft ist. Normalerweise ist ein Cookie der Seite zugeordnet, von der er erzeugt wurde. Er ist auch nur für diese sowie für alle anderen Seiten im selben Verzeichnis und in dessen Unterverzeichnissen zugänglich. Wenn beispielsweise die Webseite <http://www.beispiel.de/katalog/index.html> einen Cookie erzeugt, ist dieser auch für die Seiten <http://www.beispiel.de/katalog/bestellung.html> und <http://www.beispiel.de/katalog/spielzeug/index.html> sichtbar, nicht aber für <http://www.beispiel.de/info.html>.

Dieses Verhalten ist häufig genau das, was Sie benötigen. Manchmal werden Sie jedoch einen Cookie auch auf allen Seiten einer Website verwenden wollen, unabhängig davon, welche Seite den Cookie erzeugt hat. Wenn beispielsweise der Benutzer seine Mail-Adresse in ein Formular auf einer Webseite einträgt, könnten Sie diese Adresse speichern und als Voreinstellung verwenden, sobald der Benutzer später auf diese Seite zurückkehrt, aber auch, wenn er in einem Formular auf einer anderen Seite gebeten wird, seine Adresse anzugeben. Um diese Verwendung zu ermöglichen, können Sie für einen Cookie einen `path` angeben. Dann können alle Webseiten, die auf diesem Server liegen und deren URL den angegebenen Pfad enthält, gemeinsam auf diesen Cookie zugreifen. Wird z.B. der Pfad eines Cookies von der Seite <http://www.beispiel.de/katalog/spielzeug/index.html> auf den Wert »/katalog« gesetzt, so ist der Cookie auch für die Seite <http://www.beispiel.de/katalog/bestellung.html> sichtbar. Wenn der Pfad auf den Wert »/« gesetzt wird, ist der Cookie für alle Seiten auf dem Webserver www.beispiel.de sichtbar.

Normalerweise können nur Seiten auf einen Cookie zugreifen, die auf demselben Webserver gespeichert sind wie die Seite, die den Cookie gesetzt hat. Größere Sites werden jedoch mitunter auch gemeinsam auf mehreren Servern die gleichen Cookies benutzen wollen. Beispielsweise könnte der Server _bestellung.beispiel.de_ Cookies lesen wollen, die vom Server _katalog.beispiel.de_ definiert wurden. An dieser Stelle kommt das nächste Cookie-Attribut, `domain`, ins Spiel. Wenn ein Cookie von einer Seite auf _katalog. beispiel.de_ sein `path`-Attribut auf den Wert »/« und sein `domain`-Attribut auf den Wert »beispiel.de« setzt, ist der Cookie für alle Seiten auf den Servern _katalog.beispiel.de_ , _bestellung.beispiel.de_ und auf allen anderen Servern in der Domäne _beispiel.de_ verfügbar. Wenn das Attribut `domain` für einen Cookie nicht gesetzt ist, wird als Voreinstellung der Hostname des Webservers verwendet, von dem die Seite stammt. Beachten Sie, dass Sie die Domäne eines Cookies nur auf die Domäne Ihres eigenen Servers setzen können.

Das letzte Cookie-Attribut ist ein Boolesches Attribut namens `secure`. Es gibt an, wie die Werte des Cookies über das Netzwerk übertragen werden. Normalerweise sind Cookies unsicher, das heißt, dass sie über eine normale, unsichere HTTP-Verbindung übertragen werden. Wenn ein Cookie als sicher gekennzeichnet ist, wird er jedoch nur übertragen, wenn Browser und Server über HTTPS oder ein anderes sicheres Protokoll miteinander verbunden sind.

Beachten Sie, dass die Attribute `expires`, `max-age`, `path`, `domain` und `secure` eines Cookies keine Objekteigenschaften in JavaScript sind. Wir werden weiter unten in diesem Kapitel sehen, wie Sie diesen Attributen Werte zuweisen.

Cookies haben bei vielen Webanwendern einen schlechten Ruf, und zwar aufgrund der hemmungslosen Verwendung von Cookies, die von Drittparteien stammen, d.h. von Cookies, die nicht mit der Webseite selbst, sondern mit den Bildern einer Seite assoziiert sind. Mit Cookies von Drittparteien können Hosting-Firmen zum Beispiel den Benutzer von einer Clientseite zur nächsten verfolgen. Aufgrund der damit verbundenen Datenschutzprobleme deaktivieren manche Benutzer Cookies in ihren Webbrowsern. Vor der Verwendung von Cookies in Ihrem JavaScript-Code empfiehlt es sich, zuerst zu überprüfen, ob sie auch aktiviert sind. Bei den meisten Browsern machen Sie das durch die Überprüfung der `navigator.cookieEnabled`-Eigenschaft. Steht sie auf `true`, dann sind Cookies aktiviert. Steht sie auf `false`, so sind Cookies deaktiviert (obwohl nicht-persistente Cookies, die nur während der aktuellen Sitzung gelten, trotzdem aktiviert sein können). Das ist keine standardisierte Eigenschaft. Wenn Sie feststellen, dass sie in dem Browser, in dem Ihr Code läuft, nicht definiert ist, müssen Sie die Unterstützung für Cookies prüfen, indem Sie versuchen, einen Test-Cookie zu schreiben, zu lesen und zu löschen. Weiter unten im Kapitel erkläre ich die dazu nötige Vorgehensweise, und Beispiel 19-2 enthält Code zum Testen der Cookie-Unterstützung.

Wenn Sie sich für die vollständigen technischen Einzelheiten zur Funktionsweise von Cookies (auf HTTP-Protokoll-Ebene) interessieren, sollten Sie RFC 2965 unter <http://www.ietf.org/rfc/rfc2965.txt> zurate ziehen. Cookies wurden ursprünglich von Netscape erfunden, und die Original-Cookie-Spezifikation von Netscape ist immer noch interessant. Obwohl Teile davon inzwischen überholt sind, ist sie um einiges kürzer und leichter zu lesen als der formale RFC. Dieses alte Dokument steht unter <http://wp.netscape.com/newsref/std/cookie_spec.html>.

Die folgenden Abschnitte erläutern, wie Sie in JavaScript die Werte von Cookies zuweisen und abfragen können und wie das Verfallsdatum, der Pfad, die Domäne sowie das Sicherheitsniveau eines Cookies angegeben werden können. Daran schließt sich ein Abschnitt über Cookie-Alternativen an.

* * *

[68] Der Name »Cookie« besitzt keine tiefere Bedeutung. Er ist aber auch keine Neuschöpfung aus »modernen JavaScript-Zeiten«. In der Frühzeit der Computer-Geschichte bezeichnete der Begriff »Cookie« oder »Magic Cookie« ein kleines Bröckchen von Daten (insbesondere von vertraulichen oder geheimen Daten ähnlich einem Passwort), das zur Identifikation dient oder den Zugang erlaubt. In JavaScript werden Cookies verwendet, um Zustände zu speichern und eine Art von »Identifikation« für einen Webbrowser bereitzustellen. Cookies in JavaScript verwenden allerdings keine Kryptografie und sind auf keine Weise abgesichert (obwohl für ihre Übertragung eine verschlüsselte `https:`-Verbindung hilfreich ist).

# 19.2 Cookies speichern

Um einem Dokument einen vergänglichen Cookie zuzuordnen, kann der `cookie`-Eigenschaft einfach in der folgenden Form ein String zugewiesen werden:

     _name_ = _wert_

Beispielsweise:

    document.cookie = "version=" + encodeURIComponent(document.lastModified);

Wenn Sie die Eigenschaft `cookie` später abfragen, wird das entsprechende Paar aus Namen und Wert in die Liste der Cookies für dieses Dokument aufgenommen. Der Wert eines Cookies darf keine Semikola, Kommata und Leerzeichen enthalten. Daher sollten Sie die globale `encodeURIComponent( )`-Funktion des JavaScript-Sprachkerns zum Codieren des Werts benutzen, bevor Sie ihn im Cookie speichern. Wenn Sie dies tun, müssen Sie die Umkehrfunktion `decodeURIComponent( )` verwenden, um den Wert des Cookies lesen zu können. (Die älteren Funktionen `escape( )` und `unescape( )` werden auch häufig verwendet, sind inzwischen aber veraltet).

Ein Cookie, der mit einem einfachen Name/Wert-Paar beschrieben ist, bleibt für die Dauer der aktuellen Websitzung bestehen, wird aber gelöscht, sobald der Benutzer den Browser verlässt. Um einen Cookie zu erzeugen, der über mehrere Browsersitzungen hinweg bestehen bleibt, geben Sie seine Lebensdauer (in Sekunden) mit einem `max-age`-Attribut an. Sie erreichen dies, indem Sie der `cookie`-Eigenschaft einen String der folgenden Form zuweisen:

     _name_ = _wert_ ; max-age= _sekunden_

Um beispielsweise einen Cookie zu erzeugen, der für ein Jahr gültig bleibt, können Sie den folgenden Code verwenden:

    document.cookie = "version=" + document.lastModified +
             "; max-age=" + (60*60*24*365);

Sie können die Lebensdauer eines Cookies auch mit dem überholten `expires`-Attribut festlegen, das auf ein Datum im von `Date.toGMTString( )` geschriebenen Format gesetzt werden sollte. Beispielsweise:

    var naechstesJahr = new Date( );
    naechstesJahr.setFullYear(naechstesJahr.getFullYear( ) + 1);
    document.cookie = "version=" + document.lastModified +
             "; expires=" + naechstesJahr.toGMTString( );

In ähnlicher Weise können Sie die Attribute `path`, `domain` und `secure` eines Cookies festlegen, indem Sie Strings der folgenden Form an den Cookie-Wert anhängen, bevor er der Eigenschaft `cookie` zugewiesen wird:

    ; path= _pfad_
    ; domain= _domaene_
    ; secure

Um den Wert eines Cookies zu verändern, weisen Sie ihm einfach einen neuen Wert zu und verwenden dabei den gleichen Namen, Pfad und Domain. Die Lebensdauer eines Cookies kann verändert werden, indem Sie seinen Wert durch Angabe eines neuen `max-age`\- oder `expires`-Attributs ändern.

Um einen Cookie zu löschen, setzen Sie ihn noch einmal und verwenden den gleichen Namen, Pfad und Domain, geben aber einen beliebigen (oder leeren) Wert und ein `max-age`-Attribut von 0 an (oder verwenden Sie das `expires`-Attribut mit einem bereits abgelaufenen Verfallsdatum). Allerdings bedeutet dies nicht, dass der Browser verfallene Cookies unmittelbar löschen muss, sodass ein Cookie auch nach dem Verfallsdatum noch in der Cookie-Datei des Browsers enthalten sein kann.

## 19.2.1 Beschränkungen von Cookies

Cookies sind für das gelegentliche Speichern kleiner Datenmengen gedacht, nicht als Allzweckmechanismus für die Kommunikation oder Datenübertragung. Verwenden Sie sie daher maßvoll. RFC 2965 ermutigt Browserhersteller, eine unbegrenzte Anzahl von Cookies mit unbegrenzter Größe zu erlauben. Dazu müssen Sie wissen, dass der Standard von Browsern nur verlangt, insgesamt bis zu 300 Cookies oder 20 Cookies pro Webserver aufbewahren zu können (nicht pro Seite oder Site, sondern für den gesamten Server). Auch müssen pro Cookie nicht mehr als 4 Kilobyte an Daten gespeichert werden (zu dieser Menge zählen sowohl der Name als auch der Wert). In der Praxis erlauben moderne Browser viel mehr als insgesamt 300 Cookies, aber die 4 Kilobyte-Grenze wird z.T. weiterhin beibehalten.

# 19.3 Cookies lesen

Wenn Sie die `cookie`-Eigenschaft in einem JavaScript-Ausdruck verwenden, wird als Wert ein String zurückgegeben, der alle Cookies enthält, die für das aktuelle Dokument relevant sind. Dieser String besteht aus einer Liste von _`name`_`=` _`wert`_ -Paaren, die jeweils durch ein Semikolon voneinander getrennt werden. Dabei ist _`name`_ der Name eines Cookies und _`wert`_ der von ihm gespeicherte String. Dieser Wert enthält keines der Attribute, die für den Cookie eventuell definiert sind. Um den Wert eines bestimmten benannten Cookies zu ermitteln, können Sie die Methoden `String.indexOf( )` und `String.substring( )` verwenden oder den String mit der Methode `String.split( )` in einzelne Cookies zerteilen.

Nachdem Sie den Wert eines Cookies in dieser Weise aus der Eigenschaft `cookie` ausgelesen haben, müssen Sie ihn auf Basis des Formats bzw. der Codierung interpretieren, die vom Erzeuger des Cookies benutzt wurde. Beispielsweise könnte der Cookie mehrere Datenfelder enthalten, die jeweils durch einen Doppelpunkt voneinander getrennt sind. In diesem Fall müssten Sie die entsprechenden String-Methoden verwenden, um die einzelnen Datenfelder auszulesen. Denken Sie daran, die Funktion `decodeURIComponent( )` für den Wert des Cookies zu benutzen, wenn dieser zuvor mit der Funktion `encodeURIComponent( )` codiert wurde.

Das folgende Programmstück zeigt, wie Sie die Eigenschaft `cookie` lesen, einen einzelnen Cookie extrahieren und den Wert dieses Cookies verwenden:

    // Lies die cookie-Eigenschaft. So erhalten wir alle Cookies für dieses Dokument.
    var alleCookies = document.cookie;
    // Suche nach dem Anfang des Cookies namens "version"
    var pos = alleCookies.indexOf("version=");

    // Wenn wir einen Cookie dieses Namens finden, lesen wir ihn aus
    // und verwenden seinen Wert
    if (pos != −1) {
      var start = pos + 8;              // Anfang des Cookie-Werts
      var ende = alleCookies.indexOf(";", start);   // Ende des Cookie-Werts
      if (ende == −1) ende = alleCookies.length;
      var wert = alleCookies.substring(start, ende); // Extrahiere den Wert
      value = decodeURIComponent(value);       // Decodiere ihn

      // Nun haben wir den Wert des Cookies und können ihn verwenden.
      // In diesem Fall war der Cookie zuvor auf das Änderungsdatum des
      // Dokuments gesetzt. Mit seiner Hilfe können wir feststellen, ob sich
      // das Dokument seit dem letzten Besuch dieses Benutzers geändert hat.
      if (wert != document.lastModified)
        alert("Dieses Dokument hat sich seit Ihrem letzten Besuch geändert!");
    }

Beachten Sie, dass der beim Lesen der `cookie`-Eigenschaft zurückgegebene String keine Informationen über die einzelnen Attribute des Cookies enthält. Die Eigenschaft `cookie` ermöglicht es Ihnen, diese Werte festzulegen, nicht aber, sie abzufragen.

# 19.4 Ein Cookie-Beispiel

Wir beenden diese Besprechung von Cookies mit einem nützlichen Utility für den Umgang mit Cookies, das etwas später in Beispiel 19-2 gezeigt wird. Der `Cookie( )`-Konstruktor liest den Wert eines benannten Cookies. Die `store( )`-Methode eines Cookies speichert Daten in diesem Cookie mit Hilfe der von Ihnen festgelegten Lebensdauer, des Pfads und der Domain. Die `remove( )`-Methode eines Cookies löscht den Cookie, indem sie sein `max-age`-Attribut auf 0 setzt.

Die in diesem Beispiel definierte Cookie-Klasse speichert die Namen und Werte von mehreren Statusvariablen in einem einzigen Cookie. Um Daten mit einem Cookie zu assoziieren, setzen Sie einfach die Eigenschaften des Cookie-Objekts. Beim Aufruf der `store( )`-Methode am Cookie werden die von Ihnen zum Objekt hinzugefügten Eigenschaftsnamen und -werte zu dem Cookie-Wert, der gespeichert wird. Wenn Sie ein neues `Cookie`-Objekt erzeugen, sucht der `Cookie( )`-Konstruktor nach einem bereits bestehenden Cookie mit dem von Ihnen angegebenen Namen. Findet er ihn, parst er seinen Wert als Name/Wert-Paare und setzt sie als Eigenschaften des neu erstellten Cookie-Objekts.

Damit Sie Beispiel 19-2 besser verstehen, beginnt Beispiel 19-1 mit einer einfachen Webseite, die die Cookie-Klasse verwendet.

Beispiel 19-1. Die Cookie-Klasse verwenden

    <script src="Cookie.js"></script><!-- Cookie-Klasse einbeziehen -->
    <script>
    // Erzeuge den Cookie zum Speichern des Zustands dieser Seite.
    var cookie = new Cookie("besucherdaten");

    // Zunächst versuchen wir, die im Cookie gespeicherten Daten zu lesen. Wenn der Cookie
    // noch nicht vorhanden ist (oder nicht die erwarteten Daten enthält), dann frage den Benutzer
    if (!cookie.name || !cookie.color) {
      cookie.name = prompt("Ihr Name bitte:", "");
      cookie.color = prompt("Ihre Lieblingsfarbe:", "");
    }

    // Zähle, wie oft dieser Benutzer die Seite besucht hat
    if (!cookie.visits) cookie.visits = 1;
    else cookie.visits++;

    // Speichere die Cookie-Daten, die die neue Besuchsanzahl enthalten. Wir setzen
    // die Cookie-Lebensdauer auf 10 Tage. Da wir keinen Pfad angeben,
    // kann auf diesen Cookie von allen Webseiten im gleichen Verzeichnis oder
    // "darunter" zugegriffen werden. Deshalb sollten wir sicher sein, dass der Cookie-Name
    // "besucherdaten" in diesen Seiten eindeutig ist.
    cookie.store(10);

    // Jetzt können wir die Daten verwenden, die wir vom Cookie (oder vom
    // Benutzer) erhalten haben, um einen Benutzer mit Namen und Lieblingsfarbe zu grüßen.
    document.write('<h1 style="color:' + cookie.color + '">' +
            'Hallo ' + cookie.name + '!' + '</h1>' +
            '<p>Sie waren schon ' + cookie.visits + '-mal hier.' +
            '<button onclick="window.cookie.remove( );">Vergiss mich</button>');
    </script>

Die Cookie-Klasse selbst ist in Beispiel 19-2 aufgelistet.

Beispiel 19-2. Eine Cookie-Utility-Klasse

    /**
     * Das ist die Cookie( )-Konstruktorfunktion.
     *
     * Dieser Konstruktor sucht nach einem Cookie mit dem angegebenen Namen für das aktuelle
     * Dokument. Falls ein solcher vorhanden ist, parst er seinen Wert in eine Gruppe von
     * Name/Wert-Paaren und speichert diese Werte als Eigenschaften des neu erzeugten
     * Objekts.
     *
     * Um neue Daten im Cookie zu speichern, setzen Sie einfach die Eigenschaften des
     * Cookie-Objekts. Vermeiden Sie die Eigenschaftsnamen "store" und "remove", die für
     * Methodennamen reserviert sind.
     *
     * Um Cookie-Daten im lokalen Speicher des Webbrowsers zu speichern, rufen Sie store( ) auf.
     * Um Cookie-Daten aus dem Browserspeicher zu entfernen, rufen Sie remove( ) auf.
     *
     * Die statische Methode Cookie.enabled( ) gibt true aus, wenn Cookies
     * aktiviert sind, und false, wenn nicht.
     */
    function Cookie(name) {
      this.$name = name; // Speichere den Namen dieses Cookies

      // Erzeuge zunächst eine Liste aller Cookies, die zu diesem Dokument gehören.
      // Wir lesen dazu die Eigenschaft Document.cookie ein.
      // Sind keine Cookies vorhanden, brauchen wir nichts zu machen.
      var alleCookies = document.cookie;
      if (alleCookies == "") return;

      // Unterteile den String aller Cookies in einzelne Cookie-Strings,
      // Durchlaufe dann die Cookie-Strings und suche nach unserem Namen.
      var cookies = alleCookies.split(';');
      var cookie = null;
      for(var i = 0; i < cookies.length; i++) {
        // Fängt dieser Cookie-String mit dem richtigen Namen an?
        if (cookies[i].substring(0, name.length+1) == (name + "=")) {
          cookie = cookies[i];
          break;
        }
      }

      // Wenn wir keinen passenden Cookie fanden, dann brich ab
      if (cookie == null) return;

      // Der Cookie-Wert ist der Teil nach dem Gleichheitszeichen
      var cookieval = cookie.substring(name.length+1);

      // Wir haben den Wert des gesuchten Cookies gefunden und müssen
      // diesen nun in einzelne Namen und Werte von Zustandsvariablen
      // zerlegen. Die Paare von Namen und Werten sind voneinander
      // durch "&"-Zeichen getrennt, Name und Wert jeweils durch einen
      // Doppelpunkt. Wir verwenden die split()-Methode,
      // um alles zu parsen.
      var a = cookieWert.split('&'); // In ein Array von Name-Wert-Paaren zerlegen.
      for(var i=0; i < a.length; i++) // Zerlege jedes Paar in ein Array.
        a[i] = a[i].split(':');

      // Nun haben wir den Wert des Cookies analysiert und können alle Namen und Werte
      // als Eigenschaften dieses Cookie-Objekts setzen. Beachte, dass wir den
      // Eigenschaftswert decodieren, da ihn die store( )-Methode codiert.
      for(var i = 0; i < a.length; i++) {
        this[a[i][0]] = decodeURIComponent(a[i][1]);
      }
    }

    /**
     * Diese Funktion ist die store( )-Methode des Cookie-Objekts.
     *
     * Argumente:
     *
     *  daysToLive: die Lebensdauer des Cookies in Tagen. Wenn Sie diesen Wert auf
     *   0 setzen, wird der Cookie gelöscht. Wenn Sie ihn auf null setzen oder
     *   das Argument weglassen, gilt der Cookie nur für die eine Sitzung und wird
     *   nicht gespeichert, wenn der Browser beendet wird. Dieses Argument wird verwendet, um
     *   das max-age-Attribut des Cookies festzulegen.
     *  path: der Wert des path-Attributs des Cookies
     *  domain: der Wert des domain-Attributs des Cookies
     *  secure: wenn true, wird das secure-Attribut des Cookies gesetzt
     */
    Cookie.prototype.store = function(daysToLive, path, domain, secure) {
      // Durchlaufe zunächst die Eigenschaften des Cookie-Objekts, und
      // setze den Wert des Cookies zusammen. Da Cookies das Gleichheitszeichen
      // und Semikola als Trenner verwenden, benutzen wir Doppelpunkte
      // und das "&"-Zeichen für die einzelnen Zustandsvariablen, die
      // wir in einem einzelnen Cookie-Wert speichern. Beachte, dass wir den Wert
      // jeder Eigenschaft für den Fall codieren, dass er Sonder- oder andere
      // unzulässige Zeichen enthält.
      var cookieWert = "";
      for(var eigenschaft in this) {
        // Ignoriere Eigenschaften, deren Namen mit '$' beginnen, sowie Methoden.
        if ((eigenschaft.charAt(0) == '$') || ((typeof this[eigenschaft]) == 'function'))
          continue;
        if (cookieWert != "") cookieWert += '&';
        cookieval += prop + ':' + encodeURIComponent(this[prop]);
      }

      // Nachdem wir den Wert des Cookies haben, setzen wir nun den ganzen
      // Cookie-String zusammen, der auch den Namen und die einzelnen bei der
      // Erzeugung des Cookies-Objekts angegebenen Attribute enthält.
      var cookie = this.$name + '=' + cookieWert;
      if (daysToLive || daysToLive == 0) {
        cookie += "; max-age=" + (daysToLive*24*60*60);
      }

      if (path) cookie += "; path=" + path;
      if (domain) cookie += "; domain=" + domain;
      if (secure) cookie += "; secure";

      // Nun speichern wir den Cookie durch Zuweisung an die Eigenschaft Document.cookie.
      document.cookie = cookie;
    }

    /**
     * Diese Funktion ist die remove( )-Methode des Cookie-Objekts; sie löscht die
     * Eigenschaften des Objekts und entfernt den Cookie aus dem
     * lokalen Browserspeicher.
     *
     * Die Argumente für diese Funktion sind alle optional. Um einen Cookie zu entfernen,
     * müssen Sie aber dieselben Werte übergeben, die Sie zuvor an store( ) übergeben haben.
     */
    Cookie.prototype.remove = function(path, domain, secure) {
      // Lösche die Eigenschaften des Cookies
      for(var eigenschaft in this) {
        if (prop.charAt(0) != '$' && typeof this[prop] != 'function')
          delete this[prop];
      }

      // Speichere dann den Cookie mit einer Lebensdauer von 0
      this.store(0, path, domain, secure);
    }

    /**
     * Diese statische Methode versucht festzustellen, ob Cookies aktiviert sind.
     * Sie gibt true zurück, wenn sie aktiviert erscheinen, und ansonsten false.
     * Ein Rückgabewert von true garantiert nicht, dass Cookies auch tatsächlich überleben.
     * Cookies für nicht-persistente Sitzungen können auch dann funktionieren, wenn diese
     * Methode false ausgibt.
     */
    Cookie.enabled = function( ) {
      // Verwenden Sie navigator.cookieEnabled, wenn dieser Browser sie definiert
      if (navigator.cookieEnabled != undefined) return navigator.cookieEnabled;

      // Wenn wir bereits einen Wert im Cache haben, so verwende diesen Wert
      if (Cookie.enabled.cache != undefined) return Cookie.enabled.cache;

      // Erzeuge ansonsten einen Test-Cookie mit einer Lebensdauer
      document.cookie = "testcookie=test; max-age=10000"; // Setze den Cookie

      // Überprüfe jetzt, ob der Cookie gespeichert wurde
      var cookies = document.cookie;
      if (cookies.indexOf("testcookie=test") == −1) {
        // Der Cookie wurde nicht gespeichert
        return Cookie.enabled.cache = false;
      }
      else {
        // Cookie wurde gespeichert, wir müssen ihn also löschen, bevor wir zurückkehren
        document.cookie = "testcookie=test; max-age=0"; // Lösche den Cookie
        return Cookie.enabled.cache = true;
      }
    }

# 19.5 Cookie-Alternativen

Die Verwendung von Cookies für clientseitige Persistenz hat es ein paar Nachteile:

  * Sie sind auf eine Datenmenge von 4 KB begrenzt.

  * Auch wenn Cookies nur für die Skriptsteuerung auf der Clientseite verwendet werden, werden sie trotzdem bei jedem Anfordern einer mit ihnen assoziierten Webseite an den Webserver geschickt. Braucht der Server die Cookies nicht, ist das eine Verschwendung von Bandbreite.

Zu Cookies gibt es zwei Alternativen. Sowohl Internet Explorer von Microsoft als auch das Flash-Plug-in von Adobe definieren eigene Mechanismen für clientseitige Persistenz. Obwohl sie nicht standardisiert sind, sind beide weit verbreitetet. In der überwiegenden Mehrheit der Browser ist daher mindestens einer dieser Mechanismen vorhanden. Die Persistenzmechanismen von IE und Flash werden in den folgenden Abschnitten kurz beschrieben. Das Kapitel schließt mit einem fortgeschrittenen Beispiel, das persistenten Speicher über IE, Flash oder Cookies zur Verfügung stellt.

## 19.5.1 IE userData-Persistenz

Internet Explorer ermöglicht clientseitige Persistenz mit einem DHTML-Behavior (Verhalten). Um auf diesen Mechanismus zuzugreifen, wenden Sie ein spezielles Verhalten auf ein Element (wie z.B. `<div>`) Ihres Dokuments an. Das können Sie z.B. mit CSS erreichen:

    <!-- Dieses Stylesheet definiert eine Klasse namens "persistent" -->
    <style>.persistent { behavior:url(#default#userData);}</style>
    <!-- Dieses <div>-Element gehört dieser Klasse an -->
    <div id="memory" class="persistent"></div>

Da `behavior` nicht dem CSS-Standard entspricht, ignorieren es andere Webbrowser einfach. Sie können `behavior` auch mit JavaScript an einem Element festlegen:

    var memory = document.getElementById("memory");
    memory.style.behavior = "url('#default#userData')";

Ist ein HTML-Element mit diesem userData-Behavior assoziiert, so werden neue (vom Behavior definierte) Methoden für dieses Element verfügbar.[69] Um Daten persistent zu speichern, setzen Sie die Attribute des Elements mit `setAttribute( )` und speichern diese Attribute dann mit `save( )`:

    var memory = document.getElementById("memory");   // Hole das persistente Element
    memory.setAttribute("username", username);        // Lege die Daten als Attribute fest
    memory.setAttribute("lieblingsfarbe", lieblingsfarbe);
    memory.save("meinePersistentenDaten");            // Speichere die Daten

Beachten Sie, dass die `save( )`-Methode ein String-Argument entgegennimmt: Das ist der (frei gewählte) Name, unter dem die Daten gespeichert werden sollen. Beim Abfragen der Daten müssen Sie den gleichen Namen verwenden.

Daten, die mit Hilfe des IE-Persistenzmechanismus gespeichert werden, kann ein Verfallsdatum gegeben werden, wie das auch für Cookie-Daten möglich ist. Dazu legen Sie einfach die `expires`-Eigenschaft fest und rufen anschließend die `save( )`-Methode auf. Diese Eigenschaft sollte ein String in dem Format sein, das von `Date.toUTCString( )` zurückgegeben wird. Beispielsweise können Sie die folgenden Zeilen zum vorhergehenden Code hinzufügen, um ein Verfallsdatum 10 Tage in der Zukunft festzulegen:

    var now = (new Date( )).getTime( );                   // jetzt, in Millisekunden
    var expires = now + 10 * 24 * 60 * 60 * 1000;         // 10 Tage von jetzt an, in ms
    memory.expires = (new Date(expires)).toUTCString( );  // in einen String konvertieren

Um persistente Daten abzufragen, drehen Sie diese Schritte um und rufen `load( )` auf, um gespeicherte Attribute zu laden, und dann `getAttribute( )`, um Attributwerte abzufragen:

    var memory = document.getElementById("memory");  // Hole das persistente Element
    memory.load("meinePersistentenDaten");           // Gespeicherte Daten über Namen abrufen
    var user = memory.getAttribute("username");      // Frage die Attribute ab
    var farbe = memory.getAttribute("lieblingsfarbe");

### 19.5.1.1 Hierarchische Daten speichern

Das userData-Persistenzverhalten beschränkt sich nicht auf das Speichern und Abrufen von Attributwerten. Jedes Element, auf das dieses Behavior angewandt wird, wird mit einem vollständigen XML-Dokument assoziiert. Die Anwendung des Behaviors auf ein HTML-Element erzeugt eine `XMLDocument`-Eigenschaft am Element. Der Wert dieser Eigenschaft ist ein DOM-`Document`-Objekt. Sie können DOM-Methoden verwenden (siehe Kapitel 15), um diesem Dokument vor dem Aufruf von `save( )` einen Inhalt hinzuzufügen bzw. nach dem Aufruf von `load( )` Inhalte auszulesen. Hier ist ein Beispiel:

    var memory = document.getElementById("memory");     // Beschaffe das persistente Element
    var doc = memory.XMLDocument;                       // Beschaffe sein Dokument
    var root = doc.documentElement;                     // Root-Element des Dokuments
    root.appendChild(doc.createTextNode("Daten hier")); // Speichere den Text im Dokument

Durch die Verwendung eines XML-Dokuments können hierarchische Daten gespeichert werden; beispielsweise können Sie einen Datenbaum aus JavaScript-Objekten in einen Datenbaum aus XML-Elementen konvertieren.

### 19.5.1.2 Speicherlimits

Mit dem Persistenzmechanismus von IE kann eine viel größere Datenmenge gespeichert werden als mit Cookies. Jede Seite kann bis zu 64 KB speichern und für jeden Webserver sind insgesamt 640 KB erlaubt. Für Seiten eines vertrauenswürdigen Intranets sind noch größere Mengen erlaubt. Es existiert keine Dokumentation darüber, wie ein Endbenutzer diese Speicherbegrenzungen ändern oder den Persistenzmechanismus völlig deaktivieren kann.

### 19.5.1.3 Persistente Daten gemeinsam benutzen

Genau wie Cookies sind die mit dem IE-Persistenzmechanismus gespeicherten Daten für alle Webseiten im gleichen Verzeichnis zugänglich. Im Unterschied zu Cookies kann eine Webseite aber nicht über IE auf persistente Daten zugreifen, die von Seiten in ihren übergeordneten Verzeichnissen gespeichert wurden. Außerdem hat der IE-Persistenzmechanismus kein Äquivalent zu den Cookie-Attributen `path` und `domain`, sodass es keine Möglichkeit gibt, persistente Daten über eine größere Anzahl von Seiten gemeinsam zu verwenden. Persistente Daten lassen sich unter IE von verschiedenen Seiten auch nur gemeinsam benutzen, wenn die Seiten im gleichen Verzeichnis stehen und über das gleiche Protokoll geladen wurden. Das bedeutet, dass eine Seite, die mit dem normalen `http:`-Protokoll geladen wird, nicht auf Daten in einer Seite zugreifen kann, die mit dem `https:`-Protokoll geladen wurde.

## 19.5.2 Flash-SharedObject-Persistenz

Das Flash-Plug-in der Versionen 6 und höher ermöglicht clientseitige Persistenz über die Objektklasse Shared, die per ActionScript-Code in einem Flash-Movie skriptgesteuert werden kann.[70] Dazu erzeugen Sie ein SharedObject mit ActionScript-Code wie z.B. dem folgenden. Beachten Sie, dass Sie einen Namen (entsprechend dem Cookie-Namen) für Ihre persistenten Daten angeben müssen:

    var so = SharedObject.getLocal("meinePersistentenDaten");

Im Gegensatz zum IE-Persistenzmechanismus definiert die SharedObject-Klasse keine `load( )`-Methode. Bei der Erstellung eines SharedObject werden die vorher unter dem angegebenen Namen gespeicherten Daten automatisch geladen. Jedes SharedObject hat eine `data`-Eigenschaft. Diese `data`-Eigenschaft bezieht sich auf ein reguläres ActionScript-Objekt, und über die Eigenschaften dieses Objekts kann auf die persistenten Daten zugegriffen werden. Um persistente Daten zu lesen oder zu schreiben, lesen oder schreiben Sie einfach die Eigenschaften des `data`-Objekts:

    var name = so.data.username;  // Beschaffe einige persistente Daten
    so.data.favoriteColor = "red"; // Lege ein persistentes Feld fest

Die Eigenschaften, die Sie am `data`-Objekt setzen, müssen nicht unbedingt einfache Typen sein, wie z.B. Zahlen oder Strings. Arrays sind beispielsweise auch erlaubt.

Das `SharedObject` hat auch keine `save( )`-Methode. Es hat eine `flush( )`-Methode, die den derzeitigen Status des SharedObjects sofort speichert. Allerdings ist der Aufruf dieser Methode nicht notwendig: Eigenschaften, die im `data`-Objekt eines SharedObjects festgesetzt sind, werden beim Entladen des Flash-Movies automatisch gespeichert. Beachten Sie, dass SharedObject keine Möglichkeit unterstützt, ein Verfallsdatum oder eine Lebensdauer für die persistenten Daten festzulegen.

Denken Sie daran, dass der gesamte in diesem Abschnitt gezeigte Code ActionScript-Code ist, der unter dem Flash-Plug-in läuft, und nicht JavaScript-Code, der im Browser läuft. Wenn Sie den Flash-Persistenzmechanismus in Ihrem JavaScript-Code verwenden wollen, müssen Sie eine Möglichkeit für JavaScript finden, um mit Flash zu kommunizieren. Die Techniken für diesen Zweck werden in Kapitel 23 beschrieben. Beispiel 22-12 zeigt die Verwendung der ExternalInterface-Klasse (die in Flash 8 und höher enthalten ist), die den Aufruf von ActionScript-Methoden von JavaScript aus spielend einfach macht. Die Beispiel 19-3 und Beispiel 19-4 weiter unten in diesem Kapitel verwenden einen primitiveren Kommunikationsmechanismus zur Verbindung von JavaScript mit ActionScript. Mithilfe der `GetVariable( )`\- und `SetVariable( )`-Methoden des Flash-Plug-in-Objekts kann JavaScript ActionScript-Variablen abfragen und setzen, und die ActionScript-Funktion `fscommand( )` sendet Daten an JavaScript.

### 19.5.2.1 Speicherlimits

In der Standardeinstellung erlaubt der Flash-Player bis zu 100 KB persistente Daten pro Website. Der Benutzer kann diese Beschränkung bis auf 10 KB herunter- oder bis auf 10 MB heraufsetzen. Alternativerweise kann der Benutzer unbegrenzte oder überhaupt keine Speicherkapazität für persistente Daten zulassen. Versucht eine Website, die Beschränkung zu überschreiten, fordert Flash den Benutzer auf, eine erweiterte Speicherkapazität für Ihre Website zuzulassen oder zu verweigern.

### 19.5.2.2 Persistente Daten gemeinsam benutzen

In der Standardeinstellung sind persistente Daten in Flash nur für das Movie zugänglich, das sie erzeugt hat. Es ist aber möglich, diese Einschränkung zu lockern und zuzulassen, dass zwei Movies im gleichen Verzeichnis oder an beliebigen Stellen auf dem gleichen Server Zugang zu den persistenten Daten haben. Das wird ähnlich wie beim `path`-Attribut eines Cookies gemacht. Wenn Sie ein SharedObjekt mit `SharedObject.getLocal( )` erzeugen, können Sie einen Pfad als zweites Argument übergeben. Dieser Pfad muss ein Präfix des tatsächlichen Pfads in der URL des Movies sein. Jedes Movie, das den gleichen Pfad verwendet, kann die von diesem Movie gespeicherten persistenten Daten verwenden. Der folgende Code erzeugt beispielsweise ein SharedObject, das auch von jedem anderen Flash-Movie verwendet werden kann, das vom gleichen Webserver stammt:

    var so = SharedObject.getLocal("Meine/Gemeinsamen/Persistenten/Daten", // Objektname
                    "/");                  // Pfad

Wenn Sie die SharedObject-Klasse per Skript von JavaScript aus steuern, haben Sie wahrscheinlich kein Interesse daran, persistente Daten von mehreren Flash-Movies gemeinsam verwenden zu lassen. Wichtiger wird es für Sie sein, Daten zwischen Webseiten zu teilen, die das gleiche Movie per Skript steuern (siehe Beispiel 19-3 im nächsten Abschnitt).

## 19.5.3 Beispiel: Persistente Objekte

Dieser Abschnitt schließt mit einem ausführlichen Beispiel, das eine gemeinsame API für die drei Persistenzmechanisms definiert, die Sie in diesem Kapitel studiert haben. Beispiel 19-3 definiert eine PObject-Klasse für persistente Objekte. Die PObject-Klasse funktioniert ähnlich wie die Cookie-Klasse in Beispiel 19-2. Mit dem `PObject( )`-Konstruktor wird ein persistentes Objekt erzeugt, an das der Name, die Standardwerte und eine `onload`-Handler-Funktion übergeben werden. Der Konstruktor erzeugt ein neues JavaScript-Objekt und versucht, die bereits unter dem von Ihnen angegebenen Namen gespeicherten persistenten Daten zu laden. Findet er diese Daten, parst er sie in einen Satz von Name/Wert-Paaren und setzt diese Paare als Eigenschaften des neu erstellten Objekts. Findet er keine bereits gespeicherten Daten, verwendet er die Eigenschaften der von Ihnen angegebenen Standardobjekte. In beiden Fällen wird die von Ihnen festgelegte `onload`-Handler-Funktion asynchron aufgerufen, sobald Ihre persistenten Daten für den Gebrauch bereitstehen.

Nachdem Ihr `onload`-Handler aufgerufen wurde, können Sie die persistenten Daten einfach dadurch verwenden, dass Sie die Eigenschaften von PObject lesen. Das machen Sie wie bei jedem regulären JavaScript-Objekt. Um neue persistente Daten zu speichern, setzen Sie diese Daten als Eigenschaften (des Typs Boolean, Number oder String) des PObject. Rufen Sie dann die `save( )`-Methode von PObject auf, und geben optional eine Lebensdauer (in Tagen) für die Daten an. Um die persistenten Daten zu löschen, rufen Sie die `forget( )`-Methode des PObject auf.

Die hier definierte PObject-Klasse verwendet die IE-basierte Persistenz, wenn der Code unter IE läuft. Ansonsten sucht sie nach einer passenden Version des Flash-Plug-ins und verwendet die Flash-basierte Persistenz, falls sie zur Verfügung steht. Sind beide Möglichkeiten nicht vorhanden, wird auf Cookies zurückgegriffen.[71]

Die PObject-Klasse speichert nur einfache Werte und konvertiert Zahlen und Boolesche Werte beim Auslesen in Strings. Array- und Objektwerte können als Strings serialisiert werden. Diese Strings können in Arrays und Objekte zurückgeparst werden (siehe z.B. <http://www.json.org>), auch wenn dieses Beispiel das nicht macht.

Beispiel 19-3 ist lang, aber gut kommentiert und sollte einfach nachvollziehbar sein. Lesen Sie auf jeden Fall den ausführlichen Einführungskommentar, der die PObject-Klasse und ihre API dokumentiert.

Beispiel 19-3. PObject.js: Persistente Objekte für JavaScript

    /**
     * PObject.js: JavaScript-Objekte, die über mehrere Browsersitzungen hinweg persistent sind
     * und von Webseiten mit gleichem Verzeichnis und Host gemeinsam verwendet werden können.
     *
     * Dieses Modul definiert einen PObject( )-Konstruktor zur Erzeugung eines persistenten * Objekts.
     * PObject-Objekte haben zwei öffentliche Methoden. save( ) speichert bzw. "persistiert" die
     * derzeitigen Eigenschaften des Objekts. forget( ) löscht die persistenten
     * Eigenschaften des Objekts. Um eine persistente Eigenschaft an einem PObject zu definieren,
     * setzen Sie die Eigenschaft am Objekt einfach so, als wäre es ein reguläres JavaScript-
     * Objekt, und rufen dann die save( )-Methode auf, um den derzeitigen Status des
     * Objekts zu speichern. Die Namen "save" und "forget" dürfen Sie nicht als Eigenschaftsnamen * verwenden.
     * Eigenschaftsnamen dürfen auch nicht mit $ beginnen. PObject ist für die Verwendung mit
     * Eigenschaftswerten vom Typ String gedacht. Sie können auch Eigenschaften vom Typ
     * Boolean und Number speichern, sie werden aber beim Auslesen zu Strings konvertiert.
     *
     * Wird ein PObject erzeugt, so werden die persistenten Daten gelesen und
     * als reguläre JavaScript-Eigenschaften im neu erzeugten Objekt gespeichert.
     * Das PObject wird genau wie ein reguläres JavaScript-Objekt verwendet. Allerdings
     * sind persistente Eigenschaften nicht immer bereit, wenn der PObject( )-Konstruktor
     * zurückkehrt. Sie sollten deshalb auf eine asynchrone Benachrichtigung warten, indem Sie
     * eine onload-Handler-Funktion an den Konstruktor übergeben.
     *
     * Konstruktor:
     *  PObject(name, defaults, onload):
     *
     * Argumente:
     *
     *  name    Ein Name, der dieses persistente Objekt identifiziert. Eine einzelne Seite
     *        kann mehrere PObject-Objekte enthalten, und PObjects sind allen Seiten
     *        im selben Verzeichnis zugänglich. Daher sollte dieser Name
     *        innerhalb des Verzeichnisses eindeutig sein. Ist dieses Argument null oder
     *        nicht angegeben, so wird der Dateiname (aber nicht das Verzeichnis) verwendet,
     *        der die Webseite enthält.
     *
     *  defaults  Ein optionales JavaScript-Objekt. Kann kein gespeicherter Wert für das
     *        persistente Objekt gefunden werden (was der Fall ist, wenn ein PObject
     *        zum ersten Mal erzeugt wird), werden die Eigenschaften dieses Objekts
     *        in das neu erzeugte PObject kopiert.
     *
     *  onload   Die (asynchron) aufzurufende Funktion, wenn persistente Werte
     *        in das PObject geladen wurden und bereitstehen.
     *        Diese Funktion wird mit zwei Argumenten aufgerufen: einer Referenz
     *        auf das PObject und dem PObject-Namen. Diese Funktion wird
     *        aufgerufen, *nachdem* der PObject( )-Konstruktor zurückkehrt. PObject
     *        -Eigenschaften sollten nicht vor diesem Schritt verwendet werden.
     *
     * Method PObject.save(lebensdauerInTagen):
     *  Speichere die Eigenschaften eines PObjects. Diese Methode speichert die Eigenschaften
     *  des PObject und stellt sicher, dass sie mindestens für die angegebene
     *  Anzahl an Tagen persistent sind.
     *
     * Method PObject.forget( ):
     *  Lösche die Eigenschaften des PObject. Speichere dann das "leere" PObject in den
     *  persistenten Speicher und veranlasse, wenn möglich, das Ablaufen desselben.
     *
     * Implementierungshinweise:
     *
     * Dieses Modul definiert eine einzige PObject-API mit drei unterschiedlichen
     * Implementierungen dieser API. In Internet Explorer wird der IE-spezifische
     * "UserData"-Persistenzmechanismus verwendet. Auf allen anderen Browsern mit
     * Adobe-Flash-Plug-in wird der Flash-SharedObject-Persistenzmechanismus
     * verwendet. Nicht-IE-Browser, die kein Flash haben, greifen auf die
     * Implementierung mit Cookies zurück. Da die Flash-Implementierung
     * keine Verfallsdaten für gespeicherte Daten unterstützt, existieren Daten, die mit diesem
     * Mechanismus gespeichert werden, bis sie gelöscht werden.
     *
     * PObjects gemeinsam verwenden:
     *
     * Mit einem PObject in einer Seite gespeicherte Daten sind auch anderen Seiten
     * im gleichen Verzeichnis des gleichen Servers zugänglich. Wird die Cookie-
     * Implementierung verwendet, können Seiten in Unterverzeichnissen die in übergeordneten
     * Verzeichnissen erzeugten Eigenschaften von PObjects lesen (aber nicht schreiben). Wird die
     * Flash-Implementierung verwendet, so kann jede Seite auf dem Webserver auf die gemeinsamen
     * Daten zugreifen, sofern sie mogelt und eine modifizierte Version dieses Moduls verwendet.
     *
     * Unterschiedliche Browseranwendungen speichern ihre Cookies getrennt und
     * persistente Daten, die mit Cookies in einem Browser gespeichert werden, sind mit
     * einem anderen Browser nicht zugänglich. Wenn zwei Browser allerdings die gleiche
     * Installation des Flash-Plug-ins verwenden, können diese Browser die mit dieser
     * Flash-Implementierung gespeicherten persistenten Daten gemeinsam benutzen.
     *
     * Sicherheitshinweise:
     *
     * Die über ein PObject gespeicherten Daten werden auf der Benutzerfestplatte unverschlüsselt * gespeichert. Auf dem Rechner laufende Anwendungen können auf die Daten zugreifen. PObject
     * ist also nicht für die Speicherung empfindlicher Informationen geeignet, wie z.B. von
     * Kreditkartennummern, Passwörtern, und Bankkontonummern.
     */

    // Das ist die Konstruktor-
    Funktion PObject(name, defaults, onload) {
      if (!name) { // Wurde kein Name angegeben, verwende die letzte Komponente der URL
        name = window.location.pathname;
        var pos = name.lastIndexOf("/");
        if (pos != −1) name = name.substring(pos+1);
      }
      this.$name = name; // Speichere unseren Namen

      // Delegiere das einfach an eine private, in der Implementierung definierte $init( )-  // Methode.
      this.$init(name, defaults, onload);
    }

    // Speichere den aktuellen Status dieses PObject für mindestens so viele Tage wie angegeben.
    PObject.prototype.save = function(lebensdauerInTagen) {
      // Serialisiere die Eigenschaften des Objekts zuerst in einen einzigen String
      var s = "";                // Beginne mit einem leeren String
      for(var name in this) {          // Durchlaufe die Eigenschaften
        if (name.charAt(0) == "$") continue; // Überspringe private $-Eigenschaften
        var wert = this[name];        // Beschaffe den Eigenschaftswert
        var typ = typeof wert;       // Beschaffe den Eigenschaftstyp
        // Überspringe Eigenschaften vom Typ Object oder Function
        if (typ == "object" || typ == "function") continue;
        if (s.length > 0) s += "&";      // Trenne Eigenschaften mit &
        // Füge den Eigenschaftnamen und codierten Wert hinzu
        s += name + ':' + encodeURIComponent(wert);
      }

      // Delegiere das an eine private, in der Implementierung definierte Methode, die
      // die eigentliche Speicherung dieses serialisierten Strings durchführt.
      this.$save(s, lebensdauerInTagen);
    };

    PObject.prototype.forget = function( ) {
      // Lösche zuerst die serialisierbaren Eigenschaften dieses Objekts mit Hilfe
      // der gleichen Eigenschaftsauswahlkriterien wie in save( ).
      for(var name in this) {
        if (name.charAt(0) == '$') continue;
        var wert = this[name];
        var typ = typeof wert;
        if (typ == "function" || typ == "object") continue;
        delete this[name]; // Lösche die Eigenschaft
      }

      // Entferne alle vorher gespeicherten Daten, und lasse sie ablaufen, durch Speichern
      // des leeren Strings mit einer Lebensdauer von 0.
      this.$save("", 0);
    };

    // Parse den String s in Name/Wert-Paare, und lege sie als Eigenschaften von this fest.
    // Ist der String null oder leer, kopiere stattdessen die Eigenschaften aus den Defaults.
    // Diese private Utility-Methode wird von der unten stehenden $init( )-// Implementierung verwendet.
    PObject.prototype.$parse = function(s, defaults) {
      if (!s) { // Ist kein String vorhanden, so verwende stattdessen die Defaulteigenschaften
        if (defaults) for(var name in defaults) this[name] = defaults[name];
        return;
      }

      // Die Name/Wert-Paare werden durch Ampersands voneinander getrennt und
      // die einzelnen Namen und Werte durch Doppelpunkte.
      // Wir verwenden die split( )-Methode, um alles zu parsen.
      var eigsn = s.split('&'); // Unterteile es in ein Array mit Name/Wert-Paaren
      for(var i = 0; i < eigsn.length; i++) { // Durchlaufe die Name/Wert-Paare
        var eig = eigsn[i];
        var a = eig.split(':');   // Unterteile jedes Name/Wert-Paar am Doppelpunkt
        this[a[0]] = decodeURIComponent(a[1]); // Entschlüssele und speichere die Eigenschaft
      }
    };

    /*
     * Den implementierungsspezifischen Teil des Moduls sehen Sie unten.
     * Für jede Implementierung definieren wir eine $init( )-Methode, die
     * persistente Daten lädt, und eine $save( )-Methode, die sie speichert.
     */

    // Finde heraus, ob wir in IE sind. Falls nicht, schaue nach, ob wir ein Flash-
    // Plug-in installiert haben und ob seine Version hoch genug ist
    var istIE = navigator.appName == "Microsoft Internet Explorer";
    var hatFlash7 = false;
    if (!istIE && navigator.plugins) { // Wenn wir die Netscape-Plug-in-Architektur verwenden
      var flashplayer = navigator.plugins["Shockwave Flash"];
      if (flashplayer) {  // Wenn wir ein Flash-Plug-in haben
        // Extrahiere die Versionsnummer
        var flashversion = flashplayer.description;
        var flashversion = flashversion.substring(flashversion.search("\\d"));
        if (parseInt(flashversion) >= 7) hatFlash7 = true;
      }
    }

    if (istIE) { // Wenn wir uns in IE befinden
      // Der PObject( )-Konstruktor delegiert an diese Initialisierungsfunktion
      PObject.prototype.$init = function(name, defaults, onload) {
        // Erzeuge ein unsichtbares Element mit userData-Behavior zur Datenspeicherung
        var div = document.createElement("div"); // Erzeuge ein <div>-Tag
        this.$div = div;             // Speichere es
        div.id = "PObject" + name;        // Gib ihm einen Namen
        div.style.display = "none";        // Mache es unsichtbar

        // Das ist der IE-spezifische Trick, mit dem die Persistenz funktioniert.
        // Das "userData"-Verhalten fügt diesem <div>-Element die getAttribute( )-,     // setAttribute( )-, load( )- und save( )-Methoden hinzu. Wir verwenden sie
        // weiter unten.
        div.style.behavior = "url('#default#userData')";

        document.body.appendChild(div); // Füge das Element ins Dokument ein

        // Jetzt rufen wir ggf. bereits gespeicherte, persistente Daten ab.
        div.load(name); // Lade die unter unserem Namen gespeicherten Daten
        // Die Daten bestehen aus mehreren Attributen. Uns interessiert davon nur ein
        // Attribut. Der Name "daten" für das Attribut ist frei gewählt.
        var daten = div.getAttribute("daten");

        // Parse die abgerufenen Daten, und unterteile sie in Objekteigenschaften
        this.$parse(daten, defaults);

        // Wenn ein onload-Callback existiert, veranlasse einen asynchronen Aufruf
        // nach dem Zurückkehren des PObject( )-Konstruktors.
        if (onload) {
          var pobj = this; // In der eingebetteten Funktion kann "this" nicht verwendet
                   // werden
          setTimeout(function( ) { onload(pobj, name);}, 0);
        }
      }

      // Speichere den aktuellen Stand des persistenten Objekts
      PObject.prototype.$save = function(s, lebensdauerInTagen) {
        if (lebensdauerInTagen) { // Etwaige Lebensdauer in ein Verfallsdatum konvertieren
          var jetzt = (new Date( )).getTime( );
          var laeuftAb = jetzt + lebensdauerInTagen * 24 * 60 * 60 * 1000;
          // Lege das Verfallsdatum als Stringeigenschaft des <div>-Tags fest
          this.$div.laeuftAb = (new Date(laeuftAb)).toUTCString( );
        }

        // Speichere die Daten jetzt als persistente Daten
        this.$div.setAttribute("daten", s); // Setze Text als Attribut des <div>-Tags
        this.$div.save(this.$name);    // und mache das Attribut persistent
      };
    }
    else if (hasFlash7) { // Das ist die Flash-basierte Implementierung
      PObject.prototype.$init = function(name, defaults, onload) {
        var moviename = "PObject_" + name;  // id des <embed>-Tags
        var url = "PObject.swf?name=" + name; // URL der Movie-Datei

        // Wenn der Flash-Player läuft und unsere Daten bereithält,
        // benachrichtigt er uns mit einem FSCommand. Wir müssen einen
        // Handler definieren, der in diesem Fall aufgerufen wird.
        var pobj = this; // wird von der eingebetteten Funktion verwendet
        // Flash verlangt, dass wir unsere Funktion mit diesem globalen Symbol benennen
        window[moviename + "_DoFSCommand"] = function(kommando, args) {
          // Wir wissen, dass Flash bereit ist. Also fragen wir unsere persistenten Daten ab
          var daten = pobj.$flash.GetVariable("daten")
          pobj.$parse(daten, defaults);  // Parse Daten oder kopiere Defaults
          if (onload) onload(pobj, name); // Rufe onload-Handler auf, falls vorhanden
        };

        // Erzeuge ein <embed>-Tag zum Speichern unseres Flash-    // Movies. Die Verwendung eines <object>-
        // Tags entspricht dem Standard besser, scheint aber Probleme beim
        // Empfang des FSCommand zu machen. Wir werden Flash nie
        // unter IE verwenden, was die Sache stark vereinfacht.
        var movie = document.createElement("embed"); // Element, das das Movie enthält
        movie.setAttribute("id", moviename);     // Element-id
        movie.setAttribute("name", moviename);    // und -Name
        movie.setAttribute("type", "application/x-shockwave-flash");
        movie.setAttribute("src", url); // Das ist die URL des Movies
        // Setze das Movie unauffällig in die rechte obere Ecke
        movie.setAttribute("width", 1); // Mit einem Wert von 0 funktioniert es nicht
        movie.setAttribute("height", 1);
        movie.setAttribute("style", "position:absolute; left:0px; top:0px;");

        document.body.appendChild(movie); // Füge das Movie ins Dokument ein
        this.$flash = movie;        // Und speichere es für später
      };

      PObject.prototype.$save = function(s, lebensdauerInTagen) {
        // Um die Daten persistent zu machen, setzen wir sie einfach als eine Variable am
        // Flash-Movie. Der ActionScript-Code im Movie macht die Daten persistent.
        // Beachte, dass die Flash-Persistenz keine Zeitbegrenzung unterstützt.
        this.$flash.SetVariable("daten", s); // Fordere Flash auf, den Text zu speichern
      };
    }
    else { /* Wenn wir weder IE noch Flash 7 haben, nehmen wir Cookies */
      PObject.prototype.$init = function(name, defaults, onload) {
        var alleCookies = document.cookie;        // Beschaffe alle Cookies
        var daten = null;                // Annehmen, dass keine Cookie-                             // Daten vorhanden sind
        var start = alleCookies.indexOf(name + '=');   // Suche nach dem Cookie-Start
        if (start != −1) {                // Gefunden
          start += name.length + 1;          // Cookie-Namen überspringen
          var ende = alleCookies.indexOf(';', start); // Ende des Cookies suchen
          if (ende == −1) ende = alleCookies.length;
          daten = alleCookies.substring(start, ende); // Extrahiere die Cookie-Daten
        }
        this.$parse(daten, defaults); // Parse den Cookie-Wert in Eigenschaften
        if (onload) {         // Rufe den onload-Handler asynchron auf
          var pobj = this;
          setTimeout(function( ) { onload(pobj, name);}, 0);
        }
      };

      PObject.prototype.$save = function(s, lebensdauerInTagen) {
        var cookie = this.$name + '=' + s;     // Cookie-Name und -Wert
        if (lebensdauerInTagen != null)       // Füge Verfallsdatum hinzu
          cookie += "; max-age=" + (lebensdauerInTagen*24*60*60);
        document.cookie = cookie;          // Speichere den Cookie
      };
    }

## 19.5.4 ActionScript-Code für Flash-Persistenz

Der Code in Beispiel 19-3 ist noch unvollständig. Die Flash-basierte Persistenzimplementierung braucht ein Flash-Movie namens _PObject.swf_. Das Movie ist nichts anderes als eine kompilierte ActionScript-Datei. Beispiel 19-4 zeigt den ActionScript-Code.

Beispiel 19-4. ActionScript-Code für die Flash-basierte Persistenz

    class PObject {
      static function main( ) {
        // SharedObject ist in Flash 6 vorhanden, ist aber noch nicht gegen
        // Cross-Domain-Scripting geschützt. Also müssen wir die Flash 7
        // -Version des Flash-Players haben.
        var version = getVersion( );
        version = parseInt(version.substring(version.lastIndexOf(" ")));
        if (isNaN(version) || version < 7) return;

        // Erzeuge ein SharedObject zur Speicherung unserer persistenten Daten.
        // Der Name des Objekts wird in der Movie-URL wie folgt übergeben:
        // PObject.swf?name=name
        _root.so = SharedObject.getLocal(_root.name);

        // Rufe die ursprünglichen Daten ab, und speichere sie unter _root.data.
        _root.data = _root.so.data.data;

        // Beobachte die Datenvariable. Ändert sie sich, dann speichere ihren neuen Wert
        // persistent.
        _root.watch("daten", function(eigName, wertAlt, wertNeu) {
               _root.so.data.daten = wertNeu;
               _root.so.flush( );
             });

        // Benachrichtige JavaScript, dass es die persistenten Daten jetzt abrufen kann.
        fscommand("init");
      }
    }

Der ActionScript-Code ist recht einfach. Er beginnt mit der Erzeugung eines SharedObjects und verwendet einen (von JavaScript) angegebenen Namen im URL-Abfrageteil des Movie-Objekts. Durch die Erzeugung dieses SharedObject werden die persistenten Daten geladen, die in diesem Fall einfach aus einem einzigen String bestehen. Dieser Datenstring wird mit der `fscommand( )`-Funktion an JavaScript zurückgegeben. Die Funktion ruft den in JavaScript definierten `doFSCommand`-Handler auf. Der ActionScript-Code legt ebenfalls eine Handler-Funktion fest, die immer dann aufgerufen wird, wenn sich die `daten`-Eigenschaft des root-Objekts ändert. Der JavaScript-Code verwendet `SetVariable( )`, um die `daten`-Eigenschaft festzulegen, und diese ActionScript-Handler-Funktion wird als Antwort aufgerufen, wodurch die Daten persistent werden.

Der in der _PObject.as_ -Datei von Beispiel 19-4 gezeigte ActionScript-Code muss in eine _PObject.swf_ -Datei kompiliert werden, bevor er mit dem Flash-Player verwendet werden kann. Das machen Sie mit dem Open Source-ActionScript-Compiler _mtasc_ (erhältlich unter <http://www.mtasc.org>). Rufen Sie den Compiler wie folgt auf:

    mtasc -swf PObject.swf -main -header 1:1:1 PObject.as

 _mtasc_ erstellt eine SWF-Datei, die die `PObject.main( )`-Methode des ersten Frames im Movie aufruft. Wenn Sie stattdessen die Flash-IDE verwenden, müssen Sie `PObject. main( )` ausdrücklich vom ersten Frame aus aufrufen. Die Alternative ist, einfach den Code aus der `main( )`-Methode zu kopieren und in das erste Frame einzufügen.

* * *

[69] Das userData-Behavior ist eine von vier möglichen Verhaltenweisen unter Internet Explorer, die sich auf die Persistenz beziehen. <http://msdn.microsoft.com/workshop/author/persistence/overview.asp> enthält weitere Einzelheiten über die Persistenz unter IE.

[70] Vollständige Einzelheiten über die SharedObject-Klasse und Flash-basierte Persistenz finden Sie auf der Adobe-Website unter <http://www.adobe.com/support/flash/action_scripts/local_shared_object/>. Ich habe von der Flash-basierten Persistenz durch Brad Neuberg erfahren, der die Pionierarbeit für ihre Verwendung in clientseitigem JavaScript in seinem AMASS-Projekts geleistet hat (<http://codinginparadise.org/projects/storage/README.html>). Als dieses Buch geschrieben wurde, war dieses Projekt noch in der Entwicklung. Weiterführende Informationen über die clientseitige Persistenz finden Sie unter Umständen auch in Brads Blog (<http://codinginparadise.org>).

[71] Es besteht auch die Möglichkeit, eine Persistenzklasse zu definieren, die so weit wie möglich Cookies verwendet und auf eine auf IE oder Flash basierende Persistenz zurückgreift, falls Cookies deaktiviert wurden.

# 19.6 Persistente Daten und Sicherheit

Die Dokumentation am Anfang von Beispiel 19-3 zeigt Sicherheitsbedenken auf, die Sie bei der Verwendung clientseitiger Persistenz im Auge behalten sollten. Alle Daten, die auf der Festplatte des Benutzers gespeichert werden, stehen dort in unverschlüsseltem Format. Sie sind daher neugierigen Benutzern zugänglich, die ebenfalls auf den Rechner Zugriff haben, sowie böswilliger Software (wie z.B. Spyware), die auf dem Rechner liegt. Aus diesem Grund sollte keine Art der clientseitigen Persistenz zur Speicherung empfindlicher Informationen verwendet werden, also von Passwörtern, Bankkontonummern usw. Denken Sie daran: Nur weil ein Benutzer etwas in ein Formularfeld eintippt, während er mit Ihrer Website kommuniziert, bedeutet das nicht, dass er diese Werte auf seiner Festplatte speichern möchte. Nehmen wir beispielsweise eine Kreditkartennummer. Das ist eine empfindliche Information, die Leute in ihren Geldbörsen versteckt halten. Wenn Sie diese Informationen als persistente Daten auf der Clientseite speichern, ist es fast so, als würden Sie die Kreditkartennummer auf ein Stück Papier schreiben und es auf die Tastatur des Benutzers kleben. Da Spyware überall eindringt (zumindest auf Windows-Plattformen), können Sie die Nummer praktisch gleich im Internet bekannt geben.

Denken Sie auch daran, dass viele Webbenutzer Websites misstrauen, die Cookies oder andere persistente Mechanismen verwenden, die irgendetwas in Richtung »Verfolgung« betreiben. Versuchen Sie die in diesem Kapitel besprochenen Persistenzmechanismen dazu zu verwenden, dem Benutzer auf Ihrer Site ein positives Erlebnis zu verschaffen; verwenden Sie Persistenzmechanismen nicht als Datensammelmechanismus.

# Kapitel 20. HTTP skripten

Das _Hypertext Transfer Protocol_ (HTTP) legt fest, wie Webbrowser Dokumente von Webservern anfordern und Formularinhalte an diese übertragen und wie Webserver auf diese Anfragen und Übertragungen antworten. Webbrowser bewältigen natürlich jede Menge HTTP. Normalerweise wird HTTP allerdings nicht von Skripten aus kontrolliert, sondern hinter den Kulissen verwendet, wenn der Benutzer einen Link anklickt, ein Formular übermittelt oder eine URL eingibt. Normalerweise, aber nicht immer: JavaScript-Code _kann_ in Skripten mit HTTP arbeiten.

HTTP-Requests können initiiert werden, wenn ein Skript die `location`-Eigenschaft eines Window-Objekts festlegt oder die `submit( )`-Methode eines Form-Objekts aufruft. In beiden Fällen lädt der Browser eine neue Seite ins Fenster und überschreibt das dort laufende Skript. Diese triviale Art, HTTP per Skript zu betreiben, kann in einer Webseite mit mehreren Frames sinnvoll sein, ist aber nicht das Thema dieses Kapitels. Vielmehr betrachten wir hier, wie JavaScript-Code mit einem Webserver kommunizieren kann, ohne den Webbrowser zu veranlassen, die gerade angezeigte Seite neu zu laden.

Die Tags `<img>`, `<iframe>` und `<script>` haben `src`-Attribute. Setzt ein Skript diese Eigenschaften auf eine URL, wird ein HTTP GET-Request gestartet, um den Inhalt der URL herunterzuladen. Ein Skript kann also Informationen an einen Webserver übergeben, indem die Information in den Query-String-Teil einer Bild-URL codiert und das `src`-Attribut eines `<img>`-Elements zu dieser URL gesetzt wird. Der Webserver muss zwar als Antwort ein Bild zurückgeben, aber das kann unsichtbar sein: beispielsweise ein transparentes Bild, das 1 Pixel hoch und 1 Pixel breit ist.[72]

`<iframe>`-Tags gibt es in HTML noch nicht so lange. Sie sind oft vielseitiger als `<img>`-Tags, da der Webserver ein Ergebnis zurückgeben kann, das – anders als eine binäre Bilddatei – durch das Skript gelesen werden kann. Um ein HTTP-Skript mit dem `<iframe>`-Tag zu schreiben, codiert das Skript zuerst Informationen für den Webserver in eine URL und setzt dann das Attribut `src` des `<iframe>` auf diese URL. Der Server erzeugt ein HTML-Dokument mit der Antwort und sendet dieses an den Webbrowser zurück, der es im `<iframe>` anzeigt. Das `<iframe>` muss für den Benutzer nicht sichtbar sein. Es kann zum Beispiel mit CSS versteckt werden. Ein Skript hat zur Serverantwort Zugang, indem es das Document-Objekt des `<iframe>` parst. Beachten Sie, dass diese Analyse den Beschränkungen der Same Origin Policy unterliegt, die in "13.8.2 Die Same Origin Policy" beschrieben wird.

Selbst das `<script>`-Tag hat ein `src`-Attribut, das gesetzt werden kann, um einen dynamischen HTTP-Request durchzuführen. Die Verwendung des `<script>`-Tags zum Durchführen von HTTP-Requests ist besonders attraktiv, da kein Parsen notwendig ist, wenn die Serverantwort aus JavaScript-Code besteht: Der JavaScript-Interpreter führt die Serverantwort einfach aus.

Das Steuern von HTTP über die Tags `<img>`, `<iframe>` und `<script>` ist zwar möglich, aber recht schwierig, wenn das Skript portabel sein soll. Dieses Kapitel zeigt Ihnen eine andere, leistungsfähigere Möglichkeit, dies zu erreichen. Das XMLHttpRequest-Objekt wird von modernen Browsern gut unterstützt und gibt Ihnen vollen Zugang zum HTTP-Protokoll. Neben normalen GET-Requests können Sie auch POST- und HEAD-Requests durchführen. XMLHttpRequest kann die Antwort des Webservers synchron oder asynchron zurückgeben und den Inhalt als Text- oder DOM-Dokument zur Verfügung stellen. Trotz seines Namens ist das XML-Objekt HttpRequest nicht auf XML-Dokumente beschränkt: Es kann jede Art von Text-Dokument abrufen. Das XMLHttpRequest-Objekt ist der Hauptbaustein einer Webanwendungsarchitektur, die unter dem Namen Ajax bekannt ist. Wir werden Ajax-Anwendungen besprechen, nachdem wir gesehen haben, wie XMLHttpRequest funktioniert.

Am Kapitelende werde ich auf das Thema HTTP per `<script>`-Tags zurückkommen und Ihnen zeigen, wie Sie sie verwenden können, wenn kein XMLHttpRequest-Objekt verfügbar ist.

# 20.1 Verwendung von XMLHttpRequest

HTTP per XMLHttpRequest läuft in drei Schritten ab:

  * Erzeugung eines XMLHttpRequest-Objekts

  * den HTTP-Request konfigurieren und ihn an einen Webserver übergeben

  * die Serverantwort synchron oder asynchron empfangen

Die folgenden Abschnitte beschreiben jeden dieser Schritte ausführlich. Die Beispiele in diesem Abschnitt und im Rest des Kapitels sind Teil einer größeren Einheit. Sie definieren Utility-Funktionen im Namensraum `HTTP` (siehe Kapitel 10). Keines der folgenden Beispiele enthält jedoch den Code, der diesen Namensraum erzeugt. Im herunterladbaren Beispielcode auf der Webseite zu diesem Buch finden Sie die Datei _http.js_ , die den Code enthält, der den Namensraum `HTTP` erzeugt, und Sie müssen dazu lediglich die Zeile `var HTTP = {};` zu den hier gezeigten Beispielen hinzufügen.

## 20.1.1 Wie komme ich an ein Request-Objekt?

Das XMLHttpRequest-Objekt wurde nie standardisiert, und seine Erzeugung unter dem Internet Explorer unterscheidet sich von anderen Plattformen. (Glücklicherweise ist die API zur Verwendung eines XMLHttpRequest-Objekts nach dessen Erstellung aber unter allen Plattformen dieselbe).

Bei den meisten Browsern erzeugen Sie ein XMLHttpRequest-Objekt mit einem einfachen Konstruktor-Aufruf:

    var request = new XMLHttpRequest( );

In Versionen vor Internet Explorer 7 hat IE keine eingebaute Konstruktorfunktion namens `XMLHttpRequest( )`. Unter IE 5 und 6 ist XMLHttpRequest ein ActiveX-Objekt, das Sie durch die Übergabe des Objektnamens an den Konstruktor `ActiveXObject( )` erzeugen müssen:

    var request = new ActiveXObject("Msxml2.XMLHTTP");

Leider ist der Objektname in verschiedenen Ausgaben der Microsoft XML-HTTP-Library unterschiedlich. Abhängig von der beim Client installierten Library müssen Sie eventuell stattdessen den folgenden Code verwenden:

    var request = new ActiveXObject("Microsoft.XMLHTTP");

Beispiel 20-1 Utility") ist eine plattformübergreifende Utility-Funktion mit dem Namen `HTTP.newRequest( )` zur Erzeugung von XMLHttpRequest-Objekten.

Beispiel 20-1. Die HTTP.newRequest( ) Utility

    // Eine Liste mit XMLHttpRequest-Factory-Funktionen zum Durchprobieren
    HTTP._factories = [
      function( ) { return new XMLHttpRequest( ); },
      function( ) { return new ActiveXObject("Msxml2.XMLHTTP"); },
      function( ) { return new ActiveXObject("Microsoft.XMLHTTP"); }
    ];

    // Wenn wir eine funktionierende Factory finden, speichern wir sie hier.
    HTTP._factory = null;

    // Erzeuge ein neues XMLHttpRequest-Objekt, und gib es zurück.
    //
    // Beim ersten Aufruf probieren wir die Factory-Funktionen aus, bis
    // wir eine finden, die einen Wert ungleich null zurückgibt und keine
    // Exception auslöst. Die funktionierende Factory merken wir uns für später.
    //
    HTTP.newRequest = function( ) {
      if (HTTP._factory != null) return HTTP._factory( );

      for(var i = 0; i < HTTP._factories.length; i++) {
        try {
          var factory = HTTP._factories[i];
          var request = factory( );
          if (request != null) {
            HTTP._factory = factory;
            return request;
          }
        }
        catch(e) {
          continue;
        }
      }

      // Kommen wir hier an, dann haben wir keine passende Factory.
      // Löse jetzt und für alle zukünftigen Aufrufe eine Exception aus.
      HTTP._factory = function( ) {
        throw new Error("XMLHttpRequest wird nicht unterstützt");
      }
      HTTP._factory( ); // Exception auslösen
    }

## 20.1.2 Einen Request übermitteln

Nachdem ein XMLHttpRequest-Objekt erzeugt wurde, besteht der nächste Schritt darin, einen Request an den Webserver zu senden. Das ist wiederum selbst ein mehrstufiger Vorgang. Zuerst rufen Sie die Methode `open( )` auf, um die von Ihnen gewünschte URL anzugeben, und dann die HTTP- _Methode_ des Requests. Die meisten HTTP-Requests werden mit der Methode GET durchgeführt, die einfach den Inhalt der URL herunterlädt. Eine andere nützliche Methode ist POST, die von den meisten HTML-Formularen verwendet wird: Mit ihr können Werte von benannten Variablen in den Request aufgenommen werden. HEAD ist ebenfalls eine nützliche HTTP-Methode: Sie weist den Server an, lediglich die mit der URL assoziierten Header zurückzugeben. Dadurch kann beispielsweise das Änderungsdatum eines Dokuments überprüft werden, ohne den Dokumentinhalt selbst herunterzuladen. Geben Sie die Methode und URL Ihrer Anfrage mit der `open( )`-Methode an:

    request.open("GET", url, false);

In der Standardeinstellung führt die `open( )`-Methode einen asynchronen XMLHttpRequest durch. Die Übergabe von `false` als drittes Argument fordert eine synchrone Antwort vom Server an. Asynchrone Antworten werden im Allgemeinen vorgezogen, aber synchrone Antworten sind etwas einfacher, weswegen wir diesen Fall zuerst besprechen.

Zusätzlich zum optionalen dritten Argument kann die `open( )`-Methode einen Namen und ein Passwort als optionale vierte und fünfte Argumente annehmen. Sie werden verwendet, wenn eine URL von einem Server abgefragt wird, der eine Autorisierung benötigt.

Die `open( )`-Methode sendet den Request nicht wirklich an den Webserver. Sie speichert einfach ihre Argumente, um sie später zu verwenden, wenn der Request tatsächlich gesendet wird. Bevor Sie die Abfrage senden, müssen Sie alle notwendigen Request-Header setzen. Hier sehen Sie einige Beispiele:[73]

    request.setRequestHeader("User-Agent", "XMLHttpRequest");
    request.setRequestHeader("Accept-Language", "en");
    request.setRequestHeader("If-Modified-Since", lastRequestTime.toString( ));

Beachten Sie, dass der Webbrowser automatisch relevante Cookies zu Ihrem Request hinzufügt. Sie müssen einen Cookie-Header nur dann ausdrücklich setzen, wenn Sie einen künstlichen Cookie an den Server senden wollen.

Nachdem Sie das Request-Objekt erzeugt, die `open( )`-Methode aufgerufen und die Header festgelegt haben, senden Sie den Request an den Server:

    request.send(null);

Das Argument der `send( )`-Funktion ist der Body des Requests. Bei HTTP GET-Requests ist dieser immer `null`. Bei POST-Requests sollte er die an den Server zu sendenden Formulardaten enthalten (siehe Beispiel 20-5-Utility")). Fürs Erste übergeben Sie einfach `null`. (Beachten Sie, dass das Argument `null` zwingend notwendig ist. XMLHttpRequest ist ein Objekt auf der Clientseite, und seine Methoden – zumindest unter Firefox – mögen keine ausgelassenen Argumente, anders als Funktionen im JavaScript-Sprachkern.)

## 20.1.3 Eine synchrone Antwort erhalten

Das XMLHttpRequest-Objekt speichert nicht nur die Einzelheiten des durchgeführten HTTP-Requests, sondern repräsentiert auch die Antwort des Servers. Wenn Sie `false` als drittes Argument an `open( )` übergeben, so ist die `send( )`-Methode synchron: Sie blockiert und gibt nichts zurück, bis die Serverantwort empfangen wurde.[74]

`send( )` gibt keinen Statuscode zurück. Nach der Rückgabe können Sie den vom Server zurückgegebenen HTTP-Statuscode mit der Eigenschaft `status` des Request-Objekts überprüfen. Die möglichen Werte des Codes werden vom HTTP-Protokoll festgelegt. Der Status 200 bedeutet, dass die Anfrage erfolgreich war und die Antwort vorliegt. Der Status 404 zeigt einen »not found«-Fehler an, der auftritt, wenn die URL nicht vorhanden ist.

Das XMLHttpRequest-Objekt stellt die Serverantwort als String über die Eigenschaft `responseText` des Request-Objekts zur Verfügung. Wenn die Antwort ein XML-Dokument ist, können Sie auf das Dokument auch als DOM-Document-Objekt über die Eigenschaft `responseXML` zugreifen. Damit XMLHttpRequest die Antwort an ein Document-Objekt übergeben kann, muss der Server sein XML-Dokument mit dem MIME-Typ »text/xml« identifizieren.

Ist eine Anfrage synchron, sieht der auf `send( )` folgende Code oft ungefähr so aus:

    if (request.status == 200) {
      // Wir haben die Serverantwort erhalten. Zeige den Antworttext an.
      alert(request.responseText);
    }
    else {
      // Etwas ist schiefgelaufen. Zeige Fehlercode und -meldung an.
      alert("Error " + request.status + ": " + request.statusText);
    }

Zusätzlich zum Statuscode und dem Antworttext oder -dokument stellt das XMLHttpRequest-Objekt auch den Zugriff auf die vom Webserver zurückgegebenen HTTP-Header bereit. `getAllResponseHeaders( )` gibt die Antwort-Header als ungeparsten Textblock zurück, und `getResponseHeader( )` gibt den Wert eines angegebenen Headers zurück. Ein Beispiel:

    if (request.status == 200) { // Stellen Sie sicher, dass es keine Fehler gab
      // Stellen Sie sicher, dass die Antwort ein XML-Dokument ist
      if (request.getResponseHeader("Content-Type") == "text/xml") {
        var doc = request.responseXML;
        // Machen Sie jetzt etwas mit dem Antwortdokument
      }
    }

Bei der synchronen Verwendung von XMLHttpRequest besteht ein ernstes Problem: Wenn der Webserver seine Antwort abbricht, blockiert die `send( )`-Methode eine lange Zeit. Die Ausführung von JavaScript hält an, und der Webbrowser kann so aussehen, als ob er sich aufgehängt hätte (das hängt natürlich von der Plattform ab). Hängt der Server sich während eines normalen Ladevorgangs auf, kann der Benutzer im Browserfenster einfach auf **Stop** klicken und es mit einem anderen Link oder einer anderen URL versuchen. Aber für XMLHttpRequest gibt es keine **Stop** -Schaltflächen. Die Methode `send( )` bietet keine andere Möglichkeit an, die maximale Wartezeit anzugeben, und das Ausführungsmodell von JavaScript auf der Clientseite sieht nur einen Thread vor. Das verbietet dem Skript, einen synchronen XMLHttpRequest nach dessen Absenden zu unterbrechen.

Diese Probleme lassen sich umgehen, indem Sie XMLHttpRequest asynchron verwenden.

## 20.1.4 Umgang mit einer asynchronen Response

Um ein XMLHttpRequest-Objekt im asynchronen Modus zu verwenden, übergeben Sie `true` als drittes Argument der `open( )`-Methode (oder übergehen Sie einfach das dritte Argument: `true` ist der Default). Daduch sendet die `send( )`-Methode die Anfrage an den Server und kehrt sofort zurück. Wenn die Antwort des Servers ankommt, steht sie über das XMLHttpRequest-Objekt zur Verfügung. Dazu werden die gleichen Eigenschaften verwendet, die oben für die synchrone Verwendung beschrieben wurden.

Eine asynchrone Antwort vom Server ist wie ein asynchroner Mausklick vom Benutzer: Wenn sie stattfindet, müssen Sie benachrichtigt werden. Das machen Sie mit einem Event-Handler. Bei XMLHttpRequest wird der Event-Handler über die Eigenschaft `onreadystatechange` festgelegt. Wie der Name der Eigenschaft schon sagt, wird die Event-Handler-Funktion aufgerufen, wenn sich der Wert der Eigenschaft `readyState` ändert. `readyState` ist eine ganze Zahl, die den Status der HTTP-Anfrage angibt, und die möglichen Werte sind in Tabelle 20-1 aufgezählt. Das XMLHttpRequest-Objekt definiert keine symbolischen Konstanten für die in der Tabelle aufgeführten fünf Werte.

Tabelle 20-1. Werte für XMLHttpRequest.readyState

readyState | Bedeutung

---|---

0 | `open( )` wurde bis jetzt noch nicht aufgerufen.

1 | `open( )` wurde aufgerufen, aber `send( )` noch nicht.

2 | `send( )` wurde aufgerufen, aber der Server hat noch nicht geantwortet.

3 | Daten werden vom Server empfangen. `readyState` 3 unterscheidet sich in Firefox und Internet Explorer etwas; siehe "20.1.4.1 Hinweise für readyState 3".

4 | Die Antwort des Servers ist vollständig.

Da `XMLHttpRequest` nur diesen einen Event-Handler hat, wird er für alle möglichen Events aufgerufen. Ein typischer `onreadystatechange`-Handler wird erst einmal beim Aufruf von `open( )` und dann einmal beim Aufruf von `send( )` aufgerufen. Wenn der erste Teil der Serverantwort eintrifft, erfolgt ein weiterer Aufruf, und ein letzter erfolgt, wenn die Antwort vollständig ist. Im Gegensatz zu den meisten Events in JavaScript auf der Clientseite wird an den `onreadystatechange`-Handler kein Objekt übergeben. Um festzustellen, warum Ihr Event-Handler aufgerufen wurde, müssen Sie sich die `readyState`-Eigenschaft des XMLHttpRequest-Objekts ansehen. Leider wird das XMLHttpRequest-Objekt auch nicht als Argument des Event-Handlers übergeben. Sie müssen die Event-Handler-Funktion also so definieren, dass sie auf das Request-Objekt zugreifen kann. Ein typischer Event-Handler einer asynchronen Anfrage sieht so aus:

    // Erzeuge mit der oben definierten Utility einen XMLHttpRequest
    var request = HTTP.newRequest( );

    // Registriere einen Event-Handler für asynchrone Notifikationen.
    // Dieser Code schreibt vor, was mit der Antwort zu tun ist, und wird in eine
    // eingebettete Funktion gestellt, bevor der Request überhaupt gesendet wird.
    request.onreadystatechange = function( ) {
      if (request.readyState == 4) {    // Die Anfrage ist abgeschlossen
          if (request.status == 200)    // Sie war erfolgreich
               alert(request.responseText); // Zeige die Antwort des Servers an
    HTTP.getText = function(url, callback) {
      var request = HTTP.newRequest( );
      request.onreadystatechange = function( ) {
        if (request.readyState == 4 && request.status == 200)
          callback(request.responseText);
      }
      request.open("GET", url);
      request.send(null);
    };

### 20.1.4.1 Hinweise für readyState 3

Das Objekt XMLHttpRequest wurde nie standardisiert und die Browser handhaben `readyState` 3 unterschiedlich. Beispielsweise ruft Firefox bei großen Downloads den `onreadystatechange`-Handler mehrmals in `readyState` 3 auf, um den derzeitigen Stand des Downloads mitzuteilen. Ein Skript kann diese mehrfachen Aufrufe verwenden, um dem Benutzer den Stand des Downloads anzuzeigen. Im Gegensatz dazu interpretiert Internet Explorer den Event-Handler im engeren Sinne und ruft ihn nur auf, wenn sich der `readyState`-Wert ändert. Das bedeutet, dass er für `readyState` 3 nur einmal aufgerufen wird, unabhängig davon, wie groß das heruntergeladene Dokument ist.

Zusätzlich unterscheiden sich Browser darin, welcher Teil der Serverantwort in `readyState` 3 zur Verfügung steht. Obwohl Status 3 bedeutet, dass zumindest ein Teil der Antwort vom Server angekommen ist, sagt die Microsoft-Dokumentation für XMLHttpRequest ausdrücklich, dass eine Abfrage von `responseText` in diesem Status einem Fehler gleichkommt. Bei anderen Browsern scheint es ein undokumentiertes Feature zu sein, dass `responseText` den Teil der Serverantwort zurückgibt, der schon angekommen ist.

Leider hat keiner der bekannten Browseranbieter sein XMLHttpRequest-Objekt ausreichend dokumentiert. Bis zum Erscheinen eines Standards oder zumindest einer eindeutigen Dokumentation ist es am sichersten, den Wert von `readyState` zu ignorieren, wenn er nicht 4 ist.

## 20.1.5 XMLHttpRequest Security

Aufgrund der Same Origin Policy (siehe "13.8.2 Die Same Origin Policy") kann das XMLHttpRequest-Objekt HTTP-Requests nur an den Server senden, von dem das verwendete Dokument heruntergeladen wurde. Das ist eine vernünftige Einschränkung, die Sie aber zur Not umgehen können, indem Sie ein Skript auf der Serverseite als Proxy verwenden, das den Inhalt einer anderen URL für Sie holt.

Diese Sicherheitsbeschränkung für XMLHttpRequest hat eine sehr wichtige Auswirkung: XMLHttpRequest führt HTTP-Requests aus und ist nicht mit anderen URL-Modellen kompatibel. Zum Beispiel funktioniert es nicht mit URLs, die das `file://`-Protokoll verwenden. Das bedeutet, dass Sie XMLHttpRequest-Skripte nicht von Ihrem lokalen Dateisystem aus testen können. Sie müssen das Testskript auf einem Webserver speichern (oder einen Server auf Ihrem Computer laufen lassen). Damit Ihre Testskripte ihre eigenen HTTP-Requests durchführen können, müssen sie über HTTP an Ihren Webbrowser übertragen werden.

* * *

[72] Bilder dieser Art werden ab und zu _Web Bugs_ genannt. Sie haben einen schlechten Ruf, da Datenschutzprobleme entstehen, wenn Web Bugs verwendet werden, um Informationen an einen anderen Server zu übermitteln als an den, von dem aus die Webseite geladen wurde. Eine übliche und legitime Verwendung des Web Bugs über einen dritten Server ist zur Trefferzählung und Verkehrsanalyse. Wenn das Skript der Webseite die `src`-Eigenschaft eines Bildes veranlasst, Informationen an den Server zurückzusenden, über den die Seite ursprünglich geladen wurde, brauchen Sie sich über Datenschutzprobleme keine Gedanken zu machen.

[73] Einzelheiten zum HTTP-Protokoll sprengen den Umfang des Buches. Konsultieren Sie eine HTTP-Referenz für Details zu diesem und weiteren Headern, die bei HTTP-Requests gesetzt werden können.

[74] XMLHttpRequest hat unheimlich leistungsfähige Features, aber seine API ist nicht besonders gut gestaltet. Beispielsweise sollte der Boolesche Wert, der synchrones oder asynchrones Verhalten festlegt, eigentlich ein Argument der Methode `send( )` sein.

# 20.2 XMLHttpRequest-Beispiele und Utilities

Am Anfang dieses Kapitels habe ich Ihnen die Utility-Funktion `HTTP.newRequest( )` vorgestellt, die für jeden beliebigen Browser ein XMLHttpRequest-Objekt beschaffen kann. Zusätzlich können Utility-Funktionen den Gebrauch von XMLHttpRequest vereinfachen. Die hier folgenden Unterabschnitte zeigen Beispiele von Utilities.

## 20.2.1 Einfache GET-Utilities

Beispiel 20-2-Utility") ist eine sehr einfache Funktion, die den häufigsten Gebrauch von XMLHttpRequest abdecken kann: Übergeben Sie ihr einfach die zu holende URL und die Funktion, an die der Text der URL übergeben werden soll.

Beispiel 20-2. Die HTTP.getText( )-Utility

    /**
     * Mit XMLHttpRequest holen Sie den Inhalt der angegebenen URL per
     * HTTP GET-Request. Kommt die Antwort an, übergeben Sie sie (als
     * Klartext) an die angegebene Callback-Funktion.
     *
     * Diese Funktion blockiert nicht und hat keinen Rückgabewert.
     */
    HTTP.getText = function(url, callback) {
      var request = HTTP.newRequest( );
      request.onreadystatechange = function( ) {
        if (request.readyState == 4 && request.status == 200)
          callback(request.responseText);
      }
      request.open("GET", url);
      request.send(null);
    };

Beispiel 20-3-Utility") ist eine triviale Variante, die XML-Dokumente holt und ihre geparste Repräsentation an eine Callback-Funktion übergibt.

Beispiel 20-3. Die HTTP.getXML( )-Utility

    HTTP.getXML = function(url, callback) {
      var request = HTTP.newRequest( );
      request.onreadystatechange = function( ) {
        if (request.readyState == 4 && request.status == 200)
          callback(request.responseXML);
      }
      request.open("GET", url);
      request.send(null);
    };

## 20.2.2 Nur Header holen

Ein Feature von XMLHttpRequest ist, dass es Sie die zu verwendende HTTP-Methode aussuchen lässt. Der HTTP HEAD-Request fordert beim Server die Header einer bestimmten URL an, ohne dabei den Inhalt der URL zurückzugeben. Das kann beispielsweise gemacht werden, um das Änderungsdatum einer Ressource vor dem Herunterladen zu überprüfen.

Beispiel 20-4-Utility") zeigt Ihnen, wie Sie einen HEAD-Request ausführen. Es enthält eine Funktion zur Übergabe der HTTP-Header-Name/Wert-Paare und zum Speichern dieser Paare als Namen und Werte der Eigenschaften eines JavaScript-Objekts. Außerdem führt es eine Fehlerbehandlungsfunktion ein, die aufgerufen wird, wenn der Server 404 oder einen anderen Fehlercode zurückgibt.

Beispiel 20-4. Die HTTP.getHeaders( )-Utility

    /**
     * Per HTTP HEAD-Request holen Sie sich die Header der angegebenen URL.
     * Eingehende Header werden mit HTTP.parseHeaders( ) geparst, und das
     * Rückgabeobjekt wird an die angegebene Callback-Funktion übergeben. * Schickt der Server einen Fehlercode, rufen Sie stattdessen die angegebene
     * errorHandler-Funktion auf. Ist kein Error-Handler angegeben, übergeben Sie null an die
     * Callback-Funktion.
     */
    HTTP.getHeaders = function(url, callback, errorHandler) {
      var request = HTTP.newRequest( );
      request.onreadystatechange = function( ) {
        if (request.readyState == 4) {
          if (request.status == 200) {
            callback(HTTP.parseHeaders(request));
          }
          else {
            if (errorHandler) errorHandler(request.status,
                            request.statusText);
            else callback(null);
          }
        }
      }
      request.open("HEAD", url);
      request.send(null);
    };

    // Parse die Response-Header eines XMLHttpRequest-Objekts, und
    // gib die Header-Namen und -Werte als Eigenschaftsnamen und -werte
    // eines neuen Objekts zurück.
    HTTP.parseHeaders = function(request) {
      var headerText = request.getAllResponseHeaders( ); // Text vom Server
      var headers = {}; // Das ist unser Rückgabewert
      var ls = /^\s*/; // Regulärer Ausdruck für führende Leerzeichen
      var ts = /\s*$/; // Regulärer Ausdruck für Leerzeichen am Ende

      // Brich die Header in Zeilen auf
      var lines = headerText.split("\n");
      // Zeilen durchlaufen
      for(var i = 0; i < lines.length; i++) {
        var line = lines[i];
        if (line.length == 0) continue; // Leerzeilen überspringen
        // Jede Zeile am ersten Doppelpunkt trennen und Whitespace entfernen
        var pos = line.indexOf(':');
        var name = line.substring(0, pos).replace(ls, "").replace(ts, "");
        var value = line.substring(pos+1).replace(ls, "").replace(ts, "");
        // Name/Wert-Paar des Headers in einem JavaScript-Objekt speichern
        headers[name] = value;
      }
      return headers;
    };

## 20.2.3 HTTP POST

HTML-Formulare werden oft mit der HTTP POST-Methode an Webserver gesendet. Bei POST-Requests werden die Daten nicht in die URL codiert, sondern im Body des Requests an den Server übergeben. Da Request-Parameter in die URL eines GET-Requests codiert werden, ist die GET-Methode nur geeignet, wenn der Request beim Server keine Nebenwirkungen auslöst – also wenn von wiederholten GET-Requests für die gleiche URL mit den gleichen Parametern die Rückgabe der gleichen Ergebnisse erwartet werden kann. Verursacht ein Request Nebeneffekte (z.B. wenn der Server einige der Parameter in einer Datenbank speichert), sollte stattdessen ein POST-Request verwendet werden.

Beispiel 20-5-Utility") zeigt, wie ein POST-Request mit Hilfe eines XMLHttpRequest-Objekts durchgeführt wird. Die `HTTP.post( )`-Methode verwendet die `HTTP.encodeFormData( )`-Funktion, um die Eigenschaften eines Objekts in ein Stringformat zu konvertieren, das als Body eines POST-Requests verwendet werden kann. Der String wird dann an die `XMLHttpRequest.send( )`-Methode übergeben und wird zum Body des Requests. (Der von `HTTP.encodeFormData( )` zurückgebene String kann auch an die GET-URL angehängt werden; verwenden Sie einfach ein Fragezeichen, um die URL von den Daten zu trennen.) Beispiel 20-5-Utility") verwendet außerdem die Methode `HTTP._getResponse( )`. Sie parst die Serverantwort abhängig von deren Typ und wird im nächsten Abschnitt implementiert.

Beispiel 20-5. Die HTTP.post( )-Utility

    /**
     * Sende einen HTTP POST-Request an die angegebene URL. Verwende Namen
     * und Werte der Eigenschaften des values-Objekts als Body des Requests.
     * Parse die Serverantwort je nach Content-Type, und übergib das
     * Ergebnis an die Callback-Funktion. Tritt ein HTTP-Fehler auf,
     * so rufe die angegebene errorHandler-Funktion auf.
     * Ist kein Error-Handler vorhanden, übergib null an die Callback-Funktion.
     /**
    HTTP.post = function(url, values, callback, errorHandler) {
      var request = HTTP.newRequest( );
      request.onreadystatechange = function( ) {
        if (request.readyState == 4) {
          if (request.status == 200) {
            callback(HTTP._getResponse(request));
          }
          else {
            if (errorHandler) errorHandler(request.status,
                            request.statusText);
            else callback(null);
          }
        }
      }

      request.open("POST", url);
      // Dieser Header informiert den Server, wie der Body des Requests
      // interpretiert werden soll.
      request.setRequestHeader("Content-Type",
                   "application/x-www-form-urlencoded");
      // Codiere die Eigenschaften des values-Objekts, und sende sie als
      // Body des Requests.
      request.send(HTTP.encodeFormData(values));
    };

    /**
     * Eigenschafts-Name/Wert-Paare eines Objekts so codieren, als ob sie von
     * einem HTML-Formular stammen, und zwar im Format application/x-www-form-urlencoded
     */
    HTTP.encodeFormData = function(data) {
      var pairs = [];
      var regexp = /%20/g; // Ein regulärer Ausdruck für ein codiertes Leerzeichen

      for(var name in data) {
        var value = data[name].toString( );
        // Erzeuge ein Name/Wert-Paar, aber codiere erst Namen und Wert,
        // Die globale Funktion encodeURIComponent ist fast perfekt dafür;
        // sie codiert Leerzeichen aber als %20 statt "+". Wir müssen das
        // mit String.replace( ) nachbessern
        var pair = encodeURIComponent(name).replace(regexp,"+") + '=' +
          encodeURIComponent(value).replace(regexp,"+");
        pairs.push(pair);
      }

      // Alle Name/Wert-Paare mit & aneinander anhängen
      return pairs.join('&');
    };

Beispiel 21-14 ist ein weiteres Beispiel, das mit einem XMLHttpRequest-Objekt einen POST-Request durchführt. In diesem Beispiel wird ein Webservice aufgerufen, und statt der Übergabe von Formularwerten im Body des Requests wird der Text eines XML-Dokuments übergeben.

## 20.2.4 HTML, XML und JSON-codierte Responses

Bei den meisten bisherigen Beispielen wird die Serverantwort auf einen HTTP-Request als Klartextwert behandelt. Das ist durchaus zulässig, und es ist nirgendwo vorgeschrieben, dass Webserver keine Dokumente mit einem Content-Type von »text/plain« zurückgeben dürfen. Ihr JavaScript-Code kann solche Responses mit den verschiedenen String-Methoden parsen und mit ihnen machen, was immer Sie wollen.

Sie können die Serverantwort auch dann als Klartext behandeln, wenn sie einen anderen Content-Type aufweist. Wenn der Server beispielsweise ein HTML-Dokument zurückgibt, können Sie dessen Inhalt mit der Eigenschaft `responseText` abrufen und ihn dann zum Setzen der `innerHTML`-Eigenschaft eines Dokumentelements verwenden.

Sie können die Serverantwort aber auch auf andere Arten verarbeiten. Wie am Anfang dieses Kapitels beschrieben, können Sie eine geparste Darstellung des XML-Dokuments über die `responseXML`-Eigenschaft abfragen, wenn der Server eine Response mit dem Content-Type »text/xml« sendet. Der Wert dieser Eigenschaft ist ein DOM-Document-Objekt, das Sie mit DOM-Methoden durchsuchen und durchlaufen können.

Beachten Sie jedoch, dass XML als Datenformat nicht immer die beste Lösung ist. Wenn der Server Daten zur Bearbeitung an das JavaSkript-Skript übergeben will, ist es ineffizient, diese Daten auf der Serverseite in XML-Format zu codieren, das XMLHttpRequest-Objekt zu veranlassen, diese Daten in einen Baum von DOM-Knoten zu parsen und dann Ihr Script diesen Baum durchlaufen zu lassen, um die Daten wieder zu extrahieren. Es ist einfacher, den Server die Daten mit dem JavaScript-Objekt und Array als Text codieren zu lassen und den JavaScript-Quelltext an den Webbrowser zu übergeben. Dann »parst« das Skript die Antwort einfach, indem sie an die JavaScript-`eval( )`-Methode übergeben wird.

Die Datencodierung als JavaScript-Objekt- und -Array-Literale wird als JSON oder JavaScript Object Notation[75] bezeichnet. Hier sehen Sie XML- und JSON-Codierungen der gleichen Daten:

    <!-- XML-codiert -->
    <author>
     <name>Wendell Berry</name>
     <books>
      <book>The Unsettling of America</book>
      <book>What are People For?</book>
     </books>
    </author>

    // JSON-codiert
    {
     "name": "Wendell Berry",
     "books": [
      "The Unsettling of America",
      "What are People For?"
     ]
    }

Die `HTTP.post( )`-Funktion, die in Beispiel 20-5-Utility") gezeigt ist, ruft die `HTTP._getResponse( )`-Funktion auf, die den `Content-Type`-Header untersucht, um das Format der Response zu bestimmen. Beispiel 20-6") ist eine einfache Implementierung von `HTTP._getResponse( )`, die XML-Dokumente als Document-Objekte zurückgibt, JavaScript- bzw. JSON-Dokumente mit `eval( )` auswertet und sonstigen Inhalt als Klartext zurückgibt.

Beispiel 20-6. HTTP._getResponse( )

    HTTP._getResponse = function(request) {
      // Vom Server zurückgesandten Content-Type prüfen
      switch(request.getResponseHeader("Content-Type")) {
      case "text/xml":
        // Wenn es ein XML-Dokument ist, geparstes Document-Objekt verwenden.
        return request.responseXML;

      case "text/json":
      case "text/javascript":
      case "application/javascript":
      case "application/x-javascript":
        // Wenn die Response JavaScript-Code oder ein JSON-codierter Wert ist,
        // den Text an eval( ) senden, um ihn in einen JavaScript-Wert zu parsen.
        // NB: Machen Sie das nur, wenn Sie dem Server trauen, von dem der JavaScript-    // Code kommt!
        return eval(request.responseText);

      default:
        // In allen anderen Fällen: Response als Text ansehen und String zurückgeben.
        return request.responseText;
      }
    };

Die `eval( )`-Methode zum Parsen von JSON-codierten Daten, wie sie in Beispiel 20-6") gezeigt wird, sollten Sie nicht verwenden, wenn Sie sich nicht sicher sind, dass der Webserver niemals bösartigen ausführbaren JavaScript-Code statt korrekt codierten JSON-Daten schickt. Eine sichere Alternative ist die Benutzung eines JSON-Decoders, der JavaScript-Objekt-Literale »von Hand« parst, ohne dabei `eval( )` aufzurufen.

## 20.2.5 Zeitabbruch eines Requests

Ein Mangel des XMLHttpRequest-Objekts ist das Fehlen eines Mechanismus, mit dem sich ein Timeout-Wert für einen Request angeben lässt. Im Fall von synchronen Requests ist das ein schwerer Mangel. Wenn sich des Server aufhängt, verbleibt der Webbrowser in der `send( )`-Methode, und alles friert ein. Asynchrone Requests können nicht zu einem Einfrieren führen, weil die `send( )`-Methode nicht blockiert und der Webbrowser damit weiter Benutzerevents verarbeiten kann. Ein Timeout-Problem haben Sie aber trotzdem. Nehmen wir mal an, Ihre Anwendung sendet einen HTTP-Request mit einem XMLHttpRequest-Objekt, wenn der Benutzer auf einen Button klickt. Um mehrfache Requests zu verhindern, empfiehlt es sich, den Button bis zum Eintreffen der Response zu deaktivieren. Was aber, wenn der Server ausfällt oder es aus irgendwelchen Gründen nicht schafft, den Request zu beantworten? Dann friert der Browser zwar nicht ein, aber Ihre Anwendung ist jetzt mit einem deaktivierten Button eingefroren.

Um diese Art Problem zu verhindern, kann es nützlich sein, Ihre eigenen Timeouts mit der `Window.setTimeout( )`-Funktion festzulegen, wenn Sie HTTP-Requests starten. Normalerweise bekommen Sie Ihre Response, bevor Ihr Timeout-Handler ausgelöst wird; in diesem Fall verwenden Sie einfach die `Window.clearTimeout( )`-Funktion, um den Timeout abzubrechen. Wenn Ihr Timeout aber ausgelöst wird, bevor der XMLHttpRequest `readyState` 4 erreicht hat, können Sie den Request mit der `XMLHttpRequest.abort( )`-Methode abbrechen. Danach werden Sie üblicherweise den Benutzer davon unterrichten, dass der Request fehlgeschlagen ist (beispielsweise mit `Window.alert( )`). Wenn Sie, wie in unserem hypothetischen Beispiel, einen Button vor dem Absenden des Requests deaktiviert haben, werden Sie ihn jetzt vermutlich reaktivieren wollen, nachdem der Timeout eingetreten ist.

Beispiel 20-7-Utility") definiert eine `HTTP.get( )`-Funktion, die diese Timeout-Technik vorführt. Dabei handelt es sich um eine fortgeschrittene Version der `HTTP.getText( )`-Methode aus Beispiel 20-2-Utility"), die viele der in vorhergehenden Beispielen eingeführten Features integriert, einschließlich eines Error-Handlers, Request-Parametern und der bereits beschriebenen `HTTP._getResponse( )`-Methode. Sie ermöglicht es der aufrufenden Funktion auch, eine optionale Callback-Funktion zur Fortschrittsüberwachung anzugeben, die immer dann aufgerufen wird, wenn der `onreadystatechange`-Handler mit einem `readyState` aufgerufen wird, der nicht 4 ist. In Browsern wie Firefox, die diesen Handler mehrfach in Status 3 aufrufen, ermöglicht ein solcher Callback einem Skript die Anzeige von Download-Feedback für den Benutzer.

Beispiel 20-7. Die HTTP.get( )-Utility

    /**
     * Sende einen HTTP GET-Request für die angegebene URL. Wenn eine erfolgreiche
     * Response eintrifft, wird sie in ein Objekt nach Maßgabe des Content-Type-Headers
     * konvertiert und der angegebenen Callback-Funktion übergeben.
     * Zusätzliche Argumente können als Eigenschaften des options-Objekts angegeben werden.
     *
     * Wenn eine Response mit Fehler eintrifft (z.B., ein 404 Not Found),
     * werden Statuscode und -meldung an options.errorHandler übergeben.
     * Wenn kein Error-Handler angegeben ist, wird die Callback-
     * Funktion mit einem null-Argument aufgerufen.
     *
     * Wenn das options.parameters-Objekt angegeben ist, werden seine Eigenschaften
     * als Namen und Werte von Request-Parametern verwendet. Sie werden
     * mit HTTP.encodeFormData( ) in einen URL-codierten String konvertiert und
     * nach einem '?' an die URL angehängt.
     *
     * Wenn eine options.progressHandler-Funktion angegeben ist, wird sie
     * jedes Mal aufgerufen, wenn die readyState-Eigenschaft auf einen Wert kleiner
     * gesetzt 4 wird. Jedem Aufruf der Progress-Handler-Funktion wird eine
     * Zahl übergeben, die angibt, wie oft die Funktion aufgerufen worden ist.
     *
     * Wenn ein options.timeout-Wert angegeben ist, wird der XMLHttpRequest
     * abgebrochen, wenn er nicht vor Ablauf der angegebenen Anzahl Millisekunden
     * fertig geworden ist. Wenn der Timeout abläuft und ein
     * options.timeoutHandler angegeben ist, wird diese Funktion mit der
     * angeforderten URL als Argument aufgerufen.
     /**
    HTTP.get = function(url, callback, options) {
      var request = HTTP.newRequest( );
      var n = 0;
      var timer;
      if (options.timeout)
        timer = setTimeout(function( ) {
                    request.abort( );
                    if (options.timeoutHandler)
                      options.timeoutHandler(url);
                  },
                  options.timeout);

      request.onreadystatechange = function( ) {
        if (request.readyState == 4) {
          if (timer) clearTimeout(timer);
          if (request.status == 200) {
            callback(HTTP._getResponse(request));
          }
          else {
            if (options.errorHandler)
              options.errorHandler(request.status,
                         request.statusText);
            else callback(null);
          }
        }
        else if (options.progressHandler) {
          options.progressHandler(++n);
        }
      }

      var target = url;
      if (options.parameters)
        target += "?" + HTTP.encodeFormData(options.parameters)
      request.open("GET", target);
      request.send(null);
    };

* * *

[75] Weitere Einzelheiten zu JSON finden Sie unter <http://json.org>. Die Idee stammt von Douglas Crockford, und diese Website enthält Verweise auf JSON-Codierer und -Decodierer für verschiedene Programmiersprachen; sie kann zur Datencodierung nützlich sein, selbst wenn Sie JavaScript nicht verwenden.

# 20.3 Ajax und dynamisches Skripten

Der Begriff _Ajax_ beschreibt eine Architektur für Webanwendungen, in denen skriptkontrolliertes HTTP und das XMLHttpRequest-Objekt eine prominente Rolle spielen. (Für viele sind das XMLHttpRequest-Objekt und Ajax sogar mehr oder minder dasselbe.) Ajax ist ein (meist nicht komplett großgeschriebenes) Akronym für _Asynchronous JavaScript and XML_.[76] Der Begriff wurde von Jesse James Garrett geprägt und erschien zum ersten Mal in seinem Essay »Ajax: A New Approach to Web Applications« im Februar 2005. Sie können dieses richtungsweisende Essay unter <http://www.adaptivepath.com/publications/essays/archives/000385.php> finden (auf Englisch).

Das XMLHttpRequest-Objekt, auf dem Ajax beruht, gab es in Webbrowsern von Microsoft und Netscape/Mozilla bereits ungefähr vier Jahre lang, bevor Garretts Essay veröffentlicht wurde, es hatte aber nie viel Aufmerksamkeit bekommen.[77] In 2004 änderte sich das, als Google seine Webmail-Anwendung Gmail vorstellte, die XMLHttpRequest verwendet. Die Kombination dieses weithin sichtbaren und professionell ausgeführten Beispiels zusammen mit Garretts Essay von Anfang 2005 ließ das Interesse an Ajax rasant wachsen.

Das zentrale Feature einer Ajax-Anwendung ist, dass sie skriptgesteuertes HTTP verwendet, um mit einem Webserver ohne Neuladen von Seiten zu kommunizieren. Da die ausgetauschte Datenmenge oft gering ist und weil der Browser kein Dokument (und die damit verbundenen Stylesheets und Skripten) parsen und darstellen muss, ist die Ansprechzeit erheblich kürzer. Das Ergebnis sind Webanwendungen, die sich mehr wie traditionelle Desktopanwendungen verhalten.

Ein optionales Feature von Ajax-Anwendungen ist die Verwendung von XML zur Codierung des Datenaustausches zwischen Client und Server. Kapitel 21 zeigt, wie man clientseitiges JavaScript dazu verwenden kann, XML-Daten zu manipulieren, einschließlich der Ausführung von XPath-Abfragen und XSL-Transformationen von XML nach HTML. Einige Ajax-Anwendungen verwenden XSLT, um den Inhalt (XML-Daten) von der Darstellung (HTML-Formatierung, durch ein XSL-Stylesheet ausgedrückt) zu trennen. Diese Vorgehensweise hat die zusätzlichen Vorteile, dass die zwischen Server und Client zu transferierende Datenmenge reduziert wird und die Transformation vom Server auf den Client verlegt wird.

Man kann Ajax in einen RPC-Mechanismus formalisieren.[78] In dieser Formel verwenden Webentwickler Ajax-Bibliotheken im Hintergrund sowohl auf Client- als auch Serverseite, um Kommunikation zwischen Client und Server auf höherer Ebene zu ermöglichen. Dieses Kapitel beschreibt keine solchen RPC-over-Ajax-Bibliotheken und konzentriert sich stattdessen auf die darunterliegende Technologie, die Ajax funktionsfähig macht.

Ajax ist eine junge Anwendungsarchitektur, und Garretts Essay, das sie beschreibt, endet mit einem Aufruf zur Tat, den es sich hier wiederzugeben lohnt:

> Die größten Herausforderungen in der Erstellung von Ajax-Anwendungen sind nicht technischer Natur. Die Kerntechnologien für Ajax sind ausgereift, stabil und werden gut verstanden. Die Herausforderungen richten sich stattdessen an die Designer dieser Anwendungen: Vergessen wir, was wir glauben, über die Beschränkungen des Webs zu wissen, und stellen wir uns eine breitere und reichere Palette an Möglichkeiten vor.
>
> Das wird noch spaßig.

## 20.3.1 Ajax-Beispiel

Die XMLHttpRequest-Beispiele, die wir bisher in diesem Kapitel gesehen haben, waren Utility-Funktionen, die demonstrieren, wie man das XMLHttpRequest-Objekt verwendet. Sie haben nicht gezeigt, _warum_ Sie das Objekt vielleicht verwenden möchten, oder erläutert, _was_ Sie mit ihm erreichen können. Wie das Zitat vom Garrett zeigt, eröffnet die Ajax-Architektur viele Möglichkeiten – und wir stehen erst am Anfang, sie zu ergründen. Das nächste Beispiel ist ein einfaches, aber es fängt etwas vom Geschmack und der Bequemlichkeit der Ajax-Architektur ein.

Beispiel 20-8 ist ein unauffälliges Skript, das Event-Handler auf Links im Dokument registriert, sodass sie Tooltips anzeigen, wenn der Benutzer mit der Maus über sie fährt. Für Links, die zum selben Server zurückführen, von dem das Dokument geladen wurde, verwendet das Skript XMLHttpRequest, um einen HTTP HEAD-Request zu starten. Aus den zurückgegebenen Headern extrahiert es den Content-Type, die Größe und den Änderungszeitpunkt des gelinkten Dokuments und zeigt diese Informationen im Tooltip an (siehe Abbildung 20-1). Der Tooltip stellt daher eine Art Vorschau auf das Linkziel dar und kann einem Benutzer helfen zu entscheiden, ob er klicken soll oder nicht.

Abbildung 20-1. Ein Ajax-Tooltip

Der Code stützt sich auf die Tooltip-Klasse, die in Beispiel 16-4 entwickelt wurde, braucht aber die Erweiterung dieser Klasse aus Beispiel 17-3 nicht. Zusätzlich benutzt er das Geometry-Modul aus Beispiel 14-2 und die Utility-Funktion `HTTP.getHeaders( )`, die wir in Beispiel 20-4-Utility") entwickelt haben. Der Code enthält mehrere Ebenen an Asynchronität, und zwar in der Form eines `onload`-Event-Handlers, eines `onmouseover`-Event-Handlers, eines Timers und einer Callback-Funktion für das XMLHttpRequest-Objekt. Dadurch entstehen tief verschachtelte Funktionen.

Beispiel 20-8. Ajax-Tooltips

    /**
     * linkdetails.js
     *
     * Dieses unauffällige JavaScript-Modul gibt Links in einem Dokument Event-Handler,
     * sodass sie Tooltips anzeigen, wenn die Maus länger als eine halbe Sekunde lang
     * über ihnen verweilt. Wenn der Link auf ein Dokument auf dem gleichen Server zeigt, auf dem * auch das Quelldokument liegt, enthält der Tooltip Informationen über den Typ, die Größe
     * und das Datum des Dokuments, die über einen XMLHttpRequest-HEAD-Request beschafft werden.
     *
     * Dieses Modul braucht die Module Tooltip.js, HTTP.js und Geometry.js
     */
    (function( ) { // Anonyme Funktion, die alle unsere Symbole speichert
      // Erzeuge das von uns zu verwendende Tooltip-Objekt
      var tooltip = new Tooltip( );

      // Beim Laden des Dokuments die init( )-Funktion aufrufen lassen
      if (window.addEventListener) window.addEventListener("load", init, false);
      else if (window.attachEvent) window.attachEvent("onload", init);

      // Nach dem Laden des Dokuments aufzurufen
      function init( ) {
        var links = document.getElementsByTagName('a');
        // Durchlaufe alle Links, und füge ihnen Event-Handler hinzu
        for(var i = 0; i < links.length; i++)
          if (links[i].href) addTooltipToLink(links[i]);
      }

      // Diese Funktion fügt die Event-Handler hinzu
      function addTooltipToLink(link) {
        // Event-Handler hinzufügen
        if (link.addEventListener) { // standardisiertes Verfahren
          link.addEventListener("mouseover", mouseover, false);
          link.addEventListener("mouseout", mouseout, false);
        }
        else if (link.attachEvent) { // IE-spezifisches Verfahren
          link.attachEvent("onmouseover", mouseover);
          link.attachEvent("onmouseout", mouseout);
        }

        var timer; // Wird mit setTimeout/clearTimeout verwendet

        function mouseover(event) {
          var e = event || window.event;
          // Mausposition holen, zu Dokumentkoordinaten konvertieren, Offset addieren
          var x = e.clientX + Geometry.getHorizontalScroll( ) + 25;
          var y = e.clientY + Geometry.getVerticalScroll( ) + 15;

          // Falls ein Tooltip ansteht, abbrechen
          if (timer) window.clearTimeout(timer);

          // Einen Tooltip in einer halben Sekunde einplanen
          timer = window.setTimeout(showTooltip, 500);

          function showTooltip( ) {
            // Wenn es ein HTTP-Link ist und er vom gleichen Host kommt
            // wie dieses Skript, können wir XMLHttpRequest verwenden,
            // um mehr über ihn zu erfahren.
            if (link.protocol == "http:" && link.host == location.host) {
              // Starte einen XMLHttpRequest für die Link-Header
              HTTP.getHeaders(link.href, function(headers) {
                // Mit den Headern bauen wir einen Textstring
                var tip = "URL: " + link.href + "<br>" +
                  "Typ: " + headers["Content-Type"] + "<br>" +
                  "Größe: " + headers["Content-Length"] + "<br>" +
                  "Date: " + headers["Last-Modified"];
                // und zeigen ihn als Tooltip an
                tooltip.show(tip, x, y);
              });

            }
            else {
              // Wenn es sich um einen Link auf eine andere Site handelt,
              // ist der Tooltip einfach die URL des Links
              tooltip.show("URL: " + link.href, x, y);
            }
          }
        }

        function mouseout(e) {
          // Wenn die Maus den Link verlässt, werden alle
          // anstehenden Tooltips gelöscht oder versteckt, falls sie angezeigt werden
          if (timer) window.clearTimeout(timer);
          timer = null;
          tooltip.hide( );
        }
      }
    })( );

## 20.3.2 Einseiten-Anwendungen

Eine _Einseiten-Anwendung_ ist genau das, was der Name schon sagt: eine über JavaScript erstellte Webanwendung, für die nur eine Seite geladen werden muss. Manche Einseiten-Anwendungen brauchen nach dem Laden nie mehr mit dem Server zu reden. Ein Beispiel dafür sind DHTML-Spiele, bei denen die Kommunikation mit dem Benutzer einfach zu skriptgesteuerten Änderungen am geladenen Dokument führt.

Das XMLHttpRequest-Objekt und die Ajax-Architektur eröffnen jedoch viele Möglichkeiten. Webanwendungen können diese Verfahren benutzen, um Daten mit dem Server auszutauschen, und trotzdem Einseiten-Anwendungen sein. Solche Webanwendungen können einen kurzen JavaScript-Bootstrapping-Code und einen einfachen HTML-Splash-Screen enthalten, der angezeigt wird, während die Anwendung hochgefahren wird. Während der Splash-Screen gezeigt wird, kann der Bootstrap-Code beispielsweise ein XMLHttpRequest-Objekt verwenden, um den eigentlichen JavaScript-Code für die Anwendung herunterzuladen, der dann mit der Methode `eval( )` ausgeführt wird. Dann übernimmt der JavaScript-Code, lädt Daten mit XMLHttpRequest, wenn sie benötigt werden, und verwendet DOM, um diese Daten als DHTML für den Benutzer anzuzeigen.

## 20.3.3 Remote Scripting

Der Begriff _Remote Scripting_ entstand vier Jahre vor dem Begriff _Ajax_ und ist einfach ein nicht so modischer Name für die gleiche Grundidee: die Verwendung von skriptgesteuertem HTTP für eine engere Integration (und verbesserte Ansprechzeit) zwischen Client und Server. Ein weithin bekannter Artikel von Apple aus dem Jahr 2002 beschreibt beispielsweise, wie man ein `<iframe>`-Tag verwendet, um skriptgesteuerte HTTP-Requests an einen Webserver zu senden (siehe <http://developer.apple.com/internet/webcontent/iframe.html>). Im Artikel wird auf Folgendes hingewiesen: Wenn der Webserver eine HTML-Datei zurücksendet, die `<script>`-Tags enthält, wird der in diesen Tags enthaltene JavaScript-Code vom Browser ausgeführt und kann Methoden aufrufen, die in dem Fenster definiert sind, das das `<iframe>` enthält. Auf diese Weise kann der Server direkte Befehle in Form von JavaScript-Statements an seinen Client senden.

## 20.3.4 Vorsicht bei Ajax

Wie jede Architektur hat Ajax seine Fallen. In diesem Abschnitt werden drei Situationen bei der Erstellung von Ajax-Anwendungen beschrieben, in denen Sie aufpassen müssen.

Die erste Falle ist die des sichtbaren Feedbacks. Wenn ein Benutzer auf einen herkömmlichen Hyperlink klickt, gibt der Webbrowser Feedback und zeigt damit an, dass der Inhalt des Links gerade geholt wird. Das Feedback erscheint, bevor der Inhalt fertig zur Anzeige ist, wodurch der Benutzer sieht, dass der Browser sich mit der Anfrage beschäftigt. Wenn ein HTTP-Request aber über XMLHttpRequest gesendet wird, gibt der Browser kein Feedback. Selbst bei Breitbandverbindungen verursachen Netzwerklaufzeiten oft merkbare Verzögerungen zwischen dem Senden eines HTTP-Requests und dem Erhalt einer Antwort. Deshalb ist es für Ajax-basierte Anwendungen nützlich, wenn während des Wartens auf eine Anwort auf den XMLHttpRequest optisches Feedback vorhanden ist (wie z.B. eine einfache DHTML-Animation: siehe Kapitel 16).

Beachten Sie, dass Beispiel 20-8 diesem Rat hinsichtlich optischem Feedback nicht folgt. Das liegt daran, dass der Benutzer in diesem Beispiel den HTTP-Request nicht aus eigenem Antrieb auslöst. Stattdessen wird die Anfrage initiiert, wenn der Benutzer die Maus (passiv) über einen Link hält. Der Benutzer verlangt nichts ausdrücklich von der Anwendung und erwartet folglich kein Feedback.

Die zweite Falle hat mit den URLs zu tun. Herkömmlich gehen Webanwendungen von einem in den nächsten Zustand über, indem neue Seiten geladen werden, wobei jede Seite eine eindeutige URL hat. Bei Ajax-Anwendungen ist das anders: Wenn eine Ajax-Anwendung skriptgesteuertes HTTP verwendet, um neuen Inhalt herunterzuladen und anzuzeigen, ändert sich die URL in der Navigationsleiste nicht. Wenn Benutzer versuchen, ein Lesezeichen für einen bestimmten Zustand zu setzen, werden sie feststellen, dass das über die Lesezeichenoption des Browsers nicht geht. Sie können nicht einmal eine URL aus der Navigationsleiste des Browsers ausschneiden, um sie anderswo einzufügen.

Dieses Problem und seine Lösung werden von Google Maps (<http://local.google.com>) ausgezeichnet vorgeführt. Beim Zoomen und Scrollen der Karte werden sehr viele Daten zwischen Client und Server hin- und hergeschoben, aber die im Browser angezeigte URL ändert sich überhaupt nicht. Google löst das Problem der Lesezeichen, indem es einen »link to this page«-Link in jede Seite einfügt. Beim Anklicken dieses Links wird eine URL für die momentan angezeigte Karte erzeugt und die Karte mit Hilfe dieser URL neu geladen. Nach diesem erneuten Laden kann der momentane Status der Karte als Lesezeichen gespeichert oder der Link per E-Mail an Freunde gesendet werden usw. Was lernen Ajax-Anwendungsentwickler daraus? Dass es weiterhin wichtig ist, den Anwendungsstatus in eine URL einzubauen und dem Benutzer die Möglichkeit zu geben, diese URLs zu nutzen.

Eine dritte Falle, die bei Diskussionen über Ajax oft auftaucht, hängt mit der Schaltfläche **Zurück** zusammen. Da dem Browser die Kontrolle über HTTP genommen wird, übergehen Skripte, die XMLHttpRequest verwenden, den History-Mechanismus des Browsers. Benutzer sind daran gewöhnt, sich mit den Schaltflächen **Zurück** und **Vorwärts** durchs Netz zu bewegen. Wenn eine Ajax-Anwendung skriptgesteuertes HTTP verwendet, um umfassende neue Inhalte anzuzeigen, versuchen Benutzer eventuell, sich innerhalb der Anwendung mit diesen Schaltflächen zu bewegen. Dabei stellen sie frustriert fest, dass die Schaltfläche **Zurück** sie nicht auf die gerade vorher angezeigte Seite führt, sondern ganz nach vorne auf die vor der Anwendung angezeigte Seite.

Es gab Versuche, das Problem mit **Zurück** dadurch zu lösen, dass der Browser dazu gebracht wird, URLs in seine History einzufügen. Diese Vorgehensweisen bleiben allerdings meistens in browserabhängigem Code stecken und sind nicht wirklich zufriedenstellend. Und selbst wenn sie irgendwann funktionieren, untergraben sie das Ajax-Denkmuster und ermutigen Benutzer, über das erneute Laden von Seiten anstatt über skriptgesteuertes HTTP zu navigieren.

Meiner Ansicht nach ist das **Zurück** -Problem nicht so schwerwiegend, wie das oft dargestellt wird, und kann mit gut durchdachtem Webdesign weitgehend umgangen werden. Anwendungselemente, die wie Hyperlinks aussehen, sollten sich auch so verhalten und Seiten tatsächlich neu laden. Dadurch werden sie in die Browser-History aufgenommen, wie es vom Benutzer erwartet wird. Umgekehrt sollten Anwendungselemente, die skriptgesteuertes HTTP ausführen und nicht in die Browser-History aufgenommen werden, nicht wie Hyperlinks aussehen. Schauen wir uns noch einmal die Kartenanwendung von Google an. Wenn der Benutzer zum Scrollen einer Karte mit der Maus klickt und sie zieht, erwartet er nicht, dass die **Zurück** -Schaltfläche sein Scrollen zunichte macht. Schließlich macht die **Zurück** -Schaltfläche das Scrollen in einer Webseite über die Bildlaufleiste des Browsers auch nicht zunichte.

In Ajax-Anwendungen sollten Sie darauf achten, Wörter wie »Vorwärts« und »Zurück« in den anwendungsinternen Navigationselementen zu vermeiden. Wenn eine Anwendung eine Schnittstelle im Wizard-Stil z.B. mit den Schaltflächen **Weiter** und **Zurück** verwendet, sollte sie zur Anzeige der nächsten oder vorherigen Bildschirmflächen tatsächlich Seiten traditionell laden (anstatt XMLHttpRequest zu verwenden). In dieser Situation erwartet der Benutzer, dass die Schaltfläche **Zurück** des Browsers genauso funktioniert wie die Schaltfläche **Zurück** der Anwendung.

Allgemeiner gesagt, sollte das **Zurück** des Browsers nicht mit dem **Rückgängig** -Feature der Anwendung verwechselt werden. Ajax-Anwendungen können ihre eigenen Undo/redo-Optionen implementieren, wenn das für die Benutzer nützlich ist. Allerdings sollte klar sein, dass sie sich von den Schaltflächen **Zurück** und **Nächste** unterscheiden.

* * *

[76] Die Ajax-Architektur ist überzeugend, und die Vergabe eines einfachen Namens für sie hat mitgeholfen, eine Revolution im Design von Webanwendungen auszulösen. Wie sich zeigt, klärt das Akronym aber nicht besonders gut über die an Ajax-Anwendungen beteiligten Technologien auf. Clientseitiges JavaScript verwendet überall Event-Handler und ist daher asynchron. Außerdem ist die Benutzung von XML in Ajax-Anwendungen oft zwar bequem, aber immer optional. Das definierende Merkmal von Ajax-Anwendungen ist die Tatsache, dass sie skriptgesteuertes HTTP betreiben, aber diese Charakteristik taucht in der Abkürzung gar nicht auf.

[77] Ich bedaure es, XMLHttpRequest nicht in der vierten Auflage dieses Buchs dokumentiert zu haben. Diese Auflage war sehr auf Standards bezogen und XMLHttpRequest wurde ausgelassen, weil das Objekt nie von einer standardisierenden Organisation unterstützt wurde. Hätte ich die Fähigkeiten von skriptgesteuertem HTTP zu dieser Zeit erkannt, hätte ich meine eigenen Regeln gebrochen und es trotzdem mit aufgenommen.

[78] RPC steht für _Remote Procedure Call_ und beschreibt eine Strategie, die im Distributed Computing zur Vereinfachung der Client/Server-Kommunikation angewandt wird.

# 20.4 Skriptgesteuertes HTTP mit <script>-Tags

In Internet Explorer 5 und 6 ist das XMLHttpRequest-Objekt ein ActiveX-Objekt. Manchmal deaktivieren Benutzer aus Sicherheitsgründen alle ActiveX-Skripten in Internet Explorer, wodurch Skripten kein XMLHttpRequest-Objekt erzeugen können. Falls notwendig, können einfache HTTP GET-Requests mit den Tags `<script>` und `<iframe>` ausgeführt werden. Dadurch können zwar nicht alle Funktionalitäten von XMLHttpRequest neu implementiert werden,[79] aber Sie können zumindest eine Version der `HTTP.getText( )`-Utility-Funktion erzeugen, die ohne ein ActiveX-Skript funktioniert.

Ein HTTP-Request kann recht einfach über das Setzen der Eigenschaft `src` eines `<script>`\- oder `<iframe>`-Tags erzeugt werden. Die gewünschten Daten aus diesen Elementen zu extrahieren, ohne dass die Daten dabei vom Browser geändert werden, ist aufwändiger. Ein `<iframe>`-Tag erwartet, dass es in ein HTML-Dokument geladen wird. Wenn Sie versuchen, den Inhalt einer Klartextdatei in ein iframe herunterzuladen, stellen Sie fest, dass Ihr Text in HTML konvertiert wird. Außerdem implementieren manche Internet Explorer-Versionen den `onload`\- oder `onreadystatechange`-Handler für das `<iframe>`-Tag nicht richtig, was die Sache weiter kompliziert.

Der hier gewählte Weg verwendet ein `<script>`-Tag und ein Skript auf der Serverseite. Sie teilen dem Skript auf der Serverseite die URL mit, deren Inhalt Sie aufrufen wollen, und an welche Funktion auf der Clientseite der Inhalt übergeben werden soll. Das Skript auf der Serverseite holt den Inhalt der gewünschten URL, codiert ihn als (möglicherweise sehr langen) JavaScript-String und gibt ein Skript auf der Clientseite zurück, das diesen String an die von Ihnen angegebene Funktion übergibt. Da dieses Skript auf der Clientseite in ein `<script>`-Tag geladen wird, wird der URL-Inhalt automatisch als JavaScript interpretiert, wenn der Download abgeschlossen ist, und die Funktion dadurch aufgerufen.

Beispiel 20-9 implementiert ein passendes serverseitiges Skript in der Skriptsprache PHP.

Beispiel 20-9. jsquoter.php

    <?php
    // Sage dem Browser, dass wir ein Skript senden
    header("Content-Type: text/javascript");
    // Hole Argumente aus der URL
    $func = $_GET["func"];    // Die aufzurufende Funktion in unserem JS-Code
    $filename = $_GET["url"];  // Die an die Funktion zu übergebende Datei oder URL
    $lines = file($filename);  // Hole die Zeilen der Datei
    $text = implode("", $lines); // Füge sie zu einem String zusammen
    // Anführungszeichen und Zeilenumbrüche escapen
    $escaped = str_replace(array("'", "\"", "\n", "\r"),
                array("\\'", "\\\"", "\\n", "\\r"),
                $text);
    // Gib alles als einen einzigen JavaScript-Funktionsaufruf aus
    echo "$func('$escaped');"
    ?>

Die Funktion auf der Clientseite in Beispiel 20-10-Utility") verwendet das serverseitige Skript _jsquoter.php_ aus Beispiel 20-9 und arbeitet wie die `HTTP.getText( )`-Funktion, die in Beispiel 20-2-Utility") vorgestellt wurde.

Beispiel 20-10. Die HTTP.getTextWithScript( )-Utility

    HTTP.getTextWithScript = function(url, callback) {
      // Erzeuge ein neues script-Element, und füge es dem Dokument hinzu.
      var script = document.createElement("script");
      document.body.appendChild(script);

      // Erzeuge einen eindeutigen Funktionsnamen.
      var funcname = "func" + HTTP.getTextWithScript.counter++;

      // Definiere eine Funktion mit diesem Namen, und verwende diese Funktion als
      // praktischen Namensraum. Das auf dem Server erzeugte Skript
      // ruft diese Funktion auf.
      HTTP.getTextWithScript[funcname] = function(text) {
        // Übergib den Text an die Callback-Funktion
        callback(text);

        // Räumen Sie das Script-Tag und die erzeugte Funktion auf.
        document.body.removeChild(script);
        delete HTTP.getTextWithScript[funcname];
      }

      // Codiere die zu beschaffende URL und den Namen der Funktion
      // als Argumente des Skripts jsquoter.php auf der Serverseite. Setze die Eigenschaft src
      // des Script-Tags, um die URL zu holen.
      script.src = "jsquoter.php" +
             "?url=" + encodeURIComponent(url) + "&func=" +
             encodeURIComponent("HTTP.getTextWithScript." + funcname);
    }

    // Das verwenden wir, um eindeutige Funktions-Callback-Namen zu erzeugen,
    // falls zu einer bestimmten Zeit mehrere Requests anstehen.
    HTTP.getTextWithScript.counter = 0;

* * *

[79] Um XMLHttpRequest komplett zu ersetzen, brauchen Sie wahrscheinlich ein Java-Applet.

# Kapitel 21. JavaScript und XML

Das wichtigste Feature der Ajax-Anwendungsarchitektur ist ihre Fähigkeit, skriptgesteuertes HTTP mit dem XMLHttpRequest-Objekt durchzuführen. Dieses Thema wurde in Kapitel 20 behandelt. Das X in »Ajax« steht allerdings für XML, und in vielen Webanwendungen sind XML-formatierte Daten, wie Ajax sie verwendet, das zweitwichtigste Merkmal.

In diesem Kapitel wird beschrieben, wie Sie JavaScript zur Arbeit mit XML-Daten verwenden. Wir beginnen mit Techniken zur Beschaffung von XML-Daten: Daten aus dem Netzwerk laden, sie aus einem String parsen und sie aus XML- _Dateninseln_ innerhalb eines HTML-Dokuments holen. Anschließend beschreibt das Kapitel grundlegende Techniken zum Arbeiten mit diesen Daten. Es erklärt die Verwendung des W3C DOM API, die Transformation von XML-Daten mittels XSL-Stylesheets, die Abfrage von XML-Daten mit XPath-Ausdrücken und die Serialisierung von XML-Daten zurück ins Stringformat.

Auf diese Beschreibung der grundlegenden XML-Techniken folgen zwei Abschnitte, die Anwendungen dieser Techniken vorstellen. Zuerst lernen Sie, wie Sie HTML-Dokumentvorlagen definieren und automatisch expandieren können, indem Sie DOM und XPath mit den Daten aus einem XML-Dokument verwenden. Dann erfahren Sie, wie Sie mit den XML-Techniken aus diesem Kapitel einen Webservices-Client in JavaScript schreiben können.

Das Kapitel schließt mit einer Kurzeinführung in E4X, das beim Arbeiten mit XML eine leistungsfähige Erweiterung des JavaScript-Sprachkerns darstellt.

# 21.1 XML-Dokumente beschaffen

Kapitel 20 zeigt, wie man das XMLHttpRequest-Objekt verwendet, um ein XML-Dokument von einem Webserver zu erhalten. Wenn der Request abgeschlossen ist, verweist die `responseXML`-Eigenschaft des XMLHttpRequest-Objekts auf ein Document-Objekt, das die geparste Darstellung des XML-Dokuments repräsentiert. Das ist jedoch nicht die einzige Art, auf man sich ein XML-Document-Objekt beschaffen kann. Die folgenden Abschnitte zeigen, wie Sie ein leeres XML-Dokument erzeugen, ein XML-Dokument ohne Verwendung von XMLHttpRequest aus einer URL laden, ein XML-Dokument aus einem String parsen und ein XML-Dokument aus einer XML-Dateninsel laden.

Wie bei vielen anspruchsvollen clientseitigen JavaScript-Features hängen die Techniken zur Beschaffung von XML-Daten gewöhnlich vom Browser ab. Die hier folgenden Unterabschnitte definieren Utility-Funktionen, die unter Internet Explorer (IE) und Firefox arbeiten.

Diese Utility-Funktionen sind Teil einer größeren Einheit und gehören alle in den Namensraum `XML` (siehe Kapitel 10). Allerdings enthält keines der hier gezeigten Beispiele Code, der diesen Namensraum erzeugt. Im herunterladbaren Beispielcode auf der Webseite zu diesem Buch finden Sie die Datei _xml.js_ , die den Code enthält, der diesen Namensraum erzeugt, und Sie müssen dazu lediglich die Zeile `var XML = {};` zu den hier gezeigten Beispielen hinzufügen.

## 21.1.1 Ein neues Dokument erstellen

Unter Firefox und ähnlichen Browsern können Sie ein (bis auf ein optionales Root-Element) leeres XML-Dokument mit der DOM Level 2-Methode `document.implementation. createDocument( )` erzeugen. Unter IE können Sie mit dem ActiveX-Objekt namens MSXML2.DOMDocument etwas Ähnliches bewerkstelligen. Beispiel 21-1 definiert eine Utility-Funktion `XML.newDocument( )`, die die Unterschiede zwischen diesen beiden Vorgehensweisen verbirgt. Ein leeres XML-Dokument alleine nützt nicht viel, es ist aber der erste Schritt für die Lade- und Parsingtechniken eines Dokuments, die in den Beispielen nach diesem Beispiel gezeigt werden.

Beispiel 21-1. Erzeugung eines leeren XML-Dokuments

    /**
     * Erzeuge ein neues Document-Objekt. Werden keine Argumente übergeben,
     * ist das Dokument leer. Wenn ein Root-Tag übergeben wird, enthält
     * das Dokument genau ein Root-Tag. Wenn das Root-Tag ein Namensraum-
     * präfix hat, muss das zweite Argument die URL angeben, die
     * den Namensraum identifiziert.
     */
    XML.newDocument = function(rootTagName, namespaceURL) {
      if (!rootTagName) rootTagName = "";
      if (!namespaceURL) namespaceURL = "";

      if (document.implementation && document.implementation.createDocument) {
        // So geht es nach W3C-Standard
        return document.implementation.createDocument(namespaceURL,
                               rootTagName, null);
      }
      else { // So geht es im IE
        // Erzeuge ein leeres Dokument als ActiveX-Objekt.
        // Ohne Root-Element haben wir nur das hier zu tun
        var doc = new ActiveXObject("MSXML2.DOMDocument");

        // Für ein Root-Tag müssen wir das Dokument initialisieren
        if (rootTagName) {
          // Suche ein Namensraumpräfix
          var prefix = "";
          var tagname = rootTagName;
          var p = rootTagName.indexOf(':');
          if (p != −1) {
            prefix = rootTagName.substring(0, p);
            tagname = rootTagName.substring(p+1);
          }

          // Bei einem Namensraum müssen wir auch ein Präfix haben.
          // Ohne Namensraum löschen wir ein eventuell vorhandenes Präfix
          if (namespaceURL) {
            if (!prefix) prefix = "a0"; // Das verwendet Firefox
          }
          else prefix = "";

          // Erzeuge das Root-Element (mit optionalem Namensraum) als
          // Textstring
          var text = "<" + (prefix?(prefix+":"):"") + tagname +
            (namespaceURL
             ?(" xmlns:" + prefix + '="' + namespaceURL +'"')
             :"") +
            "/>";
          // und parse diesen Text in das leere Dokument
          doc.loadXML(text);
        }
        return doc;
      }
    };

## 21.1.2 Ein Dokument aus dem Netzwerk laden

Kapitel 20 zeigt, wie das XMLHttpRequest-Objekt verwendet wird, um dynamisch HTTP-Requests für textbasierte Dokumente zu generieren. In Kombination mit XML-Dokumenten verweist die `responseXML`-Eigenschaft auf die geparste Darstellung als DOM-Document-Objekt. XMLHttpRequest ist nicht standardisiert, ist aber weitgehend verfügbar, gut bekannt und ist meist die beste Vorgehensweise beim Laden von XML-Dokumenten.

Es geht aber auch anders. Das mit der in Beispiel 21-1 gezeigten Technik erzeugte XML-Document-Objekt kann ein XML-Dokument mit Hilfe einer weniger bekannten Technik laden und parsen. Beispiel 21-2 zeigt, wie das geht. Erstaunlicherweise funktioniert dieser Code sowohl unter IE als auch in Browsern, die auf Mozilla basieren.

Beispiel 21-2. Ein XML-Dokument synchron laden

    /**
     * Lade das XML-Dokument synchron von der angegebenen URL, und
     * gib es als Document-Objekt zurück
     */
    XML.load = function(url) {
      // Erzeuge ein neues Dokument mit der vorher definierten Funktion
      var xmldoc = XML.newDocument( );
      xmldoc.async = false; // Es soll synchron geladen werden,
      xmldoc.load(url);   // Lade und parse.
      return xmldoc;     // Dokument zurückgeben
    };

Genau wie XMLHttpRequest ist auch die `load( )`-Methode nicht standardisiert. Sie unterscheidet sich von XMLHttpRequest in mehreren wichtigen Aspekten. Erstens funktioniert sie nur mit XML-Dokumenten; XMLHttpRequest kann zum Herunterladen beliebiger Textdokumente verwendet werden. Zweitens ist sie nicht auf das HTTP-Protokoll beschränkt. Insbesondere kann sie zum Lesen von Dateien aus dem lokalen Dateisystem verwendet werden. Während der Test- und Entwicklungsphase einer Webanwendung ist das hilfreich. Drittens kann sie, wenn sie mit HTTP verwendet wird, keine Daten mit POST an einen Webserver posten, sondern erzeugt lediglich GET-Requests.

Wie XMLHttpRequest kann auch `load( )` asynchron verwendet werden. Das ist sogar der Standardmodus, es sei denn, die `async`-Eigenschaft wird auf `false` gesetzt. Beispiel 21-3 zeigt eine asynchrone Version der `XML.load( )`-Methode.

Beispiel 21-3. Ein XML-Dokument asynchron laden

    /**
     * Lade ein XML-Dokument asynchron von der angegebenen URL, und parse es.
     * Wenn das Dokument bereit ist, übergib es an die angegebene Callback-Funktion.
     * Diese Funktion kehrt sofort und ohne Rückgabewert zurück.
     */
    XML.loadAsync = function(url, callback) {
      var xmldoc = XML.newDocument( );

      // Wenn das XML-Dokument mit createDocument erzeugt wurde, verwende
      // onload, um den Ladezeitpunkt zu bestimmen
      if (document.implementation && document.implementation.createDocument) {
        xmldoc.onload = function( ) { callback(xmldoc); };
      }
      // Ansonsten verwende onreadystatechange (wie bei XMLHttpRequest)
      else {
        xmldoc.onreadystatechange = function( ) {
          if (xmldoc.readyState == 4) callback(xmldoc);
        };
      }

      // Starte jetzt das Herunterladen und Parsen
      xmldoc.load(url);
    };

## 21.1.3 XML-Text parsen

Manchmal wollen Sie aber kein aus dem Netz geladenes XML-Dokument parsen, sondern einfach ein XML-Dokument aus einem JavaScript-String. In Browsern auf Mozilla-Basis wird dazu ein DOMParser-Objekt verwendet; in IE ist es die `loadXML( )`-Methode des Document-Objekts. (Wenn Sie sich den Code `XML.newDocument( )` in Beispiel 21-1 genau angeschaut haben, haben Sie die Verwendung dieser Methode schon einmal gesehen.)

Beispiel 21-4 zeigt eine plattformübergreifende XML-Parserfunktion, die unter Mozilla und IE funktioniert. Auf anderen Plattformen versucht sie, den Text zu parsen, indem ein XMLHttpRequest aus einer `data:`-URL geladen wird.

Beispiel 21-4. Ein XML-Dokument parsen

    /**
     * Parse das im Stringargument enthaltene XML-Dokument, und
     * gib ein Document-Objekt zurück, das es repräsentiert.
     */
    XML.parse = function(text) {
      if (typeof DOMParser != "undefined") {
        // Mozilla, Firefox und ähnliche Browser
        return (new DOMParser( )).parseFromString(text, "application/xml");
      }
      else if (typeof ActiveXObject != "undefined") {
        // Internet Explorer
        var doc = XML.newDocument( ); // Erzeuge ein leeres Dokument.
        doc.loadXML(text);      // Parse text in dieses Dokument.
        return doc;          // Gib es zurück.
      }
      else {
        // Letzter Ausweg: Laden des Dokuments aus einer data:-URL
        // Das soll unter Safari funktionieren. Vielen Dank an Manos Batsis und
        // seine Sarissa-Bibliothek (sarissa.sourceforge.net) für diese Technik.
        var url = "data:text/xml;charset=utf-8," + encodeURIComponent(text);
        var request = new XMLHttpRequest( );
        request.open("GET", url, false);
        request.send(null);
        return request.responseXML;
      }
    };

## 21.1.4 XML-Dokumente aus Dateninseln

Microsoft erweitert HTML durch ein `<xml>`-Tag, das eine XML-Dateninsel im umgebenden »Meer« von HTML-Markup erzeugt. Trifft IE auf dieses `<xml>`-Tag, so behandelt es seinen Inhalt als getrenntes XML-Dokument, das Sie mit `document.getElementById( )` oder anderen HTML DOM-Methoden abrufen können. Wenn das `<xml>`-Tag ein `src`-Attribut hat, so wird das XML-Dokument nicht aus dem Inhalt des `<xml>`-Tags geparst, sondern von der URL geladen, die von diesem Attribut angegeben wird.

Wenn eine Webanwendung XML-Daten braucht und die Daten beim ersten Laden der Anwendung bekannt sind, ist es vorteilhaft, diese Daten direkt in die HTML-Seite aufzunehmen: Die Daten stehen dann schon zur Verfügung, und die Webanwendung muss keine neue Netzwerkverbindung herstellen, um sie herunterzuladen. XML-Dateninseln sind für diesen Zweck gut geeignet. IE-Dateninseln können in anderen Browsern mit ähnlichem Code wie in Beispiel 21-5 simuliert werden.

Beispiel 21-5. Ein XML-Dokument aus einer Dateninsel laden

    /**
     * Gib ein Document-Objekt mit dem Inhalt des <xml>-Tags mit der
     * angegebenen id zurück. Hat es ein src-Attribut, so wird stattdessen ein XML-
     * Dokument aus dieser URL geladen und zurückgegeben.
     *
     * Da Dateninseln oft mehrmals abgefragt werden, hält diese Funktion das
     * zurückgegebene Dokument in einem Cache.
     */
    XML.getDataIsland = function(id) {
      var doc;

      // Überprüfe zuerst den Cache
      doc = XML.getDataIsland.cache[id];
      if (doc) return doc;

      // Suche nach dem angegebenen Element
      doc = document.getElementById(id);

      // Existiert ein src-Attribut, dann hole das Dokument von dieser URL
      var url = doc.getAttribute('src');
      if (url) {
        doc = XML.load(url);
      }
      // Existiert kein src-Attribut, dann wollen wir den Inhalt des <xml>-
      // Tags als Dokument zurückgeben. In IE ist doc bereits das
      // gesuchte Document-Objekt. In anderen Browsern verweist doc auf
      // ein HTML-Element, sodass wir den Elementinhalt in ein
      // neues Document-Objekt kopieren müssen
      else if (!doc.documentElement) {// Wenn es noch kein Document ist...

        // Suchen Sie zuerst das Dokumentelement mit dem <xml>-Tag. Das ist
        // das erste Kind des <xml>-Tags, das ein Element und nicht nur
        // Text, Kommentar oder Verarbeitungsanweisung ist
        var docelt = doc.firstChild;
        while(docelt != null) {
          if (docelt.nodeType == 1 /*Node.ELEMENT_NODE*/) break;
          docelt = docelt.nextSibling;
        }

        // Ein leeres Dokument erzeugen
        doc = XML.newDocument( );

        // <xml>-Knoten mit Inhalt ins neue Dokument importieren
        if (docelt) doc.appendChild(doc.importNode(docelt, true));
      }

      // Cache das Dokument, und gib es zurück
      XML.getDataIsland.cache[id] = doc;
      return doc;
    };
    XML.getDataIsland.cache = {}; // Cache initialisieren

Dieser Code simuliert XML-Dateninseln in Nicht-IE-Browsern nicht perfekt. Der HTML-Standard verlangt, dass Browser Tags wie `<xml>`, die sie nicht kennen, parsen (aber ignorieren). Das bedeutet, dass Browser XML-Daten in einem `<xml>`-Tag nicht verwerfen. Es bedeutet auch, dass jedweder Text in der Dateninsel per Default angezeigt wird. Das lässt sich mit dem folgenden CSS-Stylesheet einfach verhindern:

    <style type="text/css">xml { display: none; }</style>

Eine weitere Inkompatibilität besteht darin, dass Nicht-IE-Browser den Inhalt von XML-Dateninseln als HTML- und nicht als XML-Inhalt ansehen. Wenn Sie den Code in Beispiel 21-5 in Firefox verwenden und dann beispielsweise das resultierende Dokument wieder serialisieren (wie, erfahren Sie später in diesem Kapitel), werden Sie feststellen, dass die Tagnamen alle zu Großbuchstaben konvertiert werden, weil Firefox sie für HTML-Tags hält. In manchen Fällen mag das problematisch sein; in vielen anderen Fällen ist es das nicht. Schließlich und endlich müssen Sie beachten, dass XML-Namensräume kaputtgehen, wenn der Browser die XML-Tags wie HTML-Tags behandelt. Das bedeutet, dass eingebettete XML-Dateninseln sich nicht für Dinge wie XSL-Stylesheets eignen (XSL wird noch in Einzelheiten später in diesem Kapitel behandelt), da diese Stylesheets immer Namensräume verwenden.

Wollen Sie die Netzwerkvorteile der direkten Einbindung von XML-Daten in einer HTML-Seite nutzen, aber die Inkompatibilitäten zwischen den Browsern umgehen, die bei der Verwendung von XML-Dateninseln und dem `<xml>`-Tag bestehen? Dann sollten Sie sich überlegen, Ihren XML-Dokumenttext als JavaScript-String zu codieren und das Dokument dann mit Code zu parsen, wie er in Beispiel 21-4 steht.

# 21.2 XML über die DOM API manipulieren

Der vorherige Abschnitt führt einige Möglichkeiten auf, mit denen sich geparste XML-Daten als Document-Objekt beschaffen lassen. Das Document-Objekt wird von der W3C DOM API definiert und hat sehr große Ähnlichkeit zum HTMLDocument-Objekt, auf das die `document`-Eigenschaft des Webbrowsers verweist.

In den nächsten Abschnitten werden einige wichtige Unterschiede zwischen dem HTML DOM und dem XML DOM erläutert. Dann wird gezeigt, wie Sie die DOM API zur Extraktion von Daten aus einem XML-Dokument verwenden können und diese Daten dem Benutzer anzeigen können, indem Sie im HTML-Dokument des Browsers dynamisch Knoten erzeugen.

## 21.2.1 XML im Vergleich zum HTML DOM

Kapitel 15 erklärt das W3C DOM, konzentriert sich dabei aber auf dessen Anwendung auf HTML-Dokumente in clientseitigem JavaScript. Tatsächlich entwickelte das W3C die DOM API sprachneutral und hatte dabei vor allem XML-Dokumente im Auge; seine Anwendung auf HTML-Dokumente erfolgt über ein optionales Erweiterungsmodul. In Teil IV sehen Sie, dass getrennte Einträge für Document und HTMLDocument sowie für Element und HTMLElement vorhanden sind. HTMLDocument und HTMLElement sind Erweiterungen der XML-Document- und -Element-Objekte im Sprachkern. Wenn Sie es gewöhnt sind, HTML-Dokumente mit DOM zu ändern, müssen Sie beim Arbeiten mit XML-Dokumenten darauf achten, keine HTML-spezifischen API-Features zu verwenden.

Der wichtigste Unterschied zwischen HTML DOMs und XML DOMs ist wahrscheinlich, dass die `getElementById( )`-Methode bei XML-Dokumenten normalerweise nutzlos ist. Im DOM Level 1 ist die Methode sogar HTML-spezifisch und wird lediglich vom HTMLDocument-Interface definiert. Im DOM Level 2 wird die Methode zwar um eine Ebene auf das Document-Interface hochgehoben, aber da gibt es einen Haken. In XML-Dokumenten sucht `getElementById( )` nach Elementen mit dem angegebenen Wert eines Attributs, dessen _Type_ »id« ist. Es genügt nicht, ein Attribut _namens_ »id« für ein Element zu definieren: Der Elementname hat keine Auswirkung, sondern nur der Attributtyp. Attributtypen werden in der DTD eines Dokuments deklariert, und die DTD eines Dokuments wird in der DOCTYPE-Deklaration angegeben. Die von Webanwendungen verwendeten XML-Dokumente haben oft keine DOCTYPE-Deklaration, die eine DTD angibt. Bei einem solchen Dokument gibt ein Aufruf von `getElementById( )` immer `null` zurück. Beachten Sie, dass die `getElementsByTagName( )`-Methode der Document- und Element-Interfaces bei XML-Dokumenten gut funktioniert. (Weiter unten in diesem Kapitel zeige ich Ihnen, wie Sie ein XML-Dokument mit leistungsfähigen XPath-Ausdrücken abfragen können; mit XPath können Elemente anhand des Werts eines beliebigen Attributs abgefragt werden).

Ein weiterer Unterschied zwischen HTML- und XML-Document-Objekten ist, dass HTML-Dokumente eine `body`-Eigenschaft haben, die auf das `<body>`-Tag im Dokument verweist. Bei XML-Dokumenten verweist lediglich die `documentElement`-Eigenschaft auf das höchste Element in der Hierarchie des Dokuments. Beachten Sie, dass dieses Toplevel-Element auch über die `childNodes[]`-Eigenschaft des Dokuments verfügbar ist. Es ist aber nicht unbedingt das erste oder einzige Element dieses Arrays, da ein XML-Dokument auch eine DOCTYPE-Deklaration, Kommentare und Verarbeitungsanweisungen auf der obersten Ebene enthalten kann.

Es gibt noch einen weiteren Unterschied zwischen dem XML-Element-Interface und dem HTMLElement-Interface, das dieses erweitert. Im HTML DOM werden standardisierte HTML-Attribute eines Elements als Eigenschaften des HTMLElement-Interfaces verfügbar gemacht. Beispielsweise ist das `src`-Attribut eines `<img>`-Tags über die `src`-Eigenschaft des HTMLImageElement-Objekts erhältlich, das das `<img>`-Tag repräsentiert. Das ist beim XML DOM anders: Das Element-Interface hat lediglich eine `tagName`-Eigenschaft. Die Attribute eines XML-Elements müssen über `getAttribute( )`, `setAttribute( )` und verwandte Methoden explizit abgefragt und gesetzt werden.

Die logische Folge ist, dass bestimmte Attribute, die für irgendein HTML-Element bedeutsam sind, für alle XML-Elemente bedeutungslos sind. Denken Sie daran, dass durch das Setzen des Attributs mit Namen »id« in einem XML-Element dieses Element nicht mit `getElementById( )` gesucht werden kann. Genauso können Sie den Stil eines XML-Elements nicht verändern, indem Sie sein Style-Attribut setzen. Außerdem können Sie eine CSS-Klasse nicht mit einem XML-Element assoziieren, indem Sie sein Class-Attribut festlegen. Alle diese Attribute sind HTML-spezifisch.

## 21.2.2 Beispiel: Aus XML-Daten eine HTML-Tabelle erstellen

Beispiel 21-7 definiert eine Funktion namens `makeTable( )`, die sowohl das XML- als auch das HTML DOM verwendet, um Daten aus einem XML-Dokument zu extrahieren und als Tabelle in ein HTML-Dokument einzufügen. Die Funktion erwartet ein JavaScript-Objekt-Literalargument, das festlegt, welche Elemente des XML-Dokuments die Tabellendaten enthalten und wie diese Daten in der Tabelle angeordnet werden sollen.

Bevor wir uns den Code für `makeTable( )` ansehen, betrachten wir ein Anwendungsbeispiel. Beispiel 21-6 zeigt ein Beispiel-XML-Dokument, das hier verwendet wird (und an anderen Stellen im Kapitel).

Beispiel 21-6. Eine XML-Datendatei

    <?xml version="1.0"?>
    <contacts>
     <contact name="Able Baker"><email>able@example.com</email></contact>
     <contact name="Careful Dodger"><email>dodger@example.com</email></contact>
     <contact name="Eager Framer" personal="true"><email>framer@example.com</email></contact>
    </contacts>

Das unten aufgeführte HTML-Fragment zeigt, wie die `makeTable( )`-Funktion mit diesen XML-Daten verwendet werden kann. Beachen Sie, dass sich das Schema-Objekt auf Tag- und Attributnamen aus dieser Beispiel-Datendatei bezieht:

    <script>
    // Diese Funktion verwendet makeTable( )
    function displayAddressBook( ) {
      var schema = {
        rowtag: "Kontakt",
        columns: [
          { tagname: "@name", label: "Name" },
          { tagname: "email", label: "Adresse" }
        ]
      };

      var xmldoc = XML.load("adressen.xml"); // Lies die XML-Daten
      makeTable(xmldoc, schema, "adressen"); // Konvertiere in eine HTML-Tabelle
    }
    </script>

    <button onclick="displayAddressBook( )">Adressbuch anzeigen</button>
    <div id="addresses"><!--Tabelle wird hier eingefügt --></div>

Die Implementierung von `makeTable( )` zeigt Beispiel 21-7.

Beispiel 21-7. Eine HTML-Tabelle aus XML-Daten erstellen

    /**
     * Extrahiere Daten aus dem angegebenen XML-Dokument, und formatiere sie als HTML-Tabelle.
     * Füge die Tabelle an das angegebene HTML-Element an. (Wenn das Element ein String ist,
     * wird es als Element-ID genommen und das angegebene Element wird herausgesucht.)
     *
     * Das schema-Argument ist ein JavaScript-Objekt und gibt an, welche Daten
     * extrahiert und wie sie angezeigt werden sollen. Das schema-Objekt muss eine Eigenschaft
     * mit dem Namen "rowtag" haben, die den Tagnamen des XML-Elements angibt, das
     * die Daten für eine Tabellenreihe enthält. Das schema-Objekt muss außerdem eine Eigenschaft
     * mit dem Namen "columns" haben, die auf ein Array verweist. Die Elemente dieses Arrays
     * geben die Reihenfolge und den Inhalt der Tabellenspalten an. Jedes
     * Array-Element kann ein String oder ein JavaScript-Objekt sein. Ist ein Element ein
     * String, so wird dieser String als Tag-Name des XML-Elements verwendet, das die
     * Tabellendaten und Überschrift für die Spalte enthält.
     * Wenn ein Element des columns[]-Arrays ein Objekt ist, muss es eine Eigenschaft
     * mit dem Namen "tagname" und eine mit dem Namen "label" haben. Die tagname-Eigenschaft * wird verwendet, um Daten aus dem XML-Dokument zu extrahieren, und die label-Eigenschaft
     * wird als Text für die Spaltenüberschrift verwendet. Beginnt der Tagname mit einem
     * @-Zeichen, so ist er kein Nachfahre der Reihe, sondern ein Attribut des Reihenelements.
     */
    function makeTable(xmldoc, schema, element) {
      // Erzeuge das <table>-Element
      var table = document.createElement("table");

      // Erzeuge den Kopf für die Reihen der <th>-Elemente in einem <tr> eines <thead>
      var thead = document.createElement("thead");
      var header = document.createElement("tr");
      for(var i = 0; i < schema.columns.length; i++) {
        var c = schema.columns[i];
        var label = (typeof c == "string")?c:c.label;
        var cell = document.createElement("th");
        cell.appendChild(document.createTextNode(label));
        header.appendChild(cell);
      }
      // Füge den Kopf in die Tabelle ein
      thead.appendChild(header);
      table.appendChild(thead);

      // Die restlichen Reihen der Tabelle gehen in einen <tbody>
      var tbody = document.createElement("tbody");
      table.appendChild(tbody);

      // Hole jetzt die Elemente, die unsere Daten enthalten, aus dem XML-Dokument
      var xmlrows = xmldoc.getElementsByTagName(schema.rowtag);

      // Durchlaufe diese Elemente. Bei jedem Durchgang holen wir eine Tabellenreihe.
      for(var r=0; r < xmlrows.length; r++) {
        // Das ist das XML-Element, in dem sich die Daten für die Reihe befinden
        var xmlrow = xmlrows[r];
        // Erzeuge ein HTML-Element, um die Daten in der Reihe anzuzeigen
        var row = document.createElement("tr");

        // Durchlaufe die Spalten, die vom schema-Objekt angegeben werden
        for(var c = 0; c < schema.columns.length; c++) {
          var sc = schema.columns[c];
          var tagname = (typeof sc == "string")?sc:sc.tagname;
          var celltext;
          if (tagname.charAt(0) == '@') {
            // Beginnt der Tagname mit '@', so ist es ein Attributname
            celltext = xmlrow.getAttribute(tagname.substring(1));
          }
          else {
            // Suche das XML-Element, das die Daten für diese Spalte enthält
            var xmlcell = xmlrow.getElementsByTagName(tagname)[0];
            // Gehe davon aus, dass das Element als ersten Nachfahren einen Textknoten
            // enthält
            var celltext = xmlcell.firstChild.data;
          }
          // Erzeuge das HTML-Element für diese Zelle
          var cell = document.createElement("td");
          // Füge den Datentext in die HTML-Zelle ein
          cell.appendChild(document.createTextNode(celltext));
          // Füge die Zelle an die Zeile an
          row.appendChild(cell);
        }

        // Füge die Zeile dem tbody der Tabelle hinzu
        tbody.appendChild(row);
      }

      // Setze ein HTML-Attribut am Tabellenelement durch Setzen einer Eigenschaft.
      // Beachten Sie, dass wir in XML stattdessen setAttribute( ) verwenden müssen.
      table.frame = "border";

      // Jetzt haben wir die HTML-Tabelle erzeugt und fügen sie dem angegebenen Element hinzu.
      // Wenn das Element ein String ist, gehen wir von einer Element-ID aus.
      if (typeof element == "string") element = document.getElementById(element);
      element.appendChild(table);
    }

# 21.3 XML mit XSLT transformieren

Nachdem Sie ein Document-Objekt, das ein XML-Dokument repräsentiert, geladen, geparst oder sonstwie erhalten haben, ist seine Transformation mit Hilfe eines XSLT-Stylesheets eines der eindrucksvollsten Dinge, die Sie mit ihm machen können. XSLT steht für _XSL-Transformationen_ und XSL für _Extensible Stylesheet Language_. XSL-Stylesheets sind XML-Dokumente, die genau wie andere XML-Dokumente geladen und geparst werden können. Eine Anleitung zu XSL übersteigt den Umfang dieses Buchs bei Weitem, aber Beispiel 21-8 zeigt ein Beispiel-Stylesheet, das ein XML-Dokument, wie es in Beispiel 21-6 gezeigt ist, in eine HTML-Tabelle transformiert.

Beispiel 21-8. Ein einfaches XSL-Stylesheet

    <?xml version="1.0"?><!-- das ist ein xml-Dokument -->
    <!-- deklariere den xsl-Namensraum, um xsl-Tags von html-Tags zu unterscheiden -->
    <xsl:stylesheet version="1.0"
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output method="html"/>

     <!-- Wenn wir auf das Root-Element stoßen, dann gib das HTML-Framework -->
     <!-- einer Tabelle aus -->
     <xsl:template match="/">
      <table>
       <tr><th>Name</th><th>E-Mail-Adresse</th></tr>
       <xsl:apply-templates/> <!-- und löse andere Templates rekursiv auf -->
      </table>
     </xsl:template>

     <!-- Wenn wir auf das <contact>-Element stoßen, ... -->
     <xsl:template match="contact">
      <tr> <!-- Fange eine neue Tabellenzeile an -->
       <!-- Verwende den Attributnamen der Kontakte als erste Spalte -->
       <td><xsl:value-of select="@name"/></td>
       <xsl:apply-templates/> <!-- und löse andere Templates rekursiv auf -->
      </tr>
     </xsl:template>

     <!-- Wenn wir auf ein <email>-Element stoßen, gib seinen Inhalt -->
     <!-- in der nächsten Zelle aus -->
     <xsl:template match="email">
      <td><xsl:value-of select="."/></td>
     </xsl:template>
    </xsl:stylesheet>

XSLT transformiert den Inhalt eines XML-Dokuments anhand der Regeln in einem XSL-Stylesheet. Bei clientseitigem JavaScript wird das normalerweise gemacht, um das XML-Dokument in HTML zu transformieren. Zwar verwenden viele Webanwendungsarchitekturen XSLT auf der Serverseite, aber Mozilla-basierte Browser und IE unterstützen XSLT auch auf der Clientseite. Das Verschieben der Transformation vom Server zum Client kann Serverressourcen und Netzwerkbandbreite einsparen (weil XML-Daten normalerweise kompakter sind als die HTML-Darstellung dieser Daten).

Viele moderne Browser können XML sowohl durch CSS- als auch durch XSL-Stylesheets mit Stilen versehen. Wenn Sie ein Stylesheet in einer Verarbeitungsanweisung `xml-stylesheet` angeben, können Sie ein XML-Dokument direkt in den Browser laden, und der Browser übernimmt die Style-Transformation des Dokuments und zeigt es an. Die benötigte Verarbeitungsanweisung kann z.B. so aussehen:

    <?xml-stylesheet href="dataToTable.xml" type="text/xsl"?>

Beachten Sie, dass der Browser diese Art der XSLT-Transformation automatisch durchführt, wenn ein XML-Dokument mit einer entsprechenden Verarbeitungsanweisung in das Anzeigefenster des Browsers geladen wird. Das ist wichtig und hilfreich, aber nicht das Thema dieses Abschnitts. Hier erkläre ich vielmehr, wie Sie mit JavaScript XSL-Transformationen dynamisch durchführen.

Das W3C definiert keine standardisierte API zur Durchführung von XSL-Transformationen an DOM-Document- und -Element-Objekten. In Mozilla-basierten Browsern stellt das XSLTProcessor-Objekt eine JavaScript-XSLT-API zur Verfügung. Unter IE haben XML-Document- und -Element-Objekte eine `transformNode( )`-Methode zum Ausführen von Transformationen. Beispiel 21-9 zeigt beide APIs. Es definiert eine XML.Transformer-Klasse, die ein XSL-Stylesheet kapselt und dessen Verwendung zur Transformation mehrerer XML-Dokumente gestattet. Die `transform( )`-Methode eines XML.Transformer-Objekts verwendet das gekapselte Stylesheet zur Transformation eines angegebenen XML-Dokuments und ersetzt den Inhalt eines angegebenen DOM-Elements durch die Transformationsergebnisse.

Beispiel 21-9. XSLT unter Mozilla und Internet Explorer

    /**
     * Diese XML.Transformer-Klasse kapselt ein XSL-Stylesheet ein.
     * Wenn der Stylesheet-Parameter eine URL ist, laden wir sie.
     * Ansonsten gehen wir davon aus, dass es ein passendes DOM-Dokument ist.
     */
    XML.Transformer = function(stylesheet) {
      // Lade das Stylesheet, wenn nötig.
      if (typeof stylesheet == "string") stylesheet = XML.load(stylesheet);
      this.stylesheet = stylesheet;

      // In Mozilla-basierten Browsern: Erzeuge ein XSLTProcessor-Objekt, und
      // informiere es über das Stylesheet.
      if (typeof XSLTProcessor != "undefined") {
        this.processor = new XSLTProcessor( );
        this.processor.importStylesheet(this.stylesheet);
      }
    };

    /**
     * Das ist die transform( )-Methode der XML.Transformer-Klasse.
     * Sie transformiert den angegebenen XML-Knoten mit Hilfe des gekapselten Stylesheets.
     * Es wird davon ausgegangen, dass die Transformation HTML ergibt. Es
     * ersetzt den Inhalt des angegebenen Elements.
     */
    XML.Transformer.prototype.transform = function(node, element) {
      // Wenn element über id angegeben ist, suche es.
      if (typeof element == "string") element = document.getElementById(element);

      if (this.processor) {
        // Haben wir einen XSLTProcessor erzeugt (d.h. wir sind in Mozilla), wenden wir
        // ihn an.
        // Transformiere den Knoten in ein DOM-DocumentFragment.
        var fragment = this.processor.transformToFragment(node, document);
        // Lösche den momentanen Inhalt von element.
        element.innerHTML = "";
        // Und füge die transformierten Knoten ein.
        element.appendChild(fragment);
      }
      else if ("transformNode" in node) {
        // Wenn der Knoten eine transformNode( )-Funktion hat (unter IE), verwende sie.
        // Beachten Sie, dass transformNode( ) einen String zurückgibt.
        element.innerHTML = node.transformNode(this.stylesheet);
      }
      else {
        // Ansonsten haben wir diesmal kein Glück.
        throw "XSLT wird unter diesem Browser nicht unterstützt";
      }
    };

    /**
     * Das ist eine XSLT-Utility-Funktion, die nützlich ist, wenn ein Stylesheet
     * nur einmal verwendet wird.
     */
    XML.transform = function(xmldoc, stylesheet, element) {
      var transformer = new XML.Transformer(stylesheet);
      transformer.transform(xmldoc, element);
    }

Als dieses Buch geschrieben wurde, waren IE und Mozilla-basierte Browser die einzigen geläufigen Browser, die eine JavaScript-API für XSLT zur Verfügung stellten. Wenn die Unterstützung anderer Browser für Sie wichtig ist, interessiert Sie vielleicht AJAXSLT, eine Open Source JavaScript XSLT-Implementierung. AJAXSLT stammt von Google und wird unter <http://goog-ajaxslt.sourceforge.net> entwickelt.

# 21.4 XML mit XPath abfragen

XPath ist eine einfache Sprache, die sich auf die Elemente, Attribute und Text in einem XML-Dokument bezieht. Ein XPath-Ausdruck kann auf ein XML-Element über seine Position in der Dokumenthierarchie verweisen oder ein Element aufgrund des Wertes (oder auch nur des Vorhandenseins) eines Attributs auswählen. Eine vollständige Behandlung von XPath übersteigt den Umfang dieses Kapitels, aber "21.4.1 XPath-Beispiele" gibt eine einfache Einführung in XPath, die häufige XPath-Ausdrücke durch Beispiele erklärt.

Das W3C entwarf eine API zur Auswahl von Knoten in einem DOM-Dokumentenbaum über einen XPath-Ausdruck. Firefox und verwandte Browser implementieren diese W3C API mit Hilfe der `evaluate( )`-Methode des Document-Objekts (für HTML- und XML-Dokumente). Mozilla-basierte Browser implementieren auch noch `Document. createExpression( )`. Diese Methode kompiliert einen XPath-Ausdruck, damit er auch mehrmals effizient ausgewertet werden kann.

Unter IE kann ein XPath-Ausdruck mit den Methoden `selectSingleNode( )` und `selectNodes( )` von XML- (aber nicht HTML-) Dokument- und -Element-Objekten evaluiert werden. Weiter unten in diesem Abschnitt ist Beispielcode aufgeführt, der sowohl die W3C als auch die IE API verwendet.

Falls Sie XPath mit anderen Browsern verwenden wollen, eignet sich vielleicht das AJAXSLT-Projekt unter <http://goog-ajaxslt.sourceforge.net>.

## 21.4.1 XPath-Beispiele

Wenn Sie die Baumstruktur eines DOM-Dokuments verstehen, sollte es kein Problem für Sie sein, einfache XPath-Ausdrücke mit Hilfe von Beispielen zu lernen. Um diese Beispiele zu verstehen, müssen Sie wissen, dass ein XPath-Ausdruck in Bezug zu einem _Kontextknoten_ im Dokument ausgewertet wird. Die einfachsten XPath-Ausdrücke verweisen schlicht auf Nachfahren des Kontextknotens:

    contact      // Die Gruppe aller <contact>-Tags unterhalb des Kontextknotens
    contact[1]     // Das erste <contact>-Tag unterhalb des Kontexts
    contact[last( )]  // Das letzte <contact>-Kind des Kontextknotens
    contact[last( )-1] // Das vorletzte <contact>-Kind des Kontextknotens

Beachten Sie, dass die Indizes in der Syntax eines XPath-Arrays nicht mit 0 (wie bei Arrays im JavaScript-Stil) anfangen, sondern mit 1.

Der »path« im Namen XPath bezieht sich darauf, dass die Sprache Ebenen in der XML-Element-Hierarchie genau wie Verzeichnisse in einem Dateisystem behandelt und das Zeichen »/« verwendet, um Hierarchieebenen voneinander abzugrenzen. Also:

    contact/email   // Alle <email>-Kinder der <contact>-Kinder des Kontexts
    /contacts     // Das <contacts>-Kind der Document Root (voranstehendes /)
    contact[1]/email  // Die <email>-Kinder des ersten <contact>-Kindes
    contact/email[2]  // Das zweite <email>-Kind aller <contact>-Kinder des Kontexts

Beachten Sie, dass `contact/email[2]` die Gruppe von `<email>`-Elementen ausgibt, die jeweils das zweite `<email>`-Kind aller `<contact>`-Kinder des Kontextknotens sind. Das unterscheidet sich von `contact[2]/email` und `(contact/email)[2]`.

Ein Punkt (.) in einem XPath-Ausdruck verweist auf das `context`-Element. Ein doppelter Schrägstrich (`//`) ignoriert die Hierarchieebenen und verweist nicht auf ein direktes Kind, sondern auf einen beliebigen Nachfahren. Ein Beispiel:

    .//email     // Alle <email>-Nachfahren des Kontexts
    //email     // Alle <email>-Tags im Dokument         // (beachten Sie den am Anfang stehenden Schrägstrich)

XPath-Ausdrücke können sowohl auf XML-Attribute als auch auf Elemente verweisen. Das Zeichen `@` wird als Präfix verwendet, um einen Attributnamen zu identifizieren:

    @id      // Der Wert des id-Attributs des Kontextknotens
    contact/@name // Die Werte der Namensattribute der <contact>-Kinder

Der Wert eines XML-Attributs kann die von einem XPath-Ausdruck zurückgegebene Gruppe von Elementen filtern. Ein Beispiel:

    contact[@personal="true"] // Alle <contact>-Tags mit dem Attribut personal="true"

Um den Textinhalt von XML-Elementen auszuwählen, verwenden Sie die `text( )`-Methode:

    contact/email/text( ) // Die Textknoten in <email>-Tags
    //text( )       // Alle Textknoten im Dokument

XPath erkennt Namensräume, und Sie können Namensraumpräfixe in Ihre Ausdrücke aufnehmen:

    //xsl:template   // Wähle alle <xsl:template>-Elemente aus

Wenn Sie einen XPath-Ausdruck evaluieren, der Namensräume verwendet, müssen Sie den Namensraum-URLs natürlich Namensraumpräfixe zuordnen.

Diese Beispiele spiegeln lediglich die üblichen Gebrauchsmuster von XPath wider. XPath hat zusätzliche Syntax und Features, die hier nicht beschrieben werden. Ein Beispiel hierfür ist die Funktion `count( )`, die nicht die Knotenmenge selbst zurückgibt, sondern die Knotenanzahl in der Menge:

    count(//email)  // Die Anzahl der <email>-Elemente im Dokument

## 21.4.2 XPath-Ausdrücke auswerten

Beispiel 21-10 zeigt eine Klasse XML.XPathExpression, die unter IE und standardkonformen Browsern, wie z.B. Firefox, funktioniert.

Beispiel 21-10. XPath-Ausdrücke auswerten

    /**
     * XML.XPathExpression ist eine Klasse, die eine XPath-Query und ihre
     * zugehörige Namensraumpräfix-zu-URL-Zuordnung kapselt. Wurde ein Objekt XML.XPathExpression
     * erzeugt, kann es einmal oder mehrfach ausgewertet werden (in einem
     * oder mehreren Kontexten), und zwar mit den Methoden getNode( ) oder getNodes( ).
     *
     * Das erste Argument dieses Konstruktors ist der Text des XPath-Ausdrucks.
     *
     * Enthält der Ausdruck beliebige XML-Namensräume, so muss das zweite Argument
     * ein JavaScript-Objekt sein, das Namensraumpräfixe den URLs zuordnet, die
     * diese Namensräume definieren. Die Eigenschaften dieses Objekts sind die Präfixe, und
     * die Werte dieser Eigenschaften sind die URLs.
     */
    XML.XPathExpression = function(xpathText, namespaces) {
      this.xpathText = xpathText;  // Speichere den Text des Ausdrucks
      this.namespaces = namespaces; // Und die Namensraumzuordnung

      if (document.createExpression) {
        // Wenn wir in einem W3C-konformen Browser sind, so verwende die W3C API,
        // um den Text der XPath-Query zu kompilieren
        this.xpathExpr =
          document.createExpression(xpathText,
                       // Diese Funktion wird an das
                       // Namensraumpräfix übergeben und gibt die URL zurück.
                       function(prefix) {
                         return namespaces[prefix];
                       });
      }
      else {
        // Ansonsten nehmen wir fürs Erste an, dass wir unter IE arbeiten, und konvertieren
        // das namespaces-Objekt in das von IE geforderte Textformat.
        this.namespaceString = "";
        if (namespaces != null) {
          for(var prefix in namespaces) {
            // Füge ein Leerzeichen ein, wenn hier bereits etwas steht
            if (this.namespaceString) this.namespaceString += ' ';
            // Und füge den Namensraum hinzu
            this.namespaceString += 'xmlns:' + prefix + '="' +
              namespaces[prefix] + '"';
          }
        }
      }
    };

    /**
     * Das ist die getNodes( )-Methode von XML.XPathExpression. Sie wertet den
     * XPath-Ausdruck im angegebenen Kontext aus. Das Kontextargument sollte
     * ein Document- oder Element-Objekt sein. Der Rückgabewert ist ein Array
     * oder ein Array-ähnliches Objekt mit den Knoten, die zum Ausdruck passen.
     */
    XML.XPathExpression.prototype.getNodes = function(context) {
      if (this.xpathExpr) {
        // Wenn wir in einem W3C-konformen Browser sind, dann haben wir
        // den Ausdruck im Konstruktor kompiliert. Jetzt werten wir diesen kompilierten
        // Ausdruck im angegebenen Kontext aus.
        var result =
          this.xpathExpr.evaluate(context,
                      // Das ist der gewünschte Ergebnistyp
                      XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                      null);

        // Kopiere die erhaltenen Ergebnisse in ein Array.
        var a = new Array(result.snapshotLength);
        for(var i = 0; i < result.snapshotLength; i++) {
          a[i] = result.snapshotItem(i);
        }
        return a;
      }
      else {
        // Wenn wir nicht in einem W3C-konformen Browser sind, so versuche, den Ausdruck
        // mit der IE API auszuwerten.
        try {
          // Wir brauchen das Document-Objekt, um Namensräume anzugeben
          var doc = context.ownerDocument;
          // Wenn der Kontext kein ownerDocument hat, ist es das Dokument
          if (doc == null) doc = context;
          // Das ist IE-spezifische Zauberei, um die Präfix-zu-URL-Zuordnung anzugeben
          doc.setProperty("SelectionLanguage", "XPath");
          doc.setProperty("SelectionNamespaces", this.namespaceString);

          // Unter IE muss der Kontext ein Element und kein Dokument sein;
          // wenn der Kontext ein Dokument ist, so verwende stattdessen documentElement
          if (context == doc) context = doc.documentElement;
          // Verwende jetzt die IE-Methode selectNodes( ), um den Ausdruck auszuwerten
          return context.selectNodes(this.xpathText);
        }
        catch(e) {
          // Wenn die IE API nicht funktioniert, geben wir auf
          throw "XPath wird von diesem Browser nicht unterstützt";
        }
      }
    }

    /**
     * Das ist die getNode( )-Methode von XML.XPathExpression. Sie wertet den
     * XPath-Ausdruck im angegebenen Kontext aus und gibt genau einen passenden
     * Knoten aus (oder null, wenn kein Knoten passt). Wenn mehrere Knoten passen,
     * gibt diese Methode den ersten im Dokument aus.
     * Die Implementierung unterscheidet sich von getNodes( ) lediglich durch den Rückgabetyp.
     */
    XML.XPathExpression.prototype.getNode = function(context) {
      if (this.xpathExpr) {
        var result =
          this.xpathExpr.evaluate(context,
                      // Wir wollen nur den ersten passenden Knoten
                      XPathResult.FIRST_ORDERED_NODE_TYPE,
                      null);
        return result.singleNodeValue;
      }
      else {
        try {
          var doc = context.ownerDocument;
          if (doc == null) doc = context;
          doc.setProperty("SelectionLanguage", "XPath");
          doc.setProperty("SelectionNamespaces", this.namespaceString);
          if (context == doc) context = doc.documentElement;
          // Unter IE rufe selectSingleNode statt selectNodes auf
          return context.selectSingleNode(this.xpathText);
        }
        catch(e) {
          throw "XPath wird unter diesem Browser nicht unterstützt";
        }
      }
    };

    // Eine Utility, die eine XML.XPathExpression erzeugt und an ihr getNodes( ) aufruft
    XML.getNodes = function(context, xpathExpr, namespaces) {
      return (new XML.XPathExpression(xpathExpr, namespaces)).getNodes(context);
    };

    // Eine Utility, die eine XML.XPathExpression erzeugt und an ihr getNode( ) aufruft
    XML.getNode = function(context, xpathExpr, namespaces) {
      return (new XML.XPathExpression(xpathExpr, namespaces)).getNode(context);
    };

## 21.4.3 Mehr zur W3C XPath API

Aufgrund der Einschränkungen in der IE XPath API verarbeitet der Code in Beispiel 21-10 nur Anfragen, die nach einem Dokumentknoten oder einer Knotengruppe suchen. Unter IE ist es unmöglich, einen XPath-Ausdruck auszuwerten, der einen Textstring oder eine Zahl zurückgibt. Mit der W3C-Standard-API ist das mit Code möglich, der so aussieht:

    // Wie viele <p>-Tags sind im Dokument?
    var n = document.evaluate("count(//p)", document, null,
                 XPathResult.NUMBER_TYPE, null).numberValue;
    // Was ist der Text im zweiten Absatz?
    var text = document.evaluate("//p[2]/text( )", document, null,
                   XPathResult.STRING_TYPE, null).stringValue;

Bei diesen einfachen Beispielen müssen Sie auf zwei Dinge achten. Erstens verwenden sie die `document.evaluate( )`-Methode, um einen XPath-Ausdruck direkt auszuwerten, ohne ihn zuerst zu kompilieren. Im Gegensatz dazu verwendet der Code in Beispiel 21-10 die `document.createExpression( )`-Methode, um einen XPath-Ausdruck in ein wiederverwendbares Format zu kompilieren. Zweitens benutzen diese Beispiele HTML-`<p>`-Tags im `document`-Objekt. Unter Firefox können XPath-Abfragen für HTML- und XML-Dokumente verwendet werden.

Unter Document, XPathExpression und XPathResult im Teil IV werden alle Einzelheiten der W3C XPath API beschrieben.

# 21.5 Serialisierung von XML

Manchmal ist es sinnvoll, ein XML-Dokument (oder ein Unterelement des Dokuments) durch Konvertierung in einen String zu _serialisieren_ – zum Beispiel, um ein XML-Dokument als Body eines HTTP POST-Requests zu senden, der mit dem XMLHttpRequest-Objekt erzeugt wurde. Ein XML-Dokument und XML-Elemente werden auch oft serialisiert, um Nachrichten zu debuggen

In Mozilla-basierten Browsern wird die Serialisierung mit dem XMLSerializer-Objekt durchgeführt. Unter IE ist es noch einfacher: Die `xml`-Eigenschaft eines XML-Document- oder -Element-Objekts gibt das serialisierte Format des Dokuments oder Elements aus.

Beispiel 21-11 zeigt Code zur Serialisierung unter Mozilla und IE.

Beispiel 21-11. Serialisierung von XML

    /**
     * Serialisiere ein XML-Document oder -Element, und gib es als String zurück.
     */
    XML.serialize = function(node) {
      if (typeof XMLSerializer != "undefined")
        return (new XMLSerializer( )).serializeToString(node);
      else if (node.xml) return node.xml;
      else throw "XML.serialize wird nicht unterstützt oder kann " + node + " nicht
      serialisieren";
    };

# 21.6 HTML-Templates mit XML-Daten erweitern

Ein Hauptfeature der XML-Dateninseln von IE ist die Möglichkeit, dass sie mit einer automatischen Template-Einrichtung verwendet werden können, in der Daten aus einer Dateninsel automatisch in HTML-Elemente eingefügt werden. Diese HTML-Templates werden unter IE definiert, indem die Attribute `datasrc` und `datafld` (»fld« kurz für »field«) zu den Elementen hinzugefügt werden.

In diesem Abschnitt werden die weiter oben im Kapitel vorgestellten Techniken angewandt. XPath und DOM werden verwendet, um eine verbesserte Template-Einrichtung zu erstellen, die unter IE und Firefox funktioniert. Ein Template ist ein beliebiges HTML-Element mit einem `datasource`-Attribut. Der Wert dieses Attributs sollte die ID einer XML-Dateninsel oder die URL eines externen XML-Dokuments sein. Das Template-Element sollte außerdem ein `foreach`-Attribut haben. Der Wert dieses Attributs ist ein XPath-Ausdruck, dessen Auswertung die Knotenliste ergibt, aus der die XML-Daten extrahiert werden. Für jeden XML-Knoten, der von dem `foreach`-Ausdruck zurückgegeben wird, wird eine erweiterte Kopie des Templates in das HTML-Dokument eingefügt. Das Template wird erweitert, indem alle Elemente in ihm gefunden werden, die ein `data`-Attribut haben. Dieses Attribut ist ein weiterer XPath-Ausdruck, der im Kontext eines von dem `foreach`-Ausdruck zurückgegeben Knotens ausgewertet werden soll. Dieser `data`-Ausdruck wird mit `XML.getNode( )` ausgewertet. Der im zurückgegebenen Knoten enthaltene Text wird als Inhalt des HTML-Elements verwendet, für das das `data`-Attribut definiert wurde.

Diese Beschreibung wird durch ein praktisches Beispiel viel verständlicher. Beispiel 21-12 ist ein einfaches HTML-Dokument, das eine XML-Dateninsel enthält, sowie ein Template, das die Dateninsel verwendet. Es hat einen `onload( )`-Event-Handler, der das Template erweitert.

Beispiel 21-12. Eine XML-Dateninsel und HTML-Template

    <html>
    <!-- Lade unsere XML-Utilities für Dateninseln und Templates -->
    <head><script src="xml.js"></script></head>
    <!-- Erweitere alle Templates beim Laden des Dokuments-->
    <body onload="XML.expandTemplates( )">

    <!-- Das ist eine XML-Dateninsel mit unseren Daten -->
    <xml id="data" style="display:none"> <!-- per CSS versteckt -->
     <contacts>
      <contact name="Able Baker"><email>able@example.com</email></contact>
      <contact name="Careful Dodger"><email>dodger@example.com</email></contact>
      <contact name="Eager Framer"><email>framer@example.com</email></contact>
     </contacts>
    </xml>

    <!-- Das hier sind ganz normale HTML-Elemente -->
    <table>
    <tr><th>Name</th><th>Adresse</th></tr>
    <!-- Das ist ein Template. Die Daten stammen aus der Dateninsel mit der id "data". -->
    <!-- Das Template wird für jedes <contact>-Tag einmal erweitert und kopiert -->
    <tr datasource="#data" foreach="//contact">
    <!-- Das "name"-Attribut von <contact> wird in dieses Element eingefügt -->
    <td data="@name"></td>
    <!-- Hier kommt der Inhalt des <email>-Kindes von <contact> hin -->
    <td data="email"></td>
    </tr> <!-- Ende des Templates -->
    </table>
    </body>
    </html>

Ein wichtiger Teil im Beispiel 21-12 ist der `onload`-Event-Handler, der eine Funktion namens `XML.expandTemplates( )` aufruft. Beispiel 21-13 zeigt die Implementierung dieser Funktion. Der Code ist weitgehend plattformunabhängig und benutzt die grundlegende Level 1 DOM-Funktionalität und die XPath-Utility-Funktionen `XML.getNode( )` and `XML. getNodes( )`, die aus Beispiel 21-10 stammen.

Beispiel 21-13. HTML-Templates erweitern

    */
     * Erweitere beliebige Templates in oder unterhalb von Element e.
     * Wenn ein Template XPath-Ausdrücke mit Namensräumen verwendet, so übergib
     * als zweites Argument eine Präfix-zu-URL-Zuordnung (wie bei XML.XPathExpression( ))
     *
     * Wenn e nicht angegeben wird, wird stattdessen document.body verwendet. Eine übliche
     * Anwendung ist der Aufruf dieser Funktion ohne Argumente als Antwort auf einen
     * onload-Event-Handler. Dadurch werden alle Templates automatisch erweitert.
     */
    XML.expandTemplates = function(e, namespaces) {
      // Argumente etwas in Ordnung bringen.
      if (!e) e = document.body;
      else if (typeof e == "string") e = document.getElementById(e);
      if (!namespaces) namespaces = null; // undefined funktioniert nicht

      // Ein HTML-Element ist ein Template, wenn es ein "datasource"-Attribut aufweist.
      // Suche und erweitere alle Templates rekursiv. Beachten Sie, dass wir
      // Templates innerhalb von Templates nicht zulassen.
      if (e.getAttribute("datasource")) {
        // Wenn es ein Template ist, so erweitere es.
        XML.expandTemplate(e, namespaces);
      }
      else {
        // Ansonsten führe die Rekursion für jedes Kind durch. Wir fertigen eine statische
        // Kopie der Kinder an, sodass die Template-Erweiterung unsere
        // Rekursion nicht durcheinanderbringt.
        var kids = []; // Zum Speichern der Kindelement-Kopien
        for(var i = 0; i < e.childNodes.length; i++) {
          var c = e.childNodes[i];
          if (c.nodeType == 1) kids.push(e.childNodes[i]);
        }

        // Rekursiere jetzt für jedes Kindelement
        for(var i = 0; i < kids.length; i++)
          XML.expandTemplates(kids[i], namespaces);
      }
    };

    /**
     * Erweitere genau ein angegebenes Template.
     * Wenn die XPath-Ausdrücke im Template Namensräume verwenden, muss das zweite
     * Argument eine Präfix-zu-URL-Zuordnung angeben
     */
    XML.expandTemplate = function(template, namespaces) {
      if (typeof template=="string") template=document.getElementById(template);
      if (!namespaces) namespaces = null; // undefined funktioniert nicht

      // Das Erste, was wir über ein Template wissen müssen, ist, woher die
      // Daten kommen.
      var datasource = template.getAttribute("datasource");

      // Wenn das Attribut der Datenquelle mit '#' beginnt, so ist das der
      // Name einer XML-Dateninsel. Ansonsten ist es die URL einer externen XML-Datei.
      var datadoc;
      if (datasource.charAt(0) == '#')  // Hole Dateninsel
        datadoc = XML.getDataIsland(datasource.substring(1));
      else                // Oder lade externes Dokument
        datadoc = XML.load(datasource);

      // Finde jetzt heraus, welche Knoten in der Datenquelle verwendet werden, um
      // die Daten zur Verfügung zu stellen. Wenn das Template ein foreach-Attribut hat,
      // verwenden wir es als XPath-Ausdruck, um eine Knotenliste zu erhalten. Andernfalls
      // verwenden wir alle Kind-Elemente des document-Elements.
      var datanodes;
      var foreach = template.getAttribute("foreach");
      if (foreach) datanodes = XML.getNodes(datadoc, foreach, namespaces);
      else {
        // Wenn kein "foreach"-Attribut existiert, verwende die Kindelemente
        // des documentElement
        datanodes = [];
        for(var c=datadoc.documentElement.firstChild; c!=null; c=c.nextSibling)
          if (c.nodeType == 1) datanodes.push(c);
      }

      // Entferne das Template-Element aus seinem übergeordeten Knoten,
      // aber speichere den übergeordneten Knoten sowie den nextSibling des Templates.
      var container = template.parentNode;
      var insertionPoint = template.nextSibling;
      template = container.removeChild(template);

      // Für jedes Element des datanodes-Arrays fügen wir wieder eine Kopie des
      // Templates in den container ein. Bevor wir das machen,
      // erweitern wir aber jedes Kind in der Kopie, das ein "data"-Attribut hat.
      for(var i = 0; i < datanodes.length; i++) {
        var copy = template.cloneNode(true);      // Kopiere die Vorlage
        expand(copy, datanodes[i], namespaces);    // Erweitere die Kopie
        container.insertBefore(copy, insertionPoint); // Füge die Kopie ein
      }

      // Diese rekursive Funktion sucht alle Kindelemente von e, die ein data-
      // Attribut haben. Sie behandelt dieses Attribut als einen XPath-Ausdruck und
      // wertet ihn im Kontext von datanode aus. Sie nimmt den Textwert des
      // XPath-Ergebnisses und macht ihn zum Inhalt des zu erweiternden HTML-Knotens.
      // Der restliche Inhalt wird gelöscht.
      function expand(e, datanode, namespaces) {
        for(var c = e.firstChild; c != null; c = c.nextSibling) {
          if (c.nodeType != 1) continue; // nur Elemente
          var dataexpr = c.getAttribute("data");
          if (dataexpr) {
            // Werte XPath-Ausdruck in context aus.
            var n = XML.getNode(datanode, dataexpr, namespaces);
            // Lösche den gesamten Inhalt des Elements
            c.innerHTML = "";
            // und füge den Textinhalt des XPath-Ergebnisses ein
            c.appendChild(document.createTextNode(getText(n)));
          }
          // Wenn wir das Element nicht erweitern, rekursiere auf ihm
          else expand(c, datanode, namespaces);
        }
      }

      // Diese rekursive Funktion extrahiert den Text aus einem DOM-Node und
      // rekursiert, wenn notwendig.
      function getText(n) {
        switch(n.nodeType) {
        case 1: /* Element */
          var s = "";
          for(var c = n.firstChild; c != null; c = c.nextSibling)
            s += getText(c);
          return s;
        case 2: /* Attribut*/
        case 3: /* Text */
        case 4: /* cdata */
          return n.nodeValue;
        default:
          return "";
        }
      }

    };

# 21.7 XML und Webservices

Webservices stellen eine wichtige Anwendung für XML dar, und SOAP ist ein beliebtes Webservice-Protokoll, das vollständig auf XML beruht. In diesem Abschnitt zeige ich Ihnen, wie man das XMLHttpRequest-Objekt und XPath-Queries verwenden kann, um einfache SOAP-Anfragen an einen Webservice zu stellen.

Beispiel 21-14 ist JavaScript-Code, der ein XML-Dokument konstruiert. Dieses stellt einen SOAP-Request dar und verwendet `XMLHttpRequest`, um ihn an einen Webservice zu senden. (Der Webservice gibt den Umtauschkurs zwischen den Währungen zweier Länder zurück). Der Code verwendet dann eine XPath-Query, um das Ergebnis aus der vom Server zurückgegebenen SOAP-Response zu extrahieren.

Bevor wir uns den Code anschauen, muss ich Sie auf einige Dinge aufmerksam machen. Erstens sind Einzelheiten des SOAP-Protokolls nicht Gegenstand dieses Kapitels. Dieses Beispiel veranschaulicht einen einfachen SOAP-Request und eine SOAP-Response, ohne den Versuch zu machen, das Protokoll oder das XML-Format zu erklären. Zweitens verwendet das Beispiel keine _Web Services Definition Language_ -(WSDL-)Dateien, um Einzelheiten über den Webservice herauszufinden. Die URL des Servers, die Methode und die Parameternamen sind alle im Beispielcode hardcodiert.

Die dritte Warnung hat es in sich. Die Verwendung von Webservices von clientseitigem JavaScript aus wird durch die Same Origin Policy (siehe "13.8.2 Die Same Origin Policy") stark eingeschränkt. Erinnern wir uns daran, dass die Same Origin Policy Skripte auf der Clientseite davon abhält, eine Verbindung zu anderen Hosts herzustellen oder Daten von ihnen abzurufen, wenn sie nicht mit dem Host identisch sind, von dem das Dokument mit dem gerade ausführenden Skript geladen wurde. Das bedeutet, dass JavaScript-Code in der Regel nur dann zum Zugriff auf einen Webservice geeignet ist, wenn er auf dem gleichen Server wie der Webservice untergebracht wird. Wer einen Webservice implementiert, will JavaScript vielleicht als einfache auf HTML basierende Schnittstelle für seine Dienste verwenden. Die Same Origin Policy verhindert aber den umfangreichen Gebrauch von clientseitigem JavaScript, wenn es um die Aggregation von Webservice-Ergebnissen aus verschiedenen Internetquellen in einer einzigen Webseite geht.

Um Beispiel 21-14 unter IE laufen zu lassen, können Sie die Same Origin Policy lockern. Wählen Sie **Extras** → **Internetoptionen** → **Sicherheit** , und klicken Sie im dann erscheinenden Dialog auf die Registrierkarte **Internet**. Scrollen Sie durch die Auflistung der Sicherheitsoptionen, bis Sie die mit dem Namen »Auf Datenquellen über Domänengrenzen hinweg zugreifen« finden. Diese Option ist gewöhnlich auf **deaktivieren** gesetzt (und das ist auch richtig so). Um dieses Beispiel laufen zu lassen, ändern Sie sie in **Eingabeaufforderung**.

Damit Beispiel 21-14 unter Firefox läuft, enthält es einen Aufruf der Firefox-spezifischen `enablePrivilege( )`-Methode. Dieser Aufruf fordert den Benutzer auf, dem Skript weiterführende Rechte zuzugestehen, so dass es die Same Origin Policy aufheben kann. Das funktioniert, wenn das Beispiel von einer `file:`-URL aus im lokalen Dateisystem läuft. Es funktioniert nicht, wenn das Beispiel von einem Webserver heruntergeladen wird (es sei denn, das Skript wurde digital unterschrieben, was in diesem Buch nicht behandelt wird).

Mit diesen Warnungen im Hinterkopf schauen wir uns jetzt den Code an.

Beispiel 21-14. Einen Webservice mit SOAP abfragen

    /**
     * Diese Funktion gibt den Wechselkurs zwischen den Währungen zweier
     * Länder aus. Sie holt den Wechselkurs über einen SOAP-Request an einen
     * Beispiel-Webservice, der von XMethods gehostet wird (http://www.xmethods.net).
     * Dieser Service dient nur zur Veranschaulichung und garantiert keine
     * Ansprechempfindlichkeit, kein Vorhandensein oder keine Richtigkeit der Daten. Bitte
     * überlasten Sie den XMethod-Server nicht durch zu häufiges Ausführen dieses Beispiels.
     * Siehe http://www.xmethods.net/v2/demoguidelines.html
     */
    function getExchangeRate(land1, land2) {
      // Unter Firefox müssen wir den Benutzer auffordern, uns die erforderlichen Rechte zu
      // geben.
      // Wir brauchen spezielle Rechte, da wir mit einem Webserver reden, der
      // nicht derjenige ist, von dem aus das Dokument geladen wurde, das das Skript enthält.
      // UniversalXPConnect ermöglicht es uns, einen XMLHttpRequest an den Server zu senden,
      // ermöglicht es uns, einen XMLHttpRequest an den Server zu senden, und
      // und UniversalBrowserRead erlaubt es uns, die Antwort anzuschauen.
      // Unter IE muss der Benutzer "Auf Datenquellen über Domänengrenzen hinweg zugreifen"
      // im Dialog Extras->Internetoptionen->Sicherheit aktivieren.
      if (typeof netscape != "undefined") {
        netscape.security.PrivilegeManager.
            enablePrivilege("UniversalXPConnect UniversalBrowserRead");
      }

      // Erzeuge einen XMLHttpRequest, um den SOAP-Request auszugeben. Das ist eine Utility-
      // Funktion, die im letzten Kapitel definiert wurde.
      var request = HTTP.newRequest( );

      // Wir werden an diese URL POSTen und wollen eine synchrone Response
      request.open("POST", "http://services.xmethods.net/soap", false);

      // Setze ein paar Header: Der Body dieses POST-Requests ist XML
      request.setRequestHeader("Content-Type", "text/xml");

      // Dieser Header ist ein notwendiger Teil des SOAP-Protokolls
      request.setRequestHeader("SOAPAction", '""');

      // Sende jetzt einen SOAP-Request im XML-Format an den Server
      request.send(
        '<?xml version="1.0" encoding="UTF-8"?>' +
        '<soap:Envelope' +
        ' xmlns:ex="urn:xmethods-CurrencyExchange"' +
        ' xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"' +
        ' xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"' +
        ' xmlns:xs="http://www.w3.org/2001/XMLSchema"' +
        ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
        '  <soap:Body ' +
        '   soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'+
        '   <ex:getRate>' +
        '     <country1 xsi:type="xs:string">' + land1 + '</country1>' +
        '     <country2 xsi:type="xs:string">' + land2 + '</country2>' +
        '   </ex:getRate>' +
        '  </soap:Body>' +
        '</soap:Envelope>'
        );

      // Wenn wir einen HTTP-Fehler erhalten, löse eine Exception aus
      if (request.status != 200) throw request.statusText;

      // Diese XPath-Query gibt uns das <getRateResponse>-Element aus dem Dokument
      var query = "/s:Envelope/s:Body/ex:getRateResponse";

      // Dieses Objekt definiert die in der Abfrage verwendeten Namensräume
      var namespaceMapping = {
        s: "http://schemas.xmlsoap.org/soap/envelope/", // SOAP-Namensraum
        ex: "urn:xmethods-CurrencyExchange" // der service-spezifische Namensraum
      };

      // Extrahiere das <getRateResponse>-Element aus dem Antwortdokument
      var responseNode=XML.getNode(request.responseXML, query, namespaceMapping);

      // Das tatsächliche Ergebnis steht in einem Textknoten innerhalb eines <Result>-Knotens
      // in der <getRateReponse>
      return responseNode.firstChild.firstChild.nodeValue;
    }

# 21.8 E4X: ECMAScript für XML

ECMAScript für XML, besser bekannt als E4X, ist eine Standarderweiterung[80] von JavaScript, die eine Anzahl leistungsfähiger Features für die Verarbeitung von XML-Dokumenten definiert. Als dieses Buch geschrieben wurde, stand E4X noch nicht weitverbreitet zur Verfügung. Firefox 1.5 unterstützt es, und es ist auch auch in Version 1.6 von Rhino verfügbar, dem JavaScript-Interpreter auf Java-Basis. Microsoft plant nicht, es in IE 7 zu unterstützen, und es ist auch nicht klar, ob oder wann andere Browser Unterstützung anbieten werden.

Obwohl E4X ein offizieller Standard ist, ist es noch nicht verbreitet genug, um eine genaue Beschreibung in diesem Buch zu rechtfertigen. Trotz seiner begrenzten Verfügbarkeit werden die leistungsstarken und einzigartigen Features von E4X hier kurz dargestellt. In diesem Abschnitt erhalten Sie anhand von Beispielen einen Überblick über E4X. Zukünftige Ausgaben dieses Buchs werden das Thema eventuell besser abdecken.

Das Auffälligste an E4X ist, dass die XML-Syntax zum Bestandteil der JavaScript-Sprache wird und Sie XML-Literale wie das folgende direkt in Ihren JavaScript-Code aufnehmen können:

    // Erzeuge ein XML-Objekt
    var pt =
      <periodentafel>
       <element id="1"><name>Wasserstoff</name></element>
       <element id="2"><name>Helium</name></element>
       <element id="3"><name>Lithium</name></element>
      </periodentafel>;

    // Füge der Tabelle ein neues Element hinzu
    pt.element += <element id="4"><name>Beryllium</name></element>;

Die XML-Literalsyntax von E4X verwendet geschweifte Klammern als Escape-Zeichen, mit denen Sie JavaScript-Ausdrücke in XML unterbringen können. Das hier ist ein anderes Beispiel zum Erstellen des gerade gezeigten XML-Elements:

    pt = <periodentafel></periodentafel>;         // Beginne mit einer leeren Tabelle
    var elements = ["Wasserstoff", "Helium", "Lithium"]; // die hinzuzufügenden Elemente
    // Erzeuge XML-Tags aus Array-Inhalten
    for(var n = 0; n < elements.length; n++) {
      pt.element += <element id={n+1}><name>{elements[n]}</name></element>;
    }

Zusätzlich zu dieser Literal-Syntax können Sie auch mit aus Strings geparstem XML arbeiten. Dieser Code fügt Ihrem Periodensystem ein weiteres Element hinzu:

    pt.element += new XML('<element id="5"><name>Bor</name></element>');

Wenn Sie mit XML-Fragmenten arbeiten, sollten Sie statt `XML( )` besser `XMLList( )` verwenden:

    pt.element += new XMLList('<element id="6"><name>Kohlenstoff</name></element>' +
                 '<element id="7"><name>Stickstoff</name></element>');

Sobald Sie ein XML-Dokument definiert haben, definiert E4X eine intuitive Syntax zum Zugriff auf seinen Inhalt:

    var elements = pt.element;  // Ergibt eine Liste aller <element>-Tags
    var names = pt.element.name; // Eine Liste aller <name>-Tags
    var n = names[0];       // "Wasserstoff": Inhalt des <name>-Tags 0.

Zusätzlich fügt E4X neue Syntax zum Arbeiten mit XML-Objekten hinzu. Der Operator .. ist der Nachfahre-Operator; Sie können ihn anstelle des normalen .-Klassenelement-Operators verwenden:

    // Hier sehen Sie eine Alternative zum Erzeugen einer Liste aller <name>-Tags
    var names2 = pt..name;

E4X hat sogar einen Wildcard-Operator:

    // Hole alle Nachfahren aller <element>-Tags.
    // Dies ist noch eine Möglichkeit, eine Liste aller <name>-Tags zu erhalten.
    var names3 = pt.element.*;

Unter E4X unterscheiden sich Attributnamen von Tag-Namen durch das Zeichen `@` (eine von XPath übernommene Syntax). Den Wert eines Attributs können Sie beispielsweise so abfragen:

    // Welche Ordnungszahl hat Helium?
    var atomicNumber = pt.element[1].@id;

Der Wildcard-Operator für Attributnamen ist `@*`:

    // Eine Liste aller Attribute aller <element>-Tags
    var atomicNums = pt.element.@*;

E4X enthält sogar eine schlagkräftige und erstaunlich präzise Syntax zum Filtern einer Liste anhand einer beliebigen Eigenschaft:

    // Beginne mit einer Liste aller Elemente, und filtere sie so,
    // dass sie nur diejenigen Elemente enthält, deren id-Attribut < 3 ist
    var lightElements = pt.element.(@id < 3);

    // Beginne mit einer Liste aller <element>-Tags, und filtere sie so, dass sie nur
    // diejenigen Tags enthält, deren Namen mit "B" beginnen. Erstelle dann eine Liste der
    // <name>-Tags mit jedem dieser übrig gebliebenen <element>-Tags.
    var bElementNames = pt.element.(name.charAt(0) == 'B').name;

E4X definiert ein neues Schleifenstatement, um die Liste der XML-Tags und -Attribute zu durchlaufen. Die Schleife `for/each/in` funktioniert wie die `for/in`-Schleife, außer dass nicht durch die Eigenschaften eines Objekts iteriert wird, sondern durch die Werte der Eigenschaften eines Objekts:

    // Drucke den Namen jedes Elements im Periodensystem aus
    // (Angenommen, Sie haben eine print( )-Funktion definiert.)
    for each (var e in pt.element) {
      print(e.name);
    }

    // Drucke die Ordnungszahlen der Elemente aus
    for each (var n in pt.element.@*) print(n);

Bei E4X-aktivierten Browsern ist diese `for/each/in`-Schleife auch zum Durchlaufen von Arrays nützlich.

E4X-Ausdrücke können auf der linken Seite einer Zuweisung stehen. Dadurch können bestehende Tags und Attribute geändert und neue hinzugefügt werden:

    // Ändere das <element>-Tag für Wasserstoff, um ein neues Attribut
    // und ein neues Kindelement hinzuzufügen, sodass der Code so aussieht:
    //
    // <element id="1" symbol="H">
    //  <name>Wasserstoff</name>
    //  <gewicht>1.00794</gewicht>
    // </element>
    //
    pt.element[0].@symbol = "H";
    pt.element[0].gewicht = 1.00794;

Mit dem standardisierten `delete`-Operator können Attribute und Tags auch auf einfache Art entfernt werden:

    delete pt.element[0].@symbol; // lösche ein Attribut
    delete pt..gewicht;      // lösche alle <gewicht>-Tags

E4X wurde so entwickelt, dass die meisten üblichen XML-Manipulationen mit der Sprachsyntax durchgeführt werden können. E4X definiert zusätzlich Methoden, die Sie auf XML-Objekten aufrufen können. Hier sehen Sie beispielsweise die `insertChildBefore( )`-Methode:

    pt.insertChildBefore(pt.element[1],
               <element id="1"><name>Deuterium</name></element>);

Beachten Sie, dass die von E4X-Ausdrücken erzeugten und bearbeiteten Objekte XML-Objekte sind. Sie sind keine DOM-Node- oder -Element-Objekte und sind nicht mit der DOM API kompatibel. Der E4X-Standard definiert eine optionale XML-Methode `domNode( )`, die ein DOM-Knoten-Äquivalent zum XML-Objekt zurückgibt, aber diese Methode wird unter Firefox 1.5 nicht implementiert. Der E4X-Standard gibt auch an, dass ein DOM-Knoten an den `XML( )`-Konstruktor übergeben werden kann, um das E4X-Äquivalent des DOM-Baums zu erhalten. Dieses Feature ist unter Firefox 1.5 ebenfalls nicht implementiert, wodurch die Nützlichkeit von E4X für clientseitiges JavaScript eingeschränkt wird.

E4X ist vollständig kompatibel mit Namensräumen und enthält Sprachsyntax und APIs zum Arbeiten mit XML-Namensräumen. Aus Gründen der Einfachheit haben die hier gezeigten Beispiele diese Syntax nicht demonstriert.

* * *

[80] E4X wird durch den ECMA-357-Standard definiert. Sie können die offizielle Spezifikation unter <http://www.ecma-international.org/publications/standards/Ecma-357.htm> finden.

# Kapitel 22. Skriptgesteuerte Grafiken auf der Clientseite

In diesem Kapitel wird beschrieben, wie Grafiken mit JavaScript bearbeitet werden. Zunächst werden herkömmliche JavaScript-Techniken für optische Effekte erklärt, wie z.B. für Bild-Rollover (bei denen ein statisches Bild durch ein anderes ersetzt wird, wenn der Mauszeiger über dieses bewegt wird). Anschließend wird beschrieben, wie Sie JavaScript zum Zeichnen Ihrer eigenen Grafiken verwenden können. Durch die Kombination von JavaScript mit CSS können horizontale und vertikale Linien und Rechtecke gezeichnet werden, was für viele Arten von »Boxen-und-Pfeile«-Zeichnungen und aufwendigere Grafiken, wie beispielsweise Balkendiagramme, ausreicht.

Als Nächstes werden Vektorgrafik-Technologien erläutert, die weitaus leistungsfähigere Zeichnungsmöglichkeiten auf der Clientseite bereitstellen. Die Fähigkeit, anspruchsvolle Grafiken auf der Clientseite dynamisch erzeugen zu können, ist aus mehreren Gründen wichtig:

  * Der Code zum Erstellen von clientseitigen Grafiken ist normalerweise viel kürzer als die eigentlichen Bilder es wären, wodurch erhebliche Bandbreiteneinsparungen möglich werden.

  * Dynamisch erzeugte Grafiken aus Echtzeitdaten brauchen sehr viele CPU-Zyklen. Wird diese Arbeit an den Client weitergegeben (der oft CPU-Leistung übrig hat), vermindert sich die Serverlast, und es lassen sich eventuell sogar Hardware-Kosten einsparen.

  * Die Erzeugung von clientseitigen Grafiken passt in die Ajax-Anwendungsarchitektur, bei der Server die Daten zur Verfügung stellen und die Clients die Anzeige dieser Daten regeln.

Das Kapitel enthält Beispiele für fünf Vektorgrafik-Technologien, die mit clientseitigem JavaScript nutzbar gemacht werden können:

  * Scalable Vector Graphics oder kurz SVG ist eine dem W3C-Standard entsprechende und auf XML basierende Sprache zur Beschreibung von Zeichnungen. SVG wurde als Erstes von Firefox 1.5 unterstützt und ist über Plug-ins auch in anderen Browsern verfügbar. Da SVG-Zeichnungen XML-Dokumente sind, können sie in JavaScript dynamisch erzeugt werden.

  * Vector Markup Language, oder VML, ist die SVG-Alternative exklusiv für Microsoft. Sie ist nicht sehr bekannt, steht aber seit Internet Explorer 5.5 zur Verfügung. Genau wie SVG basiert VML auf XML, und VML-Zeichnungen können dynamisch auf der Clientseite erzeugt werden.

  * Das HTML-`<canvas>`-Tag stellt eine ausdrücklich auf JavaScript basierende Zeichnungs-API zur Verfügung. Sie wurde unter Safari 1.3 eingeführt und von Firefox 1.5 und Opera 9 übernommen.

  * Der Flash-Player steht in den meisten Webbrowsern als Plug-in zur Verfügung. Flash 6 führte eine Zeichnungs-API ein, und mit Flash 8 wurde die Verwendung dieser API von clientseitigem JavaScript aus sehr vereinfacht.

  * Schließlich unterstützt Java eine leistungsstarke Zeichnungs-API und ist in vielen Webbrowsern über ein Plug-in von Sun Microsystems erhältlich. Wie die Kapitel 12 und 23 zeigen, kann JavaScript-Code Methoden von Java-Applets aufrufen und – in Mozilla-basierten Browsern – sogar Java-Methoden in Abwesenheit eines Applets aufrufen. Dadurch kann JavaScript-Code auf der Clientseite die hochentwickelte Vektorzeichnungs-API von Java verwenden.

Bevor wir uns diese fortgeschrittenen Zeichentechniken ansehen, fangen wir zunächst mit den Grundlagen an.

# 22.1 Skriptgesteuerte Bilder

Webseiten enthalten Bilder, die das HTML-`<img>`-Tag verwenden. Wie alle HTML-Elemente ist ein `<img>`-Tag Teil des DOM und kann deshalb wie jedes andere Element in einem Dokument per Skript gesteuert werden. Dieser Abschnitt stellt einige weitverbreitete Techniken vor.

## 22.1.1 Bilder und das Level 0 DOM

Bilder waren eines der ersten skriptgesteuerten HTML-Elemente. Mit dem Level 0 DOM können Sie über das `images]`-Array des Document-Objekts auf Bilder zugreifen. Jedes Element dieses Arrays ist ein Image-Objekt, das ein `<img>`-Tag im Dokument repräsentiert. Eine vollständige Dokumentation des Image-Objekts finden Sie in [Teil IV. Zugriff auf Image-Objekte besteht auch über Level 1 DOM-Methoden, wie z.B. `getElementById( )` und `getElementsByTagName( )` (siehe Kapitel 15).

Das Array `document.images[]` führt Image-Objekte in der im Dokument angezeigten Reihenfolge auf. Nützlicher ist allerdings, dass es Zugang zu angegebenen Bildern verschafft. Hat ein `<img>`-Tag ein `name`-Attribut, so kann das Bild über den von diesem Attribut angegebenen Namen abgefragt werden. Nehmen wir beispielsweise dieses `<img>`-Tag:

    <img name="nextpage" src="nextpage.gif">

Wenn wir annehmen, dass kein anderes `<img>`-Tag den gleichen Wert für sein `name`-Attribut hat, ist das entsprechende Image-Objekt zugänglich als:

    document.images.nextpage

oder als:

    document.images["nextpage"]

Wenn kein anderes Tag (beliebigen Typs) im Dokument das gleiche `name`-Attribut hat, ist das Image-Objekt sogar über eine Eigenschaft des `document`-Objekts selbst zugänglich:

    document.nextpage

## 22.1.2 Herkömmliche Bild-Rollover

Das wichtigste Feature des Image-Objekts ist, dass seine `src`-Eigenschaft les- und schreibbar ist. Sie können diese Eigenschaft lesen, um die URL zu erhalten, von der ein Bild geladen wurde. Noch wichtiger ist die Möglichkeit, die `src`-Eigenschaft setzen zu können, um den Browser zum Laden und Anzeigen eines neuen Bildes am gleichen Platz aufzufordern.

Die Fähigkeit, in einem HTML-Dokument ein Bild dynamisch durch ein anderes zu ersetzen, öffnet die Tür zu einer Vielzahl von Spezialeffekten, von der Animation bis zu Digitaluhren, die sich selbst in Echtzeit laufen lassen. In der Praxis ist die verbreitetste Anwendung der Bildersetzung die Implementierung von Bild-Rollovern, in denen sich ein Bild ändert, wenn der Mauszeiger über das Bild bewegt wird. (Um verzerrende optische Effekte zu umgehen, sollte das neue Bild die gleiche Größe wie das Original haben.) Wenn Sie Bilder anklickbar machen, indem Sie sie in Ihre Hyperlinks setzen, sind Rollover-Effekte eine leistungsfähige Methode, den Benutzer einzuladen, auf das Bild zu klicken.[81] Dieses einfache HTML-Fragment zeigt ein Bild in einem `<a>`-Tag an und verwendet JavaScript-Code in den Event-Handlern `onmouseover` und `onmouseout`, um einen Rollover-Effekt zu erzeugen:

    <a href="help.html"
      onmouseover="document.helpimage.src='images/help_rollover.gif';"
      onmouseout="document.helpimage.src='images/help.gif';">
    <img name="helpimage" src="images/help.gif" border="0">
    </a>

Beachten Sie, dass das `<img>`-Tag in diesem Code-Fragment ein `name`-Attribute enthält, das es einfach macht, in den Event-Handlern des `<a>`-Tags auf das entsprechende Image-Objekt zu verweisen. Das Attribut `border` hindert den Browser daran, einen blauen Hyperlink-Rahmen um das Bild herum anzuzeigen. Die Event-Handler des `<a>`-Tags erledigen die ganze Arbeit: Sie ändern das gerade dargestellte Bild, indem sie einfach die `src`-Eigenschaft des Bildes auf die URLs der gewünschten Bilder setzen. Diese Event-Handler sind im `<a>`-Tag platziert, da sehr alte Browser diese Handler nur für bestimmte Tags, wie z.B. `<a>`, unterstützen. In praktisch allen derzeit verwendeten Browsern können Sie die Event-Handler in das `<img>`-Tag selbst setzen, was das Auffinden des Bilds vereinfacht. Der Event-Handler-Code kann dann mit dem `this`-Schlüsselwort auf dieses Image-Objekt verweisen:

    <img src="images/help.gif"
      onmouseover="this.src='images/help_rollover.gif'"
      onmouseout="this.src='images/help.gif'">

Bild-Rollover werden stark mit der Anklickbarkeit von Bildern assoziiert. Daher sollte dieses `<img>`-Tag außerdem in ein `<a>`-Tag eingebettet werden oder einen `onclick`-Event-Handler haben.

## 22.1.3 Offscreen-Bilder und Caching

Um praktikabel zu sein, müssen Bild-Rollover und ähnliche Effekte schnelle Ansprechzeiten haben. Also müssen Sie sicherstellen können, dass die benötigten Bilder im Voraus in den Cache des Browsers geladen werden. Um ein Bild in den Cache zu zwingen, erzeugen Sie zuerst mit dem `Image( )`-Konstruktor ein Image-Objekt. Dann laden Sie das Bild in das Objekt, indem Sie dessen `src`-Eigenschaft auf die gewünschte URL setzen. Das Bild wird nicht zum Dokument hinzugefügt und ist damit nicht sichtbar, aber der Browser lädt es trotzdem und speichert die Bilddaten im Cache. Wenn die gleiche URL später für ein Onscreen-Bild verwendet wird, braucht es nicht erst langsam über das Netzwerk geladen zu werden, sondern kann schnell aus dem Browser-Cache geholt werden.

Das im vorhergehenden Abschnitt gezeigte Bild-Rollover-Codefragment lädt das verwendete Rollover-Bild nicht vorab, wodurch der Benutzer eventuell eine Verzögerung im Rollover-Effekt bemerkt, wenn er die Maus zum ersten Mal über das Bild bewegt. Um dieses Problem zu umgehen, ändern Sie den Code wie folgt:

    <script>(new Image( )).src = "images/help_rollover.gif";</script>
    <img src="images/help.gif"
      onmouseover="this.src='images/help_rollover.gif'"
      onmouseout="this.src='images/help.gif'">

## 22.1.4 Unauffällige Bild-Rollover

Der gerade gezeigte Bild-Rollover-Code benötigt ein `<script>`-Tag und zwei JavaScript-Event-Handler-Attribute, um einen einzigen Rollover-Effekt zu implementieren. Das ist ein perfektes Beispiel für _auffälliges_ JavaScript. Obwohl es oft vorkommt, dass Code die Darstellung (HTML) und das Verhalten (JavaScript) auf diese Art kombiniert, ist es besser, das so weit wie möglich zu vermeiden. Das gilt vor allem dann, wenn wie in diesem Beispiel der Umfang des JavaScript-Codes sehr umfangreich ist und das HTML praktisch versteckt. Für den Anfang zeigt Beispiel 22-1 eine Funktion, die einen Rollover-Effekt zu einem angegebenen `<img>`-Element hinzufügt.

Beispiel 22-1. Einem Bild einen Rollover-Effekt hinzufügen

    /**
     * Dem angegebenen Bild einen Rollover-Effekt geben, indem wir Event-
     * Handler zum Umschalten des Bilds auf die angegebene URL hinzufügen,
     * wenn die Maus über dem Bild steht.
     *
     * Wenn das Bild als String angegeben ist, so suche nach einem Bild mit
     * diesem String als id- oder name-Attribut.
     *
     * Diese Methode setzt die Event-Handler onmouseover und onmouseout
     * des angegebenen Bildes und überschreibt/entsorgt dabei alle Handler,
     * die vorher über diese Eigenschaften gesetzt wurden.
     */
    function addRollover(img, rolloverURL) {
      if (typeof img == "string") { // Wenn img ein String ist,
        var id = img;       // es ist eine id, kein Image
        img = null;        // und wir haben noch kein Bild.

        // Versuche zuerst, das Bild über die id zu finden
        if (document.getElementById) img = document.getElementById(id);
        else if (document.all) img = document.all[id];

        // ist es über id erfolglos, suche das Bild über den Namen.
        if (!img) img = document.images[id];

        // Bild nicht aufzufinden? Nichts machen und aufgeben
        if (!img) return;
      }

      // Wenn das Element kein <img>-Tag ist, geben wir auch auf
      if (img.tagName.toLowerCase( ) != "img") return;

      // Speichere die Original-URL des Bildes
      var baseURL = img.src;

      // Lade das Rollover-Bild im Voraus in den Browser-Cache
      (new Image( )).src = rolloverURL;

      img.onmouseover = function( ) { img.src = rolloverURL; }
      img.onmouseout = function( ) { img.src = baseURL; }
    }

Die `addRollover( )`-Funktion, die in Beispiel 22-1 definiert wurde, ist nicht komplett unauffällig, da Sie immer noch ein Skript in Ihren HTML-Dateien unterbringen müssen, das die Funktion aufruft. Um wirklich unauffällige Bild-Rollover zu erreichen, müssen Sie signalisieren können, welche Bilder Rollover haben und was die URL des Rollover-Bildes ist, ohne dabei JavaScript zu verwenden. Eine einfache Art, das zu erreichen, ist das Einfügen eines unechten HTML-Attributs in das `<img>`-Tag. Sie können Bilder mit Rollover-Effekten beispielsweise so codieren:

    <img src="normalImage.gif" rollover="rolloverImage.gif">

Mit einer solchen Konvention bei der HTML-Codierung können Sie auf einfache Art alle Bilder lokalisieren, die Rollover-Effekte benötigen, und können diese Effekte mit der Funktion `initRollovers( )` implementieren, die in Beispiel 22-2 definiert wird.

Beispiel 22-2. Rollover-Effekte unauffällig hinzufügen

    /**
     * Suche alle <img>-Tags im Dokument, die ein "rollover"-
     * Attribut haben. Verwende den Wert dieses Attributs als URL eines
     * Bildes, das beim Bewegen der Maus über dem Bild angezeigt wird, und setze
     * entsprechende Event-Handler, um den Rollover-Effekt zu erzielen.
     */
    function initRollovers( ) {
      var images = document.getElementsByTagName("img");
      for(var i = 0; i < images.length; i++) {
        var image = images[i];
        var rolloverURL = image.getAttribute("rollover");
        if (rolloverURL) addRollover(image, rolloverURL);
      }
    }

Jetzt müssen Sie nur noch sicherstellen, dass diese `initRollovers( )`-Methode aufgerufen wird, sobald das Dokument geladen wurde. Der folgende Code sollte in derzeitigen Browsern funktionieren:

    if (window.addEventListener)
      window.addEventListener("load", initRollovers, false);
    else if (window.attachEvent)
      window.attachEvent("onload", initRollovers);

Kapitel 17 enthält eine vollständigere Beschreibung des `onload`-Handlings.

Sie können also eine vollständig unauffällige Lösung für Bild-Rollovers erzielen, indem Sie die Funktionen `addRollover( )` und `initRollovers( )` in die gleiche Datei wie den Code für die Event-Handler-Registrierung setzen. Fügen Sie die Code-Datei einfach mit einem `<script src=>`-Tag ein, und setzen Sie `rollover`-Attribute für alle `<img>`-Tags, die Rollover-Effekte benötigen.

Wenn Sie verhindern möchten, dass Ihre HTML-Dateien bei der Validierung durchfallen, weil Sie in Ihre `<img>`-Tags ein nicht standardkonformes `rollover`-Attribut eingefügt haben, können Sie auf XHTML umsatteln und für die neuen Attribute XML-Namensräume verwenden. Beispiel 22-3 zeigt eine Version der `initRollovers( )`-Funktion, die Namensräume erkennt. Beachten Sie allerdings, dass diese Version der Funktion nicht unter Internet Explorer 6 läuft, da dieser Browser die DOM-Methoden zur Namensraumunterstützung nicht implementiert.

Beispiel 22-3. Rollover mit XHTML und Namensräumen initialisieren

    /**
     * Suche alle <img>-Tags im Dokument mit einem "ro:src"
     *-Attribut. Verwende den Wert dieses Attributs als die URL eines
     * Bildes, das beim Bewegen der Maus über dem Bild angezeigt wird, und setze
     * entsprechende Event-Handler, um den Rollover-Effekt zu erzielen.
     * Das ro:-Namensraum-Präfix bezieht sich auf die URI
     * "http://www.davidflanagan.com/rollover"
     */
    function initRollovers( ) {
      var images = document.getElementsByTagName("img");
      for(var i = 0; i < images.length; i++) {
        var image = images[i];
        var rolloverURL = image.getAttributeNS(initRollovers.xmlns, "src");
        if (rolloverURL) addRollover(image, rolloverURL);
      }
    }
    // Diese Namensraum-URI wurde für unseren "ro:"-Namensraum erfunden
    initRollovers.xmlns = "http://www.davidflanagan.com/rollover";

## 22.1.5 Bildanimationen

Ein weiterer Grund, die `src`-Eigenschaft eines `<img>`-Tags per Skript zu steuern, ist die Durchführung einer Animation, in der sich ein Bild oft genug ändert, um als flüssige Bewegung zu erscheinen. Eine typische Anwendung dieser Technik ist z.B. die Anzeige einer Abfolge von Wetterkarten, die die vergangene oder vorhergesagte stündliche Entwicklung eines Sturms über 2 Tage hinweg zeigen.

Beispiel 22-4 zeigt eine JavaScript-ImageLoop-Klasse zur Erstellung einer solchen Bildanimation. Es verwendet die gleiche Steuerung der `src`-Eigenschaft per Skript und die gleichen Techniken zum Vorausladen von Bildern wie Beispiel 22-1. Es führt außerdem den Event-Handler `onload` des Image-Objekts ein, der bestimmen kann, wann der Ladevorgang eines Bildes (oder in diesem Fall einer Bildabfolge) abgeschlossen ist. Der Code für die Animation selbst wird von `Window.setInterval( )` gesteuert und ist recht einfach: Die Frame-Nummer wird einfach um eins erhöht und die `src`-Eigenschaft des angegebenen `<img>`-Tags auf die URL des nächsten Frames gesetzt.

Diese einfache HTML-Datei verwendet die ImageLoop-Klasse:

    <head>
    <script src="ImageLoop.js"></script>
    <script>
    var animation =
      new ImageLoop("loop", 5,["images/0.gif", "images/1.gif", "images/2.gif",
                   "images/3.gif", "images/4.gif", "images/5.gif",
                   "images/6.gif", "images/7.gif", "images/8.gif"]);
    </script>
    </head>
    <body>
    <img id="loop" src="images/loading.gif">
    <button onclick="animation.start( )">Start</button>
    <button onclick="animation.stop( )">Stop</button>
    </body>

Der Code in Beispiel 22-4 ist vielleicht etwas komplizierter als erwartet, da sowohl der Event-Handler `Image.onload` als auch die Timer-Funktion `Window.setInterval( )` Funktionen nicht als Methoden, sondern als Funktionen aufrufen. Aus diesem Grund muss der `ImageLoop( )`-Konstruktor verschachtelte Funktionen definieren, die wissen, wie sie mit dem neu konstruierten ImageLoop umgehen sollen.

Beispiel 22-4. Bildanimationen

    /**
     * ImageLoop.js: Eine ImageLoop-Klasse zur Durchführung von Bildanimationen
     *
     * Konstruktor-Argumente:
     *  imageId:  Die id des zu animierenden <img>-Tags
     *  fps:    die Anzahl der pro Sekunde darzustellenden Frames
     *  frameURLs: Ein Array aus URLs, eines für jedes Frame der Animation
     *
     * Öffentliche Methoden:
     *  start( ):  Animation starten (nachdem alle Frames geladen sind)
     *  stop( ):  Animation anhalten
     *
     * Öffentliche Eigenschaften:
     *  loaded:  true, wenn alle Frames der Animation geladen sind,
     *       andernfalls false
     */
    function ImageLoop(imageId, fps, frameURLs) {
      // Speichere die Image-ID. Suche sie aber noch nicht, da dieser Konstruktor
      // eventuell aufgerufen wird, bevor das Dokument geladen ist.
      this.imageId = imageId;
      // Berechne die Wartezeit zwischen den Frames der Animation
      this.frameInterval = 1000/fps;
      // Ein Array, das die Image-Objekte jedes Frames speichert
      this.frames = new Array(frameURLs.length);

      this.image = null;       // Das <img>-Element mit id
      this.loaded = false;      // Sind alle Frames geladen?
      this.loadedFrames = 0;     // Anzahl der geladenen Frames
      this.startOnLoad = false;   // Starte Animation nach dem Laden?
      this.frameNumber = −1;     // Momentan angezeigtes Frame
      this.timer = null;       // Der Rückgabewert von setInterval( )

      // Initialisiere das frames[]-Array, und lade die Bilder im Voraus
      for(var i = 0; i < frameURLs.length; i++) {
        this.frames[i] = new Image( );   // Erzeuge Image-Objekt
        // Registriere Event-Handler, um Laden des Frames anzuzeigen
        this.frames[i].onload = countLoadedFrames; // wird später definiert
        this.frames[i].src = frameURLs[i]; // Lade das Frame-Bild im Voraus
      }

      // Diese eingebettete Event-Handler-Funktion zählt, wie viele
      // Frames geladen wurden. Wenn alle geladen sind, setzt sie ein Flag
      // und beginnt mit der Animation, wenn das so gewünscht wurde.
      var loop = this;
      function countLoadedFrames( ) {
        loop.loadedFrames++;
        if (loop.loadedFrames == loop.frames.length) {
          loop.loaded = true;
          if (loop.startOnLoad) loop.start( );
        }
      }

      // Hier definieren wir eine Funktion, die das nächste Frame der
      // Animation anzeigt. Sie darf keine normale Instanzmethode sein, da
      // setInterval( ) keine Methoden, sondern nur Funktionen aufrufen kann.
      // Also erstellen wir einen Wrapper, der eine Referenz
      // auf das ImageLoop-Objekt enthält
      this._displayNextFrame = function( ) {
        // Erhöhe zuerst die Frame-Nummer. Per Modulo-Operator (%)
        // springen wir vom letzten zum ersten Frame
        loop.frameNumber = (loop.frameNumber+1)%loop.frames.length;
        // Setze die src-Eigenschaft des Bildes auf die URL des neuen Frames
        loop.image.src = loop.frames[loop.frameNumber].src;
      };
    }

    /**
     * Diese Methode startet eine ImageLoop-Animation. Sind die Frame-Bilder nicht vollständig
     * geladen, setzt sie stattdessen ein Flag, und die Animation wird
     * automatisch gestartet, sobald der Ladevorgang abgeschlossen ist
     */
    ImageLoop.prototype.start = function( ) {
      if (this.timer != null) return;  // Bereits gestartet
      // Ist der Ladevorgang noch nicht abgeschlossen, setzte ein Flag und warte
      if (!this.loaded) this.startOnLoad = true;
      else {
        // Bild über id holen, falls wir es nicht schon haben
        if (!this.image) this.image = document.getElementById(this.imageId);
        // Zeige das erste Frame sofort an
        this._displayNextFrame( );
        // und setze einen Timer für die Anzeige der folgenden Frames
        this.timer = setInterval(this._displayNextFrame, this.frameInterval);
      }
    };

    /** Halte eine ImageLoop-Animation an */
    ImageLoop.prototype.stop = function( ) {
      if (this.timer) clearInterval(this.timer);
      this.timer = null;
    };

## 22.1.6 Weitere Image-Eigenschaften

Zusätzlich zum Event-Handler `onload`, der in Beispiel 22-4 vorgestellt wurde, unterstützt das Image-Objekt zwei weitere Event-Handler. Der Event-Handler `onerror` wird aufgerufen, wenn während des Ladevorgangs der Bilder ein Fehler auftritt, z.B. wenn sich die angegebene URL auf fehlerhafte Bilddaten bezieht. Der Event-Handler `onabort` wird aufgerufen, wenn der Benutzer den Ladevorgang vorzeitig abbricht (z.B., indem er im Browser auf die Schaltfläche **Stop** klickt). Für ein Bild wird einer (und nur einer) dieser Event-Handler aufgerufen.

Zusätzlich hat jedes Image-Objekt eine `complete`-Eigenschaft. Während das Bild lädt, steht diese Eigenschaft auf `false`; sie ändert sich zu `true`, nachdem das Bild geladen wurde oder der Browser das Laden aufgegeben hat. Anders ausgedrückt, die Eigenschaft `complete` wird zu `true`, nachdem einer der drei möglichen Event-Handler aufgerufen wurde.

Die anderen Eigenschaften des Image-Objekts spiegeln einfach die Attribute des HTML `<img>`-Tags wider. Bei modernen Browsern sind diese Eigenschaften les- und schreibbar, und Sie können JavaScript verwenden, um die Bildgröße dynamisch zu ändern, sodass der Browser das Bild auseinanderzieht oder zusammendrückt.

* * *

[81] Eine Besprechung von Bild-Rollovern wäre unvollständig ohne den Hinweis, dass sie auch mit der CSS-Pseudoklasse `:hover` implementiert werden können, um verschiedene CSS-Hintergrundbilder auf Elemente anzuwenden, wenn die Maus über ihnen »schwebt«. Leider ist es schwierig, CSS-Bild-Rollover portabel zu machen. In der Praxis ist `:hover` nützlicher, wenn es statt auf Bilder auf Hyperlinks mit Textinhalt angewendet wird.

# 22.2 Grafiken mit CSS

Cascading Style Sheets (CSS) werden in Kapitel 16 beschrieben, und Sie haben dort gelernt, wie Sie Code für CSS-Styles schreiben, um damit DHTML-Effekte zu erzielen. CSS kann auch einfache Grafiken erzeugen: Die Eigenschaft `background-color` »füllt« ein Rechteck mit einer Deckfarbe, und die Eigenschaft `border` zeichnet den Umriss eines Rechtecks. Zusätzlich können speziellere Rahmeneigenschaften, wie z.B. `border-left` und `border-top`, verwendet werden, um nur an einer Reckteckseite einen Rahmen zu zeichnen, wodurch horizontale und vertikale Linien entstehen. In Browsern, die Styles unterstützen, können diese Linien sogar gepunktet oder gestrichelt werden

Das ist zwar nichts Großartiges, aber wenn diese grundlegenden CSS-Rechteck- und Linien-Bausteine mit absoluter Positionierung verknüpft werden, können sie Diagramme ergeben wie die in Abbildung 22-1 und Abbildung 22-2 gezeigten. Die anschließenden Abschnitte zeigen, wie diese Figuren erzeugt wurden.

Abbildung 22-1. Ein mit CSS gestaltetes Balkendiagramm

Abbildung 22-2. Ein mit CSS gestalteter Baum

## 22.2.1 Balkendiagramme mit CSS

Das Balkendiagramm in Abbildung 22-1 wurde mit der folgenden HTML-Datei erstellt:

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
               "http://www.w3.org/TR/html4/loose.dtd">
    <!-- Ohne einen solchen doctype sieht die Zeichnung unter IE nicht richtig aus -->
    <html>
    <head>
    <script src="BarChart.js"></script> <!-- Chart-Library einbeziehen -->
    <script>
    function drawChart( ) {
      var chart = makeBarChart([1,2,4,8,16,32,64,128,256], 600, 300);
      var container = document.getElementById("chartContainer");
      container.appendChild(chart);
    }
    </script>
    </head>
    <body onload="drawChart( )">
    <h2>y = 2<sup>n</sup></h2><!-- Diagrammtitel -->
    <div id="chartContainer"><!-- Diagramm kommt hierhin --></div>
    <!-- Diagrammbeschriftung -->
    <i>Beachte, dass jeder Balken doppelt so hoch wie der vorhergehende ist -- eine
    Eigenschaft exponentiellen Wachstums</i>
    </body>
    </html>

Natürlich ist der interessante Code die Funktion `makeBarChart( )`, die in der Datei _BarChart.js_ definiert und in Beispiel 22-5 aufgeführt ist.

Beispiel 22-5. Balkendiagramme mit CSS zeichnen

    /**
     * BarChart.js:
     * Diese Datei definiert die Funktion makeBarChart( ), die ein Balkendiagramm erzeugt, mit dem
     * die Zahlen im data[]-Array dargestellt werden. Die Gesamtgröße des Diagramms wird
     * durch die optionalen Argumente width und height angegeben, die den
     * Platz für den Diagrammrahmen und den Abstand zwischen Rahmen und Inhalt einbeziehen.
     * Das optionale Argument barcolor gibt die Balkenfarbe an. Die Funktion gibt das
     * von ihr erzeugte <div>-Element zurück, sodass der Benutzer das Diagramm weiter
     * ändern kann, z.B., indem er eine Randbreite festlegt. Der Aufrufer muss das zurückgegebene
     * Element wieder in das Dokument einfügen, um das Diagramm sichtbar zu machen.
     /**
    function makeBarChart(data, width, height, barcolor) {
      // Stelle die Standardwerte für die optionalen Argumente zu Verfügung
      if (!width) width = 500;
      if (!height) height = 350;
      if (!barcolor) barcolor = "blue";

      // Die Argumente width und height geben die Gesamtgröße des
      // erzeugten Diagramms an. Davon müssen wir den Rahmen und den Abstand zwischen Rahmen
      // und Inhalt abziehen,
      // um die Größe des zu erzeugenden Elements zu erhalten.
      width -= 24; // Ziehe 10px für das Padding ab und jeweils 2px für den linken und rechten          // Rahmen
      height -= 14; // Ziehe 10px für Padding oben ab und jeweils 2px für den oberen und          // unteren Rahmen

      // Erstelle jetzt das Element, in das das Diagramm aufgenommen wird. Beachten Sie,
      // dass wir das Diagramm relativ positionieren, damit die Kinder absolut positioniert
      // werden können und trotzdem im normalen Elementablauf erscheinen.
      var chart = document.createElement("div");
      chart.style.position = "relative";     // Setze relative Positionierung
      chart.style.width = width + "px";      // Setze Diagrammbreite
      chart.style.height = height + "px";     // Setze Diagrammhöhe
      chart.style.border = "solid black 2px";   // Mache einen Rahmen
      chart.style.paddingLeft = "10px";      // Füge links Leerraum hinzu
      chart.style.paddingRight = "10px";     // und rechts ebenso
      chart.style.paddingTop = "10px";      // und oben
      chart.style.paddingBottom = "0px";     // aber nicht unten
      chart.style.backgroundColor = "white";   // Mache den Diagrammhintergrund weiß

      // Berechne die Breite jedes Balkens
      var barwidth = Math.floor(width/data.length);
      // Suche nach der größten Zahl in data[]. Beachte die smarte Verwendung von
      // Function.apply( ).
      var maxdata = Math.max.apply(this, data);
      // Der Skalierfaktor für das Diagramm: scale*data[i] ergibt die Höhe eines Balkens
      var scale = height/maxdata;

      // Durchlaufe das data-Array, und erzeuge für jedes Datum einen Balken
      for(var i = 0; i < data.length; i++) {
        var bar = document.createElement("div"); // Erzeuge div für Balken
        var barheight = data[i] * scale;     // Berechne Balkenhöhe
        bar.style.position = "absolute";     // Setze Balkenposition und -größe
        bar.style.left = (barwidth*i+1+10)+"px"; // Füge Balkenrahmen und Diagrammpad hinzu
        bar.style.top = height-barheight+10+"px";// Füge Diagrammleerraum hinzu
        bar.style.width = (barwidth-2) + "px";  // −2 für den Balkenrahmen
        bar.style.height = (barheight-1) + "px"; // −1 für den oberen Balkenrahmen
        bar.style.border = "solid black 1px";  // Art des Balkenrahmens
        bar.style.backgroundColor = barcolor;  // Balkenfarbe
        bar.style.fontSize = "0px";       // IE-Hilfskonstruktion
        chart.appendChild(bar);         // Füge den Balken ins Diagramm ein
      }

      // Gib schließlich das Diagrammelement zurück, damit der Aufrufer es ändern kann
      return chart;
    }

Der Code für Beispiel 22-5 ist unkompliziert und recht verständlich. Er verwendet die in Kapitel 15 vorgestellten Techniken, um neue `<div>`-Elemente zu erzeugen und sie zum Dokument hinzuzufügen. Zusätzlich verwendet er die in Kapitel 16 gezeigten Techniken, um CSS-Style-Eigenschaften an den von ihm erzeugten Elementen zu setzen. Hier wird kein Text oder anderer Inhalt verwendet; das Balkendiagramm ist einfach ein Bündel maßgeschneiderter Rechtecke, die in einem weiteren Rechteck positioniert sind. Die CSS-Attribute `border` und `background-color` machen die Rechtecke sichtbar. Ein wichtiger Codeteil setzt den Stil `position:relative` für das Balkengramm selbst, ohne dabei die Stile `top` und `left` zu setzen. Durch diese Festlegung kann das Diagramm im normalen Dokumentenfluss bleiben und gleichzeitig Kinder haben, die mit Bezug auf die linke obere Diagrammecke absolut positioniert sind. Wenn das Diagramm nicht auf relative (oder absolute) Positionierung gesetzt wird, stehen alle Balken am falschen Platz.

Beispiel 22-5 enthält ein bisschen elementare Mathematik zur Berechnung der Höhe jedes Balkens in Pixeln anhand der zu plottenden Datenwerte. Der Code, der die Position und Größe des Diagramms und der Balken festlegt, enthält außerdem eine Arithmetik, die die Rahmen und Abstände zwischen Rahmen und Inhalt berücksichtigt.

## 22.2.2 Eine CSSDrawing-Klasse

Der Code in Beispiel 22-5 ist genau für eine Aufgabe gemacht: Er zeichnet Linien, und nichts anderes als Linien. Genauso ist es durchaus möglich, CSS zum Zeichnen allgemeinerer Diagramme zu verwenden (z.B. für den in Abbildung 22-2 dargestellten Baum), solange sie aus Boxen, horizontalen Linien und vertikalen Linien bestehen.

Beispiel 22-6 ist eine CSSDrawing-Klasse, die eine einfache API zum Zeichnen von Boxen und Linien definiert, und Beispiel 22-7 enthält Code, der mit der CSSDrawing-Klasse die in Abbildung 22-2 gezeigten Figuren erzeugt.

Beispiel 22-6. Die CSSDrawing-Klasse

    /**
     * Diese Konstruktorfunktion erzeugt ein div-Element, in das eine
     * CSS-basierte Figur gezeichnet werden kann. Instanzmethoden werden definiert, um
     * Linien und Boxen zu zeichnen und die Figur in das Dokument einzufügen.
     *
     * Der Konstruktor kann mit zwei verschiedenen Signaturen aufgerufen werden:
     *
     *  new CSSDrawing(x, y, width, height, classname, id)
     *
     * In diesem Fall wird ein <div> mit position:absolute und der
     * angegebenen Position und Größe erzeugt.
     *
     * Der Konstruktor kann auch nur mit einer Breite und Höhe aufgerufen werden:
     *
     *  new CSSDrawing(width, height, classname, id)
     *
     * In diesem Fall hat das erzeugte <div> die angegebene Breite und Höhe
     * und verwendet position:relative (was notwendig ist, damit die Kindelemente,
     * mit denen Linien und Boxen gezeichnet werden, absolut positioniert werden können).
     *
     * In beiden Fällen sind die Argumente classname und id optional. Wenn sie angegeben sind,
     * werden sie als Wert der class- und id-Attribute des erzeugten <div>
     * verwendet und lassen sich benutzen, um CSS-Styles wie z.B. Rahmen mit
     * der Figur zu assoziieren.
     */
    function CSSDrawing(/* variable Argumente, siehe oben */) {
      // Erzeuge und speichere das <div>-Element für die Zeichnung
      var d = this.div = document.createElement("div");
      var next;

      // Finde heraus, ob wir vier oder zwei Zahlen haben, und lege die Größe und
      // Position des div entsprechend fest
      if (arguments.length >= 4 && typeof arguments[3] == "number") {
        d.style.position = "absolute";
        d.style.left = arguments[0] + "px";
        d.style.top = arguments[1] + "px";
        d.style.width = arguments[2] + "px";
        d.style.height = arguments[3] + "px";
        next = 4;
      }
      else {
        d.style.position = "relative"; // Das ist wichtig
        d.style.width = arguments[0] + "px";
        d.style.height = arguments[1] + "px";
        next = 2;
      }

      // Setze class- und id-Attribute, sofern sie angegeben sind.
      if (arguments[next]) d.className = arguments[next];
      if (arguments[next+1]) d.id = arguments[next+1];
    }

    /**
     * Füge der Zeichnung eine Box hinzu.
     *
     * x, y, w, h:  lege die Position und Größe der Box fest.
     * content:    ein Text- oder HTML-String, der in der Box angezeigt wird
     * classname, id: optionale class- und id-Werte für die Box. Nützlich, um
     *        Styles mit der Box zu assoziieren (Farbe, Umrandung usw.)
     * Rückgabewert: Das für die Anzeige der Box erzeugte <div>-Element
     */
    CSSDrawing.prototype.box = function(x, y, w, h, content, classname, id) {
      var d = document.createElement("div");
      if (classname) d.className = classname;
      if (id) d.id = id;
      d.style.position = "absolute";
      d.style.left = x + "px";
      d.style.top = y + "px";
      d.style.width = w + "px";
      d.style.height = h + "px";
      d.innerHTML = content;
      this.div.appendChild(d);
      return d;
    };

    /**
     * Füge der Zeichnung eine horizontale Linie hinzu.
     *
     * x, y, width:  Gib die Startposition und Linienbreite an
     * classname, id: Optionale class- und id-Werte für die Box. Mindestens ein solcher Wert
     *        muss vorhanden sein und einen Umrandungsstil angeben, der
     *        für den Linienstil, die Linienfarbe und -breite verwendet wird.
     * Rückgabewert: Das für die Anzeige der Linie erzeugte <div>-Element
     */
    CSSDrawing.prototype.horizontal = function(x, y, width, classname, id) {
      var d = document.createElement("div");
      if (classname) d.className = classname;
      if (id) d.id = id;
      d.style.position = "absolute";
      d.style.left = x + "px";
      d.style.top = y + "px";
      d.style.width = width + "px";
      d.style.height = 1 + "px";
      d.style.borderLeftWidth = d.style.borderRightWidth =
        d.style.borderBottomWidth = "0px";
      this.div.appendChild(d);
      return d;
    };

    /**
     * Füge der Zeichnung eine vertikale Linie hinzu.
     * Für Einzelheiten siehe horizontal( ).
     */
    CSSDrawing.prototype.vertical = function(x, y, height, classname, id) {
      var d = document.createElement("div");
      if (classname) d.className = classname;
      if (id) d.id = id;
      d.style.position = "absolute";
      d.style.left = x + "px";
      d.style.top = y + "px";
      d.style.width = 1 + "px";
      d.style.height = height + "px";
      d.style.borderRightWidth = d.style.borderBottomWidth =
        d.style.borderTopWidth = "0px";
      this.div.appendChild(d);
      return d;
    };

    /** Füge die Zeichnung als Kind des angegebenen Containers in das Dokument ein */
    CSSDrawing.prototype.insert = function(container) {
      if (typeof container == "string")
        container = document.getElementById(container);
      container.appendChild(this.div);
    }

    /** Füge die Zeichnung durch Ersetzen des angegebenen Elements in das Dokument ein */
    CSSDrawing.prototype.replace = function(elt) {
      if (typeof elt == "string") elt = document.getElementById(elt);
      elt.parentNode.replaceChild(this.div, elt);
    }

Der Konstruktor `CSSDrawing( )` erzeugt ein neues CSSDrawing-Objekt, das lediglich einen Wrapper um ein `<div>`-Element darstellt. Die Instanzmethoden `box( )`, `vertical( )` und `horizontal( )` verwenden CSS, um Boxen bzw. vertikale und horizontale Linien zu zeichnen. Bei jeder Methode lässt sich die Position und Größe des zu zeichnenden Rahmens oder der Linie festlegen sowie eine Klasse und/oder eine ID für das erzeugte Rahmen- oder Linienelement. Über die Klasse oder ID können CSS-Styles mit dem Element assoziiert und so Farben, Linienbreiten usw. angegeben werden. Die Erzeugung eines CSSDrawing-Objekts macht es noch nicht sichtbar. Verwenden Sie die Methoden `insert( )` oder `replace( )`, um es dem Dokument hinzuzufügen.

Beispiel 22-7 zeigt, wie die CSSDrawing-Klasse verwendet werden kann. Sowohl der JavaScript-Code in der Methode `drawFigure( )` und das CSS-Stylesheet sind für die Figur entscheidend. Der Code definiert die Positionen und Größen der Rahmen und Linien, und das Stylesheet definiert die Farben und Linienbreiten. Beachten Sie, dass sich JavaScript und CSS verheddern und der Code in der Methode `drawFigure( )` die Rahmenbreiten und Padding-Abstände miteinbeziehen muss, die im Stylesheet angegeben werden. Das ist ein Manko der von der CSSDrawing-Klasse definierten API.

Beispiel 22-7. Eine Figur mit der CSSDrawing-Klasse zeichnen

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
               "http://www.w3.org/TR/html4/loose.dtd">
    <!-- Ohne einen solchen doctype sieht die Zeichnung unter IE nicht richtig aus -->
    <html>
    <head>
    <script src="CSSDrawing.js"></script> <!-- Beziehe unsere Zeichenklasse ein -->
    <style>
    /* Styles für die Box mit der Abbildung */
    .figure { border: solid black 2px; background-color: #eee;}
    /* Styles für Rasterlinien */
    .grid { border: dotted black 1px; opacity: .1; }
    /* Styles für Boxen in der Abbildung */
    .boxstyle {
      border: solid black 2px;
      background: #aaa;
      padding: 2px 10px 2px 10px;
      font: bold 12pt sans-serif;
      text-align: center;
    }
    /* Styles für die Linie, die die Boxen verbindet */
    .boldline { border: solid black 2px; }
    </style>

    <script>
    // Zeichne ein Raster im angegebenen Rechteck mit dx,dy als Linienabstand
    function drawGrid(drawing, x, y, w, h, dx, dy) {
      for(var x0 = x; x0 < x +w; x0 += dx)
        drawing.vertical(x0, y, h, "grid");
      for(var y0 = y; y0 < y + h; y0 += dy)
        drawing.horizontal(x, y0, w, "grid");
    }

    function drawFigure( ) {
      // Erzeuge eine neue Abbildung
      var figure = new CSSDrawing(500, 200, "figure");

      // Füge der Zeichnung ein Raster hinzu
      drawGrid(figure, 0, 0, 500, 200, 25, 25);

      // Zeichne vier Boxen in der Figur
      figure.box(200, 50, 75, 25, "Life", "boxstyle");    // obere Box
      figure.box(50, 125, 75, 25, "Archaea", "boxstyle");   // Reihe von 3
      figure.box(200, 125, 75, 25, "Bacteria", "boxstyle");  // ...Boxen unter
      figure.box(350, 125, 75, 25, "Eukaryota", "boxstyle"); // ...der oberen

      // Diese Linie wird vom unteren Mittelpunkt der oberen "Life"-Box gezeichnet.
      // Die y-Startposition dieser Linie ist 50+25+2+2+2+2 oder
      // y + Höhe + oberer Rahmen + oberes Padding + unteres Padding + unterer Rahmen
      // NB: Diese Berechnung benötigt Informationen sowohl aus dem Code als auch
      // aus dem Stylesheet, was nicht ideal ist.
      figure.vertical(250, 83, 20, "boldline");

      figure.horizontal(100, 103, 300, "boldline"); // Linie über den 3 unteren Boxen
      figure.vertical(100, 103, 22, "boldline");   // verbinde mit "archaea"
      figure.vertical(250, 103, 22, "boldline");   // verbinde mit "bacteria"
      figure.vertical(400, 103, 22, "boldline");   // verbinde mit "eukaryota"

      // Füge jetzt die Figur in das Dokument ein, und ersetze den Platzhalter
      figure.replace("placeholder");
    }
    </script>
    </head>
    <body onload="drawFigure( )">
    <div id="placeholder"></div>
    </body>
    </html>

# 22.3 SVG: Scalable Vector Graphics

SVG ist eine XML-Grammatik für Grafiken. Der Begriff »Vektor« im Namen zeigt bereits, dass diese Grafiken grundsätzlich anders aufgebaut sind als Rasterbildformate (wie z.B. GIF, JPEG und PNG), die eine Matrix mit Pixelwerten angeben. Vielmehr ist eine SVG-»Abbildung« eine genaue, auflösungsunabhängige (und dadurch »skalierbare«) Beschreibung der notwendigen Schritte zum Zeichnen der gewünschten Grafik. Hier sehen Sie, wie ein einfaches SVG-Bild im Textformat aussieht:

    <!-- Starte eine SVG-Abbildung, und deklariere unseren Namensraum -->
    <svg xmlns="http://www.w3.org/2000/svg"
       viewBox="0 0 1000 1000"> <!-- Koordinatensystem für die Abbildung -->
     <defs>            <!-- Lege ein paar Definitionen an, -->
                    <!-- die wir benutzen werden -->
      <linearGradient id="fade"> <!-- ein Farbgradient namens "fade" -->
       <stop offset="0%" stop-color="#008"/>  <!-- Beginne mit Dunkelblau -->
       <stop offset="100%" stop-color="#ccf"/> <!-- und blende zu Hellblau auf -->
      </linearGradient>
     </defs>
     <!-- Zeichne ein Rechteck mit einer breiten schwarzen Umrandung, -->
     <!-- und fülle es mit dem Farbgradienten -->
     <rect x="100" y="200" width="800" height="600"
       stroke="black" stroke-width="25" fill="url(#fade)"/>
    </svg>

Abbildung 22-3 zeigt, wie die SVG-Datei aussieht, wenn sie grafisch wiedergegeben wird.

Abbildung 22-3. Eine einfache SVG-Grafik

SVG hat eine umfrangreiche und ziemlich komplizierte Grammatik. Zusätzlich zu Bausteinen für das Zeichnen einfacher Formen unterstützt es beliebige Kurven, Text und Animation. SVG-Grafiken können sogar JavaScript-Skripten und CSS-Stylesheets beinhalten und so Informationen zum Verhalten und zur Darstellung aufnehmen. Dieser Abschnitt zeigt, wie clientseitiger JavaScript-Code (der nicht in SVG, sondern in HTML eingebettet ist) mit Hilfe von SVG dynamisch Grafiken zeichnen kann. Er enthält Beispiele mit SVG-Zeichnungen, kann aber die Möglichkeiten mit SVG nur in sehr begrenztem Umfang darstellen. Vollständige Erläuterungen zu SVG sind in der umfangreichen, aber recht gut lesbaren Spezifikation aufgeführt. Diese wird vom W3C unter <http://www.w3.org/TR/SVG/> gepflegt. Beachten Sie, dass diese Spezifikation ein vollständiges Document Object Model für SVG-Dokumente enthält. In diesem Abschnitt werden SVG-Grafiken mit dem standardisierten XML DOM bearbeitet, und das SVG DOM wird überhaupt nicht verwendet.

Als dieses Buch geschrieben wurde, war Firefox 1.5 der einzige der Mainstream-Browser, der von Haus aus SVG-Grafiken unterstützte. Unter diesem Browser können Sie eine SVG-Grafik ganz einfach anzeigen, indem Sie die URL des anzuzeigenden Bildes eingeben. Sinnvoller ist es, SVG-Grafiken direkt in eine XHTML-Datei einzubetten, wie in dieser einfachen Datei:

    <?xml version="1.0"?>
    <!-- deklariere HTML als Standard-Namensraum und SVG mit dem Präfix "svg:" -->
    <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:svg="http://www.w3.org/2000/svg">
    <body>
    Das ist ein rotes Quadrat: <!-- Das ist HTML-Text -->
    <svg:svg width="10" height="10"> <!-- Das ist ein SVG-Bild -->
     <svg:rect x="0" y="0" width="10" height="10" fill="red"/></svg:svg>
    Das ist ein blauer Kreis:
    <svg:svg width="10" height="10">
     <svg:circle cx="5" cy="5" r="5" fill="blue"/></svg:svg>
    </body>
    </html>

Abbildung 22-4 zeigt, wie Firefox 1.5 dieses XHTML-Dokument wiedergibt.

Abbildung 22-4. SVG-Grafiken in einem XHTML-Dokument

SVG-Abbildungen können auch mit einem `<object>`-Tag in ein HTML-Dokument eingebettet werden, sodass sie mit einem Plug-in angezeigt werden können. Adobe hat ein kostenloses (aber nicht Open Source) SVG-Viewer-Plug-in, das unter den üblichen Browsern und Betriebssystemen funktioniert. Sie finden es, indem Sie den Links unter <http://www.adobe.com/svg> folgen.

Da SVG eine XML-Grammatik hat, lässt sich beim Zeichnen von SVG-Grafiken einfach das DOM verwenden, um die passenden XML-Elemente zu erzeugen. Beispiel 22-8 ist eine Auflistung einer `pieChart( )`-Funktion, die die SVG-Elemente für ein Tortendiagramm erzeugt, wie es in Abbildung 22-5 dargestellt ist. (Die anderen in diesem Kapitel untersuchten Vektorgrafik-Technologien werden ebenfalls zum Zeichnen von Tortendiagrammen verwendet, die diesem sehr ähnlich sind.)

Abbildung 22-5. Ein mit JavaScript

Beispiel 22-8. Ein Tortendiagramm mit JavaScript und SVG zeichnen

    /**
     * Zeichne ein Tortendiagramm in ein <svg>-Element.
     * Argumente:
     *  Canvas: das SVG-Element (oder die id dieses Elements), in das gezeichnet wird
     *  Daten: ein Array mit darzustellenden Zahlen, eine für jedes Tortenstück
     *  cx, cy, r: der Mittelpunkt und Radius der Torte
     *  Farben: ein Array mit HTML-Farbstrings, einer für jedes Tortenstück
     *  Beschriftungen: ein Array mit Beschriftungen, die in der Legende angezeigt werden, eine
     *  für jedes Tortenstück lx, ly: die linke obere Ecke der Diagrammlegende
     */
    function pieChart(canvas, data, cx, cy, r, colors, labels, lx, ly) {
      // Suche nach dem Canvas, wenn er nicht durch ein Element, sondern über eine id angegeben  // wird
      if (typeof canvas == "string") canvas = document.getElementById(canvas);

      // Zähle die Datenwerte zusammen, damit wir wissen, wie groß die Torte ist
      var total = 0;
      for(var i = 0; i < data.length; i++) total += data[i];

      // Finde jetzt heraus, wie groß jedes Tortenstück ist. Winkel im Bogenmaß.
      var angles = []
      for(var i = 0; i < data.length; i++) angles[i] = data[i]/total*Math.PI*2;

      // Durchlaufe alle Tortenstücke.
      startangle = 0;
      for(var i = 0; i < data.length; i++) {
        // Hier endet das Tortenstück
        var endangle = startangle + angles[i];

        // Berechne die zwei Punkte, in denen das Tortenstück den Kreis schneidet.
        // Diese Formeln sind so gewählt, dass ein Winkel von 0 auf der 12-Uhr-Position steht
        // und positive Winkel im Uhrzeigersinn abgebildet werden.
        var x1 = cx + r * Math.sin(startangle);
        var y1 = cy – r * Math.cos(startangle);
        var x2 = cx + r * Math.sin(endangle);
        var y2 = cy – r * Math.cos(endangle);

        // Das ist ein Flag für Winkel, die größer als ein Halbkreis sind
        var big = 0;
        if (endangle – startangle > Math.PI) big = 1;

        // Wir beschreiben ein Tortenstück mit einem <svg:path>-Element
        // Sie sehen, dass wir das mit createElementNS( ) erstellen
        var path = document.createElementNS(SVG.ns, "path");

        // Dieser String enthält die Pfaddaten
        var d = "M " + cx + "," + cy + // Beginne am Kreismittelpunkt
          " L " + x1 + "," + y1 +   // Zeichne eine Linie nach (x1,y1)
          " A " + r + "," + r +    // Zeichne einen Kreisbogen mit dem Radius r
          " 0 " + big + " 1 " +    // Kreisbogendaten ...
          x2 + "," + y2 +       // ein Kreisbogen bis nach (x2,y2)
          " Z";            // Schließe den Pfad durch eine Verbindung zu (cx,cy)
        // Das ist ein XML-Element, sodass alle Attribute mit
        // setAttribute( ) gesetzt werden müssen. Wir können nicht einfach JavaScript-
        // Eigenschaften verwenden
        path.setAttribute("d", d);       // Setze diesen Pfad
        path.setAttribute("fill", colors[i]);  // Setze Farbe des Tortenstücks
        path.setAttribute("stroke", "black");  // Zeichne einen schwarzen Umriss für das
                            // Tortenstück,
        path.setAttribute("stroke-width", "2"); // der 2 Einheiten breit ist
        canvas.appendChild(path);        // Setze das Tortenstück auf den Canvas

        // Das nächste Tortenstück beginnt dort, wo dieses aufhört
        startangle = endangle;

        // Zeichne jetzt ein kleines, passendes Quadrat für die Legende
        var icon = document.createElementNS(SVG.ns, "rect");
        icon.setAttribute("x", lx);       // Positioniere das Quadrat
        icon.setAttribute("y", ly + 30*i);
        icon.setAttribute("width", 20);     // Lege die Quadratgröße fest
        icon.setAttribute("height", 20);
        icon.setAttribute("fill", colors[i]);  // Gleiche Füllfarbe wie das Tortenstück
        icon.setAttribute("stroke", "black");  // und die gleiche Umrissfarbe.
        icon.setAttribute("stroke-width", "2");
        canvas.appendChild(icon);        // Setze es auf den Canvas

        // Füge eine Beschriftung rechts vom Rechteck ein
        var label = document.createElementNS(SVG.ns, "text");
        label.setAttribute("x", lx + 30);    // Positioniere den Text
        label.setAttribute("y", ly + 30*i + 18);
        // Text-Style-Attribute können auch mit CSS gesetzt werden
        label.setAttribute("font-family", "sans-serif");
        label.setAttribute("font-size", "16");
        // Füge einen DOM-Textknoten zum <svg:text>-Element hinzu
        label.appendChild(document.createTextNode(labels[i]));
        canvas.appendChild(label);       // Setze den Text auf den Canvas
      }

Der Code in Beispiel 22-8 ist ziemlich unkompliziert. Etwas Mathematik ist nötig, um die Daten in grafisch darstellbare Tortenstückwinkel zu konvertieren. Der Großteil des Beispiels ist allerdings DOM-Code, der SVG-Elemente erzeugt und Attribute für diese Elemente setzt. Da SVG einen Namensraum verwendet, wird statt `createElement( ) createElementNS( )` verwendet. Die Namensraumkonstante `SVG.ns` wird später in Beispiel 22-9 definiert.

Der undurchsichtigste Teil dieses Beispiels ist der Code, der die eigentlichen Tortenstücke zeichnet. Das zur Darstellung der einzelnen Tortenstücke verwendete Tag ist `<svg:path>`. Dieses SVG-Element beschreibt beliebige Formen, die aus Linien und Kurven bestehen. Die Formbeschreibung wird durch das Attribut `d` des `<svg:path>`-Tags angegeben. Der Wert dieses Attributs verwendet eine kompakte, aus Buchstabencode und Zahlen bestehende Grammatik, die die Koordinaten, Winkel und andere Werte festlegt. Beispielsweise bedeutet der Buchstabe M »move to (gehe zu)«; auf ihn folgen die X- und Y-Koordinaten. Der Buchstabe L bedeutet »line to« und zeichnet eine Linie vom momentanen Punkt zu den Koordinaten, die auf den Buchstaben folgen. Dieses Beispiel verwendet außerdem den Buchstaben A, um einen Kreisbogen zu zeichnen. Auf den Buchstaben folgen sieben Zahlen, die den Kreisbogen beschreiben. Die genauen Einzelheiten sind hier nicht von Bedeutung, aber Sie können sie in der Spezifikation unter <http://www.w3.org/TR/SVG/> nachlesen.

Der Code in Beispiel 22-8 verwendet die Konstante `SVG.ns`, um den SVG-Namensraum zu beschreiben. Diese Konstante und mehrere SVG-Utility-Funktionen werden in einer separaten _SVG.js_ -Datei definiert, die in Beispiel 22-9 aufgelistet ist.

Beispiel 22-9. SVG-Utility-Code

    // Erzeuge einen Namensraum für unsere SVG-bezogenen Utilities
    var SVG = {};

    // Das sind Namensraum-URLs, die mit SVG zu tun haben
    SVG.ns = "http://www.w3.org/2000/svg";
    SVG.xlinkns = "http://www.w3.org/1999/xlink";

    // Erzeuge ein leeres <svg>-Element, und gib es aus.
    // Beachte, dass das Element nicht in das Dokument eingefügt wird.
    // Beachte, dass wir sowohl die Größe des Bilds in Pixeln angeben können als auch
    // sein internes Koordinatensystem.
    SVG.makeCanvas = function(id, pixelWidth, pixelHeight, userWidth, userHeight) {
      var svg = document.createElementNS(SVG.ns, "svg:svg");
      svg.setAttribute("id", id);
      // Wie groß ist der Canvas in Pixeln?
      svg.setAttribute("width", pixelWidth);
      svg.setAttribute("height", pixelHeight);
      // Setze die Koordinaten, die für die Zeichnungen auf dem Canvas verwendet werden
      svg.setAttribute("viewBox", "0 0 " + userWidth + " " + userHeight);
      // Definiere den XLink-Namensraum, den SVG verwendet
      svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink",
                SVG.xlinkns);
      return svg;
    };

    // Serialisiere das Canvas-Element in einen String, und verwende ihn
    // in einer data:-URL zur Anzeige in einem <object>-Tag. Dadurch funktioniert SVG
    // unter Browsern, die das data:-URL-Schema unterstützen und ein SVG-
    // Plug-in installiert haben.
    SVG.makeDataURL = function(canvas) {
      // Wir geben uns nicht mit der IE-Serialisierungstechnik ab, da sie
      // data:-URLs nicht unterstützt
      var text = (new XMLSerializer( )).serializeToString(canvas);
      var encodedText = encodeURIComponent(text);
      return "data:image/svg+xml," + encodedText;
    };

    // Erzeuge ein <object>, um mit einer data:-URL eine SVG-Zeichnung anzuzeigen
    SVG.makeObjectTag = function(canvas, width, height) {
      var object = document.createElement("object"); // Erzeuge HTML-<object>-Tag
      object.width = width;             // Setze die Größe des Objekts
      object.height = height;
      object.data = SVG.makeDataURL(canvas);     // SVG-Bild als data:-URL
      object.type = "image/svg+xml"         // SVG-MIME-Typ
      return object;
    }

Die wichtigste Utility-Funktion in diesem Beispiel ist `SVG.makeCanvas( )`. Sie verwendet DOM-Methoden zur Erzeugung eines neuen `<svg>`-Elements, mit dem dann SVG-Grafiken gezeichnet werden können. Mit `makeCanvas( )` kann sowohl die Größe (in Pixeln) angegeben werden, in der die SVG-Grafik wiedergeben wird, als auch die Größe des internen Koordinatensystems (oder »user space«), das die Zeichnung verwendet. (Beispiel: Eine Zeichnung mit 1000 × 1000 als _user space_ wird als Quadrat mit 250 × 250 Pixeln wiedergegeben. Damit entspricht jede Einheit _user space_ einem Viertelpixel). `makeCanvas( )` erzeugt ein `<svg>`-Tag und gibt es zurück, fügt es aber nicht in das Dokument ein. Das muss der aufrufende Code machen.

Die zwei anderen Utility-Methoden in Beispiel 22-9 werden unter Browsern verwendet, die SVG-Grafiken über ein Plug-in anzeigen. `SVG.makeDataURL( )` serialisiert den XML-Text eines `<svg>`-Tags und codiert ihn als eine `data:`-URL. `SVG.makeObjectTag( )` geht noch einen Schritt weiter und erzeugt ein HTML-Tag `<object>` zum Einbetten einer SVG-Grafik und ruft dann `SVG.makeDataURL( )` auf, um das `data`-Attribut dieses Tags zu setzen. Ebenso wie `SVG.makeCanvas( )` gibt die Methode `SVG.makeObjectTag( )` das `<object>`-Tag zurück. Sie fügt es aber nicht ins Dokument ein.

`SVG.makeObjectTag( )` funktioniert beispielsweise in Browsern wie Firefox 1.0, die das `data:`-URL-Schema und DOM-Methoden unterstützen, namensraumkompatible Methoden wie z.B. `document.createElementNS( )` unterstützen und in denen ein SVG-Plug-in installiert ist. Unter IE funktioniert das nicht, weil dieser Browser die `data:`-URLs und `createElementNS( )` nicht unterstützt. Unter IE bietet es sich als Alternative an, das SVG-Dokument über Stringmanipulation zu konstruieren und nicht über den Aufruf von DOM-Methoden. Anstelle einer `data:`-URL kann die Grafik dann mit einer `javascript:`-URL codiert werden.

Ich schließe diese Besprechung der skriptgesteuerten SVG-Grafiken mit der HTML-Datei ab, die die Funktion `pieChart( )` aus Beispiel 22-8 und die SVG-Utility-Methoden in Beispiel 22-9 verbindet. Der folgende Code erzeugt einen SVG-Canvas, zeichnet auf ihm und fügt ihn dann zweimal in das Dokument ein: einmal direkt und einmal über ein `<object>`-Tag:

    <script src="SVG.js"></script>      <!-- Utility-Methoden -->
    <script src="svgpiechart.js"></script>  <!-- Methode zum Zeichnen -->                     <!-- eines Tortendiagramms -->
    <script>
    function init( ) {
      // Erzeuge ein <svg>-Tag mit 600x400 Auflösung, wiedergegeben
      // mit 300x200 Pixeln
      var canvas = SVG.makeCanvas("canvas", 300, 200, 600, 400);
      pieChart(canvas, [12, 23, 34, 45], 200, 200, 150,  // Canvas, Daten, Größe
           ["red", "blue", "yellow", "green"],     // Farbe der Tortenstücke
           ["Nord","Süd","Ost","West"], 400, 100);   // Legende

      // Füge die Grafik direkt in das Dokument ein:
      document.body.appendChild(canvas);

      // Bette sie mit einem <object>-Tag ein
      var object = SVG.makeObjectTag(canvas, 300, 200);
      document.body.appendChild(object);
    }
    // Lasse diese Funktion laufen, sobald das Dokument vollständig geladen ist
    window.onload = init;
    </script>

# 22.4 VML: Vector Markup Language

VML ist die Microsoft-Antwort auf SVG. Wie SVG ist VML eine XML-Grammatik zur Beschreibung von Zeichnungen. VML ähnelt SVG in vielen Gesichtspunkten. VML ist zwar nicht ganz so umfangreich, hat aber vollständige Zeichenfähigkeiten und steht unter IE 5.5 und höher von Haus aus zur Verfügung. Microsoft (und einige andere Partnerunternehmen) reichten VML beim W3C als Normentwurf ein, aber dieses Bestreben verlief im Sand. Die beste Dokumentation für VML ist Microsofts Entwurf, der immer noch auf der W3C-Webseite unter <http://www.w3.org/TR/NOTE-VML> zu haben ist. Beachten Sie, dass das Dokument zwar auf der W3C-Seite steht, aber nie standardisiert wurde und die Implementierung unter IE Microsoft gehört.

VML ist eine leistungsfähige Technologie, die nie richtig Fuß gefasst hat. Da sie nicht weit verbreitet ist,[82] wurde sie anscheinend nie sorgfältig dokumentiert. Die Microsoft-eigenen Webseiten verweisen normalerweise auf Microsofts W3C-Entwurf als maßgebende Dokumentationsquelle. Da dieses Dokument ein vorläufiger Entwurf war, war es leider nie Gegenstand der sorgfältigen Überprüfung des Standardisierungsverfahrens und ist in Teilen immer noch unvollständig oder verwirrend. Beim Arbeiten mit VML müssen Sie eventuell mit der IE-Implementierung experimentieren, um die Zeichnungen zu erzeugen, die Sie wollen. Das ist ein ziemlich geringfügiger Nachteil, wenn Sie bedenken, dass VML eine leistungsstarke clientseitige Vektorzeichnungsengine ist und in den Webbrowser eingebettet ist, der immer noch den Markt dominiert.

VML ist ein XML-Dialekt und unterscheidet sich von HTML. Allerdings unterstützt IE keine echten XHTML-Dokumente, und seine DOM-Implementierung unterstützt keine namensraumkompatiblen Funktionen, wie z.B. `document.createElementNS( )`. IE bringt VML zum Laufen, indem ein HTML-Behavior (eine weitere IE-spezifische Erweiterung) zum Umgang mit Tags im VML-Namensraum verwendet wird. Alle HTML-Dateien mit VML müssen den Namensraum zuerst wie folgt deklarieren:

    <html xmlns:v="urn:schemas-microsoft-com:vml">

Alternativ kann diese Namensraumdeklaration auf eine IE-spezifische (und nicht standardisierte) Weise wie folgt erreicht werden:

    <xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v"/>

Sie müssen dann mit diesem nicht standardkonformen Teil von CSS angeben, wie mit Tags in diesem Namensraum umgegangen werden soll:

    <style>v\:* { behavior: url(#default#VML); }</style>

Sind diese Teile vorhanden, können VML-Zeichnungen frei mit HTML kombiniert werden, wie im folgenden Code gezeigt wird, der ein sehr ähnliches Ergebnis wie die SVG-Grafik in Abbildung 22-4 produziert:

    <html xmlns:v="urn:schemas-microsoft-com:vml">
    <head><style>v\:* { behavior: url(#default#VML); }</style></head>
    <body>
    Das ist ein rotes Quadrat:<v:rect style="width:10px;height:10px;" fillcolor="red"/>
    Das ist ein blauer Kreis:<v:oval style="width:10px;height:10px;" fillcolor="blue"/>
    </body>
    </html>

Dieses Kapitel konzentriert sich allerdings auf die Verwendung von clientseitigem JavaScript zum dynamischen Zeichnen von Grafiken. Beispiel 22-10 zeigt, wie VML ein Tortendiagramm erzeugt. Dieses Beispiel und das Beispiel des SVG-Tortendiagramms sind sich sehr ähnlich, außer dass SVG-Zeichenfunktionen durch VML-Funktionen ersetzt werden. Aus Gründen der Einfachheit kombiniert dieses Beispiel die Funktionen `makeVMLCanvas( )` und `pieChart( )` mit dem Code, der diese Funktionen zur Erzeugung des Diagramms aufruft. Die Codeausgabe wird hier nicht gezeigt: Sie hat große Ähnlichkeit mit dem SVG-Tortendiagramm in Abbildung 22-5.

Beispiel 22-10. Ein Tortendiagramm mit JavaScript und VML zeichnen

    <!-- HTML-Dokumente, die VML verwenden, müssen dafür einen Namensraum deklarieren -->
    <html xmlns:v="urn:schemas-microsoft-com:vml">
    <head>
    <!-- So assoziieren wir VML-Verhalten mit dem VML-Namensraum -->
    <style>v\:* { behavior: url(#default#VML); }</style>
    <script>
    */
     * Erzeuge ein VML-<v:group>-Element, in dem mit VML gezeichnet werden kann, und gib es aus.
     * Beachte, dass das zurückgegebene Element noch nicht ins Dokument eingefügt wurde.
     */
    function makeVMLCanvas(id, pixelWidth, pixelHeight) {
      var vml = document.createElement("v:group");
      vml.setAttribute("id", id);
      vml.style.width = pixelWidth + "px";
      vml.style.height = pixelHeight + "px";
      vml.setAttribute("coordsize", pixelWidth + " " + pixelHeight);

      // Beginne mit einem weißen Rechteck mit dünnem schwarzen Umriss.
      var rect = document.createElement("v:rect");
      rect.style.width = pixelWidth + "px";
      rect.style.height = pixelHeight + "px";
      vml.appendChild(rect);

      return vml;
    }

    /* Zeichne ein Tortendiagramm auf einen VML-Canvas */
    function pieChart(canvas, data, cx, cy, r, colors, labels, lx, ly) {
      // Hole das Canvas-Element, sofern es per id angegeben wurde
      if (typeof canvas == "string") canvas = document.getElementById(canvas);

      // Zähle die Datenwerte zusammen
      var total = 0;
      for(var i = 0; i < data.length; i++) total += data[i];

      // Bestimme die Größe (in Grad) jedes Tortenstücks
      var angles = []
      for(var i = 0; i < data.length; i++) angles[i] = data[i]/total*360;

      // Durchlaufe alle Tortenstücke
      // VML misst Winkel in Grad/65535, bewegt sich
      // gegen den Uhrzeigersinn und beginnt an der 3-Uhr-Position
      startangle = 90; // Beginne an der 12-Uhr-Position.
      for(var i = 0; i < data.length; i++) {
        // Winkel anpassen, damit unsere Torte im Uhrzeigersinn ab 12 Uhr läuft.
        var sa = Math.round(startangle * 65535);
        var a = -Math.round(angles[i] * 65536);

        // Erzeuge ein VML-Form-Element
        var wedge = document.createElement("v:shape");
        // VML beschreibt die Form eines Pfads ähnlich wie SVG
        var path = "M " + cx + " " + cy +  // Gehe zu (cx,cy)
          " AE " + cx + " " + cy + " " +  // Kreisbogen mit Mittelpunkt auf (cx,cy)
          r + " " + r + " " +       // Horizontale und vertikale Radii
          sa + " " + a +          // Startwinkel und Gesamtwinkel
          " X E";             // Schließe den Pfad durch eine Verbindung zum
                           // Mittelpunkt
        wedge.setAttribute("path", path);      // Setze Form des Tortenstücks
        wedge.setAttribute("fillcolor", colors[i]); // Setze Farbe des Tortenstücks
        wedge.setAttribute("strokeweight", "2px");  // Umrissbreite
        // Positioniere das Tortenstück mit CSS-Styles. Die Koordinaten des
        // Pfads werden relativ zu dieser Größe interpretiert. Also geben wir jeder
        // Form die gleiche Größe wie dem Canvas selbst.
        wedge.style.position = "absolute";
        wedge.style.width = canvas.style.width;
        wedge.style.height = canvas.style.height;

        // Füge die Tortenstücke zum Canvas hinzu
        canvas.appendChild(wedge);

        // Nächstes Tortenstück beginnt dort, wo dieses aufhört
        startangle -= angles[i];

        // Erzeuge ein VML-Element <rect> für die Legende
        var icon = document.createElement("v:rect");
        icon.style.left = lx + "px";        // CSS-Positionierung
        icon.style.top = (ly+i*30) + "px";
        icon.style.width = "20px";         // CSS-Größe
        icon.style.height = "20px";
        icon.setAttribute("fillcolor", colors[i]); // Gleiche Füllfarbe wie das Tortenstück
        icon.setAttribute("stroke", "black");   // Umrissfarbe
        icon.setAttribute("strokeweight", "2");  // Umrissbreite
        canvas.appendChild(icon);         // Setze es auf den Canvas

        // VML hat fortgeschrittene Textfähigkeiten, aber der Großteil des Texts ist einfach
        // HTML wird direkt auf den VML-Canvas gesetzt, und zwar
        // mit Canvas-Koordinaten
        var label = document.createElement("div"); // <div> für den Text
        label.appendChild(document.createTextNode(labels[i])); // die Text-
        label.style.position = "absolute";     // Position mit CSS
        label.style.left = (lx + 30) + "px";
        label.style.top = (ly + 30*i + 5) + "px";
        label.style.fontFamily = "sans-serif";   // Text-Styles
        label.style.fontSize = "16px";
        canvas.appendChild(label);         // Setze den Text auf den Canvas
      }
    }

    function init( ) {
      var canvas = makeVMLCanvas("canvas", 600, 400);
      document.body.appendChild(canvas);
      pieChart(canvas, [12, 23, 34, 45], 200, 200, 150,
           ["red", "blue", "yellow", "green"],
           ["Nord", "Süd", "Ost", "West"],
           400, 100);
    }
    </script>
    </head>
    <body onload="init( )">
    </body>
    </html>

* * *

[82] Google Maps (<http://local.google.com>) ist die einzige mir bekannte berühmtere Site, die VML verwendet.

# 22.5 Grafiken auf einem <canvas>

Die nächste Station auf unserer Tour der Vektorgrafik-Technologien auf der Clientseite ist das `<canvas>`-Tag. Dieses nicht standardkonforme HTML-Element wurde speziell für clientseitige Vektorgrafiken entwickelt. Es hat keine eigene Darstellung, sondern stellt eine Zeichnungs-API für clientseitiges JavaScript zur Verfügung, damit Skripten beliebig auf einen Canvas zeichnen können. Das `<canvas>`-Tag wurde von Apple unter dem Webbrowser Safari 1.3 eingeführt. (Der Grund für diese extreme Erweiterung von HTML liegt darin, dass die HTML-Wiedergabefähigkeit von Safari auch von der Dashboard-Komponente des Mac OS X-Desktops verwendet wird und Apple nach einer Möglichkeit suchte, skriptgesteuerte Grafiken in Dashboard zu unterstützen).

Firefox 1.5 und Opera 9 sind dem Beispiel von Safari gefolgt. Beide unterstützen das `<canvas>`-Tag. Sie können das `<canvas>`-Tag sogar unter IE mit Open Source-JavaScript-Code (ursprünglich von Google) verwenden, der Canvas-Kompatibilität auf der Grundlage der VML-Unterstützung in IE herstellt (siehe <http://excanvas.sourceforge.net>). Der Prozess zur Standardisierung des `<canvas>`-Tags läuft dank eines informellen Zusammenschlusses von Webbrowser-Herstellern, und eine vorläufige Spezifikation steht unter <http://www.whatwg.org/specs/web-apps/current-work>.

Ein wichtiger Unterschied zwischen dem `<canvas>`-Tag und SVG und VML ist, dass `<canvas>` eine auf JavaScript aufbauende Zeichnungs-API hat, während SVG und VML eine Zeichnung anhand eines XML-Dokuments beschreiben. Diese zwei Vorgehensweisen sind in der Funktion äquivalent: Die eine kann durch die andere simuliert werden. An der Oberfläche sind sie jedoch ziemlich unterschiedlich, und jede hat ihre Stärken und Schwächen. Beispielsweise kann eine SVG-Zeichnung einfach bearbeitet werden, indem Elemente aus ihrer Beschreibung gelöscht werden. Um ein Element aus der gleichen Grafik in einem `<canvas>`-Tag zu entfernen, muss man die Zeichnung oft löschen und von Grund auf neu zeichnen. Da die Canvas-Zeichnungs-API auf JavaScript basiert und recht kompakt ist (was für die SVG- und VML-Grammatiken nicht zutrifft), wird sie in diesem Buch dokumentiert. Vergleichen Sie Canvas, CanvasRenderingContext2D und dazugehörige Einträge in Teil IV.

Der Großteil der Canvas-Zeichnungs-API wird nicht auf dem `<canvas>`-Element selbst definiert, sondern über ein »drawing context«-Objekt, das über die Methode `getContext( )` des Canvas verfügbar ist.[83] Dieses Skript zeichnet ein kleines, rotes Quadrat und einen blauen Kreis und ist typisch für das Zeichnen auf einem Canvas:

    <head>
    <script>
    window.onload = function( ) { // Zeichne, wenn das Dokument lädt
      var canvas = document.getElementById("square"); // Hole das Canvas-Element
      var context = canvas.getContext("2d");      // Hole den 2D-Zeichnungskontext
      context.fillStyle = "#f00";           // Setze Füllfarbe auf Rot
      context.fillRect(0,0,10,10);           // Fülle ein Rechteck

      canvas = document.getElementById("circle");   // Neues Canvas-Element
      context = canvas.getContext("2d");        // Hole seinen Inhalt
      context.fillStyle = "#00f";           // Setze Füllfarbe auf Blau
      context.beginPath( );               // Beginne mit einer Form
      // Füge einen vollen Kreis mit Radius 5 und Mittelpunkt (5,5) zur Form hinzu
      context.arc(5, 5, 5, 0, 2*Math.PI, true);
      context.fill( );                 // Fülle die Form
    }
    </script>
    </head>
    <body>
    Das ist ein rotes Quadrat: <canvas id="square" width="10" height="10"></canvas>.
    Das ist ein blauer Kreis: <canvas id="circle" width="10" height="10"></canvas>.
    </body>

In den vorherigen Abschnitten haben Sie gesehen, dass SVG und VML komplizierte Formen als »Pfade« aus Linien und Kurven beschreiben, die gefüllt oder als Umrisse gezeichnet werden können. Die Canvas-API verwendet ebenfalls das Pfad-Konzept. Anstatt einen Pfad als String von Buchstaben und Zahlen zu beschreiben, wird der Pfad hier aber durch eine Abfolge von Methodenaufrufen definiert, wie beispielsweise die Aufrufe `beginPath( )` und `arc( )` in unserem Beispiel. Nach der Definition eines Pfads führen andere Methoden, wie z.B. `fill( )`, Operationen an ihm durch. Verschiedene Eigenschaften des Zeichnungskontexts, wie zum Beispiel `fillStyle`, geben an, wie diese Operationen durchzuführen sind.

Ein Grund für die Kompaktheit der Canvas-API liegt in der fehlenden Unterstützung zum Zeichnen von Text. Um Text in eine `<canvas>`-Grafik einzubauen, müssen Sie ihn entweder selbst zeichnen, ihn mit Bitmap-Bildern einbauen oder HTML-Text mittels CSS-Positionierung über `<canvas>` legen.

Beispiel 22-11 zeigt, wie Tortendiagramme mit einem Canvas gezeichnet werden. Ein großer Teil dieses Codes in diesem Beispiel ist Ihnen aus den SVG- und VML-Beispielen geläufig. Der neue Code im Beispiel ist die Canvas-API. Sie können diese Methoden in Teil IV nachschlagen.

Beispiel 22-11. Ein Tortendiagramm auf ein <canvas>-Tag zeichnen

    <html>
    <head>
    <script>
    // Erzeuge ein neues Canvas-Tag mit der angegebenen id und Größe, und gib es zurück.
    // Beachte, dass diese Methode den Canvas noch nicht in das Dokument einfügt
    function makeCanvas(id, width, height) {
      var canvas = document.createElement("canvas");
      canvas.id = id;
      canvas.width = width;
      canvas.height = height;
      return canvas;
    }

    /**
     * Zeichne ein Tortendiagramm auf den per Element oder id angegebenen Canvas.
     * Daten: ein Array mit darzustellenden Zahlen, eine für jedes Tortenstück
     * Das Tortendiagramm hat seinen Mittelpunkt in (cx, cy) und den Radius r.
     * Die Farben der Tortenstücke sind HTML-Farbstrings im colors[]-array.
     * Eine Legende erscheint bei (lx,ly), um die Beschriftungen mit den labels[] zu assoziieren
     * Array mit allen Farben.
     */
    function pieChart(canvas, data, cx, cy, r, colors, labels, lx, ly) {
      // Hole den Canvas, sofern per id angegeben
      if (typeof canvas == "string") canvas = document.getElementById(canvas);

      // Wir zeichnen mit dem Zeichnungskontext des Canvas
      var g = canvas.getContext("2d");

      // Alle von uns gezeichneten Linien sind 2 Pixel breit und schwarz
      g.lineWidth = 2;
      g.strokeStyle = "black";

      // Summiere die Datenwerte
      var total = 0;
      for(var i = 0; i < data.length; i++) total += data[i];

      // Berechne den Winkel (im Bogenmaß) für jedes Tortenstück
      var angles = []
      for(var i = 0; i < data.length; i++) angles[i] = data[i]/total*Math.PI*2;

      // Durchlaufe jetzt alle Tortenstücke
      startangle = -Math.PI/2; // Beginne nicht bei 3, sondern bei 12 Uhr
      for(var i = 0; i < data.length; i++) {
        // Das ist der Winkel, bei dem das Tortenstück endet
        var endangle = startangle + angles[i];

        // Zeichne ein Tortenstück
        g.beginPath( );       // Beginne eine neue Form
        g.moveTo(cx,cy);      // Gehe zum Mittelpunkt
        // Linie zum startangle-Punkt und Kreisbogen zum endangle-Punkt
        g.arc(cx,cy,r,startangle, endangle, false);
        g.closePath( );       // Zurück zum Mittelpunkt und beende die Figur
        g.fillStyle = colors[i];    // Farbe setzen
        g.fill( );          // Fülle das Tortenstück
        g.stroke( );         // Zeichne den Umriss des Tortenstücks

        // Das nächste Tortenstück beginnt dort, wo dieses aufhört
        startangle = endangle;

        // Zeichne das Rechteck für dieses Tortenstück in der Legende
        g.fillRect(lx, ly+30*i, 20, 20);
        g.strokeRect(lx, ly+30*i, 20, 20);

        // Setze eine Beschriftung neben das Rechteck.
        // Die Canvas-API unterstützt keinen Text, weswegen wir hier
        // einfach normalen HTML-Text verwenden. Wir verwenden die CSS-    // Positionierung, um den Text an die richtige Stelle, also über das
        // Canvas-Element zu setzen. Das wäre etwas sauberer, wenn das canvas-Tag
        // selbst absolut positioniert wäre
        var label = document.createElement("div");
        label.style.position = "absolute";
        label.style.left = (canvas.offsetLeft + lx+30)+"px";
        label.style.top = (canvas.offsetTop+ly+30*i-4) + "px";
        label.style.fontFamily = "sans-serif";
        label.style.fontSize = "16px";
        label.appendChild(document.createTextNode(labels[i]));
        document.body.appendChild(label);
      }
    }

    function init( ) {
      // Erzeuge ein Canvas-Element
      var canvas = makeCanvas("canvas", 600, 400);
      // Füge es zum Dokument hinzu,
      document.body.appendChild(canvas);
      // und zeichne ein Tortendiagramm auf ihm
      pieChart("canvas", [12, 23, 34, 45], 200, 200, 150,
           ["red", "blue", "yellow", "green"],
           ["Nord", "Süd", "Ost", "West"],
           400, 100);
    }
    </script>
    </head>
    <body onload="init( )"></body>
    </html>

* * *

[83] Die Methode braucht ein einziges Argument, das der String »2d« sein muss, und gibt einen Zeichnungskontext zurück, der eine zweidimensionale API implementiert. Falls das `<canvas>`-Tag in der Zukunft 3D-Zeichnungen unterstützt, wird die Methode `getContext( )` voraussichtlich die Übergabe des Strings »3d« erlauben.

# 22.6 Grafiken mit Flash

Alle bisher im Kapitel besprochenen Vektorgrafik-Technologien sind nur begrenzt verfügbar: Das `<canvas>`-Tag wird nur von Safari 1.3, Firefox 1.5 und Opera 9 unterstützt. VML ist (und bleibt) nur unter IE anwendbar, und SVG wird von Haus aus nur unter Firefox 1.5 unterstützt. Eine Plug-in-Unterstützung für SVG ist erhältlich, aber das Plug-in ist nicht weit genug verbreitet.

Ein leistungsstarkes Vektorgrafik-Plug-in _ist_ aber vielerorts (fast überall) installiert: der Flash-Player von Adobe (früher Macromedia). Der Flash-Player hat seine eigene skriptgesteuerte Sprache namens ActionScript (das eigentlich ein Dialekt von JavaScript ist). Seit Flash 6 exponiert der Player eine einfache, aber leistungsfähige Zeichnungs-API für ActionScript-Code. Flash 6 und 7 stellen auch begrenzte Kommunikationskanäle zwischen clientseitigem JavaScript und ActionScript bereit. Das ermöglicht es clientseitigem JavaScript, Zeichenkommandos an das Flash-Plug-in zu senden, die dann durch den ActionScript-Interpreter des Plug-ins ausgeführt werden.

Dieses Kapitel beruht auf Flash 8, das brandneu war, als dieses Buch geschrieben wurde. Flash 8 enthält eine ExternalInterface-API, die den Export von ActionScript-Methoden spielend leicht macht, um sie dann von clientseitigem JavaScript transparent aufzurufen. Kapitel 23 zeigt, wie Flash-Zeichenmethoden unter Flash 6 und 7 aufgerufen werden.

Um einen Flash-basierenden Zeichnungscanvas zu erzeugen, brauchen Sie eine _.swf_ -Datei, die nicht selbst zeichnet, sondern lediglich eine Zeichnungs-API in JavaScript84] auf der Clientseite exportiert. Fangen wir mit der in [Beispiel 22-12 gezeigten ActionScript-Datei an.

Beispiel 22-12. Canvas.as

    import flash.external.ExternalInterface;

    class Canvas {
      // Der Open Source-mtasc-ActionScript-Kompilierer ruft automatisch diese
      // main( )-Methode in der von ihm erstellten kompilierten .swf-Datei auf. Wenn Sie das
      // Flash-IDE verwenden, um eine Canvas.swf-Datei zu erzeugen, müssen Sie stattdessen
      // Canvas.main( ) vom ersten Frame des Movies aus aufrufen.
      static function main( ) { var canvas = new Canvas( ); }

      // Dieser Konstruktor enthält Initialisierungscode für unseren Flash-Canvas
      function Canvas( ) {
        // Gib das Verhalten bei der Größenanpassung für den Canvas an
        Stage.scaleMode = "noScale";
        Stage.align = "TL";

        // Exportiere jetzt einfach die Funktionen der Flash-Zeichnungs-API
        ExternalInterface.addCallback("beginFill", _root, _root.beginFill);
        ExternalInterface.addCallback("beginGradientFill", _root,
                       _root.beginGradientFill);
        ExternalInterface.addCallback("clear", _root, _root.clear);
        ExternalInterface.addCallback("curveTo", _root, _root.curveTo);
        ExternalInterface.addCallback("endFill", _root, _root.endFill);
        ExternalInterface.addCallback("lineTo", _root, _root.lineTo);
        ExternalInterface.addCallback("lineStyle", _root, _root.lineStyle);
        ExternalInterface.addCallback("moveTo", _root, _root.moveTo);

        // Exportiere die unten stehende addText( )-Funktion ebenfalls
        ExternalInterface.addCallback("addText", null, addText);
      }

      static function addText(text, x, y, w, h, depth, font, size) {
        // Erzeuge ein TextField-Objekt, um den Text an der angegebenen Stelle anzuzeigen
        var tf = _root.createTextField("tf", depth, x, y, w, h);
        // Sage ihm, welcher Text angezeigt werden soll
        tf.text = text;
        // Setze die Font-Familie und Punktgröße für den Text
        var format = new TextFormat( );
        format.font = font;
        format.size = size;
        tf.setTextFormat(format);
      }
    }

Der ActionScript-Code in der _Canvas.as_ -Datei in Beispiel 22-12 muss zuerst zu einer _Canvas.swf_ -Datei kompiliert werden, bevor sie mit dem Flash-Player verwendet werden kann. Die Einzelheiten zu dieser Vorgehensweise übersteigen den Umfang dieses Buchs, aber Sie können ein kommerzielles Flash-IDE von Adobe oder einen Open Source-ActionScript-Compiler verwenden.[85]

Leider bietet Flash nur eine primitive API. Insbesondere ist `curveTo( )` die einzige Funktion zum Zeichnen von Kurven und zeichnet eine quadratische Bezierfunktion. Alle Kreise, Ellipsen und kubische Bezierfunktionen müssen durch einfachere quadratische Kurven approximiert werden. Diese primitive API passt gut zum kompilierten SWF-Flash-Format: Alle Berechnungen, die für kompliziertere Kurven benötigt werden, können während der Kompilation durchgeführt werden, und der Flash-Player muss lediglich wissen, wie einfachere Kurven gezeichnet werden. Eine Zeichnungs-API auf höherer Ebene kann auf der von Flash-Player bereitgestellten einfacheren Ebene aufgebaut werden, was in ActionScript und JavaScript möglich ist (Beispiel 22-13 ist in JavaScript).

Beispiel 22-13 beginnt mit einer Utility-Funktion, die die _Canvas.swf_ -Datei in das HTML-Dokument einbettet. Das wird unter verschiedenen Browsern unterschiedlich gemacht, und das `insertCanvas( )`-Utility vereinfacht diese Arbeit. Dann kommt eine `wedge( )`-Funktion, die die einfache Flash-Zeichnungs-API verwendet, um ein Tortenstück des Diagramms zu zeichnen. Daran schließt sich die `pieChart( )`-Funktion an und ruft `wedge( )` auf, um ihr Tortenstück zu zeichnen. Schließlich definiert das Beispiel einen `onload`-Handler, um den Flash-Canvas in das Dokument einzufügen und ihn zu zeichnen.

Beispiel 22-13. Ein Tortendiagramm mit JavaScript und Flash zeichnen

    <html>
    <head>
    <script>
    // Bette einen Flash-Canvas der angegebenen Größe ein, und füge ihn als einziges
    // Kind des angegebenen Container-Elements ein. Aus Gründen der Übertragbarkeit verwendet
    // diese Funktion in Netscape-artigen Browsern ein <embed>-Tag und in anderen Browsern
    // ein <object>-Tag. Inspiriert durch FlashObject von Geoff Stearns.
    // Siehe http://blog.deconcept.com/flashobject/
    function insertCanvas(containerid, canvasid, width, height) {
      var container = document.getElementById(containerid);
      if (navigator.plugins && navigator.mimeTypes&&navigator.mimeTypes.length){
        container.innerHTML =
          "<embed src='Canvas.swf' type='application/x-shockwave-flash' " +
          "width='" + width +
          "' height='" + height +
          "' bgcolor='#ffffff' " +
          "id='" + canvasid +
          "' name='" + canvasid +
          "'>";
      }
      else {
        container.innerHTML =
          "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' "+
          "width='" + width +
          "' height='" + height +
          "' id='"+ canvasid + "'>" +
          " <param name='movie' value='Canvas.swf'>" +
          " <param name='bgcolor' value='#ffffff'>" +
          "</object>";
      }
    }

    // Die Flash-Zeichnungs-API ist primitiver als andere und hat nur eine einfache
    // Bezierkurvenfunktion. Diese Methode zeichnet ein Tortenstück mit dieser API.
    // Beachte, dass die Winkel im Bogenmaß angegeben werden.
    function wedge(canvas, cx, cy, r, startangle, endangle, color) {
      // Bestimme den Startpunkt des Tortenstücks
      var x1 = cx + r*Math.sin(startangle);
      var y1 = cy – r*Math.cos(startangle);

      canvas.beginFill(color, 100); // Fülle mit angegebener Farbe, voll deckend
      canvas.moveTo(cx, cy);    // Gehe zum Kreismittelpunkt
      canvas.lineTo(x1, y1);    // Zeichne eine Linie zum Kreisumfang

      // Unterteile jetzt den Kreisbogen in Stücke < 45 Grad, und zeichne jedes davon
      // über einen Aufruf an die eingebettete arc( )-Methode
      while(startangle < endangle) {
        var theta;
        if (endangle-startangle > Math.PI/4) theta = startangle+Math.PI/4;
        else theta = endangle;
        arc(canvas,cx,cy,r,startangle,theta);
        startangle += Math.PI/4;
      }

      canvas.lineTo(cx, cy);    // Beende mit einer Linie zurück zum Mittelpunkt
      canvas.endFill( );       // Fülle den Umriss des Tortenstücks

      // Diese eingebettete Funktion zeichnet einen Kreisbogen per Bezierkurve.
      // endangle-startangle muss <= 45 Grad sein.
      // Der momentane Punkt muss bereits am startangle-Punkt sein.
      // Glauben Sie mir einfach, wenn Sie die Mathematik nicht verstehen.
      function arc(canvas, cx, cy, r, startangle, endangle) {
        // Berechne den Kurvenendpunkt
        var x2 = cx + r * Math.sin(endangle);
        var y2 = cy – r * Math.cos(endangle);

        var theta = (endangle – startangle)/2;
        // Das ist der Abstand zwischen Mittelpunkt und Kontrollpunkt
        var l = r/Math.cos(theta);
        // Winkel vom Mittelpunkt zum Kontrollpunkt ist:
        var alpha = (startangle + endangle)/2;

        // Berechne den Kontrollpunkt für die Kurve
        var controlX = cx + l * Math.sin(alpha);
        var controlY = cy – l * Math.cos(alpha);

        // Rufe jetzt die Flash-API zum Zeichnen des Kreisbogens als Bezierkurve auf.
        canvas.curveTo(controlX, controlY, x2, y2);
      }
    }

    /**
     * Zeichne ein Tortendiagramm auf den per Element oder id angegebenen Flash-Canvas.
     * Daten: ein Array mit darzustellenden Zahlen, eine für jedes Tortenstück
     * Das Tortendiagramm hat seinen Mittelpunkt in (cx, cy) und den Radius r.
     * Die Farben der Tortenstücke sind Flash-Farbwerte im colors[]-array.
     * Eine Legende erscheint bei (lx,ly), um die Beschriftungen mit den labels[] zu assoziieren
     * Array mit allen Farben.
     */
    function pieChart(canvas, data, cx, cy, r, colors, labels, lx, ly) {
      // Hole den Canvas, sofern per id angegeben
      if (typeof canvas == "string")
        canvas = document.getElementById(canvas);

      // Alle von uns gezeichneten Linien sind 2 Pixel breit, schwarz und 100% deckend.
      canvas.lineStyle(2, 0x000000, 100);

      // Summiere die Datenwerte
      var total = 0;
      for(var i = 0; i < data.length; i++) total += data[i];

      // Berechne den Winkel (im Bogenmaß) für jedes Tortenstück.
      var angles = []
      for(var i = 0; i < data.length; i++) angles[i] = data[i]/total*Math.PI*2;

      // Durchlaufe jetzt alle Tortenstücke
      startangle = 0;
      for(var i = 0; i < data.length; i++) {
        // Das ist der Winkel, bei dem das Tortenstück endet
        var endangle = startangle + angles[i];

        // Zeichne ein Tortenstück: Diese Funktion wurde weiter oben definiert
        wedge(canvas, cx, cy, r, startangle, endangle, colors[i]);

        // Das nächste Tortenstück beginnt dort, wo dieses aufhört.
        startangle = endangle;

        // Zeichne ein Rechteck für die Legende
        canvas.beginFill(colors[i], 100);
        canvas.moveTo(lx, ly+30*i);
        canvas.lineTo(lx+20, ly+30*i);
        canvas.lineTo(lx+20, ly+30*i+20);
        canvas.lineTo(lx, ly+30*i+20);
        canvas.lineTo(lx, ly+30*i);
        canvas.endFill( );

        // Füge einen Text neben das Rechteck ein
        canvas.addText(labels[i], lx+30, ly+i*30, 100, 20, // Text und Position
                i, // jedes Textfeld muss eine andere Tiefe haben
                "Helvetica", 16);          // Font-Information
      }
    }

    // Füge nach dem Laden des Dokuments einen Flash-Canvas ein, und zeichne auf ihm
    // Beachte, dass Farben in Flash nicht Strings, sondern ganze Zahlen sind
    window.onload = function( ) {
      insertCanvas("placeholder", "canvas", 600, 400);
      pieChart("canvas", [12, 23, 34, 45], 200, 200, 150,
           [0xff0000, 0x0000ff, 0xffff00, 0x00ff00],
           ["Nord", "Süd", "Ost", "West"],
           400, 100);
    }
    </script>
    </head>
    <body>
    <div id="placeholder"></div>
    </body>
    </html>

* * *

84] Der Flash-basierte Tortendiagramm-Code in [Beispiel 22-13 verwendet diese Flash-Zeichnungs-API, die ich aber hier nicht dokumentiere. Eine Dokumentation befindet sich auf der Adobe-Website.

[85] Ich habe den Open Source- _mtasc_ -Compiler (<http://www.mtasc.org>) verwendet und den Code mit diesem Befehl kompiliert:

    mtasc -swf Canvas.swf -main -version 8 -header 500:500:1 Canvas.as

Nach der Kompilation ist die resultierende _Canvas.swf_ -Datei gerade mal 578 Bytes groß – kleiner als die meisten Bitmap-Bilder.

# 22.7 Grafiken mit Java

Das Java-Plug-in von Sun Microsystems ist nicht so weit verbreitet wie das Flash-Plug-in, wird aber zunehmend üblicher, und manche Computerhersteller installieren es von vorneherein auf ihrer Hardware. Die Java2D API ist eine leistungsstarke Vektorzeichnungs-API. Es gibt sie seit Java 1.2. Sie ist auf einer höheren Ebene angeordnet, in der Verwendung einfacher als die Flash-Zeichnungs-API und umfangreicher (sie unterstützt beispielsweise Text) als die `<canvas>`-API. Mit Hinsicht auf Features hat Java2D am meisten Ähnlichkeit mit SVG. Dieser Abschnitt beschreibt zwei nützliche Vorgehensweisen zur Verwendung der Java2D API von clientseitigem JavaScript aus.

## 22.7.1 Tortendiagramme mit Java

Unter Java können Sie genauso vorgehen wie unter Flash: Erstellen Sie ein »Canvas«-Applet, das kein eigenes Verhalten hat, sondern nur dem Export der Java2D API dient, damit sie von clientseitigem JavaScript aufgerufen werden kann. (Kapitel 23 enthält weitere Informationen zur Steuerung von Java aus JavaScript.) Ein solches Applet kann z.B. so wie das in Beispiel 22-14 aussehen. Beachten Sie, dass dieses Applet die Möglichkeiten der Java2D API nur sehr begrenzt ausnutzt und nur einen minimalen Teil der vorhandenen Methoden zeigt. Die Flash-Zeichnungs-API bestand aus acht einfachen Methoden, die alle auf einfache Art exportiert werden konnten. Java2D hat weitaus mehr Methoden. Obwohl es technisch nicht schwer ist, sie alle über ein Canvas-Applet bereitzustellen, wäre so ein Applet zu lang, um hier aufgeführt zu werden. Der Code in Beispiel 22-14 zeigt eine einfache Vorgehensweise und eine ausreichend vielfältige API, um das Tortendiagramm in Abbildung 22-5 zu zeichnen.

Beispiel 22-14. Ein Java-Canvas-Applet zum clientseitigen Zeichnen

    import java.applet.*;
    import java.awt.*;
    import java.awt.geom.*;
    import java.awt.image.*;

    /**
     * Dieses einfache Applet macht selbst nichts: Es exportiert lediglich eine API
     * zur Benutzung durch clientseitigen JavaScript-Code.
     */
    public class Canvas extends Applet {
      BufferedImage image; // Wir zeichnen in dieses Offscreen-Bild
      Graphics2D g;     // mit diesem Grafikkontext

      // Der Browser ruft diese Methode auf, um das Applet zu initialisieren
      public void init( ) {
        // Bestimme die Größe des Applets, und erzeuge ein Offscreen-Bild
        // mit dieser Größe.
        int w = getWidth( );
        int h = getHeight( );
        image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        // Hole einen Grafikkontext, um in das Bild zu zeichnen
        g = image.createGraphics( );
        // Beginne mit einem völlig weißen Hintergrund
        g.setPaint(Color.WHITE);
        g.fillRect(0, 0, w, h);
        // Schalte Anti-Aliasing ein
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                  RenderingHints.VALUE_ANTIALIAS_ON);
      }

      // Der Browser ruft diese Methode automatisch auf, wenn das Applet
      // neu gezeichnet werden muss. Wir kopieren das Offscreen-Bild auf den Bildschirm.
      // JavaScript-Code, der auf dieses Applet zeichnet, muss die vererbte
      // repaint( )-Methode aufrufen, um ein Neuzeichnen anzufordern.
      public void paint(Graphics g) { g.drawImage(image, 0, 0, this); }

      // Diese Methoden setzen einfache Zeichenparameter
      // Das ist nur eine kleine Auswahl: die Java2D API unterstützt viel mehr
      public void setLineWidth(float w) { g.setStroke(new BasicStroke(w)); }
      public void setColor(int color) { g.setPaint(new Color(color)); }
      public void setFont(String fontfamily, int pointsize) {
        g.setFont(new Font(fontfamily, Font.PLAIN, pointsize));
      }

      // Das sind einfache Zeichenbausteine
      public void fillRect(int x, int y, int w, int h) { g.fillRect(x,y,w,h); }
      public void drawRect(int x, int y, int w, int h) { g.drawRect(x,y,w,h); }
      public void drawString(String s, int x, int y) { g.drawString(s, x, y); }

      // Diese Methoden füllen und zeichnen beliebige Formen
      public void fill(Shape shape) { g.fill(shape); }
      public void draw(Shape shape) { g.draw(shape); }

      // Diese Methoden geben einfache Form-Objekte zurück
      // Das ist nur eine kleine Probe. Die Java2D API unterstützt noch viel mehr.
      public Shape createRectangle(double x, double y, double w, double h) {
        return new Rectangle2D.Double(x, y, w, h);
      }
      public Shape createEllipse(double x, double y, double w, double h) {
        return new Ellipse2D.Double(x, y, w, h);
      }
      public Shape createWedge(double x, double y, double w, double h,
                   double start, double extent) {
        return new Arc2D.Double(x, y, w, h, start, extent, Arc2D.PIE);
      }
    }

Dieses Applet wurde mit dem _javac_ -Compiler kompiliert, um eine Datei namens _Canvas. class_ zu erhalten:

    % avac Canvas.java

Die kompilierte _Canvas.class_ kann dann in eine HTML-Datei eingebettet und wie folgt gesteuert werden:

    <head>
    <script>
    window.onload = function( ) {
       var canvas = document.getElementById('square');
       canvas.setColor(0x0000ff); // Achtung: Farbe ist Ganzzahl
       canvas.fillRect(0,0,10,10);
       canvas.repaint( );

       canvas = document.getElementById('circle');
       canvas.setColor(0xff0000);
       canvas.fill(canvas.createEllipse(0,0,10,10));
       canvas.repaint( );
    };
    </script>
    </head>
    <body>
    Das ist ein blaues Quadrat:
    <applet id="square" code="Canvas.class" width="10" height="10"></applet>
    Das ist ein roter Kreis:
    <applet id="circle" code="Canvas.class" width="10" height="10"></applet>
    </body>

Dieser Code ist darauf angewiesen, dass der `onload`-Event-Handler erst dann aufgerufen wird, wenn das Applet vollständig geladen und bereit ist. Bei alten Browsern und Java-Plug-in-Versionen vor Java 5 wird der `onload`-Handler häufig aufgerufen, bevor die Applets initialisiert sind, wodurch Code wie dieser fehlschlägt. Wenn die Zeichnung nicht als Antwort auf das `onload`-Event stattfindet, sondern als Antwort auf Benutzer-Events, ist das normalerweise kein Problem.

Beispiel 22-15 enthält den JavaScript-Code zum Zeichnen eines Tortendiagramms mit dem Java-Canvas-Applet. Dieses Beispiel lässt die Funktion `makeCanvas( )` aus, die in den anderen Beispielen definiert wurde. Aufgrund des oben erläuterten Problems mit `onload` zeichnet dieses Beispiel das Diagramm nicht automatisch nach dem Laden des Dokuments, sondern als Antwort auf einen Mausklick.

Beispiel 22-15. Ein Tortendiagramm mit JavaScript und Java zeichnen

    <head>
    <script>
    // Zeichne ein Tortendiagramm mit dem Java-Canvas-Applet
    function pieChart(canvas, data, cx, cy, r, colors, labels, lx, ly) {
      // Suche nach dem Canvas per Namen, falls erforderlich
      if (typeof canvas == "string") canvas = document.getElementById(canvas);

      // Alle Linien haben eine Breite von 2 Einheiten. Aller Text ist 16pt, fett und
      // sans-serif.
      canvas.setLineWidth(2);
      canvas.setFont("SansSerif", 16);

      // Addiere die Daten
      var total = 0;
      for(var i = 0; i < data.length; i++) total += data[i];

      // Berechne die Tortenstückwinkel in Grad
      var angles = []
      for(var i = 0; i < data.length; i++) angles[i] = data[i]/total*360;

      startangle = 90; // Beginne nicht bei 3, sondern bei 12 Uhr
      // Durchlaufe die Tortenstücke
      for(var i = 0; i < data.length; i++) {
        // Dieses Objekt beschreibt ein Stück der Torte
        var arc = canvas.createWedge(cx-r, cy-r, r*2, r*2,
                       startangle, -angles[i]);
        canvas.setColor(colors[i]);       // Setze diese Farbe
        canvas.fill(arc);            // Fülle das Tortenstück
        canvas.setColor(0x000000);       // Ändere die Farbe zu Schwarz
        canvas.draw(arc);            // Zeichne den Umriss des Tortenstücks

        startangle -= angles[i]; // fürs nächste Mal

        // Zeichne jetzt das Rechteck für die Legende
        canvas.setColor(colors[i]);       // Zurück zur Tortenstückfarbe
        canvas.fillRect(lx, ly+30*i, 20, 20);  // Fülle das Rechteck
        canvas.setColor(0x000000);       // Und wieder zurück zu Schwarz
        canvas.drawRect(lx, ly+30*i, 20, 20);  // Zeichne den Umriss des Rechtecks

        // Und zeichne die Beschriftung für jedes Tortenstück
        // Denke daran, dass wir die Schrift schon oben gesetzt haben
        canvas.drawString(labels[i], lx+30, ly+30*i+18);
      }
      // Das Applet anweisen, sich selbst anzuzeigen
      canvas.repaint( ); // Diesen Aufruf nicht vergessen
    }

    // Diese Funktion wird aufgerufen, wenn der Zeichnen!-Button angeklickt wird
    function draw( ) {
      pieChart("canvas", [12, 23, 34, 45], 200, 200, 150,
           [0xff0000, 0x0000ff, 0xffff00, 0x00ff00], // Farben werden als Zahlen dargestellt
           ["Nord", "Süd", "Ost", "West"],
           400, 100);
    }
    </script>
    </head>
    <body>
    <applet id="canvas" code="Canvas.class" width="600" height="400"></applet>
    <button onclick="draw( )">Zeichnen!</button>
    </body>

## 22.7.2 Clientseitige Sparkline-Bilder mit Java

Dieser Abschnitt verwendet die Java2D API zum Zeichnen von Grafiken. Anstatt diese Grafiken direkt in einem Applet darzustellen, wird die wiedergegebene Grafik aber als PNG-Bytestream geschrieben. Dieser Bytestream wird dann als eine `data:`-URL codiert. Auf diese Weise kann JavaScript auf der Clientseite seine eigenen Inline-Bilder erzeugen. Das kann zwar auch über ein Applet gemacht werden, aber diese Vorgehensweise steuert Java direkt über LiveConnect (siehe Kapitel 12), was unter Firefox und ähnlichen Browsern möglich ist.

Mit dieser Bilderzeugungstechnik können Sparklines angezeigt werden. Eine _Sparkline_ ist eine kleine Datenanzeigegrafik, die in den Textfluss integriert wird. Hier sehen Sie ein Beispiel:

Der Begriff _Sparkline_ wurde von dem Autor Edward Tufte erfunden, der sie als »kleine hochauflösende Grafiken« beschreibt, »die in den Kontext von Worten, Zahlen und Bildern eingebettet sind. Sparklines sind datenintensive Grafiken von Wortgröße und einfachem Design«. (Mehr über Sparklines erfahren Sie in Tuftes Buch _Beautiful Evidence_ [Graphics Press].)

Beispiel 22-16 zeigt den Code, mit dem die oben gezeigte Serverload-Sparkline erstellt wurde. Die JavaScript-Funktion `makeSparkline( )` verwendet LiveConnect ausgiebig zur Steuerung der Java2D API, ohne auf ein Applet angewiesen zu sein.

Beispiel 22-16. Ein Sparkline-Bild mit Javascript und Java erzeugen

    <head>
    <script>
    /**
     * Die Daten sind ein Zahlenarray, das als Zeitabfolge aufgezeichnet wird.
     * dx stellt die Anzahl der x-Pixel zwischen den Datenpunkten dar.
     * config ist ein Objekt mit Werten, die wahrscheinlich
     * für mehrere Aufrufe gleich bleiben:
     *  height: die Höhe in Pixeln des erzeugten Bildes
     *  ymin, ymax: der Bereich oder die Y-Achsenschranken in Benutzerkoordinaten
     *  backgroundColor: die Hintergrundfarbe als numerische HTML-Farbe.
     *  lineWidth: die Breite der zu zeichnenden Linie
     *  lineColor: die Farbe der zu zeichnenden Linie als HTML-#-Farbangabe
     *  dotColor: sofern angegeben, wird ein Punkt mit dieser Farbe auf
     *       den letzten Datenpunkt gesetzt
     *  bandColor: sofern angegeben, wird ein Band mit dieser Farbe zwischen den
     *   bandMin- und bandMax-Werten gezeichnet, um den "normalen" Bereich der
     *   Datenwerte darzustellen und die Werte außerhalb des Bereichs hervorzuheben
     */
    function makeSparkline(data, dx, config) {
      var width = data.length * dx + 1; // Gesamtbreite der Grafik
      var yscale = config.height/(config.ymax – config.ymin); // Zum Skalieren der Daten

      // Konvertiere die Datenpunktzahl in einen Pixelwert
      function x(i) { return i * dx; }
      // Konvertiere eine Y-Koordinate von Benutzer- in Pixelkoordinaten
      function y(y) { return config.height – (y – config.ymin)*yscale; }
      // Konvertiere eine HTML-Farbangabe in ein java.awt.Color-Objekt
      function color(c) {
        c = c.substring(1); // Entferne vorangestelltes #
        if (c.length == (3)) { // konvertiere in 6-Zeichen-Darstellung, falls nötig
          c = c.charAt(0) + c.charAt(0) + c.charAt(1) + c.charAt(1) +
            c.charAt(2) + c.charAt(2);
        }
        var red = parseInt(c.substring(0,2), 16);
        var green = parseInt(c.substring(2,4), 16);
        var blue = parseInt(c.substring(4,6), 16);
        return new java.awt.Color(red/255, green/255, blue/255);
      }

      // Erzeuge ein Offscreen-Bild für die Sparkline
      var image = new java.awt.image.BufferedImage(width, config.height,
                      java.awt.image.BufferedImage.TYPE_INT_RGB);

      // Hole ein Grafikobjekt, mit dem wir in das Bild zeichnen können
      var g = image.createGraphics( );

      // Alles mit Anti-Aliasing. Kompromiss: ergibt eine glattere, aber unschärfere Linie
      g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING,
                java.awt.RenderingHints.VALUE_ANTIALIAS_ON);

      // Fülle das Bild mit der Hintergrundfarbe
      g.setPaint(color(config.backgroundColor));
      g.fillRect(0, 0, width, config.height);

      // Zeichne das Band, falls eine bandColor angegeben ist
      if (config.bandColor) {
        g.setPaint(color(config.bandColor));
        g.fillRect(0, y(config.bandMax),
              width, y(config.bandMin)-y(config.bandMax));
      }

      // Konstruiere jetzt die Linie
      var line = new java.awt.geom.GeneralPath( );
      line.moveTo(x(0), y(data[0]));
      for(var i = 1; i < data.length; i++) line.lineTo(x(i), y(data[i]));

      // Setze die Linienfarbe und -breite, und zeichne dann die Linie
      g.setPaint(color(config.lineColor));           // Setze Linienfarbe
      g.setStroke(new java.awt.BasicStroke(config.lineWidth)); // Setze Breite
      g.draw(line);                      // Zeichne!

      // Zeichne den Punkt, sofern die dotColor angegeben ist
      if (config.dotColor) {
        g.setPaint(color(config.dotColor));
        var dot=new java.awt.geom.Ellipse2D$Double(x(data.length-1)-.75,
                              y(data[data.length-1])-.75,
                              1.5, 1.5)
        g.draw(dot);
      }

      // Schreibe das Bild als Byte-Array im PNG-Format
      var stream = new java.io.ByteArrayOutputStream( );
      Packages.javax.imageio.ImageIO.write(image, "png", stream);
      var imageData = stream.toByteArray( );

      // Konvertiere die Daten in einen URL-codierten String
      var rawString = new java.lang.String(imageData, "iso8859-1");
      var encodedString = java.net.URLEncoder.encode(rawString, "iso8859-1");
      encodedString = encodedString.replaceAll("\\+", "%20");

      // Und gib alles als eine data:-URL zurück
      return "data:image/png," + encodedString;
    }

    // Hier ist ein Beispiel, das die makeSparkline( )-Funktion verwendet
    window.onload = function( ) {
      // Erzeuge das img-Tag für die Sparkline
      var img = document.createElement("img");
      img.align = "center";
      img.hspace = 1;

      // Setze sein src-Attribut auf die data:-URL der Sparkline
      img.src = makeSparkline([3, 4, 5, 6, 7, 8, 8, 9, 10, 10, 12,
                   16, 11, 10, 11, 10, 10, 10, 11, 12,
                   16, 11, 10, 11, 10, 10, 10, 11, 12,
                   14, 16, 18, 18, 19, 18, 17, 17, 16,
                   14, 16, 18, 18, 19, 18, 17, 17, 16],
                  2, { height: 20, ymin: 0, ymax: 20,
                     backgroundColor: "#fff",
                     lineWidth: 1, lineColor: "#000",
                     dotColor: "#f00", bandColor: "#ddd",
                     bandMin: 6, bandMax: 14
                  });

      // Suche nach dem Platzhalterelement für die Sparkline
      var placeholder = document.getElementById("placeholder");

      // und ersetze sie es durch das Bild.
      placeholder.parentNode.replaceChild(img, platzhalter);
    }
    </script>
    </head>
    <body>
    Serverload: <span id="platzhalter"></span><span style="color:#f00">16</span>
    </body>

# Kapitel 23. Java-Applets und Flash-Movies skripten

Ein _Plug-in_ ist ein Software-Modul, das in einen Webbrowser »eingesteckt« werden kann, um dessen Funktionalität für einen bestimmten Zweck zu erweitern. Die zwei gebräuchlichsten (und nicht zufällig auch leistungsfähigsten) Plug-ins sind das Java-Plug-in von Sun Microsystems und der Flash-Player von Adobe (der Firma, die Macromedia übernommen hat). Mit einem Java-Plug-in kann ein Browser Anwendungen laufen lassen, die als _Applets_ bezeichnet werden und in der Programmiersprache Java geschrieben sind. Das Java-Sicherheitssystem verhindert, dass Applets, denen Sie nicht trauen, Dateien im lokalen Dateisystem lesen oder schreiben können oder sonst etwas machen können, wodurch Daten geändert würden oder Ihr Datenschutz gefährdet werden könnte. Trotz der Sicherheitseinschränkungen bei Applets enthält das Java-Plug-in eine riesige Bibliothek von vordefinierten Klassen, von denen die Applets Gebrauch machen können. Diese Bibliothek enthält Grafiken und GUI-Pakete, leistungsstarke Netzwerkkommunikationsoptionen, Pakete zum Parsen und Manipulieren von XML und kryptografische Algorithmen. Java 6, das beim Schreiben dieses Buches als Pre-Release verfügbar war, enthält eine komplette Paketpalette für Webservices.

Der Flash-Player ist außergewöhnlich beliebt und wird fast flächendeckend verwendet. Er ist eine virtuelle Maschine, die »Movies« interpretiert. Diese können echtes Streaming-Video enthalten, bestehen aber meist aus Animationen und aufwendigen grafischen Benutzeroberflächen. Flash-Movies können Skripten enthalten, die in einer als ActionScript bekannten Sprache geschrieben sind. ActionScript ist eine Variante von JavaScript, die zusätzlich Konstrukte der objektorientierten Programmierung enthält, wie z.B. Klassen, statische Methoden und optionale Typenbindung von Variablen. Der ActionScript-Code in einem Flash-Movie hat Zugriff auf eine leistungsstarke Bibliothek mit Code, der Grafiken, Netzwerkkommunikation und XML-Bearbeitung unterstützt (obwohl die Bibliothek nicht ganz so umfangreich ist wie die für Java).

Der Begriff _Plug-in_ wird Java und Flash eigentlich nicht gerecht. Sie sind nicht nur einfache Browsererweiterungen; sie sind beide für eigenständige Anwendungsentwicklung geeignet und können beide eine reichhaltigere Benutzererfahrung bieten als DHTML-basierte Webanwendungen. Wenn Sie erst einmal mitbekommen, wie enorm der Beitrag dieser Plug-ins zur Webbrowserumgebung ist, ist es ganz verständlich, wenn Sie diese Leistung auch mit JavaScript anzapfen wollen. Zum Glück geht das auch. JavaScript kann sowohl zum Steuern von Java-Applets als auch von Flash-Movies verwendet werden. Das ist sogar auch umgekehrt möglich: Java-Applets und Flash-Movies können JavaScript-Funktionen aufrufen. In diesem Kapitel wird erklärt, wie Sie das zum Laufen kriegen. Nehmen Sie sich aber in Acht: Die Schnittstellen zwischen JavaScript, Java und ActionScript sind umständlich, und wenn Sie eine ernsthafte Skriptsteuerung von Java und Flash durchführen, werden Sie es mit Inkompatibilitäten, Bugs und Frustrationen zu tun bekommen.

Am Anfang dieses Kapitel wird erklärt, wie Sie clientseitiges JavaScript verwenden können, um Java-Applets zu steuern. (Sie erinnern sich vielleicht an Beispiel 22-14, das ein skriptgesteuertes Java-Applet verwendet, um Grafiken auf der Clientseite zu erzeugen). Anschließend wird erläutert, wie JavaScript-Code unter Firefox und verwandten Browsern das Java-Plug-in sogar dann steuern kann, wenn kein Applet vorhanden ist. (Diese Technik wurde in Beispiel 22-16 gezeigt).

Nachdem wir erklärt haben, wie man Java durch JavaScript steuert, zeigt das Kapitel, wie man Applets erstellt, die JavaScript-Eigenschaften lesen und schreiben und JavaScript-Methoden aufrufen können. Dazu zählen auch Applets, die die Java-Version der DOM API verwenden, um mit dem vom Webbrowser angezeigten Dokument zu interagieren.

Java und JavaScript wurden bereits in Kapitel 12 besprochen. Das Kapitel hatte aber ein ganz anderes Thema. Es beschrieb, wie ein JavaScript-Interpreter in eine Java-Anwendung eingebettet wird und wie den Skripten, die unter diesem Interpreter laufen, erlaubt wird, mit einem Java-Programm zu kommunizieren. Kapitel 12 behandelte weder clientseitiges JavaScript noch Applets. Es behandelte LiveConnect, eine Technologie, mit der JavaScript mit Java kommunizieren kann, und die Erläuterungen zu LiveConnect spielen in diesem Kapitel hier eine Rolle. Beachten Sie aber, dass die Features, die in Kapitel 12 speziell als Features für die »Rhino-Version von LiveConnect« angegeben wurden, mit clientseitigem JavaScript und Applets nicht funktionieren.

In den Java-Abschnitten dieses Kapitels wird davon ausgegangen, dass Sie zumindest die Grundlagen der Java-Programmierung kennen. Wenn Sie in Ihren Webseiten keine Applets verwenden, können Sie die entsprechenden Abschnitte überspringen.

Nachdem wir Java behandelt haben, widme ich mich dem Steuern von Flash: Wie wird es JavaScript-Code ermöglicht, ActionScript-Methoden aufzurufen, die in einem Flash-Movie definiert wurden, und wie wird dem ActionScript-Code in einem Movie ermöglicht, JavaScript-Code aufzurufen? Dieses Thema bespreche ich in zwei Varianten: Zuerst stelle ich Techniken vor, die in allen modernen Flash-Versionen funktionieren, und dann erläutere ich eine einfachere, neuere Technik, die nur in den Flash-Versionen 8 und höher funktioniert.

Da Flash so leistungsstark ist, wurde es bereits in den vorhergehenden Kapiteln dieses Buches verwendet. In Kapitel 22 zeigte der Flash-Player in einem Flash-Movie dynamische, clientseitige Grafiken mit Hilfe einfachen ActionScript-Codes (Beispiel 22-12) an. In Kapitel 19 nutzte Beispiel 19-4 den clientseitigen Persistence-Mechanismus des Flash-Players.

# 23.1 Applets skripten

Um ein Applet zu skripten, müssen Sie zuerst auf das HTML-Element zugreifen können, das dieses Applet repräsentiert. Wie in Kapitel 15 besprochen, werden alle in einer Webseite eingebetteten Java-Applets Teil des Arrays `Document.applets[]`. Wenn ein `name` oder eine `id` gegeben sind, kann auf ein Applet direkt als Eigenschaft des Document-Objekts zugegriffen werden. Beispielsweise kann auf ein Applet, das mit dem Tag `<applet name="chart">` erzeugt wurde, mit `document.chart` verwiesen werden. Wenn Sie ein `id`-Attribut für Ihr Applet angeben, können Sie das Applet natürlich über `Document. getElementById( )` suchen.

Wenn das Applet-Objekt erstellt ist, kann JavaScript auf die vom Applet definierten öffentlichen Felder und Methoden genauso zugreifen, als ob sie die Eigenschaften und Methoden des HTML-Elements `<applet>` selbst wären. Nehmen wir als Beispiel den in Beispiel 22-14 definierten Canvas. Wenn eine Instanz dieses Applets mit der `id` »canvas« in eine HTML-Seite eingebettet wird, kann der folgende Code verwendet werden, um Methoden des Applets aufzurufen:

    var canvas = document.getElementById('canvas');
    canvas.setColor(0x0000ff);
    canvas.fillRect(0,0,10,10);
    canvas.repaint( );

JavaScript kann die Werte von öffentlichen Feldern eines Applets und sogar von Feldern, die ein Array sind, abfragen und setzen. Nehmen wir an, ein Applet mit `name="chart"` definiere zwei Felder, die wie folgt deklariert sind (Java-Code):

    public int numPoints;
    public double[] points;

Ein JavaScript-Programm könnte diese Felder über den folgenden Code verwenden:

    for(var i = 0; i < document.chart.numPoints; i++)
      document.chart.points[i] = i*i;

Dieses Codefragment zeigt die Schwierigkeit beim Verbinden von JavaScript und Java: _Typumwandlung_. Java ist eine stark typisierte Sprache mit einigen eindeutigen einfachen Typen; JavaScript ist nur schwach typisiert und hat lediglich einen numerischen Typ. Im obigen Beispiel wird ein Integer in Java in eine JavaScript-Zahl konvertiert und werden mehrere JavaScript-Zahlen zu `double`-Werten in Java konvertiert. Im Hintergrund wird sehr viel Arbeit durchgeführt, um sicherzugehen, dass diese Werte korrekt und wie benötigt konvertiert werden. Das Thema Datenumwandlung bei der Verwendung von JavaScript zum Skripten von Java wird in Kapitel 12 behandelt. Es empfiehlt sich eventuell, an dieser Stelle auf dieses Kapitel zurückzugreifen. Teil III enthält ebenfalls nützliche Einträge zu JavaObject, JavaArray, JavaClass und JavaPakkage. Beachten Sie, dass Kapitel 12 LiveConnect dokumentiert, eine Technologie, die von Netscape stammt. Nicht alle Browser verwenden LiveConnect. IE beispielsweise verwendet seine eigene ActiveX-Technologie als Brücke zwischen JavaScript und Java. Unabhängig von der Technologie unter der Motorhaube sind die Grundregeln zur Konvertierung von Werten zwischen Java und JavaScript in allen Browsern mehr oder weniger gleich.

Schließlich ist es wichtig zu erwähnen, dass Java-Methoden Java-Objekte zurückgeben können und JavaScript die öffentlichen Felder dieser Objekte genau wie bei einem Applet-Objekt lesen und schreiben kann und die öffentlichen Methoden dieser Objekte aufrufen kann. Außerdem kann JavaScript Java-Objekte als Argumente an Java-Methoden übergeben. Nehmen wir noch einmal das Canvas-Applet von Beispiel 22-14. Es definiert Methoden, die Shape-Objekte zurückgeben. JavaScript-Code kann Methoden dieser Shape-Objekte aufrufen und die Objekte an andere Applet-Methoden weiterreichen, die Shape-Argumente erwarten.

Beispiel 23-1 ist ein Beispiel für ein Java-Applet, das nichts anderes macht, als eine nützliche Methode zu definieren, die JavaScript aufrufen kann. Diese `getText( )`-Methode liest eine URL (die vom gleichen Server wie das Applet stammen muss) und gibt ihren Inhalt als Java-String zurück. Hier kommt ein Beispiel, das dieses Applet in einer einfachen selbstauflistenden HTML-Datei verwendet:

    <!-- Selbstauflistende HTML-Datei, die ein Applet zum Abruf einer URL verwendet -->
    <body onload="alert(document.http.getText('GetText.html'));">
    <applet name="http" code="GetTextApplet.class" width="1" height="1"></applet>
    </body>

Beispiel 23-1 verwendet einfache Java-Netzwerkverbindungen, I/O- und Textmanipulationsklassen, macht aber sonst nichts besonders Schwieriges. Es definiert lediglich eine nützliche Methode und deklariert sie als `public`, sodass JavaScript sie zur Skriptsteuerung verwenden kann.

Beispiel 23-1. Ein für die Skriptsteuerung geeignetes Applet

    import java.applet.Applet;
    import java.net.URL;
    import java.io.*;

    public class GetTextApplet extends Applet {
      public String getText(String url)
        throws java.net.MalformedURLException, java.io.IOException
      {
        URL resource = new URL(this.getDocumentBase( ), url);
        InputStream is = resource.openStream( );
        BufferedReader in = new BufferedReader(new InputStreamReader(is));
        StringBuilder text = new StringBuilder( );
        String line;
        while((line = in.readLine( )) != null) {
          text.append(line);
          text.append("\n");
        }
        in.close( );
        return text.toString( );
      }
    }

# 23.2 Skriptsteuerung des Java-Plug-ins

Zusätzlich zur Skriptsteuerung von Applets können Firefox und ähnliche Browser das Java-Plug-in direkt steuern, ohne dazu ein Applet zu benötigen. Die LiveConnect-Technologie lässt JavaScript-Code in diesen Browsern Java-Objekte instanziieren und verwenden, auch wenn kein Applet vorhanden ist. Diese Technik ist allerdings nicht portabel und läuft beispielsweise nicht unter Internet Explorer.

In Browsern, die diese Steuerungsmöglichkeit unterstützen, bietet das `Packages`-Objekt zu allen Java-Paketen Zugang, die der Browser kennt. Der Ausdruck `Packages.java.lang` bezieht sich auf das _java.lang_ -Packet, und der Ausdruck `Packages.java.lang.System` bezieht sich auf die _java.lang.System_ -Klasse. Der Einfachheit halber ist `java` eine Abkürzung für `Packages.java` (siehe die Einträge Packages und Core DOMjava in Teil III). JavaScript-Code kann also eine statische Methode dieser _java.lang.System_ -Klasse wie folgt aufrufen:

    // Rufe die statische Java-Methode System.getProperty( ) auf
    var javaVersion = java.lang.System.getProperty("java.version");

Sie müssen sich aber nicht auf statische Methoden und vordefinierte Objekt beschränken: Mit LiveConnect können Sie über den JavaScript-Operator `new` neue Instanzen von Java-Klassen erzeugen. Beispiel 23-2 zeigt JavaScript-Code, der ein neues Java-Fenster erzeugt und darin eine Nachricht anzeigt. Sie sehen, dass dieser JavaScript-Code fast wie Java-Code aussieht. Der Code tauchte bereits in Kapitel 12 auf, aber hier ist er in ein `<script>`-Tag in einer HTML-Datei eingebettet. Abbildung 23-1 zeigt das Java-Fenster, das erzeugt wird, wenn das Skript unter Firefox ausgeführt wird.

Beispiel 23-2. Skriptsteuerung des Java-Plug-ins

    <script>
    // Definiere eine Abkürzung für die javax.* package-Hierarchie
    var javax = Packages.javax;

    // Erzeuge einige Java-Objekte
    var frame = new javax.swing.JFrame("Hello World");
    var button = new javax.swing.JButton("Hello World");
    var font = new java.awt.Font("SansSerif", java.awt.Font.BOLD, 24);

    // Rufe Methoden an den neuen Objekten auf
    frame.add(button);
    button.setFont(font);
    frame.setSize(300, 200);
    frame.setVisible(true);
    </script>

Abbildung 23-1. Ein Java-Fenster, das von JavaScript erzeugt wird

Beim Skriptsteuern von Java-Plug-ins unterliegen Ihre Skripten den gleichen Sicherheitsbeschränkungen wie Applets, denen nicht getraut wird. Beispielsweise kann ein JavaScript-Programm die Klasse _java.io.File_ nicht verwenden, weil es dadurch die Möglichkeit hätte, Dateien im Clientsystem zu lesen, zu schreiben und zu löschen.

# 23.3 Skriptsteuerung mit Java

Nachdem wir wissen, wie wir Java von JavaScript-Code aus kontrollieren, nehmen wir uns jetzt das umgekehrte Problem vor: wie wir JavaScript von einem Java-Applet aus kontrollieren. Alle Java-Kommunikationen mit JavaScript laufen über eine Instanz der Klasse `netscape.javascript.JSObject`. (Eine vollständige Dokumentation für JSObject befindet sich in Teil IV.) Eine Instanz dieser Klasse ist ein Wrapper um ein einzelnes JavaScript-Objekt. Die Klasse definiert Methoden, mit denen Sie Eigenschaftswerte und Array-Elemente des JavaScript-Objekts lesen und schreiben und Methoden des Objekts aufrufen können. Hier folgt ein Überblick über diese Klasse:

    public final class JSObject extends Object {
      // Diese statische Methode gibt ein erstes JSObject für das Browserfenster zurück
      public static JSObject getWindow(java.applet.Applet applet);

      // Diese Instanzmethoden bearbeiten das Objekt
      public Object getMember(String name);       // Lies Objekt-Eigenschaft
      public Object getSlot(int index);         // Lies Array-Element
      public void setMember(String name, Object value); // Setze Objekt-Eigenschaft
      public void setSlot(int index, Object value);   // Setze Array-Element

      public void removeMember(String name);       // Lösche Eigenschaft
      public Object call(String name, Object args[]);  // Rufe Methode auf
      public Object eval(String s);           // Evaluiere String
      public String toString( );              // Konvertiere in String
      protected void finalize( );
    }

Die JSObject-Klasse hat keinen Konstruktor. Ein Java-Applet erhält sein erstes JSObject mit der statischen Methode `getWindow( )`. Wenn eine Referenz an ein Applet übergeben wird, gibt diese Methode ein JSObject zurück, das das Browserfenster repräsentiert, in dem das Applet erscheint. Dadurch enthält jedes mit JavaScript kommunizierende Applet eine Zeile, die ungefähr wie folgt aussieht:

    JSObject win = JSObject.getWindow(this); // "this" ist das Applet selbst

Nachdem Sie sich ein JSObject beschafft haben, das auf das Window-Objekt verweist, können Sie die Instanzmethode dieses ersten JSObjects verwenden, um andere JSObjects zu erhalten, die weitere JavaScript-Objekte darstellen:

    import netscape.javascript.JSObject; // Das muss ganz oben in der Datei stehen
    ...
    // Hole das erste JSObject, das das Fenster darstellt
    JSObject win = JSObject.getWindow(this);       // Fenster
    // Hole über getMember( ) ein JSObject, das das Document-Objekt repräsentiert
    JSObject doc = (JSObject)win.getMember("document"); // document
    // Hole ein JSObject über call( ), das ein Element des Dokuments repräsentiert
    JSObject div = (JSObject)doc.call("getElementById", // .getElementById('test')
                     new Object[] { "test" });

Beachten Sie, dass sowohl `getMember( )` als auch `call( )` einen Wert vom Typ Object zurückgeben, der normalerweise in einen spezielleren Typ konvertiert werden muss, wie z.B. in ein JSObject. Weiterhin sollten Sie beachten, dass Sie beim Aufruf einer JavaScript-Methode mit `call( )` Argumente als ein Array von Java-Objektwerten übergeben. Dieses Array ist selbst dann erforderlich, wenn die von Ihnen aufgerufene Methode nur ein oder gar kein Argument erwartet.

Die JSObject-Klasse hat noch eine weitere wichtige Methode: `eval( )`. Diese Java-Methode funktioniert genauso wie die JavaScript-Funktion gleichen Namens: Sie führt einen String aus, der JavaScript-Code enthält. Wie Sie sehen werden, ist die Verwendung von `eval( )` oft viel einfacher als die der anderen Methoden der JSObject-Klasse. Betrachten Sie beispielsweise die folgende Anwendung von `eval( )`, mit der ein CSS-Stil eines Dokumentelements gesetzt wird:

    JSObject win = JSObject.getWindow(this);
    win.eval("document.getElementById('test').style.backgroundColor = 'gray';");

Um das Gleiche ohne die `eval( )`-Methode zu machen, brauchen wir mehr Code:

    JSObject win = JSObject.getWindow(this);       // Fenster
    JSObject doc = (JSObject)win.getMember("document"); // document
    JSObject div = (JSObject)doc.call("getElementById", // .getElementById('test')
                     new Object[] { "test" });
    JSObject style = (JSObject)div.getMember("style");  // .style
    style.setMember("backgroundColor", "gray");     // .backgroundColor="gray"

## 23.3.1 Applets, die JSObject verwenden, kompilieren und einsetzen

Um ein Applet einzusetzen, müssen Sie es kompilieren und dann in eine HTML-Datei einbetten. Verwendet ein Applet die JSObject-Klasse, müssen diese beiden Schritte besonders sorgfältig durchgeführt werden.

Um ein Applet zu kompilieren, das mit JavaScript kommuniziert, müssen Sie dem Compiler mitteilen, wo er die Klasse `netscape.javascript.JSObject` finden kann. Als Browser noch mit ihren eigenen, integrierten Java-Interpretern geliefert wurden, war das kompliziert. Heute verwenden alle Broser das Java-Plug-in von Sun, und alles ist viel einfacher. Die JSObject-Klasse befindet sich in der Datei _jre/lib/plugin.jar_ der Java-Distribution. Um also ein Applet zu kompilieren, das JSObject verwendet, verwenden Sie den folgenden Befehl und ersetzen das Java-Installationsverzeichnis durch das entsprechende Verzeichnis auf Ihrem System:

    % **javac -cp /usr/local/java/jre/lib/plugin.jar ScriptedApplet.java**

Es gibt noch eine weitere Voraussetzung, die erfüllt werden muss, um ein Applet laufen zu lassen, das mit JavaScript interagiert. Aus Sicherheitsgründen darf das Applet kein JavaScript verwenden, es sei denn, der Autor der Webseite (der nicht unbedingt der Autor des Applets sein muss) erlaubt das ausdrücklich. Diese Erlaubnis wird gegeben, indem Sie das Attribut `mayscript` in das Applet-Tag `<applet>` (oder `<object>` oder `<embed>`) einfügen. Ein Beispiel:

    <applet code="ScriptingApplet.class" mayscript width="300px" height="300px">
    </applet>

Wenn Sie das `mayscript`-Attribut vergessen, hat das Applet keine Genehmigung zur Verwendung der JSObject-Klasse.

## 23.3.2 Daten von Java nach JavaScript konvertieren

Beim Aufruf einer Methode oder Setzen eines Feldes muss die JSObject-Klasse die entsprechenden Java-Werte in JavaScript-Werte konvertieren. Bei der Rückgabe einer Methode oder beim Lesen eines Feldes muss sie JavaScript-Werte in Java-Werte zurückkonvertieren. Die von JSObject durchgeführten Konvertierungen unterscheiden sich von den in Kapitel 12 dokumentierten LiveConnect-Konvertierungen. Leider sind die von JSObject durchgeführten Konvertierungen stärker von der Plattform abhängig als die Konvertierung, bei der JavaScript Java steuert.

Wenn Java einen JavaScript-Wert liest, sind die Konvertierungen unkompliziert:

  * JavaScript-Zahlen werden in `java.lang.Double` konvertiert.

  * JavaScript-Strings werden in `java.lang.String` konvertiert.

  * Boolesche Werte aus JavaScript werden in `java.lang.Boolean` konvertiert.

  * Der JavaScript-Wert `null` wird zum Java-Wert `null` konvertiert.

  * Der JavaScript-Wert `undefined` wird plattformabhängig konvertiert: Mit dem Java-5-Plug-in wird `undefined` in Internet Explorer zu `null` konvertiert, unter Firefox aber in den String »undefined«.

Wenn Java eine JavaScript-Eigenschaft setzt oder ein Argument an eine JavaScript-Methode übergibt, sollten die Konvertierungen eigentlich genauso unkompliziert sein. Leider sind diese aber plattformabhängig. Bei Firefox 1.0 mit dem Java-5-Plug-in werden Java-Werte nicht konvertiert, und JavaScript betrachtet sie als JavaObject-Objekte (mit denen die Sprache über LiveConnect kommunizieren kann):

Unter IE 6 mit dem Java-5-Plug-in ist die Konvertierung sinnvoller:

  * Java-Zahlen und -Zeichen werden zu JavaScript-Zahlen konvertiert.

  * Java-String-Objekte werden zu JavaScript-Strings konvertiert.

  * Boolesche Werte in Java werden zu Booleschen Werten in JavaScript konvertiert.

  * Der `null`-Wert in Java wird zum `null`-Wert in JavaScript konvertiert.

  * Alle anderen Java-Werte werden in JavaObjects konvertiert.

Aufgrund dieses Unterschieds zwischen Firefox und IE sollten Sie darauf achten, die Werte korrekt in Ihren JavaScript-Code zu konvertieren. Wenn Sie beispielsweise eine JavaScript-Funktion schreiben, die von einem Applet aufgerufen werden wird, sollten Sie die Argumente ausdrücklich mit den Konvertierungsfunktionen `Number( )`, `String( )` und `Boolean( )` konvertieren, bevor Sie sie verwenden.

Eine Möglichkeit, den ganzen Aufwand der Datenkonvertierung zu umgehen, besteht darin, immer die `JSObject.eval( )`-Methode zu verwenden, wenn Ihr Java-Code mit JavaScript kommunizieren möchte.

## 23.3.3 Die Common DOM API

In Java 1.4 und höher enthält das Java-Plug-in die Common DOM API. Dabei handelt es sich um eine Java-Implementierung des DOM Level 2 auf Basis der `netscape.javascript.JSObject`-Klasse. Mit ihr können Java-Applets über die Java-Anbindung des DOM mit dem Dokument kommunizieren, in das sie eingebettet sind.

Das ist zwar eine gute Idee, ihre Umsetzung lässt aber leider einiges zu wünschen übrig. Ein ernsthaftes Problem ist, dass die Implementierung (sowohl unter IE als auch Firefox) unfähig zu sein scheint, einen neuen Textknoten zu erzeugen oder auf einen bereits bestehenden Textknoten zuzugreifen, wodurch die Common DOM API zum Abfragen oder Ändern des Dokumentinhalts unbrauchbar wird. Allerdings werden manche DOM-Manipulationen unterstützt. Beispiel 23-3 zeigt, wie ein Applet die Common DOM API verwenden kann, um einen CSS-Style an einem HTML-Element zu setzen.

Bei diesem Beispiel müssen Sie auf ein paar Dinge achten. Erstens ist die zur Manipulation des DOMs verwendete API etwas ungewöhnlich. Sie platzieren Ihren DOM-Code in der `run( )`-Methode eines DOMAction-Objekts. Dann übergeben Sie dieses DOMAction-Objekt an eine Methode eines DOMService-Objekts. Wenn Ihre `run( )`-Methode aufgerufen wird, wird sie an ein DOMAccessor-Objekt übergeben, mit dem Sie das Document-Objekt holen können, das das Wurzelelement der DOM-Objekt-Hierarchie ist.

Zweitens werden Sie bei Beispiel 23-3 feststellen, dass die Java-Anbindung der DOM API um einiges langatmiger und umständlicher ist als die JavaScript-Anbindung der gleichen API. Schließlich sollten Sie beachten, dass die in diesem Beispiel ausgeführte Aufgabe einfacher durch die Übergabe einer Zeile mit JavaScript-Code an die Methode `JSObject.eval( )` erreicht werden könnte

Der Code in Beispiel 23-3 verwendet die JSObject-Klasse nicht ausdrücklich und kompiliert ohne Zusätze zum Klassenpfad.

Beispiel 23-3. Ein Applet, das die Common DOM API verwendet

    import java.applet.Applet;     // Die Applet-Klasse selbst
    import com.sun.java.browser.dom.*; // Die Common DOM API
    import org.w3c.dom.*;        // Die W3C Core DOM API
    import org.w3c.dom.css.*;      // Die W3C CSS DOM API

    // Dieses Applet allein hat keine Funktion. Es definiert einfach eine Methode zum
    // Aufruf von JavaScript-Code. Diese Methode verwendet dann die Common DOM API zum
    // Manipulieren des Dokuments, in das das Applet eingebettet ist.
    public class DOMApplet extends Applet {
      // Setze den Hintergrund des Elements mit der angegebenen id auf die
      // angegebene Farbe.
      public void setBackgroundColor(final String id, final String color)
        throws DOMUnsupportedException, DOMAccessException
      {
        // Wir beginnen mit einem DOMService-Objekt, das wir uns so holen:
        DOMService service = DOMService.getService(this);

        // Dann rufen wir am DOMService invokeAndWait( ) auf und übergeben eine DOMAction
        service.invokeAndWait(new DOMAction( ) {
            // Der auszuführende DOM-Code befindet sich in der run( )-Methode
            public Object run(DOMAccessor accessor) {
              // Mit dem DOMAccessor kommen wir an das Document-Objekt
              // Beachten Sie, dass wir das Applet-Objekt als Argument übergeben
              Document d = accessor.getDocument(DOMApplet.this);

              // Hole das zu bearbeitende Element
              Element e = d.getElementById(id);

              // Mache das Element zu einem ElementCSSInlineStyle, damit wir
              // dessen getStyle( )-Methode aufrufen können, und konvertiere den
              // Rückgabewert dieser Methode in ein CSS2Properties-Objekt.
              CSS2Properties style =
                (CSS2Properties) ((ElementCSSInlineStyle)e).getStyle( );

              // Jetzt können wir die Eigenschaft backgroundColor setzen
              style.setBackgroundColor(color);

              // Eine DOMAction kann einen Wert zurückgeben, tut es aber hier nicht
              return null;
            }
          });
      }
    }

# 23.4 Flash skripten

Nachdem Sie gelernt haben, wie JavaScript Java per Skript steuern kann und umgekehrt, sehen wir uns nun den Flash Player und das Skripten von Flash-Movies an. Die anschließenden Unterabschnitte erläutern mehrere unterschiedliche Ebenen des Flash-Scriptings. Zunächst kann JavaScript verwendet werden, um den Flash-Player selbst zu steuern: Movies laufen lassen und abbrechen, auf bestimmte Frames springen usw. Eine interessantere Art des Skriptens ruft sogar ActionScript-Funktionen auf, die in einem Flash-Movie definiert werden. Dieser Abschnitt zeigt die Tricks (vor Flash 8), mit denen man das erreicht.

Als Nächstes sind die Rollen vertauscht, und Sie sehen, wie ActionScript-Code mit JavaScript kommunizieren kann. Anschließend enthält "23.4.5 Beispiel: von Flash zu JavaScript und zurück zu Flash" ein zweiteiliges Beispiel (den JavaScript-Code und den ActionScript-Code), das eine bidirektionale Kommunikation zwischen JavaScript und ActionScript vorführt. "23.5 Flash 8 skripten" zeigt das gleiche einfache Beispiel noch einmal, diesmal aber mit Hilfe der Möglichkeiten von Flash 8 neu geschrieben und vereinfacht.

Flash bietet mehr als nur ActionScript, und die meisten Entwickler von Flash-Inhalten verwenden eine kommerzielle Flash-Entwicklungsumgebung von Adobe. Alle Flash-Beispiele in diesem Kapitel enthalten lediglich ActionScript-Code, der in SWF-Dateien (z.B. Flash-Movies) konvertiert werden kann, beispielsweise mit einem Tool wie dem Open Source ActionScript-Compiler _mtasc_ (siehe <http://www.mtasc.org>). Die Beispiel-Movies enthalten keine eingebetteten Medien, können aber ohne eine teure Entwicklungsumgebung kompiliert werden.

Dieses Kapitel versucht nicht, Ihnen die Programmiersprache ActionScript oder die API der Bibliotheken zu vermitteln, die den Flash-Playern zur Verfügung stehen. Im Netz gibt es viele Materialien, die Ihnen beim Lernen von ActionScript und der Flash-API helfen. Besonders hilfreich kann das ActionScript Dictionary sein.[86]

## 23.4.1 Flash-Movies einbetten und auf sie zugreifen

Bevor ein Flash-Movie skriptgesteuert werden kann, muss es in eine HTML-Seite eingebettet werden, damit Ihr JavaScript-Code auf das Movie zugreifen kann. Das einzige Problem besteht hier in Browser-Inkompatibilitäten: IE braucht ein `<object>`-Tag mit bestimmten Attributen, andere Browser wollen ein `<object>`-Tag mit anderen Attributen oder ein `<embed>`-Tag. Das Standard-HTML-Tag ist `<object>`, aber die hier beschriebene Skripttechnik für Flash-nach-JavaScript scheint ein `<embed>`-Tag zu erwarten.

In diesem Fall beruht die Lösung auf IE-spezifischem HTML mit bedingten Kommentaren, mit denen das `<object>`-Tag vor allen Browsern außer IE und das `<embed>`-Tag vor IE versteckt wird. Hier sehen Sie, wie das Flash-Movie _meinfilm.swf_ eingebettet und mit dem Namen »film« versehen wird:

    <!--[if IE]>
    <object id="movie" type="application/x-shockwave-flash"
        width="300" height="300">
     <param name="film" value="meinfilm.swf">
    </object>
    <![endif]--><!--[if !IE]> <-->
    <embed name="film" type="application/x-shockwave-flash"
        src="meinfilm.swf" width="300" height="300">
    </embed>
    <!--> <![endif]-->

Das `<object>`-Tag hat ein `id`-Attribut, und das `<embed>`-Tag hat ein `name`-Attribut. Das ist eine übliche Vorgehensweise und bedeutet, dass Sie mit dem folgenden browserübergreifenden Codefragment auf das einbettende Element verweisen können:

    // Hole das Flash-Movie unter IE über Window und unter anderen Browsern über Document
    var flash = window.movie || document.movie; // Hole das Flash-Objekt

Damit die Flash-nach-JavaScript-Skriptsteuerung einwandfrei läuft, muss das `<embed>`-Tag ein `name`-Attribut haben. Wenn Sie möchten, können Sie ihm auch eine `id` geben. Damit lässt sich `getElementById( )` browserübergreifend zur Lokalisierung des `<object>`\- oder `<embed>`-Tags verwenden.

## 23.4.2 Den Flash-Player steuern

Jetzt haben wir das Flash-Movie in Ihre HTML-Seite eingebettet und JavaScript verwendet, um eine Referenz auf das HTML-Element zu erhalten, das das Movie zur Einbettung enthält. Jetzt können wir den Flash-Player per Skript steuern, indem wir einfach Methoden dieses Elements aufrufen. Beispielsweise lassen sich die folgenden Dinge über diese Methoden machen:

    var flash = window.movie || document.movie; // Hole das Movie-Objekt
    if (flash.IsPlaying( )) {           // Wenn das Movie läuft,
      flash.StopPlay( );            // halte es an,
      flash.Rewind( );             // und spule es zurück
    }
    if (flash.PercentLoaded( ) == 100)      // Ist es vollständig geladen,
      flash.Play( );              // dann lasse es laufen.
    flash.Zoom(50);               // Zoome 50% heran
    flash.Pan(25, 25, 1);            // Verschiebe es um 25% nach unten rechts
    flash.Pan(100, 0, 0);            // Verschiebe 100 Pixel nach rechts

Diese Flash-Player-Methoden sind in Teil IV unter FlashPlayer dokumentiert, und einige werden in Beispiel 23-5 vorgeführt.

## 23.4.3 Flash-Movies skripten

Interessanter als das Steuern des Flash-Players ist das Aufrufen von ActionScript-Methoden, die im Movie selbst definiert sind. Mit Java-Applets geht das ganz leicht: Rufen Sie einfach die gewünschten Methoden so auf, als wären sie die Methoden des HTML-`<applet>`-Tags. Unter Flash 8 geht das genauso einfach. Wenn Sie allerdings auf Benutzer abzielen, die diese Player-Version vielleicht noch nicht haben, müssen Sie ein paar Hürden mehr nehmen.

Eine der grundlegenden Methoden zum Skripten des Flash-Players heißt `SetVariable( )`. Eine andere hat den Namen `GetVariable( )`. Sie lassen sich zum Setzen und Auslesen der Werte von ActionScript-Variablen verwenden. Es gibt keine `InvokeFunction( )`-Methode, aber Sie können eine ActionScript-Erweiterung von JavaScript ausnutzen, um mit `SetVariable( )` einen Funktionsaufruf auszulösen. Unter ActionScript hat jedes Objekt eine `watch( )`-Methode, die einen _Watchpoint_ im Debugging-Stil setzen kann: Jedes Mal, wenn sich der Wert einer angegebenen Eigenschaft des Objekts ändert, wird eine angegebene Funktion aufgerufen. Schauen Sie sich diesen ActionScript-Code an:

    /* ActionScript */
    // Definiere Variablen für Funktionsargumente und Rückgabewert
    // _root bezieht sich auf die Root-Timeline des Movies. SetVariable( ) und
    // GetVariable( ) können Eigenschaften dieses Objekts setzen und lesen.
    _root.arg1 = 0;
    _root.arg2 = 0;
    _root.result = 0;
    // Diese Variable wird definiert, um einen Funktionsaufruf auszulösen
    _root.multiply = 0;
    // Verwenden Sie jetzt Object.watch( ), um eine Funktion aufzurufen, wenn sich der Wert
    // der "multiply"-Eigenschaft ändert. Beachten Sie, dass wir die Typenkonvertierung
    // der Argumente explizit verwenden
    _root.watch("multiply", function( ) {
            _root.result = Number(_root.arg1) * Number(_root.arg2);
            // Dieser Rückgabewert wird zum neuen Wert der Eigenschaft.
            return 0;
          });

Um ein Beispiel zu formulieren: Nehmen wir einfach einmal an, dass das Flash-Player-Plug-in Multiplikationen viel besser durchführen kann als der JavaScript-Interpreter. So rufen Sie den ActionScript-Multiplikationscode über JavaScript auf:

    /* JavaScript */
    // Weise das Flash-Movie an, zwei Zahlen zu multiplizieren
    function multiply(x, y) {
      var flash = window.movie || document.movie; // Hole das Flash-Objekt
      flash.SetVariable("arg1", x);        // Setze das erste Argument
      flash.SetVariable("arg2", y);        // Setze das zweite Argument
      flash.SetVariable("multiply", 1);      // Triggere den Multiplikationscode
      var result = flash.GetVariable("result");  // Rufe das Ergebnis ab
      return Number(result);           // Konvertiere es, und gib es aus.

## 23.4.4 JavaScript von Flash aus aufrufen

ActionScript kann über eine Funktion namens `fscommand( )` mit JavaScript kommunizieren, der es zwei Strings übergibt:

    fscommand("eval", "alert('Hallo von Flash')");

Die Argumente von `fscommand( )` tragen zwar die Namen _`kommando`_ und _`argumente`_ , müssen aber keinen Befehl und keine Argumente repräsentieren: Sie dürfen zwei beliebige Strings sein.

Wenn der ActionScript-Code `fscommand( )` aufruft, werden die zwei Strings an eine spezielle JavaScript-Funktion übergeben, die als Antwort auf _`kommando`_ eine beliebige Aktion durchführen kann. Beachten Sie, dass der JavaScript-Rückgabewert nicht an ActionScript zurückgegeben wird. Der von `fscommand( )` aufgerufene Name der JavaScript-Funktion hängt von dem `id`\- oder `name`-Argument ab, das beim Einbetten des Movies zusammen mit dem `<object>`\- oder `<embed>`-Tag verwendet wurde. Wenn Ihr Movie »film« heißt, müssen Sie eine JavaScript-Funktion mit dem Namen `film_DoFSCommand` definieren. Das folgende Beispiel kann den `fscommand( )`-Aufruf verarbeiten:

    function film_DoFSCommand(kommando, argumente) {
        if (kommando == "eval") eval(argumente);
    }

Das ist recht unkompliziert, funktioniert aber nicht unter Internet Explorer. Aus unerklärlichen Gründen kann im IE-Browser eingebettetes Flash nicht direkt mit JavaScript kommunizieren. Das ist jedoch über die Microsoft-eigene Skriptsprache VBScript möglich. Und VBScript kann mit JavaScript kommunizieren. Um also `fscommand( )` unter IE zum Laufen zu bringen, müssen Sie zusätzlich das folgende Codefragment in Ihre HTML-Datei aufnehmen, das auch davon ausgeht, dass das Movie »film« heißt:

    <script language="VBScript">
    sub film_FSCommand(byval kommando, byval argumente)
     call film_DoFSCommand(kommando, argumente) ' Einfach die JavaScript-Version aufrufen
    end sub
    </script>

Niemand erwartet, dass Sie diesen Code verstehen; binden Sie ihn einfach in Ihre HTML-Datei ein. Browser, die VBScript nicht verstehen, ignorieren dieses `<script>`-Tag und seinen Inhalt einfach.

## 23.4.5 Beispiel: von Flash zu JavaScript und zurück zu Flash

Jetzt setzen wir diese ganzen Teile in einem zweiteiligen Beispiel zusammen, das aus aus einer Datei mit ActionScript-Code (Beispiel 23-4) und einer Datei mit HTML- und JavaScript-Code (Beispiel 23-5) besteht. Beim Laden des Flash-Movies verwendet es `fscommand( )`, um JavaScript Bescheid zu geben, das daraufhin einen Formular-Button aktiviert. Wenn der Formular-Button gedrückt wird, verwendet JavaScript die `SetVariable( )`-Technik, um das Flash-Movie zum Zeichnen eines Dreiecks aufzufordern. Wenn der Benutzer die Maus innerhalb des Flash-Movies anklickt, verwendet es `fscommand( )`, um JavaScript die Mausposition mitzuteilen. Beide Beispiele sind ausführlich dokumentiert und sollten leicht verständlich sein.

Beispiel 23-4. ActionScript-Code, der unter JavaScript funktioniert

    /**
     * Box.as: ActionScript-Code zur Demonstration der
     * JavaScript <-> Flash-Kommunikation
     *
     * Diese Datei ist in ActionScript 2.0 geschrieben, einer Sprache, die auf
     * JavaScript beruht, aber zur objektorientierteren Programmierung erweitert wurde.
     * Wir definieren hier nur eine einfache, statische Funktion namens main( )
     * in einer Klasse mit dem Namen Box.
     *
     * Dieser Code kann mit dem Open Source-ActionScript-Compiler mtasc
     * kompiliert werden, und zwar mit folgendem Befehl:
     *
     *  mtasc -header 300:300:1 -main -swf Box1.swf Box1.as
     *
     * mtasc erzeugt eine SWF-Datei, die die main( )-Methode des ersten
     * Movie-Frames aufruft. Wenn Sie das Flash-IDE verwenden, müssen Sie Ihren eigenen
     * Aufruf an Box.main( ) in das erste Frame einfügen.
     */
    class Box {
      static function main( ) {
        // Eine ActionScript-Funktion, die wir von JavaScript aus nutzen wollen.
        // Sie zeichnet ein Quadrat und gibt seine Fläche zurück.
        var drawBox = function(x,y,b,h) {
          _root.beginFill(0xaaaaaa, 100);
          _root.lineStyle(5, 0x000000, 100);

          _root.moveTo(x,y);
          _root.lineTo(x+b, y);
          _root.lineTo(x+b, y+h);
          _root.lineTo(x, b+h);
          _root.lineTo(x,y);
          _root.endFill( );
          return b*h;
        }

        // So können wir der Funktion erlauben, von JavaScript aufgerufen zu werden
        // (vor Flash 8). Erst definieren wir in unserer Root-Timeline Eigenschaften,
        // um die Funktionsargumente und den Rückgabewert zu speichern.
        _root.arg1 = 0;
        _root.arg2 = 0;
        _root.arg3 = 0;
        _root.arg4 = 0;
        _root.result = 0;

        // Dann definieren wir noch eine Eigenschaft mit dem gleichen Namen wie die Funktion.
        _root.drawBox = 0;

        // Jetzt "beobachten" wir die Eigenschaft mit der Object.watch( )-Methode.
        // Wenn sie gesetzt wird, ruft das die von uns angegebene Funktion auf.
        // Das bedeutet: Der JavaScript-Code kann mit SetVariable einen
        // Funktionsaufruf auslösen.
        _root.watch("drawBox",  // Der Name der zu beobachtenden Eigenschaft
              function( ) { // Die bei Eigenschaftsänderung aufzurufende Funktion
                // Rufe die drawBox( )-Funktion auf, und konvertiere die
                // Argumente von Strings nach Zahlen, und speichere
                // den Rückgabewert.
                _root.result = drawBox(Number(_root.arg1),
                            Number(_root.arg2),
                            Number(_root.arg3),
                            Number(_root.arg4));

                // Gib 0 zurück, damit sich der Wert der von uns
                // beobachteten Eigenschaft nicht ändert.
                return 0;
              });

        // Das ist ein ActionScript-Event-Handler.
        // Er ruft die fscommand( )-Funktion auf, um die
        // Koordinaten eines Mausklicks an JavaScript weiterzuleiten.
        _root.onMouseDown = function( ) {
          fscommand("mousedown", _root._xmouse + "," + _root._ymouse);
        }

        // Hier verwenden wir fscommand( ) noch einmal, um JavaScript mitzuteilen,
        // dass das Flash-Movie geladen und bereit ist.
        fscommand("loaded", "");
      }
    }

Beispiel 23-5. Skriptsteuerung eines Flash-Movies

    <!--
     Das ist ein in unsere Webseite eingebettetes Flash-Movie.
     Wie üblich verwenden wir <object id=""> unter IE und
     <embed name=""> für andere Browser.
     Beachten Sie die IE-spezifischen bedingten Kommentare.
    -->
    <!--[wenn IE]>
    <object id="movie" type="application/x-shockwave-flash"
        width="300" height="300">
     <param name="film" value="Box1.swf">
    </object>
    <![endif]--><!--[if !IE]> <-->
    <embed name="film" type="application/x-shockwave-flash"
        src="Box1.swf" width="300" height="300">
    </embed>
    <!--> <![endif]-->

    <!--
     Dieses HTML-Formular enthält Buttons, die das Movie oder den Player per Skript steuern.
     Beachten Sie, dass der Zeichnen-Button zunächst deaktiviert ist. Wenn das Flash-Movie  geladen ist,
     sendet es einen Befehl an JavaScript. JavaScript
     aktiviert dann den Button.
    -->
    <form name="f" onsubmit="return false;">
    <button name="button" onclick="draw( )" disabled>Zeichnen</button>
    <button onclick="zoom( )">Zoom</button>
    <button onclick="pan( )">Verschieben</button>
    </form>

    <script>
    // Diese Funktion zeigt, wie Flash auf die harte Tour aufgerufen wird.
    function draw( ) {
      // Zuerst beschaffen wir uns das Flash-Objekt. Da wir "film" als id
      // und Name der <object>- und <embed>-Tags verwenden, steht das Objekt
      // in einer Eigenschaft mit dem Namen "film". Unter IE ist sie eine window-Eigenschaft
      // und unter anderen Browsern eine document-Eigenschaft.
      var flash = window.movie || document.movie; // Hole das Flash-Objekt.

      // Sicherstellen, dass das Movie vor der Skriptsteuerung vollständig geladen ist.
      // Das ist nur zu Veranschaulichung: Es ist überflüssig, da wir
      // den Button, der diese Methode aufruft, so lange deaktivieren,
      // bis Flash uns über das vollständige Laden informiert
      if (flash.PercentLoaded( ) != 100) return;

      // Als Nächstes "übergeben" wir Funktionsargumente, indem wir sie einzeln setzen.
      flash.SetVariable("arg1", 10);
      flash.SetVariable("arg2", 10);
      flash.SetVariable("arg3", 50);
      flash.SetVariable("arg4", 50);

      // Jetzt lösen wir die Funktion aus, indem wir noch eine Eigenschaft setzen.
      flash.SetVariable("drawBox", 1);

      // Schließlich fordern wir den Rückgabewert der Funktion an.
      return flash.GetVariable("result");
    }

    function zoom( ) {
      var flash = window.movie || document.movie; // Hole Flash-Objekt.
      flash.Zoom(50);
    }

    function pan( ) {
      var flash = window.movie || document.movie; // Hole Flash-Objekt.
      flash.Pan(-50, −50, 1);
    }

    // Diese Funktion wird aufgerufen, wenn Flash fscommand( ) aufruft.
    // Die zwei Argumente sind Strings, die Flash übergibt.
    // Diese Funktion muss genau diesen Namen haben, sonst wird sie nicht aufgerufen.
    // Der Funktionsname beginnt mit "film", weil das die id bzw. der Name der
    // vorher verwendeten <object>- und <embed>-Tags ist.
    function film_DoFSCommand(kommando, argumente) {
      if (kommando=="loaded") {
        // Wenn Flash uns das vollständige Laden anzeigt, können wir den Formular-    // Button aktivieren.
        document.f.button.disabled = false;
      }
      else if (kommando == "mousedown") {
        // Flash sagt uns, wenn der Benutzer die Maus anklickt.
        // Flash kann uns nur Strings übergeben. Wir müssen Sie nach Bedarf parsen,
        // um an die Daten zu kommen, die Flash uns sendet.
        var coords = argumente.split(",");
        alert("Mousedown: (" + coords[0] + ", " + coords[1] + ")");
      }
      // Das hier sind ein paar andere nützliche Befehle.
      else if (kommando == "debug") alert("Flash-Debug: " + argumente);
      else if (kommando == "eval") eval(argumente);
    }
    </script>

    <script language="VBScript">
    ' Dieses Skript ist nicht in JavaScript, sondern in der
    ' Visual Basic Scripting Edition von Microsoft geschrieben. Dieses Skript ist unter Internet ' Explorer notwendig, um fscommand( )-Benachrichtigungen von Flash zu erhalten. Unter allen anderen
    ' Browsern wird es ignoriert, da sie VBScript nicht unterstützen.
    ' Der Name dieser VBScript-Subroutine muss genau so sein, wie hier gezeigt.
    sub film_FSCommand(byval kommando, byval argumente)
     call film_DoFSCommand(kommando, argumente) ' Einfach die JavaScript-Version aufrufen
    end sub
    </script>

* * *

[86] Beim Schreiben dieses Buchs stand das Dictionary unter <http://www.adobe.com/support/flash/action_scripts/actionscript_dictionary/>.

# 23.5 Flash 8 skripten

Die Version 8 des Flash-Players enthält eine Klasse namens ExternalInterface, die das Skripten von Flash revolutioniert, indem sie die JavaScript-nach-Flash-Kommunikation und Flash-nach-JavaScript-Kommunikation außerordentlich vereinfacht. ExternalInterface definiert eine statische `call( )`-Funktion zum Aufruf angegebener JavaScript-Funktionen und zum Lesen ihrer Rückgabewerte. Sie definiert außerdem eine statische `addCallback( )`-Methode zum Export von ActionScript-Funktionen, die unter JavaScript verwendet werden sollen. ExternalInterface wird in Teil IV dokumentiert.

Um die Einfachheit der Skriptsteuerung mit ExternalInterface zu demonstrieren, konvertieren wir die Beispiel 23-4 und Beispiel 23-5 zur Verwendung mit ExternalInterface. Beispiel 23-6 zeigt den konvertierten ActionScript-Code und Beispiel 23-7 den konvertierten JavaScript-Code (die Tags `<object>`, `<embed>` und `<form>` werden aus Beispiel 23-5 übernommen und sind hier nicht aufgeführt).

Die Kommentare dieser Beispiele sollten Ihnen alles Nötige über die Verwendung von ExternalInterface sagen. Die Methode `ExternalInterface.addCallback( )` wird außerdem in Beispiel 22-12 angewendet.

Beispiel 23-6. ActionScript unter Verwendung von ExternalInterface

    /**
     * Box2.as: ActionScript-Code zur Darstellung der
     *     JavaScript <-> Flash-Kommunikation
     *     unter Verwendung der ExternalInterface-Klasse von Flash 8.
     *
     * Kompilieren Sie diesen Code, indem Sie mtasc mit einem
     * Befehl wie dem folgenden verwenden:
     *
     *  mtasc -version 8 -header 300:300:1 -main -swf Box2.swf Box2.as
     *
     * Wenn Sie stattdessen das Flash-IDE verwenden, fügen Sie den Aufruf von Box.main( )
     * in das erste Frame Ihres Movies ein.
     */
    import flash.external.ExternalInterface;
     class Box {
      static function main( ) {
        // Verwende das neue ExternalInterface für den Export unserer ActionScript-Funktion.
        // Das vereinfacht den Funktionsaufruf über JavaScript stark,
        // wird aber nur von Flash 8 und höher unterstützt.
        // Das erste Argument von addCallback ist der Name, unter dem die Funktion
        // in JavaScript bekannt ist. Das zweite Argument ist das
        // ActionScript-Objekt, an dem die Funktion aufgerufen wird. Das
        // ist der Wert des 'this'-Schlüsselworts. Das dritte Argument
        // ist die aufzurufende Funktion.
        ExternalInterface.addCallback("drawBox", null, function(x,y,b,h) {
                         _root.beginFill(0xaaaaaa, 100);
                         _root.lineStyle(5, 0x000000, 100);
                         _root.moveTo(x,y);
                         _root.lineTo(x+b, y);
                         _root.lineTo(x+b, y+h);
                         _root.lineTo(x, b+h);
                         _root.lineTo(x,y);
                         _root.endFill( );
                         return b*h;
                       });

        // Das ist ein ActionScript-Event-Handler.
        // Informiere JavaScript mittels ExternalInterface.call( ) über Mausklicks.
        _root.onMouseDown = function( ) {
          ExternalInterface.call("reportMouseClick",
                      _root._xmouse, _root._ymouse);
        }

        // Teile JavaScript mit, dass wir alles geladen haben und zum Skriptsteuern bereit
        // sind.
        ExternalInterface.call("flashReady");
      }
    }

Beispiel 23-7. Vereinfachte Flash-Skriptsteuerung mit ExternalInterface

    <script>
    // Wurde eine ActionScript-Funktion mit ExternalInterface.addCallback exportiert,
    // können wir sie ganz einfach als eine Methode des Flash-Objekts aufrufen.
    function draw( ) {
      var flash = window.movie || document.movie; // Hole das Flash-Objekt
      return flash.drawBox(100, 100, 100, 50);  // Rufe einfach eine Funktion an ihm auf
    }

    // Das sind Funktionen, die Flash über ExternalInterface.call( ) aufruft.
    function flashReady( ) { document.f.button.disabled = false; }
    function reportMouseClick(x, y) { alert("Klick: " + x + ", " + y); }
    </script>

# Teil III. Referenz für den JavaScript-Sprachkern

Dieser Teil des Buchs ist eine vollständige Referenz der Klassen, Eigenschaften, Funktionen und Methoden der JavaScript-Kern-API. Die ersten Seiten dieses Teils erklären, wie Sie das Referenzmaterial benutzen, und sie sind es wert, sorgfältig durchgelesen zu werden.

Die in diesem Teil dokumentierten Klassen und Objekte beinhalten:

`Arguments`
---
`Array`
`Boolean`
`Date`
`Error`
`Function`
`Global`
`JavaArray`
`JavaClass`
`JavaObject`
`JavaPackage`
`Math`
`Number`
`Object`
`RegExp`
`String`

# Referenz für den Sprachkern von JavaScript

Dieser Teil des Buchs ist eine Referenz der Klassen, Methoden und Eigenschaften, die im Kern von JavaScript definiert sind. Die Einleitung und der nachfolgende Beispiel-Referenzeintrag erklären, wie Sie diese Referenz benutzen und am besten davon profitieren können. Bitte nehmen Sie sich die Zeit, dieses Material sorgfältig durchzulesen, denn dann können Sie die benötigten Informationen später schneller auffinden.

Diese Referenz ist alphabetisch angelegt. Die Referenzeinträge für die Methoden und Eigenschaften von Klassen werden alphabetisch mit ihrem vollen Namen aufgeführt, zu dem auch die Klasse gehört, in der sie definiert sind. Wenn Sie z.B. etwas über die `replace( )`-Methode der Klasse String nachlesen möchten, müssen Sie unter `String.replace()` und nicht nur unter `replace` nachschlagen.

Der JavaScript-Sprachkern definiert einige globale Funktionen und Eigenschaften, z.B. `eval( )` und `NaN`. Technisch gesehen sind dies Eigenschaften des globalen Objekts. Da ein globales Objekt jedoch keinen Namen hat, werden sie in der Referenz unter ihren eigenen Namen aufgeführt. Der Bequemlichkeit halber werden alle globalen Funktionen und Eigenschaften des JavaScript-Sprachkerns in einem speziellen Referenzeintrag unter dem Namen »Global« zusammengefasst, obwohl es eigentlich kein Objekt und keine Klasse dieses Namens gibt.

Haben Sie erst den gesuchten Eintrag gefunden, so dürfte das Auffinden der benötigten Informationen keine Schwierigkeiten mehr bereiten. Doch Sie können den Referenzteil noch besser ausnutzen, wenn Sie wissen, wie die Einträge geschrieben und organisiert sind. Im Folgenden sehen Sie einen Beispiel-Referenzeintrag, der die Struktur der einzelnen Referenzeinträge veranschaulicht und Ihnen erklärt, wo Sie die verschiedenen Informationen finden können. Nehmen Sie sich bitte die Zeit, diesen Eintrag zu lesen, ehe Sie sich in den Rest des Referenzmaterials vertiefen.

## Name

Beispiel-Referenzeintrag — So werden diese Referenzeinträge gelesenVerfügbarkeitErbt von

## Titel und Kurzbeschreibung

Jeder Referenzeintrag beginnt mit einem vierteiligen Titelblock wie dem oben gezeigten. Die Einträge sind alphabetisch nach Titeln geordnet. Die Kurzbeschreibung unter dem Titel fasst das in dem betreffenden Eintrag dokumentierte Element kurz zusammen und kann Ihnen helfen, rasch zu entscheiden, ob der Rest des Eintrags für Sie interessant ist.

## Verfügbarkeit

Die Verfügbarkeitsinformationen werden in der rechten oberen Ecke des Titelblocks angezeigt. In früheren Versionen dieses Buchs hat diese Information Ihnen gesagt, welche Version welches Webbrowsers das dokumentierte Element unterstützt. Heutzutage unterstützen die meisten Browser die in diesem Buch dokumentierten Elemente. Deswegen sagt Ihnen der Verfügbarkeitsabschnitt eher, welcher Standard die formelle Spezifikation des Elements liefert. Hier könnten Sie beispielsweise »ECMAScript v1« oder »DOM Level 2 HTML« sehen. Wenn das Element veraltet ist, wird das ebenfalls hier festgehalten.

Gelegentlich tauchen hier auch Browsernamen und Versionsnummern auf, üblicherweise wenn das dokumentierte Element ein brandneues Feature ist, das noch nicht allgemein übernommen wurde, oder wenn ein Element ein Internet Explorer-spezifisches Feature ist.

Wenn ein Element (wie z.B. das History-Objekt) nie standardisiert wurde, von Browsern, die eine bestimmte JavaScript-Version unterstützen, aber umfassend unterstützt wird, kann in diesem Abschnitt auch die JavaScript-Versionsnummer stehen. Die Verfügbarkeit des History-Objekts ist beispielsweise »JavaScript 1.0«.

Wenn der Eintrag für eine Methode keine Verfügbarkeitsinformationen enthält, dann heißt das, dass sie die gleiche Verfügbarkeit hat wie die Klasse, die die Methode definiert.

## Erbt von

Wenn eine Klasse von einer Oberklasse erbt oder eine ihrer Methoden eine Oberklassenmethode überschreibt, finden Sie unten rechts im Titelblock entsprechende Hinweise.

Wie in Kapitel 9 bereits erläutert wurde, können JavaScript-Klassen Eigenschaften und Methoden anderer Klassen erben. Beispielsweise erbt die Klasse String von Object und HTMLDocument von Document, das seinerseits von Node erbt. Der Eintrag für String fasst diese Vererbung als »Object → String« zusammen, und der Eintrag für HTMLDocument sagt »Node → Document → HTMLDocument«. Wenn Sie solche Vererbungsinformationen finden, sollten Sie auch die angegebenen Oberklassen nachschlagen.

Hat eine Methode denselben Namen wie eine Oberklassenmethode, so überschreibt sie diese. Ein Beispiel finden Sie in `Array.toString( )`.

## Konstruktor

Wenn der Referenzeintrag eine Klasse dokumentiert und die Klasse einen Konstruktor-Abschnitt hat, zeigt dieser Abschnitt, wie Sie mit der Konstruktormethode Instanzen der Klasse erzeugen können. Da auch Konstruktoren eine Art von Methoden sind, gleicht der Konstruktor-Abschnitt dem Überblick in einem Methodenreferenzeintrag.

## Überblick

Referenzeinträge zu Funktionen, Methoden und Eigenschaften enthalten einen Überblick, der zeigt, wie Sie diese Funktion, Methode oder Eigenschaft in Ihrem Code benutzen. Die Referenzeinträge in diesem Buch verwenden zwei verschiedene Arten von Überblickzusammenfassungen. Die Einträge in der Kern-JavaScript-Referenz und die clientseitigen Einträge, die Methoden dokumentieren (wie die Window-Methoden), die keinen Bezug zum DOM haben, nutzen einen typlosen Überblick. So ist z.B. Folgendes der Überblick über die Methode `Array.concat( )`:

     _array_.concat( _wert_ , ...)

Die _`kursive`_ Schrift weist darauf hin, dass der betreffende Text durch etwas anderes ersetzt wird. _`array`_ sollte durch eine Variable, die ein Array enthält, oder einen JavaScript-Ausdruck ersetzt werden, dessen Wert ein Array ist. _`wert`_ ist einfach ein Name für ein Argument, das an das Array übergeben werden soll. Dieses benannte Argument wird später im Überblick beschrieben, und diese Beschreibung enthält Informationen zum Typ und zum Zweck des Arguments. Die Auslassungszeichen (`...`) deuten an, dass diese Methode beliebig viele _`wert`_ -Argumente entgegennimmt. Da der Begriff `concat` und die öffnende und schließende Klammer nicht kursiv geschrieben sind, müssen Sie sie genau so, wie sie gezeigt werden, in den JavaScript-Code übernehmen.

Viele der Methoden, die im clientseitigen JavaScript-Abschnitt dokumentiert werden, wurden vom W3C standardisiert, und ihre Spezifikationen enthalten definitive Typinformationen für Methodenargumente und Methodenrückgabewerte. Diese Einträge schließen diese Typinformationen in die Synopsis ein. Der Überblick für die Methode `Document.getElementById( )` ist beispielsweise:

    Element getElementById(String _elementId_ );

Dieser Überblick nutzt eine Java-artige Syntax, um anzuzeigen, dass die Methode `getElementById( )` ein Element-Objekt zurückliefert und ein Stringargument namens _`elementId`_ erwartet. Weil es sich um eine Methode von Document handelt, ist impliziert, dass die Methode auf einem Document-Objekt aufgerufen wird. Deswegen wird kein `document`-Präfix angegeben.

### Argumente

Wenn ein Referenzeintrag eine Funktion, Methode oder Klasse mit einer Konstruktormethode dokumentiert, steht hinter dem Abschnitt »Konstruktor« oder »Überblick« ein Unterabschnitt namens »Argumente«, der die Argumente der betreffenden Methode, Funktion oder Konstruktormethode beschreibt. Gibt es keine Argumente, so fällt dieser Unterabschnitt einfach weg.

 _`arg1`_

Die Argumente werden hier in einer Liste beschrieben. Dies ist z.B. die Beschreibung von _`arg1`_.

 _`arg2`_

Und dies ist die Beschreibung von Argument _`arg2`_.

## Rückgabewert

Wenn eine Funktion oder eine Methode einen Rückgabewert hat, wird er hier erklärt.

## Exceptions

Wenn ein Konstruktor, eine Funktion oder eine Methode eine Exception auslösen kann, steht hier, welche Typen von Exceptions unter welchen Umständen vorkommen können.

## Konstanten

Einige Klassen definieren einen Satz von Konstanten, die als Werte für eine Eigenschaft oder als Argument für eine Methode dienen. Das Interface Node definiert beispielsweise wichtige Konstanten, die als zulässige Werte für ihre `nodeType`-Eigenschaft dienen. Wenn ein Interface Konstanten definiert, werden sie in diesem Abschnitt aufgeführt und dokumentiert. Beachten Sie, dass Konstanten statische Eigenschaften der Klasse selbst und nicht von Instanzen der Klasse sind.

## Eigenschaften

Wenn der Referenzeintrag eine Klasse dokumentiert, finden Sie im Abschnitt »Eigenschaften« die Eigenschaften, die die Klasse definiert, zusammen mit einer kurzen Erklärung. In Teil III hat auch jede Eigenschaft einen ganzen eigenen Referenzeintrag. In Teil IV werden die meisten Eigenschaften in der Eigenschaftsliste vollständig beschrieben. Die wichtigsten oder komplexesten clientseitigen Eigenschaften haben eine eigene Seite, auf die hier verwiesen wird. In Teil IV schließen die Eigenschaften DOM-bezogener Klassen Typinformationen ein. Eigenschaften anderer Klassen und alle Eigenschaften in Teil III haben keine Typinformationen Die Eigenschaften werden folgendermaßen aufgeführt:

`eigenschaft1`

Dies ist eine Zusammenfassung der typlosen Eigenschaft `eigenschaft1`. Oben wird nur der Eigenschaftsname aufgeführt, aber die Beschreibung teilt Ihnen den Typ, den Zweck oder die Bedeutung der Eigenschaft mit und enthält Informationen dazu, ob sie schreibgeschützt ist oder nicht.

`readonly integer eigenschaft2`

Dies ist eine Zusammenfassung für die typisierte Eigenschaft `eigenschaft2`. Neben dem Eigenschaftsnamen wird auch der Typ aufgeführt. Der beschreibende Absatz erklärt den Zweck der Eigenschaft.

## Methoden

Der Referenzeintrag zu einer Klasse, die Methoden definiert, hat auch einen Abschnitt namens »Methoden«. Dieser gleicht dem Abschnitt »Eigenschaften«, nur dass hier Methoden anstelle von Eigenschaften angegeben werden. Die Methoden haben ebenfalls eigene Referenzeinträge. Dieser Zusammenfassungsabschnitt führt nur die Methodennamen auf. Informationen zu Argumenttypen und Rückgabetypen finden Sie auf der Referenzseite zu der entsprechenden Methode.

## Beschreibung

Die meisten Referenzeinträge enthalten auch einen Abschnitt namens »Beschreibung, in dem die Grundlagen der dokumentierten Klasse, Methode, Funktion oder Eigenschaft erläutert werden. Dies ist der Kern des Referenzeintrags. Wenn Sie erstmals etwas über eine Klasse, Methode oder Eigenschaft lernen, gehen Sie am besten direkt zu diesem Abschnitt und kehren dann zu den darüber stehenden Abschnitten wie z.B. »Argumente«, »Eigenschaften« und »Methoden« zurück. Wenn Sie die Klasse, Methode oder Eigenschaft schon kennen, brauchen Sie diese Beschreibung eventuell gar nicht zu lesen, sondern schlagen nur rasch eine spezielle Information nach (z.B. im Abschnitt »Argumente« oder »Eigenschaften«).

In manchen Einträgen ist dieser Abschnitt nur ein kurzer Absatz; in anderen erstreckt er sich über eine Seite oder mehr. Bei manchen einfachen Methoden reichen die Abschnitte »Argumente« und »Rückgabewert« bereits zur Dokumentation aus; dann wird der Beschreibungsabschnitt weggelassen.

## Beispiel

In manchen Einträgen steht ein typisches Anwendungsbeispiel. Die meisten Einträge enthalten jedoch keine Beispiele; diese stehen in der ersten Hälfte des Buchs.

## Fehler

Wenn etwas nicht richtig funktioniert, beschreibt dieser Abschnitt die Fehler. Beachten Sie jedoch, dass dieses Buch nicht den Versuch unternimmt, jeden Fehler zu katalogisieren.

## Siehe auch

Viele Referenzeinträge geben am Ende einen Querverweis auf Referenzeinträge zu verwandten Themen, die von Interesse sein könnten. Manchmal beziehen sich Referenzeinträge auch auf eines der Hauptkapitel des Buchs.

## Name

arguments[ ] — Ein Array von FunktionsargumentenECMAScript v1

## Überblick

    arguments

## Beschreibung

Das Array `arguments[]` ist nur innerhalb eines Funktionsbodys definiert. Darin bezieht sich `arguments` auf das Arguments-Objekt für die Funktion. Dieses Objekt hat durchnummerierte Eigenschaften und dient als das Array, das alle an die Funktion übergebenen Argumente enthält. Der Bezeichner `arguments` ist im Grunde eine lokale Variable, die in jeder Funktion automatisch deklariert und initialisiert wird. Er bezieht sich nur innerhalb des Bodys einer Funktion auf ein Argument-Objekt und ist im globalen Code nicht definiert.

## Siehe auch

`Arguments`; Kapitel 8

## Name

Arguments — Argumente und andere Eigenschaften einer FunktionECMAScript v1Object → Arguments

## Überblick

    arguments
    arguments[ _n_ ]

## Elemente

Das Arguments-Objekt ist nur in einem Funktionsbody definiert. Obwohl es technisch gesehen eigentlich kein Array ist, hat das Arguments-Objekt nummerierte Eigenschaften, die wie Array-Elemente funktionieren, und eine `length`-Eigenschaft, die die Anzahl der Array-Elemente spezifiziert. Seine Elemente sind die Werte, die der Funktion als Argumente übergeben werden. Element 0 ist das erste Argument, Element 1 das zweite usw. Alle als Argumente übergebenen Werte werden Array-Elemente des Arguments-Objekts, egal ob diese Argumente in der Funktionsdeklaration Namen erhalten oder nicht.

## Eigenschaften

`callee`

Eine Referenz auf die Funktion, die gerade ausgeführt wird.

`length`

Die Anzahl der an die Funktion übergebenen Argumente und die Anzahl der Array-Elemente im Arguments-Objekt.

## Beschreibung

Wenn eine Funktion aufgerufen wird, wird ein Arguments-Objekt für sie erzeugt und die lokale Variable `arguments` automatisch als Referenz auf dieses Arguments-Objekt initialisiert. Das Arguments-Objekt soll hauptsächlich eine Möglichkeit bieten, die Anzahl der an die Funktion übergebenen Argumente zu ermitteln, und es soll eine Referenz auf unbenannte Argumente geben. Zusätzlich zu den Array-Elementen und der `length`-Eigenschaft gibt die `callee`-Eigenschaft einer unbenannten Funktion auch die Möglichkeit, sich selbst zu referenzieren.

Meist kann man sich das Arguments-Objekt wie ein Array mit zusätzlicher `callee`-Eigenschaft vorstellen. Es ist jedoch keine Instanz von Array, und die Eigenschaft `Arguments.length` zeigt keine der speziellen Verhaltensweisen von `Array.length` und kann auch nicht dazu verwendet werden, die Array-Größe zu ändern.

Das Arguments-Objekt hat ein sehr ungewöhnliches Merkmal. Wenn eine Funktion benannte Argumente hat, sind die Array-Elemente des Arguments-Objekts synonym zu den lokalen Variablen, die die Funktionsargumente speichern. Das Arguments-Objekt und die Namen der Argumente bieten zwei unterschiedliche Möglichkeiten, dieselbe Variable zu referenzieren. Wenn Sie den Wert eines benannten Arguments ändern, ändert sich auch der Wert, der über das Arguments-Objekt abgerufen wird; und wenn Sie den Wert eines Arguments über das Arguments-Objekt ändern, ändert sich ebenso der Wert, der über den Argumentnamen abgerufen wird.

## Siehe auch

`Function`; Kapitel 8

## Name

Arguments.callee — Die Funktion, die gerade läuftECMAScript v1

## Überblick

    arguments.callee

## Beschreibung

`arguments.callee` bezieht sich auf die Funktion, die gerade läuft. Diese Eigenschaft ist nur in einem Funktionsbody definiert.

## Beispiel

    // Ein unbenanntes Funktionsliteral referenziert sich mit der callee-
    // Eigenschaft selbst, kann also rekursiv sein
    var factorial = function(x) {
      if (x < 2) return 1;
      else return x * arguments.callee(x-1);
    }
    var y = factorial(5); // Gibt 120 zurück

## Name

Arguments.length — Die Anzahl der an eine Funktion übergebenen ArgumenteECMAScript v1

## Überblick

    arguments.length

## Beschreibung

Die `length`-Eigenschaft des Arguments-Objekts gibt an, wie viele Argumente der aktuellen Funktion übergeben werden. Diese Eigenschaft ist nur in einem Funktionsbody definiert.

Beachten Sie, dass diese Eigenschaft nur die Anzahl der tatsächlich übergebenen Argumente angibt und nicht die Anzahl der erwarteten Argumente. Unter `Function.length` finden Sie die Anzahl der deklarierten Argumente. Außerdem hat diese Eigenschaft keine der speziellen Verhaltensweisen von `Array.length`.

## Beispiel

    // Prüfe mit einem Arguments-Objekt, ob die richtige Anzahl Argumente
    // übergeben wurde
    function check(args) {
      var actual = args.length;      // Tatsächliche Anzahl Argumente
      var expected = args.callee.length; // Erwartete Anzahl Argumente
      if (actual != expected) {      // Exception, wenn sie nicht übereinstimmen
        throw new Error("Falsche Anzahl Argumente: erwartet: " +
                 expected + "; übergeben " + actual);
      }
    }
    // Diese Funktion demonstriert die Verwendung der obigen Funktion
    function f(x, y, z) {
      check(arguments); // Prüfe auf richtige Anzahl Argumente
      return x + y + z; // Lass den Rest der Funktion normal laufen
    }

## Siehe auch

`Array.length, Function.length`

## Name

Array — Eingebaute Array-UnterstützungECMAScript v1Object → Array

## Konstruktor

    new Array( )
    new Array( _groesse_ )
    new Array( _element0_ , _element1_ , ..., _elementn_ )

### Argumente

 _`groesse`_

Die gewünschte Anzahl Array-Elemente. Das `length`-Feld des Rückgabe-Arrays ist auf _`groesse`_ gesetzt.

 _`element0, ... elementn`_

Eine Liste von Argumenten mit zwei oder mehr beliebigen Werten. Wenn der `Array( )`-Konstruktor mit diesen Argumenten aufgerufen wird, wird das neu erzeugte Array mit den angegebenen Argumentwerten als Elementen initialisiert und sein `length`-Feld auf die Anzahl der Argumente gesetzt.

### Rückgabewert

Das neu erzeugte und initialisierte Array. Wenn `Array( )` ohne Argumente aufgerufen wird, ist das Rückgabe-Array leer und sein `length`-Feld 0. Wird `Array( )` mit einem einzigen numerischen Argument aufgerufen, so gibt der Konstruktor ein Array mit der angegebenen Anzahl undefinierter Elemente zurück. Wird der Konstruktor mit irgendwelchen anderen Argumenten aufgerufen, so initialisiert er das Array mit den durch diese Argumente angegebenen Werten. Wird der `Array( )`-Konstruktor als Funktion ohne den `new`-Operator aufgerufen, dann verhält er sich genauso, als sei er mit dem `new`-Operator aufgerufen worden.

### Exceptions

`RangeError`

Wenn ein einziges, ganzzahliges _`groesse`_ -Argument an den `Array( )`-Konstruktor übergeben wird, wird eine `RangeError`-Exception ausgelöst, falls _`groesse`_ negativ oder größer als 232−1 ist.

## Literalsyntax

ECMAScript v3 spezifiziert eine Array-Literalsyntax. Sie können ein Array auch anlegen und initialisieren, indem Sie eine Liste von durch Kommata getrennten Ausdrücken in eckige Klammern setzen. Die Werte dieser Ausdrücke werden zu den Array-Elementen. Ein Beispiel:

    var a = [1, true, 'abc'];
    var b = [a[0], a[0]*2, f(x)];

## Eigenschaften

`length`

Ein les- und schreibbarer Integer, der die Anzahl der Array-Elemente angibt. Wenn das Array nicht aus aufeinanderfolgenden Elementen besteht, ist dies eine Zahl, die um eins größer als der Index des letzten Array-Elements ist. Wenn Sie den Wert dieser Eigenschaft ändern, wird das Array abgeschnitten oder erweitert.

## Methoden

`concat( )` | Verkettet Elemente mit einem Array.

---|---

`join( )` | Konvertiert alle Array-Elemente in Strings und verkettet diese.

`pop( )` | Entfernt ein Element vom Array-Ende.

`push( )` | Hängt ein Element an das Array-Ende an.

`reverse( )` | Kehrt die Reihenfolge der Elemente innerhalb des Arrays um.

`shift( )` | Schiebt ein Element aus dem Array-Anfang heraus.

`slice( )` | Gibt ein Slice (Stück) eines Arrays als Teil-Array zurück.

`sort( )` | Sortiert die Elemente innerhalb des Arrays.

`splice( )` | Fügt Array-Elemente ein oder löscht oder ersetzt sie.

`toLocaleString( )` | Konvertiert ein Array in einen lokalisierten String.

`toString( )` | Konvertiert ein Array in einen String.

`unshift( )` | Fügt Elemente am Array-Anfang ein.

## Beschreibung

Arrays sind ein grundlegender Bestandteil von JavaScript und werden in Kapitel 7 genau dokumentiert.

## Siehe auch

Kapitel 7

## Name

Array.concat( ) — Verkettet ArraysECMAScript v3

## Überblick

     _array_.concat( _wert_ , ...)

### Argumente

 _`wert, ...`_

Eine beliebige Anzahl Werte, die mit _`array`_ verkettet werden sollen.

### Rückgabewert

Ein neues Array. Es wird durch Verkettung jedes der angegebenen Argumente mit _`array`_ gebildet.

## Beschreibung

Die Methode `concat( )` erzeugt und liefert ein neues Array, das durch Verkettung jedes ihrer Argumente mit _`array`_ entsteht. Wenn ein Argument von `concat( )` selbst ein Array ist, wird nicht dieses Array selbst, sondern jedes seiner Argumente verkettet.

## Beispiel

    var a = [1,2,3];
    a.concat(4, 5)     // Gibt [1,2,3,4,5] zurück
    a.concat([4,5]);    // Gibt [1,2,3,4,5] zurück
    a.concat([4,5],[6,7])  // Gibt [1,2,3,4,5,6,7] zurück
    a.concat(4, [5,[6,7]]) // Gibt [1,2,3,4,5,[6,7]] zurück

## Siehe auch

`Array.join( )`, `Array.push( )`, `Array.splice( )`

## Name

Array.join( ) — Verkettet Array-Elemente zu einem StringECMAScript v1

## Überblick

     _array_.join( )
    _array_.join( _trennzeichen_ )

### Argumente

 _`trennzeichen`_

Ein optionales Zeichen oder ein String, um im Rückgabe-String ein Element des Arrays von dem nächsten zu trennen. Wenn dieses Argument fehlt, wird ein Komma genommen.

### Rückgabewert

Der String, der sich ergibt, wenn jedes Element des _`array`_ s in einen String konvertiert und alle diese Strings dann verkettet werden. Zwischen den Elementen steht jeweils ein _`trennzeichen`_ -String.

## Beschreibung

`join( )` konvertiert alle Elemente eines Arrays in Strings und verkettet dann diese Strings, indem zwischen den Elementen jeweils der String _`trennzeichen`_ gesetzt wird. Der Ergebnis-String wird zurückgegeben.

Sie können eine Konvertierung auch in der entgegengesetzten Richtung vornehmen, indem Sie mit der `split( )`-Methode des String-Objekts einen String in Array-Elemente aufspalten. Einzelheiten hierzu finden Sie unter `String.split( )`.

## Beispiel

    a = new Array(1, 2, 3, "Test");
    s = a.join("+"); // s ist der String "1+2+3+Test"

## Siehe auch

`String.split( )`

## Name

Array.length — Die Größe eines ArraysECMAScript v1

## Überblick

     _array_.length

## Beschreibung

Die `length`-Eigenschaft eines Arrays ist immer um eins größer als der Index des größten im Array definierten Elements. Bei traditionellen, »dichten« Arrays mit aufeinanderfolgenden Elementen, die bei 0 beginnen, gibt die `length`-Eigenschaft die Anzahl der Array-Elemente wieder.

Die `length`-Eigenschaft eines Arrays wird initialisiert, wenn das Array mit dem Konstruktor `Array( )` angelegt wird. Werden einem Array neue Elemente hinzugefügt, so wird `length` wenn nötig geändert:

    a = new Array( );            // a.length mit 0 initialisiert
    b = new Array(10);            // b.length mit 10 initialisiert
    c = new Array("eins", "zwei", "drei");  // c.length mit 3 initialisiert
    c[3] = "vier";              // c.length mit 4 aktualisiert
    c[10] = "peng";              // c.length wird 11

Durch Setzen der `length`-Eigenschaft können Sie die Größe eines Arrays ändern. Wenn Sie `length` auf einen kleineren als den bisherigen Wert setzen, wird das Array abgeschnitten, und die letzten Elemente gehen verloren. Setzen Sie `length` auf einen größeren Wert, so wird das Array größer, und die am Ende des Arrays neu hinzugefügten Werte haben den Wert `undefined`.

## Name

Array.pop( ) — Entfernt und liefert das letzte Element eines ArraysECMAScript v3

## Überblick

     _array_.pop( )

### Rückgabewert

Das letzte Element von _`array`_.

## Beschreibung

`pop( )` löscht das letzte Element von _`array`_ , dekrementiert die Array-Länge und gibt den Wert des gelöschten Elements zurück. Wenn das Array bereits leer ist, ändert `pop( )` es nicht und gibt den Wert `undefined` zurück.

## Beispiel

`pop( )` und sein Gegenstück `push( )` ergeben die Funktionalität eines Last-in-first-out-Stacks. Ein Beispiel:

    var stack = [];    // Stack: []
    stack.push(1, 2);   // Stack: [1,2]   Gibt 2 zurück
    stack.pop( );    // Stack: [1]    Gibt 2 zurück
    stack.push([4,5]);  // Stack: [1,[4,5]] Gibt 2 zurück
    stack.pop( )     // Stack: [1]    Gibt [4,5] zurück
    stack.pop( );    // Stack: []    Gibt 1 zurück

## Siehe auch

`Array.push( )`

## Name

Array.push( ) — Hängt Elemente an ein Array anECMAScript v3

## Überblick

     _array_.push( _wert_ , ...)

### Argumente

 _wert_ _`, ...`_

Ein oder mehr Werte, die an _`array`_ angehängt werden.

### Rückgabewert

Die neue Array-Länge, nachdem die angegebenen Werte angehängt wurden.

## Beschreibung

Die Methode `push( )` hängt ihre Argumente der Reihe nach am Ende von _`array`_ an. Sie erzeugt kein neues Array, sondern modifiziert _`array`_ direkt. Die Methoden `push( )` und `pop( )` stellen mit Hilfe von Arrays die Funktionalität eines Last-in-first-out-Stacks zur Verfügung. Ein Beispiel finden Sie unter `Array.pop( )`.

## Siehe auch

`Array.pop( )`

## Name

Array.reverse( ) — Kehrt die Elemente eines Arrays umECMAScript v1

## Überblick

     _array_.reverse( )

## Beschreibung

Die `reverse( )`-Methode eines Array-Objekts kehrt die Reihenfolge der Array-Elemente um. Das macht sie _vor Ort_ , indem sie die Elemente des angegebenen _`array`_ s umkehrt, ohne ein neues Array anzulegen. Wenn es mehrere Referenzen auf _`array`_ gibt, kann auf die neue Reihenfolge der Array-Elemente über alle diese Referenzen zugegriffen werden.

## Beispiel

    a = new Array(1, 2, 3);  // a[0] == 1, a[2] == 3;
    a.reverse( );       // Nun a[0] == 3, a[2] == 1;

## Name

Array.shift( ) — Schiebt Array-Elemente nach untenECMAScript v3

## Überblick

     _array_.shift( )

### Rückgabewert

Das vormals erste Array-Element.

## Beschreibung

`shift( )` entfernt und liefert das erste Element von _`array`_. Alle Folgeelemente werden um eins nach unten verschoben, um den frei gewordenen Platz am Array-Anfang wieder zu füllen. Wenn das Array leer ist, macht `shift( )` überhaupt nichts und gibt den Wert `undefined` zurück. `shift( )` erzeugt kein neues Array, sondern modifiziert unmittelbar das _`array`_.

`shift( )` ist wie `Array.pop( )`, arbeitet jedoch auf dem Anfang und nicht auf dem Ende des Arrays. Diese Methode wird oft zusammen mit `unshift( )` eingesetzt.

## Beispiel

    var a = [1, [2,3], 4]
    a.shift( ); // Gibt 1 zurück; a = [[2,3], 4]
    a.shift( ); // Gibt [2,3] zurück; a = [4]

## Siehe auch

`Array.pop( )`, `Array.unshift( )`

## Name

Array.slice( ) — Gibt einen Teil eines Arrays zurückECMAScript v3

## Überblick

     _array_.slice( _anfang_ , _ende_ )

### Argumente

 _`anfang`_

Der Array-Index, bei dem das Stück (Slice) beginnen soll. Ist er negativ, so gibt dieses Argument eine vom Array-Ende ab gemessene Position an. −1 meint also das letzte Element, −2 das vorletzte und so weiter.

 _`ende`_

Der Array-Index, der unmittelbar auf das Ende des Stücks folgt. Ist keiner angegeben, enthält das Stück alle Array-Elemente von _`anfang`_ bis zum Array-Ende. Ist dieses Argument negativ, so gibt es vom Array-Ende an gemessen ein Array-Element an.

### Rückgabewert

Ein neues Array, das die _`array`_ -Elemente von dem durch _`anfang`_ angegebenen Element bis (ausschließlich) zum _`ende`_ -Element enthält.

## Beschreibung

`slice( )` gibt ein Stück oder ein Teil-Array von _`array`_ zurück. Das Rückgabe-Array enthält das _`anfang`_ -Element und alle nachfolgenden Elemente bis hin zum (aber ohne das) _`ende`_ -Element. Wenn _`ende`_ nicht angegeben ist, enthält das Rückgabe-Array alle Elemente von _`anfang`_ bis zum Ende von _`array`_.

Beachten Sie, dass `slice( )` das Array nicht ändert. Wenn Sie ein Stück des Arrays entfernen möchten, müssen Sie `Array.splice( )` verwenden.

## Beispiel

    var a = [1,2,3,4,5];
    a.slice(0,3);  // Gibt [1,2,3] zurück
    a.slice(3);   // Gibt [4,5] zurück
    a.slice(1,-1);  // Gibt [2,3,4] zurück
    a.slice(-3,-2); // Gibt [3] zurück; Bug in IE 4: liefert [1,2,3]

## Fehler

 _`anfang`_ darf im Internet Explorer 4 keine negative Zahl sein. Dies ist erst in späteren Versionen des IE geändert worden.

## Siehe auch

`Array.splice( )`

## Name

Array.sort( ) — Sortiert die Elemente eines ArraysECMAScript v1

## Überblick

     _array_.sort( )
    _array_.sort( _sortierfunktion_ )

### Argumente

 _`sortierfunktion`_

Optional eine Funktion, die die Sortierreihenfolge festlegt.

### Rückgabewert

Eine Referenz auf das Array. Es wird vor Ort sortiert und nicht kopiert.

## Beschreibung

Die `sort( )`-Methode sortiert die _`array`_ -Elemente vor Ort, ohne das Array zu kopieren. Wird sie ohne Argumente aufgerufen, so werden die Array-Elemente in alphabetischer Reihenfolge (genauer gesagt: in der Reihenfolge, die die Zeichencodierung festlegt) sortiert. Damit ein Vergleich möglich ist, werden die Elemente notfalls zuvor in Strings konvertiert.

Wenn Sie die Array-Elemente in eine andere Reihenfolge bringen möchten, müssen Sie eine Vergleichsfunktion angeben, die zwei Werte vergleicht und eine Zahl zurückgibt, die ihre Reihenfolge anzeigt. Die Vergleichsfunktion sollte die beiden Argumente _`a`_ und _`b`_ entgegennehmen und eines der folgenden Ergebnisse liefern:

  * Einen kleineren Wert als null, wenn nach Ihren Sortierkriterien _`a`_ kleiner als _`b`_ ist und im sortierten Array vor _`b`_ stehen soll.

  * Null, wenn _`a`_ und _`b`_ bei dieser Sortierung äquivalent sind.

  * Einen Wert größer null, wenn _`a`_ nach Ihren Sortierkriterien größer als _`b`_ ist.

Beachten Sie, dass nicht-definierte Elemente eines Arrays immer an das Array-Ende sortiert werden. Das gilt auch dann, wenn Sie eine eigene Sortierfunktion bereitstellen: Nicht-definierte Werte werden der von Ihnen angegebenen _`sortierfunktion`_ nicht übergeben.

## Beispiel

Der folgende Code zeigt, wie Sie beispielsweise eine Vergleichsfunktion schreiben können, um ein Zahlen-Array nicht alphabetisch, sondern numerisch zu sortieren:

    // Eine Funktion für eine numerische Sortierung
    function numberorder(a, b) { return a - b; }

    a = new Array(33, 4, 1111, 222);
    a.sort( );       // Alphabetische Sortierung: 1111, 222, 33, 4
    a.sort(numberorder);  // Numerische Sortierung: 4, 33, 222, 1111

## Name

Array.splice( ) — Fügt Array-Elemente ein oder entfernt oder ersetzt sieECMAScript v3

## Überblick

     _array_.splice( _anfang_ , _loeschAnzahl_ , _wert_ , ...)

### Argumente

 _`anfang`_

Das Array-Element, mit dem die Einfügung und/oder Löschung anfangen soll.

 _`loeschAnzahl`_

Die Anzahl der Elemente, die aus dem _`array`_ gelöscht werden sollen, beginnend mit _`anfang`_. Dieses Argument ist optional: Wenn nichts angegeben ist, löscht `splice( )` alle Elemente von _`anfang`_ bis zum Ende des Arrays.

 _`wert, ...`_

Null oder mehr Werte, die in _`array`_ eingefügt werden sollen, beginnend mit dem Index _`anfang`_.

### Rückgabewert

Ein Array, das (gegebenenfalls) die aus _`array`_ gelöschten Elemente enthält.

## Beschreibung

`splice( )` löscht null oder mehr Array-Elemente einschließlich des und beginnend mit dem _`anfang`_ -Element und ersetzt sie durch null oder mehr Werte, die in der Argumentliste angegeben sind. Array-Elemente, die hinter der Einfügung oder Löschung stehen, werden derart verschoben, dass sie an den Rest des Arrays anschließen. Beachten Sie, dass `splice( )` im Gegensatz zu der ähnlich klingenden Methode `slice( )` das _`array`_ unmittelbar modifiziert.

## Beispiel

Die Funktion von `splice( )` lässt sich am besten an einem Beispiel erklären:

    var a = [1,2,3,4,5,6,7,8]
    a.splice(4);    // Gibt [5,6,7,8] zurück; a = [1,2,3,4]
    a.splice(1,2);   // Gibt [2,3] zurück; a = [1,4]
    a.splice(1,1);   // Gibt [] zurück; a ist [1]
    a.splice(1,0,2,3); // Gibt [] zurück; a ist [1 2 3]

## Siehe auch

`Array.slice( )`

## Name

Array.toLocaleString( ) — Konvertiert ein Array in einen lokalisierten StringECMAScript v1Überschreibt Object.toLocaleString( )

## Überblick

     _array_.toLocaleString( )

### Rückgabewert

Eine lokalisierte String-Darstellung von _`array`_.

### Exceptions

`TypeError`

Wenn diese Methode auf einem Objekt aufgerufen wird, das kein Array ist.

## Beschreibung

Die `toLocaleString( )`-Methode eines Arrays gibt eine lokalisierte String-Darstellung eines Arrays zurück. Dies macht sie, indem sie zuerst auf allen Array-Elementen die Methode `toLocale-String( )` aufruft und dann die daraus resultierenden Strings verkettet, indem sie ein locale-spezifisches Trennzeichen dazwischensetzt.

## Siehe auch

`Array.toString( )`, `Object.toLocaleString( )`

## Name

Array.toString( ) — Konvertiert ein Array in einen StringECMAScript v1Überschreibt Object.toString( )

## Überblick

     _array_.toString( )

### Rückgabewert

Eine String-Darstellung von _`array`_.

### Exceptions

`TypeError`

Wenn die Methode auf einem Objekt aufgerufen wird, das kein Array ist.

## Beschreibung

Die `toString( )`-Methode eines Arrays konvertiert ein Array in einen String und gibt diesen String zurück. Wird ein Array in einem String-Kontext benutzt, konvertiert JavaScript es automatisch in einen String, indem es diese Methode aufruft. In manchen Situationen sollten Sie aber dennoch `toString( )` explizit aufrufen.

Die Methode `toString( )` konvertiert ein Array in einen String, indem sie zuerst jedes einzelne Array-Element (durch Aufruf seiner `toString( )`-Methode) in einen String umwandelt und diese Ergebnis-Strings dann in Form einer durch Kommata getrennten Liste ausgibt. Dieser Rückgabewert ist derselbe String, den auch ein parameterloser Aufruf der Methode `join( )` zurückgeben würde.

## Siehe auch

`Array.toLocaleString( )`, `Object.toString( )`

## Name

Array.unshift( ) — Fügt am Array-Anfang Elemente einECMAScript v3

## Überblick

     _array_.unshift( _wert_ , ...)

### Argumente

 _wert_ _`, ...`_

Ein oder mehr Werte, die am Anfang von _`array`_ eingefügt werden sollen.

### Rückgabewert

Die neue Länge des Arrays.

## Beschreibung

Die Methode `unshift( )` fügt ihre Argumente am Anfang von _`array`_ ein und verschiebt dabei die vorhandenen Elemente auf nachfolgende Indexpositionen, um Platz zu schaffen. Das erste Argument von `unshift( )` wird nun das Array-Element mit dem Index 0; das zweite Element wird, falls vorhanden, das Element 1 und so weiter. Beachten Sie, dass `unshift( )` kein neues Array anlegt, sondern das vorhandene _`array`_ unmittelbar modifiziert.

## Beispiel

`unshift( )` wird oft zusammen mit `shift( )` verwendet. Ein Beispiel:

    var a = [];       // a:[]
    a.unshift(1);      // a:[1]     Rückgabe: 1
    a.unshift(22);     // a:[22,1]    Rückgabe: 2
    a.shift( );      // a:[1]     Rückgabe: 22
    a.unshift(33,[4,5]);  // a:[33,[4,5],1] Rückgabe: 3

## Siehe auch

`Array.shift( )`

## Name

Boolean — Unterstützung für Boolesche WerteECMAScript v1Object → Boolean

## Konstruktor

    new Boolean( _wert_ ) // Konstruktorfunktion
    Boolean( _wert_ )   // Konvertierungsfunktion

### Argumente

 _`wert`_

Der Wert, der vom Boolean-Objekt gespeichert oder in einen Booleschen Wert konvertiert werden soll.

### Rückgabewert

Wird die Funktion `Boolean( )` als Konstruktor mit dem Operator `new` aufgerufen, so konvertiert sie ihr Argument in einen Booleschen Wert und gibt ein Boolean-Objekt zurück, das diesen Wert enthält. Wird sie als Funktion und ohne den `new`-Operator aufgerufen, konvertiert sie nur ihr Argument in einen Booleschen Wert und gibt diesen Wert zurück.

Die Werte 0, NaN, `null`, leerer String `""` und `undefined` werden alle in `false` konvertiert. Alle anderen elementaren Werte, mit Ausnahme von `false` (aber einschließlich des Strings »false«), sowie alle Objekte und Arrays werden in `true` konvertiert.

## Methoden

`toString( )`

Gibt je nach dem Booleschen Wert, den das Boolean-Objekt repräsentiert, `true` oder `false` zurück.

`valueOf( )`

Gibt den elementaren Boolean-Wert zurück, der in dem Boolean-Objekt enthalten ist.

## Beschreibung

Boolean-Werte sind in JavaScript ein grundlegender Datentyp. Das Boolean-Objekt ist ein Wrapper-Objekt um den Booleschen Wert. Der Objekttyp Boolean ist vor allem dazu da, eine `toString( )`-Methode zu liefern, die Boolesche Werte in Strings konvertiert. Wird die `toString( )`-Methode aufgerufen, um einen Booleschen Wert in einen String zu konvertieren (und JavaScript ruft sie oft implizit auf), konvertiert JavaScript diesen Booleschen Wert intern in ein transientes Boolean-Objekt, auf dem die Methode aufgerufen werden kann.

## Siehe auch

`Object`

## Name

Boolean.toString( ) — Konvertiert einen Booleschen Wert in einen StringECMAScript v1Überschreibt Object.toString( )

## Überblick

     _b_.toString( )

### Rückgabewert

Der String »true« oder »false«, je nachdem, welchen Wert der elementare Boolesche Wert oder das Boolean-Objekt _`b`_ hat.

### Exceptions

`TypeError`

Wenn diese Methode auf einem Objekt aufgerufen wird, das kein Boolean-Objekt ist.

## Name

Boolean.valueOf( ) — Der Boolesche Wert eines Boolean-ObjektsECMAScript v1Überschreibt Object.valueOf( )

## Überblick

     _b_.valueOf( )

### Rückgabewert

Der elementare Boolesche Wert, den das Boolean-Objekt _`b`_ speichert.

### Exceptions

`TypeError`

Wenn diese Methode auf einem Objekt aufgerufen wird, das kein Boolean-Objekt ist.

## Name

Date — Bearbeitet Datums- und UhrzeitwerteECMAScript v1Object → Date

## Konstruktor

    new Date( )
    new Date( _millisekunden_ )
    new Date( _datumsstring_ )
    new Date( _jahr_ , _monat_ , _tag_ , _stunden_ , _minuten_ , _sekunden_ , _ms_ )

Wenn man ihn ohne Argumente aufruft, erzeugt der Konstruktor `Date( )` ein Date-Objekt, das auf das heutige Datum und die momentane Uhrzeit eingestellt ist. Übergibt man ihm ein numerisches Argument, so wird dieses als eine interne, numerische Datumsdarstellung in Millisekunden betrachtet, wie sie von der Methode `getTime( )` zurückgegeben wird. Wird ihm ein String-Argument übergeben, so handelt es sich um eine String-Darstellung eines Datums in einem Format, das die Methode `Date.parse( )` akzeptiert. Andernfalls werden dem Konstruktor zwei bis sieben numerische Argumente übergeben, die die Inhalte der einzelnen Datums- und Uhrzeitfelder angeben. Alle außer den ersten beiden Argumenten (Jahr und Monat) sind optional. Beachten Sie, dass diese Datums- und Uhrzeitfelder in der Ortszeit und nicht in UTC (ähnlich wie GMT) spezifiziert werden. Eine Alternative finden Sie unter der statischen Methode `Date.UTC( )`.

`Date( )` kann auch als Funktion ohne den Operator `new` aufgerufen werden. Dann ignoriert `Date( )` alle übergebenen Argumente und gibt eine String-Darstellung des heutigen Datums und der Uhrzeit zurück.

### Argumente

 _`millisekunden`_

Die Anzahl der Millisekunden zwischen dem gewünschten Datum und dem 1. Januar 1970 um Mitternacht (UTC). Wenn Sie z.B. das Argument 5000 übergeben, so würde ein Datum angelegt, das den Zeitpunkt fünf Sekunden nach Mitternacht am 01.01.1970 repräsentiert.

 _`datumsstring`_

Ein einzelnes Argument, das das Datum und optional auch die Uhrzeit als String angibt. Der String sollte ein Format haben, das von `Date.parse( )` akzeptiert wird.

 _`jahr`_

Das vierstellig angegebene Jahr. Für das Jahr 2002 geben Sie z.B. `2002` an. Wenn das Argument zwischen 0 und 99 liegt, wird aus Gründen der Kompatibilität zu Vorläuferimplementierungen von JavaScript 1900 hinzuaddiert.

 _`monat`_

Der Monat, angegeben als Integer zwischen 0 (Januar) und 11 (Dezember).

 _`tag`_

Der Tag des Monats, angegeben als Integer zwischen 1 und 31. Beachten Sie, dass dieses Argument 1 als kleinsten Wert verwendet, während andere Argumente 0 als kleinsten Wert haben. Optional.

 _`stunden`_

Die Stunde, angegeben als Integer zwischen 0 (Mitternacht) und 23 (elf Uhr nachts). Optional.

 _`minuten`_

Die Minuten einer Stunde, angegeben als Integer zwischen 0 und 59. Optional.

 _`sekunden`_

Die Sekunden der Minute, angegeben als Integer zwischen 0 und 59. Optional.

 _`ms`_

Die Millisekunden der Sekunde, angegeben als Integer zwischen 0 und 999. Optional.

## Methoden

Das Date-Objekt hat keine Eigenschaften, die unmittelbar gelesen und geschrieben werden können. Jeglicher Zugriff auf Datums- und Uhrzeitwerte erfolgt über Methoden. Die meisten Methoden des Date-Objekts haben zwei Formen: eine, die mit der Ortszeit arbeitet, und eine, die mit der Universalzeit (UTC oder GMT) arbeitet. Hat eine Methode den Begriff »UTC« im Namen, dann arbeitet sie mit der Universalzeit. Die Methodenpaare sind nachfolgend aufgelistet. Das Listing für `get[UTC]Day( )` bezieht sich beispielsweise auf die beiden Methoden `getDay( )` und `getUTCDay( )`.

Datumsmethoden können nur auf Date-Objekten aufgerufen werden, und sie lösen bei jedem Versuch, sie auf einem anderen Objekttyp aufzurufen, eine TypeError-Exception aus.

`get[UTC]Date( )`

Gibt den Monatstag eines Date-Objekts in Ortszeit oder Universalzeit zurück.

`get[UTC]Day( )`

Gibt den Wochentag eines Date-Objekts in Ortszeit oder Universalzeit zurück.

`get[UTC]FullYear( )`

Gibt das Jahr des Date-Objekts mit vier Ziffern in Orts- oder Universalzeit zurück.

`get[UTC]Hours( )`

Gibt das Stundenfeld eines Date-Objekts in Ortszeit oder Universalzeit zurück.

`get[UTC]Milliseconds( )`

Gibt das Millisekunden-Feld eines Date-Objekts in Ortszeit oder Universalzeit zurück.

`get[UTC]Minutes( )`

Gibt das Minutenfeld eines Date-Objekts in Ortszeit oder Universalzeit zurück.

`get[UTC]Month( )`

Gibt das Monatsfeld eines Date-Objekts in Ortszeit oder Universalzeit zurück.

`get[UTC]Seconds( )`

Gibt das Sekundenfeld eines Date-Objekts in Ortszeit oder Universalzeit zurück.

`getTime( )`

Gibt die interne Millisekundendarstellung eines Date-Objekts zurück. Beachten Sie, dass dieser Wert nicht von der Zeitzone abhängt und daher keine separate `getUTCTime( )`-Methode vorhanden ist.

`getTimezoneOffset( )`

Gibt die Differenz zwischen der Ortszeit- und der UTC-Darstellung dieses Datums in Minuten zurück. Der Rückgabewert hängt auch davon ab, ob zu dem angegebenen Datum Sommer- oder Winterzeit gilt.

`getYear( )`

Gibt das Jahresfeld eines Date-Objekts zurück. Ist für veraltet erklärt worden. Man verwendet stattdessen `getFullYear( )`.

`set[UTC]Date( )`

Setzt im Datum den Tag des Monatsfelds entweder in Ortszeit oder in Universalzeit.

`set[UTC]FullYear( )`

Setzt das Jahr (und optional auch Monat und Tag) des Datums entweder in Ortszeit oder in Universalzeit.

`set[UTC]Hours( )`

Setzt die Stunde (und optional die Felder für Minuten, Sekunden und Millisekunden) des Datums entweder in Ortszeit oder in Universalzeit.

`set[UTC]Milliseconds( )`

Setzt das Millisekundenfeld eines Datums entweder in Ortszeit oder in Universalzeit.

`set[UTC]Minutes( )`

Setzt das Minutenfeld (und optional die Felder für Sekunden und Millisekunden) eines Datums entweder in Ortszeit oder in Universalzeit.

`set[UTC]Month( )`

Setzt das Monatsfeld (und optional den Tag des Monats) eines Datums entweder in Ortszeit oder in Universalzeit.

`set[UTC]Seconds( )`

Setzt das Sekundenfeld (und optional das Millisekundenfeld) eines Datums entweder in Ortszeit oder in Universalzeit.

`setTime( )`

Setzt die Felder eines Date-Objekts im Millisekunden-Format.

`setYear( )`

Setzt das Jahresfeld eines Date-Objekts. Heute verwendet man dafür `setFullYear( )`.

`toDateString( )`

Gibt einen String zurück, der den Datumsteil eines Datums in der lokal gültigen Zeitzone ausdrückt.

`toGMTString( )`

Konvertiert ein Date-Objekt in einen String und verwendet dazu die GMT-Zeitzone. Heute verwendet man dafür `toUTCString( )`.

`toLocaleDateString( )`

Gibt einen String zurück, der den Datumsteil des Datums repräsentiert. Er wird in Ortszeit im lokal üblichen Datumsformat ausgedrückt.

`toLocaleString( )`

Konvertiert ein Date-Objekt in einen String und verwendet dazu die Ortszeit und das lokal übliche Datumsformat.

`toLocaleTimeString( )`

Gibt einen String zurück, der den Uhrzeitteil des Datums repräsentiert, und verwendet dazu die Ortszeit und das lokal übliche Datumsformat.

`toString( )`

Konvertiert ein Date-Objekt in einen String und verwendet dazu die Ortszeit.

`toTimeString( )`

Gibt einen String zurück, der den Uhrzeitteil des Datums repräsentiert, und verwendet dazu die Ortszeit.

`toUTCString( )`

Konvertiert ein Date-Objekt in einen String und verwendet dazu die Universalzeit.

`valueOf( )`

Konvertiert ein Date-Objekt in sein internes Millisekundenformat.

## Statische Methoden

Neben den vielen bereits aufgeführten Instanzmethoden definiert das Date-Objekt auch zwei statische Methoden. Diese werden über den `Date( )`-Konstruktor und nicht über einzelne Date-Objekte aufgerufen:

`Date.parse( )`

Parst eine String-Darstellung von Datum und Uhrzeit und gibt die interne Millisekundendarstellung des betreffenden Datums zurück.

`Date.UTC( )`

Gibt die Millisekundendarstellung der UTC-Datums- und Uhrzeitangaben zurück.

## Beschreibung

Das Date-Objekt ist ein eingebauter Datentyp von JavaScript. Date-Objekte werden mit der Schreibweise `new Date( )` erzeugt, die Sie weiter oben finden.

Wenn Sie ein Date-Objekt erzeugt haben, dann stehen Ihnen etliche Methoden zur Verfügung, um auf diesem zu arbeiten. Mit den meisten dieser Methoden können Sie einfach nur die Felder, die das Objekt für Jahr, Monat, Tag, Stunde, Minute, Sekunde und Millisekunde hat, mit Werten versehen. Sie verwenden dazu entweder die Ortszeit oder UTC (Universalzeit oder GMT). Die Methode `toString( )` und ihre Varianten konvertieren Datumswerte in Strings, die für Menschen lesbar sind. `getTime( )` und `setTime( )` konvertieren aus der und in die interne Darstellung des Date-Objekts, geben also die Anzahl der Millisekunden seit Mitternacht (GMT) am 1. Januar 1970 an. In diesem standardmäßigen Millisekundenformat stellt ein einziger Integer das Datum und die Uhrzeit dar, was Rechenoperationen mit dem Datum extrem vereinfacht. Der ECMAScript-Standard verlangt, dass das Date-Objekt in der Lage sein muss, jedes Datum und jede Uhrzeit von hundert Millionen Tagen vor bis hundert Millionen Tage nach dem 1.1.1970 millisekundengenau darzustellen. Da dieses Zeitintervall rund 273.785 Jahre beträgt, wird die Uhr von JavaScript bis zum Jahr 275755 problemlos laufen.

## Beispiel

Auf einem Date-Objekt können Sie mit einer Vielzahl von Methoden arbeiten:

    d = new Date( ); // Hole das aktuelle Datum samt Uhrzeit
    document.write('Wir haben den: ' + d.toLocaleDateString( ) + '. '); // Zeige Datum
    document.write('Die Uhrzeit ist: ' + d.toLocaleTimeString( ));    // Zeige Uhrzeit
    var dayOfWeek = d.getDay( );                     // Wochentag?
    var weekend = (dayOfWeek == 0) || (dayOfWeek == 6);         // Wochenende?

Das Date-Objekt wird auch oft dazu genutzt, die Millisekundendarstellung des jetzigen Zeitpunkts von einem anderen Zeitpunkt zu subtrahieren, um die Zeitspanne zwischen den beiden zu ermitteln. Das folgende clientseitige Beispiel zeigt zwei solche Anwendungen:

    <script language="JavaScript">
    today = new Date( );   // Notiere das heutige Datum
    christmas = new Date( ); // Hole ein Datum dieses Jahres
    christmas.setMonth(11);  // Setze den Monat auf Dezember ...
    christmas.setDate(24);   // und den Tag auf den 24.

    // Wenn Weihnachten noch nicht vorbei ist, berechne die Anzahl der
    // Millisekunden von jetzt bis Weihnachten, konvertiere das
    // in die Anzahl der Tage, und gib eine Nachricht aus.
    if (today.getTime( ) < christmas.getTime( )) {
      difference = christmas.getTime( ) - today.getTime( );
      difference = Math.floor(difference / (1000 * 60 * 60 * 24));
      document.write('Nur noch ' + difference + ' Tage bis Weihnachten!<p>');
    }
    </script>
    // ... hier kommt der Rest des HTML-Dokuments hin ...

    <script language="JavaScript">
    // Hier verwenden wir Date-Objekte zur Zeitmessung
    // Wir teilen durch 1000, um aus Millisekunden Sekunden zu machen
    now = new Date( );
    document.write('<p>Es dauerte ' +
      (now.getTime( )-today.getTime( ))/1000 +
      'Sekunden, diese Seite zu laden.');
    </script>

## Siehe auch

`Date.parse( )`, `Date.UTC( )`

## Name

Date.getDate( ) — Gibt das Monatstag-Feld eines Date-Objekts zurückECMAScript v1

## Überblick

     _date_.getDate( )

### Rückgabewert

Der Tag des Monats, wie er von dem angegebenen Date-Objekt _`date`_ spezifiziert wird, in Ortszeit. Die Rückgabewerte liegen zwischen 1 und 31.

## Name

Date.getDay( ) — Gibt das Wochentagsfeld eines Date-Objekts zurückECMAScript v1

## Überblick

     _date_.getDay( )

### Rückgabewert

Der Wochentag des angegebenen Date-Objekts _`date`_ in Ortszeit. Die Rückgabewerte liegen zwischen 0 (Sonntag) und 6 (Samstag).

## Name

Date.getFullYear( ) — Gibt des Jahresfeld eines Date-Objekts zurückECMAScript v1

## Überblick

     _date_.getFullYear( )

### Rückgabewert

Das Jahr, das sich ergibt, wenn _`date`_ in Ortszeit ausgedrückt wird. Der Rückgabewert ist eine Jahresangabe mit vier Ziffern, die das Jahrhundert einschließt, und keine Abkürzung mit zwei Ziffern.

## Name

Date.getHours( ) — Gibt das Stundenfeld zurückECMAScript v1

## Überblick

     _date_.getHours( )

### Rückgabewert

Das Stundenfeld des angegebenen Date-Objekts _`date`_ in Ortszeit. Die Rückgabewerte liegen zwischen 0 (Mitternacht) und 23 (elf Uhr nachts).

## Name

Date.getMilliseconds( ) — Gibt das Millisekundenfeld zurückECMAScript v1

## Überblick

     _date_.getMilliseconds( )

### Rückgabewert

Das Millisekundenfeld eines _`date`_ in Ortszeit.

## Name

Date.getMinutes( ) — Gibt das Minutenfeld zurückECMAScript v1

## Überblick

     _date_.getMinutes( )

### Rückgabewert

Das Minutenfeld des angegebenen Date-Objekts _`date`_ in Ortszeit. Die Rückgabewerte liegen zwischen 0 und 59.

## Name

Date.getMonth( ) — Gibt das Monatsfeld zurückECMAScript v1

## Überblick

     _date_.getMonth( )

### Rückgabewert

Das Monatsfeld des angegebenen Date-Objekts _`date`_ in Ortszeit. Die Rückgabewerte liegen zwischen 0 (Januar) und 11 (Dezember).

## Name

Date.getSeconds( ) — Gibt das Sekundenfeld zurückECMAScript v1

## Überblick

     _date_.getSeconds( )

### Rückgabewert

Das Sekundenfeld des angegebenen Date-Objekts _`date`_ in Ortszeit. Die Rückgabewerte liegen zwischen 0 und 59.

## Name

Date.getTime( ) — Gibt ein Date in Millisekunden zurückECMAScript v1

## Überblick

     _date_.getTime( )

### Rückgabewert

Die Millisekundendarstellung des angegebenen Date-Objekts _`date`_ , d.h. die Anzahl der Millisekunden zwischen Mitternacht des 1.1.1970 (GMT) und dem durch _`date`_ angegebenen Datums- und Uhrzeitwert.

## Beschreibung

`getTime( )` konvertiert ein Datum und eine Uhrzeit in einen einzigen Integer. Das ist nützlich, wenn Sie zwei Date-Objekte vergleichen oder den Zeitraum zwischen zwei Zeitpunkten herausfinden möchten. Beachten Sie, dass die Millisekundendarstellung eines Datums zeitzonenunabhängig ist und dass daher auch keine Schwestermethode `getUTCTime( )` existiert. Verwechseln Sie diese Methode bitte nicht mit den Methoden `getDay( )` und `getDate( )`, die den Wochentag und den Tag des Monats zurückgeben.

Mit `Date.parse( )` und `Date.UTC( )` können Sie eine Datums- und Uhrzeitangabe in ihre Millisekundendarstellung konvertieren, ohne dafür extra ein Date-Objekt erzeugen zu müssen.

## Siehe auch

`Date`, `Date.parse( )`, `Date.setTime( )`, `Date.UTC( )`

## Name

Date.getTimezoneOffset( ) — Ermittelt die Zeitdifferenz gegenüber GMTECMAScript v1

## Überblick

     _date_.getTimezoneOffset( )

### Rückgabewert

Die Differenz zwischen Greenwich Mean Time (GMT) und Ortszeit in Minuten.

## Beschreibung

`getTimezoneOffset( )` gibt zurück, wie viele Minuten zwischen der GMT- oder UTC-Zeit und der Ortszeit liegen. Letztlich verrät Ihnen diese Funktion, in welcher Zeitzone der JavaScript-Code läuft und ob dort am angegebenen _`date`_ Sommerzeit oder Winterzeit gilt.

Der Rückgabewert wird nicht in Stunden, sondern in Minuten bemessen, da manche Länder Zeitzonen haben, die sich nicht nach vollen Stundenintervallen richten.

## Name

Date.getUTCDate( ) — Gibt das Monatstag-Feld eines Date-Objekts zurück (Universalzeit)ECMAScript v1

## Überblick

     _date_.getUTCDate( )

### Rückgabewert

Der Tag des Monats (ein Wert zwischen 1 und 31), der sich ergibt, wenn _`date`_ in Universalzeit ausgedrückt wird.

## Name

Date.getUTCDay( ) — Gibt das Wochentagsfeld eines Date-Objekts zurück (Universalzeit)ECMAScript v1

## Überblick

     _date_.getUTCDay( )

### Rückgabewert

Der Wochentag, der sich ergibt, wenn _`date`_ in Universalzeit ausgedrückt wird. Die Rückgabewerte liegen zwischen 0 (Sonntag) und 6 (Samstag).

## Name

Date.getUTCFullYear( ) — Gibt das Jahresfeld eines Date-Objekts zurück (Universalzeit)ECMAScript v1

## Überblick

     _date_.getUTCFullYear( )

### Rückgabewert

Das Jahr, das sich ergibt, wenn _`date`_ in Universalzeit ausgedrückt wird. Der Rückgabewert ist ein Jahr mit vier Ziffern und keine zweistellige Abkürzung.

## Name

Date.getUTCHours( ) — Gibt das Stundenfeld zurück (Universalzeit)ECMAScript v1

## Überblick

     _date_.getUTCHours( )

### Rückgabewert

Das Stundenfeld von _`date`_ in Universalzeit. Der Rückgabewert ist ein Integer zwischen 0 (Mitternacht) und 23 (elf Uhr nachts).

## Name

Date.getUTCMilliseconds( ) — Gibt das Millisekundenfeld zurück (Universalzeit)ECMAScript v1

## Überblick

     _date_.getUTCMilliseconds( )

### Rückgabewert

Das Millisekundenfeld von _`date`_ in Universalzeit.

## Name

Date.getUTCMinutes( ) — Gibt das Minutenfeld zurück (Universalzeit)ECMAScript v1

## Überblick

     _date_.getUTCMinutes( )

### Rückgabewert

Das Minutenfeld von _`date`_ in Universalzeit. Der Rückgabewert ist ein Integer zwischen 0 und 59.

## Name

Date.getUTCMonth( ) — Gibt das Monat-des-Jahrs-Feld eines Date-Objekts zurück (Universalzeit)ECMAScript v1

## Überblick

     _date_.getUTCMonth( )

### Rückgabewert

Der Monat des Jahres, der sich ergibt, wenn _`date`_ in Universalzeit ausgedrückt wird. Der Rückgabewert ist ein Integer zwischen 0 (Januar) und 11 (Dezember). Beachten Sie, dass das Date-Objekt den ersten Tag des Monats zwar als 1, aber den ersten Monat des Jahres als 0 darstellt.

## Name

Date.getUTCSeconds( ) — Gibt das Sekundenfeld zurück (Universalzeit)ECMAScript v1

## Überblick

     _date_.getUTCSeconds( )

### Rückgabewert

Das Sekundenfeld von _`date`_ in Universalzeit. Der Rückgabewert ist ein Integer zwischen 0 und 59.

## Name

Date.getYear( ) — Gibt das Jahresfeld zurückECMAScript v1; veraltet seit ECMAScript v3

## Überblick

     _date_.getYear( )

### Rückgabewert

Das Jahresfeld des angegebenen Date-Objekts _`date`_ minus 1900.

## Beschreibung

`getYear( )` gibt das Jahresfeld eines angegebenen Date-Objekts minus 1900 zurück. Seit ECMAScript v3 ist es für standardkonforme JavaScript-Implementierungen nicht mehr erforderlich. Verwenden Sie stattdessen `getFullYear( )`.

## Name

Date.parse( ) — Parst einen Datums/Uhrzeit-StringECMAScript v1

## Überblick

    Date.parse( _date_ )

### Argumente

 _`date`_

Ein String, der den zu parsenden Datums- und Uhrzeitwert enthält.

### Rückgabewert

Die Anzahl Millisekunden zwischen dem angegebenen Zeitpunkt und Mitternacht des 1. Januar 1970 GMT.

## Beschreibung

`Date.parse( )` ist eine statische Methode von Date. Sie wird immer als `Date.parse( )` über den `Date`-Konstruktor und nicht als _`date`_`.parse( )` über ein Date-Objekt aufgerufen. `Date.parse( )` nimmt ein einziges String-Argument entgegen. Die Methode parst das darin enthaltene Datum und gibt es im Millisekundenformat zurück. Dieses Format kann entweder direkt benutzt werden, oder man verwendet es, um ein neues Date-Objekt zu erzeugen oder um das Datum in einem vorhandenen Date-Objekt mit der Methode `Date.setTime( )` zu setzen.

Der ECMAScript-Standard legt das Format der Strings nicht fest, die mit `Date.parse( )` geparst werden. Er besagt lediglich, dass diese Methode die von `Date.toString( )` und `Date.toUTC-String( )` zurückgegebenen Strings parsen kann. Leider formatieren diese Funktionen Datumswerte in einer implementierungsabhängigen Weise, sodass Datumsangaben generell nicht in einer Form geschrieben werden können, die auch garantiert von allen JavaScript-Implementierungen verstanden wird.

## Siehe auch

`Date`, `Date.setTime( )`, `Date.toGMTString( )`, `Date.UTC( )`

## Name

Date.setDate( ) — Setzt das Monatstag-Feld eines Date-ObjektsECMAScript v1

## Überblick

     _date_.setDate( _tag_des_monats_ )

### Argumente

 _`tag_des_monats`_

Ein Integer zwischen 1 und 31, der als neuer (in Ortszeit ausgedrückter) Wert des Monatstag-Felds von _`date`_ verwendet wird.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums. Vor der ECMAScript-Standardisierung gab diese Methode gar nichts zurück.

## Name

Date.setFullYear( ) — Setzt das Jahr und optional den Monat und das DatumECMAScript v1

## Überblick

     _date_.setFullYear( _jahr_ )
    _date_.setFullYear( _jahr_ , _monat_ )
    _date_.setFullYear( _jahr_ , _monat_ , _tag_ )

### Argumente

 _`jahr`_

Das Jahr in Ortszeit, das in _`date`_ gesetzt werden soll. Dieses Argument sollte ein Integer sein, der auch das Jahrhundert angibt, also z.B. 1999. Es sollte keine Abkürzung wie 99 sein.

 _`monat`_

Ein optionaler Integer zwischen 0 und 11, der als neuer Wert (in Ortszeit) des Monatsfelds von _`date`_ benutzt wird.

 _`tag`_

Ein optionaler Integer zwischen 1 und 31, der als neuer Wert (in Ortszeit) des Monatstag-Felds von _`date`_ benutzt wird.

### Rückgabewert

Die interne Millisekundendarstellung des angepassten Datums.

## Name

Date.setHours( ) — Setzt die Felder für Stunden, Minuten, Sekunden und Millisekunden eines Date-ObjektsECMAScript v1

## Überblick

     _date_.setHours( _stunden_ )
    _date_.setHours( _stunden_ , _minuten_ )
    _date_.setHours( _stunden_ , _minuten_ , _sekunden_ )
    _date_.setHours( _stunden_ , _minuten_ , _sekunden_ , _millis_ )

### Argumente

 _`stunden`_

Ein Integer zwischen 0 (Mitternacht) und 23 (elf Uhr nachts) Ortszeit, der als neuer Stundenwert von _`date`_ eingesetzt wird.

 _`minuten`_

Ein optionaler Integer zwischen 0 und 59, der als neuer Wert (in Ortszeit) des Minutenfelds von _`date`_ verwendet wird. Dieses Argument wird erst seit der ECMAScript-Standardisierung unterstützt.

 _`sekunden`_

Ein optionaler Integer zwischen 0 und 59, der als neuer Wert (in Ortszeit) des Sekundenfelds von _`date`_ verwendet wird. Dieses Argument wird erst seit der ECMAScript-Standardisierung unterstützt.

 _`millis`_

Ein optionaler Integer zwischen 0 und 999, der als neuer Wert (in Ortszeit) des Millisekundenfelds von _`date`_ verwendet wird. Dieses Argument wird erst seit der ECMAScript-Standardisierung unterstützt.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums. Vor der ECMAScript-Standardisierung gab diese Methode nichts zurück.

## Name

Date.setMilliseconds( ) — Setzt das Millisekundenfeld eines Date-ObjektsECMAScript v1

## Überblick

     _date_.setMilliseconds( _millis_ )

### Argumente

 _`millis`_

Das Millisekundenfeld (in Ortszeit), das in _`date`_ gesetzt werden soll. Dieses Argument sollte ein Integer zwischen 0 und 999 sein.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums.

## Name

Date.setMinutes( ) — Setzt die Felder für Minuten, Sekunden und Millisekunden eines Date-ObjektsECMAScript v1

## Überblick

     _date_.setMinutes( _minuten_ )
    _date_.setMinutes( _minuten_ , _sekunden_ )
    _date_.setMinutes( _minuten_ , _sekunden_ , _millis_ )

### Argumente

 _`minuten`_

Ein Integer zwischen 0 und 59, der als Minutenwert (in Ortszeit) des Date-Objekts _`date`_ gesetzt wird.

 _`sekunden`_

Ein optionaler Integer zwischen 0 und 59, der als neuer Wert (in Ortszeit) des Sekundenfelds von _`date`_ verwendet wird. Dieses Argument wurde vor der ECMAScript-Standardisierung nicht unterstützt.

 _`millis`_

Ein optionaler Integer zwischen 0 und 999, der als neuer Wert (in Ortszeit) des Millisekundenfelds von _`date`_ verwendet wird. Dieses Argument wurde vor der ECMAScript-Standardisierung nicht unterstützt.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums. Vor der ECMAScript-Standardisierung gab diese Methode nichts zurück.

## Name

Date.setMonth( ) — Setzt die Felder für Monat und Tag eines Date-ObjektsECMAScript v1

## Überblick

     _date_.setMonth( _monat_ )
    _date_.setMonth( _monat_ , _tag_ )

### Argumente

 _`monat`_

Ein Integer zwischen 0 (Januar) und 11 (Dezember), der als Monatswert (in Ortszeit) für das Date-Objekt _`date`_ gesetzt wird. Beachten Sie, dass Monate beginnend mit 0 und Monatstage beginnend mit 1 nummeriert werden.

 _`tag`_

Ein optionaler Integer zwischen 1 und 31, der als neuer Wert (in Ortszeit) des Monatstag-Felds von _`date`_ verwendet wird. Dieses Argument wurde vor der ECMAScript-Standardisierung nicht unterstützt.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums. Vor der ECMAScript-Standardisierung gab diese Methode nichts zurück.

## Name

Date.setSeconds( ) — Setzt die Felder für Sekunden und Millisekunden eines DateECMAScript v1

## Überblick

     _date_.setSeconds( _sekunden_ )
    _date_.setSeconds( _sekunden_ , _millis_ )

### Argumente

 _`sekunden`_

Ein Integer zwischen 0 und 59, der als Sekundenwert für das Date-Objekt _`date`_ gesetzt wird.

 _`millis`_

Ein optionaler Integer zwischen 0 und 999, der als neuer Wert (in Ortszeit) für das Millisekundenfeld von _`date`_ verwendet wird. Dieses Argument wurde vor der ECMAScript-Standardisierung nicht unterstützt.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums. Vor der ECMAScript-Standardisierung gab diese Methode nichts zurück.

## Name

Date.setTime( ) — Setzt ein Date in MillisekundenECMAScript v1

## Überblick

     _date_.setTime( _millisekunden_ )

### Argumente

 _`millisekunden`_

Die Anzahl der Millisekunden zwischen dem gewünschten Zeitpunkt und Mitternacht des 1. Januar 1970 GMT. Ein Millisekundenwert dieses Typs kann auch dem `Date( )`-Konstruktor übergeben und durch Aufruf der Methoden `Date.UTC( )` und `Date.parse( )` beschafft werden. Wenn Sie ein Datum in diesem Millisekundenformat darstellen, machen Sie es zeitzonenunabhängig.

### Rückgabewert

Das Argument _`millisekunden`_. Vor der ECMAScript-Standardisierung gab diese Methode nichts zurück.

## Name

Date.setUTCDate( ) — Setzt das Monatstag-Feld eines Date-Objekts (Universalzeit)ECMAScript v1

## Überblick

     _date_.setUTCDate( _tag_des_monats_ )

### Argumente

 _`tag_des_monats`_

Der Monatstag in Universalzeit, der in _`date`_ gesetzt werden soll. Dieses Argument sollte ein Integer zwischen 1 und 31 sein.

### Rückgabewert

Die interne Millisekundendarstellung des angepassten Datums.

## Name

Date.setUTCFullYear( ) — Setzt Jahr, Monat und Tag eines Date-Objekts (Universalzeit)ECMAScript v1

## Überblick

     _date_.setUTCFullYear( _jahr_ )
    _date_.setUTCFullYear( _jahr_ , _monat_ )
    _date_.setUTCFullYear( _jahr_ , _monat_ , _tag_
    )

### Argumente

 _`jahr`_

Das Jahr in Universalzeit, das in _`date`_ gesetzt werden soll. Dieses Argument sollte ein Integer sein, der auch das Jahrhundert einschließt, z.B. 1999, und keine Abkürzung wie etwa 99.

 _`monat`_

Ein optionaler Integer zwischen 0 und 11, der als neuer (Universalzeit-)Wert des Monatsfelds von _`date`_ verwendet wird. Beachten Sie, dass die Nummerierung von Monaten bei 0 beginnt, während die Nummerierung von Monatstagen mit 1 beginnt.

 _`tag`_

Ein optionaler Integer zwischen 1 und 31, der als neuer (Universalzeit-)Wert des Monatstag-Felds von _`date`_ verwendet wird.

### Rückgabewert

Die interne Millisekundendarstellung des angepassten Datums.

## Name

Date.setUTCHours( ) — Setzt die Felder für Stunden, Minuten, Sekunden und Millisekunden eines Date-Objekts (Universalzeit)ECMAScript v1

## Überblick

     _date_.setUTCHours( _stunden_ )
    _date_.setUTCHours( _stunden_ , _minuten_ )
    _date_.setUTCHours( _stunden_ , _minuten_ , _sekunden_ )
    _date_.setUTCHours( _stunden_ , _minuten_ , _sekunden_ , _millis_ )

### Argumente

 _`stunden`_

Das Stundenfeld in Universalzeit, das in _`date`_ gesetzt werden soll. Dieses Argument sollte ein Integer zwischen 0 (Mitternacht) und 23 (elf Uhr nachts) sein.

 _`minuten`_

Ein optionaler Integer zwischen 0 und 59, der als neuer Wert (in Universalzeit) des Minutenfelds von _`date`_ verwendet wird.

 _`seconds`_

Ein optionaler Integer zwischen 0 und 59, der als neuer Wert (in Universalzeit) des Sekundenfelds von _`date`_ verwendet wird.

 _`millis`_

Ein optionaler Integer zwischen 0 und 999, der als neuer Wert (in Universalzeit) des Millisekundenfelds von _`date`_ verwendet wird.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums.

## Name

Date.setUTCMilliseconds( ) — Setzt das Millisekundenfeld eines Date-Objekts (Universalzeit)ECMAScript v1

## Überblick

     _date_.setUTCMilliseconds( _millis_ )

### Argumente

 _`millis`_

Das Millisekundenfeld in Universalzeit, das in _`date`_ gesetzt werden soll. Dieses Argument sollte ein Integer zwischen 0 und 999 sein.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums.

## Name

Date.setUTCMinutes( ) — Setzt die Felder für Minuten, Sekunden und Millisekunden eines Date-Objekts (Universalzeit)ECMAScript v1

## Überblick

     _date_.setUTCMinutes( _minuten_ )
    _date_.setUTCMinutes( _minuten_ , _sekunden_ )
    _date_.setUTCMinutes( _minuten_ , _sekunden_ , _millis_ )

### Argumente

 _`minuten`_

Das Minutenfeld in Universalzeit, das in _`date`_ gesetzt werden soll. Dieses Argument sollte ein Integer zwischen 0 und 59 sein.

 _`sekunden`_

Ein optionaler Integer zwischen 0 und 59, der als der neue Wert (in Universalzeit) des Sekundenfelds von _`date`_ verwendet wird.

 _`millis`_

Ein optionaler Integer zwischen 0 und 999, der als der neue Wert (in Universalzeit) des Millisekundenfelds von _`date`_ verwendet wird.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums.

## Name

Date.setUTCMonth( ) — Setzt die Felder für Monat und Tag eines Date-Objekts (Universalzeit)ECMAScript v1

## Überblick

     _date_.setUTCMonth( _monat_ )
    _date_.setUTCMonth( _monat_ , _tag_ )

### Argumente

 _`monat`_

Der Monat in Universalzeit, der in _`date`_ gesetzt werden soll. Dieses Argument sollte ein Integer zwischen 0 (Januar) und 11 (Dezember) sein. Beachten Sie, dass die Monate beginnend mit 0 und die Monatstage beginnend mit 1 nummeriert sind.

 _`tag`_

Ein optionaler Integer zwischen 1 und 31, der als neuer Wert (in Universalzeit) des Monatstag-Felds von _`date`_ benutzt wird.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums.

## Name

Date.setUTCSeconds( ) — Setzt die Felder für Sekunden und Millisekunden eines Date-Objekts (Universalzeit)ECMAScript v1

## Überblick

     _date_.setUTCSeconds( _sekunden_ )
    _date_.setUTCSeconds( _sekunden_ , _millis_ )

### Argumente

 _`sekunden`_

Das Sekundenfeld in Universalzeit, das in _`date`_ gesetzt werden soll. Dieses Argument sollte ein Integer zwischen 0 und 59 sein.

 _`millis`_

Ein optionaler Integer zwischen 0 und 999, der als neuer Wert (in Universalzeit) des Millisekundenfelds von _`date`_ benutzt wird.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums.

## Name

Date.setYear( ) — Setzt das Jahresfeld eines Date-ObjektsECMAScript v1; veraltet seit ECMAScript v3

## Überblick

     _date_.setYear( _jahr_ )

### Argumente

 _`jahr`_

Ein Integer, der als Jahreswert (in Ortszeit) für das Date-Objekt _`date`_ gesetzt wird. Wenn dieser Wert zwischen 0 und einschließlich 99 liegt, wird 1900 hinzuaddiert, und der Wert wird als Jahr zwischen 1900 und 1999 behandelt.

### Rückgabewert

Die Millisekundendarstellung des angepassten Datums. Vor der ECMAScript-Standardisierung gab diese Methode nichts zurück.

## Beschreibung

`setYear( )` setzt das Jahresfeld eines angegebenen Date-Objekts und bietet für die Jahre 1900 bis 1999 ein spezielles Verhalten.

Seit ECMAScript v3 ist diese Funktion für standardkonforme JavaScript-Implementierungen nicht mehr erforderlich. Verwenden Sie stattdessen `setFullYear( )`.

## Name

Date.toDateString( ) — Gibt den Datumsteil eines Date-Objekts als String zurückECMAScript v3

## Überblick

     _date_.toDateString( )

### Rückgabewert

Eine implementierungsabhängige, für Menschen lesbare Darstellung des Datumsteils von _`date`_ , ausgedrückt in Ortszeit.

## Siehe auch

`Date.toLocaleDateString( )`, `Date.toLocaleString( )`, `Date.toLocaleTimeString( )`, `Date.to String( )`, `Date.toTimeString( )`

## Name

Date.toGMTString( ) — Konvertiert ein Date-Objekt in einen Universalzeit-StringECMAScript v1; veraltet seit ECMAScript v3

## Überblick

     _date_.toGMTString( )

### Rückgabewert

Eine String-Darstellung des Zeitpunkts, der durch das Date-Objekt _`date`_ spezifiziert ist. Das Datum wird von der Ortszeit in GMT konvertiert, bevor es in einen String umgewandelt wird.

## Beschreibung

`toGMTString( )` wurde durch die identische Methode `Date.toUTCString( )` ersetzt.

Seit ECMAScript v3 brauchen standardkonforme JavaScript-Implementierungen diese Methode nicht mehr zur Verfügung zu stellen. Verwenden Sie stattdessen `toUTCString( )`.

## Siehe auch

`Date.toUTCString( )`

## Name

Date.toLocaleDateString( ) — Gibt den Datumsteil eines Date-Objekts als String im ortsüblichen Format ausECMAScript v3

## Überblick

     _date_
    .toLocaleDateString( )

### Rückgabewert

Eine implementierungsabhängige, für Menschen lesbare Darstellung des Datumsteils von _`date`_ in Ortszeit und im ortsüblichen Format.

## Siehe auch

`Date.toDateString( )`, `Date.toLocaleString( )`, `Date.toLocaleTimeString( )`, `Date.toString( )`, `Date.toTimeString( )`

## Name

Date.toLocaleString( ) — Konvertiert ein Date-Objekt in einen String im ortsüblichen FormatECMAScript v1

## Überblick

     _date_.toLocaleString( )

### Rückgabewert

Eine String-Darstellung des durch _`date`_ spezifizierten Zeitpunkts. Datum und Uhrzeit werden in Ortszeit und im ortsüblichen Format dargestellt.

## Verwendung

`toLocaleString( )` konvertiert ein Datum gemäß der Ortszeit in einen String. Außerdem hält sich diese Methode bei der Formatierung von Datum und Uhrzeit an die lokalen Gepflogenheiten, sodass sich das Format von Plattform zu Plattform und von Land zu Land ändern kann. `toLocaleString( )` gibt den String zurück, der wahrscheinlich dem vom Benutzer bevorzugten Datums- und Uhrzeitformat entspricht.

## Siehe auch

`Date.toLocaleDateString( )`, `Date.toLocaleTimeString( ), Date.toString( )`, `Date.toUTCString( )`

## Name

Date.toLocaleTimeString( ) — Gibt den Uhrzeitteil eines Date-Objekts als String im ortsüblichen Format zurückECMAScript v3

## Überblick

     _date_.toLocaleTimeString( )

### Rückgabewert

Eine implementierungsabhängige, für Menschen lesbare Darstellung des Uhrzeitteils von _`date`_ in Ortszeit und im ortsüblichen Format.

## Siehe auch

`Date.toDateString( )`, `Date.toLocaleDateString( )`, `Date.toLocaleString( )`, `Date.toString( )`, `Date.toTimeString( )`

## Name

Date.toString( ) — Konvertiert ein Date-Objekt in einen StringECMAScript v1Überschreibt Object.toString( )

## Überblick

     _date_.toString( )

### Rückgabewert

Eine für Menschen lesbare String-Darstellung von _`date`_ in Ortszeit.

## Beschreibung

`toString( )` gibt eine für Menschen lesbare, implementierungsabhängige String-Darstellung von _`date`_ zurück. Im Gegensatz zu `toUTCString( )` drückt `toString( )` das Datum in Ortszeit aus, und im Gegensatz zu `toLocaleString( )` gibt `toString( )` das Datum und die Uhrzeit möglicherweise nicht in einem locale-spezifischen Format zurück.

## Siehe auch

`Date.parse( )`, `Date.toDateString( )`, `Date.toLocaleString( )`, `Date.toTimeString( )`, `Date. toUTCString( )`

## Name

Date.toTimeString( ) — Gibt den Uhrzeitteil eines Date-Objekts als String zurückECMAScript v3

## Überblick

     _date_.toTimeString( )

### Rückgabewert

Eine implementierungsabhängige, für Menschen lesbare String-Darstellung des Uhrzeitteils eines _`date`_ , ausgedrückt in Ortszeit.

## Siehe auch

`Date.toString( )`, `Date.toDateString( )`, `Date.toLocaleDateString( )`, `Date.toLocaleString( )`, `Date.toLocaleTimeString( )`

## Name

Date.toUTCString( ) — Konvertiert ein Date-Objekt in einen String (Universalzeit)ECMAScript v1

## Überblick

     _date_.toUTCString( )

### Rückgabewert

Eine für Menschen lesbare String-Darstellung von _`date`_ in Universalzeit.

## Beschreibung

`toUTCString( )` gibt einen implementierungsabhängigen String zurück, der _`date`_ in Universalzeit darstellt.

## Siehe auch

`Date.toLocaleString( )`, `Date.toString( )`

## Name

Date.UTC( ) — Konvertiert eine Date-Angabe in MillisekundenECMAScript v1

## Überblick

    Date.UTC( _jahr_ , _monat_ , _tag_ , _stunden_ , _minuten_ , _sekunden_ , _ms_ )

### Argumente

 _`jahr`_

Vierstellige Jahresangabe. Wenn dieses Argument zwischen 0 und einschließlich 99 liegt, wird 1900 hinzuaddiert, und es wird als eine Jahreszahl zwischen 1900 und 1999 behandelt.

 _`monat`_

Der Monat als Integer zwischen 0 (Januar) und 11 (Dezember).

 _`tag`_

Der Monatstag als Integer zwischen 1 und 31. Beachten Sie, dass dieses Argument als kleinsten Wert 1 verwendet, während andere Argumente 0 als kleinsten Wert haben. Dieses Argument ist optional.

 _`stunden`_

Die Stunde als Integer zwischen 0 (Mitternacht) und 23 (elf Uhr nachts). Dieses Argument ist optional.

 _`minuten`_

Die Minuten der Stunde, angegeben als Integer zwischen 0 und 59. Dieses Argument ist optional.

 _`sekunden`_

Die Sekunden der Minute, angegeben als Integer zwischen 0 und 59. Dieses Argument ist optional.

 _`ms`_

Die Anzahl der Millisekunden, angegeben als Integer zwischen 0 und 999. Dieses Argument ist optional und wird erst seit der ECMAScript-Standardisierung berücksichtigt.

### Rückgabewert

Die Millisekundendarstellung des angegebenen Zeitpunkts in Universalzeit. Folglich gibt diese Methode die Millisekunden zurück, die zwischen Mitternacht am 1. Januar 1970 GMT und dem angegebenen Zeitpunkt verstrichen sind.

## Beschreibung

`Date.UTC( )` ist eine statische Methode, die über den `Date( )`-Konstruktor und nicht über ein einzelnes Date-Objekt aufgerufen wird.

Die Argumente von `Date.UTC( )` geben ein Datum und eine Uhrzeit an und werden als UTC (Universal Coordinated Time) interpretiert, also als GMT-Zeitangabe. Die angegebene UTC-Zeit wird in das Millisekundenformat konvertiert, das von der Konstruktormethode `Date( )` und der Methode `Date.setTime( )` verstanden wird.

Die Konstruktormethode `Date( )` kann dieselben Datums- und Uhrzeitargumente entgegennehmen wie `Date.UTC( )`. Der Unterschied besteht darin, dass der Konstruktor `Date( )` von der Ortszeit ausgeht, während `Date.UTC( )` die Universalzeit (GMT) zugrunde legt. Mit Code wie diesem können Sie ein Date-Objekt anlegen, das eine UTC-Zeitangabe verwendet:

    d = new Date(Date.UTC(1996, 4, 8, 16, 30));

## Siehe auch

`Date`, `Date.parse( )`, `Date.setTime( )`

## Name

Date.valueOf( ) — Konvertiert ein Date-Objekt in MillisekundenECMAScript v1Überschreibt Object.valueOf( )

## Überblick

     _date_.valueOf( )

### Rückgabewert

Die Millisekundendarstellung von _`date`_. Der Rückgabewert ist derselbe wie bei `Date. getTime( )`.

## Name

decodeURI( ) — Entfernt Escape-Zeichen aus einer URIECMAScript v3

## Überblick

    decodeURI( _uri_ )

### Argumente

 _`uri`_

Ein String, der eine codierte URI oder einen anderen Text enthält, der decodiert werden soll.

### Rückgabewert

Eine Kopie von _`uri`_ , in der hexadezimale Escape-Sequenzen durch die von diesen dargestellten Zeichen ersetzt wurden.

### Exceptions

`URIError`

Besagt, dass eine oder mehrere Escape-Sequenzen in _`uri`_ nicht wohlgeformt sind und daher nicht korrekt decodiert werden können.

## Beschreibung

`decodeURI( )` ist eine globale Funktion, die eine decodierte Kopie ihres `uri`-Arguments zurückliefert. Sie macht die von `encodeURI( )` vorgenommene Codierung rückgängig. Weitere Einzelheiten finden Sie unter `encodeURI( )`.

## Siehe auch

`decodeURIComponent( )`, `encodeURI( )`, `encodeURIComponent( )`, `escape( )`, `unescape( )`

## Name

decodeURIComponent( ) — Entfernt Escape-Zeichen aus einem URI-BestandteilECMAScript v3

## Überblick

    decodeURIComponent( _s_ )

### Argumente

 _`s`_

Ein String, der einen codierten URI-Bestandteil oder anderen zu decodierenden Text enthält.

### Rückgabewert

Eine Kopie von _`s`_ , in der hexadezimale Escape-Sequenzen durch die Zeichen ersetzt werden, die sie repräsentieren.

### Exceptions

`URIError`

Zeigt an, dass eine oder mehrere der Escape-Sequenzen in _`s`_ nicht wohlgeformt sind und daher auch nicht korrekt decodiert werden können.

## Beschreibung

`decodeURIComponent( )` ist eine globale Funktion, die eine decodierte Kopie ihres Arguments _`s`_ zurückgibt. Sie macht die von `encodeURIComponent( )` vorgenommene Codierung rückgängig. Näheres dazu finden Sie im Referenzeintrag zu `encodeURIComponent( )`.

## Siehe auch

`decodeURI( )`, `encodeURI( )`, `encodeURIComponent( )`, `escape( )`, `unescape( )`

## Name

encodeURI( ) — Ersetzt Zeichen in einer URI durch Escape-SequenzenECMAScript v3

## Überblick

    encodeURI( _uri_ )

### Argumente

 _`uri`_

Ein String, der die URI oder einen anderen zu codierenden Text enthält.

### Rückgabewert

Eine Kopie von _`uri`_ , in der bestimmte Zeichen durch hexadezimale Escape-Sequenzen ersetzt wurden.

### Exceptions

`URIError`

Zeigt an, dass _`uri`_ nicht-wohlgeformte Unicode-Surrogatpaare enthält und nicht codiert werden kann.

## Beschreibung

`encodeURI( )` ist eine globale Funktion, die eine codierte Kopie ihres _`uri`_ -Arguments zurückgibt. ASCII-Buchstaben und Ziffern werden ebenso wenig codiert wie die folgenden ASCII-Interpunktionszeichen:

    - _ . ! ~ * ' ( )

Da `encodeURI( )` dafür gedacht ist, vollständige URIs zu codieren, werden auch die folgenden ASCII-Interpunktionszeichen, die in URIs eine Sonderbedeutung haben, nicht mit Escape-Zeichen versehen:

    ; / ? : @ & = + $, #

Alle anderen Zeichen in _`uri`_ werden ersetzt, indem jedes Zeichen in seine UTF-8-Codierung konvertiert und dann jedes der resultierenden ein, zwei oder drei Bytes durch eine hexadezimale Escape-Sequenz der Form `%xx` codiert wird. Bei diesem Codierschema werden ASCII-Zeichen durch eine einzige Escape-Sequenz der Form `%xx` ersetzt. Zeichen mit einer Codierung zwischen `\u0080` und `\u07ff` werden durch zwei Escape-Sequenzen ersetzt, und alle anderen 16-Bit-Unicode-Zeichen werden durch drei Escape-Sequenzen ersetzt.

Wenn Sie eine URI mit dieser Methode codieren, müssen Sie ganz sicher sein, dass keine der URI-Komponenten (wie z.B. der Abfrage-String) URI-Trennzeichen wie etwa ? oder # enthält. Wenn die Bestandteile solche Zeichen enthalten müssen, sollten Sie jeden Bestandteil separat mit `encodeURIComponent( )` codieren.

Mit `decodeURI( )` können Sie die Codierung, die diese Methode vornimmt, rückgängig machen. Vor der ECMAScript v3-Standardisierung konnten Sie die (mittlerweile veralteten) Methoden `escape( )` und `unescape( )` verwenden, um eine ähnliche Codierung und Decodierung vorzunehmen.

## Beispiel

    // Rückgabe: http://www.isp.com/app.cgi?arg1=1&arg2=hello%20world
    encodeURI("http://www.isp.com/app.cgi?arg1=1&arg2=hello world");
    encodeURI("\u00a9"); // Das Copyright-Zeichen wird als %C2%A9 codiert

## Siehe auch

`decodeURI( )`, `decodeURIComponent( )`, `encodeURIComponent( )`, `escape( )`, `unescape( )`

## Name

encodeURIComponent( ) — Ersetzt Zeichen in einem URI-Bestandteil durch Escape-SequenzenECMAScript v3

## Überblick

    encodeURIComponent( _s_ )

### Argumente

 _`s`_

Ein String, der einen Teil einer URI oder eines anderen zu codierenden Texts enthält.

### Rückgabewert

Eine Kopie von _`s`_ , in der bestimmte Zeichen durch hexadezimale Escape-Sequenzen ersetzt wurden.

### Exceptions

`URIError`

Zeigt an, dass _`s`_ nicht-wohlgeformte Unicode-Surrogatpaare enthält und nicht codiert werden kann.

## Beschreibung

`encodeURIComponent( )` ist eine globale Funktion, die eine codierte Kopie ihres Arguments _`s`_ zurückgibt. ASCII-Buchstaben und Ziffern werden ebenso wenig codiert wie die folgenden ASCII-Interpunktionszeichen:

    - _ . ! ~ * ' ( )

Alle anderen Zeichen einschließlich der Interpunktionszeichen wie /, : und #, mit denen die einzelnen URI-Bestandteile voneinander getrennt werden, werden durch eine oder mehrere hexadezimale Escape-Sequenzen ersetzt. Unter `encodeURI( )` finden Sie eine Beschreibung des verwendeten Codierungsschemas.

Es gibt einen Unterschied zwischen `encodeURIComponent( )` und `encodeURI( )`: Die Methode `encodeURIComponent( )` geht davon aus, dass ihr Argument ein Teil einer URI ist (z.B. Protokoll, Hostname, Pfad oder Abfrage-String). Daher setzt sie für die Interpunktionszeichen, die als Trennzeichen zwischen den einzelnen Teilen einer URI dienen, Escape-Sequenzen ein.

## Beispiel

    encodeURIComponent("hello world?"); // Rückgabe: hello%20world%3F

## Siehe auch

`decodeURI( )`, `decodeURIComponent( )`, `encodeURI( )`, `escape( )`, `unescape( )`

## Name

Error — Eine generische ExceptionECMAScript v3Object → Error

## Konstruktor

    new Error( )
    new Error( _meldung_ )

### Argumente

 _`meldung`_

Eine optionale Fehlermeldung, die Einzelheiten über die Exception mitteilt.

### Rückgabewert

Ein neu erzeugtes Error-Objekt. Wenn das Argument _`meldung`_ angegeben ist, verwendet das Error-Objekt dieses als Wert seiner `message`-Eigenschaft. Andernfalls verwendet es als Wert dieser Eigenschaft einen implementierungsabhängigen Standard-String. Wenn der `Error( )`-Konstruktor als Funktion ohne den `new`-Operator aufgerufen wird, verhält er sich genauso, wie wenn er mit dem `new`-Operator aufgerufen wird.

## Eigenschaften

`message`

Eine Fehlermeldung, die Einzelheiten über die Exception angibt. Diese Eigenschaft speichert den String, der an den Konstruktor übergeben wurde, oder einen implementierungsspezifischen Standard-String.

`name`

Ein String, der den Exception-Typ angibt. Bei Instanzen der Klasse Error und ihrer Unterklassen gibt diese Eigenschaft den Namen des Konstruktors an, mit dem die Instanz erzeugt wurde.

## Methoden

`toString( )`

Gibt einen je nach Implementierung definierten String zurück, der dieses Error-Objekt repräsentiert.

## Beschreibung

Instanzen der Klasse Error stellen Fehler oder Exceptions dar, die in der Regel zusammen mit den Anweisungen `throw` und `try/catch` benutzt werden. Die Eigenschaft `name` gibt den Exception-Typ an, und mit der Eigenschaft `message` können Sie Einzelheiten über den Fehler in einer für Menschen lesbaren Form angeben.

Der JavaScript-Interpreter löst nie ein Error-Objekt direkt aus, sondern verwendet immer Instanzen einer Unterklasse von Error, also beispielsweise SyntaxError oder RangeError. Bequem ist es, in eigenem Code Exceptions durch die Auslösung von Error-Objekten zu signalisieren oder einfach nur eine Fehlermeldung oder einen Fehlercode als String bzw. numerischen Grundtyp auszulösen.

Beachten Sie, dass die ECMAScript-Spezifikation eine `toString( )`-Methode für die Klasse Error definiert (die von allen Unterklassen von Error geerbt wird). Die Spezifikation fordert jedoch nicht, dass diese `toString( )`-Methode einen String mit dem Inhalt der Eigenschaft `message` zurückgibt. Daher dürfen Sie nicht davon ausgehen, dass `toString( )` ein Error-Objekt in einen für Menschen lesbaren String konvertiert. Um einem Benutzer eine Fehlermeldung anzuzeigen, sollten Sie explizit die Eigenschaften `name` und `message` des Error-Objekts verwenden.

## Beispiel

Eine Exception können Sie mit Code wie diesem signalisieren:

    function factorial(x) {
      if (x < 0) throw new Error("factorial: x muss >= 0 sein");
      if (x <= 1) return 1; else return x * factorial(x-1);
    }

Wenn Sie eine Exception abfangen, können Sie sie dem Benutzer mit folgendem Code anzeigen (der die clientseitige Methode `Window.alert( )` verwendet):

    try { &*(&/* hier wird ein Fehler ausgelöst */ }
    catch(e) {
      if (e instanceof Error) { // Instanz bzw. Unterklasse von Error?
        alert(e.name + ": " + e.message);
      }
    }

## Siehe auch

`EvalError`, `RangeError`, `ReferenceError`, `SyntaxError`, `TypeError`, `URIError`

## Name

Error.message — Eine für Menschen lesbare FehlermeldungECMAScript v3

## Überblick

     _error_.message

## Beschreibung

Die Eigenschaft `message` eines Error-Objekts (oder einer Unterklasseninstanz von Error) ist dazu da, einen für Menschen lesbaren String aufzunehmen, der Einzelheiten über den Fehler oder die Exception verrät. Wenn ein `message`-Argument an den `Error( )`-Konstruktor übergeben wird, so wird diese Meldung der Wert der Eigenschaft `message`. Wurde kein `message`-Argument übergeben, erbt ein Error-Objekt einen durch die Implementierung definierten Standardwert für diese Eigenschaft. Dieser Wert kann auch der leere String sein.

## Name

Error.name — Der Typ eines FehlersECMAScript v3

## Überblick

     _error_.name

## Beschreibung

Die Eigenschaft `name` eines Error-Objekts (oder irgendeiner Unterklasseninstanz von Error) gibt an, welcher Typ von Fehler oder Exception aufgetreten ist. Alle Error-Objekte erben diese Eigenschaft von ihrem Konstruktor. Der Wert der Eigenschaft ist der Name des Konstruktors. Also haben SyntaxError-Objekte die `name`-Eigenschaft »SyntaxError« und EvalError-Objekte die `name`-Eigenschaft »EvalError«.

## Name

Error.toString( ) — Konvertiert ein Error-Objekt in einen StringECMAScript v3Überschreibt Object.toString( )

## Überblick

     _error_.toString( )

### Rückgabewert

Ein implementierungsabhängiger String. Der ECMAScript-Standard sagt über den Rückgabewert dieser Methode nur aus, dass es sich um einen String handelt. Er verlangt nicht, dass der Rückgabe-String den Namen des Fehlers oder die Fehlermeldung enthalten muss.

## Name

escape( ) — Codiert einen StringECMAScript v1; veraltet in ECMAScript v3

## Überblick

    escape( _s_ )

### Argumente

 _`s`_

Der String, der mit Escape-Zeichen versehen oder codiert werden muss.

### Rückgabewert

Eine codierte Kopie von _`s`_ , in der bestimmte Zeichen durch hexadezimale Escape-Sequenzen ersetzt wurden.

## Beschreibung

`escape( )` ist eine globale Funktion. Sie gibt einen neuen String zurück, der eine codierte Version von _`s`_ enthält. Dabei wird der String _`s`_ selbst nicht verändert.

`escape( )` gibt einen String zurück, in dem alle Zeichen von _`s`_ außer den ASCII-Buchstaben, den Ziffern und den Interpunktionszeichen @, *, _, +, -, . und / durch Escape-Sequenzen der Form `%` _`xx`_ oder `%u` _`xxxx`_ ersetzt wurden (wobei _`x`_ eine Hexadezimalziffer darstellt). Die Unicode-Zeichen `\u0000` bis `\u00ff` werden durch die Escape-Sequenz `%` _`xx`_ und alle anderen Unicode-Zeichen durch die Escape-Sequenz `%u` _`xxxx`_ ersetzt.

Mit der Funktion `unescape( )` können Sie einen mit `escape( )` codierten String wieder decodieren.

Die Funktion `escape( )` wurde zwar in der ersten Version von ECMAScript noch übernommen, aber seit ECMAScript v3 war sie veraltet und gehört nicht mehr zum Standard. Implementierungen von ECMAScript werden diese Funktion zwar wahrscheinlich enthalten, aber sie müssen es nicht. Sie sollten statt `escape( )` die Methoden `encodeURI( )` und `encodeURIComponent( )` verwenden.

## Beispiel

    escape("Hello World!"); // Rückgabe: "Hello%20World%21"

## Siehe auch

`encodeURI( )`, `encodeURIComponent( )`

## Name

eval( ) — Führt JavaScript-Code in einem String ausECMAScript v1

## Überblick

    eval( _code_ )

### Argumente

 _`code`_

Ein String, der den auszuwertenden JavaScript-Ausdruck oder die auszuführenden Anweisungen enthält.

### Rückgabewert

Der Wert des ausgewerteten _`code`_ , falls vorhanden.

### Exceptions

`SyntaxError`

Besagt, dass _`code`_ kein zulässiges JavaScript enthält.

`EvalError`

Weist darauf hin, dass `eval( )` unzulässigerweise aufgerufen wurde, beispielsweise über einen anderen Bezeichner als »eval«. Im nächsten Abschnitt sehen Sie, welchen Beschränkungen diese Funktion unterliegt.

`Andere Exception`

Wenn der an `eval( )` übergebene JavaScript-Code eine Exception auslöst, gibt `eval( )` diese Exception an den Aufrufer weiter.

## Beschreibung

`eval( )` ist eine globale Methode, die einen String mit JavaScript-Code unter der aktuellen lexikalischen Geltung ausführt. Enthält _`code`_ eine oder mehrere JavaScript-Anweisungen, führt `eval( )` diese aus und gibt (falls vorhanden) den Wert zurück, den die letzte Anweisung lieferte. Wenn _`code`_ überhaupt keinen Wert zurückgibt, liefert `eval( )` den Wert `undefined`. Und wenn _`code`_ eine Exception auslöst, so übergibt `eval( )` diese dem Aufrufer.

`eval( )` ist für JavaScript zwar eine mächtige Fähigkeit, aber in der Praxis wird diese Methode selten benutzt. Ihr Einsatz bietet sich für Programme an, die als rekursive JavaScript-Interpreter fungieren, oder für Programme, die JavaScript-Code dynamisch generieren und auswerten.

Die meisten Funktionen und Methoden von JavaScript akzeptieren, wenn sie String-Argumente erwarten, auch andere Typen und konvertieren die betreffenden Argumente einfach in Strings, bevor sie weitermachen. `eval( )` verhält sich jedoch anders. Wenn das _`code`_ -Argument kein elementarer String ist, wird es nur unverändert zurückgegeben. Daher müssen Sie aufpassen, dass Sie nicht versehentlich statt eines elementaren Strings ein String-Objekt an `eval( )` übergeben.

Um die Implementierung effizienter zu machen, definiert der ECMAScript v3-Standard für `eval( )` eine ungewöhnliche Beschränkung. Eine ECMAScript-Implementierung darf einen EvalError auslösen, wenn Sie die Eigenschaft `eval` zu überschreiben versuchen oder die Methode `eval( )` einer anderen Eigenschaft zuweisen und sie über diese Eigenschaft aufzurufen versuchen.

## Beispiel

    eval("1+2"); // Gibt 3 zurück

    // Dieser Code nutzt clientseitige JavaScript-Methoden, um den Benutzer
    // aufzufordern, einen Ausdruck einzugeben, und die Ergebnisse
    // seiner Auswertung anzuzeigen.
    // Siehe die clientseitigen Methoden Window.alert( ) und Window.prompt( ).
    try {
      alert("Ergebnis: " + eval(prompt("Geben Sie einen Ausdruck ein:","")));
    }
    catch(exception) {
      alert(exception);
    }

    var myeval = eval; // Kann einen EvalError auslösen
    myeval("1+2");   // Kann einen EvalError auslösen

## Name

EvalError — Wird bei falscher Verwendung von eval( ) ausgelöstECMAScript v3Object → Error → EvalError

## Konstruktor

    new EvalError( )
    new EvalError( _meldung_ )

### Argumente

 _`meldung`_

Eine optionale Fehlermeldung, die Einzelheiten über die Exception verrät. Wenn dieses Argument angegeben ist, wird es als Wert der `message`-Eigenschaft des EvalError-Objekts verwendet.

### Rückgabewert

Ein neu erzeugtes EvalError-Objekt. Wenn das Argument _`meldung`_ angegeben ist, verwendet das Error-Objekt dieses als Wert seiner `message`-Eigenschaft; andernfalls verwendet es dazu einen implementierungsspezifischen Standard-String. Wenn der Konstruktor `EvalError( )` als Funktion ohne `new`-Operator aufgerufen wird, verhält er sich genauso, wie wenn er mit dem `new`-Operator aufgerufen wird.

## Eigenschaften

`message`

Eine Fehlermeldung, die Einzelheiten über die Exception verrät. Diese Eigenschaft speichert den String, der dem Konstruktor übergeben wurde, oder einen implementierungsspezifischen Standard-String. Siehe auch `Error.message`.

`name`

Ein String, der den Typ der Exception spezifiziert. Alle EvalError-Objekte erben den Wert »EvalError« für diese Eigenschaft.

## Beschreibung

Eine Instanz der Klasse EvalError kann ausgelöst werden, wenn die globale Funktion `eval( )` unter irgendeinem anderen Namen aufgerufen wird. Die Beschränkungen, denen ein Aufruf dieser Funktion unterliegt, werden unter `eval( )` erläutert. Einzelheiten zum Auslösen und Abfangen von Exceptions finden Sie unter `Error`.

## Siehe auch

`Error`, `Error.message`, `Error.name`

## Name

Function — Eine JavaScript-FunktionECMAScript v1Object → Function

## Überblick

    function _funktionsname_ ( _argumentnamenslist_ e) // Anweisung zur Funktionsdefinition
    {
      _body_
    }
    function ( _argumentnamenslist_ e) { _body_ } // Unbenanntes Funktionsliteral
    _funktionsname_ ( _argumentwertelist_ e)    // Funktionsaufruf

## Konstruktor

    new Function( _argumentnamen..._ , _body_ )

### Argumente

 _`argumentnamen...`_

Eine beliebige Anzahl String-Argumente, die je ein oder mehrere Argumente des zu erzeugenden Function-Objekts aufführen.

 _`body`_

Ein String, der den Funktionsbody spezifiziert. Er kann beliebig viele, durch Semikola getrennte JavaScript-Anweisungen enthalten und sich auf beliebige, vorher als Argumente für den Konstruktor angegebene Argumentnamen beziehen.

### Rückgabewert

Ein neu erzeugtes Function-Objekt. Ein Aufruf der Funktion führt den JavaScript-Code in _`body`_ aus.

### Exceptions

`SyntaxError`

Besagt, dass im _`body`_ -Argument oder in einem der _`argumentnamen`_ -Argumente ein JavaScript-Syntaxfehler war.

## Eigenschaften

`arguments[]`

Ein Array von Argumenten, die der Funktion übergeben wurden. Veraltet.

`caller`

Eine Referenz auf das Function-Objekt, das dieses hier aufrief, oder `null`, wenn die Funktion von Toplevel-Code aufgerufen wurde. Veraltet.

`length`

Die Anzahl benannter Argumente, die bei der Deklaration der Funktion angegeben wurden.

`prototype`

Ein Objekt, das für eine Konstruktorfunktion die Eigenschaften und Methoden definiert, die alle mit dieser Konstruktorfunktion erzeugten Objekte gemeinsam haben.

## Methoden

`apply( )`

Ruft eine Funktion als Methode eines angegebenen Objekts auf und übergibt ihr ein angegebenes Array mit Argumenten.

`call( )`

Ruft eine Funktion als Methode eines angegebenen Objekts auf und übergibt ihr die angegebenen Argumente.

`toString( )`

Gibt eine String-Darstellung der Funktion zurück.

## Beschreibung

Eine Funktion ist in JavaScript ein elementarer Datentyp. In Kapitel 8 wird erklärt, wie Sie Funktionen definieren und anwenden, und Kapitel 9 behandelt die damit verbundenen Themen Methoden, Konstruktoren und die `prototype`-Eigenschaft von Funktionen. Die vollständigen Einzelheiten finden Sie in diesen Kapiteln. Bachten Sie, dass Sie zwar mit dem hier beschriebenen Konstruktor `Function( )` Funktionsobjekte erzeugen können, aber effizient ist das nicht. Meist definieren Sie Funktionen besser mit einer Funktionsdefinitionsanweisung oder mit einem Funktionsliteral.

In JavaScript 1.1 und seinen Folgeversionen erhält der Body einer Funktion automatisch eine lokale Variable namens _`arguments`_ , die ein Arguments-Objekt referenziert. Dabei handelt es sich um ein Array von Werten, die der Funktion als Argumente übergeben werden. Bitte verwechseln Sie dies nicht mit der oben genannten, veralteten Eigenschaft `arguments[]`. Die Einzelheiten hierzu finden Sie im Referenzeintrag `Arguments`.

## Siehe auch

`Arguments`; Kapitel 8; Kapitel 9

## Name

Function.apply( ) — Ruft eine Funktion als Methode eines Objekts aufECMAScript v3

## Überblick

     _function_.apply( _thisobj_ , _args_ )

### Argumente

 _`thisobj`_

Das Objekt, auf das _`function`_ angewendet werden soll. Im Funktionsbody wird _`thisobj`_ zum Wert des Schlüsselworts `this`. Wenn dieses Argument `null` ist, wird das globale Objekt verwendet.

 _`args`_

Ein Array von Werten, die _`function`_ als Argumente übergeben werden.

### Rückgabewert

Der durch einen Aufruf von _`function`_ zurückgegebene Wert.

### Exceptions

`TypeError`

Wird ausgelöst, wenn diese Methode auf einem Objekt aufgerufen wird, das keine Funktion ist, oder wenn sie mit einem _`args`_ -Argument aufgerufen wird, das kein Array oder Arguments-Objekt ist.

## Beschreibung

`apply( )` ruft die angegebene _`function`_ auf, als sei sie eine Methode von _`thisobj`_ , und übergibt ihr die Argumente aus dem _`args`_ -Array. Sie gibt den Wert zurück, den der Funktionsaufruf liefert. Im Body der Funktion bezieht sich das Schlüsselwort `this` auf das Objekt _`thisobj`_.

Das Argument _`args`_ muss ein Array oder Arguments-Objekt sein. Wenn Sie die zu übergebenden Argumente einzeln und nicht als Array-Elemente angeben möchten, müssen Sie `Function.call( )` verwenden.

## Beispiel

    // Wende die Standardmethode Object.toString( ) auf ein Objekt an,
    // das sie mit einer eigenen Version der Methode überschreibt. Hier
    // gibt es keine Argumente.
    Object.prototype.toString.apply(o);

    // Rufe die Math.max( )-Methode mit apply auf, um das größte Element
    // eines Arrays zu finden. Hier spielt das erste Argument keine
    // Rolle.
    var data = [1,2,3,4,5,6,7,8];
    Math.max.apply(null, data);

## Siehe auch

`Function.call( )`

## Name

Function.arguments[] — An eine Funktion übergebene ArgumenteECMAScript v1; veraltet seit ECMAScript v3

## Überblick

     _function._ arguments[ _i_ ]
    _function._ arguments.length

## Beschreibung

Die `arguments`-Eigenschaft eines Function-Objekts ist ein Array der an die Funktion übergebenen Argumente. Sie ist nur während der Ausführung der Funktion definiert. `arguments. length` gibt die Anzahl der Array-Elemente an.

Diese Eigenschaft wurde durch das Arguments-Objekt ersetzt. Zwar unterstützt ECMAScript v1 noch die Eigenschaft `Function.arguments`, aber aus ECMAScript v3 wurde sie bereits herausgenommen, und standardkonforme Implementierungen brauchen sie nicht mehr zu unterstützen. Daher sollte sie in neuem JavaScript-Code nie benutzt werden.

## Siehe auch

`Arguments`

## Name

Function.call( ) — Ruft eine Funktion als Methode eines Objekts aufECMAScript v3

## Überblick

     _function._ call( _thisobj_ , _args..._ )

### Argumente

 _`thisobj`_

Das Objekt, auf dem _`function`_ aufgerufen wird. Im Funktionsbody wird _`thisobj`_ zum Wert des Schlüsselworts `this`. Wenn dieses Argument `null` ist, wird das globale Objekt verwendet.

 _`args...`_

Eine beliebige Anzahl Argumente, die an _`function`_ übergeben werden.

### Rückgabewert

Die Rückgabe eines _`function`_ -Aufrufs.

### Exceptions

`TypeError`

Tritt auf, wenn diese Methode auf einem Objekt aufgerufen wird, das keine Funktion ist.

## Beschreibung

`call( )` ruft die angegebene _`function`_ auf, als sei sie eine Methode von _`thisobj`_ , und übergibt ihr die Argumente, die in der Argumentliste auf _`thisobj`_ folgen. Der Rückgabewert von `call( )` ist der Rückgabewert des Funktionsaufrufs. Im Rumpf der Funktion verweist das Schlüsselwort `this` auf das _`thisobj`_ -Objekt.oder das globale Objekt, wenn _`thisobj`_ gleich `null` ist.

Verwenden Sie stattdessen `Function.apply( )`, wenn Sie die zu übergebenden Argumente als Array spezifizieren möchten.

## Beispiel

    // Rufe die Standardmethode Object.toString( ) auf einem Objekt auf,
    // das sie mit seiner eigenen Version der Methode überschreibt.
    // Hier gibt es keine Argumente.
    Object.prototype.toString.call(o);

## Siehe auch

`Function.apply( )`

## Name

Function.caller — Die Funktion, die diese hier aufriefJavaScript 1.0; veraltet seit ECMAScript

## Überblick

     _function._ caller

## Beschreibung

In frühen Versionen von JavaScript ist die `caller`-Eigenschaft eines Function-Objekts eine Referenz auf die Funktion, die die jetzige Funktion aufrief. Wurde die jetzige Funktion vom Toplevel eines JavaScript-Programms aufgerufen, so hat `caller` den Wert `null`. Diese Eigenschaft kann nur innerhalb der Funktion benutzt werden (d.h., die `caller`-Eigenschaft ist nur für eine Funktion definiert, solange diese ausgeführt wird).

`Function.caller` gehört nicht zum ECMAScript-Standard und muss von standardkonformen Implementierungen nicht unterstützt werden. Verwenden Sie es besser nicht.

## Name

Function.length — Die Anzahl der deklarierten ArgumenteECMAScript v1

## Überblick

     _function._ length

## Beschreibung

Die `length`-Eigenschaft einer Funktion gibt an, wie viele benannte Argumente beim Definieren der Funktion deklariert wurden. In Wirklichkeit kann die Funktion auch mit einer größeren oder kleineren Anzahl von Argumenten aufgerufen werden. Verwechseln Sie diese Eigenschaft eines Function-Objekts bitte nicht mit der `length`-Eigenschaft des Arguments-Objekts, die die Anzahl der tatsächlich an die Funktion übergebenen Argumente spezifiziert. Ein Beispiel finden Sie unter `Arguments.length`.

## Siehe auch

`Arguments.length`

## Name

Function.prototype — Der Prototyp für eine Klasse von ObjektenECMAScript v1

## Überblick

     _function._ prototype

## Beschreibung

Die Eigenschaft `prototype` wird verwendet, wenn eine Funktion als Konstruktor eingesetzt wird. Sie verweist auf ein Objekt, das als Prototyp für eine ganze Klasse von Objekten dient. Jedes mit diesem Konstruktor angelegte Objekt erbt alle Eigenschaften des von der Eigenschaft `prototype` referenzierten Objekts.

In Kapitel 9 werden Konstruktorfunktionen, die Eigenschaft `prototype` und die Definition von Klassen in JavaScript genau beschrieben.

## Siehe auch

Kapitel 9

## Name

Function.toString( ) — Konvertiert eine Funktion in einen StringECMAScript v1

## Überblick

     _function._ toString( )

### Rückgabewert

Ein String, der die Funktion repräsentiert.

### Exceptions

`TypeError`

Tritt auf, wenn diese Methode auf einem anderen als einem Function-Objekt aufgerufen wird.

## Beschreibung

Die `toString( )`-Methode des Function-Objekts konvertiert in einer implementierungsabhängigen Weise eine Funktion in einen String. In Netscape-Implementierungen wie den Implementierungen von Firefox und in dem IE gibt sie einen String mit gültigem JavaScript-Code zurück. Der Code enthält das Schlüsselwort `function`, eine Argumentliste, den vollständigen Funktionsbody usw. In diesen Implementierungen ist die Ausgabe der Methode `toString( )` eine gültige Eingabe für die globale Funktion `eval( )`. Dieses Verhalten wird von der Spezifikation nicht verlangt. Man sollte sich deswegen nicht darauf verlassen.

## Name

getClass( ) — Gibt die JavaClass eines JavaObjects zurückLiveConnect

## Überblick

    getClass( _javaobj_ )

### Argumente

 _`javaobj`_

Ein JavaObject-Objekt.

### Rückgabewert

Das JavaClass-Objekt von _`javaobj`_.

## Beschreibung

`getClass( )` ist eine Funktion, die ein JavaObject-Objekt ( _`javaobj`_ ) als Argument akzeptiert. Sie liefert das JavaClass-Objekt für das JavaObject zurück. Das heißt, sie liefert das JavaClass-Objekt für die Java-Klasse des Java-Objekts zurück, das das angebene JavaObject repräsentiert.

## Verwendung

Verwechseln Sie die JavaScript-Funktion `getClass( )` nicht mit der _getClass_ -Methode, die alle Java-Objekte haben. Gleichermaßen sollten Sie das JavaScript-JavaClass-Objekt nicht mit der Java-Klasse _java.lang.Class_ verwechseln.

Betrachten Sie das Java Rectangle-Objekt, das mit der folgenden Zeile erzeugt wird:

    var r = new java.awt.Rectangle( );

`r` ist eine JavaScript-Variable, die ein JavaObject-Objekt enthält. Rufen Sie mit ihr die JavaScript-Funktion `getClass( )` auf, erhalten Sie ein JavaClass-Objekt, das die Klasse _java.awt.Rectangle_ repräsentiert:

    var c = getClass(r);

Das können Sie sehen, indem Sie dieses JavaClass-Objekt mit `java.awt.Rectangle` vergleichen:

    if (c == java.awt.Rectangle) ...

Die Java-Methode `getClass( )` wird anders aufgerufen und erfüllt einen vollkommen anderen Zweck:

    c = r.getClass( );

Nach der Ausführung dieser Codezeile ist `c` ein JavaObject, das ein `java.lang.Class`-Objekt repräsentiert. Dieses `java.lang.Class`-Objekt ist ein Java-Objekt, das eine Java-Repräsentation der Klasse _java.awt.Rectangle_ ist. In Ihrer Java-Dokumentation finden Sie Informationen dazu, was Sie mit der Klasse _java.lang.Class_ machen können.

Zusammenfassend können Sie sehen, dass der folgende Ausdruck für jedes JavaObject `o` mit `true` ausgewertet wird:

    (getClass(o.getClass( )) == java.lang.Class)

## Siehe auch

`JavaArray`, `JavaClass`, `JavaObject`, `JavaPackage`; Kapitel 12, Kapitel 23

## Name

Global — Das globale ObjektECMAScript v1Object → Global

## Überblick

    this

## Globale Eigenschaften

Da das globale Objekt keine Klasse ist, haben folgende globale Eigenschaften eigene Referenzeinträge unter ihren jeweiligen eigenen Namen. So finden Sie z.B. Einzelheiten zur Eigenschaft `undefined` unter dem Namen `undefined` und nicht unter `Global.undefined`. Beachten Sie, dass alle Variablen auf dem Toplevel ebenfalls Eigenschaften des globalen Objekts sind.

`Infinity` | Ein numerischer Wert, der positiv Unendlich repräsentiert.

---|---

`java` | Ein JavaPackage, das die _java.*_ -Package-Hierarchie repräsentiert.

`NaN` | Der Wert Not-a-Number.

`Packages` | Das Wurzel-JavaPackage-Objekt.

`undefined` | Der Wert `undefined`.

## Globale Funktionen

Das globale Objekt ist ein Objekt und keine Klasse. Die hier aufgeführten globalen Funktionen sind keine Methoden irgendeines Objekts; ihre Referenzeinträge stehen unter dem Funktionsnamen. Einzelheiten über die Funktion `parseInt( )` finden Sie folglich unter `parseInt()` und nicht unter `Global.parseInt( )`.

`decodeURI( )` | Decodiert einen String, der nach Anwendung von `encodeURI( )` Escape-Sequenzen enthält.

---|---

`decodeURIComponent( )` | Decodiert einen String, der nach Anwendung von `encodeURICom-ponent( )` Escape-Sequenzen enthält.

`encodeURI` | Codiert eine URI, indem bestimmte Zeichen durch Escape-Sequenzen ersetzt werden.

`encodeURIComponent` | Codiert eine URI-Komponente, indem bestimmte Zeichen durch Escape-Sequenzen ersetzt werden.

`escape( )` | Codiert einen String, indem bestimmte Zeichen durch Escape-Sequenzen ersetzt werden.

`eval( )` | Wertet einen String mit JavaScript-Code aus und gibt das Ergebnis zurück.

`getClass( )` | Liefert die JavaClass eines JavaObjects zurück.

`isFinite( )` | Testet, ob ein Wert eine endliche Zahl ist.

`isNaN()` | Testet, ob ein Wert Not-a-Number ist.

`parseFloat( )` | Parst eine Zahl in einem String.

`parseInt( )` | Parst einen Integer in einem String.

`unescape( )` | Decodiert einen mit `escape( )` codierten String.

## Globale Objekte

Neben den bereits erwähnten globalen Eigenschaften und Funktionen definiert das globale Objekt auch Eigenschaften, die sich auf alle sonstigen vordefinierten JavaScript-Objekte beziehen. Alle diese Eigenschaften sind Konstruktorfunktionen, die Klassen definieren. Eine Ausnahme ist `Math`: Dies ist eine Referenz auf ein Objekt, das kein Konstruktor ist.

`Array` | Der Konstruktor `Array( )`.

---|---

`Boolean` | Der Konstruktor `Boolean( )`.

`Date` | Der Konstruktor `Date( )`.

`Error` | Der Konstruktor `Error( )`.

`EvalError` | Der Konstruktor `EvalError( )`.

`Function` | Der Konstruktor `Function( )`.

`Math` | Eine Referenz auf ein Objekt, das mathematische Funktionen definiert.

`Number` | Der Konstruktor `Number( )`.

`Object` | Der Konstruktor `Object( )`.

`RangeError` | Der Konstruktor `RangeError( )`.

`ReferenceError` | Der Konstruktor `ReferenceError( )`.

`RegExp` | Der Konstruktor `RegExp( )`.

`String` | Der Konstruktor `String( )`.

`SyntaxError` | Der Konstruktor `SyntaxError( )`.

`TypeError` | Der Konstruktor `TypeError( )`.

`URIError` | Der Konstruktor `URIError( )`.

## Beschreibung

Das globale Objekt ist ein vordefiniertes Objekt, das als Platzhalter für die globalen Eigenschaften und Funktionen von JavaScript fungiert. Auf alle anderen vordefinierten Objekte, Funktionen und Eigenschaften kann über das globale Objekt zugegriffen werden. Da es keine Eigenschaft irgendeines anderen Objekts ist, hat es keinen Namen. (Der Titel dieses Referenzeintrags wurde nur aus organisatorischen Gründen gewählt und bedeutet nicht, dass das globale Objekt etwa den Namen »Global« trüge.) In Toplevel-JavaScript-Code können Sie sich mit dem Schlüsselwort `this` auf das globale Objekt beziehen. Dies wird jedoch kaum jemals nötig sein, da dieses Objekt am oberen Ende der Geltungsbereichskette steht, was bedeutet, dass unqualifizierte Variablen- und Funktionsnamen als Eigenschaften dieses Objekts nachgeschlagen werden. Wenn sich beispielsweise JavaScript-Code auf die Funktion `parseInt( )` bezieht, bezieht er sich damit auf die `parseInt`-Eigenschaft des globalen Objekts. Der Umstand, dass das globale Objekt ganz oben in der Geltungsbereichskette steht, bedeutet auch, dass alle im Toplevel-JavaScript-Code deklarierten Variablen Eigenschaften des globalen Objekts werden.

Das globale Objekt ist keine Klasse, sondern nur ein Objekt. Es gibt keinen `Global( )`-Konstruktor und keine Möglichkeit, ein neues globales Objekt zu instanziieren.

Wird JavaScript in eine bestimmte Umgebung eingebettet, so erhält das globale Objekt in der Regel zusätzliche Eigenschaften, die für diese Umgebung spezifisch sind. Der Typ des globalen Objekts wird vom ECMAScript-Standard nicht vorgeschrieben, und eine Implementierung oder eingebettetes JavaScript kann einen beliebigen Objekttyp als globales Objekt nutzen, solange es nur die hier aufgelisteten Grundeigenschaften und -funktionen definiert. In JavaScript-Implementierungen, die das Scripting von Java über LiveConnect oder verwandte Technologien ermöglichen, erhält das globale Objekt die Eigenschaften `java` und `Packages` und die Methode `getClass( )`, die hier aufgeführt wurden. Und in clientseitigem JavaScript ist das globale Objekt ein Window-Objekt und repräsentiert das Webbrowser-Fenster, in dem der JavaScript-Code läuft.

## Beispiel

Im JavaScript-Sprachkern ist keine der vordefinierten Eigenschaften des globalen Objekts aufzählbar. Daher können Sie alle implizit und explizit deklarierten globalen Variablen mit einer `for/in`-Schleife wie dieser aufführen:

    var variables = ""
    for(var name in this)
      variables += name + "\n";

## Siehe auch

`Window` in Teil IV; Kapitel 4

## Name

Infinity — Eine numerische Eigenschaft, die unendlich darstelltECMAScript v1

## Überblick

    Infinity

## Beschreibung

`Infinity` ist eine globale Eigenschaft, die den speziellen numerischen Wert plus unendlich repräsentiert. Die Eigenschaft `Infinity` ist nicht mit `for/in`-Schleifen aufzählbar und kann mit dem Operator `delete` auch nicht gelöscht werden. Beachten Sie, dass `Infinity` keine Konstante ist: Sie kann auf einen anderen Wert gesetzt werden, aber das sollten Sie tunlichst unterlassen. (`Number.POSITIVE_INFINITY` hingegen ist eine Konstante.)

## Siehe auch

`isFinite( )`, `NaN`, `Number.POSITIVE_INFINITY`

## Name

isFinite( ) — Stellt fest, ob eine Zahl endlich istECMAScript v1

## Überblick

    isFinite( _n_ )

### Argumente

 _`n`_

Die zu überprüfende Zahl.

### Rückgabewert

`true`, wenn _`n`_ eine endliche Zahl (oder in eine solche konvertierbar) ist; `false`, wenn _`n`_ den Wert `NaN` (Not-a-Number) hat oder plus oder minus unendlich ist.

## Siehe auch

`Infinity`, `isNaN( )`, `NaN`, `Number.NaN`, `Number.NEGATIVE_INFINITY`, `Number.POSITIVE_ INFINITY`

## Name

isNaN( ) — Prüft, ob der Wert Not-a-Number istECMAScript v1

## Überblick

    isNaN( _x_
    )

### Argumente

 _`x`_

Der zu überprüfende Wert.

### Rückgabewert

`true`, wenn _`x`_ der Spezialwert Not-a-Number ist (oder in diesen konvertiert werden kann); `false`, wenn _`x`_ ein anderer Wert ist.

## Beschreibung

`isNaN( )` prüft sein Argument daraufhin, ob es `NaN` ist, also eine ungültige Zahl darstellt (beispielsweise als Ergebnis einer Division durch null). Diese Funktion ist notwendig, da ein Vergleich von `NaN` mit irgendeinem anderen Wert einschließlich sich selbst immer `false` ergibt. Daher kann `NaN` nicht mit den Operatoren `==` oder `===` ermittelt werden.

`isNaN( )` wird häufig verwendet, um die Ergebnisse von `parseFloat( )` und `parseInt( )` daraufhin zu testen, ob sie gültige Zahlen sind. Sie können mit `isNaN( )` auch arithmetische Fehler wie z.B. die Division durch null finden.

## Beispiel

    isNaN(0);         // Gibt false zurück
    isNaN(0/0);        // Gibt true zurück
    isNaN(parseInt("3"));   // Gibt false zurück
    isNaN(parseInt("hello")); // Gibt true zurück
    isNaN("3");        // Gibt false zurück
    isNaN("hello");      // Gibt true zurück
    isNaN(true);        // Gibt false zurück
    isNaN(undefined);     // Gibt true zurück

## Siehe auch

`isFinite( )`, `NaN`, `Number.NaN, parseFloat( ), parseInt( )`

## Name

Core DOMjava — Das JavaPackage für die java.*-Package-HierarchieLiveConnect

## Überblick

    java

## Beschreibung

In JavaScript-Implementierungen, die LiveConnect oder andere Technologien zum Scripting von Java einschließen, ist die globale Eigenschaft `java` ein JavaPackage-Objekt, das die Package-Hierarchie _java.*_ repräsentiert. Die Existenz dieser Eigenschaft bedeutet, dass ein JavaScript-Ausdruck wie `java.util` auf das Java-Package _java.util_ verweist. Für Java-Packages, die nicht in die _java.*_ -Hierarchie fallen, siehe die globale Eigenschaft `Packages`.

## Siehe auch

`JavaPackage`, `Packages`; Kapitel 12

## Name

JavaArray — JavaScript-Repräsentation eines Java-ArraysLiveConnect

## Überblick

     _javaarray_.length // Die Länge des Arrays
    _javaarray_ [ _index_ ] // Ein Array-Element lesen oder schreiben

## Eigenschaften

`length`

Eine schreibgeschützte Eigenschaft, die die Anzahl von Elementen in dem Java-Array angibt, das dieses JavaArray-Objekt repräsentiert.

## Beschreibung

Das JavaArray-Objekt ist eine JavaScript-Darstellung eines Java-Arrays und ermöglicht es JavaScript-Code, die Elemente des Arrays mit der vertrauten JavaScript-Array-Syntax zu lesen und zu schreiben. Außerdem hat das JavaArray-Objekt ein `length`-Feld, das die Anzahl der Elemente im Array angibt.

Wenn Sie Werte aus Array-Elementen lesen oder in Array-Elemente schreiben, wird die Datenumwandlung zwischen den JavaScript- und Java-Darstellungen automatisch vom System abgewickelt. Vollständige Informationen finden Sie in Kapitel 12.

## Verwendung

Beachten Sie, dass sich Java-Arrays von JavaScript-Arrays in einer Reihe von wichtigen Aspekten unterscheiden. Erstens haben Java-Arrays eine feste Länge, die festgelegt wird, wenn sie erzeugt werden. Aus diesem Grund ist das `length`-Feld von JavaArrays schreibgeschützt. Der zweite Unterschied ist, dass Java-Arrays _typisiert_ sind (d.h., ihre Elemente müssen alle den gleichen Datentyp haben). Wenn Sie versuchen, ein Array-Element auf einen Wert des falschen Typs zu setzen, erhalten Sie einen JavaScript-Fehler oder eine JavaScript-Exception.

## Beispiel

`java.awt.Polygon` ist ein JavaClass-Objekt. Ein JavaObject, das eine Instanz dieser Klasse repräsentiert, können Sie folgendermaßen erzeugen:

    p = new java.awt.Polygon( );

Das Objekt `p` hat die Eigenschaften `xpoints` und `ypoints`, die JavaArray-Objekte sind, die Java-Arrays mit Integern repräsentieren. Den Inhalt dieser Arrays können Sie mit JavaScript-Code wie dem folgenden initialisieren:

    for(var i = 0; i < p.xpoints.length; i++)
      p.xpoints[i] = Math.round(Math.random( )*100);
    for(var i = 0; i < p.ypoints.length; i++)
      p.ypoints[i] = Math.round(Math.random( )*100);

## Siehe auch

`getClass( )`, `JavaClass`, `JavaObject`, `JavaPackage`; Kapitel 12

## Name

JavaClass — JavaScript-Repräsentation einer Java-KlasseLiveConnect

## Überblick

     _javaclass_. _static_member_  // Ein statisches Java-Feld oder
                 // eine statische Java-Methode lesen oder schreiben
    new _javaclass_ (...)    // Ein neues Java-Objekt erzeugen

## Eigenschaften

Jedes JavaClass-Objekt enthält Eigenschaften, die die gleichen Namen haben wie die öffentlichen statischen Felder und Methoden der Java-Klasse, die es repräsentiert. Diese Eigenschaften ermöglichen es Ihnen, die statischen Felder der Klasse zu lesen und zu schreiben und die statischen Methoden der Klasse aufzurufen. Jedes JavaClass-Objekt hat andere Eigenschaften. Sie können eine `for/in`-Schleife verwenden, um sie für ein gegebenes JavaClass-Objekt aufzuzählen.

## Beschreibung

Das JavaClass-Objekt ist eine JavaScript-Repräsentation einer Java-Klasse. Die Eigenschaften eines JavaClass-Objekts repräsentieren die öffentlichen statischen Felder und Methoden (die gelegentlich als Klassenfelder und Klassenmethoden bezeichnet werden) der repräsentierten Klasse. Beachten Sie, dass das JavaClass-Objekt keine Eigenschaften hat, die die _Instanzfelder_ einer Java-Klasse repräsentieren. Einzelne Instanzen einer Java-Klasse werden durch das JavaObject-Objekt repräsentiert.

Das JavaClass-Objekt implementiert die LiveConnect-Funktionalität, die es JavaScript-Programmen ermöglicht, die statischen Variablen von Java-Klassen über die gewöhnliche JavaScript-Syntax zu lesen und zu schreiben. Es stellt außerdem die Funktionalität, die es JavaScript ermöglicht, die statischen Methoden einer Java-Klasse aufzurufen.

Zusätzlich dazu, dass es JavaScript ermöglicht, die Java-Variablen- und -Methodenwerte zu lesen und zu schreiben, ermöglicht es das JavaClass-Objekt JavaScript-Programmen, Java-Objekte zu erstellen (die durch JavaObject-Objekte repräsentiert werden), indem das Schlüsselwort `new` verwendet und die Konstruktormethode einer JavaClass aufgerufen wird.

Die Datenumwandlung, die für die Kommunikation zwischen JavaScript und Java über das JavaClass-Objekt erforderlich ist, wird von LiveConnect automatisch gesteuert. Vollständige Informationen finden Sie in Kapitel 12.

## Verwendung

Beachten Sie, dass Java eine _typisierte_ Sprache ist. Das heißt, dass jedes der Felder eines Objekts einen bestimmten Datentyp hat, der auf Werte nur dieses Typs festgelegt ist. Wenn Sie versuchen, ein Feld auf einen Wert zu setzen, der nicht den richtigen Typ hat, erhalten Sie einen JavaScript-Fehler oder eine JavaScript-Exception. Sie erhalten ebenfalls einen Fehler oder eine Exception, wenn Sie versuchen, eine Methode mit Argumenten des falschen Typs aufzurufen.

## Beispiel

`java.lang.System` ist ein JavaClass-Objekt, das die Java-Klasse _java.lang.System_ repräsentiert. Sie können ein statisches Feld dieser Klasse mit Code wie dem folgenden lesen:

    var java_console = java.lang.System.out;

Eine statische Methode dieser Klasse können Sie mit einer Zeile wie dieser aufrufen:

    var version = java.lang.System.getProperty("java.version");

Außerdem ermöglicht Ihnen das JavaClass-Objekt die Erstellung neuer Java-Objekte:

    var java_date = new java.lang.Date( );

## Siehe auch

`getClass( )`, `JavaArray`, `JavaObject`, `JavaPackage`; Kapitel 12

## Name

JavaObject — JavaScript-Repräsentation eines Java-ObjektsLiveConnect

## Überblick

     _javaobject_. _member_ // Ein Instanzfeld oder eine Instanzmethode lesen oder schreiben

## Eigenschaften

Jedes JavaObject-Objekt enthält Eigenschaften, die die gleichen Namen haben wie die öffentlichen Instanzfelder und -methoden (aber nicht die statischen Klassenfelder und -methoden) des Java-Objekts, das es repräsentiert. Diese Eigenschaften ermöglichen es Ihnen, die Werte der öffentlichen Felder zu lesen und zu schreiben und die öffentlichen Methoden aufzurufen. Diese Eigenschaften eines bestimmten JavaObject-Objekts sind offensichtlicherweise vom Typ des Java-Objekts abhängig, das es repräsentiert. Sie können die `for/in`-Schleife verwenden, um die Eigenschaften eines gegebenen JavaObjects aufzuzählen.

## Beschreibung

Das JavaObject-Objekt ist die JavaScript-Beschreibung eines Java-Objekts. Die Eigenschaften eines JavaObject-Objekts repräsentieren die öffentlichen Instanzfelder und die öffentlichen Instanzmethoden, die für das Java-Objekt definiert sind. (Die Klassenfelder und -methoden des Objekts werden durch das JavaClass-Objekt repräsentiert.)

Das JavaObject-Objekt implementiert die LiveConnect-Funktionalität, die es JavaScript-Programmen ermöglicht, die öffentlichen Instanzfelder eines Java-Objekts über die normale JavaScript-Syntax zu lesen und zu schreiben. Es stellt auch die Funktionalität, die es JavaScript ermöglicht, die Methoden eines Java-Objekts aufzurufen. Die Datenumwandlung zwischen den JavaScript- und Java-Repräsentationen wird von LiveConnect automatisch gesteuert. Vollständige Informationen finden Sie in Kapitel 12.

## Verwendung

Beachten Sie, dass Java eine _typisierte_ Sprache ist. Das bedeutet, dass jedes Feld eines Objekts einen bestimmten Datentyp hat und Sie es nur auf Werte dieses Typs setzen können. Das `width`-Feld eines `java.awt.Rectangle`-Objekts ist beispielsweise ein Integer-Objekt. Wenn Sie versuchen, es auf einen String zu setzen, verursacht das einen JavaScript-Fehler oder eine JavaScipt-Exception.

## Beispiel

`java.awt.Rectangle` ist ein JavaClass-Objekt, das die Klasse _java.awt.Rectangle_ repräsentiert. Ein JavaObject, das eine Instanz dieser Klasse repräsentiert, können Sie folgendermaßen erzeugen:

    var r = new java.awt.Rectangle(0,0,4,5);

Dann können Sie mit Code wie diesem die öffentlichen Instanzvariablen dieses JavaObjects `r` lesen:

    var perimeter = 2*r.width + 2*r.height;

Sie können die Werte der öffentlichen Instanzvariablen von `r` auch mit JavaScript-Syntax setzen:

    r.width = perimeter/4;
    r.height = perimeter/4;

## Siehe auch

`getClass( )`, `JavaArray`, `JavaClass`, `JavaPackage`; Kapitel 12

## Name

JavaPackage — JavaScript-Repräsentation eines Java-PackagesLiveConnect

## Überblick

     _package_. _package_name_ // Verweist auf ein anderes JavaPackage
    _package_. _class_name_  // Verweist auf ein JavaClass-Objekt

## Eigenschaften

Die Eigenschaften eines JavaPackage-Objekts sind die Namen der JavaPackage-Objekte und JavaClass-Objekte, die es enthält. Diese Eigenschaften sind für jedes JavaPackage unterschiedlich. Beachten Sie, dass es nicht möglich ist, die `for/in`-Schleife von JavaScript zu verwenden, um die Liste der Eigenschaftsnamen eines JavaPackage-Objekts aufzuzählen. Werfen Sie einen Blick in ein Java-Handbuch, um die Packages und Klassen in Erfahrung zu bringen, die in einem bestimmten Package enthalten sind.

## Beschreibung

Das JavaPackage-Objekt ist eine JavaScript-Repräsentation eines Java-Packages. In Java ist ein Package eine Sammlung verwandter Klassen. In JavaScript kann ein JavaPackage Klassen (die durch JavaClass-Objekte repräsentiert werden) und andere JavaPackage-Objekte enthalten.

Das globale Objekt hat eine JavaPackage-Eigenschaft namens `java`, die die _java.*_ -Package-Hierarchie repräsentiert. Dieses JavaPackage-Objekt definiert Eigenschaften, die auf andere JavaPackage-Objekte verweisen. Beispielsweise verweisen `java.lang` und `java.net` auf die Packages _java.lang_ und java.net. Das JavaPackage `java.awt` enthält Eigenschaften namens `Frame` und `Button`, die Referenzen auf JavaClass-Objekte sind und die Klassen _java.awt.Frame_ und _java.awt.Button_ repräsentieren.

Das globale Objekt definiert außerdem eine Eigenschaft namens `Packages`, die das Wurzel-JavaPackage repräsentiert, dessen Eigenschaften auf die Wurzeln aller bekannten Package-Hierarchien verweisen. Beispielsweise verweist der Ausdruck `Packages.javax.swing` auf das Java-Package _javax.swing_.

Es ist nicht möglich, eine `for/in`-Schleife zu verwenden, um die Namen der Packages und Klassen zu ermitteln, die in einem JavaPackage enthalten sind. Sie müssen diese Informationen im Voraus haben. Sie können sie in einem beliebigen Java-Handbuch finden oder, indem Sie die Java-Klassenhierarchie untersuchen.

Weitere Informationen zur Arbeit mit Java-Packages, -Klassen und -Objekten finden Sie in Kapitel 12.

## Siehe auch

`java`, `JavaArray`, `JavaClass`, `JavaObject`, `Packages`; Kapitel 12

## Name

JSObject — Siehe JSObject in Teil IV

##

## Name

Math — Mathematische Funktionen und KonstantenECMAScript v1

## Überblick

    Math. _konstante_
    Math. _funktion_ ( )

## Konstanten

`Math.E` | Die Konstante _e_ , die Basis des natürlichen Logarithmus.

---|---

`Math.LN10` | Der natürliche Logarithmus von 10.

`Math.LN2` | Der natürliche Logarithmus von 2.

`Math.LOG10E` | Der Logarithmus von _e_ zur Basis 10.

`Math.LOG2E` | Der Logarithmus von _e_ zur Basis 2.

`Math.PI` | Die Konstante π.

`Math.SQRT1_2` | Die Zahl 1, geteilt durch die Quadratwurzel von 2.

`Math.SQRT2` | Die Quadratwurzel von 2.

## Statische Funktionen

`Math.abs( )` | Berechnet einen Absolutwert.

---|---

`Math.acos( )` | Berechnet einen Arkus Kosinus.

`Math.asin( )` | Berechnet einen Arkus Sinus.

`Math.atan( )` | Berechnet einen Arkus Tangens.

`Math.atan2( )` | Berechnet den Winkel zwischen der X-Achse und einem Punkt.

`Math.ceil( )` | Rundet eine Zahl auf.

`Math.cos( )` | Berechnet einen Kosinus.

`Math.exp( )` | Berechnet eine Potenz von _e_.

`Math.floor( )` | Rundet eine Zahl ab.

`Math.log( )` | Berechnet einen natürlichen Logarithmus.

`Math.max( )` | Gibt die größte von mehreren Zahlen zurück.

`Math.min( )` | Gibt die kleinste von mehreren Zahlen zurück.

`Math.pow( )` | Berechnet _x y_

`Math.random( )` | Berechnet eine Zufallszahl.

`Math.round( )` | Rundet auf die nächste ganze Zahl.

`Math.sin( )` | Berechnet einen Sinus.

`Math.sqrt( )` | Berechnet eine Quadratwurzel.

`Math.tan( )` | Berechnet einen Tangens.

## Beschreibung

Math ist ein Objekt, das Eigenschaften definiert, die sich auf nützliche mathematische Funktionen und Konstanten beziehen. Diese sind im Math-Objekt bequem zusammengefasst und werden beispielsweise mit folgender Syntax aufgerufen:

    y = Math.sin(x);
    area = radius * radius * Math.PI;

Math ist keine Klasse von Objekten, wie es Date und String sind. Es gibt keinen `Math( )`-Konstruktor, und Funktionen wie `Math.sin( )` sind einfach nur Funktionen und keine Methoden, die auf einem Objekt operieren.

## Siehe auch

`Number`

## Name

Math.abs( ) — Berechnet einen AbsolutwertECMAScript v1

## Überblick

    Math.abs( _x_ )

### Argumente

 _`x`_

Eine beliebige Zahl.

### Rückgabewert

Der Absolutwert von _`x`_.

## Name

Math.acos( ) — Berechnet einen Arkus KosinusECMAScript v1

## Überblick

    Math.acos( _x_ )

### Argumente

 _`x`_

Eine Zahl zwischen −1.0 und 1.0.

### Rückgabewert

Der Arkus Kosinus oder inverse Kosinus des angegebenen Werts _`x`_. Dieser Rückgabewert liegt zwischen 0 und π im Bogenmaß.

## Name

Math.asin( ) — Berechnet einen Arkus SinusECMAScript v1

## Überblick

    Math.asin( _x_ )

### Argumente

 _`x`_

Eine Zahl zwischen −1.0 und 1.0.

### Rückgabewert

Der Arkus Sinus des angegebenen Werts _`x`_. Dieser Rückgabewert liegt zwischen -π/2 und π/2 im Bogenmaß.

## Name

Math.atan( ) — Berechnet einen Arkus TangensECMAScript v1

## Überblick

    Math.atan( _x_ )

### Argumente

 _`x`_

Eine beliebige Zahl.

### Rückgabewert

Der Arkus Tangens des angegebenen Werts _`x`_. Dieser Rückgabewert liegt zwischen -π/2 und π/2 im Bogenmaß.

## Name

Math.atan2( ) — Berechnet den Winkel zwischen X-Achse und einem PunktECMAScript v1

## Überblick

    Math.atan2( _y_ , _x_ )

### Argumente

 _`y`_

Die Y-Koordinate des Punkts.

 _`x`_

Die X-Koordinate des Punkts.

### Rückgabewert

Ein Wert zwischen -π und π im Bogenmaß, der entgegen dem Uhrzeigersinn den Winkel zwischen der positiven X-Achse und dem Punkt ( _`x`_ , _`y`_ ) spezifiziert.

## Beschreibung

Die Funktion `Math.atan2( )` berechnet den Arkus Tangens des Verhältnisses _`y`_ / _`x`_. Das Argument _`y`_ kann als Y-Koordinate eines Punkts und das Argument _`x`_ als X-Koordinate des Punkts interpretiert werden. Beachten Sie die ungewöhnliche Reihenfolge der Argumente dieser Funktion: Die Y-Koordinate wird vor der X-Koordinate übergeben.

## Name

Math.ceil( ) — Rundet eine Zahl aufECMAScript v1

## Überblick

    Math.ceil( _x_ )

### Argumente

 _`x`_

Ein beliebiger numerischer Wert oder Ausdruck.

### Rückgabewert

Der nächste Integer größer oder gleich _`x`_.

## Beschreibung

`Math.ceil( )` berechnet den aufgerundeten Wert, gibt also den nächsten Integer größer oder gleich dem Funktionsargument zurück. `Math.ceil( )` unterscheidet sich insofern von `Math. round( )`, als es immer aufrundet und nicht je nachdem, was der nächstgelegene Integer ist, entweder auf- oder abrundet. Beachten Sie außerdem, dass `Math.ceil( )` negative Zahlen nicht auf größere negative Zahlen, sondern immer in Richtung null rundet.

## Beispiel

    a = Math.ceil(1.99);  // Ergebnis ist 2.0
    b = Math.ceil(1.01);  // Ergebnis ist 2.0
    c = Math.ceil(1.0);  // Ergebnis ist 1.0
    d = Math.ceil(-1.99); // Ergebnis ist −1.0

## Name

Math.cos( ) — Berechnet einen KosinusECMAScript v1

## Überblick

    Math.cos( _x_ )

### Argumente

 _`x`_

Ein Winkel im Bogenmaß. Um Grad in Bogenmaß umzurechnen, müssen Sie den Grad-Wert mit 0.017453293 (π/180) multiplizieren.

### Rückgabewert

Der Kosinus des angegebenen Werts _`x`_. Dieser Rückgabewert liegt zwischen −1.0 und 1.0.

## Name

Math.E — Die mathematische Konstante _e_ ECMAScript v1

## Überblick

    Math.E

## Beschreibung

`Math.E` ist die mathematische Konstante _e_ die Basis des natürlichen Logarithmus, deren Wert ungefähr 2.71828 beträgt.

## Name

Math.exp( ) — Berechnet _e_ ECMAScript v1

## Überblick

    Math.exp( _x_ )

### Argumente

 _`x`_

Ein numerischer Wert oder Ausdruck, der als Exponent verwendet wird.

### Rückgabewert

 _e_ also _e_ hoch dem angegebenen Exponenten _`x`_ , wobei _e_ die Basis des natürlichen Logarithmus ist und ungefähr den Wert 2.71828 hat.

## Name

Math.floor( ) — Rundet eine Zahl abECMAScript v1

## Überblick

    Math.floor( _x_ )

### Argumente

 _`x`_

Ein beliebiger numerischer Wert oder Ausdruck.

### Rückgabewert

Der nächste Integer kleiner oder gleich _`x`_.

## Beschreibung

`Math.floor( )` berechnet den abgerundeten Wert, gibt also den nächsten Integer kleiner oder gleich dem Funktionsargument zurück.

`Math.floor( )` rundet einen Gleitkommawert auf den nächsten Integer ab. Insofern verhält sich diese Funktion anders als `Math.round( )`, die je nachdem, welcher Integer näher liegt, entweder auf- oder abrundet. Außerdem rundet `Math.floor( )` negative Zahlen ab (d.h. noch weiter in den negativen Bereich hinein) und nicht auf (also nicht in Richtung null).

## Beispiel

    a = Math.floor(1.99);  // Ergebnis ist 1.0
    b = Math.floor(1.01);  // Ergebnis ist 1.0
    c = Math.floor(1.0);   // Ergebnis ist 1.0
    d = Math.floor(-1.01);  // Ergebnis ist −2.0

## Name

Math.LN10 — Die mathematische Konstante _e_ ECMAScript v1

## Überblick

    Math.LN10

## Beschreibung

`Math.LN10` ist _e_ der natürliche Logarithmus von 10. Diese Konstante hat ungefähr den Wert 2.3025850929940459011.

## Name

Math.LN2 — Die mathematische Konstante _e_ ECMAScript v1

## Überblick

    Math.LN2

## Beschreibung

`Math.LN2` ist _e_ der natürliche Logarithmus von 2. Diese Konstante hat ungefähr den Wert 0.69314718055994528623.

## Name

Math.log( ) — Berechnet einen natürlichen LogarithmusECMAScript v1

## Überblick

    Math.log( _x_ )

### Argumente

 _`x`_

Ein beliebiger numerischer Wert oder Ausdruck größer als null.

### Rückgabewert

Der natürliche Logarithmus von _`x`_.

## Beschreibung

Die Methode `Math.log( )` berechnet _e_ den natürlichen Logarithmus ihres Arguments. Das Argument muss größer als null sein.

Sie können für eine Zahl die Logarithmen zur Basis 10 oder zur Basis 2 mit folgenden Formeln berechnen:

    log10x = log10e·logex
    log2x = log2e·logex

Diese Formeln lassen sich in die folgenden JavaScript-Funktionen übersetzen:

    function log10(x) { return Math.LOG10E * Math.log(x); }
    function log2(x) { return Math.LOG2E * Math.log(x); }

## Name

Math.LOG10E — Die mathematische Konstante log10eECMAScript v1

## Überblick

    Math.LOG10E

## Beschreibung

`Math.LOG10E` ist _e_ der Logarithmus der Konstanten _e_ zur Basis 10. Er hat ungefähr den Wert 0.43429448190325181667.

## Name

Math.LOG2E — Die mathematische Konstante _e_ ECMAScript v1

## Überblick

    Math.LOG2E

## Beschreibung

`Math.LOG2E` ist _e_ der Logarithmus der Konstanten _e_ zur Basis 2. Er hat ungefähr den Wert 1.442695040888963387.

## Name

Math.max( ) — Gibt das größte Argument zurückECMAScript v1; verbessert in ECMAScript v3

## Überblick

    Math.max( _args..._
    )

### Argumente

 _`args...`_

Eine beliebige Anzahl von Argumenten. Vor ECMAScript v3 erwartete diese Methode genau zwei Argumente.

### Rückgabewert

Das größte der Argumente. Gibt `-Infinity` zurück, wenn keine Argumente vorhanden sind. Gibt `NaN` zurück, wenn irgendeines der Argumente `NaN` oder ein nicht-numerischer Wert ist, der sich nicht in eine Zahl konvertieren lässt.

## Name

Math.min( ) — Gibt das kleinste Argument zurückECMAScript v1; verbessert in ECMAScript v3

## Überblick

    Math.min( _args..._ )

### Argumente

 _`args...`_

Eine beliebige Anzahl von Argumenten. Vor ECMAScript v3 erwartete diese Funktion genau zwei Argumente.

### Rückgabewert

Das kleinste der angegebenen Argumente. Gibt `Infinity` zurück, wenn keine Argumente vorhanden sind, und `NaN`, wenn eines der Argumente `NaN` oder ein nicht-numerischer Wert ist, der sich nicht in eine Zahl konvertieren lässt.

## Name

Math.PI — Die mathematische Konstante πECMAScript v1

## Überblick

    Math.PI

## Beschreibung

`Math.PI` ist die Konstante π (Pi), das Verhältnis von Umfang und Durchmesser eines Kreises. Sie hat ungefähr den Wert 3.14159265358979.

## Name

Math.pow( ) — Berechnet _e_ ECMAScript v1

## Überblick

    Math.pow( _x_ , _y_ )

### Argumente

 _`x`_

Die Zahl, die zur Potenz erhoben werden soll.

 _`y`_

Die Potenz, zu der _`x`_ erhoben werden soll.

### Rückgabewert

 _`x`_ hoch _`y`_ , also _e_

## Beschreibung

`Math.pow( )` berechnet _`x`_ hoch _`y`_. Dieser Funktion können beliebige Werte für _`x`_ und _`y`_ übergeben werden. Ist das Ergebnis allerdings eine imaginäre oder komplexe Zahl, gibt `Math.pow( )` den Wert `NaN` zurück. In der Praxis bedeutet das: Wenn _`x`_ negativ ist, sollte _`y`_ eine positive oder negative ganze Zahl sein. Denken Sie bitte auch daran, dass große Exponenten leicht einen Gleitkomma-Speicherüberlauf verursachen und den Wert `Infinity` zurückgeben können.

## Name

Math.random( ) — Gibt eine Pseudozufallszahl zurückECMAScript v1

## Überblick

    Math.random( )

### Rückgabewert

Eine Pseudozufallszahl zwischen 0.0 und 1.0.

## Name

Math.round( ) — Rundet auf die nächste ganze ZahlECMAScript v1

## Überblick

    Math.round( _x_ )

### Argumente

 _`x`_

Eine beliebige Zahl.

### Rückgabewert

Der Integer, der _`x`_ am nächsten kommt.

## Beschreibung

Die Methode `Math.round( )` rundet ihr Argument auf die nächstgelegene ganze Zahl auf oder ab. Der Wert .5 wird aufgerundet. So wird z.B. 2.5 auf 3 und −2.5 auf −2 gerundet.

## Name

Math.sin( ) — Berechnet einen SinusECMAScript v1

## Überblick

    Math.sin( _x_ )

### Argumente

 _`x`_

Ein Winkel im Bogenmaß. Um Grad in Bogenmaß umzurechnen, multiplizieren Sie mit 0.017453293 (π/180).

### Rückgabewert

Der Sinus von _`x`_. Dieser Rückgabewert liegt zwischen −1.0 und 1.0.

## Name

Math.sqrt( ) — Berechnet eine QuadratwurzelECMAScript v1

## Überblick

    Math.sqrt( _x_ )

### Argumente

 _`x`_

Ein numerischer Wert größer oder gleich null.

### Rückgabewert

Die Quadratwurzel von _`x`_. Gibt `NaN` zurück, wenn _`x`_ kleiner als null ist.

## Beschreibung

`Math.sqrt( )` berechnet die Quadratwurzel einer Zahl. Sie können aber auch mit `Math.pow( )` beliebige Wurzeln ausrechnen. Ein Beispiel:

    Math.cuberoot = function(x){ return Math.pow(x,1/3); }
    Math.cuberoot(8); // Gibt 2 zurück

## Name

Math.SQRT1_2 — Die mathematische Konstante _e_ ECMAScript v1

## Überblick

    Math.SQRT1_2

## Beschreibung

`Math.SQRT1_2` ist _e_ der Kehrwert der Quadratwurzel von 2. Diese Konstante hat ungefähr den Wert 0.7071067811865476.

## Name

Math.SQRT2 — Die mathematische Konstante _e_ ECMAScript v1

## Überblick

    Math.SQRT2

## Beschreibung

`Math.SQRT2` ist die Konstante _e_ die Quadratwurzel von 2. Sie hat ungefähr den Wert 1.414213562373095.

## Name

Math.tan( ) — Berechnet einen TangensECMAScript v1

## Überblick

    Math.tan( _x_ )

### Argumente

 _`x`_

Ein Winkel im Bogenmaß. Um Grad in Bogenmaß umzurechnen, multiplizieren Sie den Grad-Wert mit 0.017453293 (π/180).

### Rückgabewert

Der Tangens des angegebenen Winkels _`x`_.

## Name

NaN — Die Eigenschaft Not-a-NumberECMAScript v1

## Überblick

    NaN

## Beschreibung

`NaN` ist eine globale Eigenschaft, die sich auf den speziellen numerischen Wert Not-a-Number bezieht. Diese Eigenschaft ist nicht mit `for/in`-Schleifen aufzählbar und kann auch nicht mit dem Operator `delete` gelöscht werden. Beachten Sie, dass `NaN` keine Konstante ist und auch auf einen anderen Wert gesetzt werden kann, was Sie jedoch tunlichst unterlassen sollten.

Wenn Sie ermitteln möchten, ob ein Wert Not-a-Number ist, verwenden Sie `isNaN( )`, da `NaN` ungleich jedem anderen Wert einschließlich seiner selbst ist!

## Siehe auch

`Infinity`, `isNaN( )`, `Number.NaN`

## Name

Number — Unterstützung für ZahlenECMAScript v1Object → Number

## Konstruktor

    new Number( _wert_ )
    Number( _wert_ )

### Argumente

 _`wert`_

Der numerische Wert des anzulegenden Number-Objekts oder ein Wert, der in eine Zahl konvertiert werden soll.

### Rückgabewert

Wenn die Funktion `Number( )` mit dem Operator `new` als Konstruktor eingesetzt wird, gibt sie ein neu angelegtes Number-Objekt zurück. Wird sie als Funktion ohne den Operator `new` aufgerufen, so konvertiert sie ihr Argument in einen numerischen Grundtyp und gibt diesen Wert zurück (oder `NaN`, wenn die Konvertierung fehlgeschlagen ist).

## Konstanten

`Number.MAX_VALUE`

Die größte darstellbare Zahl.

`Number.MIN_VALUE`

Die kleinste darstellbare Zahl.

`Number.NaN`

Not-a-Number.

`Number.NEGATIVE_INFINITY`

Der Wert minus unendlich, der bei einem Überlauf zurückgegeben wird.

`Number.POSITIVE_INFINITY`

Der Wert plus unendlich, der bei einem Überlauf zurückgegeben wird.

## Methoden

`toString( )`

Konvertiert eine Zahl in einen String unter Verwendung einer angegebenen Zahlenbasis (Radix).

`toLocaleString( )`

Konvertiert eine Zahl in einen String, der die örtlichen Konventionen für die Schreibweise von Zahlen verwendet.

`toFixed( )`

Konvertiert eine Zahl in einen String mit einer festgelegten Anzahl von Dezimalstellen.

`toExponential( )`

Konvertiert eine Zahl in einen String in Exponentialschreibweise mit der angegebenen Anzahl von Dezimalstellen.

`toPrecision( )`

Konvertiert eine Zahl in einen String und verwendet dazu die angegebene Anzahl von Ziffern. Je nach der Größe der Zahl und der angegebenen Anzahl der signifikanten Ziffern wird entweder die Exponential- oder die Festkommaschreibweise verwendet.

`valueOf( )`

Liefert den elementaren numerischen Wert eines Number-Objekts.

## Beschreibung

Zahlen sind ein grundlegender primitiver Datentyp in JavaScript. JavaScript unterstützt allerdings auch das Number-Objekt, ein Wrapper-Objekt um einen elementaren numerischen Wert. JavaScript konvertiert automatisch zwischen der elementaren Form und der Objektform hin und her, wenn dies notwendig ist. Sie können mit dem Konstruktor `Number( )` auch explizit ein Number-Objekt anlegen, was allerdings kaum jemals erforderlich sein wird.

Sie können den Konstruktor `Number( )` auch ohne den Operator `new` als Konvertierungsfunktion verwenden. Wenn Sie ihn auf diese Weise aufrufen, versucht er, sein Argument in eine Zahl zu konvertieren, und gibt den daraus resultierenden elementaren numerischen Wert zurück (oder `NaN`, wenn die Konvertierung fehlschlägt).

Der Konstruktor `Number( )` wird auch als Platzhalter für fünf nützliche numerische Konstanten verwendet: die jeweils größte oder kleinste darstellbare Zahl, plus oder minus unendlich und den Spezialwert `NaN`. Diese Werte sind Eigenschaften des `Number( )`-Konstruktors und nicht von einzelnen Number-Objekten. Die Eigenschaft `MAX_VALUE` können Sie z.B. so verwenden:

    var biggest = Number.MAX_VALUE

aber _nicht_ so:

    var n = new Number(2);
    var biggest = n.MAX_VALUE

Dagegen sind `toString( )` und andere Methoden des Number-Objekts Methoden jedes einzelnen Number-Objekts und nicht der Konstruktorfunktion `Number( )`. Wie bereits gesagt, konvertiert JavaScript automatisch elementare numerische Werte in Number-Objekte, wenn dies notwendig ist. Das bedeutet, dass wir die Number-Methoden sowohl mit elementaren numerischen Werten als auch mit Number-Objekten verwenden können.

    var value = 1234;
    var binary_value = n.toString(2);

## Siehe auch

`Infinity`, `Math`, `NaN`

## Name

Number.MAX_VALUE — Der größtmögliche ZahlwertECMAScript v1

## Überblick

    Number.MAX_VALUE

## Beschreibung

`Number.MAX_VALUE` ist die größte in JavaScript darstellbare Zahl und beträgt ungefähr 1.79E+308.

## Name

Number.MIN_VALUE — Der kleinstmögliche ZahlwertECMAScript v1

## Überblick

    Number.MIN_VALUE

## Beschreibung

`Number.MIN_VALUE` ist die kleinste (am nächsten bei null liegende, nicht die negative mit dem größten Absolutwert) in JavaScript darstellbare Zahl und beträgt ungefähr 5E-324.

## Name

Number.NaN — Der Spezialwert Not-a-NumberECMAScript v1

## Überblick

    Number.NaN

## Beschreibung

`Number.NaN` ist ein Spezialwert, der anzeigt, dass das Ergebnis einer mathematischen Operation (z.B. Quadratwurzel aus einer negativen Zahl) keine Zahl ist. Die Methoden `parseInt( )` und `parseFloat( )` geben diesen Wert zurück, wenn sie den angegebenen String nicht parsen können. Sie können `Number.NaN` in ähnlicher Weise verwenden, um eine Fehlerbedingung für eine Funktion anzuzeigen, die normalerweise eine gültige Zahl zurückgibt.

JavaScript gibt den Wert `Number.NaN` als `NaN` aus. Beachten Sie, dass der Wert `NaN` ungleich jeder anderen Zahl einschließlich seiner selbst ist. Sie können also nicht durch Vergleichen von `Number.NaN` das Vorhandensein eines NaN-Werts ermitteln. Verwenden Sie dafür die Funktion `isNaN( )`. In ECMAScript v1 und seinen Folgeversionen können Sie statt `Number.NaN` auch die vordefinierte globale Eigenschaft `NaN` benutzen.

## Siehe auch

`isNaN( )`, `NaN`

## Name

Number.NEGATIVE_INFINITY — Minus unendlichECMAScript v1

## Überblick

    Number.NEGATIVE_INFINITY

## Beschreibung

`Number.NEGATIVE_INFINITY` ist ein numerischer Spezialwert, der zurückgegeben wird, wenn eine arithmetische Operation oder mathematische Funktion einen negativen Wert generiert, dessen Absolutwert größer als die größte in JavaScript darstellbare Zahl ist (d.h. eine negative Zahl, die noch kleiner als `-Number.MAX_VALUE` ist).

JavaScript zeigt den Wert `NEGATIVE_INFINITY` als `-Infinity` an. Mathematisch verhält sich dieser Wert wie unendlich. Irgendetwas mal unendlich ergibt also unendlich, und irgendetwas geteilt durch unendlich ergibt null. In ECMAScript v1 und seinen Folgeversionen können Sie auch `-Infinity` anstelle von `Number.NEGATIVE_INFINITY` verwenden.

## Siehe auch

`Infinity`, `isFinite( )`

## Name

Number.POSITIVE_INFINITY — UnendlichECMAScript v1

## Überblick

    Number.POSITIVE_INFINITY

## Beschreibung

`Number.POSITIVE_INFINITY` ist ein numerischer Spezialwert, der zurückgegeben wird, wenn eine arithmetische Operation oder mathematische Funktion einen Überlauf verursacht oder einen Wert generiert, der größer als die größte in JavaScript darstellbare Zahl (also größer als `Number.MAX_VALUE`) ist. Beachten Sie, dass Zahlen bei einem »Unterlauf«, also wenn sie kleiner als `Number.MIN_VALUE` werden, von JavaScript in null konvertiert werden.

JavaScript zeigt den Wert `POSITIVE_INFINITY` als `Infinity` an. Dieser Wert verhält sich mathematisch wie unendlich. Irgendetwas mal unendlich ergibt also unendlich, und irgendetwas geteilt durch unendlich ergibt null. In ECMAScript v1 und seinen Folgeversionen können Sie auch die vordefinierte globale Eigenschaft `Infinity` anstelle von `Number.POSITIVE_INFINITY` verwenden.

## Siehe auch

`Infinity`, `isFinite( )`

## Name

Number.toExponential( ) — Formatiert eine Zahl in ExponentialschreibweiseECMAScript v3

## Überblick

     _number_.toExponential( _ziffern_ )

### Argumente

 _`ziffern`_

Die Anzahl der Nachkommastellen. Sie kann ein Wert zwischen 0 und 20 einschließlich sein, und einzelne Implementierungen können auch einen größeren Wertebereich zulassen. Wenn dieses Argument fehlt, werden so viele Ziffern wie nötig verwendet.

### Rückgabewert

Eine String-Darstellung von _`number`_ in Exponentialschreibweise mit einer Vorkommastelle und _`ziffern`_ Nachkommastellen. Der Bruchteil der Zahl wird je nach Bedarf gerundet oder mit Nullen aufgefüllt, damit sie die angegebene Länge bekommt.

### Exceptions

`RangeError`

Tritt auf, wenn _`ziffern`_ zu klein oder zu groß ist. Werte zwischen 0 und 20 einschließlich verursachen keinen RangeError. Implementierungen können auch größere oder kleinere Werte unterstützen.

`TypeError`

Tritt auf, wenn diese Methode auf einem Objekt aufgerufen wurde, das kein Number-Objekt ist.

## Beispiel

    var n = 12345.6789;
    n.toExponential(1);   // Gibt 1.2e+4 zurück
    n.toExponential(5);   // Gibt 1.23457e+4 zurück
    n.toExponential(10);  // Gibt 1.2345678900e+4 zurück
    n.toExponential( );  // Gibt 1.23456789e+4 zurück

## Siehe auch

`Number.toFixed( )`, `Number.toLocaleString( )`, `Number.toPrecision( )`, `Number.toString( )`

## Name

Number.toFixed( ) — Formatiert eine Zahl in FestkommaschreibweiseECMAScript v3

## Überblick

     _number_.toFixed( _ziffern_ )

### Argumente

 _`ziffern`_

Die Anzahl der Nachkommastellen. Sie kann ein Wert zwischen 0 und 20 einschließlich sein, und einzelne Implementierungen können auch einen größeren Wertebereich zulassen. Wenn dieses Argument fehlt, wird es als 0 behandelt.

### Rückgabewert

Eine String-Darstellung von _`number`_ , die nicht die Exponentialschreibweise verwendet und genau _`ziffern`_ Stellen hinter dem Dezimalpunkt hat. Falls nötig, wird die Zahl gerundet und der Bruchteil mit Nullen aufgefüllt, damit er die angegebene Länge hat. Wenn _`number`_ größer als 1e+21 ist, ruft diese Methode einfach `Number.toString( )` auf und gibt einen String in Exponentialschreibweise zurück.

### Exceptions

`RangeError`

Tritt auf, wenn _`ziffern`_ zu klein oder zu groß ist. Werte von 0 bis 20 einschließlich verursachen keinen RangeError. Implementierungen können auch größere oder kleinere Werte unterstützen.

`TypeError`

Tritt auf, wenn diese Methode auf einem anderen als einem Number-Objekt aufgerufen wird.

## Beispiel

    var n = 12345.6789;
    n.toFixed( );      // Rückgabewert 12346: gerundet, kein Bruchteil
    n.toFixed(1);       // Rückgabewert 12345.7: gerundet!
    n.toFixed(6);       // Rückgabewert 12345.678900: Nullen am Ende
    (1.23e+20).toFixed(2);  // Rückgabewert 123000000000000000000.00
    (1.23e-10).toFixed(2)   // Rückgabewert 0.00

## Siehe auch

`Number.toExponential( )`, `Number.toLocaleString( )`, `Number.toPrecision( )`, `Number.toString( )`

## Name

Number.toLocaleString( ) — Konvertiert eine Zahl in einen String im ortsüblichen FormatECMAScript v3

## Überblick

     _number_
    .toLocaleString( )

### Rückgabewert

Eine implementierungsabhängige String-Darstellung der Zahl im ortsüblichen Format. Dadurch sind z.B. Interpunktionszeichen betroffen, die für das Dezimal-Trennzeichen oder als Tausender-Trennzeichen eingesetzt werden.

### Exceptions

`TypeError`

Tritt auf, wenn diese Methode auf einem Objekt aufgerufen wird, das kein Number-Objekt ist.

## Siehe auch

`Number.toExponential( )`, `Number.toFixed( )`, `Number.toPrecision( )`, `Number.toString( )`

## Name

Number.toPrecision( ) — Formatiert die signifikanten Ziffern einer ZahlECMAScript v3

## Überblick

     _number_.toPrecision( _genauigkeit_ )

### Argumente

 _`genauigkeit`_

Die Anzahl der signifikanten Ziffern, die im Rückgabestring erscheinen sollen. Dieser Wert liegt zwischen 1 und 21 einschließlich. Optional können Implementierungen auch kleinere oder größere Werte für _`genauigkeit`_ unterstützen. Wenn dieses Argument fehlt, wird die Zahl stattdessen mit der `toString( )`-Methode in einen Wert zur Basis 10 konvertiert.

### Rückgabewert

Eine String-Darstellung von _`number`_ , die _`genauigkeit`_ signifikante Ziffern enthält. Wenn _`genauigkeit`_ groß genug ist, um alle Ziffern des ganzzahligen Teils von _`number`_ aufzunehmen, verwendet der Rückgabe-String die Festkommaschreibweise. Andernfalls wird die Exponentialschreibweise mit einer Ziffer vor dem Komma und _`genauigkeit`_ −1 Nachkommastellen verwendet. Die Zahl wird, soweit nötig, gerundet oder mit Nullen aufgefüllt.

### Exceptions

`RangeError`

Tritt auf, wenn _`ziffern`_ zu klein oder zu groß ist. Werte von 1 bis 21 einschließlich verursachen keinen RangeError. Implementierungen können auch größere oder kleinere Werte unterstützen.

`TypeError`

Tritt auf, wenn diese Methode auf einem anderen als einem Number-Objekt aufgerufen wird.

## Beispiel

    var n = 12345.6789;
    n.toPrecision(1);  // Rückgabe 1e+4
    n.toPrecision(3);  // Rückgabe 1.23e+4
    n.toPrecision(5);  // Rückgabe 12346: gerundet!
    n.toPrecision(10); // Rückgabe 12345.67890: mit einer Null am Ende

## Siehe auch

`Number.toExponential( )`, `Number.toFixed( )`, `Number.toLocaleString( )`, `Number.toString( )`

## Name

Number.toString( ) — Konvertiert eine Zahl in einen StringECMAScript v1Überschreibt Object.toString( )

## Überblick

     _number_.toString( _basis_ )

### Argumente

 _`basis`_

Ein optionales Argument, das die Zahlenbasis (Radix) oder numerische Basis zwischen 2 und 36 angibt, in der die Zahl dargestellt werden soll. Wenn das Argument fehlt, wird die Basis 10 verwendet. Allerdings darf eine Implementierung laut ECMAScript-Spezifikation einen beliebigen Wert zurückgeben, wenn für dieses Argument nicht 10, sondern ein anderer Wert angegeben ist.

### Rückgabewert

Eine String-Darstellung der Zahl zur angegebenen Basis.

### Exceptions

`TypeError`

Tritt auf, wenn diese Methode auf einem Objekt aufgerufen wird, das kein Number-Objekt ist.

## Beschreibung

Die `toString( )`-Methode des Number-Objekts konvertiert eine Zahl in einen String. Wenn das Argument _`basis`_ fehlt oder als 10 spezifiziert ist, wird die Zahl in einen String zur Basis 10 konvertiert. Obwohl die ECMAScript-Spezifikation nicht verlangt, dass Implementierungen andere Werte für die Basis berücksichtigen, akzeptieren alle Implementierung in allgemeiner Verwendung Werte zwischen 2 und 36.

## Siehe auch

`Number.toExponential( )`, `Number.toFixed( )`, `Number.toLocaleString( )`, `Number.toPrecision( )`

## Name

Number.valueOf( ) — Gibt den Wert als elementare Zahl zurückECMAScript v1Überschreibt Object.valueOf( )

## Überblick

     _number_.valueOf( )

### Rückgabewert

Der Wert dieses Number-Objekts als elementare Zahl. Diese Methode muss kaum jemals explizit aufgerufen werden.

### Exceptions

`TypeError`

Tritt auf, wenn diese Methode auf einem Objekt aufgerufen wird, das kein Number-Objekt ist.

## Siehe auch

`Object.valueOf( )`

## Name

Object — Eine Oberklasse, die Features aller JavaScript-Objekte enthältECMAScript v1

## Konstruktor

    new Object( )
    new Object( _wert_ )

### Argumente

 _`wert`_

Dieses optionale Argument spezifiziert einen elementaren JavaScript-Wert, also eine Zahl, einen Booleschen Wert oder einen String, der in ein Number-, Boolean- oder String-Objekt konvertiert werden soll.

### Rückgabewert

Wenn kein _`wert`_ -Argument übergeben wird, gibt dieser Konstruktor eine neu erzeugte Instanz von Object zurück. Wird ein elementares _`wert`_ -Argument angegeben, so erzeugt und liefert er ein Wrapper-Objekt für Number, Boolean oder String. Wird der Konstruktor `Object( )` als Funktion ohne den Operator `new` aufgerufen, verhält er sich genau wie bei einem Aufruf mit dem `new`-Operator.

## Eigenschaften

`constructor`

Eine Referenz auf die JavaScript-Funktion, die der Konstruktor für das Objekt war.

## Methoden

`hasOwnProperty( )`

Prüft, ob ein Objekt eine lokal definierte (nicht geerbte) Eigenschaft mit dem angegebenen Namen hat.

`isPrototypeOf( )`

Prüft, ob dieses Objekt das Prototyp-Objekt eines spezifizierten Objekts ist.

`propertyIsEnumerable( )`

Prüft, ob es eine spezifische Eigenschaft gibt und ob diese mit einer `for/in`-Schleife aufzählbar wäre.

`toLocaleString( )`

Gibt eine lokalisierte String-Darstellung des Objekts zurück. Die Standardimplementierung dieser Methode ruft einfach `toString( )` auf, aber Unterklassen können diese Methode zum Zweck der Lokalisierung auch überschreiben.

`toString( )`

Gibt eine String-Darstellung des Objekts zurück. Die von der Klasse Object zur Verfügung gestellte Implementierung dieser Methode ist ziemlich allgemein und bietet kaum nützliche Informationen. Unterklassen von Object überschreiben diese Methode normalerweise mit einer eigenen `toString( )`-Methode, deren Ausgabe nützlicher ist.

`valueOf( )`

Gibt, falls vorhanden, den elementaren Wert des Objekts zurück. Bei Objekten vom Typ Object gibt diese Methode nur das Objekt selbst zurück. Object-Unterklassen wie Number und Boolean überschreiben diese Methode, um den elementaren Wert zurückzugeben, der mit dem Objekt verbunden ist.

## Beschreibung

Die Klasse Object ist ein eingebauter Datentyp von JavaScript. Da sie die Oberklasse aller anderen JavaScript-Objekte ist, erben alle anderen Objekte die Methoden und das Verhalten der Klasse Object. Das grundlegende Verhalten von Objekten in JavaScript wird in Kapitel 7 erläutert.

Objekte können nicht nur mit dem oben gezeigten Konstruktor `Object( )` erzeugt werden; sie lassen sich auch mit der in Kapitel 7 beschriebenen Object-Literalsyntax erzeugen und initialisieren.

## Siehe auch

`Array`, `Boolean`, `Function`, `Function.prototype`, `Number`, `String`; Kapitel 7

## Name

Object.constructor — Die Konstruktorfunktion eines ObjektsECMAScript v1

## Überblick

     _object._ constructor

## Beschreibung

Die `constructor`-Eigenschaft eines Objekts ist eine Referenz auf die Funktion, die als Konstruktor für dieses Objekt verwendet wurde. Wenn Sie beispielsweise ein Array `a` mit dem Konstruktor `Array( )` anlegen, dann ist `a.constructor` ein `Array`:

    a = new Array(1,2,3);  // Erzeuge ein Objekt
    a.constructor == Array // Wird als true ausgewertet

Oft dient die `constructor`-Eigenschaft dazu, den Typ unbekannter Objekte zu ermitteln. Wenn Sie einen unbekannten Wert haben, können Sie mit dem Operator `typeof` feststellen, ob er ein elementarer Wert oder ein Objekt ist. Ist er ein Objekt, lässt sich mit der `constructor`-Eigenschaft ermitteln, welchen Objekttyp er hat. Die folgende Funktion ermittelt z.B., ob ein gegebener Wert ein Array ist:

    function isArray(x) {
      return ((typeof x == "object") && (x.constructor == Array));
    }

Diese Technik funktioniert zwar bei Objekten, die in den JavaScript-Sprachkern eingebaut sind, aber es gibt keine Garantie dafür, dass sie auch mit Host-Objekten wie z.B. dem Window-Objekt von clientseitigem JavaScript funktionieren. Die Standardimplementierung der Methode `Object.toString( )` gibt Ihnen eine andere Möglichkeit, den Typ eines unbekannten Objekts zu ermitteln.

## Siehe auch

`Object.toString( )`

## Name

Object.hasOwnProperty( ) — Prüft, ob eine Eigenschaft geerbt wurdeECMAScript v3

## Überblick

     _object._ hasOwnProperty( _eigname_ )

### Argumente

 _`eigname`_

Ein String mit dem Namen einer Eigenschaft von _`object.`_

### Rückgabewert

`true`, wenn _`object`_ eine nicht-geerbte Eigenschaft namens _`eigname`_ hat; `false`, wenn _`object`_ eine geerbte Eigenschaft dieses Namens hat oder von seinem Prototyp-Objekt erbt.

## Beschreibung

Wie in Kapitel 9 erklärt wurde, können JavaScript-Objekte viele eigene Eigenschaften haben und außerdem noch Eigenschaften ihres Prototyp-Objekts erben. Mit der Methode `hasOwnProperty( )` können Sie zwischen geerbten und nicht-geerbten Eigenschaften unterscheiden.

## Beispiel

    var o = new Object( );     // Erzeuge ein Objekt
    o.x = 3.14;           // Definiere eine nicht-geerbte,
                     // lokale Eigenschaft
    o.hasOwnProperty("x");      // Rückgabe true: x ist eine
                     // lokale Eigenschaft von o
    o.hasOwnProperty("y");      // Rückgabe false: o hat keine
                     // Eigenschaft y
    o.hasOwnProperty("toString");  // Rückgabe false: toString-Eigenschaft
                     // ist geerbt

## Siehe auch

`Function.prototype`, `Object.propertyIsEnumerable( )`; Kapitel 9

## Name

Object.isPrototypeOf( ) — Prüft, ob ein Objekt Prototyp eines anderen istECMAScript v3

## Überblick

     _object._ isPrototypeOf( _o_ )

### Argumente

 _`o`_

Ein beliebiges Objekt.

### Rückgabewert

`true`, wenn _`object`_ der Prototyp von _`o`_ ist. Gibt `false` zurück, wenn _`o`_ kein Objekt oder _`object`_ nicht der Prototyp von _`o`_ ist.

## Beschreibung

Wie in Kapitel 9 beschrieben, erben JavaScript-Objekte Eigenschaften von ihrem Prototyp-Objekt. Der Prototyp eines Objekts wird von der `prototype`-Eigenschaft der Konstruktorfunktion referenziert, die das Objekt erzeugt und initialisiert. Die Methode `isProto-typeOf( )` bietet eine andere Möglichkeit zu ermitteln, ob ein Objekt der Prototyp eines anderen ist. Mit dieser Technik können Sie auch die Klasse eines Objekts herausfinden.

## Beispiel

    var o = new Object( );             // Erzeuge ein Objekt
    Object.prototype.isPrototypeOf(o)        // true: o ist ein Objekt
    Function.prototype.isPrototypeOf(o.toString);  // true: toString ist eine Funktion
    Array.prototype.isPrototypeOf([1,2,3]);     // true: [1,2,3] ist ein Array
    // So können Sie einen ähnlichen Test durchführen
    (o.constructor == Object); // true: o wurde mit dem Object( )-Konstruktor erzeugt
    (o.toString.constructor == Function);     // true: o.toString ist eine Funktion
    // Prototyp-Objekte haben auch selbst Prototypen. Der folgende Aufruf
    // gibt true zurück und zeigt somit, dass Funktionsobjekte Eigenschaften
    // von Function.prototype und Object.prototype erben.
    Object.prototype.isPrototypeOf(Function.prototype);

## Siehe auch

`Function.prototype`, `Object.constructor`; Kapitel 9

## Name

Object.propertyIsEnumerable( ) — Kann eine for/in-Schleife diese Eigenschaft erkennen?ECMAScript v3

## Überblick

     _object._ propertyIsEnumerable( _eigname_ )

### Argumente

 _`eigname`_

Ein String mit dem Namen einer Eigenschaft von _`object.`_

### Returns

`true`, wenn _`object`_ eine nicht-geerbte Eigenschaft namens _`eigname`_ hat und wenn diese Eigenschaft »aufzählbar« ist, also mit einer `for/in`-Schleife auf _`object`_ aufgezählt werden könnte.

## Beschreibung

Die `for/in`-Anweisung durchläuft die aufzählbaren Eigenschaften eines Objekts. Doch nicht alle Objekteigenschaften sind aufzählbar: Eigenschaften, die einem Objekt durch JavaScript-Code hinzugefügt wurden, sind es, aber die vordefinierten Eigenschaften (z.B. Methoden) von eingebauten Objekten sind es in der Regel nicht. Mit der Methode `propertyIsEnumerable( )` können Sie aufzählbare von nicht-aufzählbaren Eigenschaften unterscheiden. Allerdings sagt die ECMAScript-Spezifikation, dass `propertyIsEnumerable( )` nicht die Prototypenkette untersucht, sodass diese Methode nur bei lokalen Eigenschaften eines Objekts funktioniert und keine Möglichkeit bietet, die Aufzählbarkeit geerbter Eigenschaften zu prüfen.

## Beispiel

    var o = new Object( );        // Erzeuge ein Objekt
    o.x = 3.14;              // Definiere eine Eigenschaft
    o.propertyIsEnumerable("x");      // true: Eigenschaft x ist lokal und aufzählbar
    o.propertyIsEnumerable("y");      // false: o hat keine Eigenschaft y
    o.propertyIsEnumerable("toString");  // false: die toString-Eigenschaft ist geerbt
    Object.prototype.propertyIsEnumerable("toString"); // false: nicht aufzählbar

## Siehe auch

`Function.prototype`, `Object.hasOwnProperty( )`; Kapitel 7

## Name

Object.toLocaleString( ) — Gibt die lokalisierte String-Darstellung eines Objekts zurückECMAScript v3

## Überblick

     _object._ toString( )

### Rückgabewert

Eine String-Darstellung des Objekts.

## Beschreibung

Diese Methode soll eine String-Darstellung des Objekts in lokalisierter Form zurückgeben. Die Standardmethode `toLocaleString( )` der Klasse Object ruft einfach die Methode `toString( )` auf und liefert deren nicht-lokalisierten Rückgabe-String. Andere Klassen, darunter Array, Date und Number, definieren jedoch eigene Versionen dieser Methode, um lokalisierte String-Konvertierungen vorzunehmen. Wenn Sie eigene Klassen definieren, überschreiben Sie diese Methode am besten ebenfalls.

## Siehe auch

`Array.toLocaleString( )`, `Date.toLocaleString( )`, `Number.toLocaleString( )`, `Object.toString( )`

## Name

Object.toString( ) — Definiert die String-Darstellung eines ObjektsECMAScript v1

## Überblick

     _object._ toString( )

### Rückgabewert

Ein String, der das Objekt repräsentiert.

## Beschreibung

Die Methode `toString( )` werden Sie in Ihren JavaScript-Programmen wahrscheinlich nur selten direkt aufrufen. Sie definieren diese Methode in Ihren Objekten, und das System ruft sie immer dann auf, wenn es ein Objekt in einen String konvertieren muss.

Das JavaScript-System ruft die Methode `toString( )` auf, um ein Objekt in einen String zu konvertieren, wenn es in einem String-Kontext benutzt wird. Das geschieht beispielsweise, wenn es an eine Funktion übergeben wird, die ein String-Argument erwartet:

    alert(my_object);

Objekte werden auch dann in Strings konvertiert, wenn sie mit dem Operator `+` mit Strings verkettet werden:

    var msg = 'Mein Objekt ist: ' + my_object;

Die Methode `toString( )` wird ohne Argumente aufgerufen und sollte einen String zurückgeben. Damit sie etwas nutzt, sollte der String, den Sie zurückgeben, in irgendeiner Weise auf dem Wert des Objekts beruhen, auf dem die Methode aufgerufen wurde.

Wenn Sie in JavaScript eine eigene Klasse definieren, ist es üblich, für diese Klasse auch eine `toString( )`-Methode zu definieren. Wenn Sie dies nicht tun, erbt das Objekt die `toString( )`-Methode der Klasse Object. Diese Standardmethode gibt einen String der folgenden Form zurück:

    [object _klasse_ ]

Hier ist _`klasse`_ die Klasse des Objekts, also ein Wert wie »Object«, »String«, »Number«, »Function«, »Window«, »Document« usw. Dieses Verhalten der Standardmethode `toString( )` kann gelegentlich dabei helfen, den Typ oder die Klasse eines unbekannten Objekts zu ermitteln. Da die meisten Objekte eine benutzerdefinierte Version von `toString( )` haben, müssen Sie allerdings die Methode `Object.toString( )` auf einem Objekt `o` wie folgt aufrufen:

    Object.prototype.toString.apply(o);

Diese Technik zur Identifikation unbekannter Objekte funktioniert nur bei eingebauten Objekten. Wenn Sie eine eigene Objekt-Klasse definieren, ist ihre _`klasse`_ »Object«. In diesem Fall können Sie mit der Eigenschaft `Object.constructor` nähere Informationen über das Objekt beschaffen.

Die Methode `toString( )` kann zum Debuggen von JavaScript-Programmen sehr nützlich sein, da Sie mit ihr Objekte ausgeben und ihre Werte anschauen können. Schon allein deswegen sollten Sie für jede selbst geschriebene Objekt-Klasse immer auch eine `toString( )`-Methode definieren.

Zwar wird `toString( )` normalerweise automatisch vom System aufgerufen, aber in einigen Fällen rufen Sie diese Methode auch selbst auf, z.B. dann, wenn Sie ein Objekt explizit in einen String konvertieren möchten, wo JavaScript dies nicht automatisch für Sie tut:

    y = Math.sqrt(x);    // Berechne eine Zahl
    ystr = y.toString( ); // Konvertiere sie in einen String

Beachten Sie, dass Zahlen in diesem Beispiel eine eingebaute `toString( )`-Methode haben, mit der Sie eine Konvertierung erzwingen können.

In anderen Situationen können Sie `toString( )` in einem Kontext aufrufen, in dem JavaScript die Konvertierung auch automatisch erledigt. Die explizite Verwendung von `toString( )` kann Ihren Code klarer machen:

    alert(my_obj.toString( ));

## Siehe auch

`Object.constructor( )`, `Object.toLocaleString( )`, `Object.valueOf( )`

## Name

Object.valueOf( ) — Der elementare Wert des angegebenen ObjektsECMAScript v1

## Überblick

     _object._ valueOf( )

### Rückgabewert

Der elementare Wert (falls vorhanden), der mit _`object`_ zusammenhängt. Ist kein solcher Wert vorhanden, wird das Objekt selbst zurückgegeben.

## Beschreibung

Die `valueOf( )`-Methode eines Objekts gibt (falls vorhanden) den elementaren Wert zurück, der mit dem Objekt verbunden ist. Da für Objekte des Typs Object kein elementarer Wert existiert, gibt diese Methode hier nur das Objekt selbst zurück.

Bei Objekten vom Typ Number liefert die Methode `valueOf( )` den elementaren numerischen Wert zurück, den das Objekt repräsentiert. Ebenso gibt sie den elementaren Booleschen Wert eines Boolean-Objekts und den String eines String-Objekts zurück.

Sie müssen die Methode `valueOf( )` kaum jemals selbst aufrufen. JavaScript macht das automatisch immer dann, wenn ein Objekt in einem Kontext benutzt wird, der einen elementaren Wert erwartet. Wegen diesem automatischen Aufrufverhalten von `valueOf( )` ist es sogar schwierig, zwischen elementaren Werten und ihren zugehörigen Objekten zu unterscheiden. Der Operator `typeof` kann ihnen z.B. den Unterschied zwischen Strings und String-Objekten zeigen, aber in der Praxis können Sie beides in Ihrem JavaScript-Code äquivalent verwenden.

Die `valueOf( )`-Methoden der Objekte von Number, Boolean und String konvertieren diese Wrapper-Objekte in die von ihnen dargestellten elementaren Werte. Der Konstruktor `Object( )` macht das Gegenteil: Er hüllt den elementaren Wert in ein entsprechendes Wrapper-Objekt. Da JavaScript diese Konvertierung von Grundwerten in Objekte fast immer für Sie erledigt, brauchen Sie den Konstruktor `Object( )` kaum jemals auf diese Weise aufzurufen.

Manchmal möchten Sie eine eigene `valueOf( )`-Methode für Ihre Objekte definieren. Sie könnten z.B. einen JavaScript-Objekttyp zur Darstellung komplexer Zahlen (eine reelle Zahl plus eine imaginäre Zahl) definieren. Als Teil dieses Objekttyps würden Sie Methoden definieren, die komplexe Additionen, Multiplikationen usw. durchführen (siehe Beispiel 9-2). Außerdem möchten Sie vielleicht die Möglichkeit haben, den imaginären Teil der komplexen Zahlen wegzulassen, um die Zahlen wie normale reelle Zahlen zu behandeln. Dies können Sie z.B. folgendermaßen machen:

    Complex.prototype.valueOf = new Function("return this.real");

Mit dieser `valueOf( )`-Methode, die speziell für Ihren Objekttyp Complex definiert ist, können Sie dann z.B. eine komplexe Zahl an die Methode `Math.sqrt( )` übergeben, die die Quadratwurzel des reellen Teils der komplexen Zahl berechnet.

## Siehe auch

`Object.toString( )`

## Name

Packages — Das Wurzel-JavaPackageLiveConnect

## Überblick

    Packages

## Beschreibung

In JavaScript-Implementierungen, die LiveConnect oder eine andere Technologie zum Scripting von Java einschließen, ist die globale Eigenschaft `Packages` ein JavaPackage-Objekt, dessen Eigenschaften alle Package-Wurzeln sind, die der Java-Interpreter kennt. Beispielsweise verweist `Packages.javax.swing` auf das Java-Package _javax.swing_. Die globale Eigenschaft `java` ist eine Kurzform für `Packages.java`.

## Siehe auch

`java`, `JavaPackage`; Kapitel 12

## Name

parseFloat( ) — Konvertiert einen String in eine ZahlECMAScript v1

## Überblick

    parseFloat( _s_ )

### Argumente

 _`s`_

Der String, der geparst und in eine Zahl konvertiert werden soll.

### Rückgabewert

Die geparste Zahl oder `NaN`, wenn _`s`_ nicht mit einer gültigen Zahl beginnt. In JavaScript 1.0 gibt `parseFloat( )` 0 statt `NaN` zurück, wenn _`s`_ nicht als Zahl geparst werden kann.

## Beschreibung

`parseFloat( )` parst und liefert die erste Zahl, die in _`s`_ auftritt. Wenn `parseFloat( )` in _`s`_ ein Zeichen findet, das kein gültiger Teil der Zahl ist, endet das Parsing und der Wert wird zurückgegeben. Beginnt _`s`_ nicht mit einer Zahl, die `parseFloat( )` parsen kann, so gibt die Funktion den Wert `NaN` (Not-a-Number) zurück. Mit der Funktion `isNaN( )` können Sie auf diesen Rückgabewert hin prüfen. Wenn Sie nur den ganzzahligen Teil einer Zahl parsen möchten, verwenden Sie `parseInt( )` anstelle von `parseFloat( )`.

## Siehe auch

`isNaN( )`, `parseInt( )`

## Name

parseInt( ) — Konvertiert einen String in einen IntegerECMAScript v1

## Überblick

    parseInt( _s_ )
    parseInt( _s_ , _basis_ )

### Argumente

 _`s`_

Der zu parsende String.

 _`basis`_

Ein optionales Integer-Argument, das die Radix (d.h. numerische Basis) der zu parsenden Zahl repräsentiert. Wenn dieses Argument fehlt oder 0 ist, wird die Zahl mit der Basis 10 geparst - oder mit der Basis 16, wenn die Zahl mit 0x oder 0X beginnt. Ist dieses Argument kleiner als 2 oder größer als 36, so gibt `parseInt( )` den Wert `NaN` zurück.

### Rückgabewert

Die geparste Zahl oder `NaN`, falls _`s`_ nicht mit einem gültigen Integer beginnt. In JavaScript 1.0 gibt `parseInt( )` 0 statt `NaN` zurück, wenn _`s`_ nicht geparst werden kann.

## Beschreibung

`parseInt( )` parst und liefert die erste Zahl (optional mit einem Minuszeichen davor), die in _`s`_ auftritt. Wenn die Funktion in _`s`_ ein Zeichen findet, das keine gültige Ziffer für die angegebene _`basis`_ ist, endet das Parsing, und der Wert wird zurückgegeben. Wenn _`s`_ nicht mit einer Zahl anfängt, die `parseInt( )` parsen kann, gibt die Funktion den Wert `NaN` (Not-a-Number) zurück. Diesen Rückgabewert können Sie mit der Funktion `isNaN( )` testen.

Das Argument _`basis`_ gibt die Basis der zu parsenden Zahl an. Wenn Sie 10 angeben, parst `parseInt( )` eine Dezimalzahl. Der Wert 8 bedeutet, dass eine Oktalzahl (mit den Ziffern 0 bis 7) geparst werden soll. Der Wert 16 besagt, dass ein Hexadezimalwert mit den Ziffern 0 bis 9 und den Buchstaben A bis F geparst wird. _`basis`_ kann einen beliebigen Wert von 2 bis 36 haben.

Ist _`basis`_ 0 oder nicht angegeben, versucht `parseInt( )`, die Basis der Zahl aus _`s`_ zu ermitteln. Wenn _`s`_ (nach einem optionalen Minuszeichen) mit `0x` anfängt, parst `parseInt( )` den Rest von _`s`_ als Hexadezimalzahl. Beginnt _`s`_ mit einer `0`, so gestattet der ECMAScript v3-Standard, dass eine Implementierung von `parseInt( )` die nachfolgenden Zeichen als Oktalzahl oder als Dezimalzahl interpretieren kann. Beginnt _`s`_ mit einer Ziffer von `1` bis `9`, parst ihn `parseInt( )` als Dezimalzahl.

## Beispiel

    parseInt("19", 10); // Gibt 19 (10 + 9) zurück
    parseInt("11", 2);  // Gibt 3  (2 + 1) zurück
    parseInt("17", 8);  // Gibt 15 (8 + 7) zurück
    parseInt("1f", 16); // Gibt 31 (16 + 15) zurück
    parseInt("10");   // Gibt 10 zurück
    parseInt("0x10");  // Gibt 16 zurück
    parseInt("010");   // Doppeldeutig: Gibt 10 oder 8 zurück

## Fehler

Wenn keine _`basis`_ angegeben ist, erlaubt ECMAScript v3, dass eine Implementierung einen mit 0 (aber nicht mit 0x oder 0X) beginnenden String als Oktal- oder als Dezimalzahl parst. Um diese Doppeldeutigkeit zu vermeiden, sollten Sie explizit eine Basis angeben oder sie nur dann unspezifiziert lassen, wenn Sie sicher sind, dass alle zu parsenden Zahlen Dezimalzahlen oder aber Hexadezimalzahlen mit dem Präfix 0x oder 0X sind.

## Siehe auch

`isNaN( )`, `parseFloat( )`

## Name

RangeError — Wird ausgelöst, wenn eine Zahl außerhalb ihres zulässigen Wertebereichs liegtECMAScript v3Object → Error → RangeError

## Konstruktor

    new RangeError( )
    new RangeError( _meldung_ )

### Argumente

 _`meldung`_

Eine optionale Fehlermeldung, die Einzelheiten über die Exception liefert. Wenn dieses Argument angegeben ist, wird es als Wert der `message`-Eigenschaft des RangeError-Objekts verwendet.

### Rückgabewert

Ein neu erzeugtes RangeError-Objekt. Wurde ein _`meldung`_ -Argument angegeben, so wird es vom Error-Objekt als Wert der `message`-Eigenschaft verwendet. Andernfalls erhält diese Eigenschaft einen implementierungsspezifischen Standard-String als Wert. Wenn der Konstruktor `RangeError( )` als Funktion ohne den Operator `new` aufgerufen wird, verhält er sich genau wie bei einem Aufruf mit dem Operator `new`.

## Eigenschaften

`message`

Eine Fehlermeldung, die Einzelheiten über die Exception mitteilt. Diese Eigenschaft ist entweder ein String, der dem Konstruktor übergeben wurde, oder ein implementierungsspezifischer String. Näheres erfahren Sie unter `Error.message`.

`name`

Ein String, der den Exception-Typ angibt. Alle RangeError-Objekte erben für diese Eigenschaft den Wert »RangeError«.

## Beschreibung

Eine Instanz der Klasse RangeError wird ausgelöst, wenn ein numerischer Wert nicht in seinem zulässigen Wertebereich liegt. Wenn Sie beispielsweise die Länge eines Arrays auf eine negative Zahl setzen, wird ein RangeError ausgelöst. Einzelheiten über das Auslösen und Abfangen von Exceptions können Sie unter `Error` nachlesen.

## Siehe auch

`Error`, `Error.message`, `Error.name`

## Name

ReferenceError — Wird beim Lesen einer nicht-vorhandenen Variablen ausgelöstECMAScript v3Object → Error → ReferenceError

## Konstruktor

    new ReferenceError( )
    new ReferenceError( _meldung_
    )

### Argumente

 _`meldung`_

Eine optionale Fehlermeldung, die Einzelheiten über die Exception verrät. Ist dieses Argument angegeben, so dient es als Wert der `message`-Eigenschaft des Objekts ReferenceError.

### Rückgabewert

Ein neu erzeugtes ReferenceError-Objekt. Wenn das Argument _`meldung`_ angegeben ist, verwendet das Error-Objekt es als Wert seiner `message`-Eigenschaft. Ansonsten wird ein implementierungsspezifischer Standard-String dafür genommen. Wird der Konstruktor `ReferenceError( )` als Funktion ohne den Operator `new` aufgerufen, verhält er sich genau wie mit dem Operator `new`.

## Eigenschaften

`message`

Eine Fehlermeldung, die Einzelheiten über die Exception verrät. Diese Eigenschaft speichert den String, der dem Konstruktor übergeben wurde, oder einen implementierungsspezifischen String. Einzelheiten finden Sie unter `Error.message`.

`name`

Ein String, der den Exception-Typ angibt. Alle ReferenceError-Objekte erben für diese Eigenschaft den Wert »ReferenceError«.

## Beschreibung

Eine Instanz der Klasse ReferenceError wird ausgelöst, wenn Sie versuchen, den Wert einer nicht vorhandenen Variablen zu lesen. Einzelheiten über das Auslösen und Abfangen von Exceptions finden Sie unter `Error`.

## Siehe auch

`Error`, `Error.message`, `Error.name`

## Name

RegExp — Reguläre Ausdrücke für die MustererkennungJECMAScript v3Object → RegExp

## Literalsyntax

    / _muster_
    / _attribute_

## Konstruktor

    new RegExp( _muster_ , _attribute_ )

### Argumente

 _`muster`_

Ein String, der das Muster des regulären Ausdrucks angibt, oder ein anderer regulärer Ausdruck.

 _`attribute`_

Ein optionaler String mit den Argumenten »g«, »i« oder »m«, die eine globale Mustererkennung, eine Mustererkennung ohne Berücksichtigung der Groß- und Kleinschreibung sowie eine mehrzeilige Mustererkennung anstoßen. Das Attribut »m« ist erst seit der ECMAScript-Standardisierung verfügbar. Ist das Argument _`muster`_ kein String, sondern ein regulärer Ausdruck, muss das Argument _`attribute`_ weggelassen werden.

### Rückgabewert

Ein neues RegExp-Objekt mit dem angegebenen Muster und den angegebenen Flags. Wenn das _`muster`_ -Argument kein String ist, erzeugt der Konstruktor `RegExp( )` ein neues RegExp-Objekt mit demselben Muster und denselben Flags wie das angegebene RegExp-Objekt. Wird `RegExp( )` als Funktion ohne den Operator `new` aufgerufen, verhält sie sich genau wie bei einem Aufruf mit dem Operator `new`, es sei denn, _`muster`_ ist ein regulärer Ausdruck: In diesem Fall gibt sie einfach _`muster`_ zurück, anstatt ein neues RegExp-Objekt zu erzeugen.

### Exceptions

`SyntaxError`

Tritt auf, wenn _`muster`_ kein gültiger regulärer Ausdruck ist oder wenn _`attribute`_ andere Zeichen als »g«, »i« und »m« enthält.

`TypeError`

Tritt auf, wenn _`muster`_ ein RegExp-Objekt ist, aber das Argument _`attribute`_ dennoch vorhanden ist.

## Instanzeigenschaften

`global`

Gibt an, ob RegExp das Attribut »g« hat.

`ignoreCase`

Gibt an, ob RegExp das Attribut »i« hat.

`lastIndex`

Die Zeichenposition der letzten Übereinstimmung; dient zum Auffinden mehrerer Übereinstimmungen in einem String.

`multiline`

Gibt an, ob RegExp das Attribut »m« hat.

`source`

Der Quelltext des regulären Ausdrucks.

## Methoden

`exec( )`

Leistet eine mächtige Mustererkennung für viele Zwecke.

`test( )`

Testet, ob ein String ein Muster enthält.

## Beschreibung

Das RegExp-Objekt repräsentiert einen regulären Ausdruck, ein mächtiges Tool für die Mustererkennung in Strings. Vollständige Erläuterungen zur Syntax und Verwendung von regulären Ausdrücken finden Sie in Kapitel 11.

## Siehe auch

Kapitel 11

## Name

RegExp.exec( ) — Mustererkennung für allgemeine ZweckeECMAScript v3

## Überblick

     _regexp_.exec( _string_ )

### Argumente

 _`string`_

Der zu durchsuchende String.

### Rückgabewert

Ein Array mit dem Ergebnis der Erkennung oder `null`, wenn keine Übereinstimmung gefunden wurde. Das Format des Rückgabe-Arrays wird weiter unten beschrieben.

### Exceptions

`TypeError`

Tritt auf, wenn diese Methode auf einem anderen als einem RegExp-Objekt aufgerufen wird.

## Beschreibung

`exec( )` ist die mächtigste aller Mustererkennungsmethoden von RegExp und String. Sie ist eine Allzweckmethode, die ein wenig komplizierter als `RegExp.test( )`, `String.search( )`, `String. replace( )` und `String.match( )` zu benutzen ist.

`exec( )` sucht in _`string`_ Text, auf den _`regexp`_ passt. Wenn die Methode eine Übereinstimmung findet, gibt sie ein Array mit den Ergebnissen zurück, andernfalls `null`. Das Element 0 des Rückgabe-Arrays ist der übereinstimmende Text. Das Element 1 ist der Text, der mit dem ersten, in Klammern stehenden Teilausdruck in _`regexp`_ übereinstimmte, falls es einen gibt. Das Element 2 enthält den Text, der zum zweiten Teilausdruck passte usw. Die `length`-Eigenschaft des Arrays gibt wie immer die Anzahl der Array-Elemente an. Zusätzlich zu den Array-Elementen und der Eigenschaft `length` hat der Rückgabewert von `exec( )` noch zwei weitere Eigenschaften. Die Eigenschaft `index` gibt die Position des ersten erkannten Zeichens im Text zurück. Die Eigenschaft `input` bezieht sich auf _`string`_. Dieses Rückgabe-Array ist dasselbe, das auch die Methode `String.match( )` zurückgibt, wenn sie auf einem nicht-globalen RegExp-Objekt aufgerufen wird.

Wenn die Methode `exec( )` auf einem nicht-globalen Muster aufgerufen wird, führt sie die Suche durch und gibt die weiter oben beschriebenen Ergebnisse zurück. Ist _`regexp`_ ein globaler regulärer Ausdruck, so ist das Verhalten von `exec( )` allerdings ein wenig komplexer. Sie beginnt die Suche in _`string`_ an der Zeichenposition, die die _`regexp`_ -Eigenschaft `lastIndex` angibt. Wenn sie eine Übereinstimmung findet, setzt sie `lastIndex` auf die Position des ersten erkannten Zeichens. Sie können `exec( )` also mehrfach aufrufen, um alle Übereinstimmungen in einem String zu durchlaufen. Wenn `exec( )` keine weiteren Übereinstimmungen finden kann, gibt sie `null` zurück und setzt `lastIndex` wieder auf null. Wenn Sie sofort nach dem erfolgreichen Auffinden einer Übereinstimmung in einem anderen String beginnen, einen neuen String zu durchsuchen, müssen Sie darauf achten, dass Sie `lastIndex` wieder auf null setzen.

Beachten Sie, dass die Methode `exec( )` in ihrem Rückgabe-Array immer alle Einzelheiten über jede gefundene Übereinstimmung angibt, egal ob _`regexp`_ ein globales Muster ist oder nicht. Darin unterscheidet sich `exec( )` von `String.match( )`: Letztere Methode gibt viel weniger Informationen zurück, wenn man sie mit globalen Mustern verwendet. Nur durch wiederholten Aufruf von `exec( )` in einer Schleife können Sie vollständige Informationen über die Mustererkennung mit einem globalen Muster erlangen.

## Beispiel

Sie können mit `exec( )` eine Schleife schreiben, um alle Übereinstimmungen in einem String zu finden. Ein Beispiel:

    var pattern = /\bJava\w*\b/g;
    var text = "JavaScript ist spaßiger als Java oder JavaBeans!";
    var result;
    while((result = pattern.exec(text)) != null) {
      alert("Erkannt " + result[0] +
         "' an Position " + result.index +
         " nächste Suche beginnt an Position " + pattern.lastIndex);
    }

## Siehe auch

`RegExp.lastIndex`, `RegExp.test( )`, `String.match( )`, `String.replace( )`, `String.search( )`; Kapitel 11

## Name

RegExp.global — Zeigt an, ob ein regulärer Ausdruck eine globale Erkennung durchführtECMAScript v3

## Überblick

     _regexp_.global

## Beschreibung

`global` ist eine schreibgeschützte Boolesche Eigenschaft von RegExp-Objekten. Sie gibt an, ob ein bestimmter regulärer Ausdruck eine globale Mustererkennung durchführt, d.h., ob er mit dem Attribut »g« angelegt wurde.

## Name

RegExp.ignoreCase — Zeigt an, ob ein regulärer Ausdruck die Groß- und Kleinschreibung ignoriertECMAScript v3

## Überblick

     _regexp_.ignoreCase

## Beschreibung

`ignoreCase` ist eine schreibgeschützte Boolesche Eigenschaft von RegExp-Objekten. Sie gibt an, ob ein bestimmter regulärer Ausdruck eine Mustererkennung ohne Unterscheidung der Groß- und Kleinschreibung durchführt, d.h., ob er mit dem Attribut »i« angelegt wurde.

## Name

RegExp.lastIndex — Die Position, an der die nächste Erkennung beginntECMAScript v3

## Überblick

     _regexp_.lastIndex

## Beschreibung

`lastIndex` ist eine les- und schreibbare Eigenschaft von RegExp-Objekten. Bei regulären Ausdrücken mit dem Attribut »g« enthält sie einen Integer, der die Zeichenposition unmittelbar hinter der zuletzt von den Methoden `RegExp.exec( )` und `RegExp.test( )` gefundenen Übereinstimmung angibt. Diese Methoden verwenden diese Eigenschaft als Anfangspunkt für ihre nächste Suche. So können Sie die Methoden mehrmals aufrufen, um alle Übereinstimmungen in einem String zu durchlaufen. Beachten Sie, dass `lastIndex` nicht von RegExp-Objekten genutzt wird, die nicht das Attribut »g« gesetzt haben und keine globalen Muster darstellen.

Da diese Eigenschaft nicht schreibgeschützt ist, können Sie sie jederzeit auf die Stelle setzen, an der im Zielstring die nächste Suche beginnen soll. `exec( )` und `test( )` setzen `lastIndex` automatisch wieder auf 0, wenn sie keine Übereinstimmung finden. Wenn Sie nach der erfolgreichen Erkennung eines Strings einen neuen String zu durchsuchen beginnen, müssen Sie diese Eigenschaft explizit auf 0 setzen.

## Siehe auch

`RegExp.exec( )`, `RegExp.test( )`

## Name

RegExp.source — Der Test des regulären AusdrucksECMAScript v3

## Überblick

     _regexp_.source

## Beschreibung

`source` ist eine schreibgeschützte String-Eigenschaft von RegExp-Objekten und enthält den Text des RegExp-Musters. Dieser Text umfasst weder die Schrägstriche, die in der Literaldarstellung den regulären Ausdruck begrenzen, noch die Attribute »g«, »i« und »m«.

## Name

RegExp.test( ) — Testet, ob ein String zu einem Muster passtECMAScript v3

## Überblick

     _regexp_.test( _string_ )

### Argumente

 _`string`_

Der zu testende String.

### Rückgabewert

`true`, wenn _`string`_ Text enthält, auf den _`regexp`_ passt, andernfalls `false`.

### Exceptions

`TypeError`

Tritt auf, wenn diese Methode auf einem anderen als einem RegExp-Objekt aufgerufen wird.

## Beschreibung

`test( )` prüft, ob in _`string`_ Text vorkommt, auf den _`regexp`_ passt. Ist solcher Text vorhanden, gibt die Funktion `true` zurück, andernfalls liefert sie `false`. Wenn Sie die Testmethode eines RegExp-Objekts _`r`_ aufrufen und ihr den String _`s`_ übergeben, ist dies äquivalent zu folgendem Ausdruck:

    (r.exec(s) != null)

## Beispiel

    var pattern = /java/i;
    pattern.test("JavaScript");  // Gibt true zurück
    pattern.test("ECMAScript");  // Gibt false zurück

## Siehe auch

`RegExp.exec( )`, `RegExp.lastIndex`, `String.match( )`, `String.replace( )`, `String.substring( )`; Kapitel 11

## Name

RegExp.toString( ) — Konvertiert einen regulären Ausdruck in einen StringECMAScript v3Überschreibt Object.toString( )

## Überblick

     _regexp_.toString( )

### Rückgabewert

Eine String-Darstellung von _`regexp`_.

### Exceptions

`TypeError`

Tritt auf, wenn diese Methode auf einem anderen Objekt als einem RegExp-Objekt aufgerufen wird.

## Beschreibung

Die Methode `RegExp.toString( )` gibt eine String-Darstellung eines regulären Ausdrucks in Form eines entsprechenden Literals zurück.

Implementierungen müssen nicht unbedingt Escape-Sequenzen einfügen, um sicherzustellen, dass der Rückgabe-String die Literalsyntax eines gültigen regulären Ausdrucks hat. Schauen Sie sich an, welchen regulären Ausdruck der Ausdruck `new RegExp("/", "g")` erzeugt. Eine Implementierung von `RegExp.toString( )` könnte für diesen regulären Ausdruck `///g` zurückgeben, aber ebenso gut könnte sie auch eine Escape-Sequenz einfügen und `/\//g` zurückliefern.

## Name

String — Unterstützung für StringsECMAScript v1Object → String

## Konstruktor

    new String( _s_ ) // Konstruktorfunktion
    String( _s_ )     // Konvertierungsfunktion

### Argumente

 _`s`_

Der Wert, der in einem String-Objekt gespeichert oder in einen elementaren String konvertiert wird.

### Rückgabewert

Wenn `String( )` als Konstruktor mit dem Operator `new` verwendet wird, gibt die Funktion ein String-Objekt zurück, das den String _`s`_ oder die String-Darstellung von _`s`_ enthält. Wird der Konstruktor `String( )` ohne den Operator `new` verwendet, so konvertiert er _`s`_ nur in einen elementaren String und gibt den konvertierten Wert zurück.

## Eigenschaften

`length` | Die Anzahl der Zeichen im String.

---|---

## Methoden

`charAt( )` | Gibt das Zeichen an einer gegebenen String-Position zurück.

---|---

`charCodeAt( )` | Gibt die Codierung eines Zeichens an einer gegebenen String-Position zurück.

`concat( )` | Verkettet einen oder mehrere Werte mit einem String.

`indexOf( )` | Sucht in dem String ein Zeichen oder einen Teilstring.

`lastIndexOf( )` | Durchsucht den String rückwärts nach einem Zeichen oder Teilstring.

`localeCompare()` | Vergleicht Strings anhand einer locale-spezifischen Sortierfolge.

`match( )` | Mustererkennung mit einem regulären Ausdruck.

`replace( )` | Suchen-und-ersetzen-Operation mit einem regulären Ausdruck.

`search( )` | Sucht in einem String einen Teilstring, der zu einem regulären Ausdruck passt.

`slice( )` | Gibt ein Slice (ein Stück) oder einen Teilstring eines Strings zurück.

`split( )` | Zerlegt einen String in ein String-Array, indem sie ihn an einem angegebenen Begrenzungszeichen oder einem regulären Ausdruck aufbricht.

`substr( )` | Holt aus einem String einen Teilstring heraus. Variante von `sub-string( )`.

`substring( )` | Holt aus einem String einen Teilstring heraus.

`toLowerCase( )` | Gibt eine Kopie des Strings zurück, in der alle Zeichen in Kleinbuchstaben konvertiert wurden.

`toString( )` | Gibt den elementaren Stringwert zurück.

`toUpperCase( )` | Gibt eine Kopie des Strings zurück, in der alle Zeichen in Großbuchstaben konvertiert wurden.

`valueOf( )` | Gibt den elementaren Stringwert zurück.

## Statische Methoden

`String.fromCharCode( )`

Erzeugt einen neuen String und verwendet die übergebenen Zeichencodes als Argumente.

## HTML-Methoden

Seit den Anfängen von JavaScript hat die Klasse String etliche Methoden zur Rückgabe von Strings definiert. Der String wird insofern modifiziert, als er in HTML-Tags gesetzt wird. Diese Methoden wurden von ECMAScript zwar nie standardisiert, können aber für client- und serverseitigen JavaScript-Code, der dynamisch HTML generiert, nützlich sein. Wenn Sie diese nicht-standardmäßigen Methoden verwenden möchten, können Sie z.B. HTML-Quelltext für einen fetten, roten Hyperlink mit Code wie diesem erzeugen:

    var s = "Klicken Sie hier!";
    var html = s.bold( ).link("javascript:alert('hello')").fontcolor("red");

Da diese Methoden nicht standardisiert sind, bekommen sie auf den nachfolgenden Seiten keine eigenen Referenzeinträge:

`anchor(name)` | Gibt eine Kopie des Strings in einer `<a name=>`-Umgebung zurück.

---|---

`big( )` | Gibt eine Kopie des Strings in einer `<big>`-Umgebung zurück.

`blink( )` | Gibt eine Kopie des Strings in einer `<blink>`-Umgebung zurück.

`bold( )` | Gibt eine Kopie des Strings in einer `<b>`-Umgebung zurück.

`fixed( )` | Gibt eine Kopie des Strings in einer `<tt>`-Umgebung zurück.

`fontcolor(farbe)` | Gibt eine Kopie des Strings in einer `<font color=>`-Umgebung zurück.

`fontsize(groesse)` | Gibt eine Kopie des Strings in einer `<font size=>`-Umgebung zurück.

`italics( )` | Gibt eine Kopie des Strings in einer `<i>`-Umgebung zurück.

`link(url)` | Gibt eine Kopie des Strings in einer `<a href=>`-Umgebung zurück.

`small( )` | Gibt eine Kopie des Strings in einer `<small>`-Umgebung zurück.

`strike( )` | Gibt eine Kopie des Strings in einer `<strike>`-Umgebung zurück.

`sub( )` | Gibt eine Kopie des Strings in einer `<sub>`-Umgebung zurück.

`sup( )` | Gibt eine Kopie des Strings in einer `<sup>`-Umgebung zurück.

## Beschreibung

Strings sind in JavaScript ein primitiver Datentyp. Der Typ der Klasse String dient dazu, Methoden für die Arbeit mit elementaren Stringwerten zur Verfügung zu stellen. Die `length`-Eigenschaft eines String-Objekts gibt an, wie viele Zeichen der String hat. Die Klasse String definiert mehrere Methoden für String-Operationen: Es gibt z.B. Methoden, mit denen Sie ein Zeichen oder einen Teilstring aus dem String extrahieren oder nach einem Zeichen oder einem Teilstring suchen können. Beachten Sie, dass JavaScript-Strings _unveränderlich_ sind: Keine der Methoden der Klasse String ermöglicht es Ihnen, den Inhalt eines Strings zu ändern. Dafür gibt es Methoden wie `String.toUpperCase( )`, die einen ganz neuen String zurückgeben, ohne das Original zu verändern.

In JavaScript-Implementierungen, die von der ursprünglichen Netscape-Codebasis abgeleitet sind (wie die Implementierung in Firefox), verhalten sich Strings wie schreibgeschützte Arrays mit Zeichen. Um beispielsweise das dritte Zeichen aus einem String _`s`_ herauszuholen, können Sie statt der eher standardkonformen Methode `s.charAt(2)` auch die Array-Notation `s[2]` nutzen. Außerdem gilt: Wenn Sie auf einen String die `for/in`-Anweisung anwenden, zählt sie diese Array-Indizes für jedes Zeichen im String auf. (Die `length`-Eigenschaft ist allerdings gemäß ECMAScript-Spezifikation nicht aufzählbar.) Da dieses String-als-Array-Verhalten nicht dem Standard entspricht, sollten Sie es besser nicht benutzen.

## Siehe auch

Kapitel 3

## Name

String.charAt( ) — Holt das _n_ te Zeichen aus einem StringECMAScript v1

## Überblick

     _string_.charAt( _n_ )

### Argumente

 _`n`_

Der Index des Zeichens, das aus _`string`_ zurückgegeben werden soll.

### Rückgabewert

Das _`n`_ te Zeichen von _`string`_.

## Beschreibung

`String.charAt( )` gibt das _`n`_ te Zeichen des Strings _`string`_ zurück. Das erste Zeichen des Strings hat die Nummer 0. Liegt _`n`_ nicht zwischen 0 und _`string.`_`length`−1, so gibt diese Methode den leeren String zurück. Beachten Sie, dass JavaScript keinen anderen Zeichendatentyp als String hat und dass das Rückgabezeichen daher ein String der Länge 1 ist.

## Siehe auch

`String.charCodeAt( )`, `String.indexOf( )`, `String.lastIndexOf( )`

## Name

String.charCodeAt( ) — Holt den _n_ ten Zeichencode aus einem StringECMAScript v1

## Überblick

     _string_.charCodeAt( _n_ )

### Argumente

 _`n`_

Der Index des Zeichens, dessen Codierung zurückgegeben werden soll.

### Rückgabewert

Die Unicode-Codierung des _`n`_ ten Zeichens in _`string`_. Dieser Rückgabewert ist ein 16-Bit-Integer zwischen 0 und 65535.

## Beschreibung

`charCodeAt( )` ähnelt `charAt( )`, gibt allerdings den Zeichencode an einer spezifischen Stelle zurück und keinen Teilstring, der das eigentliche Zeichen enthält. Wenn _`n`_ negativ oder größer oder gleich der String-Länge ist, gibt `charCodeAt( )` den Wert `NaN` zurück.

Unter _String.fromCharCode( )_ finden Sie eine Möglichkeit, aus Unicode-Codierungen einen String zu erzeugen.

## Siehe auch

`String.charAt( )`, `String.fromCharCode( )`

## Name

String.concat( ) — Verkettet StringsECMAScript v3

## Überblick

     _string_.concat( _wert_ , ...)

### Argumente

 _`wert, ...`_

Ein oder mehrere Werte, die mit _`string`_ verkettet werden sollen.

### Rückgabewert

Ein neuer String, der sich ergibt, wenn Sie die Argumente mit _`string`_ verkettet haben.

## Beschreibung

`concat( )` konvertiert jedes seiner Argumente in einen String (wenn nötig) und hängt sie der Reihe nach an _`string`_ an. Die Funktion gibt die resultierende Verkettung zurück. _`string`_ selbst bleibt dabei unverändert.

`String.concat( )` ist analog zu `Array.concat( )`. Oft ist die String-Verkettung mit dem Operator `+` leichter.

## Siehe auch

`Array.concat( )`

## Name

String.fromCharCode( ) — Erzeugt einen String aus ZeichencodierungenECMAScript v1

## Überblick

    String.fromCharCode( _c1_ , _c2_ , ...)

### Argumente

 _`c1, c2, ...`_

Null oder mehr Integer, die die Unicode-Codierungen der Zeichen in dem zu erzeugenden String angeben.

### Rückgabewert

Ein neuer String, der die Zeichen mit den angegebenen Codierungen enthält.

## Beschreibung

Mit dieser statischen Methode können Sie einen String erzeugen, indem Sie die einzelnen numerischen Unicode-Codierungen seiner Zeichen angeben. Beachten Sie, dass `fromCharCode( )` als statische Methode eine Eigenschaft des Konstruktors `String( )` ist und keine Methode von Strings oder String-Objekten.

`String.charCodeAt( )` ist eine dazu passende Instanzmethode, mit der Sie die Codierungen der einzelnen Zeichen eines Strings beschaffen können.

## Beispiel

    // Erzeuge den String "hello"
    var s = String.fromCharCode(104, 101, 108, 108, 111);

## Siehe auch

`String.charCodeAt( )`

## Name

String.indexOf( ) — Durchsucht einen StringECMAScript v1

## Überblick

     _string_.indexOf( _teilstring_ )
    _string_.indexOf( _teilstring_ , _start_ )

### Argumente

 _`teilstring`_

Der Teilstring, der in _`string`_ gesucht werden soll.

 _`start`_

Ein optionales Integer-Argument, das angibt, wo in String die Suche beginnen soll. Die zulässigen Werte sind 0 (die Position des ersten Zeichens im String) bis _`string`_`.length`−1 (die Position des letzten Zeichens im String). Wenn dieses Argument fehlt, beginnt die Suche beim ersten Zeichen des Strings.

### Rückgabewert

Die Position des ersten Auftretens von _`teilstring`_ in _`string`_ , die hinter der _`start`_ -Position (falls vorhanden) liegt, oder −1, wenn kein solcher _`teilstring`_ gefunden wurde.

## Beschreibung

`String.indexOf( )` durchsucht den _`string`_ von vorne bis hinten nach dem _`teilstring`_. Die Suche beginnt an der _`start`_ -Position in _`string`_ oder am Anfang von _`string`_ , wenn _`start`_ nicht angegeben ist. Wird _`teilstring`_ gefunden, so gibt `String.indexOf( )` die Position des ersten Zeichens des ersten Auftretens von _`teilstring`_ in _`string`_ zurück. Die Zeichenpositionen in _`string`_ werden beginnend mit null nummeriert.

Wird _`teilstring`_ in _`string`_ nicht gefunden, gibt `indexOf( )` −1 zurück.

## Siehe auch

`String.charAt( )`, `String.lastIndexOf( )`, `String.substring( )`

## Name

String.lastIndexOf( ) — Durchsucht einen String rückwärtsECMAScript v1

## Überblick

     _string_.lastIndexOf( _teilstring_ )
    _string_.lastIndexOf( _teilstring_ , _start_ )

### Argumente

 _`teilstring`_

Der Teilstring, der in _`string`_ gesucht werden soll.

 _`start`_

Ein optionales Integer-Argument, das angibt, wo in String die Suche beginnen soll. Die zulässigen Werte sind 0 (die Position des ersten Zeichens im String) bis _`string`_`.length`−1 (die Position des letzten Zeichens im String). Wenn dieses Argument fehlt, beginnt die Suche beim letzten Zeichen des Strings.

### Rückgabewert

Die Position des letzten Auftretens von _`teilstring`_ in _`string`_ , die vor der _`start`_ -Position (falls vorhanden) liegt, oder −1, wenn kein solcher _`teilstring`_ gefunden wurde.

## Beschreibung

`String.lastIndexOf( )` durchsucht den _`string`_ von hinten bis vorne nach dem _`teilstring`_. Die Suche beginnt an der _`start`_ -Position in _`string`_ oder am Ende von _`string`_ , wenn _`start`_ nicht angegeben ist. Wird _`teilstring`_ gefunden, so gibt `String.indexOf( )` die Position des ersten Zeichens dieses Auftretens zurück. Da diese Methode von hinten nach vorne sucht, ist das erste Auftreten bezogen auf den String eigentlich das letzte, das vor der _`start`_ -Position liegt.

Wird _`teilstring`_ in _`string`_ nicht gefunden, so gibt `String.lastIndexOf( )` den Wert −1 zurück.

Beachten Sie, dass `String.lastIndexOf( )` den _`string`_ zwar von hinten nach vorne durchsucht, aber dennoch die Zeichenpositionen in _`string`_ von vorne beginnend nummeriert. Das erste Zeichen des Strings hat die Position 0 und das letzte die Position _`string`_`.length`−1.

## Siehe auch

`String.charAt( )`, `String.indexOf( )`, `String.substring( )`

## Name

String.length — Die Länge eines StringsECMAScript v1

## Überblick

     _string_.length

## Beschreibung

Die Eigenschaft `String.length` ist ein schreibgeschützter Integer, der die Anzahl der Zeichen im angegebenen _`string`_ zeigt. Bei jedem String _`s`_ ist der Index des letzten Zeichens _`s`_`.length`−1. Die `length`-Eigenschaft eines Strings wird nicht von einer `for/in`-Schleife aufgezählt und kann auch nicht mit dem `delete`-Operator gelöscht werden.

## Name

String.localeCompare( ) — Vergleicht einen String mit einem anderen und verwendet dazu die locale-spezifische ReihenfolgeECMAScript v3

## Überblick

     _string_.localeCompare( _ziel_ )

### Argumente

 _`ziel`_

Ein `string`, der in locale-spezifischer Weise mit _`string`_ verglichen werden soll.

### Rückgabewert

Eine Zahl, die das Vergleichsergebnis widerspiegelt. Ist _`string`_ »kleiner als« _`ziel`_ , gibt `localeCompare( )` eine Zahl kleiner als null zurück. Ist _`string`_ »größer als« _`ziel`_ , gibt die Methode eine Zahl größer als null zurück. Wenn beide Strings identisch oder nach den lokalen Ordnungskonventionen nicht zu unterscheiden sind, dann gibt die Methode 0 zurück.

## Beschreibung

Wenn die Operatoren `<` und `>` auf Strings angewendet werden, vergleichen sie diese Strings anhand der Unicode-Codierungen der Zeichen und lassen die Sortierreihenfolge gemäß dem örtlichen Locale außer acht. Auf diese Weise erhält man nicht immer die richtige Reihenfolge. In Spanien werden z.B. die Buchstaben »ch« traditionell so eingeordnet, als seien sie ein einzelner Buchstabe, der zwischen den Buchstaben »c« und »d« steht.

`localeCompare( )` bietet eine Möglichkeit, Strings in einer Weise zu vergleichen, die die Sortierreihenfolge gemäß dem örtlichen Locale berücksichtigt. Der ECMAScript-Standard sagt nicht, wie der locale-spezifische Vergleich vorgenommen werden soll, sondern verlangt nur, dass diese Funktion die Sortierreihenfolge anwendet, die das zugrunde liegende Betriebssystem vorgibt.

## Beispiel

Mit Code wie diesem können Sie ein String-Array in eine locale-spezifische Reihenfolge bringen:

    var strings; // Das zu sortierende String-Array; anderswo initialisiert
    strings.sort(function(a,b) { return a.localeCompare(b) });

## Name

String.match( ) — Sucht eine oder mehrere Übereinstimmungen mit einem regulären AusdruckECMAScript v3

## Überblick

     _string_.match( _regexp_ )

### Argumente

 _`regexp`_

Ein RegExp-Objekt, das das zu erkennende Muster angibt. Ist dieses Argument kein RegExp-Objekt, wird es zunächst durch Übergabe an den `RegExp( )`-Konstruktor in ein solches konvertiert.

### Rückgabewert

Ein Array mit den Ergebnissen der Mustererkennung. Der Array-Inhalt hängt davon ab, ob _`regexp`_ das globale Attribut »g« hat. In der Beschreibung finden Sie noch mehr Einzelheiten über den Rückgabewert.

## Beschreibung

`match( )` sucht im _`string`_ nach einem oder mehreren Auftreten von _`regexp`_. Das Verhalten dieser Methode hängt stark davon ab, ob _`regexp`_ das Attribut »g« hat oder nicht. In Kapitel 11 finden Sie Erläuterungen zu regulären Ausdrücken.

Wenn _`regexp`_ nicht das Attribut »g« hat, sucht `match( )` in _`string`_ nach einer einzigen Übereinstimmung. Ist keine zu finden, gibt `match( )` den Wert `null` zurück. Andernfalls wird ein Array mit Informationen über die gefundene Übereinstimmung zurückgegeben. Das Array-Element 0 enthält den übereinstimmenden Text, und die übrigen Elemente enthalten Text, der mit eventuell vorhandenen, in Klammern stehenden Teilausdrücken in dem regulären Ausdruck übereinstimmt. Zusätzlich zu diesen normalen Array-Elementen hat das Rückgabe-Array auch zwei Objekteigenschaften. Die Eigenschaft `index` des Arrays gibt an, an welcher Zeichenposition in _`string`_ der übereinstimmende Text beginnt, und die Eigenschaft `input` des Rückgabe-Arrays ist eine Referenz auf _`string`_ selbst.

Wenn _`regexp`_ das Flag »g« hat, führt die Methode `match( )` eine globale Suche durch, indem sie _`string`_ nach allen passenden Teilstrings durchsucht. Wenn keiner gefunden wird, ist die Rückgabe `null`, und wenn eine oder mehrere Übereinstimmungen gefunden werden, ist die Rückgabe ein Array. Der Inhalt dieses Rückgabe-Arrays ist allerdings ganz anders als bei globalen Mustererkennungen. Hier enthalten die Array-Elemente alle Teilstrings, die in _`string`_ eine Übereinstimmung erbracht haben. Das Rückgabe-Array hat in diesem Fall nicht die Eigenschaften `index` oder `input`. Beachten Sie, dass `match( )` bei globalen Mustererkennungen weder etwas über in Klammern stehende Teilausdrücke aussagt noch angibt, wo in _`string`_ die einzelnen Übereinstimmungen aufgetreten sind. Wenn Sie für eine globale Suche diese Informationen benötigen, müssen Sie `RegExp.exec( )` verwenden.

## Beispiel

Die folgende globale Mustererkennung findet alle Zahlen in einem String:

    "1 plus 2 gleich 3".match(/\d+/g) // Rückgabe ["1", "2", "3"]

Die folgende, nicht-globale Mustererkennung verwendet einen komplexeren regulären Ausdruck mit mehreren in Klammern stehenden Teilausdrücken. Er erkennt eine URL, und seine Teilausdrücke erkennen die einzelnen URL-Teile für das Protokoll, den Host und den Pfad:

    var url = /(\w+):\/\/([\w.]+)\/(\S*)/;
    var text = "Visit my home page at http://www.isp.com/~david";
    var result = text.match(url);
    if (result != null) {
      var fullurl = result[0];  // Enthält "http://www.isp.com/~david"
      var protocol = result[1]; // Enthält "http"
      var host = result[2];   // Enthält "www.isp.com"
      var path = result[3];   // Enthält "~david"
    }

## Siehe auch

`RegExp`, `RegExp.exec( )`, `RegExp.test( )`, `String.replace( )`, `String.search( )`; Kapitel 11

## Name

String.replace( ) — Ersetzt zu einem regulären Ausdruck passende(n) Teilstring(s)ECMAScript v3

## Überblick

     _string_.replace( _regexp_ , _ersatz_ )

### Argumente

 _`regexp`_

Das RegExp-Objekt, das das zu ersetzende Muster angibt. Wenn dieses Argument ein String ist, wird es wie ein zu suchendes wörtliches Textmuster behandelt und nicht erst in ein RegExp-Objekt konvertiert.

 _`ersatz`_

Ein String, der den Ersetzungstext spezifiziert, oder eine Funktion, die aufgerufen wird, um den Ersetzungstext zu generieren. Einzelheiten finden Sie im Abschnitt »Beschreibung«.

### Rückgabewert

Ein neuer String, in dem die erste oder alle Übereinstimmungen mit _`regexp`_ durch _`ersatz`_ ersetzt wurden.

## Beschreibung

`replace( )` führt eine Suchen-und-ersetzen-Operation auf _`string`_ aus. Sie sucht in _`string`_ einen oder mehrere Teilstrings, die mit _`regexp`_ übereinstimmen, und ersetzt sie durch _`ersatz`_. Wenn _`regexp`_ das globale Attribut »g« hat, ersetzt `replace( )` alle passenden Teilstrings. Andernfalls ersetzt die Methode nur den ersten passenden Teilstring.

 _`ersatz`_ kann ein String oder eine Funktion sein. Ist er ein String, so wird jede Übereinstimmung durch diesen String ersetzt. Beachten Sie, dass das Zeichen `$` im _`ersatz`_ -String eine Sonderbedeutung hat. Wie die folgende Tabelle zeigt, weist dieses Zeichen darauf hin, dass ein von einer Mustererkennung abgeleiteter String in den Ersatz einfließen soll.

Zeichen | Ersatz

---|---

`$1`, `$2`, ... `$99` | Der mit dem ersten bis 99. in Klammern stehenden Teil-Ausdruck in _`regexp`_ übereinstimmende Teilstring

`$&` | Der Teilstring, der mit _`regexp`_ übereinstimmte

`$`` | Der Text links von dem erkannten Teilstring

`$'` | Der Text rechts von dem erkannten Teilstring

`$$` | Ein Dollarzeichenliteral

ECMAScript v3 besagt, dass das _`ersatz`_ -Argument von `replace( )` auch eine Funktion statt eines Strings sein kann. In diesem Fall wird bei jeder Übereinstimmung die Funktion aufgerufen und der von ihr zurückgegebene String als Ersetzungstext verwendet. Das erste Argument der Funktion ist der String, der mit dem Muster übereinstimmt. Die nächsten Argumente sind die Strings, die mit den in Klammern stehenden Teilausdrücken des Ausdrucks übereinstimmen. Von diesen Argumenten können null oder mehr vorhanden sein. Das nächste Argument ist ein Integer, der angibt, wo in _`string`_ die Übereinstimmung auftrat, und das letzte Argument der _`ersatz`_ -Funktion ist der _`string`_ selbst.

## Beispiel

Sicherstellen, dass die Groß-/Kleinschreibung im Wort »JavaScript« richtig ist:

    text.replace(/javascript/i, "JavaScript");

Einen Namen aus dem Format »Doe, John« in das Format »John Doe« konvertieren:

    name.replace(/(\w+)\s*,\s*(\w+)/, "$2 $1");

Alle doppelten Anführungszeichen durch doppelte vor- und rückwärts gerichtete einfache Anführungszeichen ersetzen:

    text.replace(/"([^"]*)"/g, "``$1''");

Den ersten Buchstaben aller Wörter eines Strings großschreiben:

    text.replace(/\b\w+\b/g, function(word) {
                  return word.substring(0,1).toUpperCase( ) +
                     word.substring(1);
                 });

## Siehe auch

`RegExp`, `RegExp.exec( )`, `RegExp.test( )`, `String.match( )`, `String.search( )`; Kapitel 11

## Name

String.search( ) — Sucht nach einem regulären AusdruckECMAScript v3

## Überblick

     _string_.search( _regexp_ )

### Argumente

 _`regexp`_

Ein RegExp-Objekt, das das Muster angibt, das in _`string`_ gesucht werden soll. Wenn dieses Argument nicht bereits ein RegExp-Objekt ist, wird es mit dem Konstruktor `RegExp( )` in ein solches konvertiert.

### Rückgabewert

Die Position, an der der erste Teilstring von _`string`_ beginnt, der mit _`regexp`_ übereinstimmt, oder −1, wenn keine Übereinstimmung gefunden wurde.

## Beschreibung

`search( )` sucht nach einem auf _`regexp`_ passenden Teilstring von _`string`_ und gibt die Position des ersten Zeichens dieses Teilstrings zurück oder −1, wenn keine Übereinstimmung gefunden wurde.

`search( )` führt keine globalen Mustererkennungen durch und ignoriert das `g`-Flag. Außerdem ignoriert die Methode auch die Eigenschaft `lastIndex` von _`regexp`_ und beginnt die Suche immer am Anfang des Strings. Das bedeutet, dass sie immer die Position der ersten Übereinstimmung in _`string`_ zurückgibt.

## Beispiel

    var s = "JavaScript macht Spaß";
    s.search(/script/i) // Gibt 4 zurück
    s.search(/a(.)a/)   // Gibt 1 zurück

## Siehe auch

`RegExp`, `RegExp.exec( )`, `RegExp.test( )`, `String.match( )`, `String.replace( )`; Kapitel 11

## Name

String.slice( ) — Extrahiert einen TeilstringECMAScript v3

## Überblick

     _string_.slice( _anfang_ , _ende_ )

### Argumente

 _`anfang`_

Der String-Index, an dem das Slice (das Stück) anfangen soll. Ist dieses Argument negativ, so gibt es eine Position ab dem Ende des Strings an. −1 meint also das letzte Zeichen, −2 das vorletzte usw.

 _`ende`_

Der String-Index unmittelbar hinter dem Ende des Stücks. Ist keiner angegeben, so enthält das Stück alle Zeichen von _`anfang`_ bis zum Ende des Strings. Ist dieses Argument negativ, so gibt es eine Position gemessen vom Ende des Strings an.

### Rückgabewert

Ein neuer String, der alle Zeichen von _`string`_ von einschließlich _`anfang`_ bis ausschließlich _`ende`_ enthält.

## Beschreibung

`slice( )` gibt einen String mit einem Stück oder einem Teilstring von _`string`_ zurück. _`string`_ wird dabei nicht modifiziert.

Die String-Methoden `slice( )`, `substring( )` und die veraltete `substr( )`-Methode geben Teile eines Strings zurück. `slice( )` ist flexibler als `substring( )`, da sie auch Argumente mit negativem Wert gestattet. `slice( )` unterscheidet sich von `substr( )` insofern, als sie einen Teilstring mit zwei Zeichenpositionen spezifiziert, wogegen `substr( )` eine Position und eine Länge angibt. Beachten Sie auch, dass `String.slice( )` analog zu `Array.slice( )` ist.

## Beispiel

    var s = "abcdefg";
    s.slice(0,4)  // Gibt "abcd" zurück
    s.slice(2,4)  // Gibt "cd" zurück
    s.slice(4)   // Gibt "efg" zurück
    s.slice(3,-1)  // Gibt "def" zurück
    s.slice(3,-2)  // Gibt "de" zurück
    s.slice(-3,-1) // Sollte "ef" zurückgeben,
            // gibt aber in IE 4 "abcdef" zurück

## Fehler

Negative Werte für _`anfang`_ funktionieren nicht im Internet Explorer 4 (aber dafür in neueren Versionen des IE). Sie geben dort nicht eine vom String-Ende gemessene Zeichenposition, sondern die Zeichenposition 0 an.

## Siehe auch

`Array.slice( )`, `String.substring( )`

## Name

String.split( ) — Zerlegt einen String in ein String-ArrayECMAScript v3

## Überblick

     _string_.split( _begrenzer_ , _grenze_ )

### Argumente

 _`begrenzer`_

Der String oder reguläre Ausdruck, bei dem _`string`_ zerlegt wird.

 _`grenze`_

Dieser optionale Integer gibt die maximale Länge des Rückgabe-Arrays an. Wenn er spezifiziert wurde, wird nur maximal diese Anzahl Teilstrings zurückgegeben. Fehlt dieser Parameter, so wird der gesamte String ohne Rücksicht auf seine Länge zerlegt.

### Rückgabewert

Ein String-Array, das entsteht, wenn _`string`_ an den durch _`begrenzer`_ spezifizierten Stellen in Teilstrings zerlegt wurde. Die Teilstrings im Rückgabe-Array enthalten die _`begrenzer`_ selbst nicht, außer in dem Fall, der in der Beschreibung geschildert wird.

## Beschreibung

Die Methode `split( )` erzeugt und liefert ein Array mit bis zu _`grenze`_ Teilstrings des angegebenen Strings. Diese Teilstrings entstehen, wenn der String von Anfang bis Ende nach einem Text durchsucht wird, der mit _`begrenzer`_ übereinstimmt, und dann vor und hinter diesem übereinstimmenden Text aufgespalten wird. Der _`begrenzer`_ -Text selbst wird, außer in der Form, die am Ende dieses Abschnitts beschrieben wird, nicht in diese Teilstrings aufgenommen. Wenn der Begrenzer mit dem Anfang des Strings übereinstimmt, ist das erste Element des Rückgabe-Arrays der leere String, also der Text, der vor dem Begrenzer steht. In gleicher Weise wird als letztes Array-Element der leere String zurückgeliefert, wenn der Begrenzer mit dem Ende des Strings übereinstimmt (und es keinen Konflikt mit _`grenze`_ gibt).

Ist kein _`begrenzer`_ angegeben, so wird der String nicht zerlegt und das Rückgabe-Array enthält nur ein einziges, ununterbrochenes String-Element. Wenn _`begrenzer`_ der leere String oder ein auf den leeren String passender regulärer Ausdruck ist, wird der String nach jedem Zeichen zerlegt, und das Rückgabe-Array hat dieselbe Länge wie der String, sofern keine kleinere _`grenze`_ angegeben wurde. (Dies ist allerdings ein Sonderfall, da der leere String vor dem ersten und hinter dem letzten Zeichen nicht von der Mustererkennung erfasst wird.)

Wie bereits erwähnt wurde, enthalten die Teilstrings in dem Rückgabe-Array dieser Methode nicht den Begrenzungstext, mit dem der String aufgespalten wird. Ist _`begrenzer`_ jedoch ein regulärer Ausdruck mit in Klammern eingefassten Teilausdrücken, so werden die Teilstrings, die mit diesen Teilausdrücken übereinstimmen, in das Rückgabe-Array aufgenommen (nicht aber der Text, der mit dem regulären Ausdruck als Ganzes übereinstimmt).

Die Methode `String.split( )` ist das Gegenstück zu `Array.join( )`.

## Beispiel

Die Methode `split( )` ist am nützlichsten, wenn Sie mit stark strukturierten Strings arbeiten. Ein Beispiel:

    "1:2:3:4:5".split(":"); // Rückgabe ["1","2","3","4","5"]
    "|a|b|c|".split("|");  // Rückgabe ["", "a", "b", "c", ""]

Außerdem wird die Methode `split( )` oft zum Parsen von Befehlen und ähnlichen Strings verwendet, die in durch Leerzeichen getrennte Wörter zerlegt werden:

    var words = sentence.split(' ');

Es ist leichter, einen String mit einem regulären Ausdruck als Begrenzer in Wörter zu zerlegen:

    var words = sentence.split(/\s+/);

Um einen String in ein Zeichen-Array zu zerlegen, verwenden Sie als Begrenzer den leeren String. Wenn Sie nur ein Präfix des Strings in ein Zeichen-Array zerlegen möchten, können Sie dazu das Argument _`grenze`_ verwenden:

    "hello".split("");   // Rückgabe ["h","e","l","l","o"]
    "hello".split("", 3); // Rückgabe ["h","e","l"]

Wenn Sie möchten, dass die Begrenzer oder ein oder mehrere Teile davon in das Rückgabe-Array aufgenommen werden, müssen Sie einen regulären Ausdruck mit geklammerten Teilausdrücken verwenden. Der folgende Code zerlegt z.B. einen String an den HTML-Tags und lässt diese Tags in das Rückgabe-Array mit einfließen:

    var text = "hello <b>world</b>";
    text.split(/(<[^>]*>)/); // Rückgabe ["hello ","<b>","world","</b>",""]

## Siehe auch

`Array.join( )`, `RegExp`; Kapitel 11

## Name

String.substr( ) — Extrahiert einen Teilstringveraltet

## Überblick

     _string_.substr( _anfang_ , _laenge_ )

### Argumente

 _`anfang`_

Der Anfang des Teilstrings. Wenn dieses Argument negativ ist, gibt es eine vom String-Ende an gemessene Position an: −1 meint das letzte Zeichen, −2 das vorletzte und so weiter.

 _`laenge`_

Die Anzahl der Zeichen im Teilstring. Wenn dieses Argument fehlt, enthält der zurückgegebene Teilstring alle Zeichen vom Anfang bis zum Ende des Strings.

### Rückgabewert

Eine Kopie des Teils von _`string`_ , der an der Position _`anfang`_ beginnt, einschließlich dieses Zeichens, und der um _`laenge`_ Zeichen oder, falls _`laenge`_ nicht angegeben ist, bis zum Ende des Strings weitergeht.

## Beschreibung

`substr( )` holt und liefert einen Teilstring von _`string`_ , wobei _`string`_ nicht modifiziert wird.

Sie sehen, dass `substr( )` den gewünschten Teilstring mit einer Zeichenposition und einer Länge spezifiziert. Dies ist eine nützliche Alternative zu den Methoden `String.substring( )` und `String.splice( )`, die einen String durch zwei Zeichenpositionen spezifizieren. Beachten Sie jedoch, dass diese Methode nicht von ECMAScript standardisiert wurde und daher veraltet ist.

## Beispiel

    var s = "abcdefg";
    s.substr(2,2);  // Gibt "cd" zurück
    s.substr(3);   // Gibt "defg" zurück
    s.substr(-3,2); // Sollte "ef" zurückgeben, gibt aber in IE 4
             // "ab" zurück

## Fehler

Negative _`anfang`_ -Werte funktionieren nicht im Internet Explorer 4 (dies ist erst in späteren IE-Versionen behoben). Sie geben keine vom String-Ende ab gemessene Zeichenposition an, sondern die Zeichenposition 0.

## Siehe auch

`String.slice( )`, `String.substring( )`

## Name

String.substring( ) — Gibt einen Teilstring eines Strings zurückECMAScript v1

## Überblick

     _string_.substring( _von_ , _bis_ )

### Argumente

 _`von`_

Ein nicht-negativer Integer, der angibt, wo in _`string`_ das erste Zeichen des gewünschten Teilstrings liegt.

 _`bis`_

Ein nicht-negativer optionaler Integer, der um eins größer als die Position des letzten Zeichens des gewünschten Teilstrings ist. Fehlt dieses Argument, so geht der zurückgegebene Teilstring bis zum String-Ende.

### Rückgabewert

Ein neuer String der Länge _`bis`_ - _`von`_ mit einem Teilstring von _`string`_. Der neue String enthält die von den Positionen _`von`_ bis _`bis`_ −1 kopierten Zeichen von _`string`_.

## Beschreibung

`String.substring( )` gibt einen aus den Zeichen zwischen den Positionen _`von`_ und _`bis`_ bestehenden Teilstring von _`string`_ zurück. Das Zeichen an der Position _`von`_ ist dabei, nicht aber das Zeichen an der Position _`bis`_.

Ist _`von`_ gleich _`bis`_ , so gibt diese Methode den leeren String (mit der Länge 0) zurück. Ist _`von`_ größer als _`bis`_ , so vertauscht diese Methode zuerst die beiden Argumente und gibt dann den dazwischenliegenden Teilstring zurück.

Bitte vergessen Sie nicht, dass das Zeichen an der Position _`von`_ in den Teilstring einfließt und das Zeichen an der Position _`bis`_ nicht. Das mag zunächst willkürlich oder verwirrend aussehen, aber es ist ein wichtiges Merkmal dieses Systems, dass die Länge des zurückgegebenen Teilstrings immer _`bis`_ — _`von`_ beträgt.

Beachten Sie, dass Sie auch mit `String.slice( )` und der nicht-standardisierten Methode `String.substr( )` Teilstrings aus einem String extrahieren können. Anders als diese Methoden akzeptiert `String.substring( )` keine negativen Argumente.

## Siehe auch

`String.charAt( )`, `String.indexOf( )`, `String.lastIndexOf( )`, `String.slice( )`, `String.substr( )`

## Name

String.toLocaleLowerCase( ) — Konvertiert einen String in KleinschreibungECMAScript v3

## Überblick

     _string_.toLocaleLowerCase( )

### Rückgabewert

Eine in locale-spezifische Kleinschreibung konvertierte Kopie von _`string`_. Nur wenige Sprachen, wie z.B. Türkisch, haben locale-spezifische Zuordnungen für die Groß- und Kleinschreibung. Daher gibt diese Methode normalerweise denselben Wert wie `toLowerCase( )` zurück.

## Siehe auch

`String.toLocaleUpperCase( )`, `String.toLowerCase( )`, `String.toUpperCase( )`

## Name

String.toLocaleUpperCase( ) — Konvertiert einen String in GroßschreibungECMAScript v3

## Überblick

     _string_.toLocaleUpperCase( )

### Rückgabewert

Eine in locale-spezifische Großschreibung konvertierte Kopie von _`string`_. Nur wenige Sprachen, wie z.B. Türkisch, haben locale-spezifische Zuordnungen für die Groß- und Kleinschreibung. Daher gibt diese Methode normalerweise denselben Wert wie `toUpperCase( )` zurück.

## Siehe auch

`String.toLocaleLowerCase( )`, `String.toLowerCase( )`, `String.toUpperCase( )`

## Name

String.toLowerCase( ) — Konvertiert einen String in KleinschreibungECMAScript v1

## Überblick

     _string_.toLowerCase( )

### Rückgabewert

Eine Kopie von _`string`_ , in der jeder Großbuchstabe in den entsprechenden Kleinbuchstaben konvertiert wurde, falls ein solcher existiert.

## Name

String.toString( ) — Gibt den String zurückECMAScript v1Überschreibt Object.toString( )

## Überblick

     _string_.toString( )

### Rückgabewert

Der elementare Stringwert von _`string`_. Diese Methode muss fast nie aufgerufen werden.

### Exceptions

`TypeError`

Tritt auf, wenn diese Methode auf einem anderen als einem String-Objekt aufgerufen wird.

## Siehe auch

`String.valueOf( )`

## Name

String.toUpperCase( ) — Konvertiert einen String in GroßbuchstabenECMAScript v1

## Überblick

     _string_.toUpperCase( )

### Rückgabewert

Eine Kopie von _`string`_ , in der jeder Kleinbuchstabe in den entsprechenden Großbuchstaben konvertiert wurde, falls ein solcher existiert.

## Name

String.valueOf( ) — Gibt den String zurückECMAScript v1Überschreibt Object.valueOf( )

## Überblick

     _string_.valueOf( )

### Rückgabewert

Der elementare Stringwert von _`string`_.

### Exceptions

`TypeError`

Tritt auf, wenn diese Methode auf einem anderen als einem String-Objekt aufgerufen wird.

## Siehe auch

`String.toString( )`

## Name

SyntaxError — Signalisiert einen SyntaxfehlerECMAScript v3Object → Error → SyntaxError

## Konstruktor

    new SyntaxError( )
    new SyntaxError( _meldung_ )

### Argumente

 _`meldung`_

Eine optionale Fehlermeldung, die Einzelheiten über die Exception verrät. Wenn dieses Argument angegeben ist, dient es als Wert der `message`-Eigenschaft des SyntaxError-Objekts.

### Rückgabewert

Ein neu erzeugtes SyntaxError-Objekt. Wenn das Argument _`meldung`_ angegeben ist, wird es vom Error-Objekt als Wert der `message`-Eigenschaft verwendet; andernfalls erhält diese Eigenschaft einen implementierungsspezifischen Standard-String als Wert. Wenn der Konstruktor `SyntaxError( )` als Funktion ohne den Operator `new` aufgerufen wird, verhält er sich genau wie bei einem Aufruf mit dem Operator `new`.

## Eigenschaften

`message`

Eine Fehlermeldung, die Einzelheiten über die Exception verrät. Diese Eigenschaft speichert den String, der an den Konstruktor übergeben wurde, oder einen implementierungsspezifischen Standard-String. Einzelheiten finden Sie unter `Error.message`.

`name`

Ein String, der den Exception-Typ angibt. Alle SyntaxError-Objekte erben für diese Eigenschaft den Wert »SyntaxError«.

## Beschreibung

Eine Instanz der Klasse SyntaxError wird ausgelöst, um einen Syntaxfehler in JavaScript-Code anzuzeigen. Die Methode `eval( )`, der Konstruktor `Function( )` und der Konstruktor `RegExp( )` können Exceptions dieses Typs auslösen. Unter `Error` wird das Auslösen und Abfangen von Exceptions genauer beschrieben.

## Siehe auch

`Error`, `Error.message`, `Error.name`

## Name

TypeError — Zeigt an, dass ein Wert den falschen Typ hatECMAScript v3Object → Error → TypeError

## Konstruktor

    new TypeError( )
    new TypeError( _meldung_ )

### Argumente

 _`meldung`_

Eine optionale Fehlermeldung, die Einzelheiten über die Exception verrät. Wenn dieses Argument angegeben ist, dient es als Wert der `message`-Eigenschaft des TypeError-Objekts.

### Rückgabewert

Ein neu erzeugtes TypeError-Objekt. Wenn das Argument _`meldung`_ angegeben ist, wird es vom Error-Objekt als Wert der `message`-Eigenschaft verwendet; andernfalls erhält diese Eigenschaft einen implementierungsspezifischen Standard-String als Wert. Wenn der Konstruktor `TypeError( )` als Funktion ohne den Operator `new` aufgerufen wird, verhält er sich genau wie bei einem Aufruf mit dem Operator `new`.

## Eigenschaften

`message`

Eine Fehlermeldung, die Einzelheiten über die Exception verrät. Diese Eigenschaft speichert den String, der an den Konstruktor übergeben wurde, oder einen implementierungsspezifischen Standard-String. Einzelheiten finden Sie unter `Error.message`.

`name`

Ein String, der den Exception-Typ angibt. Alle TypeError-Objekte erben für diese Eigenschaft den Wert »TypeError«.

## Beschreibung

Eine Instanz der Klasse TypeError wird ausgelöst, wenn ein Wert nicht den erwarteten Typ hat. Am häufigsten geschieht dies, wenn Sie versuchen, auf eine Eigenschaft mit dem Wert `null` oder auf einen nicht definierten Wert zuzugreifen. Es kann auch passieren, wenn Sie z.B. eine Methode, die durch eine bestimmte Klasse definiert wird, auf einem Objekt aufrufen, das eine Instanz einer anderen Klasse ist, oder wenn Sie den Operator `new` mit einem Wert verwenden, der keine Konstruktorfunktion ist. JavaScript-Implementierungen dürfen auch dann `TypeError`-Objekte auslösen, wenn eine eingebaute Funktion oder Methode mit mehr Argumenten als erwartet aufgerufen wird. Einzelheiten über das Auslösen und Abfangen von Exceptions finden Sie unter `Error`.

## Siehe auch

`Error`, `Error.message`, `Error.name`

## Name

undefined — Der Wert undefinedECMAScript v3

## Überblick

    undefined

## Beschreibung

`undefined` ist eine globale Eigenschaft, die den JavaScript-Wert `undefined` speichert. Derselbe Wert wird zurückgegeben, wenn Sie versuchen, den Wert einer nicht vorhandenen Objekteigenschaft zurückzugeben. Die `undefined`-Eigenschaft ist nicht mit `for/in`-Schleifen aufzählbar und lässt sich auch nicht mit dem `delete`-Operator löschen. Beachten Sie, dass die `undefined`-Eigenschaft keine Konstante ist, sondern auf einen anderen Wert gesetzt werden kann. Dies sollten Sie allerdings besser unterlassen.

Wenn Sie prüfen möchten, ob ein Wert nicht definiert ist, verwenden Sie den Operator `===`, denn der Operator `==` unterscheidet nicht zwischen dem Wert `undefined` und dem Wert `null`.

## Name

unescape( ) — Decodiert einen String mit Escape-SequenzenECMAScript v1; veraltet in ECMAScript v3

## Überblick

    unescape( _s)_

### Argumente

 _`s`_

Der zu decodierende String.

### Rückgabewert

Eine decodierte Kopie von _`s`_.

## Beschreibung

`unescape( )` ist eine globale Funktion, die einen mit `escape( )` codierten String wieder decodiert. Dies tut sie, indem sie Zeichenfolgen der Form `%` _`xx`_ bzw. `%u` _`xxxx`_ findet (wobei _`x`_ eine Hexadezimalzahl darstellt) und durch die Unicode-Zeichen `\u00` _`xx`_ und _`\`_`u` _`xxxx`_ ersetzt.

`unescape( )` wurde zwar in der ersten Version von ECMAScript standardisiert, aber seither wieder verworfen und aus dem ECMAScript v3-Standard herausgenommen. Implementierungen von ECMAScript dürften diese Funktion zwar normalerweise noch unterstützen, aber sie müssen es nicht mehr. Sie sollten `decodeURI( )` und `decodeURIComponent( )` anstelle von `unescape( )` verwenden. Einzelheiten und ein Beispiel finden Sie unter `escape( )`.

## Siehe auch

`decodeURI( )`, `decodeURIComponent( )`, `escape( )`, `String`

## Name

URIError — Wird von Methoden zum Codieren und Decodieren von URIs ausgelöstECMAScript v3Object → Error → URIError

## Konstruktor

    new URIError( )
    new URIError( _meldung_ )

### Argumente

 _`meldung`_

Eine optionale Fehlermeldung, die Einzelheiten über die Exception verrät. Wenn dieses Argument angegeben ist, dient es als Wert der `message`-Eigenschaft des URIError-Objekts.

### Rückgabewert

Ein neu erzeugtes URIError-Objekt. Wenn das Argument _`meldung`_ angegeben ist, wird es vom Error-Objekt als Wert der `message`-Eigenschaft verwendet; andernfalls erhält diese Eigenschaft einen implementierungsspezifischen Standard-String als Wert. Wenn der Konstruktor `URIError( )` als Funktion ohne den Operator `new` aufgerufen wird, verhält er sich genau wie bei einem Aufruf mit dem Operator `new`.

## Eigenschaften

`message`

Eine Fehlermeldung, die Einzelheiten über die Exception verrät. Diese Eigenschaft speichert den String, der an den Konstruktor übergeben wurde, oder einen implementierungsspezifischen Standard-String. Einzelheiten finden Sie unter `Error.message`.

`name`

Ein String, der den Exception-Typ angibt. Alle URIError-Objekte erben für diese Eigenschaft den Wert »URIError«.

## Beschreibung

Eine Instanz der Klasse URIError wird von den Methoden `decodeURI( )` und `decodeURI Component( )` ausgelöst, wenn der angegebene String unzulässige hexadezimale Escape-Symbole enthält. Sie kann auch von `encodeURI( )` und `encodeURIComponent( )` ausgelöst werden, wenn der angegebene String unzulässige Unicode-Surrogatpaare enthält. Einzelheiten über das Auslösen und Abfangen von Exceptions finden Sie unter `Error`.

## Siehe auch

`Error`, `Error.message`, `Error.name`

# Teil IV. Referenz für clientseitiges JavaScript

Dieser Teil des Buchs ist eine vollständige Referenz der Objekte, Eigenschaften, Funktionen, Methoden und Event-Handler von clientseitigem JavaScript. Hinweise für die Benutzung dieser Referenz erhalten Sie im Beispiel-Referenzeintrag am Anfang von Teil III dieses Buchs.

Die in diesem Teil dokumentierten Klassen und Objekte beinhalten:

`Anchor`
---
`Applet`
`Attr`
`Canvas`
`CanvasGradient`
`CanvasPattern`
`CanvasRenderingContext2D`
`CDATASection`
`CharacterData`
`Comment`
`CSS2Properties`
`CSSRule`
`CSSStyleSheet`
`Document`
`DocumentFragment`
`DocumentType`
`DOMException`
`DOMImplementation`
`DOMParser`
`Element`
`Event`
`ExternalInterface`
`FlashPlayer`
`Form`
`Frame`
`History`
`HTMLCollection`
`HTMLDocument`
`HTMLElement`
`IFrame`
`Image`
`Input`
`JSObject`
`KeyEvent`
`Link`
`Location`
`MimeType`
`MouseEvent`
`Navigator`
`Node`
`NodeList`
`Option`
`Plugin`
`ProcessingInstruction`
`Range`
`RangeException`
`Screen`
`Select`
`Table`
`TableCell`
`TableRow`
`TableSection`
`Text`
`Textarea`
`UIEvent`
`Window`
`XMLHttpRequest`
`XMLSerializer`
`XPathExpression`
`XPathResult`
`XSLTProcessor`

# Referenz für clientseitiges JavaScript

Dieser Teil des Buchs ist eine Referenz, die die im clientseitigen JavaScript definierten Klassen, Methoden, Eigenschaften und Event-Handler dokumentiert. Diese Einleitung und die am Anfang von Teil III stehende Beispiel-Referenzseite erklären die Benutzung dieser Referenzdokumentation und helfen Ihnen dabei, sie voll auszuschöpfen. Wenn Sie sich die Zeit nehmen, dieses Material sorgfältig zu lesen, können Sie die benötigten Informationen leichter finden und besser einsetzen.

Dieser Referenzteil ist alphabetisch aufgebaut. Die Referenzeinträge zu Klassenmethoden und -eigenschaften werden alphabetisch nach deren vollem Namen aufgeführt, der auch die Namen der Klassen einschließt, in denen sie definiert sind. Wenn Sie z.B. die `submit( )`-Methode der Klasse Form nachschlagen möchten, schauen Sie nicht unter »submit«, sondern unter »Form.submit« nach.

Die meisten JavaScript-Eigenschaften auf der Clientseite haben keine eigenen Referenzeinträge (alle Methoden und Event-Handler haben aber einen eigenen Eintrag). Einfache Eigenschaften werden stattdessen im Referenzeintrag der Klasse, die sie definiert, vollständig dokumentiert. Die `images[]`-Eigenschaft der Klasse HTMLDocument können Sie z.B. im Referenzeintrag zu `HTMLDocument` nachschlagen. Nicht-triviale Eigenschaften, die erklärungsbedürftig sind, haben allerdings eigene Referenzeinträge. Im Referenzeintrag zu der Klasse oder dem Interface, in dem solche Eigenschaften definiert sind, finden Sie dann einen entsprechenden Querverweis. Wenn Sie beispielsweise die `cookie`-Eigenschaft in der `HTMLDocument`-Referenzseite nachschlagen, finden Sie eine Kurzbeschreibung der Eigenschaft und unter `HTMLDocument.cookie` einen Verweis auf weitere Informationen.

JavaScript auf der Clientseite hat diverse globale Eigenschaften und Funktionen, wie z.B. `window`, `history` und `alert( )`. In clientseitigem JavaScript dient ein Window-Objekt als globales Objekt, und die »globalen« Eigenschaften und Funktionen von clientseitigem JavaScript sind eigentlich Eigenschaften der Klasse Window. Deshalb sind globale Eigenschaften und Funktionen im `Window`-Referenzeintrag dokumentiert oder unter Namen wie z.B. `Window.alert( )`.

Haben Sie erst den gesuchten Referenzeintrag gefunden, so dürfte das Auffinden der benötigten Informationen keine Schwierigkeiten mehr bereiten. Doch Sie können den Referenzteil noch besser ausnutzen, wenn Sie wissen, wie die Einträge geschrieben und organisiert sind. Teil III beginnt mit einem Beispiel-Referenzeintrag unter dem Titel »Beispiel-Referenzeintrag«. Dieser Eintrag erklärt die Struktur aller Referenzeinträge und zeigt, wie Sie in ihnen die gesuchten Informationen finden.

## Name

Anchor — Das Ziel eines Hypertext-LinksDOM Level 0Node → Element → HTMLElement → Anchor

## Eigenschaften

`String name`

Enthält den Namen eines Anchor-Objekts. Der Wert dieser Eigenschaft wird vom Attribut `name` des Tags `<a>` gesetzt.

## Methoden

`focus( )`

Scrollt das Dokument so, dass der Ort des Ankers sichtbar wird.

## HTML-Syntax

Ein Anchor-Objekt wird von jedem normalen HTML-Tag `<a>` erzeugt, das ein `name`-Attribut enthält:

    <a name=" _name_
    "> // Über diesen Namen können sich Links auf diesen Anker beziehen
      ...
    </a>

## Beschreibung

Ein Anker ist eine benannte Stelle in einem HTML-Dokument. Anker werden mit einem `<a>`-Tag erzeugt, für das ein `name`-Attribut angegeben ist. Das Document-Objekt hat eine Array-Eigenschaft namens `anchors[]`, die für jeden Anker im Dokument ein Anchor-Objekt enthält. Anchor-Objekte können in diesem Array über Index oder Namen referenziert werden.

Der Browser zeigt den Ort des Ankers an, wenn Sie die `hash`-Eigenschaft des `Location`-Objekts auf ein #-Zeichen setzen, dem der Name des Ankers folgt. Alternativ können Sie einfach die `focus( )`-Methode des Anchor-Objekts selbst aufrufen.

Mit demselben `<a>`-Tag, mit dem Sie Anker erzeugen, legen Sie auch Hypertext-Links an. Zwar werden im Sprachgebrauch von HTML auch Hypertext-Links oft als Anker bezeichnet, aber in JavaScript werden sie nicht durch das Anchor-, sondern durch das Link-Objekt dargestellt.

## Beispiel

    // Scrolle das Dokument so, dass der Anker namens "_bottom_" sichtbar ist
    document.anchors['_bottom_'].focus( );

## Siehe auch

`Document`, `Link`, `Location`

## Name

Anchor.focus( ) — Scrolle, um den Ort des Anchors sichtbar zu machenDOM Level 0

## Überblick

    void focus( );

## Beschreibung

Diese Methode scrollt das Dokument so, dass die Lokation des Anchor-Objekts sichtbar wird.

## Name

Applet — Ein in eine Webseite eingebettetes AppletDOM Level 0

## Überblick

     _document_.applets[ _i_ ]
    _document_. _appletName_

## Eigenschaften

Das Applet-Objekt hat Eigenschaften, die die HTML-Attribute des `<applet>`-Tags widerspiegeln (Details siehe `HTMLElement`). Es hat außerdem Eigenschaften, die den öffentlichen Feldern des von ihm repräsentierten Java-Applets entsprechen.

## Methoden

Die Methoden eines Applet-Objekts entsprechen den öffentlichen Methoden des dazugehörigen Java-Applets.

## Beschreibung

Das Applet-Objekt stellt ein Java-Applet dar, das in ein HTML-Dokument eingebettet ist. Sie können sich Applet-Objekte eines Dokuments über die `applets[]`-Collection des Document-Objekts beschaffen.

Die Eigenschaften des Applet-Objekts entsprechen den öffentlichen Feldern und seine Methoden den öffentlichen Methoden des Applets. Denken Sie daran, dass Java eine stark typisierte Sprache ist. Daher muss jedes Feld eines Applets mit einem konkreten Datentyp deklariert werden. Wenn Sie seinen Wert auf einen anderen Typ setzen, wird ein Laufzeitfehler ausgelöst. Dasselbe trifft auf Appletmethoden zu: Jedes Argument hat einen spezifischen Typ, und Argumente können, anders als in JavaScript, nicht weggelassen werden. Eine genaue Beschreibung finden Sie in Kapitel 23.

## Siehe auch

`JSObject`; `JavaObject` in Teil III; Kapitel 12, Kapitel 23

## Name

Attr — Ein Attribut eines DokumentelementsDOM Level 1 CoreNode → Attr

## Eigenschaften

`readonly String name`

Der Name des Attributs.

`readonly Element ownerElement` _[DOM Level 2]_

Das Element-Objekt, das dieses Attribut enthält, oder `null`, wenn das Attr-Objekt gegenwärtig nicht mit einem Element verbunden ist.

`readonly boolean specified`

`true`, wenn das Attribut explizit im Dokumentquelltext angegeben oder durch ein Skript eingestellt wurde. `false`, wenn das Attribut zwar nicht explizit angegeben wurde, aber in der DTD des Dokuments ein Voreinstellungswert festgelegt wurde.

`String value`

Der Attributwert. Wenn diese Eigenschaft gelesen wird, wird der Attributwert als String zurückgegeben. Wenn Sie diese Eigenschaft als String einstellen, erzeugt sie automatisch einen Text-Knoten mit demselben Textinhalt und macht ihn zum einzigen Kind des Attr-Objekts.

## Beschreibung

Ein Attr-Objekt repräsentiert ein Attribut eines Element-Knotens. Attr-Objekte sind mit Element-Knoten assoziiert, gehören aber nicht unmittelbar zum Dokumentenbaum (und haben eine `parentNode`-Eigenschaft mit dem Wert `null`). Sie können ein Attr-Objekt entweder über die `attributes`-Eigenschaft des Node-Interfaces erhalten oder, indem Sie die `getAttributeNode( )`\- oder `getAttributeNodeNS( )`-Methoden des Element-Interfaces aufrufen.

Der Wert eines Attributs wird durch die Kindknoten eines Attr-Nodes dargestellt. In HTML-Dokumenten hat ein Attr-Knoten immer nur ein Textnode-Kind, wobei die `value`-Eigenschaft eine Abkürzung zum Lesen und Schreiben des Wertes dieses Kindknotens liefert.

Die XML-Grammatik erlaubt in XML-Dokumenten Attribute, die aus Textknoten und EntityReference-Knoten bestehen. Deshalb kann ein Attributwert durch einen String nicht vollständig dargestellt werden. In der Praxis erweitern Webbrowser allerdings alle beliebigen Entity-Referenzen zu XML-Attributwerten, ohne dabei das EntityReference-Interface zu implementieren (das in diesem Buch nicht dokumentiert wird). Folglich wird in clientseitigem JavaScript zum Lesen und Schreiben von Attributwerten lediglich die `value`-Eigenschaft benötigt.

Da Attributwerte vollständig durch Strings dargestellt werden können, ist es normalerweise nicht notwendig, die Attr-Schnittstelle zu verwenden. In den meisten Fällen ist die Arbeit mit Attributen am einfachsten, wenn Sie die Methoden `Element.getAttribute( )` und `Element. setAttribute( )` nutzen. Diese Methoden verwenden für die Attributwerte Strings und vermeiden den Einsatz von Attr-Knoten.

## Siehe auch

`Element`

## Name

Button — Siehe Input

##

## Name

Canvas — Ein HTML-Element zum skriptgesteuerten ZeichnenFirefox 1.5, Safari 1.3, Opera 9Node → Element → HTMLElement → Canvas

## Eigenschaften

`String hoehe`

Die Höhe des Canvas. Wie bei einem Bild kann die Höhe als ein ganzzahliger Pixelwert oder in Prozent der Fensterhöhe angegeben werden. Bei der Änderung dieses Werts wird eine ggf. bereits auf dem Canvas befindliche Zeichnung gelöscht. Der Standardwert ist 300.

`String breite`

Die Canvasbreite. Wie bei einem Bild kann die Höhe als ein ganzzahliger Pixelwert oder in Prozent der Fensterbreite angegeben werden. Bei der Änderung dieses Werts wird eine ggf. bereits auf dem Canvas befindliche Zeichnung gelöscht. Der Standardwert ist 300.

## Methoden

`getContext( )`

Gibt ein CanvasRenderingContext2D-Objekt zurück, mit den auf den Canvas gezeichnet werden kann. Sie müssen den String »2d« an diese Methode übergeben, um zweidimensional zu zeichnen.

## Beschreibung

Das Canvas-Objekt stellt ein HTML-Canvas-Element dar. Es hat kein eigenes Verhalten, sondern definiert eine API, die geskriptete Zeichenvorgänge auf der Clientseite unterstützt. Sie können die `breite` und `hoehe` direkt am Objekt eingeben, aber die meisten anderen Funktionalitäten werden über das CanvasRenderingContext2D-Objekt festgelegt. Das Objekt erhalten Sie, indem Sie die `getContext( )`-Methode des Canvas-Objekts aufrufen und den Textstring »2d« als einziges Argument übergeben.

Das `<canvas>`-Tag wurde mit Safari 1.3 eingeführt und wurde, als dieses Buch geschrieben wurde, auch von Firefox 1.5 und Opera 9 unterstützt. Das `<canvas>`-Tag und seine API lassen sich in IE mit dem ExplorerCanvas Open-Source-Projekt unter <http://excanvas.sourceforge.net/> simulieren.

## Siehe auch

`CanvasRenderingContext2D`; Kapitel 22

## Name

Canvas.getContext( ) — Liefert einen Context zum Schreiben auf dem Canvas zurück

## Überblick

    CanvasRenderingContext2D getContext(String _contextID_
    )

### Argumente

 _`contextID`_

Dieses Argument gibt die Art der Zeichnung an, die Sie auf dem Canvas durchführen wollen. Zurzeit ist »2d« der einzig gültige Wert. Er legt zweidimensionales Zeichnen fest und veranlasst diese Methode, ein Context-Objekt zurückzugeben, das eine 2-D-Zeichnungs-API exportiert.

### Rückgabewert

Ein CanvasRenderingContext2D-Objekt, mit dem Sie im Canvas-Element zeichnen können.

## Beschreibung

Gibt einen Kontext aus, der den zum Zeichnen zu benutzenden Kontexttyp darstellt. Die Absicht ist, für verschiedene Zeichnungstypen (2-D, 3-D) unterschiedliche Kontexte verfügbar zu machen. Zurzeit wird lediglich der »2d«-Typ unterstützt. Er liefert ein CanvasRenderingContext2D-Objekt aus, das die meisten von einem Canvas verwendeten Methoden implementiert.

## Siehe auch

`CanvasRenderingContext2D`

## Name

CanvasGradient — Ein Farbgradient, der auf einem Canvas verwendet wirdFirefox 1.5, Safari 1.3, Opera 9Object → CanvasGradient

## Methoden

`addColorStop( )`

Gibt eine Farbe und Position für den Gradienten an.

## Beschreibung

Ein CanvasGradient-Objekt stellt einen Farbgradienten dar, der sowohl den `strokeStyle`\- als auch den `fillStyle`-Eigenschaften eines CanvasRenderingContext2D-Objektes zugewiesen werden kann. Sowohl die `createLinearGradient( )`\- als auch die `createRadialGradient( )`-Methoden von CanvasRenderingContext2D geben CanvasGradient-Objekte zurück.

Nach der Erstellung eines CanvasGradient-Objektes verwenden Sie `addColorStop( )`, um festzulegen, welche Farben an welcher Position im Gradienten erscheinen sollen. Zwischen den von Ihnen festgelegten Positionen werden die Farben interpoliert, um einen fließenden Gradienten oder Farbübergang zu erreichen. Am Start- und Endpunkt des Gradienten werden automatisch transparente schwarze Stopps erstellt.

## Siehe auch

    CanvasRenderingContext2D.createLinearGradient( )
    CanvasRenderingContext2D.createRadialGradient( )

## Name

CanvasGradient.addColorStop( ) — Hinzufügen einer Farbänderung zum Gradienten

## Überblick

    void addColorStop(float _offset_ , String _color_ )

### Argumente

 _`offset`_

Ein Gleitkommazahlwert im Bereich zwischen 0,0 und 1,0, der einen Bruchteil zwischen den Start- und Endpunkten des Gradienten darstellt. Ein Offset von 0 entspricht dem Startpunkt und ein Offset von 1 dem Endpunkt.

 _`color`_

Gibt die Farbe, die am festgelegten Offset angezeigt werden soll, als CSS-Farbstring an. Farben an anderen Punkten entlang des Gradienten werden auf der Basis dieses und weiterer Farbstützpunkte (Stops) interpoliert.

## Beschreibung

`addColorStop( )` stellt den Mechanismus zur Beschreibung von Farbänderungen in einem Gradienten zur Verfügung. Diese Methode kann ein- oder mehrmals aufgerufen werden, um die Farbe an bestimmten Prozentualstellen zwischen dem Start- und Endpunkt des Gradienten zu ändern.

Wenn diese Methode an einem Gradienten nie aufgerufen wird, ist der Gradient transparent. Um einen sichtbaren Farbverlauf zu erhalten, muss mindestens ein Farbstützpunkt (Stop) festgelegt werden.

## Name

CanvasPattern — Ein auf einem Bild basierendes Muster zur Verwendung auf einem CanvasFirefox 1.5, Safari 1.3, Opera 9Object → CanvasPattern

## Beschreibung

Ein CanvasPattern-Objekt wird von der `createPattern( )`-Methode eines CanvasRenderingContext2D-Objekts ausgegeben. Ein CanvasPattern-Objekt kann als Wert der `strokeStyle`\- und `fillStyle`-Eigenschaften eines CanvasRenderingContext2D-Objekts ver-wendet werden.

Ein CanvasPattern-Objekt hat keine eigenen Eigenschaften oder Methoden. Einzelheiten zum Erstellen dieses Objekts finden Sie unter `CanvasRenderingContext2D.createPattern( )`.

## Siehe auch

`CanvasRenderingContext2D.createPattern( )`

## Name

CanvasRenderingContext2D — Das Objekt wird zum Zeichnen auf einem Canvas verwendetFirefox 1.5, Safari 1.3, Opera 9Object → CanvasRenderingContext2D

## Eigenschaften

`readonly Canvas canvas`

Das Canvas-Element, auf das dieser Kontext zeichnen wird.

`Object fillStyle`

Die Farbe, das Muster oder der Gradient, die/das/der derzeit zum Ausfüllen von Pfaden verwendet wird. Diese Eigenschaft kann auf einen String, ein CanvasGradient- oder ein CanvasPattern-Objekt gesetzt werden. Als String wird sie als CSS-Farbwert geparst und für massive Farbfüllungen verwendet. Als CanvasGradient- oder CanvasPattern-Objekt werden Farbfüllungen mit Hilfe des angegebenen Gradienten oder Musters ausgefüllt. Siehe `CanvasRenderingContext2D.createLinearGradient( )`, `CanvasRenderingContext2D.create- RadialGradient( )` und `CanvasRenderingContext2D.createPattern( )`.

`float globalAlpha`

Gibt die Deckkraft des auf den Canvas gezeichneten Inhalts an. Der Wertebereich liegt zwischen 0,0 (vollständig transparent) und 1,0 (keine zusätzliche Transparenz). Der Standardwert dieser Eigenschaft ist 1,0.

`String globalCompositeOperation`

Gibt an, wie gerade gezeichnete Farben mit bereits auf dem Canvas vorhandenen Farben kombiniert werden. Mögliche Werte finden Sie unter dem individuellen Eintrag für diese Eigenschaft.

`String lineCap`

Gibt an, wie die Linienenden wiedergegeben werden. Gültige Werte sind »butt« (stumpf), »round« (rund) und »square« (eckig). Der Defaultwert ist »butt«. Weitere Einzelheiten zu dieser Eigenschaft finden Sie in ihrem individuellen Referenzeintrag.

`String lineJoin`

Gibt an, wie zwei Linien zusammengefügt werden. Gültige Werte sind »round« (rund), »bevel« (schräg) und »miter« (auf Gehrung). Der Defaultwert ist »miter«. Weitere Einzelheiten zu dieser Eigenschaft finden Sie in ihrem individuellen Referenzeintrag.

`float lineWidth`

Gibt die Linienbreite für Strichfunktionen (Linienzeichnungen) an. Die Standardeinstellung ist 1,0, und die Eigenschaft muss größer als 0,0 sein. Breite Linien werden über dem Pfad zentriert, sodass jeweils die halbe Linienbreite links und rechts der Mitte liegt.

`float miterLimit`

Ist die `lineJoin`-Eigenschaft »miter«, dann gibt diese Eigenschaft das maximale Verhältnis zwischen Gehrungslänge zu Linienbreite an. Weitere Einzelheiten zu dieser Eigenschaft finden Sie in ihrem individuellen Referenzeintrag.

`float shadowBlur`

Gibt an, wie weit Schatten auslaufen sollen. Die Standardeinstellung ist 0. Safari unterstützt Schatten, Firefox 1.5 und Opera 9 tun das jedoch nicht.

`String shadowColor`

Gibt die Schattenfarbe als CSS- oder Web-Style-String an und kann eine Alpha-Komponente für die Transparenz enthalten. Die Standardeinstellung ist »black«. Safari unterstützt Schatten, Firefox 1.5 und Opera 9 tun das jedoch nicht.

`float shadowOffsetX, shadowOffsetY`

Gibt den horizontalen und vertikalen Offset der Schatten an. Größere Werte lassen das schattierte Objekt vor dem Hintergrund höher erscheinen. Die Standardeinstellung ist 0. Safari unterstützt Schatten, Firefox 1.5 und Opera 9 tun das jedoch nicht.

`Object strokeStyle`

Gibt die Farbe, das Muster oder den Gradienten an, die/das/der für die Striche (das Zeichnen) von Pfaden verwendet wird. Diese Eigenschaft kann ein String, ein CanvasGradient- oder ein CanvasPattern-Objekt sein. Ist sie ein String, wird sie als CSS-Farbwert interpretiert und die Linienzeichnung wird mit der resultierenden deckenden Farbe durchgeführt. Ist der Wert dieser Eigenschaft ein CanvasGradient- oder CanvasPattern-Objekt, wird die Linienzeichnung über den Gradienten oder das Muster durchgeführt. Siehe `CanvasRenderingContext2D.createLinearGradient( )`, `CanvasRenderingContext2D.createRadial- Gradient( )` und `CanvasRenderingContext2D.createPattern( )`.

## Methoden

`arc( )`

Ein Kreisbogen wird mit Hilfe eines Mittelpunktes und eines Radius zum bestehenden Subpfad eines Canvas hinzugefügt.

`arcTo( )`

Ein Kreisbogen wird mit Hilfe eines Tangentenpunktes und eines Radius zum bestehenden Subpfad hinzugefügt.

`beginPath( )`

Beginnt einen neuen Pfad (oder mehrere Subpfade) in einem Canvas.

`bezierCurveTo( )`

Fügt dem derzeitigen Subpfad eine dreidimensionale Bézierkurve hinzu.

`clearRect( )`

Löscht die Pixel in einem rechteckigen Bereich auf einem Canvas.

`clip( )`

Verwendet den derzeitigen Pfad als Clip-Region für nachfolgende Zeichnungsfunktionen.

`closePath( )`

Schließt den derzeitigen Subpfad, sofern er geöffnet ist.

`createLinearGradient( )`

Gibt ein CanvasGradient-Objekt aus, das einen linearen Farbgradienten darstellt.

`createPattern( )`

Gibt ein CanvasPattern-Objekt aus, das ein gekacheltes Bild darstellt.

`createRadialGradient( )`

Gibt ein CanvasGradient-Objekt aus, das einen radialen Farbgradienten widerspiegelt.

`drawImage( )`

Zeichnet ein Bild.

`fill( )`

Färbt oder füllt den Innenraum des derzeitigen Pfads mit der von der `fillStyle`-Eigenschaft vorgegebenen Farbe, dem Gradienten oder dem Muster.

`fillRect( )`

Färbt oder füllt ein Rechteckt.

`lineTo( )`

Fügt dem derzeitigen Subpfad ein gerades Liniensegment hinzu.

`moveTo( )`

Legt die derzeitige Position fest und beginnt mit einem neuen Subpfad.

`quadraticCurveTo( )`

Fügt dem derzeitigen Subpfad eine zweidimensionale Bézier-Kurve hinzu.

`rect( )`

Fügt dem derzeitigen Pfad einen rechteckigen Subpfad hinzu.

`restore( )`

Setzt den Canvas auf den zuletzt gespeicherten Grafikzustand zurück.

`rotate( )`

Dreht den Canvas.

`save( )`

Speichert die Eigenschaften, Clip-Region und Transformationsmatrix des CanvasRenderingContext2D-Objekts.

`scale( )`

Skaliert das Benutzerkoordinatensystem des Canvas.

`stroke( )`

Zeichnet oder strichelt eine Linie entlang des momentanen Pfads. Die Linie wird nach Vorgabe der Eigenschaften `lineWidth`, `lineJoin`, `lineCap` und `strokeStyle` sowie weiteren Eigenschaften gezeichnet.

`strokeRect( )`

Zeichnet ein Rechteck (füllt es aber nicht).

`translate( )`

Verschiebt das Benutzerkoordinatensystem des Canvas.

## Beschreibung

Das CanvasRenderingContext2D-Objekt stellt einen Satz von Grafikfunktionen zum Zeichnen auf einem Canvas zur Verfügung. Obwohl leider kein Text unterstützt wird, sind die vorhandenen Funktionen recht umfangreich. Sie können in mehrere Kategorien unterteilt werden.

### Rechtecke zeichnen

Mit `strokeRect( )` und `fillRect( )` lassen sich Rechtecke zeichnen und füllen. Außerdem können Sie die von einem Rechteck definierte Fläche mit `clearRect( )` löschen.

### Bilder zeichnen

In der Canvas-API werden Bilder mit Hilfe von Image-Objekten definiert, die für HTML-`<img>`-Elemente oder Offscreen-Bilder stehen, die mit dem `Image( )`-Konstruktor erzeugt wurden. (Einzelheiten entnehmen Sie bitte dem Referenzeintrag zu `Image`.) Ein Canvas-Objekt kann auch als Bildquelle verwendet werden.

Mit der `drawImage( )`-Methode lässt sich ein Bild auf den Canvas zeichnen. In ihrer allgemeinsten Form ermöglicht sie das Skalieren jedes beliebigen, rechteckigen Bereichs des Orginalbildes und seine Wiedergabe auf dem Canvas.

### Pfade erstellen und wiedergeben

Ein leistungsstarkes Feature des Canvas ist seine Fähigkeit, aus einfachen Zeichenfunktionen Formen aufzubauen und dann entweder ihre Umrisse zu zeichnen (mit `stroke`) oder ihre Fläche auszufüllen (mit `fill`). Die Gesamtheit der angefallenen Operationen wird als _momentaner Pfad_ bezeichnet. Ein Canvas folgt nur einem einzigen momentanen Pfad.

Um mehrere Segmente zu einer Form zusammenzufügen, brauchen wir einen Verbindungspunkt zwischen den Zeichenoperationen. Für diesen Zweck verwaltet der Canvas eine _momentane Position_. Die Canvas-Zeichenoperationen verwenden diese implizit als ihren Startpunkt und ändern sie typischerweise auf die Position, die ihren jeweiligen Endpunkten entsprechen. Sie können sich das wie das Zeichnen mit einem Stift auf Papier vorstellen: Nach dem Zeichnen einer bestimmten Linie oder Kurve ist die momentane Position dort, wo der Stift nach der Operation zum Stehen kommt.

Sie können mehrere unzusammenhängende Formen im momentanen Pfad erstellen, die dann mit den gleichen Zeichnungsparametern zusammen dargestellt werden. Um Formen voneinander zu trennen, verwenden Sie die `moveTo( )`-Methode; dadurch wird die derzeitige Position auf eine neue Stelle verschoben, ohne dass eine Verbindungslinie hinzugefügt wird. Durch diesen Vorgang erstellen Sie einen neuen _Subpfad_ , die Canvas-Bezeichnung für eine Gruppe von Operationen, die miteinander verbunden sind.

Wenn der Pfad Ihren Bedürfnissen entspricht, können Sie seine Umrisse mit `stroke( )` zeichnen und/oder seine Fläche über `fill( )` mit Farbe füllen.

Die zur Verfügung stehenden Operationen sind `lineTo( )` zum Zeichnen von geraden Linien, `rect( )` zum Zeichnen von Rechtecken, `arc( )` oder `arcTo( )` zum Zeichnen von Kreissegmenten und `bezierCurveTo( )` oder `quadraticCurveTo( )` zum Zeichnen von Kurven.

Zusätzlich zur Linienzeichnung und Farbfüllung ist der Pfad bei der Festlegung der für die Wiedergabe des Canvas verwendeten _Clip-Region_ von Nutzen. Pixel innerhalb der Region werden angezeigt; außerhalb liegende Pixel nicht. Die Clip-Region ist kumulativ; beim Aufruf von `clip( )` wird die Schnittmenge zwischen momentanem Pfad und momentaner Clip-Region gebildet, wodurch eine neue Region entsteht. Leider kann die Clip-Region nicht direkt auf den gesamten Canvas zurückgesetzt werden; dazu müssen Sie den gesamten Grafikstatus des Canvas speichern und auf den Anfangswert zurücksetzen (das wird weiter unten in diesem Eintrag beschrieben).

Wenn die Segmente in einem Subpfad keine geschlossene Form bilden, fügen die `fill( )`\- und `clip( )`-Operationen implizit ein virtuelles Liniensegment zwischen dem Anfangs- und dem Endpunkt des Subpfads ein und schließen damit die Form. Sie können dieses Liniensegment auch explizit mit einem Aufruf von `closePath( )` hinzufügen.

### Farben, Gradienten und Muster

Beim Ausfüllen oder Zeichnen von Pfaden können Sie mit den Eigenschaften `fillStyle` und `strokeStyle` festlegen, wie die Linien oder gefüllten Flächen wiedergegeben werden. Beide Eigenschaften akzeptieren sowohl Farbstrings im CSS-Stil als auch CanvasGradient- und CanvasPattern-Objekte, die Gradienten und Muster beschreiben. Um einen Gradienten zu erzeugen, verwenden Sie die Methoden `createLinearGradient( )` oder `createRadialGradient( )`. Um ein Muster zu erzeugen, verwenden Sie `createPattern( )`.

Um über die CSS-Notation eine Deckfarbe festzulegen, verwenden Sie einen String im Format »#RRGGBB«, wobei RR, GG und BB Hexadezimalzahlen sind, die die Rot-, Blau- und Grünkomponenten der Farbe als Hexadezimalwerte zwischen 00 und FF angeben. Beispielsweise ist »#FF0000« ein leuchtendes Rot. Um eine teilweise transparente Farbe zu erzeugen, verwenden Sie einen String im Format »rgba(R,G,B,A)«. Bei diesem Format geben R, G und B die Rot-, Grün- und Blaukomponenten der Farbe als dezimale Ganzzahlen zwischen 0 und 255 an, und A legt die Alphakomponente (Deckkraft) als Gleitkommazahl zwischen 0.0 (vollständig transparent) und 1.0 (vollständig deckend) fest. Beispielsweise ist »rgba(255,0,0,0.5)« ein halbtransparentes, leuchtendes Rot.

### Linienbreite, Linienabschlüsse und Linienverbindungen

Canvas bietet mehrere Optionen für das Erscheinungsbild von Linien. Mit der Eigenschaft `lineWidth` legen Sie die Linienbreite fest, mit der Eigenschaft `lineCap` definieren Sie, wie die Endpunkte der Linien gezeichnet werden, und mit der Eigenschaft `lineJoin` regeln Sie das Zusammenfügen von Linien.

### Koordinatensystem und -transformationen

Standardmäßig hat das Koordinatensystem eines Canvas seinen Ursprung bei `(0,0)` in der linken, oberen Ecke des Canvas. Die _`x`_ -Werte erhöhen sich nach rechts, und die _`y`_ -Werte erhöhen sich nach unten. Eine einzelne Einheit in diesem Koordinatenraum stellt normalerweise ein einzelnes Pixel dar.

Sie können jedoch das Koordinatensystem mit _transform_ so transformieren, dass beliebige von Ihnen festgelegte Koordinaten oder Längen in Zeichenoperationen verschoben, skaliert oder gedreht werden. Das geht mit den Methoden `translate( )`, `scale( )` und `rotate( )`, die die _Transformationsmatrix_ des Canvas beeinflussen. Da das Koordinatensystem also transformiert werden kann, werden Koordinaten, die von Ihnen an Methoden wie z.B. `lineTo( )` übergeben werden, nicht immer in Pixeln gemessen. Aus diesem Grund verwendet die Canvas-API Gleitkommazahlen statt ganzer Zahlen.

Transformationen werden in umgekehrter Reihenfolge zu ihrer Festlegung abgearbeitet. Beispielsweise wird bei einem Aufruf von `scale( )`, dem ein Aufruf von `translate( )` folgt, das Koordinatensystem zuerst verschoben und dann skaliert.

### Compositing

Meistens werden Formen übereinander gezeichnet, wobei die neue Form die vorhergehende, darunter gezeichnete Form überdeckt. Das ist das Defaultverhalten eines Canvas. Sie können jedoch viele interessante Operationen durchführen, indem Sie für die `globalCompositeOperation`-Eigenschaft unterschiedliche Werte festlegen. Diese reichen von Exklusiv-ODER-Operationen bis zur Aufhellung oder Verdunkelung von geformten Regionen; eine Liste aller Optionen finden Sie unter `CanvasRenderingContext2D.globalCompositeOperation`.

### Schatten

Die Canvas-API enthält Eigenschaften, die jeder von Ihnen gezeichneten Form automatisch einen geworfenen Schatten hinzufügen. Als dieses Buch geschrieben wurde, war Safari allerdings der einzige Browser, der diese API implementiert hatte. Die Farbe des Schattens kann mit `shadowColor` festgelegt und seine Verschiebung mit `shadowOffsetX` und `shadowOffsetY` verändert werden. Wie weit der Schatten am Schattenrand ausfransen soll, kann mit `shadowBlur` festgelegt werden.

### Den Grafikstatus speichern

Mit den Methoden `save( )` und `restore( )` lässt sich der Status eines CanvasRendering-Context2D-Objekts speichern und zurücksetzen. `save( )` schiebt den derzeitigen Status auf einen Stapelspeicher. `restore( )` nimmt den zuletzt gespeicherten Status aus dem Stapelspeicher und setzt den momentanen Zeichnungsstatus auf Basis dieser gespeicherten Werte fest.

Alle Eigenschaften des CanvasRenderingContext2D-Objekts sind Bestandteil des gespeicherten Status (mit Ausnahme der `canvas`-Eigenschaft, die eine Konstate ist). Die Transformationsmatrix und die Clip-Region sind ebenfalls Teil des Status, der momentane Pfad und die momentane Position aber nicht.

## Siehe auch

`Canvas`

## Name

CanvasRenderingContext2D.arc( ) — Ein Kreisbogen wird mit Hilfe eines Mittelpunktes und eines Radius zum bestehenden Subpfad eines Canvas hinzugefügt.

## Überblick

    void arc(float _x_ , float _y_ , float _radius_ ,
         float _startWinkel_ , _endWinkel_ ,
         boolean _gegenUhrzeigersinn_ )

### Argumente

 _`x, y`_

Die Koordinaten des Kreismittelpunktes, der den Kreisbogen beschreibt.

 _`radius`_

Der Radius des Kreises, der den Kreisbogen beschreibt.

 _`startWinkel, endWinkel`_

Die Winkel, die die Start- und Endpunkte des Kreisbogens entlang des Kreises festlegen. Diese Winkel werden im Bogenmaß angegeben. Die 3-Uhr-Position entlang der positiven X-Achse entspricht einem Winkel von 0, und die Winkel vergrößern sich im Uhrzeigersinn.

 _`gegenUhrzeigersinn`_

Gibt an, ob der Kreisbogen gegen (`true`) oder im (`false`) Uhrzeigersinn auf dem Kreis durchlaufen wird.

## Beschreibung

Die ersten fünf Argumente dieser Methode geben einen Start- und einen Endpunkt auf einem Kreis an. Beim Aufruf dieser Methode wird eine gerade Linie zwischen dem momentanen Punkt und dem Startpunkt zum momentanen Subpfad hinzugefügt. Dann wird der Kreisbogen entlang des Kreises zwischen Start- und Endpunkt zum Subpfad hinzugefügt. Das letzte Argument gibt die Richtung an, in der der Kreis beim Verbinden der Start- und Endpunkte durchlaufen werden soll. Diese Methode belässt den momentanen Punkt auf dem Endpunkt des Kreisbogens.

## Siehe auch

`CanvasRenderingContext2D.arcTo( )`
---
`CanvasRenderingContext2D.beginPath( )`
`CanvasRenderingContext2D.closePath( )`

## Name

CanvasRenderingContext2D.arcTo( ) — Fügt dem momentanen Subpfad einen Kreisbogen unter Verwendung von Tangentenpunkten und eines Radius hinzu

## Überblick

    void arcTo(float _x1_ , float _y1_ ,
          float _x2_ , float _y2_ ,
          float _radius_
    )

### Argumente

 _`x1, y1`_

Die Koordinaten des Punktes P1.

 _`x2, y2`_

Die Koordinaten des Punktes P2.

 _`radius`_

Der Radius des Kreises, der den Kreisbogen definiert.

## Beschreibung

Diese Methode fügt dem momentanen Subpfad einen Kreisbogen hinzu, beschreibt diesen Kreisbogen aber ganz anders als die `arc( )`-Methode. Der Kreisbogen, der zum Pfad hinzugefügt wird, ist ein Kreissegment mit dem angegebenen _`radius`_. Der Kreisbogen hat einen Punkt, der die Linie zwischen der momentanen Position und P1 tangiert, und einen Punkt, der die Linie von P1 nach P2 tangiert. Der Kreisbogen beginnt und endet an diesen zwei Tangentenpunkten und wird in der Richtung gezeichnet, die diese zwei Punkte mit dem kürzesten Kreisbogen verbindet.

Bei vielen Anwendungen beginnt der Kreisbogen an der momentanen Position und endet bei P2, was aber nicht immer der Fall ist. Wenn die derzeitige Position nicht mit dem Startpunkt des Kreisbogens übereinstimmt, fügt diese Methode eine gerade Linie zwischen der momentanen Position und der Startposition des Kreisbogens ein. Diese Methode belässt die momentane Position auf dem Endpunkt des Kreisbogens.

## Beispiel

Mit dem folgenden Code können Sie die rechte, obere Ecke eines Rechtecks zeichnen und ihr eine abgerundete Ecke geben:

    c.moveTo(10, 10);              // oben links beginnen
    c.lineTo(90, 10)               // horizontale Linie bis zum Beginn der abgerundeten Ecke
    c.arcTo(100, 10, 100, 20, 10); // abgerundete Ecke
    c.lineTo(100, 100);            // senkrechte Linie nach rechts unten

## Fehler

Firefox 1.5 unterstützt diese Methode nicht.

## Siehe auch

`CanvasRenderingContext2D.arc( )`

## Name

CanvasRenderingContext2D.beginPath( ) — Beginnt eine neue Gruppe von Subpfaden auf einem Canvas

## Überblick

    void beginPath( )

## Beschreibung

`beginPath( )` löscht alle bisher definierten Pfade und beginnt einen neuen Pfad. Der momentane Punkt wird auf `(0,0)` gesetzt.

Beim ersten Erstellen des Kontextes für einen Canvas wird automatisch `beginPath( )` aufgerufen.

## Siehe auch

`CanvasRenderingContext2D.closePath( )`
---
`CanvasRenderingContext2D.fill( )`
`CanvasRenderingContext2D.stroke( )`
Kapitel 22

## Name

CanvasRenderingContext2D.bezierCurveTo( ) — Fügt dem momentanen Subpfad eine dreidimensionale Bézierkurve hinzu

## Überblick

    void bezierCurveTo(float _cpX1_ , float _cpY1_ ,
              float _cpX2_ , float _cpY2_ ,
              float _x_ , float _y_ )

### Argumente

 _`cpX1, cpX2`_

Die Koordinaten des Kontrollpunkts, der mit dem Startpunkt der Kurve assoziiert wird (die momentane Position).

 _`cpX2, cpY2`_

Die Koordinaten des Kontrollpunkts, der mit dem Endpunkt der Kurve assoziiert wird.

 _`x, y`_

Die Koordinaten des Kurvenendpunktes.

## Beschreibung

`bezierCurveTo( )` fügt dem momentanen Subpfad eines Canvas eine kubische Bézierkurve hinzu. Der Startpunkt der Kurve ist der momentane Punkt auf dem Canvas, und der Endpunkt ist `(x,y)`. Die zwei Bézier-Kontrollpunkte `(cpX1, cpY1)` und `(cpX2, cpY2)` bestimmen die Kurvenform. Nach dem Ausführen der Methode liegt die momentane Position auf `(x,y)`.

## Siehe auch

`CanvasRenderingContext2D.quadraticCurveTo( )`

## Name

CanvasRenderingContext2D.clearRect( ) — Löschen eines rechteckigen Bereichs auf dem Canvas

## Überblick

    void clearRect(float _x_ , float _y_ ,
            float _breite_ , float _hoehe_ )

### Argumente

 _`x, y`_

Die Koordinaten der linken oberen Ecke des Rechtecks.

 _`breite, hoehe`_

Die Abmessungen des Rechtecks.

## Beschreibung

`clearRect( )` löscht das angegebene Rechteck durch Füllen mit einer transparenten Farbe.

## Name

CanvasRenderingContext2D.clip( ) — Legt den Clippingpfad des Canvas fest

## Überblick

    void clip( )

## Beschreibung

Diese Methode begrenzt den momentanen Pfad mit Hilfe des momentanen Clippingpfades und verwendet dann den begrenzten Pfad als den neuen Clippingpfad. Beachten Sie, dass es keine Möglichkeit gibt, den Clippingpfad zu vergrößern. Falls Sie einen temporären Clippingpfad brauchen, sollten Sie zuerst `save( )` aufrufen, um dann mit `restore( )` den ursprünglichen Clippingpfad wiederherstellen zu können. Der Default-Clippingpfad für einen Canvas ist das Canvas-Rechteck selbst.

Diese Methode setzt den momentanen Pfad auf einen leeren Pfad zurück.

## Name

CanvasRenderingContext2D.closePath( ) — Schließt einen offenen Subpfad

## Überblick

    void closePath( )

## Beschreibung

Wenn der momentane Subpfad des Canvas offen ist, so schließt ihn die Methode `closePath( )`, indem sie eine Linie einfügt, die den momentanen Punkt mit dem Startpunkt des Subpfades verbindet. Wenn der Subpfad bereits geschlossen ist, hat diese Methode keine Auswirkung. Nach dem Schließen eines Subpfades können ihm keine weiteren Linien oder Kurven hinzugefügt werden. Um den Pfad weiter zu vergrößern, müssen Sie durch Aufruf von `moveTo( )` einen neuen Subpfad beginnen.

Sie müssen `closePath( )` nicht aufrufen, bevor Sie einen Pfad zeichnen oder füllen. Beim Füllen eines Pfads wird er implizit geschlossen (wie auch beim Aufruf von `clip( )`).

## Siehe auch

`CanvasRenderingContext2D.beginPath( )`
---
`CanvasRenderingContext2D.moveTo( )`
`CanvasRenderingContext2D.stroke( )`
`CanvasRenderingContext2D.fill( )`

## Name

CanvasRenderingContext2D.createLinearGradient( ) — Einen linearen Farbgradienten erzeugen

## Überblick

    CanvasGradient createLinearGradient(float _xStart_ , float _yStart_ ,
                                        float _xEnde_ , float _yEnde_ )

### Argumente

 _`xStart, yStart`_

Die Koordinaten des Gradientenanfangspunkts.

 _`xEnde, yEnde`_

Die Koordinaten des Gradientenendpunkts.

### Rückgabewert

Ein CanvasGradient-Objekt, das einen linearen Farbgradienten darstellt.

## Beschreibung

Diese Methode erzeugt und gibt ein neues CanvasGradient-Objekt zurück, das die Farbe linear zwischen dem angegebenen Start- und Endpunkt interpoliert. Beachten Sie, dass diese Methode keine Farben für den Gradienten festlegt. Dazu verwenden Sie die `addColorStop( )`-Methode des zurückgegebenen Objekts. Um mit Hilfe eines Gradienten Linien zu zeichnen oder Flächen auszufüllen, weisen Sie der `strokeStyle` oder `fillStyle`-Eigenschaft ein CanvasGradient-Objekt zu.

## Siehe auch

`CanvasGradient.addColorStop( )`, `CanvasRenderingContext2D.createRadialGradient( )`

## Name

CanvasRenderingContext2D.createPattern( ) — Erzeugt ein Muster aus gekachelten Bildern

## Überblick

    CanvasPattern createPattern(Image _bild_ ,
                                String _wiederholStil_ )

### Argumente

 _`bild`_

Das zu kachelnde Bild. Dieses Argument ist in der Regel ein Image-Objekt. Sie können aber auch ein Canvas-Element verwenden.

 _`wiederholStil`_

Gibt an, wie das Bild gekachelt wird. Mögliche Werte sind folgende:

Wert | Bedeutung

---|---

`repeat` | Kachele das Bild in beide Richtungen. Das ist der Defaultwert.

`repeat-x` | Kachele das Bild nur in X-Richtung.

`repeat-y` | Kachele das Bild nur in Y-Richtung.

`no-repeat` | Kachele das Bild nicht; verwende es nur einmal.

### Rückgabewert

Ein CanvasPattern-Objekt, das das Muster darstellt.

## Beschreibung

Diese Methode erzeugt und gibt ein CanvasPattern-Objekt zurück, das das von einem gekachelten Bild definierte Muster repräsentiert. Um ein Muster zum Zeichnen von Linien oder zum Füllen von Flächen zu verwenden, setzen Sie ein CanvasPattern-Objekt als Wert für die `strokeStyle` oder `fillStyle`-Eigenschaft.

## Fehler

Firefox 1.5 unterstützt lediglich den `"repeat"`-Stil. Andere Stile werden ignoriert.

## Siehe auch

`CanvasPattern`

## Name

CanvasRenderingContext2D.createRadialGradient( ) — Erzeugt einen radialen Farbgradienten

## Überblick

    CanvasGradient createRadialGradient(float _xStart_ , float _yStart_ , float _radiusStart_ ,
                                        float _xEnde_ , float _yEnde_ , float _radiusEnde_ )

### Argumente

 _`xStart, yStart`_

Die Koordinaten des Startkreismittelpunkts.

 _`radiusStart`_

Der Radius des Startkreises.

 _`xEnde, yEnde`_

Die Koordinaten des Endkreismittelpunkts.

 _`radiusEnde`_

Der Radius des Endkreises.

### Rückgabewert

Ein CanvasGradient-Objekt, das einen radialen Farbgradienten widerspiegelt.

## Beschreibung

Diese Methode erzeugt und gibt ein neues CanvasGradient-Objekt zurück, das Farben zwischen den Umfängen der zwei angegebenen Kreise radial interpoliert. Beachten Sie, dass diese Methode keine Farben für den Gradienten festlegt. Dazu verwenden Sie die `addColorStop( )`-Methode des zurückgegebenen Objekts. Um mit Hilfe eines Gradienten Linien zu zeichnen oder Flächen auszufüllen, weisen Sie der `strokeStyle` oder `fillStyle`-Eigenschaft ein CanvasGradient-Objekt zu.

Radialgradienten werden wiedergegeben, indem die Farbe bei Offset 0 für den Umfang des ersten Kreises verwendet wird, die Farbe bei Offset 1 für den zweiten Kreis und interpolierte Farbwerte (Rot, Grün, Blau und Alpha) für Kreise zwischen diesen beiden Kreisen.

## Siehe auch

`CanvasGradient.addColorStop( )`, `CanvasRenderingContext2D.createLinearGradient( )`

## Name

CanvasRenderingContext2D.drawImage( ) — Zeichnet ein Bild

## Überblick

    void drawImage(Image _bild_ , float _x_ , float _y_ )
    void drawImage(Image _bild_ , float _x_ , float _y_ ,
            float _breite_ , float _hoehe_ )
    void drawImage(Image _bild_ , integer _sourceX_ , integer _sourceY_ ,
                   integer _sourceBreite_ , integer _sourceHoehe_ ,
                   float _destX_ , float _destY_ ,
                   float _destBreite_ , float _destHoehe_ )

### Argumente

 _`bild`_

Das zu zeichnende Bild. Dieses muss ein Image-Objekt sein, das ein `<img>`-Tag repräsentiert, oder auch ein Offscreen-Bild oder ein Canvas-Objekt.

 _`x, y`_

Der Punkt, an dem die linke obere Ecke des Bildes gezeichnet wird.

 _`breite, hoehe`_

Die Größe, in der das Bild gezeichnet werden soll. Die Spezifikation dieser Argumente skaliert das Bild.

 _`sourceX, sourceY`_

Die linke obere Ecke der zu zeichnenden Bildregion. Diese ganzzahligen Argumente werden in Bildpixeln angegeben.

 _`sourceBreite, sourceHoehe`_

Die Abmessungen der zu zeichnenden Bildregion, angegeben in Bildpixeln.

 _`destX, destY`_

Die Canvas-Koordinaten, an denen die linke obere Ecke der Bildregion gezeichnet werden soll.

 _`destBreite, destHoehe`_

Die Canvas-Dimensionen, mit denen die Bildregion gezeichnet werden soll.

## Beschreibung

Bei dieser Methode gibt es drei Varianten. Bei der ersten Variante wird das gesamte Bild auf den Canvas kopiert: Die linke, obere Ecke wird auf den angegebenen Punkt gesetzt, und jeder Bildpixel wird auf eine Einheit im Canvas-Koordinatensystem abgebildet. Bei der zweiten Variante wird ebenfalls das gesamte Bild auf den Canvas kopiert, Sie können die gewünschte Breite und Höhe des Bildes jedoch in Canvas-Einheiten festlegen. Die dritte Variante ist ganz variabel: Mit ihr können Sie jede beliebige, rechteckige Bildregion festlegen und kopieren, wobei das Bild beliebig auf jeder Position im Canvas skaliert werden kann.

Das an diese Methode übergebene Bild muss ein Image- oder Canvas-Objekt sein. Ein Image-Objekt kann ein `<img>`-Tag im Dokument oder ein über den `Image( )`-Konstruktor erstelltes Offscreen-Bild repräsentieren.

## Siehe auch

`Image`

## Name

CanvasRenderingContext2D.fill( ) — Füllt den Pfad

## Überblick

    void fill( )

## Beschreibung

`fill( )` füllt den momentanen Pfad mit der/dem von der `fillStyle`-Eigenschaft festgelegten Farbe bzw. Gradienten oder Muster. Jeder Subpfad des Pfades wird getrennt gefüllt. Alle nicht geschlossenen Subpfade werden so gefüllt, als wäre die `closePath( )`-Methode für sie aufgerufen worden. (Beachten Sie dabei, dass diese Subpfade dadurch aber nicht geschlossen werden.)

Über die sogenannte »non-zero winding rule« stellt der Canvas fest, welche Punkte innerhalb und welche außerhalb des Pfades liegen. Die Details dieser Regel sprengen den Umfang dieses Buches, sind aber normalerweise nur für komplizierte Pfade von Bedeutung, die sich selbst überkreuzen.

Durch das Füllen eines Pfades wird dieser nicht gelöscht. Nach dem Aufruf von `fill( )` können Sie `stroke( )` aufrufen, ohne den Pfad neu zu definieren.

## Siehe auch

`CanvasRenderingContext2D.fillRect( )`

## Name

CanvasRenderingContext2D.fillRect( ) — Füllt ein Rechteck

## Überblick

    void fillRect(float _x_ , float _y_ ,
                  float _breite_ , float _hoehe_ )

### Argumente

 _`x, y`_

Die Koordinaten der linken oberen Ecke eines Rechtecks.

 _`breite, hoehe`_

Die Abmessungen des Rechtecks.

## Beschreibung

`fillRect( )` füllt das angegebene Rechteck mit der/dem von der `fillStyle`-Eigenschaft festgelegten Farbe bzw. Gradienten oder Muster.

Derzeitige Implementierungen von `fillRect( )` löschen außerdem den Pfad, so als ob `beginPath( )` aufgerufen worden wäre. Dieses überraschende Verhalten ist nicht unbedingt standardisiert, sodass Sie sich nicht darauf verlassen sollten.

## Siehe auch

`CanvasRenderingContext2D.fill( )`
---
`CanvasRenderingContext2D.rect( )`
`CanvasRenderingContext2D.strokeRect( )`

## Name

CanvasRenderingContext2D.globalCompositeOperation — Legt fest, wie Farben auf dem Canvas kombiniert werden

## Überblick

    String globalCompositeOperation

## Beschreibung

Diese Eigenschaft legt fest, wie Farben, die auf dem Canvas wiedergegeben werden, mit den bereits auf dem Canvas vorhandenen Farben kombiniert werden. Die möglichen Werte und ihre Bedeutungen werden in der unten stehenden Tabelle aufgeführt. Der Begriff _Ursprung_ in diesen Werten bezieht sich auf die auf den Canvas zu zeichnenden Farben, und der Begriff _Ziel_ bezieht sich auf die auf dem Canvas bereits vorhandenen Farben. Der Defaultwert ist »source-over«.

Wert | Bedeutung

---|---

`copy` | Zeichnet nur die neue Form und löscht alles andere.

`darker` | Für die Überlappungsflächen der beiden Formen wird die Farbe durch Subtraktion der Farbwerte bestimmt.

`destination-atop` | Der bereits vorhandene Inhalt wird nur dort beibehalten, wo er mit der neuen Form überlappt. Die neue Form wird hinter dem Inhalt gezeichnet.

`destination-in` | Vorhandener Inhalt wird dort beibehalten, wo die neue Form mit dem vorhandenen Canvas-Inhalt überlappt . Alle anderen Bereiche werden transparent.

`destination-out` | Vorhandener Inhalt wird dort beibehalten, wo er nicht mit der neuen Form überlappt. Alle anderen Bereiche werden transparent.

`destination-over` | Die neue Form wird hinter vorhandenem Inhalt gezeichnet.

`lighter` | Wo beide Formen überlappen, wird die Farbe durch Addition der beiden Farbwerte bestimmt.

`source-atop` | Die neue Form wird nur dort gezeichnet, wo sie den vorhandenen Inhalt überdeckt.

`source-in` | Die neue Form wird nur dort gezeichnet, wo die neue Form mit dem vorhandenen Inhalt überlappt. Alle anderen Bereiche werden transparent.

`source-out` | Die neue Form wird dort gezeichnet, wo sie nicht mit bestehendem Inhalt überlappt.

`source-over` | Die neue Form wird über den bestehenden Inhalt gezeichnet. Das ist der Defaultwert.

`xor` | Formen werden an den Stellen transparent angezeigt, an denen beide überlappen. In anderen Bereichen werden sie normal gezeichnet.

## Fehler

Firefox 1.5 unterstützt die Werte »copy« und »darker« nicht.

## Name

CanvasRenderingContext2D.lineCap — Gibt an, wie die Linienenden wiedergegeben werden.

## Überblick

    String lineCap

## Beschreibung

Die `lineCap`-Eigenschaft gibt an, wie Linien enden sollen. Das ist nur beim Zeichnen von breiten Linien von Bedeutung. Die unten stehende Tabelle führt die zulässigen Werte für diese Eigenschaft auf. Der Standardwert ist »butt«.

`Wert` | Bedeutung

---|---

`butt` | Dieser Defaultwert gibt an, dass die Linie am Ende nicht abgerundet wird. Der Linienabschluss ist gerade und rechtwinklig zur Linienrichtung. Die Linie wird nicht über ihren Endpunkt hinaus erweitert.

`round` | Dieser Wert legt fest, dass Linien mit einem Halbkreis abgerundet werden, dessen Durchmesser der Linienbreite entspricht und der um die halbe Linienbreite über das Linienende hinausragt.

`square` | Dieser Wert legt fest, dass Linien mit einem Rechteck enden sollen. Der Wert entspricht »butt«, aber die Linie wird um eine halbe Linienbreite erweitert.

## Fehler

Firefox 1.5 implementiert den Stil »butt« für Linienabschlüsse nicht richtig. Die »butt«-Linienenden werden wie »square«-Linienabschlüsse dargestellt.

## Siehe auch

`CanvasRenderingContext2D.lineJoin`

## Name

CanvasRenderingContext2D.lineJoin — Legt fest, wie Eckpunkte wiedergeben werden

## Überblick

    String lineJoin

## Beschreibung

Bei Pfaden, die Eckpunkte enthalten, an denen Liniensegmente und/oder Kurven zusammentreffen, gibt die Eigenschaft `lineJoin` an, wie diese Eckpunkte gezeichnet werden. Die Eigenschaft wirkt sich nur dann sichtbar aus, wenn breite Linien gezeichnet werden.

Der Defaultwert der Eigenschaft ist »miter«; ist er gesetzt, werden die äußeren Kanten der beiden Liniensegmente so lange verlängert, bis sie aufeinandertreffen. Treffen die Linien spitzwinklig zusammen, können die Gehrungsverbindungen recht lang werden. Die Eigenschaft `miterLimit` legt eine obere Grenze für die Gehrungslänge fest. Wird diese Grenze erreicht, wird die Gehrung schräg abgeschnitten.

Beim Wert »round« wird an die Außenkanten des Scheitelpunktes ein ausgefüllter Kreisbogen mit einem Durchmesser in der Linienbreite angefügt.

Beim Wert »bevel« werden die Außenkanten der Eckpunkte mit Hilfe eines ausgefüllten Dreiecks verbunden.

## Fehler

Firefox 1.5 implementiert »bevel« falsch und zeigt eine runde Verbindung an. Außerdem werden Gehrungsverbindungen nicht richtig angezeigt, wenn sie in einer teilweise transparenten Farbe gezeichnet werden.

## Siehe auch

`CanvasRenderingContext2D.lineCap`, `CanvasRenderingContext2D.miterLimit`

## Name

CanvasRenderingContext2D.lineTo( ) — Fügt dem momentanen Subpfad eine gerade Linie hinzu

## Überblick

    void lineTo(float _x_ , float _y_ )

### Argumente

 _`x, y`_

Die Koordinaten des Linienendpunkts.

## Beschreibung

`lineTo( )` fügt dem momentanen Subpfad eine gerade Linie hinzu. Die Linie beginnt am momentanen Punkt und endet bei `(x,y)`. Nach dem Ausführen der Methode liegt die momentane Position auf `(x,y)`.

## Siehe auch

`CanvasRenderingContext2D.beginPath( )`, `CanvasRenderingContext2D.moveTo( )`

## Name

CanvasRenderingContext2D.miterLimit — Verhältnis zwischen der maximalen Gehrungslänge und der Linienbreite

## Überblick

    float miterLimit

## Beschreibung

Wenn breite Linien gezeichnet werden, die Eigenschaft `lineJoin` auf »miter« festgelegt ist und sich zwei Linien im spitzen Winkel treffen, kann die entstehende Gehrung recht lang sein. Zu lange Gehrungen sind optisch irritierend. Die Eigenschaft `miterLimit` legt eine obere Schranke für die Gehrungslänge fest. Die Eigenschaft gibt ein Verhältnis zwischen der Gehrungslänge und der Linienbreite an. Der Defaultwert ist 10, was bedeutet, dass die Gehrung nicht länger als die zehnfache Linienbreite sein soll. Erreicht eine Gehrung diese Länge, wird sie schräg abgeschnitten. Die Eigenschaft hat keine Auswirkung, wenn `lineJoin` auf »round« oder »bevel« gesetzt ist.

## Fehler

Firefox 1.5 implementiert diese Eigenschaft nicht richtig. Wenn eine Gehrungsverbindung die Grenze `miterLimit` überschreitet, werden die Linien rund statt per Gehrung verbunden.

## Siehe auch

`CanvasRenderingContext2D.lineJoin`

## Name

CanvasRenderingContext2D.moveTo( ) — Legt die derzeitige Position fest und beginnt mit einem neuen Subpfad.

## Überblick

    void moveTo(float _x_ , float _y_ )

### Argumente

 _`x, y`_

Die Koordinaten des neuen momentanen Punkts.

## Beschreibung

`moveTo( )` setzt die momentane Position auf `(` _`x`_ , _`y`_`)` und erzeugt einen neuen Subpfad, dessen erster Punkt dieser Punkt ist. Falls ein vorhergehender Subpfad vorhanden war, der aus nur einem Punkt bestand, wird dieser Subpfad aus dem Pfad entfernt.

## Siehe auch

`CanvasRenderingContext2D.beginPath( )`

## Name

CanvasRenderingContext2D.quadraticCurveTo( ) — Fügt dem momentanen Subpfad eine quadratische Bézierkurve hinzu

## Überblick

    void quadraticCurveTo(float _cpX_ , float _cpY_ ,
                          float _x_ , float _y_ )

### Argumente

 _`cpx, cpy`_

Die Koordinaten des Kurvenendpunktes.

 _`x, y`_

Die Koordinaten des Kurvenendpunktes.

## Beschreibung

Diese Methode fügt dem momentanen Subpfad ein Segment einer quadratischen Bézierkurve hinzu. Die Kurve beginnt am momentanen Punkt und endet bei `(` _`x`_ , _`y`_`)`. Der Kontrollpunkt `(` _`cpX`_ , _`cpY`_`)` legt die Kurvenform zwischen den beiden Punkten fest. (Eine Beschreibung der mathematischen Eigenschaften von Bézierkuven würde den Umfang dieses Buches allerdings sprengen.) Nach dem Ausführen der Methode liegt die momentane Position auf `(` _`x`_ , _`y`_`)`.

## Fehler

Firefox 1.5 implementiert diese Methode nicht richtig.

## Siehe auch

`CanvasRenderingContext2D.bezierCurveTo( )`

## Name

CanvasRenderingContext2D.rect( ) — Fügt dem Pfad einen rechteckigen Subpfad hinzu

## Überblick

    void rect(float _x_ , float _y_ ,
         float _breite_ , float _hoehe_ )

### Argumente

 _`x, y`_

Die Koordinaten der linken oberen Ecke des Rechtecks.

 _`breite, hoehe`_

Die Abmessungen des Rechtecks.

## Beschreibung

Diese Methode fügt ein Rechteck zum Pfad hinzu. Dieses Rechteck sitzt in einem eigenständigen Subpfad und ist mit keinem anderen Subpfad im Pfad verbunden. Nach dem Ausführen der Methode liegt die momentane Position auf `(0,0)`.

## Siehe auch

`CanvasRenderingContext2D.fillRect( )`, `CanvasRenderingContext2D.strokeRect( )`

## Name

CanvasRenderingContext2D.restore( ) — Setzt den Zeichnungsstatus auf die gespeicherten Werte zurück

## Überblick

    void restore( )

## Beschreibung

Diese Methode holt den obersten Eintrag vom Stapel der gespeicherten Grafikzustände und setzt die Werte der Eigenschaften CanvasRenderingContext2D, den Clippingpfad und die Transformationsmatrix zurück. Weitere Informationen finden Sie unter der `save( )`-Methode.

## Fehler

Firefox 1.5 speichert und setzt die Eigenschaft `strokeStyle` nicht richtig zurück.

## Siehe auch

`CanvasRenderingContext2D.save( )`

## Name

CanvasRenderingContext2D.rotate( ) — Dreht das Koordinatensystem des Canvas

## Überblick

    void rotate(float _winkel_ )

### Argumente

 _`winkel`_

Der Drehumfang in Bogenmaß. Positive Werte ergeben eine Drehung im Uhrzeigersinn und negative Werte eine Drehung gegen den Uhrzeigersinn.

## Beschreibung

Diese Methode ändert die Abbildung von Canvas-Koordinaten auf Pixel im `<canvas>`-Element im Webbrowser, wodurch alle folgenden Zeichnungen im Canvas um den angegeben Winkel gedreht erscheinen. Das `<canvas>`-Element selbst wird dabei nicht gedreht. Beachten Sie, dass der Winkel im Bogenmaß angegeben wird. Um Grad ins Bogenmaß umzurechnen, multiplizieren Sie sie mit `Math.PI` und teilen das Ergebnis durch 180.

## Siehe auch

`CanvasRenderingContext2D.scale( )`, `CanvasRenderingContext2D.translate( )`

## Name

CanvasRenderingContext2D.save( ) — Speichert eine Kopie des momentanen Grafikstatus

## Überblick

    void save( )

## Beschreibung

`save( )` schiebt den momentanen Grafikstatus auf einen Stapelspeicher, der aus den gespeicherten Grafikzuständen besteht. Dadurch können Sie den Grafikstatus vorübergehend ändern und dann die vorherigen Werte durch den Aufruf von `restore( )` wiederherstellen.

Der Grafikstatus eines Canvas enthält alle Eigenschaften des CanvasRenderingContext2D-Objekts (mit Ausnahme der schreibgeschützten Eigenschaft `canvas`). Er enthält außerdem die Transformationsmatrix, die sich aus den Aufrufen von `rotate( )`, `scale( )` und `translate( )` ergibt. Zusätzlich enthält er den Clippingpfad, der durch die `clip( )`-Methode festgelegt wird. Beachten Sie, dass der momentane Pfad und die momentane Position nicht Teil des Grafikstatus sind und von dieser Methode nicht gespeichert werden.

## Fehler

Firefox 1.5 speichert die Eigenschaft `strokeStyle` nicht und setzt sie auch nicht zurück.

## Siehe auch

`CanvasRenderingContext2D.restore( )`

## Name

CanvasRenderingContext2D.scale( ) — Skaliert das Benutzerkoordinatensystem des Canvas.

## Überblick

    void scale(float _sx_ , float _sy_ )

### Argumente

 _`sx, sy`_

Die horizontalen und vertikalen Skalierfaktoren.

## Beschreibung

`scale( )` fügt der momentanen Transformationsmatrix des Canvas eine Skaliertransformation hinzu. Die Skalierung findet mit Hilfe von unabhängigen horizontalen und vertikalen Skalierfaktoren statt. Werden beispielsweise die Werte 2,0 und 0,5 übergeben, wird der anschließend gezeichnete Pfad doppelt so breit und halb so hoch. Bei negativen Werten für _`sx`_ werden die X-Koordinaten an der Y-Achse gespiegelt. Bei negativen Werten für _`sy`_ werden die Y-Koordinaten an der X-Achse gespiegelt.

## Siehe auch

`CanvasRenderingContext2D.rotate( )`, `CanvasRenderingContext2D.translate( )`

## Name

CanvasRenderingContext2D.stroke( ) — Zeichnet den momentanen Pfad

## Überblick

    void stroke( )

## Beschreibung

Die Methode `stroke( )` zeichnet den Umriss des momentanen Pfads. Der Pfad bestimmt die Geometrie der erzeugten Linie, die optische Erscheinung dieser Linie wird aber von den Eigenschaften `strokeStyle`, `lineWidth`, `lineCap`, `lineJoin` und `miterLimit` bestimmt.

Der Begriff _stroke_ bezieht sich auf einen Stift- oder Pinselstrich. Er bedeutet »zeichne den Umriss von«. Beachten Sie den Unterschied zwischen der `stroke( )`-Methode, die den Umriss eines Pfads zeichnet, und der `fill( )`-Methode, die das Innere eines Pfads ausfüllt.

## Siehe auch

`CanvasRenderingContext2D.fill( )`
---
`CanvasRenderingContext2D.lineCap`
`CanvasRenderingContext2D.lineJoin`
`CanvasRenderingContext2D.strokeRect( )`

## Name

CanvasRenderingContext2D.strokeRect( ) — Zeichnet ein Rechteck

## Überblick

    void strokeRect(float _x_ , float _y_ ,
            float _breite_ , float _hoehe_ )

### Argumente

 _`x, y`_

Die Koordinaten der linken oberen Ecke des Rechtecks.

 _`breite, hoehe`_

Die Abmessungen des Rechtecks.

## Beschreibung

Diese Methode zeichnet den Umriss eines Rechtecks mit der angegebenen Position und Größe (füllt das Innere aber nicht aus). Die Linienfarbe und -breite werden über die Eigenschaften `strokeStyle` und `lineWidth` festgelegt. Die Eckform des Rechtecks wird über die Eigenschaft `lineJoin` angegeben.

Derzeitige Implementierungen von `strokeRect( )` löschen den Pfad, so als ob `beginPath( )` aufgerufen worden wäre. Dieses überraschende Verhalten ist nicht unbedingt standardisiert, sodass Sie sich nicht darauf verlassen sollten.

## Siehe auch

`CanvasRenderingContext2D.fillRect( )`
---
`CanvasRenderingContext2D.lineJoin`
`CanvasRenderingContext2D.rect( )`
`CanvasRenderingContext2D.stroke( )`

## Name

CanvasRenderingContext2D.translate( ) — Verschiebt das Benutzerkoordinatensystem des Canvas

## Überblick

    void translate(float _dx_ , float _dy_ )

### Argumente

 _`dx, dy`_

Die Beträge, um die in der X- und Y-Dimension verschoben werden soll.

## Beschreibung

`translate( )` fügt der Transformationsmatrix des Canvas horizontale und vertikale Offsets hinzu. Die Argumente _`dx`_ und _`dy`_ werden allen Punkten bei anschließend definierten Pfaden hinzugefügt.

## Siehe auch

`CanvasRenderingContext2D.rotate( )`, `CanvasRenderingContext2D.scale( )`

## Name

CDATASection — Ein CDATA-Knoten in einem XML-DokumentDOM Level 1 XMLNode → CharacterData → Text → CDATASection

## Beschreibung

Dieses nur selten benutzte Interface stellt einen CDATA-Abschnitt in einem XML-Dokument dar.

Programmierer, die mit HTML-Dokumenten arbeiten, stoßen nie auf Knoten dieses Typs und brauchen auch dieses Interface nicht zu benutzen.

CDATASection ist ein Subinterface von Text und definiert keine eigenen Eigenschaften oder Methoden. Auf den Textinhalt des CDATA-Abschnitts wird mit der aus Node geerbten `nodeValue`-Eigenschaft oder der aus CharacterData geerbten `data`-Eigenschaft zugegriffen. Sie können CDATASection-Knoten zwar oft wie Text-Knoten behandeln, müssen aber dabei beachten, dass die Methode `Node.normalize( )` keine benachbarten CDATA-Abschnitte miteinander verschmilzt. CDATASection erzeugt mit `Document.createCDATASection( )` eine CDATASection.

## Siehe auch

`CharacterData`, `Text`

## Name

CharacterData — Gemeinsame Funktionalität für Text- und KommentarknotenDOM Level 1 CoreNode → CharacterData

## Subinterfaces

Comment, Text

## Eigenschaften

`String data`

Der Textinhalt dieses Knotens.

`readonly unsigned long length`

Die Anzahl der Zeichen in diesem Knoten.

## Methoden

`appendData( )`

Hängt den angegebenen String an den Textinhalt dieses Knotens an.

`deleteData( )`

Löscht Text aus diesem Knoten. Beginnt mit dem Zeichen am angegebenen Offset und löscht ab dort die angegebene Anzahl Zeichen.

`insertData( )`

Fügt den angegebenen String in den Text dieses Knotens an dem Zeichen ein, das durch den Offset angegeben ist.

`replaceData( )`

Ersetzt die Zeichen ab dem angegebenen Zeichen-Offset bis zur angegebenen Anzahl von Zeichen durch den spezifizierten String.

`substringData( )`

Gibt eine Kopie des Texts vom angegebenen Zeichen-Offset bis zur spezifizierten Zeichenzahl zurück.

## Beschreibung

CharacterData ist das Superinterface für Text- und Kommentarknoten. Dokumente enthalten nie CharacterData-Knoten, sondern nur Text- und Kommentarknoten. Da jedoch diese beiden Knotentypen eine ähnliche Funktionalität haben, ist diese Funktionalität hier definiert, damit Text und Comment sie beide erben können.

Beachten Sie, dass es nicht notwendig ist, die von diesem Interface definierten Methoden zur Stringmanipulation zu verwenden. Die `data`-Eigenschaft ist ein normaler JavaScript-String, den Sie mit dem `+`-Operator (bei Stringverknüpfungen) und verschiedenen String- und RegExp-Methoden ändern können.

## Siehe auch

`Comment`, `Text`

## Name

CharacterData.appendData( ) — Hängt einen String an einen Text- oder Kommentarknoten anDOM Level 1 Core

## Überblick

    void appendData(String _arg_ )
      throws DOMException;

### Argumente

 _`arg`_

Der String, der an den Text- oder Kommentarknoten angehängt wird.

### Exceptions

Diese Methode löst eine DOMException mit dem `code NO_MODIFICATION_ALLOWED_ERR` aus, wenn sie auf einem schreibgeschützten Knoten aufgerufen wird.

## Beschreibung

Diese Methode hängt den String _`arg`_ an das Ende der `data`-Eigenschaft dieses Knotens an.

## Name

CharacterData.deleteData( ) — Löscht Zeichen aus einem Text- oder KommentarknotenDOM Level 1 Core

## Überblick

    void deleteData(unsigned long _offset_ ,
                    unsigned long _anzahl_ )
      throws DOMException;

### Argumente

 _`offset`_

Die Position des ersten zu löschenden Zeichens.

 _`anzahl`_

Die Anzahl der zu löschenden Zeichen.

### Exceptions

Diese Methode kann eine DOMException mit einem der folgenden `code`-Werte auslösen:

`INDEX_SIZE_ERR`

Das Argument _`offset`_ oder _`anzahl`_ ist negativ, oder _`offset`_ ist größer als die Länge des Text- oder Kommentarknotens.

`NO_MODIFICATION_ALLOWED_ERR`

Der Knoten ist schreibgeschützt und darf nicht geändert werden.

## Beschreibung

Diese Methode löscht Zeichen aus diesem Text- oder Kommentarknoten. Sie beginnt mit dem durch _`offset`_ angegebenen Zeichen und fährt für _`anzahl`_ Zeichen mit dem Löschen fort. Ist _`offset`_ plus _`anzahl`_ größer als die Anzahl der Zeichen des Text- oder Kommentarknotens, so werden ab _`offset`_ alle Zeichen bis zum Ende des Strings gelöscht.

## Name

CharacterData.insertData( ) — Fügt einen String in einen Text- oder Kommentarknoten einDOM Level 1 Core

## Überblick

    void insertData(unsigned long _offset_ ,
                    String _arg_ )
      throws DOMException;

### Argumente

 _`offset`_

Die Zeichenposition im Text- oder Kommentarknoten, an der der String eingefügt werden soll.

 _`arg`_

Der einzufügende String.

### Exceptions

Diese Methode kann unter den folgenden Umständen eine DOMException mit einem der folgenden `code`-Werte auslösen:

`INDEX_SIZE_ERR`

 _`offset`_ ist negativ oder größer als die Länge des Text- oder Kommentarknotens.

`NO_MODIFICATION_ALLOWED_ERR`

Der Knoten ist schreibgeschützt und darf nicht geändert werden.

## Beschreibung

Diese Methode fügt den angegebenen String _`arg`_ in den Text eines Text- oder Kommentarknotens an der durch _`offset`_ angegebenen Position ein.

## Name

CharacterData.replaceData( ) — Ersetzt Zeichen eines Text- oder Kommentarknotens durch einen StringDOM Level 1 Core

## Überblick

    void replaceData(unsigned long _offset_ ,
                     unsigned long _anzahl_ ,
                     String _arg_ )
      throws DOMException;

### Argumente

 _`offset`_

Die Zeichenposition im Text- oder Kommentarknoten, an der die Ersetzung beginnen soll.

 _`anzahl`_

Die Anzahl der zu ersetzenden Zeichen.

 _`arg`_

Der String, der die durch _`offset`_ und _`anzahl`_ angegebenen Zeichen ersetzen soll.

### Exceptions

Diese Methode kann unter den folgenden Umständen eine DOMException mit einem der folgenden `code`-Werte auslösen:

`INDEX_SIZE_ERR`

 _`offset`_ ist negativ oder größer als die Länge des Text- oder Kommentarknotens, oder _`anzahl`_ ist negativ.

`NO_MODIFICATION_ALLOWED_ERR`

Der Knoten ist schreibgeschützt und darf nicht geändert werden.

## Beschreibung

Diese Methode ersetzt _`anzahl`_ Zeichen, beginnend mit der Position _`offset`_ , durch den Inhalt des Strings _`arg`_. Ist die Summe von _`offset`_ und _`anzahl`_ größer als die Länge des Text- oder Kommentarknotens, werden beginnend mit _`offset`_ alle Zeichen ersetzt.

Beachten Sie, dass sowohl die `insertData( )`-Methode als auch die `deleteData( )`-Methode Spezialfälle dieses Falles darstellen.

## Name

CharacterData.substringData( ) — Extrahiert einen Teilstring aus einem Text- oder KommentarknotenDOM Level 1 Core

## Überblick

    String substringData(unsigned long _offset_ ,
               unsigned long _anzahl_ )
      throws DOMException;

### Argumente

 _`offset`_

Die Position des ersten zurückzugebenden Zeichens.

 _`anzahl`_

Die Anzahl der Zeichen des zurückzugebenden Teilstrings.

### Rückgabewert

Ein String, der aus _`anzahl`_ Zeichen des Text- oder Kommentarknotens besteht und der mit dem Zeichen an der Position _`offset`_ beginnt.

### Exceptions

Diese Methode kann eine DOMException mit einem der folgenden `code`-Werte auslösen:

`INDEX_SIZE_ERR`

 _`offset`_ ist negativ oder größer als die Länge des Text- oder Kommentarknotens, oder _`anzahl`_ ist negativ.

`DOMSTRING_SIZE_ERR`

Der angegebene Textbereich ist zu lang, um in einen String der JavaScript-Implementierung des Browsers zu passen.

## Beschreibung

Diese Methode extrahiert aus einem Text- oder Kommentarknoten den Teilstring, der bei der Position _`offset`_ beginnt und nach _`anzahl`_ Zeichen endet. Sie nützt nur dann, wenn die Textmenge im Knoten größer als die maximale Zeichenzahl ist, die in einen String einer browserspezifischen JavaScript-Implementierung hineinpasst. In diesem Fall kann ein JavaScript-Programm die `data`\- Eigenschaft des Text- oder Kommentarknotens nicht unmittelbar nutzen, sondern muss mit kürzeren Teilstrings des Knotentexts arbeiten. In der Praxis tritt diese Situation so gut wie nie auf.

## Name

Checkbox — Siehe Input

##

## Name

Comment — Ein HTML- oder XML-KommentarDOM Level 1 CoreNode → CharacterData → Comment

## Beschreibung

Ein Kommentarknoten repräsentiert einen Kommentar in einem HTML- oder XML-Dokument. Auf den Inhalt des Kommentars (also den Text zwischen `<!--` und `-->`) können Sie über die `data`-Eigenschaft zugreifen, die aus dem Interface CharacterData geerbt wurde, oder auch über die `nodeValue`-Eigenschaft aus dem Node-Interface. Dieser Inhalt kann mit den aus CharacterData geerbten Methoden bearbeitet werden. Sie erzeugen mit `Document. createComment( )` ein Kommentar-Objekt.

## Siehe auch

`CharacterData`

## Name

CSS2Properties — Eine Menge von CSS-Attributen und ihren WertenDOM Level 2 CSS2Object → CSS2Properties

## Eigenschaften

`String cssText`

Die Textdarstellung eines Sets von Stilattributen und ihren Werten. Der Text wird wie in einem CSS-Stylesheet formatiert, ohne den Elementselektor und die geschwungenen Klammern, die die Attribute und Werte umgeben. Wenn Sie diese Eigenschaft auf einen unzulässigen Wert setzen, wird eine DOMException mit dem `code SYNTAX_ERR` ausgelöst. Ist das CSS2Properties-Objekt schreibgeschützt und Sie versuchen die Eigenschaft zu setzen, so wird eine DOMException mit einem `code` von `NO_MODIFICATION_ALLOWED_ERR` ausgelöst.

Zusätzlich zur `cssText`-Eigenschaft hat ein CSS2Properties-Objekt auch eine Eigenschaft für jedes CSS-Attribut, das vom Browser unterstützt wird. Diese Eigenschaftsnamen sind eng an die CSS-Attributnamen angelehnt. Geringfügige Abweichungen dienen nur zur Vermeidung von Syntaxfehlern in JavaScript. Attribute, die aus mehreren Wörtern bestehen und Bindestriche enthalten (z.B. »font-family«), werden in JavaScript ohne Bindestriche geschrieben, und jedes Wort hinter dem ersten beginnt mit einem Großbuchstaben: `fontFamily`. Da auch das Attribut »float« mit dem reservierten Wort `float` in Konflikt steht, wird es als Eigenschaft `cssFloat` übersetzt.

Die CSS2Properties-Eigenschaftsnamen, die den jeweiligen von der CSS2-Spezifikation definierten Attributen entsprechen, werden in der nachfolgenden Tabelle aufgeführt. Beachten Sie jedoch, dass manche Browser nicht alle CSS-Attribute unterstützen und möglicherweise nicht alle aufgeführten Eigenschaften implementieren. Da die Eigenschaften direkt den CSS-Attributen entsprechen, werden sie hier nicht einzeln dokumentiert. Angaben zur Bedeutung und zu den zulässigen Werten einzelner Eigenschaften entnehmen Sie bitte einer CSS-Referenz, wie z.B. _CSS - Das umfassende Handbuch_ von Eric A. Meyer (O'Reilly Verlag). Alle Eigenschaften sind Strings. Das Setzen einer beliebigen dieser Eigenschaften kann die gleichen Exceptions auslösen wie das Setzen der `cssText`-Eigenschaft:

`azimuth` | `background` | `backgroundAttachment` | `backgroundColor`

---|---|---|---

`backgroundImage` | `backgroundPosition` | `backgroundRepeat` | `border`

`borderBottom` | `borderBottomColor` | `borderBottomStyle` | `borderBottomWidth`

`borderCollapse` | `borderColor` | `borderLeft` | `borderLeftColor`

`borderLeftStyle` | `borderLeftWidth` | `borderRight` | `borderRightColor`

`borderRightStyle` | `borderRightWidth` | `borderSpacing` | `borderStyle`

`borderTop` | `borderTopColor` | `borderTopStyle` | `borderTopWidth`

`borderWidth` | `bottom` | `captionSide` | `clear`

`clip` | `color` | `content` | `counterIncrement`

`counterReset` | `cssFloat` | `cue` | `cueAfter`

`cueBefore` | `cursor` | `direction` | `display`

`elevation` | `emptyCells` | `font` | `fontFamily`

`fontSize` | `fontSizeAdjust` | `fontStretch` | `fontStyle`

`fontVariant` | `fontWeight` | `height` | `left`

`letterSpacing` | `lineHeight` | `listStyle` | `listStyleImage`

`listStylePosition` | `listStyleType` | `margin` | `marginBottom`

`marginLeft` | `marginRight` | `marginTop` | `markerOffset`

`marks` | `maxHeight` | `maxWidth` | `minHeight`

`minWidth` | `orphans` | `outline` | `outlineColor`

`outlineStyle` | `outlineWidth` | `overflow` | `padding`

`paddingBottom` | `paddingLeft` | `paddingRight` | `paddingTop`

`page` | `pageBreakAfter` | `pageBreakBefore` | `pageBreakInside`

`pause` | `pauseAfter` | `pauseBefore` | `pitch`

`pitchRange` | `playDuring` | `position` | `quotes`

`richness` | `right` | `size` | `speak`

`speakHeader` | `speakNumeral` | `speakPunctuation` | `speechRate`

`stress` | `tableLayout` | `textAlign` | `textDecoration`

`textIndent` | `textShadow` | `textTransform` | `top`

`unicodeBidi` | `verticalAlign` | `visibility` | `voiceFamily`

`volume` | `whiteSpace` | `widows` | `width`

`wordSpacing` | `zIndex`

|  |

## Beschreibung

Das Interface CSS2Properties stellt ein Set von CSS-Stilattributen und ihren Werten dar. Es definiert für jedes von der CSS2-Spezifikation definierte CSS-Attribut eine JavaScript-Eigenschaft. Die `style`-Eigenschaft eines HTMLElements und die `style`-Eigenschaft eines CSSRule-Objekts sind les- und schreibbare CSS2Properties-Objekte. Der Rückgabewert von `Window.getComputedStyle( )` ist aber ein CSS2Properties-Objekt, dessen Eigenschaften schreibgeschützt sind.

## Siehe auch

`CSSRule`, `HTMLElement`, `Window.getComputedStyle( )`; Kapitel 16

## Name

CSSRule — Eine Regel in einem CSS-StylesheetDOM Level 2 CSS, IE 5Object → CSSRule

## Eigenschaften

`String selectorText`

Der Selektor, der angibt, auf welche Dokumentelemente sich diese Stilregel bezieht. Das Setzen dieser Eigenschaft löst eine DOMException mit dem `code NO_MODIFICATION_ ALLOWED_ERR` aus, wenn die Regel schreibgeschützt ist, oder mit dem `code SYNTAX_ERR`, wenn der neue Wert nicht den CSS-Syntaxregeln entspricht.

`readonly CSS2Properties style`

Die Stilwerte, die für die in `selectorText` angegebenen Elemente gelten sollen. Beachten Sie, dass die `style`-Eigenschaft selbst zwar schreibgeschützt ist, die Eigenschaften des SS2Properties-Objekts, auf die sie sich bezieht, aber les- und schreibbar sind.

## Beschreibung

Ein CSSRule-Objekt repräsentiert eine Regel in einem CSS-Stylesheet: Es repräsentiert Stil-informationen, die auf einen speziellen Satz von Dokumentelementen angewendet werden. `selectorText` ist die String-Repräsentation des Elementselektors für diese Regel, und `style` ist ein CSS2Properties-Objekt, das den Satz von Stilattributen und -werten repräsentiert, der auf die ausgewählten Elemente angewandt werden soll.

Die DOM Level 2 CSS-Spezifikation definiert im Grunde eine etwas komplexe Hierarchie von CSSRule-Interfaces, um verschiedene Arten von Regeln darzustellen, die in einem CSSStyleSheet vorkommen können. Tatsächlich werden die hier aufgeführten Eigenschaften durch das DOM CSSStyleRule-Interface definiert. Style-Regeln stellen die häufigste und wichtigste Art von Regeln in einem Stylesheet dar. Die hier aufgeführten Eigenschaften sind die einzigen, die über alle Browser hinweg portabel verwendet werden können. IE unterstützt die DOM Level 2-Spezifikation nicht sehr gut (zumindest nicht über IE 7), implementiert aber ein CSSRule-Objekt, das die beiden hier aufgeführten Eigenschaften unterstützt.

## Siehe auch

`CSS2Properties`, `CSSStyleSheet`

## Name

CSSStyleSheet — Ein CSS-StylesheetDOM Level 2 CSS, IE 4Object → CSSStyleSheet

## Eigenschaften

`readonly CSSRule[] cssRules`

Ein schreibgeschütztes, Array-ähnliches Objekt, das die CSSRule-Objekte enthält, die das Stylesheet bilden. In IE verwenden Sie stattdessen die `rules`-Eigenschaft. In DOM-konformen Implementierungen enthält dieses Array Objekte, die alle Regeln eines Stylesheets repräsentieren, und zwar einschließlich @-Regeln wie z.B. `@import`-Anweisungen. Regeln dieser Art implementieren ein anderes Interface als das für CSSRule beschriebene. Diese anderen Typen von Regel-Objekten werden nur schlecht über Browsergrenzen hinweg unterstützt und sind in diesem Buch nicht dokumentiert. Achten Sie also darauf, dass Sie alle Einträge in diesem Array daraufhin testen, ob sie CSSRule-Eigenschaften repräsentieren, bevor Sie versuchen, diese Eigenschaften zu verwenden.

`boolean disabled`

Ist diese Eigenschaft `true`, so ist das Stylesheets deaktiviert und wird nicht auf das Dokument angewendet. Ist diese Eigenschaft `false`, so ist das Stylesheet aktiviert und wird auf das Dokument angewendet.

`readonly String href`

Die URL eines Stylesheet, das mit dem Dokument verknüpft ist, oder `null` bei Inline-Stylesheets.

`readonly StyleSheet parentStyleSheet`

Das Stylesheet, in dem dieses hier enthalten war, oder `null`, wenn dieses Stylesheet unmittelbar in das Dokument eingebunden war.

`readonly CSSRule[] rules`

Das IE-Äquivalent des DOM-Standard-`cssRules[]`-Arrays.

`readonly String title`

Der Titel des Stylesheets, sofern einer festgelegt ist. Ein Titel kann über das `title`-Attribut eines `<style>`\- oder `<link>`-Elements festgelegt werden, das sich auf dieses Stylesheet bezieht.

`readonly String type`

Der Typ dieses Stylesheets als MIME-Typ. CSS-Stylesheets haben den Typ »text/css«.

## Methoden

`addRule( )`

IE-spezifische Methode, um einem Stylesheet eine CSS-Regel hinzuzufügen.

`deleteRule( )`

DOM-Standardmethode zum Löschen der Regel an der angegebenen Position.

`insertRule( )`

DOM-Standardmethode zum Einfügen einer neuen Regel in das Stylesheet.

`removeRule( )`

IE-spezifische Methode zum Entfernen einer Regel.

## Beschreibung

Dieses Interface repräsentiert ein CSS-Stylesheet. Es hat Eigenschaften und Methoden zur Deaktivierung des Stylesheets sowie zum Abfragen, Einfügen und Entfernen von Style-Regeln. IE implementiert eine API, die etwas vom DOM-Standard abweicht. In IE verwenden Sie statt des `cssRules[]`-Arrays das `rules[]`-Array, und statt den DOM-Standard-Methoden `insertRule( )` und `deleteRule( )` verwenden Sie `addRule( )` und `removeRule( )`.

Die zu einem Dokument gehörenden CSSStyleSheet-Objekte sind Teil des `styleSheets[]`-Arrays des Document-Objekts. Im DOM-Standard wird außerdem verlangt (obwohl das zu der Zeit, als dieses Buch geschrieben wurde, nicht weitverbreitet implementiert war), dass jedes `<style>`\- oder `<link>`-Element bzw. jeder -ProcessingInstruction-Knoten, das bzw. die ein Stylesheet definiert oder mit ihm verknüpft ist, das CSSStyleSheet-Objekt über eine `sheet`-Eigenschaft verfügbar machen soll.

## Siehe auch

`CSSRule`, die `styleSheets]`-Eigenschaft des `Document`-Objekts; [Kapitel 16

## Name

CSSStyleSheet.addRule( ) — IE-spezifische Methode, um eine Regel in ein Stylesheet einzufügen.IE 4

## Überblick

    void addRule(String _selector_ ,
           String _stil_ ,
           integer _index_ )

### Argumente

 _`selector`_

Der CSS-Selektor für die Regel.

 _`stil`_

Die Styles, die auf Elemente angewendet werden, die mit dem _`selector`_ übereinstimmen. Der Style-String ist eine Abfolge von mit einem Semikolon getrennten Attribut:Wert-Paaren. Er beginnt und endet _nicht_ mit geschwungenen Klammern.

 _`index`_

Die Position im `rules`-Array, an der die Regel ein- oder angefügt werden soll. Wird dieses optionale Argument weggelassen, wird die neue Regel an das Regel-Array angefügt.

## Beschreibung

Diese Methode fügt eine neue CSS-Style-Regel an der angegebenen _`index`_ -Position des `rules`-Arrays dieses Stylesheets ein oder an. Sie ist eine IE-spezifische Alternative zur normalen `insertRule( )`-Methode. Beachten Sie, dass sich die Argumente zu dieser Methode von denen für `insertRule( )` unterscheiden.

## Name

CSSStyleSheet.deleteRule( ) — Löscht eine Regel aus einem StylesheetDOM Level 2 CSS

## Überblick

    void deleteRule(unsigned long _index_ )
      throws DOMException;

### Argumente

 _`index`_

Der Index der zu löschenden Regel im `cssRules`-Array.

### Exceptions

Diese Methode löst eine DOMException mit dem `code INDEX_SIZE_ERR` aus, wenn _`index`_ negativ oder größer oder gleich `cssRules.length` ist. Sie löst eine DOMException mit dem `code NO_MODIFICATION_ALLOWED_ERR` aus, wenn dieses Stylesheet schreibgeschützt ist.

## Beschreibung

Diese Methode löscht die Regel an der angegebenen _`index`_ -Position aus dem `cssRules`-Array. Dabei handelt es sich um eine DOM-Standard-Methode; eine IE-spezifische Alternative finden Sie unter `CSSStyleSheet.removeRule( )`.

## Name

CSSStyleSheet.insertRule( ) — Fügt eine Regel in ein Stylesheet einDOM Level 2 CSS

## Überblick

    unsigned long insertRule(String _regel_ ,
                 unsigned long _index_ )
      throws DOMException;

### Argumente

 _`regel`_

Die vollständige, parser-fertige Textdarstellung der Regel, die dem Stylesheet hinzugefügt werden soll. Bei Stilregeln gehören dazu auch der Elementselektor und die Stilinformationen.

 _`index`_

Die Position im `cssRules`-Array, an der die Regel ein- oder angefügt werden soll.

### Rückgabewert

Der Wert des _`index`_ -Arguments.

### Exceptions

Diese Methode löst unter folgenden Umständen eine DOMException mit einem der folgenden `code`-Werte aus:

`HIERARCHY_REQUEST_ERR`

Die CSS-Syntax gestattet an der angegebenen Stelle diese Regel nicht.

`INDEX_SIZE_ERR`

 _`index`_ ist negativ oder größer als `cssRules.length`.

`NO_MODIFICATION_ALLOWED_ERR`

Die Regel ist schreibgeschützt.

`SYNTAX_ERR`

In dem angegebenen _`regel`_ -Text ist ein Syntaxfehler.

## Beschreibung

Diese Methode fügt eine neue CSS- _`regel`_ an der angegebenen _`index`_ -Position des `cssRules`-Arrays dieses Stylesheets ein oder an. Dabei handelt es sich um eine DOM-Standard-Methode; eine IE-spezifische Alternative finden Sie unter `CSSStyleSheet.addRule( )`.

## Name

CSSStyleSheet.removeRule( ) — IE-spezifische Methode zum Entfernen einer CSS-Regel aus einem Stylesheet.IE 4

## Überblick

    void removeRule(integer _index_ )

### Argumente

 _`index`_

Der Index der zu löschenden Regel im `rules[]`-Array. Wird dieses optionale Argument weggelassen, wird die erste Regel im Array entfernt.

## Beschreibung

Diese Methode entfernt die CSS-Style-Regel am angegebenen _`index`_ des `rules`-Arrays dieses Stylesheets. Sie ist eine IE-spezifische Alternative zur normalen `deleteRule( )`-Methode.

## Name

Document — Ein HTML- oder XML-DokumentDOM Level 1 CoreNode → Document

## Subinterfaces

HTMLDocument

## Eigenschaften

`readonly Window defaultView`

Das Webbrowser-Window-Objekt (der »view« in der DOM-Sprache), in dem das Dokument angezeigt wird.

`readonly DocumentType doctype`

Gibt für XML-Dokumente mit einer `<!DOCTYPE>`-Deklaration einen DocumentType-Knoten an, der die DTD dieses Dokuments repräsentiert. Für HTML-Dokumente und für XML-Dokumente ohne `<!DOCTYPE>` ist diese Eigenschaft `null`.

`readonly Element documentElement`

Eine Referenz auf das Wurzelelement des Dokuments. Bei HTML-Dokumenten ist diese Eigenschaft immer das Element-Objekt, das das `<html>`-Tag darstellt. Dieses Wurzelelement kann auch über das aus Node geerbte `childNodes[]`-Array abgerufen werden. Vergleichen Sie dazu die `body`-Eigenschaft von HTMLDocument.

`readonly DOMImplementation implementation`

Das DOMImplementation-Objekt, das die Implementierung darstellt, mit der dieses Dokument erzeugt wurde.

`readonly CSSStyleSheet[] styleSheets`

Eine Sammlung von Objekten, die alle Stylesheets darstellt, die in ein Dokument eingebettet oder damit verknüpft sind. In HTML-Dokumenten gehören dazu auch Stylesheets, die mit den Tags `<link>` und `<style>` definiert sind.

## Methoden

`addEventListener( )`

Fügt dem Set von Event-Handlern für dieses Dokument eine Event-Handler-Funktion hinzu. Dies ist eine DOM-Standard-Methode, die von allen modernen Browsern außer IE unterstützt wird.

`attachEvent( )`

Fügt dem Set von Event-Handlern für dieses Dokument eine Event-Handler-Funktion hinzu. Sie ist die IE-spezifische Alternative zu `addEventListener( )`.

`createAttribute( )`

Erzeugt einen neuen Attr-Knoten mit dem angegebenen Namen.

`createAttributeNS( )`

Erzeugt einen neuen Attr-Knoten mit dem angegebenen Namen und Namensraum.

`createCDATASection( )`

Erzeugt einen neuen CDATASection-Knoten mit dem angegebenen Text.

`createComment( )`

Erzeugt einen neuen Kommentarknoten mit dem angegebenen String.

`createDocumentFragment( )`

Erzeugt einen neuen, leeren DocumentFragment-Knoten.

`createElement( )`

Erzeugt einen neuen Element-Knoten mit dem angegebenen Tag-Namen.

`createElementNS( )`

Erzeugt einen neuen Element-Knoten mit dem angegebenen Tag-Namen und Namensraum.

`createEvent( )`

Erzeugt ein neues, synthetisches Event-Objekt des angegebenen Typs.

`createExpression( )`

Erzeugt ein neues XPathExpression-Objekt, das eine kompilierte XPath-Abfrage repräsentiert. Eine IE-spezifische Alternative finden Sie unter `Node.selectNodes( )`.

`createProcessingInstruction( )`

Erzeugt einen neuen ProcessingInstruction-Knoten mit dem angegebenen Ziel und Datenstring.

`createRange( )`

Erzeugt ein neues Range-Objekt. Diese Methode gehört technisch gesehen zum DocumentRange-Interface und wird nur in solchen Implementierungen vom Document-Objekt implementiert, die auch das Range-Modul unterstützen.

`createTextNode( )`

Erzeugt einen neuen Text-Knoten, der den angegebenen Text darstellt.

`detachEvent( )`

Löscht eine Event-Handler-Funktion aus diesem Dokument. Sie ist die IE-spezifische Alternative zur normalen `removeEventListener( )`-Methode.

`dispatchEvent( )`

Übermittelt ein synthetisches Event an dieses Dokument.

`evaluate( )`

Wertet eine XPath-Abfrage anhand dieses Dokuments aus. Eine IE-spezifische Alternative finden Sie unter `Node.selectNodes( )`.

`getElementById( )`

Gibt ein Nachfahren-Element dieses Dokuments zurück. Das Element hat für sein `id`-Attribut den angegebenen Wert oder `null`, wenn im Dokument kein solches Element existiert.

`getElementsByTagName( )`

Gibt ein Array (technisch gesehen eine NodeList) aller Element-Knoten in diesem Dokument zurück, die den angegebenen Tag-Namen haben. Die Element-Knoten erscheinen im Rückgabe-Array in der Reihenfolge, in der sie auch im Dokumentquelltext stehen.

`getElementsByTagNameNS( )`

Gibt ein Array aller Element-Knoten zurück, die den angegebenen Tag-Namen und Namensraum haben.

`importNode( )`

Kopiert einen Knoten aus einem anderen Dokument, der sich dazu eignet, in dieses Dokument eingefügt zu werden.

`loadXML( )` _[IE only]_

Parst einen String mit XML-Markup und speichert das Ergebnis in diesem Document-Objekt.

`removeEventListener( )`

Entfernt eine Event-Handler-Funktion aus dem Satz der Handler dieses Dokuments. Dies ist eine DOM-Standard-Methode, die von allen modernen Browsern außer IE unterstützt wird.

## Beschreibung

Das Document-Interface ist der Wurzelknoten eines Dokumentbaums. Ein Document-Knoten kann mehrere Kinder haben, doch kann nur eines der Kinder ein Element-Knoten sein: Dies ist das Wurzelelement des Dokuments. Am einfachsten greifen Sie auf das Wurzelelement mit der Eigenschaft `documentElement` zu. Die Eigenschaften `doctype` und `implementation` geben Zugriff auf das DocumentType-Objekt (falls vorhanden) und das DOMImplementation-Objekt für dieses Dokument.

Die meisten vom Document-Interface definierten Methoden sind »Factory-Methoden«, die verschiedene Arten von Knoten erzeugen, die in dieses Dokument eingefügt werden können. Erwähnenswerte Ausnahmen davon sind die Methoden `getElementById()` und `getElementsByTagName()`, die ganz nützlich sind, wenn Sie ein spezielles Element oder eine Menge zusammenhängender Element-Knoten in einem Dokumentbaum finden möchten. Weitere Ausnahmen sind Event-Handler-Registrierungsmethoden, wie z.B. `addEventHandler( )`. Diese Event-bezogenen Methoden werden auch vom Element-Interface implementiert und sind dort im Einzelnen dokumentiert.

Meist erhalten Sie ein Document-Objekt über die `document`-Eigenschaft eines Window-Objekts. Sie können aber Document-Objekte auch über die `contentDocument`-Eigenschaft von Frame und IFrame erhalten oder über die `ownerDocument`-Eigenschaft eines beliebigen Knotens, der in ein Dokument aufgenommen wurde.

Beim Arbeiten mit XML (einschließlich XHTML) lassen sich neue Document-Objekte über die `createDocument( )`-Methode der DOMImplementation erzeugen:

    document.implementation.createDocument(namespaceURL, rootTagName, null);

In IE verwenden Sie stattdessen folgenden Code:

    new ActiveXObject("MSXML2.DOMDocument");

In Beispiel 21-1 finden Sie eine plattformübergreifende Hilfsfunktion, die ein neues Document-Objekt erzeugt.

Es ist auch möglich, eine XML-Datei aus dem Netz zu laden und sie in ein Document-Objekt zu parsen. Vergleichen Sie dazu die `responseXML`-Eigenschaft des XMLHttpRequest-Objekts. Sie können auch einen String mit XML-Markup in ein Document-Objekt parsen: Vergleichen Sie dazu `DOMParser.parseFromString( )` und das IE-spezifische `Document.loadXML( )`. (Beispiel 21-4 ist eine plattformübergreifende Hilfsfunktion, die diese Methoden zum Parsen von XML-Markup verwendet).

Weitere Eigenschaften und Methoden speziell für HTML-Dokumente finden Sie unter `HTMLDocument`.

## Siehe auch

`DOMImplementation`, `DOMParser`, `HTMLDocument`, `Window`, `XMLHttpRequest`; Kapitel 15

## Name

Document.addEventListener( ) — siehe Element.addEventListener( )

##

## Name

Document.attachEvent( ) — siehe Element.attachEvent( )

##

## Name

Document.createAttribute( ) — Erzeugt einen neuen Attr-KnotenDOM Level 1 Core

## Überblick

    Attr createAttribute(String _name_ )
      throws DOMException;

### Argumente

 _`name`_

Der Name des neu erzeugten Attributs.

### Rückgabewert

Ein neu erzeugter Attr-Knoten, dessen `nodeName`-Eigenschaft auf _`name`_ gesetzt ist.

### Exceptions

Diese Methode löst eine DOMException mit dem `code INVALID_CHARACTER_ERR` aus, wenn _`name`_ ein unzulässiges Zeichen enthält.

## Siehe auch

`Attr`, `Element.setAttribute( )`, `Element.setAttributeNode( )`

## Name

Document.createAttributeNS( ) — Erzeugt ein Attr mit einem Namen und NamensraumDOM Level 2 Core

## Überblick

    Attr createAttributeNS(String _namensraumURI_ ,
                           String _qualifizerterName_ )
      throws DOMException;

### Argumente

`namensraumURI`

Der eindeutige Bezeichner des Namensraums für das Attr oder `null`, wenn kein Namensraum vorhanden ist.

 _`qualifizierterName`_

Der qualifizierte Attributname; er sollte ein Namensraumpräfix, einen Doppelpunkt und einen lokalen Namen enthalten.

### Rückgabewert

Ein neu erzeugter Attr-Knoten mit dem angegebenen Namen und Namensraum.

### Exceptions

Diese Methode kann unter den folgenden Umständen eine DOMException mit einem der folgenden `code`-Werte auslösen:

`INVALID_CHARACTER_ERR`

 _`qualifizierterName`_ enthält ein unzulässiges Zeichen.

`NAMESPACE_ERR`

 _`qualifizierterName`_ ist nicht wohlgeformt, oder _`qualifizierterName`_ und _`namensraumURI`_ passen nicht zueinander.

`NOT_SUPPORTED_ERR`

Die Implementierung unterstützt keine XML-Dokumente und implementiert daher auch nicht diese Methode.

## Beschreibung

`createAttributeNS()` ist genau wie `createAttribute()`, nur dass der erzeugte Attr-Knoten nicht nur einen Namen, sondern zusätzlich auch einen Namensraum hat. Diese Methode kann nur für XML-Dokumente eingesetzt werden, die Namensräume verwenden.

## Name

Document.createCDATASection( ) — Erzeugt einen neuen CDATASection-KnotenDOM Level 1 Core

## Überblick

    CDATASection createCDATASection(String _daten_ )
      throws DOMException;

### Argumente

 _`daten`_

Der Text der zu erzeugenden CDATASection.

### Rückgabewert

Ein neu erzeugter CDATASection-Knoten, der die angegebenen _`daten`_ enthält.

### Exceptions

Wenn das Dokument ein HTML-Dokument ist, löst diese Methode eine DOMException mit dem `code NOT_SUPPORTED_ERR` aus, da HTML-Dokumente keine CDATASection-Knoten gestatten.

## Name

Document.createComment( ) — Erzeugt einen neuen KommentarknotenDOM Level 1 Core

## Überblick

    Comment createComment(String _daten_ );

### Argumente

 _`daten`_

Der Text des zu erzeugenden Kommentarknotens.

### Rückgabewert

Ein neu erzeugter Kommentarknoten, der als Text die angegebenen _`daten`_ enthält.

## Name

Document.createDocumentFragment( ) — Erzeugt einen neuen, leeren DocumentFragment-KnotenDOM Level 1 Core

## Überblick

    DocumentFragment createDocumentFragment( );

### Rückgabewert

Ein neu erzeugter DocumentFragment-Knoten ohne Kinder.

## Name

Document.createElement( ) — Erzeugt einen neuen Element-KnotenDOM Level 1 Core

## Überblick

    Element createElement(String _tagName_ )
      throws DOMException;

### Argumente

 _`tagName`_

Der Tag-Name des zu erzeugenden Elements. Da HTML-Tags nicht zwischen Groß- und Kleinschreibung unterscheiden, können Sie für HTML-Tag-Namen nach Belieben zwischen Groß- und Kleinschreibung wählen. XML-Tag-Namen unterscheiden zwischen Groß- und Kleinschreibung.

### Rückgabewert

Ein neu erzeugter Element-Knoten mit dem angegebenen Tag-Namen.

### Exceptions

Diese Methode löst eine DOMException mit dem `code INVALID_CHARACTER_ERR` aus, wenn _`tagName`_ ein unzulässiges Zeichen enthält.

## Name

Document.createElementNS( ) — Erzeugt einen neuen Element-Knoten mit NamensraumDOM Level 2 Core

## Überblick

    Element createElementNS(String _namensraumURI_ ,
                            String _qualifizierterName_ )
       throws DOMException;

### Argumente

 _`namensraumURI`_

Der eindeutige Bezeichner für den Namensraum des neuen Elements oder `null`, wenn kein Namensraum vorhanden ist.

 _`qualifizierterName`_

Der qualifizierte Name des neuen Elements. Er sollte ein Namensraumpräfix, einen Doppelpunkt und einen lokalen Namen enthalten.

### Rückgabewert

Ein neu erzeugter Element-Knoten mit dem angegebenen Tag-Namen und Namensraum.

### Exceptions

Diese Methode kann unter den folgenden Umständen eine DOMException mit einem der folgenden `code`-Werte auslösen:

`INVALID_CHARACTER_ERR`

 _`qualifizierterName`_ enthält ein unzulässiges Zeichen.

`NAMESPACE_ERR`

 _`qualifizierterName`_ ist nicht wohlgeformt, oder _`qualifizierterName`_ und _`namensraumURI`_ passen nicht zueinander.

`NOT_SUPPORTED_ERR`

Die Implementierung unterstützt keine XML-Dokumente und implementiert daher auch nicht diese Methode.

## Beschreibung

`createAttributeNS()` ist genau wie `createAttribute()`, nur dass der erzeugte Attr-Knoten nicht nur einen Namen, sondern zusätzlich auch einen Namensraum hat. Diese Methode eignet sich nur für XML-Dokumente, die Namensräume verwenden.

## Name

Document.createEvent( ) — Erzeugt ein Event-ObjektDOM Level 2 Events

## Überblick

    Event createEvent(String _eventTyp_ )
      throws DOMException

### Argumente

 _`eventTyp`_

Der Name des Event-Moduls, für das ein Event-Objekt gewünscht wird. Gültige Event-Typen werden in der Beschreibung aufgeführt.

### Rückgabewert

Ein neu erzeugtes Event-Objekt des angegebenen Typs.

### Exceptions

Diese Methode löst eine DOMException mit dem `code NOT_SUPPORTED_ERR` aus, wenn die Implementierung keine Events des angeforderten Typs unterstützt.

## Beschreibung

Diese Methode erzeugt ein neues Event-Objekt des Typs, der im _`eventTyp`_ -Argument angegeben ist. Beachten Sie, dass der Wert dieses Arguments nicht der Name des zu erzeugenden Event-Interfaces (im Singular), sondern der Name des DOM-Moduls (im Plural) sein soll, das dieses Interface definiert. Die folgende Tabelle zeigt, welche Werte für _`eventTyp`_ zulässig sind und welches Event-Interface jeder Wert erzeugt.

eventType-Argument | Event-Interface | Initialisierungsmethode

---|---|---

HTMLEvents | Event | `initEvent( )`

MouseEvents | MouseEvent | `initMouseEvent( )`

UIEvents | UIEvent | `initUIEvent( )`

Nachdem Sie ein Event-Objekt mit dieser Methode erzeugt haben, müssen Sie es mit der in der Tabelle angegebenen Initialisierungsmethode initialisieren. Einzelheiten zur Initialisierungsmethode entnehmen Sie bitte der Referenz zu dem jeweiligen Event-Interface.

Diese Methode wird eigentlich nicht im Document-Interface, sondern im DOM-DocumentEvent-Interface definiert. Wenn eine Implementierung das Events-Modul unterstützt, implementiert das Document-Objekt immer das DocumentEvent-Interface und unterstützt somit diese Methode. Beachten Sie, dass Internet Explorer das DOM Events-Modul nicht unterstützt.

## Siehe auch

`Event`, `MouseEvent`, `UIEvent`

## Name

Document.createExpression( ) — Erzeugt einen XPath-Ausdruck zur späteren AuswertungFirefox 1.0, Safari 2.01, Opera 9

## Überblick

    XPathExpression createExpression(String _xpathText_ ,
                                     Function _namensraumURLMapper_ )
      throws XPathException

### Argumente

 _`xpathText`_

Der String, der den zu kompilierenden XPath-Ausdruck darstellt.

 _`namensraumURLMapper`_

Eine Funktion, die ein Namensraumpräfix auf eine vollständige Namensraum-URL abbildet oder `null` zurückgibt, wenn keine solche Zuordnung erforderlich ist.

### Rückgabewert

Ein XPathExpression-Objekt.

### Exceptions

Diese Methode löst eine Exception aus, wenn der _`xpathText`_ einen Syntaxfehler enthält oder wenn er ein Namensraumpräfix verwendet, das nicht mit _`namensraumURLMapper`_ aufgelöst werden kann.

## Beschreibung

Diese Methode konvertiert eine Stringrepräsentation eines XPath-Ausdrucks in eine kompilierte Form, eine `XPathExpression`. Zusätzlich zum Ausdruck nimmt diese Methode eine Funktion im Format `function(praefix)`, die einen Namensraumpräfix-String interpretiert und ihn als vollständigen Namensraum-URL-String zurückgibt.

Internet Explorer unterstützt diese API nicht. Eine IE-spezifische Alternative finden Sie unter `Node.selectNodes( )`.

## Siehe auch

`Document.evaluate( )`, `Node.selectNodes( )`, `XPathExpression`, `XPathResult`

## Name

Document.createProcessingInstruction( ) — Erzeugt einen ProcessingInstruction-KnotenDOM Level 1 Core

## Überblick

    ProcessingInstruction createProcessingInstruction(String _ziel_ ,
                                                      String _daten_ )
       throws DOMException;

### Argumente

 _`ziel`_

Das Ziel der Verarbeitungsanweisung.

 _`daten`_

Der Textinhalt der Verarbeitungsanweisung.

### Rückgabewert

Ein neu erzeugter ProcessingInstruction-Knoten.

### Exceptions

Diese Methode kann unter den folgenden Umständen eine DOMException mit einem der folgenden `code`-Werte auslösen:

`INVALID_CHARACTER_ERR`

Das angegebene _`ziel`_ enthält ein unzulässiges Zeichen.

`NOT_SUPPORTED_ERR`

Dies ist ein HTML-Dokument und unterstützt keine Verarbeitungsanweisungen.

## Name

Document.createRange( ) — Erzeugt ein Range-ObjektDOM Level 2 Range

## Überblick

    Range createRange( );

### Rückgabewert

Ein neu erzeugtes Range-Objekt. Beide Begrenzungspunkte sind auf den Dokumentenanfang gesetzt.

## Beschreibung

Diese Methode erzeugt ein Range-Objekt, mit dem Sie einen Bereich dieses Dokuments oder eines DocumentFragment aus ihm darstellen können.

Diese Methode wird eigentlich nicht im Document-Interface, sondern im DocumentRange-Interface definiert. Wenn eine Implementierung das Range-Modul unterstützt, implementiert das Document-Objekt immer das DocumentRange-Interface und unterstützt somit diese Methode. Internet Explorer 6 unterstützt dieses Modul nicht.

## Siehe auch

`Range`

## Name

Document.createTextNode( ) — Erzeugt einen neuen Text-KnotenDOM Level 1 Core

## Überblick

    Text createTextNode(String _daten_ );

### Argumente

 _`daten`_

Der Inhalt des Text-Knotens.

### Rückgabewert

Ein neu erzeugter Text-Knoten, der den angegebenen _`daten`_ -String repräsentiert.

## Name

Document.detachEvent( ) — siehe Element.detachEvent( )

##

## Name

Document.dispatchEvent( ) — siehe Element.dispatchEvent( )

##

## Name

Document.evaluate( ) — Wertet einen XPath-Ausdruck ausFirefox 1.0, Safari 2.01, Opera 9

## Überblick

    XPathResult evaluate(String _xpathText_ ,
                         Node _kontextKnoten_ ,
                         Function _namensraumURLMapper_ ,
                         short _ergebnisTyp_ ,
                         XPathResult _ergebnis_ )
      throws DOMException, XPathException

### Argumente

 _`xpathText`_

Der String, der den auszuwertenden XPath-Ausdruck darstellt.

 _`kontextKnoten`_

Der Knoten in diesem Dokument, auf dem der Ausdruck ausgewertet wird.

 _`namensraumURLMapper`_

Eine Funktion, die ein Namensraumpräfix auf eine vollständige Namensraum-URL abbildet oder `null` zurückgibt, wenn keine solche Zuordnung erforderlich ist.

 _`ergebnisTyp`_

Gibt den als Ergebnis erwarteten Objekttyp an und verwendet XPath-Konvertierungen, um das gewünschte Ergebnis zu erzwingen. Gültige Werte für `ergebnisTyp` sind die durch das XPathResult-Objekt definierten Konstanten.

 _`ergebnis`_

Ein wiederzuverwendendes XPathResult-Objekt oder `null`, wenn ein neues XPathResult-Objekt erzeugt werden soll.

### Rückgabewert

Ein XPathResult-Objekt, das die Auswertung des Ausdrucks auf dem gegebenen Kontextknoten repräsentiert.

### Exceptions

Diese Methode kann eine Exception auslösen, wenn der _`xpathText`_ einen Syntaxfehler enthält, wenn das Ergebnis des Ausdrucks nicht in den gewünschten _`ergebnisTyp`_ konvertiert werden kann, wenn der Ausdruck Namensräume enthält, die _`namensraumURLMapper`_ nicht auflösen kann, oder wenn _`kontextKnoten`_ vom falschen Typ oder nicht mit diesem Dokument verknüpft ist.

## Beschreibung

Diese Methode wertet den angegebenen XPath-Ausdruck für den angegebenen Kontext-Kno
[cell cut: 2391452 characters, first 2000000 kept]

redpajama_set_name: RedPajamaBook