Java Script 2025
Java Script 2025
Programmieren
mit JavaScript
Die vielseitige Sprache für
Webentwicklung & mehr –
Grundlagen und fort-
geschrittene Techniken
Programmieren mit JavaScript
Ralph Steyer
Programmieren mit
JavaScript
Die vielseitige Sprache für
Webentwicklung & mehr – Grundlagen
und fortgeschrittene Techniken
Ralph Steyer
Bodenheim, Rheinland-Pfalz, Deutschland
Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detail-
lierte bibliografische Daten sind im Internet über [Link] abrufbar.
© Der/die Herausgeber bzw. der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH,
ein Teil von Springer Nature 2025
Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Jede Verwertung, die nicht
ausdrücklich vom Urheberrechtsgesetz zugelassen ist, bedarf der vorherigen Zustimmung des Verlags.
Das gilt insbesondere für Vervielfältigungen, Bearbeitungen, Übersetzungen, Mikroverfilmungen und die
Einspeicherung und Verarbeitung in elektronischen Systemen.
Die Wiedergabe von allgemein beschreibenden Bezeichnungen, Marken, Unternehmensnamen etc. in diesem
Werk bedeutet nicht, dass diese frei durch jede Person benutzt werden dürfen. Die Berechtigung zur Benutzung
unterliegt, auch ohne gesonderten Hinweis hierzu, den Regeln des Markenrechts. Die Rechte des/der jeweiligen
Zeicheninhaber*in sind zu beachten.
Der Verlag, die Autor*innen und die Herausgeber*innen gehen davon aus, dass die Angaben und Informationen
in diesem Werk zum Zeitpunkt der Veröffentlichung vollständig und korrekt sind. Weder der Verlag noch die
Autor*innen oder die Herausgeber*innen übernehmen, ausdrücklich oder implizit, Gewähr für den Inhalt des
Werkes, etwaige Fehler oder Äußerungen. Der Verlag bleibt im Hinblick auf geografische Zuordnungen und
Gebietsbezeichnungen in veröffentlichten Karten und Institutionsadressen neutral.
Wenn Sie dieses Produkt entsorgen, geben Sie das Papier bitte zum Recycling.
Vorwort
JavaScript! Eine Skript- bzw. Programmiersprache, die sich längst etabliert hat. Lange
Zeit wurde sie eher belächelt und nicht wirklich ernst genommen. In den letzten Jahren
hat sich das massiv geändert. Aus dem hässlichen Entlein ist ein stolzer Schwan gewor-
den. JavaScript schafft es jetzt schon viele Jahre in fast allen Statistiken zu den meistge-
nutzten bzw. wichtigsten Programmiersprachen weltweit auf das Podium.
Dazu trägt sicher bei, dass man mit JavaScript (oder kurz JS) nicht nur Leben, Dyna-
mik und Aktivität in Webpräsentationen bringen kann, wie es am Anfang der Geschichte
von JavaScript und auch die ersten Jahre danach der Fall war. JavaScript hat längst das
Korsett eines Browsers, wo es die ideale Ergänzung zu HTML (Hyper Text Markup Lan-
guage) und Style Sheets darstellt, verlassen. Dennoch ist ein Webbrowser immer noch
das „natürliche Habitat“ von JavaScript. Das ursprüngliche Ziel von Netscape (Erfinder
von JavaScript), Webseiten zu dynamisieren, den Browser zu steuern und Aktivität vom
Webserver auf den Clientrechner zu verlagern, wurde nie aufgegeben oder eingeschränkt
und so gut wie keine Webseiten im Internet kommt ohne JavaScript aus. Im Gegenteil –
im Web übernimmt JavaScript als einzig relevante Möglichkeit zum Programmieren im
Browser immer mehr neue Aufgaben. Sogenannte Rich Internet Applications (RIAs) mit
„reichhaltigen“ Möglichkeiten, die Webapplikationen auf eine Stufe wie Desktopappli-
kationen stellen, basieren im Client wesentlich auf JavaScript. Dies umfasst sowohl viele
optische als auch funktionale Features wie animierte Inhaltsaufbereitung, das dynami-
sche Nachladen von Daten oder komfortable Benutzereingabemöglichkeiten. Auch wenn
dies oft HTML5 zugeordnet wird. HTML5 bietet vielfach nur in Verbindung mit Java-
Script echten Nutzen oder ist sogar bei einigen Features in Wirklichkeit nur JavaScript
selbst.
Die Einsatzgebiete von JavaScript haben sich aber parallel ebenfalls jenseits des
Browsers massiv erweitert. So gibt es – um erst einmal im Web zu bleiben – Webserver,
die auf JavaScript als zentrale Technik zur serverseitigen Programmierung setzen. Das
schafft einmal die Situation, dass sowohl im Client (Browser) als auch Server (Webser-
ver) die gleiche Programmiertechnologie verwendet wird. Das erleichtert viele Dinge.
Zudem hat sich gezeigt, dass man auf Serverseite durch JavaScript sowohl in der Ent-
wicklungszeit als auch der Performance im laufenden Betrieb riesige Vorteile erreichen
V
VI Vorwort
kann, die vor wenigen Jahren noch undenkbar gewesen waren. Auch in mächtige Spra-
chen wie Java bzw. JavaFX/FXML wird JavaScript mehr und mehr als Ergänzung aus-
drücklich integriert und ebenso bei der Steuerung von Micro-Controllern setzt man ver-
stärkt auf JavaScript.
JavaScript bietet uns also so viele interessante Möglichkeiten, dass eine Beschäfti-
gung damit äußerst nützlich und vor allem spannend und unterhaltsam ist. Sie können
dabei JavaScripts erstellen, ohne ein einziges Programm kaufen zu müssen. Zudem ist
JavaScript einfach zu lernen und vor allem unabhängig von einer speziellen Plattform.
Ob Sie Linux, Windows, macOS oder ein anderes Betriebssystem verwenden – Java-
Script wird auf nahezu allen Plattformen zu finden sein. Sie können unter der einen Platt-
form entwickeln und Anwender können eine beliebige andere Plattform verwenden.
Um mit einer persönlichen Bemerkung zum Abschluss des Vorworts zu kommen.
Ich nutze professionell gut ein Dutzend Programmiersprachen und habe vor allen Din-
gen sehr viel mit Java und C# zu tun. JavaScript, was ich seit dessen erstem Auftauchen
schon verwende, ist aber klar meine Lieblingssprache, der im Moment höchstens Python
halbwegs Konkurrenz machen kann. Und noch etwas – Programmieren mit JavaScript
macht einfach Spaß.
Kontact: [Link] und [Link]
1 Einleitung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1 Das Ziel des Buchs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 An wen sich das Buch wendet. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Voraussetzungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3.1 Hardware. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3.2 Die Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Erste Beispiele – Der Sprung ins kalte Wasser. . . . . . . . . . . . . . . . . . . . . . . . 11
2.1 Der Interaktivmodus – die Kommandozeile bei JavaScript. . . . . . . . . . . 11
2.2 Anweisungen in (echten) Quelltext auslagern. . . . . . . . . . . . . . . . . . . . . 13
2.3 Ein einfaches Mitteilungsfenster. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.4 Schreiben in die Webseite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.5 Entgegennahme einer Benutzereingabe. . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.6 Besonderheit bei der Webprogrammierung. . . . . . . . . . . . . . . . . . . . . . . 19
3 Versionen von JavaScript und Einbindung in Webseiten. . . . . . . . . . . . . . . 23
3.1 Versionszyklen von JavaScript, ECMAScript & Co. . . . . . . . . . . . . . . . 24
3.2 JavaScript in Webseiten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2.1 Die Inline-Referenz. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.2.2 Notation eines Skript-Containers in der Webseite. . . . . . . . . . . 26
3.2.3 Verwendung von externen JavaScript-Dateien . . . . . . . . . . . . . 29
3.3 Der gemeinsame Namensraum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.4 Kann man testen, ob bei einem Browser JavaScript aktiviert ist?. . . . . . 33
3.4.1 Eine Browserweiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4 Elementare JavaScript-Grundstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.1 Token und Parser. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.1.1 Zerlegen von Quelltext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.2 Kommentare. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.3 Schlüsselwörter und definierte Literale in JavaScript. . . . . . . . . . . . . . . 39
4.4 Bezeichner und Namenskonventionen. . . . . . . . . . . . . . . . . . . . . . . . . . . 40
VII
VIII Inhaltsverzeichnis
6.1.7 Textindizes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
6.2 Die Objekt-Notation – JSON. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
6.2.1 Der Datentyp bei JSON. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.2.2 Zugriff auf Elemente in einem JSON-Objekt . . . . . . . . . . . . . . 100
6.2.3 Verschachtelte JSON-Arrays. . . . . . . . . . . . . . . . . . . . . . . . . . . 101
6.3 Iteration über Arrays und JSON. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
6.3.1 Zugriff auf numerisch indizierte Arrays . . . . . . . . . . . . . . . . . . 103
6.3.2 Zugriff auf alle Elemente eines Arrays über for-in
und for-of sowie forEach. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
6.3.3 Über verschachtele Strukturen iterieren . . . . . . . . . . . . . . . . . . 107
6.4 Weitere iterative Datenstrukturen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.4.1 Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.4.2 Sets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.4.3 Weitere Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
7 Funktionen, Prozeduren und Methoden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
7.1 Was ist allgemein ein Unterprogramm?. . . . . . . . . . . . . . . . . . . . . . . . . . 117
7.2 Die Deklaration eigener Funktionen in JavaScript . . . . . . . . . . . . . . . . . 118
7.3 Aufruf einer Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
7.4 Lokale Variablen in Funktionen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
7.5 Besonderheiten bei JavaScript im Zusammenhang mit Funktionen . . . . 122
7.5.1 Funktionen redefinieren – wer zuletzt kommt. . . . . . . . . . . . . . 123
7.5.2 Funktionen mit Parametern. . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
7.5.3 Funktionsreferenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
7.5.4 Benannte versus anonyme Funktionen . . . . . . . . . . . . . . . . . . . 131
7.5.5 Hoisting bei Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
7.5.6 Innere Funktionen – Closures. . . . . . . . . . . . . . . . . . . . . . . . . . 133
7.5.7 Lambda-Ausdrücke. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
7.5.8 Callbacks. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
7.5.9 JavaScript-Generatoren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
7.5.10 Weitere Möglichkeiten für die Deklaration von Funktionen. . . 138
7.5.11 Rekursive Funktionsaufrufe. . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
7.6 Vordefinierte Funktionen in JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . 145
8 Module. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
8.1 Was sind Module?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
8.2 CommonJS. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
8.3 ES6-Module. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
8.4 Module in Browsern. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
9 Objekte und JavaScript. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
9.1 Grundlagen der OOP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
9.1.1 Was sind Objekte?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
X Inhaltsverzeichnis
Stichwortverzeichnis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
Einleitung
1
Übersicht
Bevor es mit JavaScript richtig losgeht, sollen in diesem einleitenden Kapitel ei-
nige Dinge geklärt werden, die Ihnen die folgende Arbeit mit diesem Buch und der
Sprache im Allgemeinen erleichtern werden. Insbesondere sorgen wir an der Stelle
dafür, dass Ihnen die Voraussetzungen zum Programmieren und Ausführen von Ja-
vaScript überhaupt zur Verfügung steht.
JavaScript wurde ursprünglich im Jahr 1995 von Netscape für dynamisches HTML in
Webbrowsern vorgestellt. Ursprünglich LiveScript genannt, wurde der Name in Ko-
operation mit Sun geändert, um eine gemeinsame Marketingstrategie mit dessen damals
fast gleichzeitig veröffentlichter Sprache Java fahren zu können.
Die Bedeutung von JavaScript, die schon aktuell größer als je in der Geschichte des
Webs ist, wächst aufgrund der immer stärkeren Verbreitung von Rich Internet Applica-
tions, mobilen Apps auf Basis von Webtechnologien, serverseitigem Scripting und di-
versen weiteren Zielgebieten. Nicht zuletzt hat HTML5 die Bedeutung von JavaScript
noch einmal gesteigert und die Anforderungen an JavaScripts wachsen. Waren es frü-
her hauptsächlich eher einfache, kleine Skripte, die eine Webseite ein bisschen ergänzt
haben, sind aktuelle JavaScripts in der Regel tragende Bestandteile der RIAs oder server-
seitiger Applikationen. Damit muss man in solchen Applikationen richtig professionell
mit JavaScript programmieren. Moderne JavaScript-Applikationen werden auf dem glei-
chen Niveau konzipiert und erstellt wie Desktop-Applikationen mit .NET oder verteilte
Client-Server-Applikationen – teilweise noch viel professioneller durch das heterogene
Umfeld, in denen diese RIAs funktionieren müssen, sowie die mangelnden Fangzäune
in der Sprache selbst, die Programmierer in Technologien wie .NET oder Java absichern.
Bei JavaScript wird deshalb mehr die Qualität des Programmierers selbst gefordert. Java-
Script schafft den Spagat zwischen einer einfachen Einsteigerlösung und – mittlerweile –
einem hochprofessionellen, wenngleich für Programmierer aus strengen Sprachen etwas
eigenwilligen, Programmierumfeld.
Gelegentlich nutzt man statt dem Begriff „Webseite“ auch „Website“. Es stellt sich die Frage, ob das
identisch ist? Die Antwort ist aber nicht ganz einfach und nicht streng festgelegt. Der Unterschied
kann einmal rein auf die Sprachpräferenz und regionale Konventionen reduziert werden. Es sind dann
synonyme Begriffe. Jedoch wird teils „Website“ auch verwendet, um auf eine ganze Sammlung von
Webseiten zu verweisen, die unter einer bestimmten Domain oder Subdomain gruppiert sind. Der Be-
griff steht also für die gesamte Präsenz im Internet, einschließlich aller Seiten, die unter einer be-
stimmten Domain oder Subdomain gehostet werden. „Webseite“ wird dann jedoch manchmal spezi-
fischer für eine einzelne Seite innerhalb einer Website verwendet. Also etwa eine HTML-Datei.
Dieses Buch ist ein Praxisbuch für den ambitionierten Einstieg in JavaScript und eng
verwandte Technologien sowie einige ergänzende Techniken. Es soll Ihnen sowohl beim
Selbststudium helfen als auch Basis dafür sein, in entsprechenden Kursen und Seminaren
JavaScript zu lernen.
Wir erstellen im Laufe des Buchs immer wieder praktische Beispiele. Die Quell-
codes des Buchs finden Sie nach Kapiteln und teils darin erstellten Projekten
sortiert auf den Webseiten des Verlags bzw. in einem Repository auf GitHub.
Die Namen der jeweilig aktuellen Dateien beziehungsweise Projekte werden
als Hinweise oder direkt im Text vor den jeweiligen Beispielen angegeben und
bei Bedarf wiederholt. Ich empfehle allerdings, dass Sie die Beispiele allesamt
von Hand selbst erstellen. Das ist für Verständnis und Lernen eindeutig besser
als ein reines Kopieren oder nur Anschauen.
Das Buch wendet sich im Wesentlichen an Leser, die von Grund auf den sicheren Um-
gang im großen Umfeld der Programmierung mit JavaScript lernen wollen. Dies umfasst
neben JavaScript Aspekte der allgemeinen Webprogrammierung sowie Grundlagen der
Webserver-Programmierung. Dabei sind als Zielgruppe dieses Werks auch Webseiteners-
teller gedacht, die JavaScript als Ergänzung zur „normalen“ Arbeit betrachten und ein
vollständiges Webprojekt (nicht nur reine HTML-Seiten) realisieren wollen. Dabei soll
1.3 Voraussetzungen 3
der Umgang mit JavaScript und ergänzenden Technologien von Anfang an erklärt wer-
den. Allerdings werden wir nicht auf einem zu niedrigen Level aufsetzen und auch bis
zu fortgeschrittenen JavaScript-Techniken kommen. Von daher haben wir insbesondere
in den letzten Kapiteln schon den Anspruch, richtig anspruchsvolle Techniken kennenzu-
lernen und beherrschen. Wir beginnen jedoch sehr langsam und grundlegend.
Diese Beschreibung der Zielgruppe soll bedeuten, Sie benötigen nicht unbedingt
Vorwissen im Bereich der Webprogrammierung und auch der Programmierung im All-
gemeinen. Allerdings erleichtert es den Umgang mit dem Buch, wenn Sie schon einmal
ein Programm oder Skript geschrieben haben (egal in welcher Programmiersprache) oder
zumindest wissen, was Programmierung ist. Gerade in den späteren Kapiteln ist das von
Vorteil. Was ich bei einem Leser bzw. einer Leserin voraussetzen möchte, ist Folgendes:
• Sie haben einen Rechner zur Verfügung und können ihn einigermaßen sicher be-
dienen. Das bedeutet, Sie kennen sich in Grundzügen mit Ihrem Betriebssystem aus.
• Sie sollten Zugang zum Internet haben und mit dem Internet zurechtkommen.
• Es ist sinnvoll, wenn Sie bereits Webseiten erstellt haben. Nicht notwendigerweise
mit CSS oder Skripten, aber zumindest sollte das Thema in Grundzügen bekannt sein.
Sie brauchen keinesfalls ein HTML-Experte zu sein, da dieses Thema für JavaScript
nicht in jedem Detail bekannt sein muss. Aber grundsätzlich sollten Ihnen die Idee
und das Konzept von HTML vertraut sein.
• Es ist wie gesagt hilfreich, wenn Sie bereits Programme oder Skripte erstellt haben,
aber es ist nicht zwingend notwendig. Allerdings ist ein professioneller Programmier-
hintergrund sicher kein Nachteil. Sollten Sie noch nie programmiert haben, können
Sie das meines Erachtens anhand von JavaScript hervorragend lernen. Sie müssen
sich nur etwas anstrengen und aufpassen, dass Sie sich ob der wenig strengen Re-
geln in JavaScript nicht gleich von Anfang an einen unsauberen Programmier-
stil angewöhnen. Ich gebe mir Mühe, Sie an einen sauberen und professionellen
Programmierstil heranzuführen und Sie davon abzuhalten, die konzeptionellen
Schwächen von JavaScript für eine schlampige Programmierung auszunutzen.
1.3 Voraussetzungen
Es gibt ein paar Dinge, die Sie unbedingt brauchen, wenn Sie erfolgreich Programmie-
rung mit JavaScript lernen und in der Praxis anwenden wollen. Aber keine Angst – viel
ist das nicht und kosten tut es auch wenig bis gar nichts.
1.3.1 Hardware
Natürlich ist ein Computer mit Internetzugang notwendig. Meist wird dabei ein PC mit
Intel-kompatibler Architektur die bevorzugte Wahl sein und darauf werden wir uns bei
4 1 Einleitung
Ausführungen beschränken. Die Fraktion der Apple-Anwender soll natürlich nicht ver-
gessen werden, denn im Web ist sie (wie allgemein im medialen Umfeld) ziemlich stark
vertreten. Aber grundsätzlich laufen JavaScripts auf allen wichtigen Computerplatt-
formen, weshalb wir hier keine echten Besonderheiten beachten müssen. Ihr Computer
braucht auch nicht sonderlich leistungsfähig zu sein.
Die Erstellung von JavaScripts erfordert im Grunde außer einem beliebigen Editor und
einem Browser zum Testen keine weitere Software. Gleichwohl ist eine Reihe von Pro-
grammen sinnvoll bzw. nützlich. Schauen wir das etwas genauer an.
[Link] Das Betriebssystem
Die im Rahmen des Buchs zum Einsatz kommenden Technologien sind – wie fast alle
wichtigen Internettechnologien – plattformneutral. Sie lassen sich unter jedem moder-
nen Betriebssystem nutzen und auch die Entwicklung von Webseiten sowie JavaScript-
Applikationen kann man auf verschiedensten Plattformen durchführen. Für die meisten
Leser werden sich eines der folgenden drei Betriebssysteme anbieten. Dies sind Linux,
Windows oder macOS. Nutzen Sie das Betriebssystem, das Ihnen am besten passt und
nicht zu alt ist. Eine Hürde könnte es darstellen, wenn Sie keine Rechte zur Installation
von Software haben. Das ist bei Firmenrechnern oft der Fall. Um bestimmte Programme
wie einen Webserver oder [Link] verwenden zu können, müssen Sie aber vermutlich
Installationsrechte haben.
Diese gestatten eine Programmierung und Ausführung von Applikation aus einer inte-
grierten, gemeinsamen Oberfläche heraus. Microsoft stellt etwa mit Visual Studio eine
sehr mächtige IDE zur Verfügung, die in der Community-Version kostenlos ist (https://
[Link]/germany) und auch für die Webprogrammierung verwendet wer-
den kann. Andere IDEs für Webentwicklung basieren auf der mächtigen Entwicklungs-
umgebung Eclipse ([Link] Es gibt zudem noch eine ganze Reihe
weiterer IDEs, die Sie gerne verwenden können. Allerdings sollten Sie beachten, dass der
Umgang mit diesen umfassenden IDEs oft nicht ganz trivial ist und sie meist sehr „über-
laden“ daherkommen. Zudem fordern sie oft nicht unerhebliche Ressourcen von der
Hardware. Visual Studio Code oder kurz VS Code ([Link] was
eine leichtere, abgespeckte Version von Visual Studio darstellt und eigentlich näher an
einem Editor denn einer IDE ist, ist meines Erachtens eine perfekte Wahl zur Program-
mierung mit JavaScript und anderen Webtechnologien. VS Code steht für verschiedene
Betriebssysteme bereit und bietet Codevervollständigung, Syntax Highlighting, einen
Navigationsbaum etc. VS Code wird im Folgenden das Referenzsystem darstellen.
Hintergrundinformation
Eine JavaScript-Engine ist vereinfacht ausgedrückt ein Programm, das JavaScript-Code a usführen
kann. Sie besteht in der heutigen Zeit in der Regel aus einem Compiler, der den JavaScript-Code bei
der Ausführung in Maschinencode übersetzt, und einem Laufzeitsystem, das den übersetzten Code
ausführt und die erforderlichen Umgebungen bereitstellt, um den Code auszuführen.
Einige bekannte JavaScript-Engines sind das:
• V8 wurde von Google entwickelt und ist eine der am häufigsten verwendeten JavaScript-
Engines. Das Programm wird in Google Chrome und dem [Link]-Laufzeitumfeld verwendet.
• SpiderMonkey ist die JavaScript-Engine, die von Mozilla entwickelt wurde und in Firefox ver-
wendet wird. SpiderMonkey stellte einen der ersten JavaScript-Compiler bereit und hat seitdem
viele Optimierungen erfahren.
• JavaScriptCore wird von Apple entwickelt und ist in Safari und einigen anderen Apple-Produk-
ten integriert. Es ist auch die Engine, die im WebKit-Projekt verwendet wird.
6 1 Einleitung
• Chakra wurde ursprünglich von Microsoft entwickelt und in älteren Versionen des Internet
Explorer-Browsers verwendet. Später wurde es durch ChakraCore ersetzt, eine Open-Source-
Version, die in Microsoft Edge und [Link] integriert ist.
• Rhino ist eine JavaScript-Engine, die in Java geschrieben wurde und die von Mozilla ent-
wickelt wurde. Rhino unterstützt die Ausführung von JavaScript in Java-Anwendungen.
Diese Engines sind entscheidend für die Ausführung von JavaScript-Code auf verschiedenen Platt-
formen und in verschiedenen Anwendungsfällen und haben im Laufe der Zeit erhebliche Fort-
schritte bei der Leistung und den Funktionen gemacht. Mittlerweile bemerkt man auch so gut wie
keine relevanten Unterschiede hinsichtlich des Verhaltens.
Nun ist der Begriff des Compilers gefallen und man muss den Kontext ausführlicher be-
trachten. Denn JavaScript ist von Anfang an eine reine Interpreter-Sprache gewesen. Interpreter
als auch Compiler dienen der Übersetzung von Quellcode und die Konzepte sollen nicht einfach
vorausgesetzt werden. Insbesondere nicht das JIT-Konzept, das moderne JavaScript-Engines ver-
folgen. Wenn Sie Skripte oder Programme erstellen, schreiben Sie den sogenannten Quellcode
oder Quelltext, der der englischen Sprache angelehnt ist. Aber zur Ausführung muss dieser über-
setzt werden, und zwar in ein Format, dass ein Computer versteht und das ist Maschinencode.
Dazu gibt es verschiedene Möglichkeiten.
• Einmal gibt es den Interpreter. Dieser sorgt für eine Übersetzung in Maschinencode während
der Programmausführung. Interpreter übersetzen den Quellcode eines Programms zeilenweise.
• Die Alternative ist der Compiler, der den kompletten Quelltext vor der Laufzeit übersetzt.
• In den vergangenen Jahren hat sich auch eine zweistufige Übersetzung etabliert – die Über-
setzung mit Zwischencode. Das wird in Java oder dem .NET-Framework gemacht. Denn ein
Nachteil bei kompilierten Programmen ist, dass diese Programme maschinenabhängig sind und
somit nicht auf jeder Computerplattform, zum Beispiel Linux und Windows, ausgeführt wer-
den können. In Java oder bei der .NET-Plattform wird Quellcode nicht zu einem ausführbaren
Programm, sondern in einen Zwischencode, kompiliert (1. Schritt). Dieser Code ist für alle
Plattformen gleich und kann mithilfe des entsprechenden plattformspezifischen Interpreters (2.
Schritt) auf der jeweiligen Plattform ausgeführt werden.
Bei JavaScript als interpretierte Sprache, wird Quellcode Zeile für Zeile ausgeführt wird und alte
Webbrowser führen den JavaScript-Code auf diese Weis aus, was zu relativ langsamer Ausführung
führte. Just-In-Time-Compilation (JIT) ist ein Ansatz, um die Leistung von interpretierten Sprachen
zu verbessern. Bei der JIT-Kompilierung wird der JavaScript-Code nicht nur interpretiert, sondern
auch in maschinenlesbaren Code übersetzt, der direkt von der CPU ausgeführt werden kann. Dieser
Prozess geschieht zur Laufzeit, was bedeutet, dass der JIT-Compiler während der Ausführung des
Programms aktiv ist. So funktioniert die moderne JIT-Kompilierung in JavaScript-Engines:
Optimierungstechniken anwenden, wie z. B. das Entfernen von unnötigen Operationen oder das
Inline-Aufrufen von Funktionen.
4. Die nun folgende Kompilierung übersetzt die optimierten Teile des Codes in maschinenlesbaren
Code, der direkt von der CPU ausgeführt werden kann. Dieser Prozess kann mehr Zeit in An-
spruch nehmen, aber da er nur für die am häufigsten verwendeten Codeabschnitte durchgeführt
wird, ist der Overhead akzeptabel.
5. Durch Iteration wird dieser Prozess immer wieder dynamisch wiederholt, während das Pro-
gramm läuft. Wenn sich das Verhalten des Codes ändert oder neue Teile des Codes ausgeführt
werden, passt sich der JIT-Compiler an und optimiert den Code entsprechend neu.
[Link] [Link]
Wenn wir JavaScript ohne Browser als Umgebung nutzen als auch uns mit serverseitiger
JavaScript-Programmierung sowie fortgeschrittenen JavaScript-Techniken beschäftigen
wollen, werden wir auf ein Projekt mit Namen [Link] beziehungsweise NodeJS set-
zen. Bei [Link] ([Link] handelt es sich erst einmal im weiteren Sinn sowohl
um ein Framework als auch um ein Interpreter-System unter der MIT-Lizenz. [Link]
ist also eine Open-Source-JavaScript-Laufzeitumgebung, die auf der V8-JavaScript-
Engine von Google basiert. Genaugenommen versteht man [Link] als eine Plattform
zum Betrieb von Netzwerkanwendungen und das können auch Webserver sein. Kompakt
kann man sich merken – [Link] ermöglicht die Ausführung von JavaScript-Code außer-
halb eines Webbrowsers und wurde entwickelt, um hochskalierbare Anwendungen zu
erstellen und zu betreiben. [Link] ist modular aufgebaut und es gibt einige Standard-
module, die direkt in das Binärpaket kompiliert werden, das Sie bei einer Standard-
installation erhalten. Aber es können auch zusätzliche Module eingebunden werden, was
wir an mehreren Stellen machen werden.
In JavaScript bezieht sich ein Modul auf eine Möglichkeit, Code in separaten Dateien
zu organisieren und zu strukturieren. Diese Dateien können dann über Import- und Ex-
portanweisungen verbunden werden, um Funktionalität zwischen ihnen zu teilen.
Zur Verwaltung der Module gibt es den Paketmanager npm, der mittlerweile auch von
vielen verteilten OpenSource-Projekten zur Softwareverwaltung eingesetzt wird. [Link]
steht für Linux, macOS, Windows und weitere Betriebssystem zur Verfügung und kann
über einen einfachen Assistenten auf einem Computer installiert werden. Das sollten Sie
tun, wenn Ihnen [Link] noch nicht zur Verfügung steht, denn wir werden in der Folge
recht oft auf [Link] zum Ausführen von JavaScripts setzen. Unter [Link]
download/ (Abb. 1.1) erhalten Sie die aktuellsten Setup-Dateien für Ihr Betriebssystem
zum Download.
8 1 Einleitung
[Link] Webserver
Auch wenn Sie eine HTML-Datei direkt vom Dateisystem in den Browser laden und
damit ein enthaltenes JavaScript ausführen können, ist es besser, wenn Sie die Dateien
stattdessen von einem Webserver anfordern. In einigen Situationen (etwa zum realisti-
schen Durchführen einer Ajax-Datenanfrage) ist es meist sogar unabdingbar, dass Sie
Ihre Webapplikation nicht direkt vom Dateisystem, sondern über das Protokoll http
oder https vom Server anfordern. Dementsprechend benötigen Sie Zugang zu einem
Webserver. Einige IDEs beinhalten einen internen Webserver. Aber es gibt auch andere
Möglichkeiten. Entweder stellt Ihnen Ihr Provider einen solchen Zugang bereit oder aber
Sie installieren auf Ihrem Rechner oder in Ihrem Netzwerk lokal einen solchen Server
zum Testen. Unter Windows bietet sich der IIS (früher stand das für Internet Information
Server und mittlerweile für Internet Information Services) von Microsoft an, besonders
dann, wenn Sie sowieso mit Microsoft-Technologien arbeiten wollen. Für viele Fälle
eignet sich eine Rundum-sorglos-Lösung namens XAMPP. XAMPP können Sie für ver-
schiedene Betriebssysteme aus dem Internet laden können (unter [Link]
[Link]/de/). Hinter XAMPP steckt ein Non-profit-Projekt namens Apache Friends,
das sich die Förderung des Apache Webservers und verbundener Technologien wie
MySQL, PHP und Perl auf die Fahne geschrieben hat. Für XAMPP gibt es nach der Ins-
tallation ein Control-Panel, worüber sich die einzelnen Dienste starten lassen (Abb. 1.2).
1.3 Voraussetzungen 9
Der Webserver steht Ihnen nach dem Start über die Angabe von localhost in der
Adresszeile des Browsers zur Verfügung, wenn Sie ihn auf dem gleichen Rechner laufen
lassen wie Ihren Browser. Das http-Protokoll davor wird in der Regel automatisch vom
Browser ergänzt. Andernfalls geben Sie in der Adresszeile einfach die IP-Nummer oder
den Namen des Rechners ein, auf dem der Webserver läuft.
Die Angabe localhost ist ein Alias für den aktuellen Rechner. Mithilfe dieser sogenannten Loop-
back-Adresse, die der IP-Nummer [Link] entspricht, können Sie auf Ihren eigenen Rechner so
zugreifen, als würden Sie dies über ein Netzwerk tun. Sie benötigen dazu nicht einmal eine Netz-
werkkarte.
Das Bereitstellen von Daten über den Webserver erfolgt bei Apache in der Regel über
das Unterverzeichnis htdocs. Dieses ist das Wurzelverzeichnis aller Zugriffe über den
Webserver. Oder mit anderen Worten: Wenn ein Anwender die Adresse des Webservers
ohne weitere Verzeichnis- oder Dateiangaben angibt, bekommt er den Inhalt dieses Ver-
zeichnisses beziehungsweise die Vorgabedatei darin angezeigt. Wurzelverzeichnis eines
Webservers bedeutet aber auch, dass sämtliche Verzeichnisangaben beim Zugriff aus
einem Browser auf einem Host ab diesem Verzeichnis aus gesehen werden. Wo konkret
sich htdocs im Verzeichnisbaum des Rechners befindet, kann je nach Konfiguration von
Apache individuell angegeben werden. Bei XAMPP unter Windows kann es sich zum
Beispiel auf dem Installationslaufwerk unter apachefriends und dann xampp befinden.
10 1 Einleitung
Das sieht aber ein Aufrufer der Webseite nicht und darf dieses aus Sicherheitsgründen
auch nicht sehen.
Wenn Sie die folgenden Beispiele des Buchs zum Testen über den Apache-
Webserver anfordern wollen oder müssen, können Sie die Dateien direkt in
htdocs bereitstellen, aber es ist zu empfehlen, in htdocs ein eigenes Verzeichnis
für unsere Experimente zu erstellen und diese sogar für einzelne Projekte bzw.
Kapitel noch zu unterteilen.
Beachten Sie, dass bei Pfadangaben unter Apache in der Regel Groß- und
Kleinschreibung relevant ist. Gerade Windows-Anwender sind das meist nicht
gewohnt, aber dies ist ja auch im Internet so gut wie immer der Fall. Am bes-
ten benennen Sie alle Verzeichnisse und Dateien konsequent klein. Vermeiden
Sie auch Sonderzeichen oder deutsche Umlaute bei Verzeichnis- und Datei-
namen.
Erste Beispiele – Der Sprung ins kalte
Wasser 2
Übersicht
In dem Kapitel kommen wir zum Erstellen erster JavaScript-Programme beziehungs-
weise -Skripte und Sie lernen, wie Sie diese ausführen können. Dabei werden wir
verschiedene Wege kennenlernen bzw. im ersten Einsatz sehen. Damit sind Tools
und Strategien gemeint, die Sie im Umgang mit JavaScript immer wieder benötigen.
Dazu möchte ich Ihnen direkt ein paar einfache JavaScript-Anwendungen vorstellen,
deren Syntax erst an späterer Stelle tiefer beleuchtet wird. Geben Sie jeweils nur den
Quelltext exakt wie vorgegeben ein (mit Beachtung der Groß- und Kleinschreibung,
Klammern, Semikolon etc.). Bevor wir uns in Details einarbeiten, schaffen wir uns
also praktische Grundlagen, damit nachfolgende Schritte leichter verständlich wer-
den. Wichtig ist hier, dass Sie den Umgang mit einem Editor und der grundsätzlichen
Erstellung von Quelltext sowie einem Browser (in Bezug zu JavaScript) und [Link]
üben und dabei erste Effekte sehen, die auf JavaScript beruhen.
Nachfolgend erstellen wir ein paar ganz einfache Programme1 mit minimaler Syntax,
die wir in der sogenannten Kommandozeile ausführen wollen. Damit werden die ein-
gegebenen JavaScript-Anweisungen unmittelbar ausgeführt. Das nennt man den Inter-
aktivmodus. Wie schon erwähnt, ist die Verwendung von JavaScript traditionell mit
einer Integration in eine Webseite verbunden, aber diese Beschränkung ist schon lange
1 „Programme“ ist eigentlich auch schon zu hoch gegriffen. Aber es ist wirklich JavaScript, was Sie
hier sehen werden.
gefallen und war auch früher – etwas verborgen –nicht zwingend. JavaScript kann ent-
weder in der Konsole eines Webbrowsers direkt ausgeführt werden oder man verwendet
eine JavaScript-Laufzeitumgebung wie [Link]. In beiden Fällen wird keine Webseite als
Gerüst bzw. Verankerung des Skripts benötigt. Allerdings können Sie dann auch nicht
auf die Bestandteile der Webseite wie Formulare oder Grafiken zugreifen. Wenn Sie in
einem Browser die Konsole öffnen (beispielsweise mit einem Rechtsklick in eine be-
liebige Webseite und Öffnen der Entwicklertools oder dem zugehörigen Menübefehl
oder meist auch der Taste F12), können Sie dort direkt JavaScript-Anweisungen notie-
ren. Zum Beispiel für die Berechnung des Ergebnisses von 1 + 1. Sie brauchen bloß 1 + 1
(oder auch irgendeine andere einfache mathematische Formel) dort einzugeben, um das
Ergebnis in der Konsole zu sehen (Abb. 2.1).
Die Berechnung wird mit JavaScript durchgeführt und sie sehen das Ergebnis 2, denn
in der Konsole eines modernen Browsers haben Sie unmittelbar Zugriff auf dessen inte-
grierter JavaScript-Engine. Teils müssen Sie mit der Enter-Taste die Eingabe bestätigen,
bevor Sie das Ergebnis sehen. Teils wird das Ergebnis direkt schon angezeigt, wenn Sie
noch bei der Eingabe sind. Zumindest soweit die Eingabe schon auszuwerten ist. Das
geht auch mit [Link]. Wenn Sie [Link] mit Standardeinstellungen installiert haben, öff-
nen Sie eine Eingabeaufforderung/Shell/Konsole Ihres Betriebssystems und geben dort
node ein. Sie gelangen danach im Eingabemodus von [Link] (REPL) und können da
vollkommen analog wie in der Browserkonsole JavaScript-Befehle eingeben (Abb. 2.2).
REPL steht für Read Evaluate Print Loop und ist eine Programmiersprachenumgebung (im
Grunde ein Konsolenfenster), die einen einzelnen Ausdruck als Benutzereingabe akzeptiert und
das Ergebnis nach der Ausführung an die Konsole zurückgibt. Verwandte Umgebungen kennt man
bei MySQL oder Python.
In der REPL können Sie analog wie in der Browserkonsole JavaScript-Befehle eingeben
und Ausgaben durch JavaScript vornehmen. Sie sehen auch da nach der Bestätigung
mit der Enter-Taste unmittelbar das Ergebnis. Die REPL-Sitzung bietet damit allgemein
eine bequeme Möglichkeit, einfachen JavaScript-Code schnell zu testen, aber auch Java-
Script-Code aus Dateien zu laden oder in Dateien zu speichern. Dabei gibt es in der
REPL ein paar grundlegende Befehle, die Sie kennen sollten. Um diese von JavaScript-
Anweisungen deutlich zu trennen, beginnen diese alle mit einem Punkt. Mit der An-
weisung .exit (beachten Sie den vorangehenden Punkt) beenden Sie die [Link]-Konsole.
Dazu gibt es weitere REPL-Anweisungen. Mit .help können Sie sich diesen Befehlssatz
der REPL anzeigen lassen. Wichtig sind die Anweisungen .load zum Laden einer ex-
ternen JavaScript-Datei in die REPL-Session und .save zum Speichern aller ausgeführten
Befehle in der aktuellen REPL-Session in eine Datei. Eine externe JavaScript-Datei kön-
nen Sie aber auch als Parameter an [Link] beim Aufruf übergeben und damit direkt aus-
führen, ohne in den Eingabemodus von [Link] gehen zu müssen. Das werden wir gleich
im auch tun.
Wenn Sie in der Kommandozeile direkt Anweisungen notieren, werden diese nach Ein-
gabe der Enter-Taste unmittelbar interpretiert, also einzeln und nacheinander im Rahmen
der speziellen Umgebung (Interaktivmodus). In der Regel wird man aber Anweisungen
in eine extra Textdatei auslagern – den Quelltext (auch Sourcecode oder Quellcode), der
dann ohne Bestätigen der einzelnen Anweisungen abgearbeitet wird. Das hat auch den
14 2 Erste Beispiele – Der Sprung ins kalte Wasser
Vorteil, dass die Anweisungen wiederverwendet werden können. Diesen Klartext kann
man mit einem beliebigen Editor erstellen.
Die Anweisung zur Berechnung zweier Zahlen, die direkt in die Konsole eines Browsers
oder der REPL eingegeben und dann dort ausgegeben wurden, kann man in eine externe
Datei mit JavaScript-Quellcode auslagern. Diese kann in eine Webseite eingebunden
oder von [Link] direkt geladen und ausgeführt werden. Der reine Quellcode für Node.
js und einen Browser wird identisch sein, sofern nicht auf Elemente einer Webseite zu-
gegriffen werden soll. Da wir uns in der Situation jedoch nicht innerhalb der JavaScript-
Laufzeitumgebung befinden, muss man angeben, wo die Ausgabe von einer eventuellen
Berechnung erfolgen soll. Man muss erst einmal festhalten, dass JavaScript selbst keinerlei
Möglichkeiten hat, eine Eingabe oder Ausgabe bereitzustellen. Diese sind jedoch ganz ent-
scheidend, denn Computerprogramme arbeiten so gut wie immer nach dem EVA-Prinzip.
Das EVA-Prinzip steht für Eingabe, Verarbeitung und Ausgabe und beschreibt ein
grundlegendes Konzept in der Informationstechnologie, ohne dessen Einhaltung Soft-
ware meist nicht sinnvoll genutzt werden kann.
Wie kann man das Dilemma lösen? JavaScript läuft im Rahmen einer Laufzeitumgebung
und diese stellt eine Schnittstelle mit passenden Objekten bereit. Da JavaScript objekt-
basierend (in neueren Versionen sogar objektorientiert) ist, gibt es also als Ergänzung der
eigentlichen Kernsprache zur Ausgabe passende Objekte.
Das Objekt zur Ausgabe in der Konsole nennt sich passender Weise console und wird
sowohl von einem Browser als auch [Link] bereitgestellt. Mit dem Objekt console und
dessen Methode log erhalten Sie Zugang zu einer Ausgabe in der Konsole.
Mit der log-Methode können Sie einen Parameter oder auch mehrere – durch
Kommata getrennte –Parameter in einer Zeile der Konsole ausgeben.
2.2 Anweisungen in (echten) Quelltext auslagern 15
[Link] (1 + 1);
Beachten Sie Groß- und Kleinschreibung, die Klammern und das Semikolon am Ende.
Sie führen diese JavaScript-Datei [Link] so mit [Link] ohne explizites Öff-
nen der RPEL aus, wenn Sie sich in dem Verzeichnis mit der JavaScript-Datei befinden:
Beispiel
node [Link]. ◄
Nach dem oben genannten Aufruf sehen Sie das Ergebnis in der Konsole, aus der Sie
node aufgerufen haben(Abb. 2.3).
Der externe JavaScript-Code kann auch in eine Webseite eingebunden werden. Diese
könnte in einer ganz einfachen Form so aussehen (kap 2_1.html):
<html><body><script src="lib/js/[Link]"></script>
</body></html>
Diesen Quellcode sollten Sie in ein eigenes Verzeichnis speichern. Etwa kap2. Beachten
Sie, dass die JavaScript-Datei in einem Unterverzeichnis lib und dessen Unterverzeich-
nis js gespeichert ist. So Strukturierung ist in der Praxis fast immer zu finden. Wenn Sie
die HTML-Datei direkt per Doppelklick oder über einen Webserver laden, sehen Sie die
Abb. 2.3 [Link] hat die externe JavaScript-Datei geladen und ausgeführt
16 2 Erste Beispiele – Der Sprung ins kalte Wasser
Abb. 2.4 Auch im Browser kann man mit der externen JavaScript-Datei in die Konsole schreiben –
hier Edge
Ausgabe in der Konsole des Browsers (Abb. 2.4). Falls der Webserver auf dem gleichen
Rechner läuft wie Ihr Webbrowser, würde der Aufruf wie folgt lauten, wenn Sie sich an
meine Empfehlung bezüglich der Wahl eines Verzeichnisses gehalten haben und dieses
dann im Installationsverzeichnis von Xampp unter htdocs bereitstellen:
Beispiel
[Link] ◄
Sollte das Beispiel nicht funktionieren, überprüfen Sie zuerst Ihren Quelltext. Achten Sie
auf Klammern, Hochkommata, Semikolon sowie Groß- und Kleinschreibung. Stimmt der
Quellcode exakt mit der Vorgabe überein und das Beispiel funktioniert dennoch nicht,
wird eventuell JavaScript in Ihrem Browser deaktiviert oder blockiert sein. Dann müssen
Sie JavaScript in den Einstellungen Ihres Browsers aktivieren bzw. eine eventuell blockie-
rende Erweiterung wie NoScript oder eine Firewall ausschalten, womit die Ausführung
von Skripten unterbunden wird. Der Zugriff über [Link] wird in der Regel (bei
den meisten Browsereinstellungen) auch als sicher angesehen, der Zugriff über das file-
Protokoll (einfaches Öffnen der Webseite mit einem Doppelklick) hingegen oft nicht.
Das direkte Laden der JavaScript-Datei selbst in den Browser zeigt nur den Quell-
text an. Er wird nicht interpretiert. JavaScript im Browser wird also immer (!)
über eine Webseite an die JavaScript-Engine übergeben. Einzige Ausnahme ist
der oben genannte Weg, dass Sie JavaScript-Befehle direkt in das Konsolenfenster
2.3 Ein einfaches Mitteilungsfenster 17
Die Ausgabe bei einem Browser in die Konsole vorzunehmen ist unüblich und meist
auch wenig sinnvoll. Ausgaben durch JavaScript sollten in der Webseite selbst oder
einem visuellen Dialog des Browsers erfolgen. Kommen wir nun dazu, wie eine Ausgabe
in einem Browser über ein einfaches Mitteilungsfenster vornehmen kann. Dazu muss das
JavaScript wieder über eine Webseite (ein HTML-Dokument) in den Browser geladen
werden. Dieses Mal soll aber ohne externe JavaScript-Datei gearbeitet werden, sondern
JavaScript direkt in den HTML-Code geschrieben werden. Um – wie versprochen – die
Theorie in diesem Kapitel knapp zu halten, wollen wir nur festhalten, dass JavaScript so
oder so in einem Browser als Bestandteil einer Webseite zu sehen ist und damit in einer
HTML-Seite referenziert wird. Damit haben Sie Zugang zum DOM, was eine Schnitt-
stelle von Objekten des Browsers darstellt. Diese wiederum stellen zahlreiche nützliche
Eigenschaften und Methoden bereit. Beispielsweise die Methode alert, um ein Dialog-
fenster anzuzeigen.
DOM steht für Document Object Model, und ist eine Spezifikation einer
Programmierschnittstelle mit Objekten, welches vor allen Dingen HTML- oder XML-
Dokumente (XML – Extensible Markup Language) als eine Baumstruktur darstellt.
Jedes Element als auch Attribut wird als Knoten verstanden. Knoten sind Objekte,
die einen Teil des Dokumentes repräsentieren. Die Schnittstelle ist plattform- und
programmiersprachenunabhängig und erlaubt standardisierte Zugriffe auf die Struktur
und das Layout eines Dokumentes. Die DOM-Schnittstelle ist in die gesamte Objekt-
schnittstelle eines Browsers nahtlos integriert.
Erstellen wir also so eine HTML-Datei. Geben Sie den nachfolgenden Quelltext in
einem Editor ein und nennen Sie die Datei kap 2_2.html.
<html><body><script>alert(1 + 1);</script></body></html>
In der Webseite finden Sie einen vereinfachten internen Skript-Container. Die eigentliche
JavaScript-Anweisung steht darin. Wenn Sie die Datei mit einem Doppelklick oder den
lokalen Webserver aufrufen, sollte der Browser ein kleines Dialogfenster mit dem Text
anzeigen, der das Ergebnis der Berechnung darstellt (Abb. 2.5).
18 2 Erste Beispiele – Der Sprung ins kalte Wasser
Schauen wir uns auch hier etwas Troubleshooting an. Sollte das Beispiel nicht funk-
tionieren, überprüfen Sie wieder zuerst Ihren Quelltext, Einstellungen des Browsers
zu JavaScript etc. Aber bei dem Quellcode greift noch ein potenzielles Problem. Sollte
das Beispiel (scheinbar) immer noch nicht funktionieren, kann es daran liegen, dass das
Dialogfenster geblockt wird. Aufgrund diverser Sicherheitsprobleme im WWW ver-
hindern einige (vor allem neuere) Browser sogenannte aktive Inhalte bzw. Popups. Sollten
Sie eine entsprechende Warnung bekommen, müssen Sie zur Ausführung des Beispiels
die aktiven Inhalte für dieses Beispiel zulassen.
Spielen wir ein weiteres Beispiel durch, in dem das aktuelle Tagesdatum dynamisch mit
JavaScript in die Webseite geschrieben wird. Geben Sie den nachfolgenden Quelltext in
einem Editor ein (kap 2_3.html):
Im Body der Webseite finden Sie wieder einen Skript-Container und die eigentliche
JavaScript-Anweisung steht im Inneren des Containers. Beachten Sie, dass vor dem
Skript-Container reiner Text steht, der nicht zum Skript zählt. Hier sind wir noch in der
HTML-Welt. Wenn Sie die Datei im Browser laden, sollte im Anzeigebereich des Brow-
sers ein Text zu sehen sein, der aus den statischen Textpassagen und einer dynamisch
per JavaScript generierten Information (dem Tagesdatum mit einer Genauigkeit eines
Bruchteils einer Sekunde) besteht. Statische und dynamisch generierte Information kann
ein Anwender nicht unterscheiden. Die Details zu document behandeln wir später, aber
schon vorab die Information – das Objekt gehört zum DOM-Konzept.
2.6 Besonderheit bei der Webprogrammierung 19
Das abschließendes Einstiegsbeispiel kap 2_4.html. zeigt die Entgegennahme einer Be-
nutzereingabe die Methode prompt. Beachten Sie, dass [Link] für die Eingabe von
Benutzereingaben keinen einfachen Weg bereitstellt und zudem ein externes Modul
benötigt. Das ist definitiv kein Hexenwerk und geht auch mit einigen wenigen Zeilen
Quellcode. Aber es ist noch zu früh, diesen Aufwand zu treiben. Das kommt später.
Eingaben im Rahmen des Browsers als auch die Ausgaben in der Webseite oder einem
Dialogfenster des Browsers sind einfacher, da sie die DOM-Schnittstelle sowie imple-
mentierte Objekte verwenden.
Sie sehen wieder den Skript-Container und die eigentliche JavaScript-Anweisung. Auch in
diesem Beispiel steht sowohl vor dem Skript-Container als auch danach reiner Text, der
nicht zum Skript zählt. Beim Öffnen der Datei in einem Browser sollten Sie ein kleines
Eingabefenster angezeigt bekommen, in dem Sie Ihren Namen eingeben sollen (Abb. 2.6).
Im Anzeigebereich des Browsers sehen Sie nach der Eingabe eines Namens und der
Bestätigung des Dialogs einen Text, der aus den statischen Textpassagen und der Ein-
gabe durch den Anwender besteht (Abb. 2.7).
Kommen wir zum Abschluss des Kapitels zu ein paar Details rund um das Internet und
das WWW, die bei der Programmierung von Web-Applikation relevant sind. Das Internet
mit all seinen Diensten gehört als Ganzes niemandem und doch allen Internetteilnehmern
20 2 Erste Beispiele – Der Sprung ins kalte Wasser
Abb. 2.7 Eine Seite, die aus statischen und dynamischen Informationen zusammengesetzt wurdet
zugleich. Es gibt einige Organisationen, welche sich mit den unterschiedlichsten Aspek-
ten des Internets beschäftigen. Insbesondere auf Ebene der einzelnen Dienste existieren
Organisationen zur Lenkung verschiedenster Belange. Besonders wichtig in Bezug auf
die Webprogrammierung ist das W3C (World Wide Web-Consortium – [Link]
[Link]), das für die Standards im WWW und damit auch DOM, HTML, Cascading Style
Sheets und XML verantwortlich ist. Das W3C ist – wie alle diese Organisationen – je-
doch weitestgehend auf die freiwillige Kooperation der Internetgemeinde und vor allen
Dingen der Browserhersteller angewiesen. JavaScript hingegen wird bei der ECMA
International ([Link] standardisiert.
Nun muss man bei der Programmierung in so einem heterogen Umfeld wie dem
WWW ein paar Details beachten. In unserem Zusammenhang bedeutet dies, dass wir
im Internet die unterschiedlichsten Plattformen vorfinden: Sowohl bei der Hardware
auch den Betriebssystemen. Auch die Kommunikationswege sind uneinheitlich. Diese
Heterogenität erzwingt eine ganz besondere Technik, auf welche Art und Weise Daten
zwischen den Welten ausgetauscht werden müssen. Die Daten müssen für alle Systeme
verständlich sein. Auf der einen Seite – dem Transport – übernehmen dies im Internet
Protokolle wie TCP/IP, welche eine plattformneutrale Übertragung gewährleisten. Die
darauf aufsetzenden Dienstprotokolle wie HTTP oder FTP gewährleisten die plattform-
neutralen Anwendungsumgebungen. Aber auch aufseiten der konkreten Anwendung dür-
fen keine plattformbezogenen Anweisungen verwendet werden, wenn man nicht bewusst
bestimmte Zielplattformen ausschließen will. Zwar gibt es auch im Internet einige Tech-
niken, die beim Anwender bestimmte Voraussetzungen notwendig machen (etwa nur
Windows und den Internet Explorer als Basis oder das Vorhandensein einer hersteller-
abhängigen Erweiterung) und dann eine weitreichende Funktionalität bieten. Im Internet
wurden und werden solche dogmatischen Techniken jedoch kritisch gesehen und sind
im Grunde über die Geschichte allesamt gescheitert. Vor allem werden Anwender aus-
geschlossen, die bestimmte Produkte nicht verwenden können oder wollen.
2.6 Besonderheit bei der Webprogrammierung 21
Die beste Variante für die Akzeptanz einer Technik im Internet scheint die voll-
kommene Plattformneutralität zu sein. Das ist auf jeden Fall bei Klartexttechniken ge-
währleistet, bei denen jede Anweisung auf jeder Plattform verstanden wird. Die An-
weisungen werden durch ein spezifisches Programm interpretiert, das auf der jeweiligen
Plattform läuft, und die neutralen Befehle werden in konkrete Prozessorbefehle über-
setzt, die dann ausgeführt werden. Ich rede also vom Interpreter-Prinzip, das im Web
den Königsweg darstellt. Deshalb verwenden Sprachen wie HTML oder JavaScript, aber
auch XML diesen Ansatz. Sowohl HTML als auch JavaScript werden auf jeder Rechner-
plattform verstanden. Über einen Interpreter werden die Anweisungen in die jeweiligen
Prozessorbefehle übersetzt und ausgeführt (also interpretiert), wobei im Fall von Java-
Script ja in modernen Systemen JIT zur Optimierung zum Einsatz kommen. Das ändert
aber nichts an dem grundsätzlichen Ansatz. Interpretation hat noch weitere Vorteile. Die
Menge der zu übertragenden Daten ist kleiner, wie wenn die gesamte Funktionalität zu
übertragen wäre. Außerdem ist es erheblich leichter, Änderungen durchzuführen, und die
gesamte Wartung ist unkomplizierter. Der aufwendige Schritt der erneuten Kompilierung
entfällt.
Ein großes Problem ist, dass Interpretation immer einen erheblichen Spielraum lässt,
wie eine Anweisung zu verstehen ist. Insbesondere ist dies der Fall, wenn es sich nur
um eine Beschreibungsanweisung handelt, aber grundsätzlich gilt das Problem auch für
Skriptsprachen. Anhand von HTML lässt sich das Problem aber leichter verdeutlichen.
Es gibt in HTML beispielsweise eine Anweisung, dass der Anweisung folgender Text
hervorgehoben dargestellt werden soll (<strong>), bis die Anweisung wieder aufgehoben
wird (</strong>). Aber wer legt fest, was hervorgehoben eigentlich bedeutet? Neigung
der Schrift. Oder fett darstellen? Oder unterstreichen? Jede der Varianten wäre denk-
bar. Der Hersteller des Browsers A hat sich für die eine Variante entschieden, während
der Hersteller des Browsers B eine andere vorzieht. Beide Browser werden also die An-
weisung verstehen und durchführen, das Resultat wird aber bei beiden unterschiedlich
aussehen. Diesen Interpretationsspielraum finden Sie bei den unterschiedlichsten An-
weisungen in HTML und teilweise auch in anderen Techniken wie Style Sheets oder Ja-
vaScript. Mittlerweile haben sich die Hersteller verschiedener Browser als auch Engines
für JavaScript weitgehend geeinigt. Es gibt jedoch immer noch Situationen, die unter-
schiedlich ausgelegt werden. Gerade bei neuen Befehlen und Techniken dauert es lange
Zeit, bis sich die verschiedenen Lager einigermaßen geeinigt haben. Ein großes Problem
beim Interpreter-Konzept ist auch das Veralten der Interpreter. Wenn eine Interpreter-
Sprache einen neuen Befehl hinzugefügt bekommt, können die bis dahin entwickelten
Interpreter diesen Befehl noch nicht kennen und ihn entsprechend nicht ausführen. An-
wender mit solchen Interpretern können unter Umständen ein Programm nicht laufen
oder ein Dokument nicht darstellen lassen.
Versionen von JavaScript und
Einbindung in Webseiten 3
Übersicht
Wir beschäftigen uns in diesem Kapitel damit, wie Sie JavaScripts in Webseiten
einbinden können, denn bevor Sie mit JavaScript einen Browser beziehungs-
weise die Webseite programmieren können, müssen Sie eine Verbindung zwischen
HTML (dem Gerüst der Webseite) und der Skript-Funktionalität schaffen. Es sind
hier einige Feinheiten zu beachten, die das Leben schwermachen können. Auf der
einen Seite werden diese Feinheiten meist keine Rolle spielen, aber es gibt durch-
aus kritischen Situationen, die man nur durch die richtige Einbindung von Java-
Scripts in die Webseite entschärfen kann. Sie lernen in diesem Kapitel
Dazu ist es wichtig zu beachten, dass JavaScript durch mehrere Versionen ge-
gangen ist und inwieweit diese Versionen in den verschiedenen relevanten Brow-
sern unterstützt werden.
JavaScript ist seit seiner Vorstellung durch Netscape unter dem ursprünglichen Namen
LiveScript und der Umbenennung im Jahr 1995 durch diverse Versionszyklen gegangen.
Grundsätzlich muss beachtet werden, dass über die Geschichte der Sprache für nahezu
alle neu eingeführten Sprachzyklen von JavaScript kaum ein zeitnah aktueller Browser
den offiziellen Standard vollständig unterstützt hat und es immer Zeit dauerte, bis die
nächsten Browserversionen einen vollständigen Sprachzyklus verdaut hatten! Obwohl
sich im November 1996 Netscape und die internationale Industrievereinigung ECMA
(European Computer Manufactures Association –früher ([Link] und
mittlerweile [Link] zusammentaten und beschlossen hatten, Ja-
vaScript zu standardisieren. Aus dem Projekt entstand ECMAScript, was der Name für
das, von der mittlerweile ECMA International genannten Organisation standardisierte,
JavaScript ist. Im Juni 1997 wurde die erste Version freigegeben (der Standard ECMA-
262). Alle neueren JavaScript-Versionen versuchen, mit der jeweilig aktuellen Version
von ECMAScript so weit wie möglich kompatibel zu sein. Nach der ECMAScript-
Edition 3, die im Dezember 1999 freigegeben wurde, richteten sich die meisten Brow-
ser sehr lange Zeit. Die dritte Version des ECMAScript-Standards entspricht JavaScript
1.5. Man sollte festhalten, dass die JavaScript-Versionen nach 1.5 mehrere Jahre von der
Mozilla-Foundation (Nachfolgerorganisation von Nescape –[Link]
org/de/) proprietär vorangetrieben wurden, was durchaus kein Vorteil ist. Im Gegenteil –
die Versionen nach der Version 1.5 wurden lange Zeit in der Praxis in nur wenigen Brow-
sern unterstützt. Was aber an Neuerungen besonders interessant war (etwa Web Worker
oder Local Storage), wurde dann irgendwann unter dem Deckmantel HTML5 etabliert.
Der als ECMAScript standardisierte Sprachkern von JavaScript beschreibt also letzt-
endlich eine dynamisch typisierte, objektorientierte, aber klassenlose Skriptsprache.
Sie wird allen objektorientierten Programmierparadigmen unter anderem auf der Basis
von Prototypen gerecht, deren Deklaration ab ECMAScript 6 mit einer Syntax ermög-
licht wird, wie sie ähnlich bei klassenbasierten Programmiersprachen üblich ist. Diverse
moderne Browser als auch [Link] unterstützen entsprechende Syntax mittlerweile ganz
oder zumindest teilweise. In JavaScript lässt sich je nach Bedarf damit objektorientiert,
prozedural oder funktional programmieren. Nun sollte man festhalten, dass es neben
JavaScript weitere Sprachen gibt, die sich an ECMAScript orientieren oder als Klone
von JavaScript entwickelt wurden. Insbesondere muss man JScript und TypeScript er-
wähnen, was beides von Microsoft kommt.
Während JScript nur ein besagter „Klon“ von JavaScript war, den Microsoft rein
aus Lizenzgründen herausgegeben hat (die Lizenz von JavaScript gehörte Netscape
und wurde nie von Microsoft erworben), ist TypeScript eine von Microsoft auf Basis
von JavaScript bzw. ECMAScript entwickelte Skriptsprache, die 2012 erstmals in einer
Betaversion und 2014 dann offiziell vorgestellt wurde. TypeScript brachte einige echte
eigene Features und Erweiterungen gegenüber JavaScript. Insbesondere eine statische
3.2 JavaScript in Webseiten 25
Typisierung und Klassen, Vererbung oder Module. Später kamen noch Syntaxelemente
wie die Möglichkeit einer Methodensignatur, Schnittstellen, Aufzählungstypen und
Tupel, generische Programmierung und Namensräume hinzu. TypeScript kann man
damit als eine Obermenge (strict superset) von JavaScript bzw. ECMAScript 2015 be-
trachten und jeder JavaScript-Code ist gültiger TypeScript-Code. Damit können alle
gängigen JavaScript-Bibliotheken in TypeScript verwendet werden. Die Umkehrung gilt
jedoch nicht. Um TypeScript wirklich in der Praxis verwenden zu können, muss dar-
aus JavaScript gemacht werden. Der von Microsoft bereitgestellte TypeScript-Compiler
transkompiliert TypeScript-Code nach ECMAScript 5 (ES5) oder ECMAScript 3 (ES3).
Neue Versionen von JavaScript haben diverse Features von TypeScript mittlerweile auch
optional verfügbar, sodass sich die Sprachen stark angeglichen haben. Nur wird Java-
Script universell unterstützt, während TypeScript weitgehend auf das Habitat von Micro-
soft eingeschränkt ist oder eben transkompiliert werden muss.
JavaScript verwendet den Unicode-Zeichensatz. Unicode ist ein internationaler Standard, der
eine einheitliche Codierung, Darstellung und Verarbeitung von Texten in verschiedenen Schrift-
systemen und Zeichenkodierungen ermöglicht. Unicode bietet eine breite Palette von Zeichen,
die verschiedene Sprachen, Symbole, Emojis und andere Zeichen umfassen. JavaScript kann
somit Zeichen aus verschiedenen Sprachen und Zeichenkodierungen verarbeiten, solange sie im
Unicode-Zeichensatz definiert sind. Aber das war nicht immer so. In den frühen Tagen von Java-
Script, als die Sprache in den 1990er Jahren entwickelt wurde, war die Unterstützung für Zeichen-
sätze weniger standardisiert. JavaScript wurde ursprünglich für die Verwendung in Webbrowsern
entwickelt, und zu dieser Zeit waren Zeichencodierungen wie ASCII und ISO-8859–1 (auch be-
kannt als Latin-1) weit verbreitet. Unicode wurde in JavaScript jedoch prinzipiell dennoch von
Anfang an unterstützt. 1995 war Unicode war bereits als aufstrebender Standard für die Zeichen-
codierung bekannt und daher von Netscape bereits in den Grundlagen der Sprache berücksichtigt.
Die offizielle Spezifikation für JavaScript über ECMAScript-Spezifikation hat die Unterstützung
für Unicode kontinuierlich verbessert und erweitert. In den modernen Versionen von JavaScript,
insbesondere in ECMAScript 6 und den nachfolgenden Versionen, ist Unicode fest im Standard
verankert. Dies ermöglicht die Verwendung von Unicode-Zeichen in Variablennamen, String-
Literalen, Funktionsnamen und anderen Teilen des Codes.
Wir haben schon zwei Wege gesehen, wie man JavaScript in einer Webseite verwendet.
Das wollen wir vertiefen. JavaScript wird im Browser in einer sogenannten Sand-
box ausgeführt. Dies soll bewirken, dass man in JavaScript nur Zugriff auf die Objekte
hat, die der Browser zur Verfügung stellt. Insbesondere wird der direkte Zugriff auf
das Dateisystem unterbunden. Eine Ausnahme stellt der Lesezugriff auf eine Datei dar,
die per Dateiauswahl-Dialog über das HTML-Element <input type = "file"> vom Be-
nutzer ausgewählt wurde. Ebenso sind lesende und schreibende Zugriffe auf dafür vor-
gesehene Bereiche erlaubt, was etwa Cookie, Local Storage oder lokale Datenbanken
ermöglicht. Die Sandbox unterbindet zudem Sicherheitsprobleme wie das sogenannte
26 3 Versionen von JavaScript und Einbindung in Webseiten
Nur noch historisch interessant ist der Aufruf einer JavaScript-Anweisung, die direkt in
eine HTML-Referenz geschrieben wird. Dies ist die sogenannte Inline-Referenz. Dabei
wird JavaScript-Code bei einem sensitiven Element wie einem Hyperlink statt eines
URL auf eine andere Ressource als Verweisziel definiert, indem dem Attribut href in An-
führungszeichen eine oder mehrere JavaScript-Anweisungen zugewiesen werden. Dazu
müssen Sie in einer entsprechenden HTML-Anweisung das Schlüsselwort javascript (als
Protokoll zu verstehen), gefolgt von einem Doppelpunkt, als Attribut angeben. Die Syntax
sieht also so aus:
Beispiel
Hier ist Beispiel einer Inline-Referenz zum Aufruf eines Dialogfensters bei einem Klick
auf einen Hyperlink:
Wenn der Anwender auf den Hyperlink klickt, wird in dem Beispiel mit alert ein kleines
Dialogfenster eingeblendet, in dem der als Parameter angegebene Text, der sich durch die
Berechnung ergibt, angezeigt wird. Dieses System der Inline-Referenz hat erhebliche Nach-
teile und wird so gut wie gar nicht mehr verwendet. Wir verfolgen das Konzept nicht weiter.
wird durch eine HTML-Steueranweisung realisiert, die mit ihrem zugehörigen Abschluss-
Tag einen Container für die Skriptanweisungen bildet. Es handelt sich bei dieser Steuer-
anweisung um das Tag <script>. Diese Anweisung haben Sie in den ersten Beispielen im
Buch auch schon gesehen. Das Tag gehört explizit zu HTML, aber all das, was in dem ein-
geschlossenen Bereich notiert wird, wird vom Browser als ein Skript interpretiert.
<script> … </script>
Das Tag verfügt über einige optionale Parameter, die aber in der Regel veraltet sind und
heute nicht mehr verwendet werden.
• Mit language können Sie angeben, um welche Skriptsprache es sich im Inneren des
Containers handelt. Das geht auch heute noch, aber der Parameter wurde schon lange
durch das Attribut type ersetzt. Damit geben Sie den MIME-Type an (für JavaScript
ist das der Wert "text/javascript"). Mit diesem Parameter können Sie also ebenfalls
angeben, dass in dem Skriptbereich mit JavaScript gearbeitet wird. In manchen älte-
ren Quellen zu JavaScript sieht man auch den MIME-Typ "application/javascript" als
Alternative. Ich rate von der Verwendung ab. Es gibt diverse Browser, die damit Pro-
bleme machen. HTML5 hat sogar den Verzicht auf die Angabe der konkreten Skript-
sprache als Vorgabe genommen.
Definition Die Angabe eines Dokumententyps im Internet erfolgt in der Regel über
einen Standard mit Namen MIME (Multipurpose Internet Mail Extensions). Dieser wird
im Wesentlichen zur Spezifizierung von Dateitypen bei der Kommunikation zwischen
Server und Browser verwendet. Sowohl Server als auch Browser kennen bestimmte
Dateitypen. Beim Übertragen vom Server zum Browser wird über das http-Protokoll der
MIME-Typ versteckt mit übertragen. Der MIME-Typ text/html steht zum Beispiel für ein
HTML-Dokument und XHTML-Dokumente gibt es die MIME-Typen text/xml oder ap-
plication/xml. Aufgrund seiner Liste mit bekannten MIME-Typen kann ein Server eine
bestimmte Dateiart auf spezielle Weise versenden bzw. markieren, der Browser aber vor
allen Dingen eine Datei eines bekannten Typs korrekt behandeln. Werden unbekannte
Dateitypen geladen, kann ein Browser nicht direkt damit umgehen. Dann kann er an-
bieten, die Datei zu speichern oder über zusätzliche Informationsquellen (etwa einen
Benutzerdialog) nach der Behandlungsweise zu forschen. Die Verwendung von MIME-
Typen umgeht zugleich das Problem, dass viele Dateierweiterungen von mehreren Pro-
grammen bearbeitet werden können (etwa .bmp, was unter Windows von zahlreichen
Grafikprogrammen bearbeitet werden kann). MIME-Typen geben eindeutig das zu ver-
wendende Programm an. MIME-Typen werden nach folgendem Schema angegeben:
Hauptkategorie/Unterkategorie.
Hauptkategorien sind etwa text, image oder audio. Unterkategorien von text sind bei-
spielsweise plain (eine reine Textdatei), javascript (beim Einbinden von JavaScripts)
28 3 Versionen von JavaScript und Einbindung in Webseiten
oder html (eine HTML-Datei). Unterkategorien von image sind beispielsweise gif oder
jpeg.
• Durch die Angabe des optionalen Attributs defer (ohne Wert) teilen Sie dem Brow-
ser mit, dass der JavaScript-Code keine Ausgabe im HTML-Dokument erzeugt. Diese
Angabe ist rein informell und dient dazu, dass der Browser dadurch das HTML-Do-
kument schneller darstellen kann, da der JavaScript-Code nicht sofort analysiert (ge-
parst) werden muss.
• Bei Bedarf können Sie im <script>-Tag das Attribut lang setzen, um eine Sprache an-
zugeben, die in dem Quellcode verwendet wird.
Wir werden im Buch in den meisten Fällen auf optionale Attribute verzichten.
<script language="javascript1.5">
Das macht man aber seit einigen Jahren gar nicht mehr.
richtigen Position eines Skriptcontainers oder der Einbindung einer externen JavaScript-
Datei kann massive Auswirkungen auf die Effizienz und Qualität der Webseite haben.
Wenn Sie sich ganz alte Webseiten mit JavaScripts ansehen, erkennen Sie dort oft einen HTML-
Kommentar im Skriptbereich. Direkt nach dem einleitenden <script>-Tag öffnet sich ein HTML-
Kommentar (<!––). Dieser wird unmittelbar vor dem abschließenden Tag </script> mit dem ent-
sprechenden HTML-Kommentar-Endezeichen (––>) wieder geschlossen. Dadurch steht der ge-
samte JavaScript-Code innerhalb eines HTML-Kommentars. Dieses Verfahren zur Absicherung
von alten Browsern ist heute überflüssig. Es ist nur dafür da, dass ein Browser ohne JavaScript-
Kenntnisse den Quelltext nicht in der Seite anzeigt. Aber diese Browser stammen aus den frühen
neunziger Jahren und sind ausgestorben. Beachten Sie, dass hier nicht von Browsern die Rede ist,
bei denen der Anwender JavaScript deaktiviert hat oder JavaScript blockiert wird. Diese verstehen
das <script>-Tag und führen einfach die darin enthaltenen Anweisungen nicht aus.
Fast immer ist es sinnvoll, JavaScripts in eine oder mehrere externe Datei(en) auszu-
lagern. Diese Vorgehensweise trägt der Tatsache Rechnung, dass man ab einer gewissen
Größe Projekte strukturieren muss. Zudem können gemeinsame Funktionalitäten in einer
Datei bereitgestellt werden und nicht jede Webseite muss deren Implementierung ent-
halten. Natürlich werden die Wartung und Anpassung erheblich erleichtert und auch
sonst gibt es weitere Vorteile. Die Auslagerung in eine externe Datei verwendet wieder
das <script>-Tag, das dann aber über das Attribut src die externe Datei angibt. Optional
ist – wie oben angesprochen – das zusätzliche type-Attribut oder früher auch language.
Die Syntax einer Referenz auf eine externe JavaScript-Datei sieht schematisch so aus:
Beispiel
In Anführungszeichen wird hinter dem Attribut src der Name bzw. vollständige URL der
separaten Datei angegeben. Dabei gelten beim Referenzieren von separaten JavaScript-
Dateien die üblichen Regeln für URLs. Entweder geben Sie einen relativen Pfad an oder
aber einen vollständigen URL, der mit dem Protokoll (in der Regel http oder https) be-
ginnt. Die Datei mit dem Quellcode muss – wie HTML-Dateien – eine reine Klartextdatei
sein und ausschließlich JavaScript-Code enthalten. Eine solche JavaScript-Datei enthält
explizit kein Grundgerüst. Wie schon erwähnt, ist die [Link] üblich, aber das
ist nicht zwingend. Hier sind ein paar konkrete Beispiele mit und ohne die type-Angabe:
Eine externe JavaScript-Datei enthält wie ein in einer Webseite notierter <script>-Con-
tainer einfach JavaScript-Code in Form von Klartext. Allerdings wird bei externen Java-
Script-Dateien fast immer mit Funktionen oder Klassen gearbeitet, da sonst alle in der
externen Datei notierten Anweisungen beim Laden einer Webseite mit einer Referenz
auf die externe Datei abgearbeitet werden. Das ist in der Regel nicht gewünscht, denn
meist finden sich mehrere Funktionalitäten in einer einzigen Datei und Funktionen oder
Klassen geben die Möglichkeit, gezielt eine der Schrittfolgen aufzurufen. Sie müssen
natürlich nicht alle in einer Datei oder einem Container deklarierten Funktionen auch
wirklich nutzen. Ansonsten gelten die gleichen Ausführungen wie beim internen Skript-
container.
var a = 1;
var a = "Text";
[Link](a);
Ich greife in dem Beispiel etwas vor, da die Deklaration von Variablen noch nicht be-
sprochen wurde. Aber ich gehe davon aus, dass das Skript dennoch verständlich ist.
Unter einer Deklaration (in einigen Programmiersprachen bzw. Sonderfällen auch De-
finition) versteht man die Festlegung von Dimension, Bezeichner, Datentyp und weiteren
32 3 Versionen von JavaScript und Einbindung in Webseiten
Aspekten einer Variable oder eines Unterprogramms. Erst durch eine Deklaration wird
dem Compiler oder Interpreter ein solches Element bekannt gemacht; es ist damit mög-
lich, diese an anderen Stellen im selben Quelltext zu verwenden.
• In der ersten Zeile des Skripts wird eine Variable a deklariert und der Wert 1 zu-
gewiesen. Damit beinhaltet die Variable eine Zahl (numerischer Datentyp).
• In der zweiten Zeile des Skripts wird erneut eine Variable deklariert, die den glei-
chen Bezeichner an erhält. Dieser wird ein Text zugewiesen.
• Die Ausgabe in der dritten Zeile zeigt, dass die zuerst deklarierte Variable mit der Zahl
nicht mehr da ist, sondern nur die nachfolgend deklarierte Textvariable ( Abb. 3.1).
Nun haben Sie in diesem einfachen Beispiel ein Verdecken des zuerst deklarierten Ele-
ments durch ein identisch benanntes nachfolgendes Element in der gleichen JavaScript-
Datei. Sie können in einer Webseite aber auch mehrere verschiedene externe JavaScript-
Dateien und interne Skripts sowie Inline-Referenzen gemeinsam verwenden. Auch das
neue Modulsystem gestattet die Verwendung mehrerer JavaScript-Ressourcen. Alle Ele-
mente landen im gemeinsamen Namensraum und dann können diese Effekte über meh-
rere Ressourcen und Bereiche hin auftreten. Also sollten Sie beachten, dass es in den
verschiedenen Dateien und Bereichen keine Funktionen, Variablen oder andere Elemente
mit gleichem Namen gibt. Wenn alle Elemente zugänglich bleiben sollen!
Aber wenn der gegenteilige Wunsch besteht, braucht man in JavaScript nicht einen
immensen Overhead wie beim Überschreiben (Override) in schwergewichtigeren Spra-
chen wie Java oder C# betreiben. Ich möchte an der Stelle nicht zu weit in die Grund-
lagen der OOP (Objektorientierte Programmierung) einsteigen und wir reden hier auch
nicht von Vererbung im Sinn der OOP, aber der gemeinsame Namensraum und das be-
schriebene Verhalten bei Identität von Bezeichnern kann als Schwäche von JavaScript,
aber auch als große Stärke ausgelegt werden. Ich stehe auf der Seite derer, die darin
einen riesigen Vorteil von JavaScript gegenüber Sprachen wie Java oder gar C# sehen1.
1 Und das, obwohl ich vermutlich in den letzten Jahren viel mehr mit C# und Java programmiere
als mit JavaScript.
3.4 Kann man testen, ob bei einem Browser JavaScript aktiviert ist? 33
Gerade die Verwendung von fremden Ressourcen ist damit erheblich erleichtert. Ent-
weder haben diese ein zentrales Objekt, in dem sie alle Funktionalitäten kapseln. Oder
das ist nicht der Fall und selbst wenn in Bibliotheken oder anderen JavaScript-Dateien
Bezeichner bereits verwendet werden, die man selbst im Code deklarieren möchte, gibt
es keine Fehler oder Beschränkungen. Sie müssen nur Ihre eigenen Codes immer als
Letztes einbinden bzw. aufrufen. Maximal sind dann diese verdeckten Elemente der vor-
her referenzierten Codes eben nicht mehr verfügbar und das kann in vielen Fällen un-
problematisch sein.
Das Verhalten von JavaScript kann man also ausdrücklich nutzen, um ein elegan-
tes und effizientes Überschreiben bzw. Verdecken von Elementen durchzuführen, aber
es kann auch ungewollte Effekte haben. Sie sollten deshalb eine Konfliktsituation ver-
meiden, wenn Sie diesen Vorteil des Überschreibens nicht nutzen wollen.
Wenn Sie in einer Webseite ein Skript verwenden, konnte früher dem <script>-Element in dem
Körper der Webseite ein <noscript>-Container folgen. In diesem konnten Informationen unter-
gebracht werden, die nur bei deaktiviertem JavaScript im Browser angezeigt wurden. Dieser Con-
tainer wird in der Praxis schon lange nicht mehr verwendet, denn JavaScript ist – wie schon er-
wähnt –so gut wie immer aktiviert und in HTML5 wird der Tag sowieso als deprecated betrachtet.
Das geht beispielsweise so. Mit JavaScript führen Sie eine Weiterleitung zu einem anderen
URL aus. Funktioniert der, muss auch JavaScript aktiviert sein. Wenn Sie den Anwender
nun komfortabel führen wollen, erstellen Sie eine Einstiegsseite in Ihr Webprojekt, die im
Wesentlichen eine solche Weiterleitung enthält und ihn für den Fall von deaktiviertem Ja-
vaScript automatisch zu einem Projekt führt, das ohne JavaScript auskommt. Eine solche
Weiterleitung funktioniert auch ohne JavaScript. Mit HTML sind automatische Weiter-
leitungen mit dem Tag <meta http-equiv = "refresh" content = "[Zeit];URL = [Ziel]"> mög-
lich. Beim Attribut content geben Sie zuerst die Zeitspanne in Sekunden an, nach der die
Weiterleitung erfolgen soll, und dann das Ziel der Weiterleitung. Der entscheidende Trick
34 3 Versionen von JavaScript und Einbindung in Webseiten
beruht nur darauf, die Weiterleitung per HTML erst nach der Weiterleitung per JavaScript
auszuführen. Das nachfolgende Beispiel zeigt so eine automatische Trennung nach Java-
Script und deaktiviertem JavaScript. Beispiel (kap 3_1.html):
<html lang="de"><head>
<meta http-equiv="refresh" content="2;URL=[Link]
<title>Browser-Weiche für JavaScript</title>
<script>[Link] = "[Link]
<a href="[Link] Webprojekt ohne JavaScript</a><br />
<a href="[Link] Webprojekt mit JavaScript</a>
</body></html>
Wenn man diese Webseite lädt, erfolgt eine automatische Weiterleitung per JavaScript
mit dem location-Objekt ([Link]), das von der DOM-Schnittstelle des Browsers
bereitgestellt wird. Nun wird diese Anweisung bei deaktiviertem JavaScript aber nicht
ausgeführt. Im Header finden Sie jedoch ein HTML-<meta>-Tag, das aber erst nach
einem Zeitintervall (hier zwei Sekunden und im Allgemeinen unbedingt länger als das
Weiterleitungsintervall des JavaScripts) automatisch auf ein Teilprojekt ohne JavaScript
weiterleitet. Zur Sicherheit notieren Sie noch zwei Hyperlinks, womit die Weiterleitung
auch vom Besucher manuell ausgelöst werden kann. Sie können in jedem Fall davon
ausgehen, dass JavaScript aktiviert ist, wenn die Weiterleitung oder auch das Öffnen
eines neuen Fensters funktioniert hat. Der Umkehrschluss ist jedoch nicht zwingend. In
einigen neueren Browsern können Anwender sehr differenziert angeben, welche Java-
Script-Aktionen sie tolerieren.
Elementare JavaScript-Grundstrukturen
4
Übersicht
In den folgenden Abschnitten lernen Sie elementare Grundstrukturen von Ja-
vaScript kennen. Dies sind logische Strukturen, die Sie in fast jeder Skript- und
Programmiersprache so finden und grundlegend für das Verständnis einer Sprache
sind. Im Einzelnen umfasst das Variablen, Datentypen, Operatoren, Ausdrücke,
Anweisungen und Kommentare in JavaScript. Dabei werden wir nahezu komplett
auf den Browser verzichten, sondern uns rein auf JavaScript konzentrieren und
deshalb mit [Link] arbeiten. Oft ist auch erfahrenen Webentwicklern nicht klar,
wann Sie mit purem JavaScript arbeiten und wann sie Objekte verwenden, die vom
Browser bereitgestellt werden. Durch die Ausführung in [Link] können wir sicher
sein, dass bis auf das console-Objekt nur reines JavaScript verwendet wird, sofern
wir nicht explizit ergänzende Module verwenden.
Token bedeutet übersetzt Zeichen oder Merkmal. Wenn ein JavaScript-Interpreter Quell-
code in Maschinenanweisungen übersetzen will, muss er vorher zunächst herausfinden
(oder mitgeteilt bekommen), welche Token oder Symbole im Code benutzt werden und
sie in sinnvolle Zusammenhänge setzen. Einen Token kann man deshalb auch als Sinn-
zusammenhang verstehen. So ist etwa in der menschlichen Sprache ein Wort nicht
nur die Summe seiner Buchstaben oder Zeichen beziehungsweise akustisch ein Klang,
sondern besitzt einen konkreten Sinnzusammenhang, den das interpretierende System
(der menschliche Geist) mit einer bestimmten Bedeutung assoziiert. Allerdings muss
das interpretierende System sowohl die Sprache als auch den konkreten Begriff ver-
stehen, sonst bleibt ein Token einfach nur die Summe seiner Buchstaben oder Zeichen
beziehungsweise im akustischen Fall ein Lautgebilde ohne Bedeutung. Stellen Sie sich
einfach vor, dass Sie ein Wort in einer Ihnen fremden Sprache hören. Für Sie ist das dann
kein Token, aber für jemanden, der diese Sprache versteht, schon. Aber wenn Sie sich
das Wort übersetzen lassen, dann wird es auch für Sie in Zukunft ein Token sein.
Der gesamte Teil der Assoziation mit gültigen Token bei einer Programmiersprache wird
bei der Übersetzung eines Quelltextes vom sogenannten Parser übernommen. Dieser
kann ein eigenständiges Programm sein, aber oft ist er auch in einen Interpreter oder
Compiler bereits integriert. Allgemein versteht man in der Informatik unter einem Par-
ser (engl. to parse – analysieren) ein Computerprogramm (oder einen Teil eines Pro-
gramms), das für die Zerlegung und Umwandlung einer beliebigen Eingabe in ein für die
Weiterverarbeitung brauchbares Format zuständig ist.
In unserem Zusammenhang zerlegt ein Parser einen HTML-Code oder auch einen
JavaScript-Quelltext in Token und bringt diese dann in eine Reihenfolge, sodass ein
Compiler oder Interpreter Token richtig verarbeiten kann. Der gesamte Quelltext muss
sich also vor einer konkreten Übersetzung respektive Ausführung von Anweisungen erst
einmal in logisch sinnvolle Einheiten zerlegen und in gültige Arten von Token einordnen
lassen. Sprachelemente werden dabei auf ihre Richtigkeit geprüft. Alle Operationen mit
einem Token werden mit dem spezifischen Wert des Tokens durchgeführt, also nicht mit
den Buchstaben und Zeichen des Tokens selbst, sondern mit dem, was in unserem Fall
JavaScript mit dem Token assoziiert. Es gibt in JavaScript wie in den meisten anderen
Computersprachen fünf Arten von Token, wobei die letztgenannten Kommentare, Trenn-
zeichen und Whitespaces als auch die zuerst genannten Bezeichner eine Sonderstellung
einnehmen:
JavaScript ist bezüglich seiner Syntax der Programmiersprache C angelehnt. Sie werden zwar
nicht alle Elemente und Grundstrukturen in JavaScript wiederfinden, die es in C gibt. Aber sehr
viele syntaktischen Elemente und Grundstrukturen, die Sie in JavaScript vorfinden, stammen aus
C. Deshalb werden JavaScript-Einsteiger mit einem C-Background oder Kenntnis einer syntak-
tisch verwandten Sprache wie Java, C#, Perl oder PHP sich in der Syntax gleich zurechtfinden.
Das Problem ist aber, dass die syntaktische Ähnlichkeit zwischen den Sprachen insbesondere
bei JavaScript den Blick darauf verstellen kann, dass die konzeptionellen Gemeinsamkeiten oft
gering sind und die Funktion von JavaScripts von zig Dingen (das diffizile Verhalten der Browser,
verschiedene automatische Verhaltensweisen des Interpreters, die Stelle der Einbindung eines
4.1 Token und Parser 37
Skripts in eine Webseite, die Art des Aufrufs einer Funktion, der unterschiedliche Aufbau des
DOM in verschiedenen Browsern etc.) beeinflusst werden kann, die in vielen anderen Sprachen
keine Rolle spielen. Dazu kommt der Umgang mit Datentypen.
[Link] Operatoren
Operatoren sind Token, die mit ihren Operanden Operationen ausführen, etwa mathe-
matische Berechnungen, Wertzuweisungen, Vergleiche etc. Wir gehen detailliert auf die
Operatoren unter JavaScript ein (Abschn. 4.7).
[Link] Literale
Literale sind Werte. Etwa eine Zahl oder ein Text. Aber man muss sich klarmachen, dass
es auch explizit Token (sinnbehaftete Ausdrücke) sind. Es gibt in JavaScript vordefinierte
Literale und verschiedene Schreibweise für die unterschiedlichen Arten an Literalen.
[Link] Schlüsselwörter
Unter dem Begriff Schlüsselwörter fasst man alle Wörter zusammen, die ein essenziel-
ler Teil der Sprachdefinition sind und die in JavaScript eine besondere Bedeutung haben.
Auch die Behandlung von Schlüsselwörtern wird auf verschiedene Abschnitte des Bu-
ches verteilt und an den passenden Stellen genauer abgehandelt.
[Link] Bezeichner
Mit dem Token-Typ Bezeichner oder Identifier berühren wir ein Thema, das auf ver-
schiedene Abschnitte des Buches verteilt wird. Es handelt sich hier um die Namen für
Klassen, Objekte, Variablen, Funktionen, Konstanten etc. Wir gehen an den passenden Stel-
len genauer darauf ein. Aber in Hinsicht auf einen Sinnzusammenhang gilt es deutlich zu
machen, dass ein Bezeichner Dingen einen Sinnzusammenhang zuordnet, den ein Parser
nicht erkennen kann. Der Name einer Variablen kann sprechend sein (etwa vname oder vor-
name, wenn der Vorname einer Person gemeint ist), aber aus Sicht eines Parsers ist dieser
Sinnzusammenhang nicht erkennbar.
4.2 Kommentare
• Ein einzeiliger Kommentar in JavaScript beginnt mit einem Doppel-Slash (//) und
endet mit dem Zeilenende. Das bedeutet, alle Zeichen in einer Zeile hinter dem Dop-
pel-Slash werden vom JavaScript-Interpreter ignoriert.
• Ein mehrzeiliger Kommentar beginnt mit einem Slash, gefolgt von einem Stern (/*),
schließt beliebigen Text ein und endet mit den gleichen Zeichen in umgekehrter Reihen-
folge – einem Stern gefolgt von einem Slash (*/). Diese Form von Kommentar kann über-
all beginnen und aufhören. Man nutzt diese Form eines Kommentars gerne, um ganze
Teile eines Skripts auszukommentieren und sie bei Bedarf wieder zur Verfügung zu
haben, indem man einfach Anfang- und Endezeichen des Kommentars wieder beseitigt.
• Es gibt nun in JavaScript eine Spezialversion von einem mehrzeiligen Kommentar,
der zu Dokumentationszwecken verwendet werden kann. Dabei wird der mehrzeilige
Kommentar mit /** begonnen (also zwei Sternchen). Aus Sicht von JavaScript ist das
dann nur ein gewöhnlicher mehrzeiliger Kommentar. Aber es gibt Dokumentations-
tools, die dies als Token verstehen und nachfolgenden Kommentar verarbeiten kön-
nen, um eine Dokumentation zu erstellen. Wir werden das noch genauer besprechen.
Bei der Zerlegung von Quellcode in Token werden Kommentare also aus dem Text ent-
fernt und damit keine Token im Sinn der Skript- bzw. Programmiersprache. Sie passen
dennoch in das Gesamtkonzept der Token, weil sie auf Quelltextebene wie Token notiert
werden und von spezifische Kennzeichen/Token von den Passagen im Text abgetrennt
werden, die letztendlich übersetzt werden. Zudem kann ein Kommentar zwar für das eine
interpretierende System kein Token sein, für ein zweites System jedoch schon, was schon
angedeutet wurde. Gerade bei Webseiten ist das keinesfalls abstrakt, denn man hat da
oft gemeinsam HTML- und JavaScript-Bereiche in einer Datei. Ein HTML-Kommentar
ist aus Sicht von JavaScript kein Kommentar-Token und umgekehrt ist ein JavaScript-
Kommentar-Token für HTML ohne Bedeutung. Ein HTML-Kommentar sieht etwa so aus:
4.2 Kommentare 39
Beispiel
<!-- … --> ◄
Aber das doppelte Minuszeichen (--) ist in JavaScript ein Operator (Abschn. 4.7.2). Tat-
sächlich gab es früher Browser, die deshalb beim sequenziellen Analysieren von Quell-
code Probleme hatten. In ganz alten Quellcodes findet man deshalb für interne Java-
Script-Container manchmal folgende Syntax:
Beispiel
Bewusst wird hier das veraltete Attribut language verwendet, weil dieses zu dieser Zeit
üblich war. Wobei das Attribut in unserem Zusammenhang nebensächlich ist. Es geht um
die zwei Arten von Kommentar-Token. Mit den HTML-Kommentaren wurden früher oft
die JavaScript-Anweisungen vor Browsern versteckt, die kein JavaScript kannten. Java-
Script-fähige Browser kannten hingegen auch HTML-Kommentare und haben dann trotz-
dem die enthaltenen Anweisungen ausgeführt. Der Token // hingegen hat den Operator –
als JavaScript-Kommentar versteckt. Das war die Reaktion auf den angedeuteten Fehler in
einigen alten Browsern. Diese waren so mit der Abarbeitung von JavaScript „beschäftigt“,
dass sie nicht „bemerkt“ haben, dass diese zwei Minuszeichen gar kein JavaScript mehr
waren, sondern Teil des abschließenden HTML-Kommentar-Tokens. Diese Probleme und
Details sind mittlerweile obsolet, machen aber aus meiner Sicht sehr gut deutlich, dass
Token bei einer gemeinsamen Analyse des gleichen Quellcodes durch verschiedene Parser
nur für ein System gelten können, für ein anderes nicht unbedingt. Das Verhalten haben
Sie manchmal auch bei Tools, die aus dem Quellcode eine Dokumentation erstellen. Der
oben beschriebene Dokumentationskommentar ist aus Sicht von JavaScript nur ein mehr-
zeiliger Kommentar, manche Dokumentationstools „sehen dahinter einen tieferen Sinn“.
Wenn ein Schlüsselwort im Quellcode auftaucht, wird automatisch die dafür vor-
gegebene Aktion ausgeführt, sofern es in syntaktisch korrekter Form verwendet wurde.
Andere Token sind definierte Werte für manche Situationen. Solche Wörter dürfen auf
der anderen Seite nicht als Bezeichner für Variablen oder Funktionen verwendet werden,
denn dann könnte der JavaScript-Interpreter etwa nicht entscheiden, ob ein Bezeichner
vorliegt oder ein Schlüsselwort, das eine vorgegebene Reaktion auslösen soll.
JavaScript hat aktuell benutzte Schlüsselwörter reserviert, aber auch bestimmte noch
nicht verwendete Zeichenkombinationen für zukünftige Sprachvarianten. Die nach-
folgende Tabelle enthält alphabetisch die Schlüsselwörter von ECMAScript 2021 (ES12)
bzw. den aktuell modernen JavaScript-Varianten sowie Token mit definierten Werten,
wobei Sie beachten müssen, dass in früheren Versionen manche Token u. U. keine Be-
deutung haben oder nicht vorhanden sind. Dennoch dürfen auch Token ohne Bedeutung
in diesen Versionen nicht für eigene Bezeichner verwendet werden (Tab. 4.1).
Es gibt noch weitere reservierte Token, die auch unter ES12 bzw. derzeit aktuellen
Versionen von JavaScript keine Bedeutung haben, aber teils in darauf aufbauenden Spra-
chen oder vielleicht in Zukunft. Beispielsweise sind das short, static, synchronized, tran-
sient oder volatile. Darüber hinaus gibt auch noch einige Token wie NaN, die hier nicht
aufgelistet sind, aber dennoch eine reservierte Bedeutung haben und nicht für eigene Be-
zeichner verwendet werden dürfen. Darauf kommen wir gleich zurück (Abschn. [Link]).
• Bezeichner müssen mit einem Buchstaben, dem Zeichen $ oder einem Unterstrich _
beginnen.
• Bezeichner dürfen nur Buchstaben, Ziffern und die beiden Sonderzeichen $ und _
enthalten. Deutsche Umlaute wie ä, ö, ü sind zwar möglich, sollten jedoch nicht ein-
gesetzt werden.
• Groß- und Kleinschreibung ist von Bedeutung, da JavaScript case-sensitive ist, d. h.,
zwischen Groß- und Kleinschreibung unterscheidet.
• Bezeichner dürfen keine Leerzeichen enthalten.
• Reservierte Wörter dürfen nicht als Bezeichner verwendet werden.
4.4 Bezeichner und Namenskonventionen 41
4.4.2 Namenskonventionen
Neben den verbindlichen Regeln für Bezeichner gibt es in JavaScript (wie eigentlich
in jeder Programmiersprache) Konventionen, die man bei Bezeichnern einhalten sollte.
Die Einhaltung solcher Konventionen ist technisch nicht zwingend, jedoch gerade in
JavaScript ist die Bedeutung dieser Namenskonventionen sogar exorbitant. Dabei es ist
durchaus von Bedeutung, ob Sie in großen Projekten oder mit mehreren Personen zu-
sammenarbeiten oder nur für sich alleine programmieren. Während eine Verletzung der
Namenskonventionen im letzteren Fall nur geringe Probleme macht und im Zweifels-
fall nur Sie selbst Ihre eigenen Codes nicht mehr durchblicken, kann die Verletzung von
Namenskonventionen in großen Projekten Zeitabläufe verzögern, unnötige Kosten ver-
ursachen oder Projekte gar ganz zum Scheitern bringen. Deshalb die dringende Empfeh-
lung, dass Sie Namenskonventionen zu 100 % einhalten sollten. Gerade in JavaScript,
das durch seine Freiheiten zwar sehr schlank und flexibel ist, aber ohne strenge Ein-
haltung dieser Konventionen – gerade für Einsteiger – keinerlei Halt und Orientierung
gibt. Die Beachtung der Namenskonventionen kann hingegen Syntaxstrukturen in Java-
Script durch Metainformationen eindeutig machen.
Die folgenden Konventionen haben sich in JavaScript etabliert (die genannten
Syntaxstrukturen werden in der Folge noch erläutert und an den jeweiligen Stellen
4.4 Bezeichner und Namenskonventionen 43
werden die Konventionen noch einmal rekapituliert). Beachten Sie, dass es in einigen
Zusammenhängen andere Regeln geben kann und insbesondere Microsoft die üblichen
JavaScript-Konventionen für sein Habitat an einigen Stellen verändert hat:
Sie werden die Einhaltung dieser Konventionen in den vorgefertigten Schlüsselworten, Funktio-
nen, Methoden, Klassen/Prototypen oder Konstanten von JavaScript konsequent wiederfinden.
Aber auch in zusätzlichen Programmierschnittstellen wie DOM.
4.5 Anweisungen
Eine Anweisung ist eine Quellcodezeile, die bestimme Befehle enthält. JavaScript-
Anweisungen können über mehrere Zeilen im Editor gehen, denn sie enden immer (bis
auf Ausnahmen, die Sie aber grundsätzlich nie ausnutzen sollten) erst mit einem Semi-
kolon. Dieses Semikolon teilt dem JavaScript-Interpreter des Browsers mit, dass das
44 4 Elementare JavaScript-Grundstrukturen
Ende der Anweisung erreicht ist und die Befehle nun ausgeführt werden können. An-
weisungen können nach Typen unterschieden und auch zusammengefasst werden.
4.5.1 Ausdrücke
Beispiel
gewicht = 86 + 15;
kontostand = 1000–150;
pi = 3.14; ◄
• Numerische Ausdrücke. Dies sind mathematische Operationen, etwa die Addition oder
Subtraktion von zwei Werten. Um diese Arithmetik durchführen zu können, benötigen
Sie Operatoren (Abschn. 4.7). Im Fall von numerischen Ausdrücken sind das numeri-
sche Operatoren, die auch gelegentlich arithmetische Operatoren genannt werden.
• Logische (boolesche) Ausdrücke. Damit werden Vergleiche durchgeführt. Solche Aus-
drücke gehören zu den Kerntechniken jeder Programmiersprache. Erst darüber können
Kontrollstrukturen realisiert werden. Logische Ausdrücke werden also zur Steuerung
von Programmabläufen verwendet. Bei logischen Ausdrücken wird nur nachgeschaut,
ob ein Vergleich richtig oder falsch ist. Das Ergebnis wird der Wert true sein, wenn der
Vergleich richtig war, und false, wenn der Vergleich falsch ist (Abschn. [Link]).
4.5.2 Blöcke
Blöcke sind im eigentlichen Sinn keine Anweisungen (obwohl sie auch Block-
anweisungen genannt werden), sondern die Zusammenfassung mehrerer Anweisungen
zu einer Gruppe. Diese werden in geschweifte Klammern eingeschlossen ({}). Diese
markieren Anfang und Ende eines Blocks. Der JavaScript-Interpreter des Browsers wird
einen solchen Block als Einheit behandeln und ihn als Ganzes abarbeiten. Ein Block
kann überall da stehen, wo auch eine einzelne Anweisung stehen kann. Insbesondere
4.6 Datentypen, Variablen und Literale 45
steht hinter der schließenden Klammer eines Blocks in der Regel kein (!) Semikolon.
Das Beendigungszeichen einer Anweisung muss innerhalb des Blocks nach jeder einzel-
nen Anweisung notiert werden.
Besonders interessant ist, dass in JavaScript jeder Block seinen eigenen Gültig-
keitsbereich (scope) hat. Das ist vor allen Dingen in Verbindung mit var und let
sehr interessant (Abschn. [Link]).
Variablen und Literale wurden bereits in den bisherigen Skripten verwendet. Jetzt gehen
wir die Details an. Variablen benannte Stellen im Hauptspeicher eines Computers, denen
zur Laufzeit eines Programms/Skripts temporär Werte zugeordnet werden können. Dies
erfolgt in JavaScript über ein Gleichheitszeichen (eine Wertzuweisung). Angesprochen
werden diese Variablen über Bezeichner (Namen), die sie vorher zugewiesen bekommen
haben (Deklaration). Und neben dem Bezeichner dieses Hauptspeicherbereichs ist die
Art des Inhalts von Bedeutung – der Datentyp.
Es ist bei einer Programmierung elementar zu wissen, von was für einem Typ eine Variable
ist. Die Wahl des Typs einer Variablen (auch genannt) hängt unter a Datentyp nderem
davon ab, was man mit der Variablen machen möchte. Umgekehrt legt die Art des Daten-
typs fest, was man dann mit der Variablen machen kann. Eine Variable mit Text als In-
halt ist beispielsweise von einem ganz bestimmten Datentyp. Sie werden mir sicher Recht
geben, dass die Division von zwei Texten ziemlich sinnlos ist. Es gibt aber auch Daten-
typen, in denen man Zahlen darstellen kann. Damit würde eine Division schon eher Sinn
machen. Auf der technischen Seite gibt es ebenfalls diverse Gründe, warum eine Variable
einen Datentyp benötigt. Im Wesentlichen muss das System wissen, wie viel Speicher für
eine Variable oder eine direkte Information wie eine Zahl eingeplant werden muss.
vollkommen automatisch und ohne explizites Zutun des Programmierers. Man nennt das
Verhalten auch „Duck-Typing“.
Definition Duck-Typing ist ein Konzept, das überwiegend bei der objektorientierten
Programmierung, zum Einsatz kommt, aber allgemein eine indirekte Typbestimmung
beschreibt. Der Typ eines Objektes oder einer Variablen allgemein wird durch das Vor-
handensein bestimmter Merkmale festgelegt. Der Name ergibt sich in Anlehnung an ein
Gedicht von James Whitcomb Rileys: „When I see a bird that walks like a duck and
swims like a duck and quacks like a duck, I Call that bird a duck.“ „Wenn ich einen
Vogel sehe, der wie eine Ente läuft, wie eine Ente schwimmt und wie eine Ente schnat-
tert, dann nenne ich diesen Vogel eine Ente.“
Beim Duck-Typing wird der Typ zur Laufzeit des Programms also indirekt geprüft.
Ein wesentlicher Vorteil der losen Typisierung ist neben der einfachen Einführung einer
Variablen, dass Sie den Datentyp jederzeit ohne Aufwand ändern können. Eine explizite
Typumwandelung (Casting) ist nicht notwendig. Wenn Sie beispielsweise einer Variab-
len den Wert einer Zeichenkette zuweisen, können Sie ihr später eine Zahl zuweisen und
verändern damit automatisch ihren Typ. Welcher Wert auch immer in der Variablen ent-
halten ist, er definiert den Datentyp. Dies ist bei vielen konventionellen Programmier-
sprachen (wie C und C + + und Java) anders. Dort muss der Programmierer auf jeden
Fall den Datentyp festlegen, bevor in einer Variablen ein Wert gespeichert werden kann.
In der so festgelegten Variablen kann dann auch nur ein Wert des einmal fixierten Typs
gespeichert werden. Jede Wertzuweisung eines anderen Datentyps wird einen Fehler er-
zeugen. Man nennt so etwas dann statische Typisierung von Variablen oder auch streng
typisiert.
Lose Typisierung macht den Einstieg in eine Sprache wie JavaScript (oder auch PHP) für Ein-
steiger einfach, hat aber nicht nur Vorteile. Sonst würden die mächtigeren Programmiersprachen
nicht den Aufwand mit der statischen Typisierung betreiben und auch neuere Versionen von Java-
Script und insbesondere TypeScript bieten die Möglichkeit für eine statische Typisierung. Lose Ty-
pisierung erhöht die Fehlerwahrscheinlichkeit in komplexeren Quellcodes und macht die Wartung
unter Umständen aufwendig. Merken Sie sich schon an der Stelle – obwohl JavaScript es zulässt,
sollten Sie eine Variable nicht unnötig im Typ verändern, indem Sie Literale oder Rückgabewerte
von Funktionen unterschiedlichen Typs zuweisen. Das ist schlechter Programmierstil und kann
Verständlichkeit und Wartbarkeit von Quellcode reduzieren.
JavaScript ist zwar lose typisiert und hält den Programmierer von der konkreten An-
gabe und dem Zugriff auf Datentypen fern. Das bedeutet aber nicht, dass nicht intern mit
Datentypen gearbeitet wird und man keine Informationen zu Datentypen erhalten kann
(Abschn. 4.7.1).
Hintergrundinformation
Die explizite Deklaration von Datentypen für Variablen, aber Rückgabewerte und Parameter von Funk-
tionen stehen schon lange auf der der Wunschliste mancher JavaScript-Programmierer. TypeScript
stellt solche Möglichkeiten bereit wie die Typfestlegung einer Variablen über eine Syntax der Art:
4.6 Datentypen, Variablen und Literale 47
Beachten Sie die Großschreibung der Typen. Hier wird eine Notation gewählt,
die den Konventionen von Klassen entspricht. Bei internen Abfragen des Typs
durch JavaScript etwa mit dem vorangestellten Operator typeof (Abschn. 4.7.1)
liefert der Operator den Datentyp jedoch kleingeschrieben und auch wenn Sie
Token mit den entsprechenden Werten im Quellcode schreiben, werden Sie
diese kleinschreiben müssen. Also etwa null oder undefined.
[Link] Grundtypen
JavaScript unterstützt folgende Grundtypen (Tab. 4.2).
[Link] Casting
Die genannten Datentypen kann man zu einer Typumwandlung (Casting) verwenden,
wenn das gewünscht ist. So kann man zum Beispiel aus einem String, der eine Zahl
beinhaltet, einen Number-Typ machen, indem man den String als Parameter an die
Funktion2 Number übergibt. Das kann man mit einem kleinen Script sehen, wobei wir
etwas vorgreifen und neben der Verwendung der Technik von Funktionen den Operator
typeof verwenden, mit dem der Datentyp eines Operant bestimmt werden kann ([Link]).
[Link](typeof "1");
[Link](typeof Number("1"));
Die Ausgabe wird string und number sein, was – wie oben erwähnt – den groß-
geschriebenen Datentypen entspricht und von typeof in Kleinschreibung geliefert wird.
[Link] Zusammengesetzte Datentypen
Auch drei zusammengesetzte Datentypen existieren in JavaScript. Meist werden die aber
gar nicht als explizite Datentypen wahrgenommen (Tab. 4.3).
2 Neben der Tatsache, dass bisher Funktionen noch nicht explizit erläutert wurden, stellt JavaScript
Funktionen sehr „speziell“ zur Verfügung. Das Konzept, wie Funktionen in JavaScript imple-
mentiert werden, ist genial und einfach, aber teils etwas ungewohnt. Wir kommen ausführlich im
nächsten Kapitel dazu.
48 4 Elementare JavaScript-Grundstrukturen
gilt, dass NaN wie undefined zu den sogenannten JavaScript Global Properties (globa-
len Eigenschaften) zählt. Wirklich „eigenartig“ ist, das NaN vom Datentyp Number ist,
was von der Aussagelogik ein Widerspruch zu „Not a Number“ darstellt. Aber auch die
anderen beiden Token null und undefined sollte man vom Datentyp betrachten. Während
null als Object gewertet wird, hat der Token undefined auch tatsächlich den Datentyp Un-
defined. Auch das kann man wieder mit einem kleinen Script sehen, wobei wir wieder den
Operator typeof verwenden, mit dem der Datentyp eines Operant bestimmt werden kann
([Link]).
[Link](typeof undefined);
[Link](typeof null);
[Link](typeof NaN);
Die Ausgabe wird undefined, object und number sein, denn die großgeschriebenen
Datentypen werden von typeof ja in Kleinschreibung geliefert.
Gerade bei undefined kann diese Situation etwas irritieren, weil Token und
Datentyp scheinbar keine Unterschiede bieten. Aber tatsächlich gibt es die in
der Groß- und Kleinschreibung, was typeof ein bisschen verschleiert.
Kommen wir noch einmal auf die JavaScript Global Properties zurück. JavaScript bietet
eine Reihe von Eigenschaften, die im sogenannten globalen Objekt (global object) verfüg-
bar sind. Damit sind wir beim Thema der objektorientierten Programmierung, was einen
weiteren kleinen Vorgriff sinnvoll macht, ohne an der Stelle schon zu tief einzusteigen.
Hintergrundinformation
JavaScript folgt von der Art der Erstellung von Strukturen auf den ersten Blick der funktionalen
Programmierung:
50 4 Elementare JavaScript-Grundstrukturen
• Funktionale Programmierung basiert auf der Verwendung von Funktionen als grundlegende
Bausteine. Dabei werden Funktionen als „First-Class Citizens“ betrachtet3. Sie können wie
Daten behandelt werden und beispielsweise als Argumente an andere Funktionen übergeben
oder von Funktionen zurückgegeben werden.
• Objektorientierte Programmierung (OOP) verfolgt einen anderen Ansatz zur Strukturierung
und Organisation von Code. Hier dreht sich alles um Objekte, die Daten (in Form von Eigen-
schaften oder Attributen) und Verhalten (in Form von Methoden) kapseln. Klassen sind Bau-
pläne für die Erstellung von Objekten und Vererbung ermöglicht es, Eigenschaften und Metho-
den von einer Klasse zu einer anderen zu übernehmen.
Doch die Art, wie man in JavaScript von Anfang an schon programmiert hat, verschleiert die Tat-
sache, dass Funktionen und Variablen zwar auf globaler Ebene deklariert werden können, aber dass
es ein globales Objekt gibt, zu dem sie automatisch dynamisch hinzugefügt werden. Das Konzept
des globalen Objekts existiert seit den frühen Versionen von JavaScript und war von Anfang an
Teil der Sprachspezifikation, ohne dass dies vielen Programmierern klar war. In den meisten Skrip-
ten musste man dies auch gar nicht bewusst beachten. Dieses Verhalten unterscheidet sich von
den meisten strengeren objektorientierten Programmiersprachen, aber über diese Strategie schafft
JavaScript einen Spagat zwischen einfacher Programmierung und der Umsetzung wichtiger OO-
Konzepte im Hintergrund, die ein Anwender/Programmierer teils gar nicht bemerkt. Das globale
Objekt dient als Container für verschiedene globale Variablen und Funktionen, die in einer Java-
Script-Anwendung verwendet werden können, und setzt auch globale Variablen und Funktionen in
einen logischen Objektkontext. In Browserumgebungen ist das globale Objekt window, während in
[Link]-Umgebungen das globale Objekt global ist. Dies ermöglichte den Zugriff auf Dinge wie
den DOM (Document Object Model) in Browsern oder Systemfunktionen in [Link].
Das globale Objekt wird von einer JavaScript-Engine vor jeder Ausführung irgendeines
Kontextes erstellt wird und integriert die build-in-Elemente von JavaScript in den Kon-
text. Tatsächlich sind auch Datentypen wie Number oder Boolean Eigenschaften des glo-
balen Objekts mit Funktionsreferenzen als Werten. Deshalb kann man auch das eben ge-
sehene Casting darüber vornehmen. Die tiefergehenden Details behandeln wir bei den
Funktionen und der OOP.
Neben den Sondertoken, den genannten zusammengesetzten Datentypen sowie String
gibt es mit Math und Date zwei eingebaute Objekte, die wir in der Folge auch noch aus-
führlich behandeln. Diese Objekte als auch die JavaScript Global Properties sind überall
im JavaScript-Code zugänglich und können direkt verwendet werden, um auf wichtige
3 Wenn Sie Erfahrung in der OOP haben, können Sie sich First-Class Citizens ein bisschen wie sta-
tische Member vorstellen, wobei der Vergleich „gewagt“ ist. Aber man kann argumentieren, dass
statische Member in bestimmten Sprachen als eine Form von First-Class Citizens betrachtet wer-
den können, da statische Methoden in Klassen ähnlich wie normale Funktionen verwendet werden.
Dennoch sind sie konzeptionell im Grunde verschieden und bedienen unterschiedliche Zwecke in
ihren jeweiligen Programmierparadigmen.
4.6 Datentypen, Variablen und Literale 51
Funktionen und Informationen zuzugreifen. Es sind also Token, die überall im Skript
automatisch verfügbar sind.
Viele gibt es in JavaScript nicht, denn mit Infinity und globalThis ist die Aufzählung
bereits vollständig.
• Die Token Infinity und –Infinity repräsentieren einen numerischen Wert für die posi-
tive oder negative Unendlichkeit.
• Mit globalThis erhalten Sie eine Referenz auf das globale Objekt, unabhängig vom
aktuellen Ausführungskontext. Es bietet eine plattformübergreifende Möglichkeit, auf
das globale Objekt zuzugreifen, unabhängig davon, ob der Code in einem Browser,
auf dem Server oder anderswo ausgeführt wird. Allerdings wird der Token nicht in
allen Systemen unterstützt.
Auf Token wie undefined, Infinity und null können Sie in JavaScript direkt mit
Operatoren (Abschn. 4.7.3) prüfen, auf NaN hingegen nicht. Dazu müssen Sie
eine spezielle Standardfunktion von JavaScript verwenden. Darauf gehen wir
genauer ein.
Auch wenn man ohne weitere Syntaxvorbereitungen nur eingeschränkte Beispiele erstellen
kann, wollen wir uns dennoch etwas Quellcode ansehen (javascript_global_properties.js).
var a;
[Link](1 / 0);
[Link](globalThis);
[Link](NaN);
[Link](a);
• Zuerst deklarieren wir eine Variable, die keinen Wert zugewiesen bekommt. Ein Zu-
griff auf die Variable mündet in dem wohldefinierten Wert undefined, den wir in der
letzten Zeile des Quellcodes ausgeben. Es ist kein Widerspruch, hier von „wohl-
definiert“ zu sprechen. Ganz und gar nicht. Denn das Skript hat bei einem Zugriff kei-
nen Fehler. Stattdessen gibt es einen Token mit einem eben wohldefinierten Zustand,
mit dem man weiterarbeiten kann. Es ist nur eine Frage der Logik, ob dieser undefi-
ned-Zustand sinnvoll ist oder nicht.
• In Zeile 2 führen wir eine Division durch 0 durch. Resultat ist der Wert „Unendlich“.
Auch das wird in JavaScript als ein wohldefinierter Zustand gewertet und das Skript
wird ohne Fehler fortgesetzt.
• In Zeile 3 greifen wir auf den globalen Kontext zu. Beachten Sie die unterschiedlichen
Eigenschaften unter Windows 11 (Abb. 4.1) und einem Linux-System (Abb. 4.2).
• Die letzte Ausgabe zeigt auf triviale Weise, dass NaN im Quellcode als Token verfüg-
bar ist.
52 4 Elementare JavaScript-Grundstrukturen
Abb. 4.1 Zugriff auf globale Eigenschaften unter [Link] auf einem Windows 11-System
4.6.3 Variablen
Abb. 4.2 Zugriff auf globale Eigenschaften unter Linux bei [Link]
aber auch weggelassen, um besondere Techniken und Effekte zu zeigen. In der Pra-
xis sollten Sie diese Einstellung aber immer verwenden. Allgemein wird man also
(globale) Variablen mit dem einleitenden Schlüsselwort var oder let deklarieren. Dies
dient es unter anderem der besseren Lesbarkeit, hat aber noch andere Konsequenzen.
Darauf gehen wir gleich ein (Abschn. [Link]).
• Variablen, die Sie über die Angabe var bzw. let deklarieren und denen Sie keinen
Wert zuweisen, haben nach der Deklaration den Zustand bzw. Wert undefined. Das
ist der oben schon beschriebene wohldefinierter Zustand, auch wenn der Bezeichner
möglicherweise etwas Anderes suggeriert.
• Variablen, die weder über var oder let noch durch eine Wertzuweisung deklariert wur-
den, erzeugen bei der Verwendung einen Laufzeitfehler. So können Sie beispielsweise
nicht den Wert einer Variablen auslesen, wenn diese nicht deklariert wurde. Die Inter-
pretation des JavaScript-Codes wird abgebrochen.
• Mehrere Variablen können in einem Schritt durch Kommata getrennt deklariert wer-
den. Diese Anweisung kann über mehrere Zeilen verteilt werden. Einzelne Variablen
können dabei initialisiert werden oder auch nicht.
• Einer Variablen können in JavaScript im Programmverlauf Werte verschiedener
Datentypen zugewiesen werden (lose Typisierung).
Initialisieren bedeutet, dass eine Variable mit einem Anfangswert belegt wird.
54 4 Elementare JavaScript-Grundstrukturen
Es wurde schon thematisiert, aber da es extrem wichtig ist, soll noch einmal
darauf hingewiesen werden. Da JavaScript case-sensitive ist, verweisen z. B.
die Variablen autofarbe und autoFarbe auf unterschiedliche Speicherbereiche.
Ein kleines Beispiel soll jetzt deutlich machen, was "use strict" bewirkt (use_strict.js):
"use strict";
k = 10;
[Link](k);
Wenn Sie das Programm ausführen, bekommen Sie die Fehlermeldung Uncaught Refe-
renceError ReferenceError: k is not defined. In Zeile 2 wird versucht, dem noch nicht
vorhandenen Bezeichner k einen Wert zuweisen. Ohne "use strict" würde damit ein-
fach eine neue Variable angelegt. Mit "use strict" wird das unterbunden. Sie sollten sich
unbedingt klarmachen, dass diese Unterbindung einen Quantensprung in der Software-
qualität darstellt. Denn sonst können ungewollt durch Schreibfehler oder Unachtsam-
keit neue Variablen entstehen. Dabei ist deren Entstehung meist das kleinere Problem.
Schlimmer ist, dass man bei so einem Fehler normalerweise einer vorhandenen Variablen
einen Wert zuweisen wollte und das damit schiefgegangen ist. Das Skript bzw. Pro-
4.6 Datentypen, Variablen und Literale 55
gramm läuft damit in einem inkonsistenten, fehlerhaften Zustand weiter, der oft nicht
bemerkt wird.
• Sichtbarkeit und Gültigkeitsbereich: Eine mit var eingeführte Variable ist überall im
Code des aktuellen Gültigkeitsbereichs (engl. Scope) (global oder lokal innerhalb
einer Funktion oder eines Blocks), aber auch teils übergeordnet und untergeordnet gül-
tig, während eine mit let deklarierte Variable innerhalb des Scopes, in dem sie definiert
wurde, gültig ist, aber nicht oberhalb und auch von untergeordneten Scopes verdeckt
werden kann. Mit let deklarierte Variablen haben eine explizite Block-Scope-Sichtbar-
keit. Das bedeutet, dass sie nur innerhalb des Blocks, in dem sie deklariert wurden, sicht-
bar sind, sei es innerhalb einer Funktion, Schleife oder eines anderen Blockstrukturen.
• Hoisting: Variablen, die mit var deklariert werden, werden gehoistet, was bedeutet,
dass die Deklaration an den Anfang ihres Scope-Bereichs verschoben wird. Die Zu-
weisung bleibt jedoch an ihrer ursprünglichen Stelle. Das bedeutet, dass Variablen-
und Funktionsdeklarationen unabhängig davon, wo sie innerhalb eines Gültigkeits-
bereichs deklariert sind, so behandelt werden, als ob sie am Anfang des Gültigkeits-
bereichs stehen. Dies kann manchmal zu unerwartetem Verhalten führen. Mit let
deklarierte Variablen werden ebenfalls gehoistet, aber im Gegensatz zu var werden sie
nicht initialisiert, bevor der Code ausgeführt wird. Zugriffe auf let-Variablen vor ihrer
Deklaration führen zu einem Referenzfehler (Uncaught ReferenceError).
• Wiederverwendung von Variablenbezeichnern: Mit var können Sie denselben
Variablennamen mehrmals innerhalb desselben Scope deklarieren. Dies kann zu un-
erwarteten Fehlern oder Konflikten führen. Bei Verwendung von let innerhalb des-
selben Blocks mit demselben Bezeichner bei der Deklaration verschiedener Variablen
führt dies zu einem Syntaxfehler. Es sollte klar werden, dass das dann nicht ein Prob-
lem ist, sondern Lösung von einem Problem.
Allgemein ist die Verwendung von let also moderner und sicherer, aber let wird nicht in
alten Browsern unterstützt. Schauen wir uns einige Beispiele an.
"use strict";
// Beispiel mit var - Hoisting
[Link](a); // Ausgabe: undefined
var a = 5;
// Beispiel mit let - Hoisting
56 4 Elementare JavaScript-Grundstrukturen
Für die Variable a in dem Quellcode let_vs_var_hoisting.js erhalten Sie den Zustand
undefined bei der Ausgabe, obwohl die Deklaration im Scope erst nach dem Zugriff er-
folgt. Für die Variable b hingegen die Fehlermeldung Uncaught ReferenceError Referen-
ceError: Cannot access 'b' before initialization und das Skript wird beendet. Damit wird
ein inkonsistenter Zustand verhindert. Hier folgt ein Beispiel zur Wiederverwendung
von den gleichen Bezeichnern für Variablen bei mehrfachen Deklarationen (let_vs_var_
[Link]).
"use strict";
// Beispiel mit var - Wiederverwendung von Variablennamen
var c = 1;
var c = 2; // Kein Fehler, die Variable c wird überschrieben
// Beispiel mit let - Wiederverwendung von Variablennamen
let d = 3;
let d = 4; // Fehler: Identifier 'd' has already been declared
Und jetzt sehen Sie noch die unterschiedliche Sichtbarkeit in verschiedenen Scopes.
JavaScript definiert neben dem globalen Scope das Innere von Funktionen als eigenen
Scope, aber Funktionen haben wir noch nicht behandelt. Aber JavaScript gestattet auch
die Angabe von getrennten Gültigkeitsbereichen über Blöcke. Das sind Bereiche, die mit
geschweiften Klammern begrenzt werden (let_vs_var.js).
"use strict";
// Beispiel mit var
{
{
var x = 10;
}
[Link](x); // Ausgabe: 10, da var Funktions-Scope
}
// Beispiel mit let
{
{
let y = 20;
}
[Link](y); // Fehler: y ist nicht definiert, da let Block-Scope
}
Obwohl die mit var deklarierte Variablen in einem untergeordneten Block deklariert ist,
kann man im übergeordneten Block darauf zugreifen. Bei der Variable, die mit let de-
klariert wurde, geht das nicht.
4.6 Datentypen, Variablen und Literale 57
4.6.4 Konstanten
JavaScript besitzt vorgegebene Konstanten. Man kann in neuen Versionen von JavaScript
aber auch mit const eigene Konstanten deklarieren, wobei man zwingend den Wert zu-
weisen muss. Konstanten haben während der Programmausführung immer einen unver-
änderlichen Wert. Sie können also keine zweite Wertzuweisung an eine Konstante vor-
nehmen, nachdem sie initialisiert wurde. Im Kern unterscheiden sich ansonsten Variablen
und Konstanten nicht sonderlich.
• Genau wie Variablen werden Konstanten mit einem Namen und einem Datentyp ver-
einbart. Der Datentyp ergibt sich wie bei normalen Variablen automatisch aus der An-
gabe des Wertes über Duck-Typing.
• Konstanten werden per Konvention vollständig großgeschrieben. Wobei dies nicht
immer beachtet wird.
Kommen wir noch einmal auf Literale zurück, die es in unterschiedlichen Versionen in
JavaScript ging. Man kann zu deren Notation einige Schreibweisen verwenden, die Sie
kennen sollten.
[Link] Ganze Zahlen
Einfache Literale für ganze Zahlen sind trivial. Also Wertdarstellungen wie 1,2, 3 etc.
Ganze Zahlen besitzen keine Nachkommastellen und können negative sowie positive
Werte und den Wert null annehmen. In JavaScript können ganze Zahlen in einer von drei
möglichen Darstellungen eingesetzt werden:
• Dezimaldarstellung: Dies ist die besagte triviale und gebräuchlichste Darstellung von
Zahlen. Die Zahl wird im Dezimalsystem (Basis 10) dargestellt, d. h., dass die Ziffern
0.9 verwendet werden.
• Hexadezimaldarstellung: Die Zahlen werden im Hexadezimalsystem (Basis 16) dar-
gestellt. Die verwendbaren Ziffern sind 0.9, A.F (A = 10, B = 11 … F = 15). Hexa-
dezimale Zahlen werden durch ein einleitendes 0x (bzw. 0X) gekennzeichnet und die
Buchstaben können auch kleingeschrieben werden. Beispiele wären 0XA oder 0Xc.
• Die Oktaldarstellung (zur Basis 8) ist seit JavaScript 1.5 nicht mehr Bestandteil
von JavaScript und wird teils gar nicht, teils nur noch aus Gründen der Abwärts-
kompatibilität unterstützt. Oktalzahlen werden durch eine vorangestellte 0 (Null) ge-
kennzeichnet, wobei man im strikten Modus ein O oder o ergänzen muss. Nur ohne
"use strict" ist ein oktales Literal wie 0755 für den dezimalen Wert 493 noch gültig.
"use strict";
let k = 10, l = 0xA, m = 0o111;
[Link](k, l, m);
58 4 Elementare JavaScript-Grundstrukturen
Bei l sehen Sie eine hexadezimale und bei m eine oktale Darstellung ([Link]).
In JavaScript können Sie den Maximalwert von ganzen Zahlen mithilfe der Kons-
tanten Number.MAX_SAFE_INTEGER bestimmen, wobei wir hier etwas auf OOP vor-
greifen. Diese Konstante repräsentiert den größten Wert, der sicher als ganze Zahl dar-
gestellt werden kann, ohne dass es zu einem Rundungsfehler oder einem anderen Rand-
effekt kommt. Hier ist ein Beispiel, wie Sie den Maximalwert von ganzen Zahlen in
JavaScript bestimmen können (max_ganzezahlen.js):
"use strict";
[Link](Number.MAX_SAFE_INTEGER); // Ausgabe: 9007199254740991
Dieser Wert entspricht 2^53–1, was die maximale Anzahl darstellt, die sicher als ganze
Zahl in JavaScript verwendet werden kann. Beachten Sie, dass man auf manchen Platt-
formen auch größere ganze Zahlen darstellen kann. Es gibt auch eine Number.MIN_SAFE_
INTEGER-Konstante, die den minimalen sicheren Wert für ganze Zahlen repräsentiert.
In neueren Versionen von JavaScript gibt es zudem mit BigInt einem eingebauten
Datentyp samt einem eigenen Literal, der es ermöglicht, ganze Zahlen beliebiger Größe
darzustellen, ohne das Risiko von Überlauf oder Verlust von Genauigkeit bei gro-
ßen Zahlen. Damit kann der begrenzte Wertebereich von herkömmlichen JavaScript-
Nummern gesprengt werden. Sie können BigInt-Literale erstellen, indem Sie einer gan-
zen Zahl das Suffix "n" anhängen oder indem Sie den BigInt()-Konstruktor verwenden.
Hier sind einige Beispiele:
Beispiel
"use strict";
let bigIntLiteral = 1234567890123456789012345678901234567890n;
let bigIntConstructed = BigInt("[Link].[Link].[Link].
567.890");
[Link](bigIntLiteral);
[Link](bigIntConstructed); ◄
BigInt-Werte können jedoch nicht direkt mit anderen Datentypen gemischt werden.
Diese müssen explizit in BigInt-Typen umgewandelt werden, um Operationen zwischen
ihnen durchführen zu können. Sie können auch nicht mit BigInt-Werten mathematische
Operationen wie [Link]() durchführen. BigInt-Werte können auch nicht mit allen Ver-
gleichsoperatoren mit regulären numerischen Werten verglichen werden.
„trivial“ nenne, muss eine Sache beachtet werden. In JavaScript wird die amerikanische
Notation, also ein Punkt (.), verwendet, um die Dezimalstellen einer Zahl kenntlich zu
machen. Die deutschen Bezeichner sind also irreführend und „Floatingpoint“ drückt viel
deutlicher aus, dass der Punkt zur Trennung verwendet wird. Man kann aber auch einen
Exponenten oder beides angeben. Dazu erfolgt eine Kennzeichnung mit E oder e.
"use strict";
let num = 3.14e2; // 3.14 * 10^2 = 314
[Link](num); // Ausgabe: 314
let num2 = 1.5E-3; // 1.5 * 10^(-3) = 0.0015
[Link](num2); // Ausgabe: 0.0015
In diesem Beispiel ([Link]) wird 3.14e2 als 3.14 multipliziert mit 10^2
interpretiert, was 314 ergibt. Ähnlich wird 1.5E-3 als 1.5 multipliziert mit 10^(-3) inter-
pretiert, was 0.0015 ergibt. Die Exponentialnotation wird häufig verwendet, um sehr
große oder sehr kleine Zahlen in einer kompakten Form darzustellen. Bei einer Fix-
kommazahl ist die Position des Kommas vorgegeben. Allerdings kennt JavaScript kein
explizites Literal, sondern wandelt eine Gleitkommazahl mit einer Methode um.
"use strict";
let num = 123.456;
[Link]([Link](2)); // Ausgabe: 123.46
"use strict";
[Link]("Max: ", Number.MAX_VALUE);
[Link]("Min: ", Number.MIN_VALUE);
Wenn Sie in große Zahlenbereiche vorstoßen, gibt es ein paar Dinge in JavaScript zu be-
achten. Beim Überschreiten des Wertebereichs der ganzen Zahlen wird teils automatisch
eine Konvertierung in Gleitkommazahlen erfolgt. Aber das ist von der jeweiligen Platt-
form abhängig. Gleitkommazahlen unterliegen aber dabei auch immer potenziellen
Rundungsfehlern. Beachten Sie dies bei Berechnungen mit großen Zahlen. Irgendwann ist
aber auch der Wertebereich der ganzen Zahlen als auch der Gleitkommazahlen erschöpft
und das Ergebnis wird der wohldefinierte Wert Infinity (ueberlauf_ganzezahlen.js).
60 4 Elementare JavaScript-Grundstrukturen
"use strict";
let z1 = 5000, z2 = 2.1, z3 = z1 ** Number.MAX_SAFE_INTEGER,
z4 = z2 * Number.MAX_VALUE;
[Link](z1, z2, z3, z4);
let z5 = 12345678901234567890;
// liefert 12345678901234567000, da die Zahl zuerst in eine
// Gleitkommazahl konvertiert und danach gerundet wird !
[Link]("z5 hat den Wert:", z5);
Die Wirkung der Steuerzeichen ist meist nicht in der Ausgabe in einer Webseite
zu sehen. Bei der Ausgabe von Text im Browser müssen Sie stattdessen für ei-
nige Steuerzeichen HTML-Tags verwenden, da der Browser etwa Tabulatoren
und Zeilenumbrüche im HTML-Code als Leerzeichen interpretiert (Tab. 4.4).
Beispiel (string_maskierungen.js):
"use strict";
// Escape-Sequenzen in einem String mit einfachen Anführungszeichen
let string1 = 'Dies ist ein String mit Escape-Sequenzen: \n\t - Neue
Zeile \n\t - Tabulator \n\t - Einzelnes Anführungszeichen (\') \n\t -
Doppeltes Anführungszeichen (")';
[Link](string1);
// Escape-Sequenzen in einem String mit doppelten Anführungszeichen
let string2 = "Dies ist ein String mit Escape-Sequenzen: \n\t - Neue
Zeile \n\t - Tabulator \n\t - Einzelnes Anführungszeichen (') \n\t -
Doppeltes Anführungszeichen (\")";
[Link](string2);
Vorsicht Beachten Sie, dass auch andere Typen an Literalen in JavaScript für
boolesche Ausdrücke stehen. Das unterscheidet JavaScript von Sprachen wie
Java oder C#, wohingegen sich alte Sprachen wie C oder Perl genauso ver-
halten. In JavaScript werden folgende Werte als false interpretiert, wenn sie in
einem booleschen Kontext verwendet werden:
• false
• 0 (als Zahl)
• –0 (als Zahl)
• 0n (als BigInt)
• "" oder (der leere String)
• null
• undefined
• NaN (Not-a-Number)
62 4 Elementare JavaScript-Grundstrukturen
JavaScript interpretiert diese Werte allerdings nicht direkt als boolesch. Wenn
Sie jedoch einen dieser Werte in einem booleschen Kontext verwenden, wird
eine implizite Umwandlung durchgeführt. So ein Kontext entsteht beim Ver-
wenden in einer Bedingung bei Entscheidungsstrukturen oder Schleifen oder
aber durch eine explizite Umwandlung, was man mit Boolean() machen kann.
Alle anderen Werte, einschließlich aller anderen numerischen Werte, werden
als true interpretiert, wenn sie in einem booleschen Kontext verwendet
werden.
Hier ist ein Beispiel ([Link]), das zeigt, wie Werte in einem booleschen Kon-
text interpretiert werden:
"use strict";
let a = 0, b = 10, c = -5, d = "", e = 0n, f = "t";
[Link](Boolean(a), Boolean(b), Boolean(c), Boolean(d), Boolean(e),
Boolean(f));
Hintergrundinformation
Wenn Sie Variablen deklarieren, reservieren Sie dafür Speicherplatz. Vollkommen unabhängig
davon, welche Inhalte sie diesen zuweisen. Grundsätzlich wird Speicherplatz benötigt. Auch beim
Erzeugen von Objekten. Dieser muss irgendwann wieder freigegeben werden, wenn diese Struk-
turen nicht mehr benötigt werden. Garbage Collection (kurz GC) in JavaScript ist der Prozess,
bei dem nicht mehr benötigte Speicherbereiche (wie nicht referenzierte Objekte und Variablen)
automatisch erkannt und freigegeben werden, um Ressourcen zu sparen und Speicherlecks zu ver-
meiden. Das funktioniert in JavaScript im Wesentlichen nach der Methode Mark-and-Sweep (Mar-
kieren und Aufräumen). Der Algorithmus besteht aus zwei Phasen:
• Beim Markieren durchläuft der GC alle erreichbaren Objekte, die von Wurzelobjekten aus er-
reichbar sind (zum Beispiel globale Objekte, lokale Variablen im aktuellen Ausführungskontext,
usw.). Alle erreichbaren Objekte werden markiert.
• Beim Aufräumen durchläuft der GC den gesamten Speicherbereich und entfernt alle nicht mar-
kierten Objekte, da sie nicht mehr erreichbar sind und somit nicht mehr benötigt werden. Dieser
Schritt stellt sicher, dass nur noch Objekte im Speicher verbleiben, die tatsächlich verwendet
werden.
Ein weiterer Ansatz für die GC ist die sogenannte Referenzzählung (Reference Counting). In Py-
thon ist das beispielsweise der Kern von dessen GC-Konzept, in JavaScript hingegen eher selten.
Bei der Referenzzählung wird jeder Wert im Speicher verfolgt, und die Anzahl der Referenzen
auf jedes Objekt wird gezählt. Wenn die Anzahl der Referenzen auf null sinkt, wird das Objekt
als nicht mehr benötigt angesehen und der Speicher wird freigegeben. Dieser Ansatz hat jedoch
den Nachteil, dass er Zyklen nicht effektiv behandeln kann und daher nicht so robust ist wie der
Mark-and-Sweep-Algorithmus. Die GC in JavaScript ist eine automatische und transparente Funk-
tion, die in modernen JavaScript-Engines implementiert ist. Entwickler müssen sich normaler-
weise nicht direkt um die GC kümmern, aber es ist wichtig zu verstehen, wie sie funktioniert, um
Leistungsprobleme zu vermeiden und den Speicherverbrauch zu optimieren.
4.7 Operatoren 63
4.7 Operatoren
Über den Operator typeof, den wir schon intensiv verwendet haben, haben Sie die Möglich-
keit, den Typ eines nachgestellten Operanden zu bestimmen. Sie werden entweder einen
der Standardtypen von JavaScript erhalten (kleingeschrieben) und auf diese Token auch in
Bedingungen vergleichen können oder aber Sie erhalten den Wert null oder undefined.
Mit delete haben Sie hingegen einen Operator zum Löschen von Objekten bzw.
Objekteigenschaften zur Verfügung. Dazu geben Sie diese als nachgestellte Operanden
an. Der Operator liefert im Erfolgsfall den Wert true und bei Misserfolg den Wert false.
Beachten Sie, dass Sie mit delete keine vordefinierten Standardobjekte und
-objekteigenschaften von JavaScript löschen können. Ebenso können in den
meisten aktuellen Browsern nur Variablen und Objekte mit delete gelöscht
werden, die nicht mit dem Schlüsselwort var bzw. let deklariert wurden. Aus
dem Grund ist der Einsatz von delete zum Löschen von Variablen selten. Statt-
dessen setzt man besser eine Variable explizit auf null oder undefined, wenn
man diese nicht mehr benötigt und freigeben will.
Um delete in der Praxis zu sehen, benötigen wir explizit OOP oder zumindest Arrays.
Deshalb wird erst da ein Beispiel gezeigt.
Arithmetische Operatoren dienen zur Berechnung eines Wertes. Dazu wird eine Opera-
tion auf einen oder mehrere Operanden angewendet (Tab. 4.5).
64 4 Elementare JavaScript-Grundstrukturen
Der Operator für die Potenz ist ziemlich neu und steht in alten Versionen von Java-
Script nicht zur Verfügung. Für die vier Grundrechenarten Addition, Subtraktion, Multi-
plikation und Division verwendet JavaScript die üblichen Zeichen. Bei den zugehörigen
Operatoren handelt es sich um binäre Operatoren, die eine Zahl als Ergebniswert liefern.
Der Modulo-Operator berechnet den Rest einer ganzzahligen Division. Bei der ganz-
zahligen Division der Zahl 23 mit der Zahl 5 bleibt ein Rest von 3 (23 = 4 * 5 + 3), d. h.,
die Zahl 5 ist viermal in der Zahl 23 enthalten. Der Rest von 3 ist nicht mehr ganzzahlig
durch 5 teilbar. Modulo ist in JavaScript auch auf Gleitkommazahlen anwendbar. Die
folgenden Anweisungen sind also gültig:
Beispiel
7.1 % 5
7.1 % 3.4 ◄
"use strict";
[Link](17 % 3);
[Link](7.1 % 3.4);
4.7 Operatoren 65
"use strict";
var x = 5; var y = 1.5;
[Link]("x = " + x + "; y = " + y);
[Link]("x++ + y ergibt: " + (x++ + y));
x = 5; y = 1.5;
[Link]("x = " + x + "; y = " + y);
[Link]("x + ++y ergibt: " + (x + ++y));
x = 5; y = 1.5;
[Link]("x = " + x + "; y = " + y);
[Link]("x-- + y ergibt: " + (x-- + y));
x = 5; y = 1.5;
[Link]("x = " + x + "; y = " + y);
[Link]("x + --y ergibt: " + (x + --y));
• Die Variablen x und y werden mit den Werten 5 und 1.5 initialisiert.
• Zuerst werden die Werte von x und y addiert und ausgegeben, erst danach wird x
automatisch um den Wert 1 erhöht.
• Die Variable y wird schon vor der Addition um 1 erhöht und dann erst mit dem Wert
von x addiert.
66 4 Elementare JavaScript-Grundstrukturen
• Die Werte von x und y werden addiert und ausgegeben. Dann erst wird x um 1 ver-
ringert.
• Erst nachdem y um 1 verringert wurde, werden beide Variablen addiert.
Um deutlich zu machen, wie man diese Operatoren jedoch für ganz schlechten
Programmierstil missbrauchen kann, versuchen Sie das Ergebnis nachzuvollziehen (ink-
[Link]).
"use strict";
var x = 1;
[Link](x++ + ++x + ++x + x++ + x + ++x);
Das ist legitimer Code. Aber vollkommen unlesbar. Nutzen Sie In- und Dekrement nur
als einzelne Anweisungen. Alles andere ist aus meiner Sicht Sabotage.
Aber grundsätzlich sind arithmetische Operatoren eher einfach und problemlos. Nur
ein Token ist mehrdeutig – das Pluszeichen. Dieser Operator kann auch als Konkatena-
tionsoperator (engl.: concatenate = verknüpfen) oder Stringverkettungsoperator dienen
und verknüpft zwei Zeichenketten mit der zusammengesetzten Zeichenkette als Ergeb-
niswert.
Elemente einer Zeichenkette können auf der anderen Seite zwar im Grunde nicht arith-
metisch verwertet werden. Aber was ist, wenn darin nur Zahlen stehen? Interessant wird
4.7 Operatoren 67
• '1' + 1 wird zu '11'. Die Zahl 1 wird vor der Verbindung zu einem String konvertiert
und dann ist das eine Stringverkettung. Immer (!) wenn ein String an einer Opera-
tion mit dem Plusoperator beteiligt ist, wird der andere Operand zu einem String kon-
vertiert und dann eine Stringverkettung vorgenommen.
• '1' * 1 wird zu 1. Der String mit der Zahl 1 als Inhalt wird vor der Verbindung zu
einem Number konvertiert und dann ist das eine einfache Multiplikation. Immer (!)
wenn eine Multiplikation, Division, Subtraktion, Modulo und De- sowie Inkrement
ausgeführt wird, wird eine Typkonvertierung der Operanden auf Number versucht,
wenn sie nicht sowieso vom Typ Number sind. Geht das schief, liefert die Berechnung
den Wert NaN (not a number = keine Zahl).
• Bei einem Ausdruck mit mehreren gleichwertigen Operationen wird von links nach
rechts ausgewertet und gegebenfalls mehrfach der Datentyp von Operanten ge-
wandelt. Jede einzelne Teiloperation wird unabhängig bewertet.
"use strict";
var zahl = 1, zeichen = '1';
var erg = zahl + zahl; // 2;
[Link](erg, typeof(erg));
erg = zeichen + zeichen; // '11';
[Link](erg, typeof(erg));
erg = zeichen - zeichen; // 0;
[Link](erg, typeof(erg));
erg = '1a' * zahl; // NaN
[Link](erg, typeof(erg));
erg = zahl + zahl + zeichen; // '21' - 1 + 1 ist 2, dann 2 + '1'
[Link](erg, typeof(erg));
erg = zeichen + zahl + zahl; // '111' - '1' + 1 ist '11', dann '11' +
1
[Link](erg, typeof(erg));
erg = "5" % 2; // ergibt 1 - '5' wird zu 5 gecastet
[Link](erg, typeof(erg));
erg = ++zeichen; // ergibt 2 - '1' wird vor der Erhöhung zu 1 ge-
castet
[Link](erg, typeof(erg));
11 string
0 number
NaN number
string
111 string
1 number
2 number
Tab. 4.6 Vergleichsoperatoren
Operator Bedeutung Beschreibung Beispiele Ergebnis
!= Ungleichheit Vergleich, ob zwei Operanden im Wert 8 ! = 4 + 4 false
nicht identisch sind. Der Vergleich lie-
fert true, wenn zwei Ausdrücke rein im
Wert nicht gleich sind
! = = Typungleichheit Durch lose Typisierung von JavaScript 7 ! = 4 + 4 true
hat man als wenig Kontrolle darüber,
von welchem Datentyp eine Variable
ist. Dennoch kann es von Bedeutung
sein, einen bestimmten Datentyp zur
Verfügung zu haben. Neben der Gleich-
heit der reinen Werte zweier Operanden
muss auch der Datentyp im Vergleich
berücksichtigt werden. Mit diesem
Operator können Sie einen Vergleich
durchführen, ob zwei Operanden ent-
weder im Wert oder im Datentyp oder
beidem nicht übereinstimmen
< Kleiner-als Vergleich, ob der erste Operand kleiner "4" ! = = 4 true
als der zweite Operand ist
<= Kleiner-als-oder- Vergleich, ob der erste Operand kleiner 4 ! = = 2 + 1 true
gleich oder gleich dem zweiten Operanden ist
= = Gleichheit Vergleich, ob zwei Operanden im Wert 5<6 true
identisch sind. Das doppelte Gleich-
heitszeichen darf nicht mit dem Zu-
weisungsoperator (=) verwechselt
werden!
= = = Identität Vergleich, ob zwei Operanden sowohl 6 <=6 true
im Wert als auch Datentyp überein-
stimmen
> Größer-als Vergleich, ob der erste Operand größer 4 = = 5 false
als der zweite Operand ist
>= Größer-als-oder- Vergleich, ob der erste Operand größer "4" = = 4 true
gleich oder gleich dem zweiten Operanden ist
4.7 Operatoren 69
4.7.3 Vergleichsoperatoren
Vorsicht Wenn Sie zwei Operanden auf strikte Gleichheit prüfen, müssen
diese den gleichen Wert und den gleichen Datentyp besitzen. Wird auf ein-
fache Gleichheit geprüft, können auch Typumwandlungen zu einer Aus-
wertung true führen. Im Falle von strikter Gleichheit ist dies nicht möglich. Das
wird auch Identität genannt.
[Link](2 = = "2"); // liefert true, da nur Werte geprüft werden.
[Link] (2 = = = "2"); // liefert false, da unterschiedliche Typen vorliegen.
Werden Vergleichsoperatoren auf Zeichenketten angewendet, ist die Reihenfolge der Zei-
chen im String von Bedeutung. Es wird Zeichen für Zeichen geprüft. Also das erste Zei-
chen des einen Operanten mit dem ersten Zeichen des zweiten Operanten usw.
1. !"#$%&'()* + ,-./
2. 0123456789
3. :;< = >?@
4. ABCDEFGHIJKLMNOPQRSTUVWXYZ
5. [\]^_`
6. abcdefghijklmnopqrstuvwxyz
7. {|} ~
Betrachten wir ein Beispiel, das mit Kommentaren das Verhalten erläutert ([Link]).
"use strict";
[Link](2 == "2"); // liefert true, da nur Werte geprüft werden
[Link] (2 === "2"); // liefert false, da unterschiedliche Typen
[Link]("a" < "A"); // liefert false, da a höhere Wertigkeit als A
[Link] (1 < "a"); // liefert false, da 1 höhere Wertigkeit als a
70 4 Elementare JavaScript-Grundstrukturen
Beispiel (logische_op.js).
"use strict";
let a = 2, b = 3, c = 4, d = 17;
// liefert false, da bereits Vergleich 1 false ergibt
[Link]((d % b < 2) && (d - a * b > 100) && (c + d > a));
// liefert true, da Vergleich 3 true ergibt
[Link]((d % b < 2) || (d - a * b > 100) || (c + d > a));
// liefert ja, da Vergleich true ergibt
[Link]((a < b) ? "ja" : "nein");
Bei den Vergleichen wurde ein spezifisches Verhalten im Hintergrund genutzt. Ausdrücke
werden einzeln von links nach rechts ausgewertet. Jeder einzelne bewertete Ausdruck
liefert true oder false. Wenn sich an der Auswertung des Gesamtausdrucks durch die
Auswertung weiter rechts stehende Teilausdrücke nichts mehr ändern kann oder bereits
das Kriterium für die Gesamtbewertung erfüllt ist, wird die Auswertung abgebrochen.
Beispiel (lazy_evaluation.js):
"use strict";
[Link]((4 > 3) || (5 > 6)); // true, da Vergleich 1 bereits true
[Link]((4 > 3) && (5 > 6)); // false, da Vergleich 1 bereits false
4.7.5 Bit-Operatoren
Bit-Operatoren werden auf Zahlen und boolesche Werte angewendet, die entsprechend ihrer
binären Darstellung verknüpft werden (die Bits können z. B. für bestimmte Eigenschaften
stehen). Die booleschen Werte true und false entsprechen dabei den Werten 1 und 0.
4.7 Operatoren 71
Beispiel
entstandenen Leerstellen immer mit Nullen auf, unabhängig vom Vorzeichen des Ope-
rands. Dieser Operator wird hauptsächlich für die Behandlung von großen positiven
Ganzzahlen oder für die Erstellung von Hashing-Funktionen verwendet, bei denen das
Vorzeichen nicht berücksichtigt werden soll. Beispiel:
Beispiel
In der Regel wird der <<<-Operator nicht so häufig verwendet wie der <<-Operator, da
die Notwendigkeit, Vorzeichenbits zu ignorieren, relativ selten ist. Der <<<-Operator
ist jedoch nützlich, wenn eine bitweise Verschiebung ohne Berücksichtigung des Vor-
zeichens erforderlich ist.
• 0 & 0=0
• 0 & 1=0
• 1 & 0=0
• 1 & 1=1
Das Ergebnis zweier mit UND verknüpfter binärer Zahlen führt also dazu, dass nur bei
den Bits, an denen in beiden Operanden übereinstimmend eine Eins steht, nach der Ope-
ration wieder eine Eins zu finden ist. Beispiel:
• 0 | 0=0
• 0 | 1=1
• 1 | 0=1
• 1 | 1=1
Hier ergibt sich nur dann 0, wenn die verglichenen Bits in den zwei Operanden beide
eine Null aufweisen. Beispiel:
Beispiel
Beispiel
• 0 ^ 0=0
• 0 ^ 1=1
• 1 ^ 0=1
• 1 ^ 1=0
Es wird eine Eins im Ergebnis gesetzt, wenn die Bits sich unterscheiden. Andernfalls ist
das Resultat Null. Betrachten wir an einem Beispiel eine konkrete binäre XOR-Verknüp-
fung.
4.7 Operatoren 75
Beispiel
Das bitweise NICHT (~) ist die Negation eines jeden Bits. Es wird in der Bitdarstellung
eines Operanden jede Null durch eine Eins und jede Eins durch eine Null ersetzt. Die
Wirkung dieses Vorgangs auf die Zahl besteht darin, dass ihr Vorzeichen umgedreht (aus
einer positiven Zahl wird also eine negative) und der Wert 1 subtrahiert wird. Dieser bit-
weise Komplementoperator unterscheidet sich deutlich von den anderen bitweisen Ope-
ratoren. Er legt eine einstellige bitweise Operation fest. Das bedeutet, er hat nur einen –
nachgestellten – Operanden, genau wie ein negatives Vorzeichen bei einer normalen
Zahl. Beispiel für die binäre Nichtoperation:
Beispiel
Schauen wir uns die bitweisen Operationen in einem gemeinsamen Beispiel an (bit-
weise_operationen.js).
"use strict";
var zahl = 42;
var a = zahl << 1;
var b = zahl >> 1;
var c = zahl << 2;
var d = zahl << 3;
var e = zahl >>> 3;
var f = zahl >>> 2;
[Link]("Der Wert von 42 << 1: " + a );
[Link]("Der Wert von 42 >> 1: " + b );
[Link]("Der Wert von 42 << 2: " + c );
[Link]("Der Wert von 42 << 3: " + d );
[Link]("Der Wert von 42 >>> 3: " + e );
76 4 Elementare JavaScript-Grundstrukturen
Zu dem Listing sind aufgrund der vorbereitenden Argumente keine weiteren Erklärungen
notwendig, aber Sie sollten die Ausgabe ansehen, die die obigen Ausführungen beweist.
Wenn man die bitweisen Verknüpfungsoperatoren auf boolesche Ausdrücke an-
wendet, erhält man im Grunde ein gleiches Verhalten wie bei „normalen“ Verknüpfungs-
operatoren. Zumindest bei den Operatoren für UND bzw. ODER. XOR und den Komple-
mentoperator gibt es ja nur als bitweise Operatoren. Dennoch lohnt es sich, das Ver-
halten genauer anzusehen. Denn es gibt interessante Situationen, wobei Sie – wie oben
erwähnt – zu beachten haben, dass bei diesen Operationen false für 0 und true für 1 steht.
Weiter gilt zu beachten, dass bei diesen Operationen keine Lazy Evaluation benutzt wird.
Das logische Schema von XOR liefert das:
"use strict";
[Link]("XOR");
[Link](false ^ true);
[Link](false ^ false);
[Link](true ^ true);
[Link](false ^ false);
[Link]("Komplement");
[Link](~false, ~true);
[Link]("Keine Lazy Evaluation");
[Link](false & true);
0
0
0
Komplement
-1 -2
Keine Lazy Evaluation
0
4.7.6 Zuweisungsoperatoren
Beispiel
let x = 2;
x ** = 3; // Jetzt ist x gleich 8 (2^3).
let str = "Hello";
str + = " World"; // Jetzt ist str gleich "Hello World". ◄
• Bei dem Modulo- und Zuweisungsoperator würde a % = 5 den Wert von a Modulo 5
nehmen. Die alternative Schreibweise wäre a = a % 5;.
• Beim Multiplikations- und Zuweisungsoperator multipliziert die Anweisung a * = 5
den Wert der Variablen a mit dem Wert 5. Die alternative Schreibweise wäre a = a * 5;.
Beispiel
Die Operatoren in JavaScript haben eine festgelegte Rangordnung (Priorität), die immer
dann angewandt wird, wenn in einem Ausdruck mehrere Operatoren verwendet wer-
den und keine Klammern gesetzt werden (Klammern sind in der Rangfolge mit auf-
genommen und haben die höchste Priorität). Als Beispiel kennen Sie die Punkt-vor-
Strich-Regel. Die Prioritäten sind von der höchsten Wertigkeit bis zur niedrigsten ab-
wärts angegeben.
1. () [].
2. ! ~ - + + –
3. * / %
4. + -
5. << >> >>>
6. < < = > > =
7. = = ! = = = = ! = =
8. &
9. ^
10. |
11. &&
12. ||
13. ?:
14. = + = - = << = >> = & = ^ = |=
15. ,
Kontrollflussanweisungen
5
Übersicht
Kontrollstrukturen sind klassische Anwendungen von logischen oder ver-
gleichenden Operatoren und spezielle Anweisungsschritte, mit denen man Ent-
scheidungen über den weiteren Ablauf eines Programms oder Skripts vorgeben
kann, wenn bestimmte Bedingungen eintreten. Kontrollstrukturen gehören in
einer Programmiersprache zu den einfachen syntaktischen Strukturen. In Java-
Script haben Sie die gleichen Kontrollstrukturen zur Verfügung, die es in fast allen
Programmiersprachen in gleicher oder ähnlicher Form gibt. Dementsprechend
wird man sich mit bereits elementaren Grundlagen in irgendeiner Programmier-
sprache damit vermutlich sehr leichttun. Es gibt neben der trivialen Sequenz (ein-
fache Abarbeitung der Reihe nach) und der noch trivialeren leeren Anweisung (in
JavaScript ein Semikolon) drei Arten von Kontrollflussanweisungen:
• Entscheidungsanweisungen. Diese suchen aufgrund einer Bedingung einen
Programmfluss heraus.
• Schleifen bzw. Iterationsanweisungen. Diese wiederholen eine bestimmte An-
zahl an Anweisungen.
• Sprunganweisungen. Diese verlassen eine Struktur.
5.1 Entscheidungsanweisungen
Über die if-Kontrollstruktur entscheiden Sie, ob eine nachfolgende Anweisung oder ein
Block auszuführen ist. Allgemeine Syntax ist folgende:
Beispiel
Wenn eine Bedingung wahr ist, werden die Anweisungen A (entweder eine einzelne
Anweisung oder eine Blockanweisung) ausgeführt, ansonsten die alternativen An-
weisungen, wobei die else-Anweisung optional ist. Unter Bedingung versteht man einen
Vergleich, der entweder wahr (true) oder falsch (false) sein kann oder den booleschen
Wert selbst.
"use strict";
// Einbinden des 'readline' Moduls von [Link]
const readline = require('readline');
// Konfigurieren des readline interfaces
const rl = [Link]({
input: [Link], output: [Link]
});
// Frage stellen und auf Benutzereingabe warten
[Link]('Wie ist dein Name? ', (name) => {
// Benutzereingabe verarbeiten
[Link](`Hallo ${name}!`);
// readline interface schließen
[Link]();
});
Das Programm wird eine Frage nach dem Namen des Benutzers stellen und nach Ein-
gabe des Namens eine Begrüßung ausgeben. Das Konzept, das hier verwendet wird,
nennt sich „Benutzereingabe über die Befehlszeile“ oder „Befehlszeileninteraktion“. Es
5.1 Entscheidungsanweisungen 81
ermöglicht einem [Link]-Programm, mit dem Benutzer über die Befehlszeile zu inter-
agieren, indem es auf Eingaben wartet und dann entsprechend reagiert. Hier ist eine de-
tailliertere Erklärung des Codes, die – wie erwähnt – einige neue Techniken verwendet,
die später noch vertieft werden und nur Basis für etwas interessantere Beispiele mit
Kontrollstrukturen bieten soll:
Hintergrundinformation
Die Ausgabe in einer Konsole über [Link] ist in der Grundversion sehr einfach. Die Ver-
wendung von dem readline-Module für die Benutzereingabe über die Befehlszeile bietet jedoch
mehr Kontrolle und Funktionalität im Vergleich zur einfachen Verwendung von [Link]. Hier
sind einige Gründe, warum das Modul verwendet wird:
• Interaktive Eingabe: Mit der Verwendung des Moduls kann das Programm auf Benutzerein-
gaben warten und dann entsprechend reagieren. Dies ermöglicht eine interaktive Erfahrung, bei
der das Programm auf Eingaben des Benutzers reagiert, anstatt einfach statische Ausgaben zu
generieren.
• Mehrere Eingaben: Mit dem Modul können mehrere Eingaben vom Benutzer in einer einzigen
Ausführung des Programms verarbeitet werden. Das bedeutet, dass das Programm nicht nur
einmalig etwas ausgeben kann, sondern auch auf mehrere Eingaben des Benutzers reagieren
kann.
• Benutzerfreundlichkeit: Durch die Verwendung von dem Modul können Programme Benutzern
Fragen stellen und auf ihre Antworten warten. Dies macht Programme benutzerfreundlicher und
82 5 Kontrollflussanweisungen
ermöglicht es Entwicklern, interaktive Anwendungen zu erstellen, die auf die Bedürfnisse ihrer
Benutzer eingehen können.
• Validierung von Eingaben: Das Modul bietet Mechanismen zur Validierung von Benutzerein-
gaben. Bevor eine Eingabe verarbeitet wird, kann das Programm überprüfen, ob sie den er-
warteten Anforderungen entspricht.
Kommen wir nun zum eigentlichen Thema zurück und testen in einem Beispiel die if-
Anweisung (if_anweisung.js), wobei wir am Beginn die Eingabe über [Link] wie oben
beschrieben ermöglichen (das wird auch in der Folge aus Platzgründen nicht mehr ab-
gedruckt).
…
[Link]("Geben Sie eine Zahl zwischen 0 und 10 ein: ", (erg) => {
[Link]("Sie haben " + erg + " eingeben!");
if (erg > 7) {
[Link]("Der Wert ist zu groß");
} else {
[Link]("Alles klar");
}
[Link]();
});
Die Benutzereingabe über die Konsole erfolgt wie oben beschrieben. Je nach ein-
gegebenem Wert wird dann der eine oder der andere Programmfluss ausgewählt.
Sie können auch im else-Zweig direkt eine if-Anweisung notieren und damit mehrere
Bedingungen prüfen sowie aus mehreren Programmflüssen auswählen. Etwa so (zur
Demonstration dieses Mal ohne Blockanweisungen):
Beispiel
In diesem Beispiel wird der Wert der Variablen zahl überprüft. Je nachdem, ob die Be-
dingungen erfüllt sind oder nicht, wird eine entsprechende Ausgabe generiert:
• Wenn zahl größer als 10 ist, wird „Die Zahl ist größer als 10“ ausgegeben.
• Wenn zahl genau 10 ist, wird „Die Zahl ist genau 10“ ausgegeben.
5.1 Entscheidungsanweisungen 83
• Wenn zahl kleiner als 10 ist (und nicht gleich 10), wird „Die Zahl ist kleiner als 10“
ausgegeben.
In JavaScript gibt es auch die Fallunterscheidung über switch-case. Ein Vorteil gegen-
über if-else ist, dass Sie auf einfache und übersichtliche Weise mehrere Fälle unter-
scheiden können. Das ist zwar auch mit if-else möglich, indem Sie – wie gesehen – meh-
rere if-Abfragen hintereinanderschreiben und ggf. mit vorangestelltem else verknüpfen
oder verschachteln. Jedoch erscheint die Fallunterscheidung mit switch-case vielen
Programmierern als eleganter. Die Syntax einer switch-case-Anweisung sieht formal
folgendermaßen aus:
Beispiel
switch([auswahl]){
case [Fall 1]: break;
case [Fall 2]: break;
…
default:
}
Mit dem Schlüsselwort switch leiten Sie die Fallunterscheidung ein. Als Argument wird
in runden Klammern eingeschlossen eine Testvariable oder ein Ausdruck angegeben,
für dessen aktuellen Wert Sie die Fallunterscheidung durchführen. Dies ist der Testwert
und es kann ein beliebiger Datentyp sein, der unter JavaScript erlaubt ist (beispiels-
weise eine Zahl, jedoch auch ein Text oder Objekt). Die einzelnen Fälle, zwischen denen
unterschieden werden soll, werden untereinander aufgelistet und innerhalb geschweifter
Klammern als Blöcke notiert. Jeden einzelnen Fall leiten Sie mit dem Schlüsselwort case
ein, gefolgt von der konkreten Angabe des Werts, auf den Sie prüfen wollen.
Die Anweisungen bei einem case-Block können Sie zwar in geschweifte Klam-
mern notieren, in der Praxis verzichtet man jedoch in der Regel darauf.
Die Anweisung break, die am Ende jedes Blocks notiert ist, ist eine – in JavaScript op-
tionale – Sprunganweisung. Damit stellen Sie sicher, dass nach einem Treffer die nach-
folgend notierten Fälle nicht ebenso ausgeführt werden. Bei dem switch-case-Konstrukt
handelt es sich um eine Fall-Through-Anweisung. Fehlt eine Sprunganweisung am Ende
jedes Blocks, werden ab einem Treffer alle folgenden Anweisungen ausgeführt, was in
84 5 Kontrollflussanweisungen
der Praxis oft nicht gewünscht ist. Beim letzten Block in der switch-case-Anweisung ist
das break immer überflüssig.
In der Tat wird die switch-case-Anweisung fast immer in Verbindung mit break erläutert und auch
so eingesetzt. Ich persönlich bin aber der Meinung, dass ein Hauptvorteil dieses Konstrukts gerade
der „durchfallende“ Charakter ohne break ist. Dieses Verhalten kann man mit der if-else-Struktur
nur mit Mühe erreichen. Beachten Sie die nachfolgenden Beispiele, in denen wir die switch-case-
Anweisung sowohl mit als auch ohne break einsetzen.
Der Fall default ist dafür da, eine Standardreaktion für den Fall zu definieren, dass kei-
ner der von Ihnen definierten Fälle zutrifft. Dieser Zweig ist optional und kann entfallen,
wenn es keinen Vorgabefall geben soll. Er entspricht dem else-Teil bei einer if-else-
Konstruktion.
• Die Überprüfung des Testwerts erfolgt auf Identität. Das bedeutet, dass sowohl der
Wert als auch der Datentyp übereinstimmen müssen. Das ist deshalb bemerkenswert,
weil in JavaScript in fast allen anderen Fällen bei einem Vergleich nur die Wertgleich-
heit überprüft und bei unterschiedlichen Datentypen im Hintergrund automatisch ge-
castet wird. Das ist hier explizit nicht der Fall.
• Wenn Sie bei mehreren hintereinander angeordneten Fällen alle Anweisungen und
insbesondere das break weglassen, können Sie eine Art diskreten Wertebereich für
Treffer angeben. Ganz ohne Anweisungen werden für alle diskreten Treffer die glei-
chen Anweisungen ausgeführt, andernfalls immer ein Teil (ab dem Treffer).
• Die Testfälle müssen nicht in irgendeiner Reihenfolge notiert werden, obwohl man in
der Praxis meist eine chronologische Reihenfolge notieren wird, sofern dies sinnvoll
ist (vor allen Dingen bei Zahlen – bei Texten oder Objekten ist das natürlich nur be-
grenzt zweckmäßig).
…
[Link]("Geben Sie eine Zahl zwischen 0 und 10 ein: ", (erg) => {
[Link]("Sie haben " + erg + " eingeben!");
switch(erg * 1) {
case 1: [Link]("Das führt zur Auswahl 1."); break;
5.1 Entscheidungsanweisungen 85
In dem Beispiel wird wie in dem vorangegangenen Beispiel mit vom Anwender eine
Zahl entgegengenommen. Beachten Sie hier aber den kleinen Trick der Multiplikation
mit dem Wert 1 – (erg *1). Der Rückgabewert der beim Einlesen eine Benutzereingabe
ist immer ein String, im nachfolgenden Test wollen wir aber mit numerischen Werten
vergleichen. Die Multiplikation eines Strings, der nur numerische Werte enthält, mit der
Zahl 1 verändert den Wert nicht, konvertiert aber den Datentyp des Ergebnisses der Ope-
ration in einen numerischen Typ und damit haben wir gegebenenfalls auch die Identität
des Datentyps. Mit der nächsten Zeile wird der Wert der eingegebenen Zahl dann wieder
in die Webseite geschrieben ([Link]("Sie haben" + erg + "eingeben!");). Dann wird
der Wert der Variablen erg getestet (switch(erg * 1)). Je nach Eingabe des Anwenders
wird ein Fall ausgewählt und die Anweisung break unterbricht die weitere Abarbeitung
dieser Fallunterscheidung und springt hinter den switch-Block. Die nachfolgende Va-
riante des Beispiels verzichtet nun auf das break. Zudem wollen wir nun bei dem switch-
case auf Zeichenketten überprüfen (switch_anweisung2.js):
…
[Link]("Geben Sie eine Zahl zwischen 0 und 10 ein: ", (erg) => {
[Link]("Sie haben " + erg + " eingeben!");
switch(erg) {
case "1": [Link]("Das führt zur Auswahl 1.");
case "2": [Link]("Das führt zur Auswahl 2.");
case "3": [Link]("Das führt zur Auswahl 3.");
case "4": [Link]("Das führt zur Auswahl 4.");
default: [Link]("Der Vorgabefall wird genommen.");
}
[Link]("Nach der switch-case-Anweisung.");
[Link]();
});
Die entscheidenden Veränderungen in dem Beispiel sind wie gesagt der Verzicht auf
break und der Verzicht auf den kleinen Trick der Multiplikation mit dem Wert 1. Damit
steht in der Variable erg ein Datentyp String. Die nachfolgend notierten Trefferfälle von
case sind deshalb auch als Strings notiert. Wenn Sie nun als Besucher der Webseite einen
Wert eingeben, werden alle Ausgaben ab dem ersten Treffer zu sehen sein. Geben Sie
86 5 Kontrollflussanweisungen
etwa den Wert 3 ein, erhalten Sie nach der Kontrollausgabe noch drei Zeilen als weitere
Ausgaben.
Den Verzicht auf break kann man sinnvoll nutzen. Denke Sie z. B. an ein
Reservierungssystem, mit dem Sie ab einem bestimmten Tag bis zum Ende
der Woche reservieren wollen.
5.2 Iterationsanweisungen
Die auszuführenden Anweisungen bei Schleifen notiert man in der Regel als
Block. Gibt es nur eine Anweisung, kann die Blocknotation entfallen. Aber das
sollte man nur sparsam machen.
Die while-Schleife ist abweisend und führt am Anfang eines Blocks eine Prüfung einer
Bedingung durch. Die nachfolgenden Anweisungen werden so lange durchlaufen, bis die
Bedingung nicht mehr erfüllt ist. Die Syntax sieht schematisch so aus:
Beispiel
Wenn eine Bedingung beim Erreichen der Schleife nicht mehr erfüllt ist, werden die An-
weisungen im Block überhaupt nicht durchgeführt. Normalerweise kontrolliert man die
Anzahl der Wiederholungen mit einer Zählvariable, die vor dem Beginn der Schleife de-
klariert und normalerweise in der Schleife erhöht wird. Oder man bricht den Schleifen-
lauf gezielt ab, was mit break möglich ist. Nach dem Sprung aus der Schleife mit break
geht es im Programmfluss hinter der Schleife weiter. Das ist insbesondere in Verbindung
mit dem Prüfen einer Bedingung über if oft sinnvoll.
Die while-Schleife benötigt (in der Regel) in ihrem Inneren eine Möglichkeit,
mit der die Schleife abgebrochen oder die Voraussetzungen für einen neuen
Durchlauf verändert werden kann (etwa durch Änderung der Zählvariablen).
Andernfalls erzeugen Sie eine Endlosschleife. Eine Endlosschleife ist eine
nicht endende Abarbeitung der Anweisungen im Inneren. Das kann zwar
sinnvoll sein (bei einer Animation zum Beispiel), ist aber normalerweise nicht
5.2 Iterationsanweisungen 87
Der Abbruch einer while-Schleife erfolgt oft darüber, dass eine in der Bedingung ge-
prüfte Variable im Inneren so verändert wird, dass irgendwann die Bedingung nicht mehr
erfüllt ist. Sie sehen es in der nachfolgenden Übung (while_anweisung.js):
"use strict";
var i = 0;
while (i < 10) {
i++;
[Link](i + "\t:\t ", i * i);
}
Das Beispiel erzeugt eine Ausgabe mit zehn Zeilen. Zuerst ist der Wert der Zählvariablen
i für unsere Schleife 0. Die Bedingung in der while-Überprüfung ist also wahr und die
Anweisungen werden ausgeführt. Nach der Erhöhung des Werts werden in einer Aus-
gabe mit zwei Spalten der Wert des Zählers und sein Quadrat ausgegeben. Wir haben den
Inkrementoperator verwendet, der dem Ausdruck i = i + 1; entspricht. Das Durchlaufen
der Schleife wird so lange fortgesetzt, bis die Variable den Wert 10 erreicht. Dann ist
die Bedingung in der while-Überprüfung falsch und die Anweisungen werden nicht mehr
ausgeführt.
Die do-while-Schleife ist eine Variante der normalen while-Schleife, bei der die Prüfung
erst nach dem ersten Durchlauf der Schleifenanweisungen erfolgt. Dese Schleife ist an-
nehmend oder fußgesteuert. Bei do-while werden die im Inneren notierten Anweisungen
auf jeden Fall einmal ausgeführt. Die schematische Syntax sieht so aus:
Beispiel
Da die Anwendung ansonsten grundsätzlich der while-Schleife entspricht, wollen wir auf
ein praktisches Beispiel verzichten.
88 5 Kontrollflussanweisungen
Eine weitere Schleifenform wird über das Schlüsselwort for eingeleitet. Die allgemeine
Syntax sieht schematisch so aus:
Beispiel
Die for-Schleife ist die kompakteste Form der Schleifen, denn sie enthält im Schleifen-
kopf alle relevanten Daten für die Abarbeitung der Schleife. Die erste Angabe in der
Klammer nach dem Schlüsselwort for gibt in der Regel die Zählvariable an und initia-
lisiert sie mit einem Startwert. Danach folgt – durch ein Semikolon getrennt – die Ab-
bruchbedingung und als dritte Angabe – wieder durch ein Semikolon getrennt – meist
die Wertänderung der Zählvariablen.
Für viele Programmierer ist die for-Schleife die sicherste Form einer Schleife, da direkt
am Beginn der Schleife in einer Zeile alle relevanten Informationen für den Schleifen-
durchlauf stehen. Zwar kann man auch in einer for-Schleife komplexere Strukturen auf-
bauen (beispielsweise im Inneren die Zählvariable ändern oder eine interne Abbruch-
bedingung über break schaffen), aber in der Grundform sieht man hier auf einen Blick
alle relevanten Dinge und die Fehlerwahrscheinlichkeit ist relativ gering.
Die for-Schleife nennt man oft auch zählergesteuert. Durch den Aufbau des
Schleifenkopfs eignet sich diese Schleife zur Verwendung von schleifen-
lokalen Variablen. Eine Zählvariable, die im Schleifenkopf deklariert wird,
ist nur im Schleifenblock gültig. Beachten Sie, dass eine for-Schleife nicht
zwangsläufig zählergesteuert ist und die anderen Schleifen auch zähler-
gesteuert sein können. Eine Syntax wie for(;;) für den Schleifenkopf ist voll-
kommen legitim. Aber es gibt eine „übliche“ Art der Verwendung und in der
nutzt man die for-Schleife zählergesteuert und wählt auch umgekehrt für eine
Anwendung, die zählergesteuert sein soll, oftmals die for-Schleife.
Nehmen wir uns als Übung das Beispiel, welches wir bei while schon hatten, und modi-
fizieren es (for_anweisung.js). Diese Version von dem Programm fragt den Benutzer
nach einer Zahl zwischen 1 und 10. Wenn die eingegebene Zahl im erlaubten Bereich
liegt, werden die Quadrate der Zahlen von 0 bis zur eingegebenen Zahl (ausschließlich)
ausgegeben. Andernfalls wird eine Meldung ausgegeben, die darauf hinweist, dass die
Eingabe außerhalb des erlaubten Bereichs liegt:
5.2 Iterationsanweisungen 89
…
[Link]("Geben Sie eine Zahl zwischen 1 und 10 ein", (erg) => {
if (erg > 0 && erg < 11) {
for (var i = 0; i < erg; i++) {
[Link](`${i}\t:\t${i * i}`);
}
}
else {
[Link]("Die Eingabe liegt nicht im erlaubten Wertebereich");
}
[Link]();
});
Das Beispiel erzeugt vollkommen analog dem Beispiel mit der while-Schleife eine
Ausgabe mit einer gewissen Anzahl an Zeilen. Allerdings sind alle Informationen zum
Durchlauf der Schleife im Schleifenkopf notiert, sowohl der Wert der Variablen für
unsere Schleife, die Bedingung als auch die Werterhöhung über den Inkrement-Operator.
Aber auch ansonsten gibt es ein paar Modifikationen, damit das Beispiel nicht nur eine
reine Wiederholung der ersten Variante ist:
Schleifen lassen sich auch verschachteln. Das bedeutet, dass Sie innerhalb einer Schleife
eine weitere Schleife ausführen. Dadurch können Sie komplexe Muster durchlaufen oder
Iterationen über mehrdimensionale Datenstrukturen durchführen. Die Arten der Schlei-
fen können unterschiedlich, aber auch gleich sein. Gegebenenfalls benötigt man dann
einen anderen Zählindex für innere Schleifen. Hier ist ein einfaches Beispiel für ver-
schachtelte Schleifen in JavaScript:
90 5 Kontrollflussanweisungen
In diesem Beispiel (verschachtelte_schleifen.js) haben wir eine äußere Schleife, die von
0 bis 2 läuft, und eine innere Schleife, die ebenfalls von 0 bis 2 läuft. Während die äu-
ßere Schleife ihren Durchlauf macht, durchläuft die innere Schleife ihren gesamten
Durchlauf für jeden Durchlauf der äußeren Schleife. Das Ergebnis ist, dass jedes mög-
liche Paar von Werten für i und j ausgegeben wird, was einem 3 × 3-Raster entspricht.
Wenn Sie diesen Code ausführen, erhalten Sie die Ausgabe:
i: 0, j: 0
i: 0, j: 1
i: 0, j: 2
i: 1, j: 0
…
i: 2, j: 2
Wie Sie sehen können, wurden alle Kombinationen von i und j durchlaufen, wobei i von
0 bis 2 und j für jeden Wert von i von 0 bis 2 läuft.
Schleifen sind insbesondere im Zusammenhang mit Arrays von Bedeutung. Wir werden bei deren
Behandlung darauf zurückkommen und insbesondere Spezialvarianten der for-Schleife (for-in, for-
of und forEach) nachtragen – diese macht auch nur bei Arrays und Objekten wirklich Sinn.
5.3 Sprunganweisungen
JavaScript stellt eine gewisse Anzahl an Sprunganweisungen zur Verfügung. Diese ver-
lassen eine umgebende syntaktische Struktur und führen dazu, dass mit der direkten An-
weisung hinter der syntaktischen Struktur weitergemacht wird. Der Begriff einer „syn-
taktischen Struktur“ ist recht abstrakt, aber eine solche syntaktische Struktur kennen
Sie – Schleifen. Allgemein wird eine Schleife beendet, wenn die überprüfte Bedingung
nicht mehr erfüllt ist. Schleifen sind aber recht kritische Stellen in jedem Programm oder
Skript. Es kann leicht zu Endlosschleifen oder ungewollten Durchläufen kommen. Oder
Sie brauchen mehrere Bedingungen, bei denen eine Schleife oder ein Schleifendurchlauf
zu beenden ist.
5.3 Sprunganweisungen 91
"use strict";
let counter = 0;
while (true) {
[Link]("Die Schleife läuft noch!");
counter++;
// Überprüfung, ob der Zähler 5 erreicht hat
if (counter === 5) {
[Link]("Der Zähler hat 5 erreicht. Die Schleife wird beendet.");
break; // Verlasse die Schleife
}
}
In diesem Beispiel wird eine Endlosschleife mit while (true) erstellt, die nur durch das
break-Statement verlassen wird. Innerhalb der Schleife wird auf der Konsole eine Nach-
richt ausgegeben und der Zähler erhöht. Wenn der Zähler den Wert 5 erreicht, wird eine
Abschlussnachricht ausgegeben und die Schleife mit break beendet. Wenn Sie dieses
Beispiel ausführen, wird die Ausgabe wie folgt sein:
Wie Sie sehen können, wird die Schleife nach Erreichen des Zählerwerts 5 verlassen.
Neben break gibt es im Zusammenhang mit Schleifen noch die Sprunganweisung con-
tinue. Sie können damit an einer bestimmten Stelle innerhalb des Schleifenblocks un-
mittelbar den nächsten Schleifendurchlauf erzwingen und die nachfolgenden An-
weisungen innerhalb des Schleifenblocks ignorieren. Die Verwendung von continue
findet man in der Praxis nicht oft. Alternativ lassen sich Bedingungen fast immer so for-
92 5 Kontrollflussanweisungen
mulieren, dass man darauf verzichten kann. Die Anwendung ist aber etwa dann effektiv,
wenn man mit einer Suche eine Liste durchläuft und in einigen Konstellationen nach-
folgende Anweisungen überspringen möchte. Wir erstellen zum Test wieder ein Beispiel
(continue_anweisung.js):
"use strict";
for (var i = 0; i < 10; i++) {
[Link]("");
if ((i % 2) != 0) continue;
[Link]("Der Wert ist ", i);
}
• Die erste Anweisung des Schleifenblocks wird für jeden Durchlauf ausgeführt (Aus-
gabe Leerzeichen).
• Danach wird unter Verwendung des Modulo-Operators und des Ungleich-Operators
in einer if-Bedingung getestet, ob die Zählvariable ungerade ist.
• Falls der Test true liefert, wird continue ausgeführt und sofort ein weiterer Durchlauf
der Schleife gestartet. Der Wert der Zählvariable wird dabei natürlich um 1 erhöht.
Die folgende Zeile im Block der for-Schleife wird dabei übersprungen.
• Falls die Zählvariable jedoch gerade ist, wird die Anweisung jedoch ausgeführt (Aus-
gabe des Werts der Zählvariablen).
Die vierte Sprunganweisung bei JavaScript nennt sich throw. Damit werfen Sie eine so-
genannte Ausnahme aus, die den normalen Programm- bzw. Skriptablauf unterbricht und
zu einer Behandlungsroutine springt. Wir behandeln Ausnahmebehandlung in einem spä-
teren Kapitel mit fortgeschrittenen JavaScript-Techniken.
Arrays, JSON und andere iterierbare
Elemente 6
Übersicht
Im Fokus des Kapitels steht ein erster Kontakt mit Arrays und dem JSON-Format.
Vereinfacht erlauben diese Strukturen das Speichern von mehreren Werten. Beides
sind aber auch Strukturen, die eng mit dem objektorientierten Ansatz in JavaScript
verbunden sind. Auf der einen Seite müsste man sich vor einer Behandlung von
JSON und Arrays die objektorientieren Konzepte noch genauer ansehen. Auf der
anderen Seite sind Arrays so elementar, dass man sie in mehreren Kontexten be-
nötigt, die nicht explizit OOP darstellen. Wie üblich in JavaScript geht man also
vielfach mit Arrays und oft auch JSON um, ohne deren Bedeutung im objekt-
orientierten Kontext zu beachten und teils sich sogar überhaupt bewusst zu sein.
Deshalb wird hier das grundsätzliche Konzept vorgestellt und später betrachten
wir Arrays und JSON erneut. Dann aber explizit unter dem OOP-Aspekt. Zudem
werden wir ganz kurz einen Blick auf Maps, Sets und ein paar weitere iterierbare
Datenstrukturen werfen.
6.1 Arrays
Arrays haben eine große Bedeutung in der Programmierung, wobei in JavaScript die Be-
deutung noch größer ist als in den meisten anderen Programmiersprachen.
Ein Array (auch Feld oder Datenfeld) ist im Prinzip ein Speicherbereich, in dem Sie
mehrere Werte speichern können. Zugriff auf die Werte erfolgt über einen Bezeichner
(eine Variable, die auf das Array verweist) und einen Index (Schlüssel).
In JavaScript ist ein Array ein Objekt1, aber jedes Objekt ist im Kern in JavaScript auch
ein Array – nur eine Liste mit Schlüssel-Werte-Paaren und das ist außergewöhnlich.
Zwar sind die verfügbaren Methoden zwischen JSON-Objekten und „normalen“ Arrays
nicht identisch. Für die Struktur und den Zugriff auf die enthaltenen Elemente ist das
aber meist2 irrelevant. Auf diese Details kommen wir später zurück.
Die Ausführungen in dem Kapitel gelten vordergründig für Arrays, ohne – bis auf
Ausnahmen – den Objektcharakter zu beachten. Aber sie gelten im Grunde immer
ebenso für alle Objekte, wenn nichts explizit Gegenteiliges ausgeführt wird, da
eben Objekte wie Arrays als Hash-Listen abgebildet werden.
Um das zumindest soweit zu erläutern, wie es an dieser Stelle sinnvoll ist, wird kurz erklärt, wie
speziell in JavaScript Objekte zu sehen sind. Es sind fundamentale Datenstrukturen, die verwendet
werden, um Daten zu organisieren und zu speichern. Objekte in JavaScript bestehen aus Schlüs-
sel-Wert-Paaren (Hash-Liste), wobei jeder Schlüssel eine Zeichenfolge (auch bekannt als Eigen-
schaft oder Attribut) ist und jeder Wert mit diesem eindeutigen Schlüssel verknüpft ist. Objekte
in JavaScript sind dynamisch, was bedeutet, dass Sie neue Eigenschaften als auch Methoden zu
einem Objekt hinzufügen, vorhandene Eigenschaften ändern oder entfernen können, nachdem das
Objekt erstellt wurde. Der Zugriff auf Member eines Objekts mithilfe des Punktoperators (Punkt-
notation oder der Klammerschreibweise ([]) zugreifen. Die Klammerschreibweise ist nützlich,
wenn der Name der Eigenschaft eine Variable ist oder wenn der Name der Eigenschaft Sonder-
zeichen enthält.
Ein Array ist von Vorteil, wenn Sie eine Reihe zusammengehörender Informationen spei-
chern wollen. Das könnten beispielsweise die Tage eines Monats sein. Sie können zwar
für jeden Tag des Monats eine eigene Variable definieren, die dann die Nummer des Tags
im Monat aufnimmt. Diese Methode wäre sehr umständlich. Durch die Verwendung
eines Arrays genügt eine Variable und der Index. Mithilfe von Variable und Index können
Sie auf mehrere Elemente zugreifen.
Arrays in JavaScript sind nullindiziert. Das bedeutet, dass das erste Element in
einem Array den Index 0 hat, das zweite den Index 1 usw. Zumindest gilt das bei
automatisch durchindizierten, numerischen Arrays. Auf die Details gehen wir
gleich ein (Abschn. 6.1.6).
Ein großer Vorteil von Arrays ist, dass Sie damit auch über Schleifen auf die Elemente
zugreifen können (Abschn. 6.3).
Ein Array wird in JavaScript etwas anders erzeugt als eine normale Variable. Sie müssen
neben dem Namen, über den das Array zugänglich ist, dem System auch kenntlich machen,
dass eine ganze Sammlung an Werten vorliegt. Es gibt dazu verschiedene Wege in Java-
Script. Durch das Duck-Typing-Konzept wird die Information, dass eine Variable auf ein
Array verweisen soll, ausschließlich durch den zugewiesenen Wert/Datentyp festgelegt. Eine
besondere Kennung einer Array-Variablen, wie es anderen Sprachen üblich ist, gibt es nicht.
Beispiel
Ein Konstruktor ist in JavaScript eine Methode bzw. Funktion, um ein Array oder Ob-
jekt zu erzeugen. Dem Aufruf der Konstruktors stellt man das Schlüsselwort new voran.
Man kann in dem Konstruktor auch die Anzahl der Elemente angeben, die das Array ent-
halten soll.
Beispiel
Das ist aber in JavaScript sowohl unnötig als auch wirkungslos. Zwar würde mit einem
numerischen Parameter tatsächlich die Größe des Arrays vorgegeben, aber Sie können
beim Hinzufügen von Elementen zu einem Array jederzeit das Array vergrößern. Des-
halb gibt man in der Praxis eigentlich nie eine Größe des Arrays bei der Deklaration an,
da Array-Elemente sowieso „on the fly“ hinzugefügt werden, wenn sie benötigt werden.
Wie erwähnt, ist ein Array in JavaScript einfach eine Liste mit Key-Value-Einträgen, die
dynamisch geändert werden kann.
Mit [Link] können Sie auf einfache Weise alle Elemente eines Arrays aus-
geben.
96 6 Arrays, JSON und andere iterierbare Elemente
Arrays können Sie in JavaScript auch unter Verwendung von einem sogenannten Ar-
ray-Literal erzeugen und damit auf die Notation von einem expliziten Konstruktor ver-
zichten. Sie verwenden dann auch nicht das Schlüsselwort new. In diesem Fall spricht
man von einer deklarativen bzw. literalen Erzeugung. Aber man muss dem System wie-
der kenntlich machen, dass eine Deklaration nicht einfach eine Variable neu anlegt, son-
dern ein Datenfeld. Dazu verwendet man in JavaScript (und vielen anderen Sprachen)
eckige Klammern – das kennzeichnet dann ein Array-Literal. Sie weisen einer Variablen
bei der Deklaration in eckigen Klammern eine Reihe an Werten zu, die durch Kommata
getrennt werden. Die eckigen Klammern können auch leer bleiben. In jedem Fall legt
man über Duck-Typing fest, dass es sich um ein Array handelt. Dabei werden die Ele-
mente automatisch numerisch indiziert, was die Größe von dem Array festlegt.
Beispiel
Arrays sind Objekte. Das kann man ganz einfach mit typeof bestimmen:
"use strict";
// Literale Erzeugung
var namen = ["Müller", "Meier", "Schmitt", "Mustermann"];
var x = new Array(); // Array-Konstruktor
[Link](namen, typeof namen);
[Link](x, typeof x);
Der Zugriff auf ein einzelnes Element des Arrays erfolgt über die Angabe eines Index,
den Sie in eckigen Klammern angeben müssen. So erhalten Sie mit namen[0] das erste
Element des Feldes namen, mit namen[1] das zweite Element usw. Dabei kann man
diese Zugriffe sowohl lesend als auch schreibend verwenden. Hier ist ein vollständiges
Beispiel, das sowohl die literale Erzeugung als auch die Verwendung eines Konstruktors
zeigt ([Link]).
6.1 Arrays 97
"use strict";
// Literale Erzeugung
var namen = ["Müller", "Meier", "Schmitt", "Mustermann"];
var x = []; // Leeres Array
var primzahlen = [1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
var person = ["Hans", "Dampf", 42] // unterschiedliche Datentypen
Elemente
var jahreszeiten = new Array(); // Array-Konstruktor
// Zuweisung von Elementen zum Array
jahreszeiten[0] = "Frühjahr";
jahreszeiten[1] = "Sommer";
jahreszeiten[2] = "Herbst";
jahreszeiten[3] = "Winter";
// Ausgabe komplette Arrays
[Link](namen);
[Link](primzahlen);
[Link](jahreszeiten);
[Link](person[0], person[1]); // Zugriff auf einzelne Elemente
Nutzen Sie einen Index das erste Mal bei einer Wertzuweisung, wird ein neues
Element in dem Array angelegt. Jeder weitere Zugriff mit dem gleichen Index
greift dann auf das Element zu. Man kann es auslesen, aber auch ändern.
Im Gegensatz zu den meisten anderen Programmiersprachen ist das Anlegen von ver-
schachtelten Arrays (auch mehrdimensionale Arrays genannt, wobei diese nur als
Spezialfall zu verstehen sind) in JavaScript im Grunde trivial. Sie geben einfach als Ele-
ment ein weiteres Array an. Dabei können Sie beliebig mit anderen Datentypen mischen
und beliebig viele Ebenen angeben. Beim Zugriff notieren Sie die verschiedenen Dimen-
sionen nacheinander in getrennte eckige Klammern.
"use strict";
var a = [[1,2],[3,4]];
var b = [[1,2],5,[3,4]];
var c = [[1,2],[[3],[4]],[9,[5,6,7,8]]];
[Link](a,b,c);
[Link](a[0],a[1][0]);
[Link](c[2],c[2][1], c[2][1][3]);
98 6 Arrays, JSON und andere iterierbare Elemente
Da Arrays Objekte sind, gibt es Eigenschaften. Bei Arrays gibt es die Eigenschaft
length, die oft als Anzahl der Elemente und damit der Größe eines Arrays erklärt bzw.
interpretiert wird. Das ist falsch. Oder zumindest gilt es nur in Ausnahmesituationen.
Schauen wir uns diese etwas provokante Behauptung mit Beispielen an.
"use strict";
// Literale Erzeugung
var namen = ["Müller", "Meier", "Schmitt", "Mustermann"];
[Link](namen, [Link]);
Das Array enthält ganz offensichtlich vier Elemente und der Wert von length ist 4. Das
sieht sehr vernünftig aus, ist aber vermutlich die Ursache für das Missverständnis. Be-
trachten Sie diese Abwandlung:
"use strict";
// Literale Erzeugung
6.2 Die Objekt-Notation – JSON 99
Dem Array wird nachträglich ein Element zugewiesen. Aber beachten Sie, dass eine
„Lücke“ in der Indizierung vorhanden ist. Das ist die Ausgabe.
[ 'Müller', 'Meier', 'Schmitt', 'Mustermann', <6 empty items>, 'Musterfrau’] 11
Es gibt nur 5 Elemente in dem Array, aber length hat den Wert 11. Was über console.
log als „empty items“ bezeichnet wird, sind Elemente mit dem Zustand undefined. Das
erkennen Sie beim direkten Zugriff über einen Index, der auf so ein Array-Element ver-
weist. Was ist der Hintergrund? Die Anzahl der zugewiesenen Werte legt im ersten Fall
indirekt die anfängliche Größe des Datenfeldes fest. Das Array wird dabei automatisch
numerisch indiziert. Hier haben wir genau die oben angedeutete Situation – die Anzahl
der Elemente und damit die Größe von dem Array wird durch die Eigenschaft length re-
präsentiert, sofern man ohne Lücken im Index arbeitet.
Tatsächlich wird die Eigenschaft length in vielen Situationen nicht die Größe eines
Arrays bzw. die Anzahl der enthaltenen Elemente widerspiegeln, sondern nur den
Wert des höchsten numerischen Index plus 1. Daraus kann man zwar in einigen
Situationen auf die Anzahl der Elemente in einem Array schließen (wenn der Index
wie schon erwähnt durchgängig hochgezählt wurde), aber man kann sich nicht dar-
auf verlassen und meist ist das auch nicht der Fall.
6.1.7 Textindizes
Tatsächlich ist dieses Verhalten absolut logisch, denn in JavaScript gibt es grundsätzlich
nur Textindizes! Wenn Sie numerische Indizes angeben oder diese automatisch gene-
riert werden, wird der numerische Schlüssel im Hintergrund zum String umgewandelt.
Auch andere Datentypen als Indizes werden auf einen String gecastet. Deshalb sind auch
scheinbar unlogische Array-Strukturen gar nicht unlogisch. Das wird noch viel deut-
licher, wenn man JSON betrachtet.
Objekte und Arrays werden in JavaScript identisch als Listen mit einer Auflistung über
Schlüssel-Werte-Paare (Hash-Listen) verwaltet. Das führt dazu, dass es auch eine de-
klarative Erzeugung von Arrays mit einem sogenannten Objekt-Literal gibt. Dazu no-
tiert man in geschweiften Klammern eine Kommata-getrennte Liste aus Schlüssel-Werte-
Paaren als Literal, das einer Variablen zugewiesen wird. Das erlaubt sprechende Indi-
zes – also Textindizes. Auch damit erzeugen Sie ein Array bzw. ein Objekt, wobei die
100 6 Arrays, JSON und andere iterierbare Elemente
Schlüsselpaare mit Kommata getrennt werden und sowohl in eine als auch mehrere Zei-
len geschrieben werden können. Auch Schlüssel und Wert können bei Bedarf auf meh-
rere Zeilen verteilt werden.
Beispiel
var kapitaen = { name: " Picard", vorname: " Jean-Luc"};
Wichtig – Textindizes dürfen nicht mit einer Zahl oder einigen anderen Sonder-
zeichen beginnen. Man kann sie sowohl explizit als String angeben oder in JSON
auch ohne Hochkommata notieren. Sie werden von JavaScript so oder so als
Strings verstanden.
Diese Objekt-Notation für Arrays wird als JavaScript Object Notation (JSON) be-
zeichnet. Damit wird auch schon deutlich, dass wir hier von Objekten reden.
Schauen wir uns an, was typeof an Datentyp bei JSON liefert – es wird object sein:
"use strict";
var kapitaen = {name: "Picard", vorname: "Jean-Luc"};
[Link](kapitaen, typeof kapitaen);
Die Verwendung von Textindizes ist auch als assoziierte Arrays bekannt. Bei einem assoziier-
ten Array verwenden Sie als Index den Datentyp String als Schlüssel. Dementsprechend wird der
Index beim Zugriff immer(!) in Hochkommata notiert. Beachten Sie den Unterschied beim Er-
zeugen, wo die Notation in Hochkommata optional ist.
Natürlich können Sie auch numerische Indizes beim Zugriff als String notieren, denn im
Hintergrund werden Indizes vom Typ Number auf String gecastet, weil es eben nur Text-
indizes gibt. Das macht den Umgang mit Arrays extrem effizient, schlank und einfach3.
Auch wenn man ohne diese Detailkenntnisse vielleicht ein „eigenartiges“ Verhalten emp-
findet. Zugriff auf einzelne Elemente eines assoziierten Arrays kann wie bei numerischen
3 Ich kenne keine andere Programmiersprache, die für mich JavaScript beim Umgang mit Arrays
Indizes erfolgen. Etwa über die Klammernotation mit kapitaen["abc"]. oder über die
Punktnotation wie bei [Link]. Wie bei „normalen“ Arrays gilt, dass der Zugriff mit
einem nicht vorhandenen Index undefined liefert.
Es ist in JavaScript meist irrelevant, auf welche Art Sie ein Array erzeugen. Letzt-
endlich kommt immer von den Inhalten ein strukturell gleiches Objekt heraus,
wobei man nur bei JSON explizit beim Anlegen die Textindizes angeben kann. Bei
JSON-Objekten kann man allerdings auch über die Punktnotation auf Elemente
zugreifen. Etwa über [Link]. Das geht nur bei JSON und nicht bei Arrays
mit numerischer Indizierung. Denn Textindizes dürfen nicht mit einer Zahl oder
einem anderen Sonderzeichen beginnen. Beachten Sie, dass es auch hinsichtlich
des konkreten Objekttyps Unterschiede zwischen einen Array, das mit JSON er-
zeugt wurde, und den anderen beiden Wegen gibt. Das beleuchten wir bei der OOP
genauer. Und nochmal zur Wiederholung – intern werden alle Arten Indizes als
Textindizes verstanden. Auch wenn Sie etwa boolesche Literalen (true oder false)
als Index hinschreiben, werden diese einfach als Texte verstanden. Es gibt keinerlei
qualitativen Unterschied zwischen einem Text "abc", "true" oder "42". Textindizes
werden nicht geparsed und sind damit keine Token. Sondern nur eindeutige Texte
im Kontext der Indizierung des Arrays. Im Prinzip ist es daher auch möglich, nu-
merische, boolesche und assoziative Indizes gemeinsam in einem Datenfeld zu
verwenden. Die Indizes müssen nur eindeutig sein. Sie sollten allerdings von sol-
chen Konstruktionen Abstand nehmen. Der Quelltext wird unübersichtlich und es
gibt meist keinen sinnvollen Grund.
Verschachtelte Arrays haben wir ja schon gesehen. Man könnte es kurzhalten: „Bei
JSON geht alles identisch!“ Aber diese Aussage setzt voraus, dass das gemeinsame Kon-
zept von Arrays und JSON komplett klar ist. Deshalb werden wir mit Beispielen die An-
gelegenheit verdeutlichen.
"use strict";
var person = {
name:"Otto", titel: ["Prof", "Dr"], adresse:{
plz:12345, ort:"Irgendwo", strasse:{ name:"Milchstrasse", nr:42 }
}
};
[Link](person, typeof person);
[Link]([Link], typeof person["name"]);
[Link]([Link][0], typeof [Link][0]);
[Link]([Link],
typeof [Link]["strasse"].nr);
102 6 Arrays, JSON und andere iterierbare Elemente
Dieser Code definiert ein JavaScript-Objekt mit dem Namen person, das verschiedene
Eigenschaften enthält. Dabei wurden bewusst an diversen Stellen unterschiedliche
Datentypen verwendet. Es gibt weitere JSON-Objekte, Arrays, Zahlen und Strings als
Inhalte. Das ist eben in JavaScript unproblematisch, einfach und konsistent.
Der Code gibt dann verschiedene Informationen über das person-Objekt aus:
• [Link](person, typeof person);: Gibt das gesamte person-Objekt aus sowie den
Datentyp von person, der ein Objekt ist.
• [Link]([Link], typeof person["name"]);: Gibt den Wert der name-Eigen-
schaft von person aus ("Otto") sowie den Datentyp dieser Eigenschaft, der ein String
ist.
• [Link]([Link][0], typeof [Link][0]);: Gibt das erste Element des titel-
Arrays von person aus ("Prof") sowie den Datentyp dieses Elements, der ein String
ist.
• [Link]([Link], typeof [Link]["strasse"].nr);: Gibt die
Hausnummer (42) der strasse-Eigenschaft des adresse-Objekts von person aus sowie
den Datentyp dieser Eigenschaft, der eine Zahl ist.
Beachten Sie bei den Ausgaben, dass zum Zugriff auf die Elemente sowohl die Array-Notation
als auch Punktnotation verwendet wird und beide sogar gemischt werden. Auch das ist ein extrem
leistungsfähiges und dennoch konsistentes, einfaches Feature in JavaScript, auch wenn dies an der
Stelle nur aus didaktischen Gründen gemacht wird.
Ein großer Vorteil von Arrays ist, dass man über Schleifen oder einen Iterator mit wenig
Aufwand auf Elemente zugreifen kann.
Auch wenn numerisch indizierte Arrays eigentlich mit Textindizes arbeiten – bei der
Iteration mit einer Zählschleife können wir dennoch den numerischen Index nutzen, um
auf die einzelnen Elemente zuzugreifen. Der Zählindex wird im Inneren der Schleife ein-
fach wieder zu einem String gecastet. So können Sie mit Schleifen über alle Elemente
eines Arrays iterieren. Dazu verwenden wir nachfolgend die for-Schleife, aber Sie kön-
nen auch jede andere Schleifenform verwenden.
"use strict";
var namen = ["Müller", "Meier", "Schmitt", "Mustermann"];
for(let i = 0; i < [Link]; i++) {
[Link](namen[i]);
}
In der Schleife wird das gesamte Array ausgegeben, indem der Zählindex für den Zugriff
auf ein Element verwendet wird. Nun ist es jedoch eine Folge sowohl der losen Typisie-
rung als auch des internen Aufbaus von Arrays, dass diese Vorgehensweise dann Prob-
leme macht, wenn nicht alle Elemente in dem Datenfeld „belegt“ sind. Dann erhalten
Sie beim Zugriff auf ein Array-Element unter Umständen den Wert undefined. Das soll
praktisch mit einer kleinen Abwandlung verdeutlicht werden:
"use strict";
var namen = new Array()
namen[0] = "Müller";
namen[2] = "Meier";
namen[4] = "Schmitt";
namen[7] = "Mustermann";
for(let i = 0; i < [Link]; i++) {
[Link](namen[i]);
}
Schmitt
undefined
undefined
Mustermann
Das ist ein klares und logische Verhalten, denn in dem Beispiel sind manche Textindizes
einfach nicht vorhanden. Das Problem ist die Schleife, die über einen numerischen Index
läuft und dabei eben auch Zahlen für den Zählindex verwendet, für die es keine String-
Repräsentation gibt. Es gibt ja auch kein namen["IrgendEinText"]. Warum sollte es
namen["6"] geben? Nun liefert length allerdings Informationen über den höchsten nu-
merischen Index des Arrays. Und beim Iterieren über die nicht initialisierten Array-Ele-
mente erhalten Sie undefined.
Sollten Sie nun in solch eine Situation kommen, in der Sie mit einer Schleife arbeiten
und die Indizes des Arrays nicht eine lückenlose numerische Reihe darstellen, können
Sie gegebenenfalls auf undefined überprüfen, bevor Sie ein Array-Element verwenden.
Beachten Sie die folgende Abwandlung des letzten Beispiels:
"use strict";
var namen = new Array()
namen[0] = "Müller";
namen[2] = "Meier";
namen[4] = "Schmitt";
namen[7] = "Mustermann";
for(let i = 0; i < [Link]; i++) {
if (namen[i] == undefined) continue;
[Link](namen[i]);
}
Wenn ein Array-Element den undefined-Zustand aufweist, startet mit continue der
nächste Schleifendurchlauf. Aber es gibt bessere Möglichkeiten.
6.3.2 Zugriff auf alle Elemente eines Arrays über for-in und for-of
sowie forEach
Beim Zugriff auf assoziative Datenfelder haben Sie den Preis zu zahlen, dass Sie die
Elemente mit einem nichtnumerischen Index nicht in normalen Schleifen erwischen. So
etwas wie die Verwendung einer Zählschleife ist also bei so einem Datenfeld mit Text-
indizes sowieso nicht möglich. Aber mit der for-of- oder for-in-Schleife ist das kein Pro-
blem. Diese kann natürlich auch auf numerisch indizierte Arrays oder gar gemischt indi-
zierte Arrays angewendet werden, denn – und das sollte mittlerweile kalter Kaffee sein
– es gibt ja sowieso nur Textindizes.
6.3 Iteration über Arrays und JSON 105
[Link] for-in
Das wäre die Syntax:
Beispiel
Mit dieser Schleifenform kann man ohne explizite Angabe der Größe des Datenfelds die-
ses vollständig durchlaufen. Damit würden Sie auch die als undefined gekennzeichneten
Array-Elemente nicht stören, da diese von der Schleife überhaupt nicht geliefert werden.
Beachten Sie, dass die Zählvariable der Schleife den Index darstellt. Das ist in ei-
nigen anderen Sprachen wie Java bei der vergleichbaren Schleife nicht der Fall.
Dort steht die Zählvariable für das Element selbst. Für den Zugriff auf den Inhalt
eines Felds müssen Sie die Zählvariable dann als Index in der Implementation der
Schleife verwenden.
"use strict";
var namen = new Array()
var x = [1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
var kapitaen = { name: " Picard", vorname: " Jean-Luc"};
namen[0] = "Müller";
namen[2] = "Meier";
namen[4] = "Schmitt";
namen[7] = "Mustermann";
for (let i in x) {
[Link](i + ": " + x[i] );
}
for (let i in namen) {
[Link](i + ": " + namen[i] );
}
for (let i in kapitaen) {
[Link](i + ": " + kapitaen[i] );
}
In der Schleife steht i für den Index und etwa x für das Datenfeld. Dementsprechend ist
x[i] in jedem Schleifendurchlauf der jeweilige Wert eines Array-Elements. Das ist die
Ausgabe:
0: 1
1: 2
2: 3
…
9: 23
106 6 Arrays, JSON und andere iterierbare Elemente
10: 29
0: Müller
2: Meier
4: Schmitt
7: Mustermann
name: Picard
vorname: Jean-Luc
[Link] for-of
Iteration mit for-of geht nur in neueren Versionen von JavaScript. Man erhält damit di-
rekt die Elemente eines Arrays.
"use strict";
var namen = new Array()
namen[0] = "Müller";
namen[2] = "Meier";
namen[4] = "Schmitt";
namen[7] = "Mustermann";
for (let i of namen) {
[Link](i);
}
Beachten Sie, dass wir hier mit abzählbaren Arrays arbeiten und hier dann auch wieder
undefined für die Elemente erhalten können.
• Mit for-in iteriert man über alles Eigenschaftsnamen eines Objekts. Man erhält diese
als Schlüssel des Objekts. Man kann die Schleife auch bei Arrays verwenden,
• Die for-of-Schleifen wird verwendet, um über die Werte einer Iterable-Struktur zu ite-
rieren, wie z. B. Arrays oder Strings, als auch Strukturen wie Maps, Sets usw., die wir
6.3 Iteration über Arrays und JSON 107
bisher noch nicht hatten. Man erhält damit die Werte der Struktur und nicht über die
Schlüssel oder Indizes. Aber for-of kann nicht direkt mit Objekten verwendet werden,
da Objekte nicht iterierbar sind.
Beispiel
Mit dem Callback wird eine Funktion angegeben, die für jedes Element in der Daten-
struktur aufgerufen wird. Sie akzeptiert drei Argumente:
• Das aktuelle Element (currentValue), das während der Iteration verarbeitet wird.
• Der Index des aktuellen Elements.
• Das optionale Array, über das iteriert wird.
• Ein optional Wert, der in der Callback-Funktion festgelegt wird, wenn sie aufgerufen
wird.
Da wir für Beispiele Funktionen benötigen, werden diese in das nächste Kapitel verlagert.
Das Iterieren über verschachtelte Arrays oder JSON-Objekte erfolgt, indem Sie jedes
Element, das selbst ein Array oder ein JSON-Objekt ist, wieder selbst iterieren. Das kann
man mit jeder Schleife machen, über die man alle Elemente „erwischt“.
108 6 Arrays, JSON und andere iterierbare Elemente
var a = [[1,2],[3,4]];
var b = [[1,2],[[3],[4]],[5,6,7,8]];
// Iteration über Array 'a'
[Link]("Array a:");
for (var i = 0; i < [Link]; i++) {
for (var j = 0; j < a[i].length; j++) {
[Link]("Element [" + i + "][" + j + "]: " + a[i][j]);
}
}
// Iteration über Array 'b'
[Link]("\nArray b:");
for (var k = 0; k < [Link]; k++) {
for (var l = 0; l < b[k].length; l++) {
[Link]("Element [" + k + "][" + l + "]: " + b[k][l]);
}
}
Die Ausgabe zeigt zuerst den Inhalt von Array a und dann von Array b. Das geht auch
mit for-in. Wobei dies sogar meist die bessere Lösung ist.
"use strict";
var a = [[1,2],[3,4]];
var b = [[1,2],[[3],[4]],[5,6,7,8]];
// Iteration über Array 'a'
[Link]("Array a:");
for (var i in a) {
for (var j in a[i]) {
[Link]("Element [" + i + "][" + j + "]: " + a[i][j]);
}
}
// Iteration über Array 'b'
[Link]("\nArray b:");
for (var k in b) {
for (var l in b[k]) {
[Link]("Element [" + k + "][" + l + "]: " + b[k][l]);
}
}
Die Ausgabe wird identisch. Dieser Code verwendet for-in-Schleifen, um über die Indi-
zes der äußeren Arrays zu iterieren. Anschließend werden weitere for-in-Schleifen ver-
wendet, um über die Indizes der inneren Arrays zu iterieren und die entsprechenden Ele-
mente auszugeben.
Beachten Sie jedoch, dass diese beiden Lösungen nicht zwischen Array-Elementen bzw.
Objekten und anderen Typen der Eigenschaften unterscheidet. Sie funktionieren nur,
weil wir die Struktur kennen und es sich um 2-dimensionale Arrays mit genau so einer
Form von Inhalten handelt.
[Link] Typüberprüfung
Deshalb sollte man im allgemeinen Fall einer Iteration über verschachtelte Strukturen
auf jedem Element eine Typüberprüfung durchführen. Das kann man mit typeof machen.
Hier ist der Code:
"use strict";
var a = [[1,2],[3,4]];
var b = [[1,2],[[3],[4]],[5,6,7,8]];
[Link]("Array a:"); // Iteration über Array 'a'
for (var i in a) {
for (var j in a[i]) {
[Link]("Element [" + i + "][" + j + "]: " + a[i][j]);
}
}
[Link]("\nArray b:"); // Iteration über Array 'b'
for (var k in b) {
for (var l in b[k]) {
[Link]("Element [" + k + "][" + l + "]: " + b[k][l]);
}
}
In diesem Code wird typeof verwendet, um zu überprüfen, ob ein Element ein Objekt ist.
Wenn ja, wird überprüft, ob es ein verschachteltes Objekt ist, und entsprechend iteriert.
Andernfalls wird das Element direkt ausgegeben, zusammen mit seinem Typ.
Array a:
Element [0][0]: 1, Type: number
Element [0][1]: 2, Type: number
Element [1][0]: 3, Type: number
Element [1][1]: 4, Type: number
Array b:
Element 0 ist ein Objekt:
Element [0]: 1,2, Type: object
Element 1 ist ein Objekt:
Element [1][0] ist ein verschachteltes Objekt:
Element [1][0][0]: 3, Type: number
Element [1][1] ist ein verschachteltes Objekt:
Element [1][1][0]: 4, Type: number
110 6 Arrays, JSON und andere iterierbare Elemente
Sie können diesen Code auf so umschreiben, dass statt typeof eine Methode isArray der
Klasse/Prototyp Array zum Einsatz kommt, die das gleiche Resultat bewirkt. Das greift
auf OOP vor, ist aber zum Vergleich ganz interessant. Das wollen wir für Array b in der
Abwandlung des Beispiels machen.
…
[Link]("\nArray b:"); // Iteration über Array 'b'
for (var k = 0; k < [Link]; k++) {
if ([Link](b[k])) {
// Überprüfen, ob das aktuelle Element ein Array ist
[Link]("Element " + k + " ist ein Array:");
if ([Link](b[k][0])) {
// Wenn das Element ein verschachteltes Array ist
for (var l = 0; l < b[k].length; l++) {
[Link]("\tElement [" + k + "][" + l + "] ist ein ver-
schachteltes Array:");
for (var m = 0; m < b[k][l].length; m++) {
[Link]("\t\tElement [" + k + "][" + l + "][" + m + "]: " + b[k]
[l][m]);
}
}
} else {
// Wenn das Element kein verschachteltes Array ist
[Link]("\tElement [" + k + "]: " + b[k]);
}
} else {
[Link]("Element " + k + ": " + b[k]);
}
}
Es sollte auffallen, dass der Quellcode mit der Typüberprüfung nicht mehr ganz trivial
ist. Und dennoch ist er nicht flexibel genug, denn sollte es mehr Ebenen, erreichen wir
nicht alle einzelnen Elemente. Für eine unbekannte an Ebenen bietet sich eine rekursive
Lösung an.
[Link] Elemente löschen
Bei selbstdefinierten Arrays und Objekten kann man mit delete vorhandene Elemente lö-
schen.
6.3 Iteration über Arrays und JSON 111
Beachten Sie, dass Sie mit delete aber keine vordefinierten Standardobjekte
und -objekteigenschaften von JavaScript löschen können. Ebenso können in
der Regel nur Variablen und Objekte mit delete gelöscht werden, die nicht mit
dem Schlüsselwort var bzw. let deklariert wurden. Was eine Nutzung in Ver-
bindung mit "use strict" für das Löschen von Variablen ausschließt.
Bei numerisch indizierten Arrays wird delete ein undefiniertes „Loch“ an der gelöschten
Stelle hinterlassen, wodurch der Wert von length unverändert bleibt. Bei Objekten oder
assoziierten Arrays wird delete die Eigenschaft ebenso löschen und bei einem späte-
ren Zugriff auf diese Eigenschaft den Wert undefined zurückgeben, da die Eigenschaft
nicht mehr im Objekt vorhanden sein wird. Da allerdings bei solchen Objekten nicht mit
length operiert wird, ist eine Auswirkung auf den Zugriffcode unkritischer. Man muss
schon bewusst einen nicht vorhanden Index angeben. Die Anwendung von delete gibt
true zurück, wenn die Operation erfolgreich war, andernfalls false.
"use strict";
var namen = new Array()
var x = [1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
var kapitaen = { name: " Picard", vorname: " Jean-Luc" };
namen[0] = "Müller";
namen[2] = "Meier";
namen[4] = "Schmitt";
namen[7] = "Mustermann";
[Link](x);
[Link](namen);
[Link](kapitaen);
delete(x[3]);
delete(namen[3]);
delete(kapitaen["name"]);
[Link](x);
[Link](kapitaen);
Neben Arrays und JSON gibt es in JavaScript ein paar weitere iterative Strukturen, die
an der Stelle vorgestellt werden sollen. Dabei sind diese meist nur für besondere An-
wendungen oder Optimierungen notwendig und funktionieren auch nur in neueren Ver-
sionen von JavaScript
6.4.1 Maps
In JavaScript ist eine Map ein eingebauter Datenstrukturtyp, der es ermöglicht, Schlüs-
sel-Wert-Paare zu speichern. Im Gegensatz zu automatisch indizierten Arrays, bei denen
die Schlüssel implizit ganzzahlige Indizes sind, können bei Maps beliebige Werte als
Schlüssel verwendet werden. Sie sind damit JSON-Objekte sehr ähnlich, haben aber ei-
nige spezifische Eigenschaften bzw. Methoden und auch die Schüssel sind noch flexibler.
Hier sind einige wichtige Merkmale von Maps in JavaScript:
• Beliebige Schlüssel: Maps ermöglichen es, beliebige Werte als Schlüssel zu ver-
wenden, einschließlich Objekte, Funktionen und sogar andere Maps.
• Iterierbarkeit Maps sind iterierbar, was bedeutet, dass Sie leicht über ihre Schlüssel-
Wert-Paare iterieren können. Dies kann mithilfe von Schleifen oder der foreach-Me-
thode erfolgen.
• Effiziente Suche: Maps bieten eine besonders effiziente Möglichkeit, einen Wert an-
hand seines Schlüssels zu suchen.
• Erhaltung der Reihenfolge: Seit ECMAScript 2015 (ES6) garantieren Maps die Bei-
behaltung der Reihenfolge ihrer Einträge, was bedeutet, dass die Reihenfolge, in der
Elemente hinzugefügt werden, beibehalten wird.
Maps bieten eine flexible Möglichkeit, Daten zu organisieren und abzurufen, ins-
besondere wenn die Schlüssel nicht auf Ganzzahlen beschränkt sind und/oder die
Reihenfolge der Elemente wichtig ist. Hier ist ein einfaches Beispiel, das zeigt, wie eine
Map in JavaScript verwendet wird:
In diesem Beispiel wird zuerst eine leere Map erstellt. Dann werden Schlüssel-Wert-
Paare zur Map hinzugefügt und darauf zugegriffen. Es wird überprüft, ob bestimmte
Schlüssel in der Map vorhanden sind, und ein Schlüssel wird aus der Map gelöscht.
Schließlich wird über die Schlüssel-Wert-Paare der Map iteriert und sie werden auf der
Konsole ausgegeben.
Eine Map in JavaScript kann auch Objekte (JSON) als Schlüssel verwenden. Hier ist ein
Beispiel, wie eine Map mit einem JSON-Objekt als Schlüssel erstellt und verwendet wird:
In diesem Beispiel wird eine Map erstellt, die JSON-Objekte als Schlüssel verwendet
und dann für den Zugriff verwendet.
6.4.2 Sets
Ein Set in JavaScript ist eine eingebaute Datenstruktur, die eine Sammlung eindeutiger
Werte enthält. Im Gegensatz zu Arrays können Sets keine Duplikate enthalten, was be-
deutet, dass jedes Element im Set einmalig ist. Ein Set bietet verschiedene nützliche Me-
thoden zum Hinzufügen, Entfernen und Überprüfen von Elementen sowie zum Durch-
laufen der Elemente in der Reihenfolge ihrer Einfügung. Einige der häufig verwendeten
Methoden/Eigenschaften für Sets sind:
114 6 Arrays, JSON und andere iterierbare Elemente
Hier ist ein einfaches Beispiel, das zeigt, wie ein Set in JavaScript verwendet wird:
In diesem Beispiel wird ein Set erstellt und einige Operationen auf diesem Set durch-
geführt, wie das Hinzufügen von Elementen, Überprüfen, ob ein Element vorhanden ist,
Durchlaufen der Elemente und Entfernen von Elementen. Besonders spannend ist die
Verwendung von forEach, denn der Callback, der als Parameter angegeben wird, wird
für jedes Element im Set aufgerufen wird.
• WeakMap und WeakSet sind ähnlich wie Maps und Sets, aber mit dem Unterschied,
dass sie nur schwache Referenzen auf die Schlüssel-Wert-Paare oder Werte halten.
Dadurch können die Einträge im Speicher automatisch freigegeben werden, wenn
keine anderen Referenzen mehr auf sie verweisen, was nützlich ist, um Speicherlecks
zu vermeiden.
6.4 Weitere iterative Datenstrukturen 115
• Eine Queue ist eine abstrakte Datenstruktur, die Elemente nach dem FIFO-Prinzip
(First-In-First-Out) organisiert. Elemente werden am Ende der Queue hinzugefügt
und am Anfang entfernt.
• Ein Stack ist eine abstrakte Datenstruktur, die Elemente nach dem LIFO-Prinzip
(Last-In-First-Out) organisiert. Elemente werden oben auf dem Stapel hinzugefügt
und von dort entfernt.
Übersicht
Beim Erstellen von JavaScripts werden Sie fast immer in die Situation kommen,
dass Sie Code zusammenfassen und über einen Namen aufrufen werden. Noch
öfter werden Sie vorhanden Code von jemanden anderem verwenden. Dies nennt
man dann eine Funktion. Aber in diesem Zusammenhang tauchen ebenfalls die Be-
griffe Unterprogramme, Prozeduren und Methoden auf. In dem Kapitel machen
wir uns deutlich, was es damit auf sich hat. wobei davon ausgehe, dass Ihnen die
Thematik grundsätzlich geläufig ist. Wir haben ja auch schon mehrfach mit Funk-
tionen bzw. Methoden gearbeitet. Aber in JavaScript gibt es eine ganze Reihe an
Besonderheiten.
Allgemein reden wir bei Unterprogrammen im Moment erst einmal nur über eine
Reihe von Anweisungen im Quelltext, die zusammenfasst werden und einen Bezeichner
oder eine andere Möglichkeit zum nachträglichen Zugriff bekommen. Damit müssen Sie
diese Folge von Anweisungen nicht jedes Mal in den Quelltext schreiben, wenn Sie diese
Anweisungen benötigen. Sie werden nur einmal geschrieben, möglichst umfassend ge-
testet und dann jedes Mal über die Angabe des Bezeichners (oder einer Referenz) auf-
gerufen, wenn diese Folge von Anweisungen benötigt wird. Unterprogramme sind ein
entscheidender Teil des DRY-Konzepts.
In JavaScript werden als Funktionen definierte JavaScript-Anweisungen beim Laden
der Webseite respektive des Skripts nicht direkt ausgeführt, sondern erst, wenn sie ex-
plizit aufgerufen werden.
© Der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH, ein 117
Teil von Springer Nature 2025
R. Steyer, Programmieren mit JavaScript, [Link]
118 7 Funktionen, Prozeduren und Methoden
Nur was hat es mit den verschiedenen Arten von Unterprogrammen auf sich? Es gab
früher in einigen Sprachen einen wesentlichen Unterschied zwischen Funktionen und
Prozeduren:
• Eine Funktion erledigt eine Aufgabe und liefert immer einen Wert als Ergebnis zurück
(einen Rückgabewert).
• Eine Prozedur erledigt eine Aufgabe, ohne einen Wert als Ergebnis zurückzugeben.
JavaScript kennt jedoch nur Funktionen und keine Prozeduren. Allerdings muss eine
JavaScript-Funktion keinen Wert zurückgeben, der manchmal sogar zwingend aus-
gewertet werden muss, wie es in anderen Programmiersprachen notwendig sein kann.
Damit sind in JavaScript die Unterschiede aufgehoben. Wir werden in Zukunft aus-
schließlich von Funktionen reden.
Eine spezielle Form einer Funktion ist eine Methode. Methoden sind Funktionen, die
einem Objekt oder einer Klasse zugeordnet sind und nur über diese genutzt werden kön-
nen. Dazu nutzt man beim Zugriff eine vorangestellte Angabe des Objekts und danach
den Bezeichner wie bei einem „normalen“ Funktionsaufruf (die sogenannte Dot-Nota-
tion oder Punktnotation), was schon von Anfang auch bei der Verwendung von console.
log() zumindest oberflächlich behandelt wurden. Auf weitere Details Methoden und ins-
besondere die Erstellung eigener Methoden in JavaScript kommen wir jedoch bei der
Behandlung von OOP ausführlich zurück, wobei wir auch hier schon einige Dinge be-
sprechen.
Es wurde schon thematisiert, dass globale Funktionen als Methoden des globalen Ob-
jekts zu sehen sind (First Class Citizen). Das ist hauptsächlich deshalb von Bedeutung,
weil sie damit in das Objektkonzept selbst passen und zudem überall im Skript verfügbar
sein werden. Allerdings gibt es spezielle Varianten von Funktionen wie innere Funktio-
nen, die nicht im globalen Scope landen. Aber die grundsätzliche Art der Deklaration ist
immer gleich. Sie können in unterschiedlichen Gültigkeitsbereichen eigene Funktionen
in JavaScript deklarieren, indem Sie folgende Syntax notieren:
• Sie beginnen immer mit dem Schlüsselwort function. Sie können in JavaScript im
Gegensatz zu vielen anderen Sprachen wie Java keinen Datentyp für den Rückgabe-
wert angeben. Aber das ist klar, weil Sie grundsätzlich Datentypen in JavaScript nicht
spezifizieren.
• Dem Schlüsselwort function folgt der gewünschte Bezeichner der Funktion (den Sie –
bis auf Sonderzeichen – selbst vollkommen frei auswählen dürfen, der jedoch nicht
7.2 Die Deklaration eigener Funktionen in JavaScript 119
mit einem Schlüsselwort oder reservierten Bezeichner allgemein identisch sein darf).
Nach Konvention werden Bezeichner einer Funktion kleingeschrieben. Damit geben
Sie einen sogenannten Funktionszeiger (auch Funktionsreferenz genannt) an. Der
Funktionsname ist optional, kann aber nur bei sogenannten anonymen Funktionen
weggelassen werden. Diese liefern immer einen Funktionszeiger als Rückgabewert,
über den sie dann verwendet werden können.
• Dann machen Sie mit einer öffnenden runden Klammer weiter.
• Optional folgt eine Parameterliste, welche Variablen bezeichnet, über die Werte an die
Funktion übergeben werden können (bei mehreren Parametern durch Kommata ge-
trennt). Sie können auch hier keine Datentypen für die Parameter angeben. Das sollte
aber klar sein, da Sie ja auch bei normalen Variablendeklarationen keine Datentypen
angeben konnten. Innerhalb der Funktion stehen die Parameter als lokale Variablen
zur Verfügung und die Bezeichner dieser Parameter sind damit nur innerhalb des Be-
reichs der Funktion sichtbar.
• Eine runde, schließende Klammer beendet die sogenannte Funktionsunterschrift
oder Signatur der Funktion.
• Die eigentlichen Anweisungen der Funktion (die sogenannte Implementation oder
Implementierung) werden in nachfolgende geschweifte Klammern eingeschlossen.
Diese geschweiften Klammern bilden also einen Block.
• Mit dem optionalen Schlüsselwort return und einem ebenfalls optionalen Rückgabe-
wert können Sie eine Funktion an jeder Stelle wieder verlassen.
Die Syntax zur Deklaration einer Funktion sieht dementsprechend schematisch so aus:
Beispiel
function [Name]([Parameterliste]) {
…Anweisungen
return [Rückgabewert]; // optional
}◄
Die Anweisung return sollte – wenn vorhanden – immer die letzte Anweisung
in einer Funktionsimplementierung bzw. dem Programmfluss der Funktion
sein. Wenn der Interpreter diese Anweisung vorfindet, verlässt er unmittel-
bar die Funktion und macht mit dem Code hinter dem Funktionsaufruf wei-
ter. Gegebenenfalls nachfolgend in der Funktionsimplementierung notierte
Anweisungen werden nicht mehr ausgeführt. Diese wären dann sogenannter
unerreichbarer Code (unreachable code) und solcher ist immer (!) ein logi-
scher Fehler im Programm oder Skript. Sie können return allerdings an jeder
Stelle der Funktion in Verbindung mit einer vorangestellten Entscheidungs-
struktur verwenden (ein bedingtes return).
Sie können in JavaScript als Rückgabewert alle Datentypen, aber auch Sondertoken wir
undefined, NaN oder Infinity liefern.
120 7 Funktionen, Prozeduren und Methoden
Sie können in einer Funktion return auch ohne einen nachgestellten Wert no-
tieren. Dann ist das einfach eine Sprunganweisung, welche die Funktion ver-
lässt, und es wird kein Wert zurückgegeben. Gerade als bedingtes return ist
das in einigen Fällen sinnvoll.
Für den Aufruf einer Funktion muss an zwischen der Verwendung in einer Webseite
und allgemeinen Aufrufen unterscheiden. Wenn Sie eine Webseite verwenden, kön-
nen Sie eine Funktion an jeder Stelle in einer Webseite aufrufen, sofern sie vorher de-
klariert wurde. Bei einer Webseite erfolgt das oft mittels sogenannter Eventhandler, zu
denen wir noch kommen, mit einer Inline-Referenz oder einfach als Anweisung in einem
Skriptbereich. Im letztgenannten Fall rufen wir Funktionen in einem Skript direkt als An-
weisung beim Laden der Webseite oder eines Skripts durch beispielsweise [Link] auf.
Funktionen werden allgemein über den Bezeichner aufgerufen, mit dem sie deklariert
wurden. Wenn die Funktion aufgerufen wird, werden die Anweisungen in dieser Funk-
tion abgearbeitet und eventuelle Ergebnisse über das Schlüsselwort return dem Aufrufer
übergeben. Beachten Sie, dass Sie nur den Funktionsnamen samt Klammern und optio-
naler Werte für die Parameter (nicht die Parameterbezeichner) bei einem Aufruf angeben.
Die runden Klammern nach dem Bezeichner der Funktion gehören unbedingt
zu einem Aufruf einer Funktion dazu, egal ob Sie darin Werte an die Funk-
tion übergeben oder nicht. Nur bei einer sogenannten Funktionsreferenz (
Abschn. 1.5.3) bzw. einem Callback werden die Klammern fehlen. Das stellt
7.4 Lokale Variablen in Funktionen 121
dann aber auch nur einen Verweis auf die Funktion und keinen Aufruf dar.
Das versehentliche Weglassen der Klammern bei einem geplanten Funktions-
aufruf ist ein tückischer und oft gemachter Fehler.
Kommen wir noch einmal zum Begriff der lokalen Variablen. Wie gerade erwähnt, sind
Parameter an Funktionen (und auch Methoden) innerhalb des Funktionskörpers lokale
Variablen. Außerhalb des Funktionskörpers sind sie nicht sichtbar. Aber Sie können auch
innerhalb des Funktionskörpers weitere lokale Variablen definieren. Grundsätzlich macht
man das auch sehr oft und das ist eine „Spezialversion“ von blocklokalen Variablen. Das
Verfahren zum Deklarieren einer lokalen Variablen ist einfach. Lokale Variablen werden
innerhalb von Funktionen beziehungsweise vergleichbaren Konstruktionen explizit mit
var oder let deklariert und können dann nur dort benutzt werden. Eventuell außerhalb der
Funktion vorhandene (globale) Variablen gleichen Namens werden temporär verdeckt.
Genaugenommen werden diese zeitweilig auf undefined gesetzt, aber diese Spitzfindig-
keit spielt meist keine Rolle. Ansonsten gelten die üblichen Regeln für var und let, die
auch bei konventionellen Blöcken gelten. Beispiel ([Link]):
"use strict";
var a = "abc"; // globale Variable a
function meineFktion() {
var a = 42;
let b = 1;
[Link]("Wert der lokalen Variablen a: " + a +
"Der Wert der globalen Variablen ist verdeckt.");
[Link]("Wert der lokalen Variablen b: " + b);
}
[Link]("Wert der globalen Variablen a: " + a);
meineFktion();
[Link]("Wert der globalen Variablen a: " + a);
[Link]("Wert von b: " + b ); // ACHTUNG – FEHLER
Zuerst wird eine Variable a eingeführt. Dies erfolgt außerhalb einer Funktion und damit
ist sie global im ganzen Skript verfügbar, auch innerhalb jeder Funktion, es sei denn, sie
wird dort verdeckt. Und das machen wir in der Funktion meineFktion() als Erstes. In der
Funktion wird nämlich eine Variable mit gleichem Bezeichner erneut definiert. Damit
ist die globale String-Variable a in dieser Funktion nicht mehr verfügbar und stattdessen
steht die lokale numerische Variable über den Bezeichner a zur Verfügung. Die Ausgabe
in der Funktion beweist das.
122 7 Funktionen, Prozeduren und Methoden
In der Funktion wird noch eine zweite lokale Variable b eingeführt. Auch darauf grei-
fen wir innerhalb der Funktion zu. Die ersten Zugriffe zeigen, dass außerhalb der Funk-
tion meineFktion() die globale String-Variable a verfügbar ist und nicht durch eine Ver-
wendung einer lokalen Variablen gleichen Namens in einer Funktion verändert wird. Die
beiden Bereiche sind strikt getrennt.
Beachten Sie auch die letzte Zeile im Skript. Hier versuchen wir, auf die nur lokal
in der Funktion meineFktion() definierte Variable b zuzugreifen, aber von außerhalb des
lokalen Sichtbarkeitsbereichs. Und das kann nicht gehen. Tatsächlich werden Sie sehen,
dass diese Anweisung keine Ausgabe erzeugt, sondern einen Fehler auswirft. Das ist so-
wohl bei der Deklaration von b mit let als auch var identisch.
Beachten Sie, dass das Skript vor dem Eintreten eines Fehlers bis zu der Fehler-
stelle ausgeführt wird. Aber eventuell nach der Fehlerstelle notierte Anweisungen
würden nicht mehr ausgeführt. Hier zeigt sich deutlich ein Unterschied zum Com-
piler-Prinzip. Ein Compiler hätte den fehlerhaften Quelltext erst gar nicht über-
setzt. Es ist also möglich, dass fehlerhafte JavaScripts ausgeführt werden, näm-
lich genau so weit, bis der Fehler tatsächlich auftritt. Die Folgen können fatal sein,
denn Sie verlassen sich ja sicherlich darauf, dass ein Skript von Anfang bis Ende
abgearbeitet wird. Aber das Skript kann in so einem Fall seine Aufgabe nur teil-
weise erledigt haben und einen Zustand zurücklassen, der ziemlich unbefriedigend
sein kann. Im Fall von Skripten ist ein richtig gründlicher Test noch wichtiger als
im Compiler-Fall, denn Ihnen stehen nicht die Vorabprüfungen eines Compilers
zur Verfügung.
Wenn Sie kein "use strict" verwenden, können Sie Variablen ohne var oder let
deklarieren. Selbst wenn Sie dann innerhalb einer Funktion eine neue Variable
ohne var oder let deklarieren, wird diese dem globalen Objekt hinzugefügt.
Die Konsequenzen sind weitreichend und in der Regel nicht gewünscht. Auch
das ist ein Grund, warum man unbedingt "use strict" verwenden sollte.
Die Deklaration als auch Verwendung von Funktionen ist bei JavaScript eigentlich ein-
fach. Aber es gibt diverse Besonderheiten zu beachten.
7.5 Besonderheiten bei JavaScript im Zusammenhang mit Funktionen 123
Wenn Sie bei der Deklaration einer Variablen mit var arbeiten, ersetzt bei einer mehr-
fachen Deklaration einer Variable in einem Scope die zuletzt deklarierte Version alle vor-
herigen (redek_var.js).
"use strict";
var a = 1;
[Link](a);
var a = true; // Redeklaration mit anderem Wert/Datentyp
[Link](a);
Es gibt keinen Fehler und die Ausgabe ist 1 2. Beim sequenziellen Verarbeiten wird die
Variable verwendet, die in der Sequenz gerade gültig ist. Verwenden Sie let, gibt es einen
Fehler. Nun stellt sich die Frage, was eine mehrfache Deklaration einer Funktion mit
dem identischen Bezeichner im gleichen Scope bewirkt? Beachten Sie das Beispiel (fkt2.
js).
"use strict";
function fkt() {
[Link](1);
}
fkt();
function fkt() {
[Link](2);
}
fkt();
Die letzte Deklaration ersetzt die vorherige im gleichen Scope. Interessant ist die Aus-
gabe. Es wird zweimal 2 ausgegeben. Das bedeutet, auch der erste Aufruf nutzt die nach-
folgende Deklaration und ignoriert die erste Deklaration.
Beispiel
"use strict";
let num = 10; // Primitive Datentypen (Call-by-Value)
function fkt(value) {
value = 20;
}
fkt(num);
[Link](num); // Ausgabe: 10
let obj = [10]; // Objekte (Call-by-Reference)
function fktObj(ref) {
ref[0] = 20;
}
fktObj(obj);
[Link](obj[0]); // Ausgabe: 20
In diesem Beispiel bleibt der Wert der Variablen num unverändert, nachdem die Funktion
fkt aufgerufen wurde, da der Wert nur kopiert und an die Funktion übergeben wird. Im
Gegensatz dazu ändert sich der Wert des ersten Elements in dem Array obj, da die Funk-
tion fktObj eine Referenz auf das ursprüngliche Objekt erhält und somit Änderungen
daran vorgenommen werden können. Das zeigt sich auch mit anderen Objekten, wenn
man deren Eigenschaften so ändert.
7.5 Besonderheiten bei JavaScript im Zusammenhang mit Funktionen 125
Für Leser mit objektorientiertem Hintergrund – mithilfe des Tokens arguments realisiert Java-
Script ein polymorphes (vielgestaltiges) Verhalten in Hinsicht auf Funktionen oder Methoden. Eine
echte Polymorphie mittels Overload gibt es in JavaScript nicht, denn für die Identifizierung einer
Funktion wird ausschließlich der Bezeichner der Funktion verwendet. Die Parameter spielen keine
Rolle. Aber man kann bei einer internen Verwendung von arguments in der Tat eine Funktion so
programmieren, dass man im Sinn der OOP von Polymorphie sprechen kann – insbesondere, wenn
Sie dann noch beachten, dass die Datentypen der Argumente natürlich ebenfalls flexibel sind.
Schauen wir uns ein Beispiel an mit Informationen über Parameter an eine Funktion via
arguments.
"use strict";
function fkt() {
var ausgabe = "Anzahl der Argumente: " + [Link];
for (var i = 0; i < [Link]; i++) {
ausgabe += "\nArgument " + (i + 1) + ": " + arguments[i] +
"\nTyp des Parameters: " + typeof arguments[i];
};
[Link](ausgabe);
}
fkt();
fkt(new Date());
fkt('Die','Antwort','ist','42');
126 7 Funktionen, Prozeduren und Methoden
fkt(1,2,3,5,7,11,13,17,19);
fkt(1,"Text",new Date(),3.14);
Sie sehen die Deklaration einer Funktion, bei der kein Parameter spezifiziert ist. Im Inne-
ren setzen wir eine lokale Variable ausgabe zusammen. Über [Link] wird die
Anzahl der Argumente bestimmt und der lokalen Variable hinzugefügt. In der Schleife
iterieren wir über alle Parameter, die an die Funktion übergeben werden. Dies wird da-
rüber gewährleistet, dass wir als Abbruchbedingung der Schleife die Anzahl der Argu-
mente verwenden, und das wieder über [Link]. Im Inneren der Schleife geben
wir den Inhalt des jeweiligen Parameters aus, und zwar mit arguments[i]. In der Funk-
tion sehen Sie auch den Ausdruck typeof arguments[i]. Mit dem Operator typeof be-
stimmen wir den Datentyp des Parameters.
Zum Aufruf der Funktion verwenden wir nun verschiedene Varianten mit einer unter-
schiedlichen Anzahl an Parametern und unterschiedlichen Parametertypen. Zuerst wird
die Funktion ganz ohne Parameter aufgerufen. Da die Anzahl der Argumente 0 ist, wird
die Schleife im Inneren der Funktion gar nicht durchlaufen, da die Bedingung für eine
Ausführung schon beim Erreichen der Schleife nicht erfüllt ist. Die zweite Version über-
gibt genau ein Argument an die Funktion ein Datumsobjekt. In den folgenden Aufrufen
sehen Sie, dass Sie auch mehrere Argumente übergeben können.
"use strict";
function fkt(a, b) {
var ausgabe = "Anzahl der Argumente: " + [Link];
for (var i = 0; i < [Link]; i++) {
ausgabe +="\nArgument " + (i + 1) + ": " + arguments[i];
};
ausgabe += "\nWert von a: " + a + "\nWert von b: " + b;
[Link](ausgabe);
}
fkt(); //Aufruf ohne Argumente
fkt(new Date()) // Aufruf mit einem Argument - Argument 2 fehlt
fkt(1,2,3,5); // Aufruf mit zu vielen Argumenten
Die in dem Beispiel deklarierte Funktion hat zwei Parameter. Die Bezeichner a und b
stehen als lokale Variablen in der Funktion zur Verfügung. Wie ist nun das Verhältnis
7.5 Besonderheiten bei JavaScript im Zusammenhang mit Funktionen 127
von arguments zu den explizit bezeichneten Parametern? Die Antwort liefert die Logik
der Funktion, in der wir sowohl arguments als auch a und b explizit verwenden. Die drei
verschiedenen Aufrufe demonstrieren zwei potenziell „falsche“ Aufrufsituationen. Wir
verwenden drei Aufrufsituationen, um das Verhältnis der explizit angegebenen Parameter
zu arguments besser zu verdeutlichen.
• Zuerst wird die Funktion ohne Argumente aufgerufen. Die Länge von arguments ist
damit also 0 und die beiden Variablen a und b haben den Wert undefined.
• Nach deutlicher wird das Verhältnis von explizit spezifizierten Parametern und argu-
ments beim zweiten Aufruf mit einem Argument. Die Werte von a und arguments[0]
sind identisch. Der Wert von b ist wieder undefined.
• Aufruf Nummer 3 macht verschiedene Dinge jetzt komplett offensichtlich:
– Die explizit angegebenen Parameter und die Werte im Datenfeld arguments be-
zeichnen sequenziell jeweils das gleiche Argument, sofern Parameter explizit
spezifiziert sind.
– Sie können arguments in einer Funktion immer verwenden, egal, ob Sie Parameter
bei einer Funktion deklarieren oder nicht.
– Deklarieren Sie mehr Parameter, als Sie beim Aufruf Argumente angeben, haben
die Bezeichner für die nicht initialisierten Parameter den Wert undefined.
– Werte, für es keine Bezeichner mehr gibt, werden dennoch in dem Übergabe-Array
bereitstehen und über den passenden Index verfügbar.
Beispiel
function fkt() {
if ([Link] = = 0) { // spezifische Reaktion auf keinen Parameter
} else if ([Link] = = 1) { // spezifische Reaktion auf einen Parameter
}
} else if ([Link] = = [Maximale Anzahl Parameter]) { // spezifische Re-
aktion
} else { // Default-Reaktion
}
} ◄
Wenn Sie dann noch mit typeof gezielt einen Datentyp beachten, haben Sie wirklich ein
polymorphes Verhalten analog Overload.
128 7 Funktionen, Prozeduren und Methoden
"use strict";
function fkt(a) {
[Link](a);
}
fkt();
function fkt(b) {
[Link](1 + b);
}
fkt();
function fkt() {
[Link]("Ohne Parameter");
}
fkt();
fkt(1);
fkt();
Die Antwort bleibt gleich – zuletzt gewinnt. Die zuletzt deklarierte Funktion ersetzt alle
im gleichen Scope bereits deklarierten Funktionen. Weder Anzahl noch Typ der Para-
meter ist relevant. Beim Aufruf ausschließlich der Bezeichner einer Funktion von Be-
deutung, der eine Funktionsreferenz darstellt. Das Verhalten ist auf den ersten Blick
möglicherweise überraschend bis unlogisch, aber es ist im Gegenteil zwangsläufig
und bei genauerem Blick einfach und effizient. Denn Funktionsbezeichner sind Werte
mit Speicheradressen und die mehrfache Deklaration entspricht dem Deklarieren einer
Variablen mit einer Initialisierung durch die Speicheradresse der ersten Funktions-
deklaration. Jede weitere Deklaration weist der vorhandenen Variablen bloß die aktuelle
Speicheradresse als neuen Wert zu. Genaugenommen haben wir im Hintergrund Objekte
und die sind nichts weiter als dynamische Listen. Selbst „normale“ Funktionen sind First
Class Citizen des globalen Objekts.
7.5.3 Funktionsreferenzen
werden bei Webseiten in der Regel an JavaScript-Eventhandler gekoppelt, die bei einem
bestimmten Ereignis die referenzierte Funktion aufrufen. Das bedeutet, wir werden aus-
nahmsweise mal wieder eine HTML-Datei nutzen ([Link]):
…
<script>
"use strict";
function mFktion() {
alert("Wer einen Fehler gemacht hat und ihn nicht korrigiert,"
+ "\nbegeht einen zweiten.");
}
</script>
</head>
<body><h1>Verwendung von Funktionsreferenzen</h1>
<button id="b1">Klick</button></body>
<script>
"use strict";
[Link]("b1").onclick = mFktion;
</script></html>
In dem Beispiel wird im Header eine einfache Funktion deklariert. Wichtig ist die Zeile
im zweiten Skriptcontainer. Hier sehen Sie die Funktionsreferenz, die an einen Event-
handler gekoppelt wird.
Mit [Link]("b1") sprechen wir die Schaltfläche in der Webseite
an. Der mit einem Punkt nachgestellte Token onclick ist ein besagter Eventhandler. Wie
der Bezeichner schon aussagt, wird dieser bei einem Klick aufgerufen. Der gesamte
Ausdruck [Link]("b1").onclick bedeutet also übersetzt, dass auf
den Klick auf die Schaltfläche durch den Anwender reagiert werden soll. Und der zu-
gewiesene Wert ist eben ein Verweis auf eine aufzurufende Funktion, die ein alert-Fenster
öffnet (Abb. 7.1) – eine Funktionsreferenz. Beachten Sie die fehlenden Klammern, die an
der Stelle unbedingt unterbleiben müssen. Es handelt sich um eine Wertzuweisung wie
bei einer anderen Variablen.
Beachten Sie, dass in dem Beispiel der Skriptcontainer mit der Funktionsreferenz hinter dem Kör-
per der Webseite notiert ist. Das ist kein Zufall oder gar Fehler, sondern für einige Browser not-
wendig. Der Skriptbereich muss in unserem Beispiel auf jeden Fall in der Webseite hinter dem
Element stehen, auf das zugegriffen wird. Andernfalls funktioniert die Zuordnung zur Schaltfläche
nicht. Wenn der Browser die Zeile mit der HTML-Beschreibung der Schaltfläche noch nicht ver-
arbeitet hat, läuft die Anweisung [Link]("b1") ins Leere und der Browser mel-
det einen Fehler. Wir gehen noch genauer auf dieses Thema ein.
"use strict";
function fkt() {
[Link]("Ohne Parameter");
}
var alias = fkt;
alias();
Abb. 7.2 Ein Dialogfenster wurde geöffnet und nach dem Bestätigen ist der Rest der Webseite zu
sehen (Abb. 7.2)
7.5 Besonderheiten bei JavaScript im Zusammenhang mit Funktionen 131
Funktionsreferenzen also gar keine Argumente für die Parameter angeben! Was also tun?
Die Antwort führt zum Beispiel zu sogenannten anonymen Funktionen (oder genauer –
die direkte Deklaration einer Funktion bei einer Zuweisung), Callbacks und Lambda-
Ausdrücken. Dort werden wir das Problem gleich lösen.
Bei der Deklaration einer Funktion kann der Funktionsname in einigen Situationen
entfallen. Man redet dann von einer anonymen Funktion (im Gegensatz zu einer be-
nannten Funktion, wie wir sie bisher immer verwendet haben). Eine anonyme Funktions-
deklaration ist dann sinnvoll, wenn sie nur an dieser Stelle verfügbar sein muss. Dann
braucht man auch keinen Funktionsbezeichner. Besonders interessant ist, dass eine sol-
che anonyme Funktion einer Variablen zugewiesen oder an eine Methode weitergereicht
werden kann. Und diese Zuweisung zu einer Variablen ist genau im Fall einer Funktions-
referenz gegeben. Eine anonyme Funktion liefert immer einen Zeiger auf sich selbst zu-
rück.
…
[Link]("b1").onclick = function() {
alert("Wer einen Fehler gemacht hat und ihn nicht korrigiert," +
"\nbegeht einen zweiten.");
};
</script></html>
In dem Skript sehen Sie, wie eine anonyme Funktion als Funktionsreferenz einem Java-
Script-Eventhandler zugewiesen wird. In den Klammern können Sie wie üblich bei Be-
darf Parameter deklarieren. Natürlich können Sie im Inneren einer so referenzierten an-
onymen Funktion beliebige weitere Funktionen aufrufen und diesen Aufrufen kann man
ja Argumente mitgeben. Sie sehen hier also, wie man im Zusammenhang mit Funktions-
referenzen Argumente angeben kann.
Üblicherweise wird man im Inneren der anonymen Funktion bei Bedarf eine
Funktionmit Parametern aufrufen.
132 7 Funktionen, Prozeduren und Methoden
Wie wir bei Variablen besprochen haben, muss man bei der Deklaration auf das Hoisting
aufpassen, bei dem Variablendeklarationen während der Kompilierungsphase an den An-
fang ihres jeweiligen Gültigkeitsbereichs verschoben werden, bevor der Code tatsächlich
ausgeführt wird. Das bedeutet, dass Variablendeklarationen unabhängig davon, wo sie
innerhalb eines Gültigkeitsbereichs deklariert sind, so behandelt werden, als ob sie am
Anfang des Gültigkeitsbereichs stehen. Beispiel:
Beispiel
[Link](x); // undefined
var x = 5; ◄
In dem obigen Code wird kein Fehler erzeugt, obwohl x erst nach der Ausgabe mit
[Link] deklariert wird. Die Deklaration var x wird an den Anfang des Gültigkeits-
bereichs verschoben, aber die Wertzuweisung erfolgt erst nach der Ausgabe. Im Fall von
Funktionen gibt es ebenso Hoisting. Der folgende Code funktioniert, obwohl die Funk-
tion aufgerufen wird, bevor sie deklariert wird. Die Funktionsdeklaration wird an den
Anfang des Gültigkeitsbereichs verschoben.
Beispiel
Beispiel
In diesem Fall wird var f gehoistet, aber die Zuweisung von function() {…} an f nicht,
was dazu führt, dass f undefined ist, wenn der Aufruf ausgeführt werden soll.
7.5 Besonderheiten bei JavaScript im Zusammenhang mit Funktionen 133
JavaScript erlaubt auch den Aufbau verschachtelter Funktionen. Dabei wird eine
Funktionsdeklaration direkt in eine andere Funktion hinein notiert! In dem Fall gelten
einige spezielle Regeln:
• Die innere verschachtelte Funktion kann nur über die äußere aufgerufen werden und
ist für direkte Zugriffe unzugänglich!
• Die innere Funktion kann die Variablen oder andere innere Funktionen der äußeren
Funktion verwenden.
• Die äußere Funktion hat keinen Zugang zu den lokalen Variablen der inneren Funktion.
• Die innere Funktion wird als lokale Variable der äußeren Funktion angesehen!
Zur Laufzeit erzeugt der JavaScript-Interpreter beim Aufruf der äußeren Funktion den
Code der inneren Funktion. Dabei entsteht ein sogenanntes Closure (Einschluss) der in-
neren Funktion – das ist der Code der Funktion und eine Referenz auf alle Variablen, die
von der inneren Funktion benötigt werden. Ein Closure kombiniert den Programmcode
mit der lexikalischen Umgebung – d. h., das Closure „merkt“ sich die Umgebung, in der
es erzeugt wurde. Beispiel ([Link]):
"use strict";
function aussen() {
var a = 21;
[Link]("Die Antwort ist ");
function innen1() {
innen2();
[Link](a);
}
function innen2() {
a *= 2;
}
innen1();
[Link]("!");
}
aussen();
• In dem Skript wird eine Funktion aussen() definiert, die direkt nach der Deklaration
aufgerufen wird. Damit wird ein statischer Text ausgegeben.
• In der Funktion aussen() gibt es eine lokale Variable a, die mit dem Wert 21 initiali-
siert wird. Dazu finden Sie zwei innere Funktionen innen1() und innen2().
• Die innere Funktion innen2() verdoppelt den Wert der Variable a. Es wird also auf
eine lokale Variable a aus dem Geltungsbereich der äußeren Funktion zugegriffen.
134 7 Funktionen, Prozeduren und Methoden
• Auch in der inneren Funktion innen1() wird auf die lokale Variable a aus dem
Geltungsbereich der äußeren Funktion zugegriffen. Zusätzlich wird aber davor die an-
dere innere Funktion innen2() aufgerufen.
• Der Aufruf der inneren Funktion innen1() erfolgt im Geltungsbereich der äußeren
Funktion, womit also auch indirekt innen2() aufgerufen wird.
Mit Closures kann man in JavaScript das Konzept der Datenkapselung unterstützen, das im Rah-
men der objektorientierten Programmierung zu den Grundprinzipien zählt. Innere Funktionen wer-
den auch gerne als anonyme Funktionen erstellt. Das sehen wir im Abschnitt zu Callbacks.
7.5.7 Lambda-Ausdrücke
"use strict";
var lambda1 = () => 4;
var lambda2 = () => {
return 4;
}
var lambda3 = _ => 4;
[Link](lambda1()); // Aufruf der 1. Lambda-Funktion
[Link](lambda2()); // Aufruf der 2. Lambda-Funktion
[Link](lambda3()); // Aufruf der 3. Lambda-Funktion
• Die erste Deklaration eines Lambda-Ausdrucks zeigt eine einfache Deklaration ohne
Parameter mit Zuweisung zu einer Variablen und Rückgabe von einem Literal. Dies
ist die Verwendung der Notation eines „knappen“ Funktionskörpers. In einem knap-
pen Funktionskörper ist lediglich ein Ausdruck nötig, und eine implizite Rückgabe
wird automatisch angehängt. Die Parameterliste für eine parameterlose Funktion
muss mit einem Klammernpaar deutlich gemacht werden.
• Die zweite Deklaration eines Lambda-Ausdrucks ohne Parameter mit Zuweisung zu
einer Variablen und Rückgabe von einem Literal zeigt die explizite Verwendung von
return. Der Körper eines Lambda-Ausdrucks kann in geschweifte Klammern gesetzt
werden (gewöhnlicher Funktionskörper) und muss es auch, wenn dort nicht nur ein-
fache Ausdrücke stehen (etwa explizites return). In einem Blockkörper muss dann
aber auch eine explizite Rückgabe-Anweisung verwendet werden.
• Variante 3 ist eine alternative Deklaration eines Lambda-Ausdrucks ohne Parameter
mit _ statt leerer Klammern für den Parameterausdruck.
Die Lambda-Ausdrücke in dem Beispiel zeigen die Deklaration mit einem Parameter
([Link]):
"use strict";
var lambda1 = x => x * x;
136 7 Funktionen, Prozeduren und Methoden
• Die erste Deklaration eines Lambda-Ausdrucks zeigt die Verwendung eines nicht-
geklammerten Übergabewerts und die Zuweisung zu einer Variablen sowie Rückgabe
eines berechneten Werts (Multiplikation des übergebenen Werts mit sich selbst).
• Die zweite Deklaration eines Lambda-Ausdrucks ist fast identisch, nur steht der
Übergabewert in Klammern.
"use strict";
var lambda1 = (x, y) => x * y;
[Link](lambda1(11, 2)); // Aufruf der Lambda-Funktion
Hier sehen Sie die Deklaration eines Lambda-Ausdrucks mit mehreren Übergabewerten.
Diese kann man dann im Ausdruck verwenden. Wichtig – bei mehreren Parametern müs-
sen diese geklammert werden. Die Lambda-Notation bietet also oftmals eine kürzere und
kompaktere Syntax für die Definition von Funktionen, was den Code lesbarer und weni-
ger fehleranfällig machen kann, insbesondere für kurze und einfache Funktionen.
7.5.8 Callbacks
Zum Abschluss dieses Kontextes von Funktionen soll noch ein wichtiger Begriff ge-
nauer besprochen werden, der von erheblicher Bedeutung ist und auch klärt, wie man
Funktionsreferenzen und anonyme Funktionen als auch die Lambda-Notation direkt aus
JavaScript allgemein nutzen kann.
Unter einer Callback-Funktion oder kurz einem Callback ist allgemein eine Java-
Script-Funktion zu verstehen, die einen Zeiger auf sich liefert. Damit sind Funktions-
referenzen, anonyme Funktionen als auch Lambda-Ausdrücke per se Callbacks. Aber
nicht jede Callback-Funktion muss eine dieser Varianten sein.
Grundsätzlich kann man Callbacks einer Methode oder Funktion oder auch Iteratoren als
Argument oder Option übergeben. Neben den schon konzeptionell als Callbacks zu ver-
stehenden anonymen Funktionen oder Lambda-Ausdrücken gibt es auch die Möglich-
keit, eine benannte Callback-Funktion zu deklarieren. In allen sinnvoll nutzbaren Situa-
tionen wird sie als Rückgabewert eine Funktionsreferenz liefern. Betrachten Sie folgen-
des Beispiel ([Link]):
7.5 Besonderheiten bei JavaScript im Zusammenhang mit Funktionen 137
"use strict";
function getText(text) {
return function() {
mFktion(text);
};
}
Betrachten wir die Deklaration der Callback-Funktion getText(). Was wird denn von der
Funktion zurückgegeben? Die Referenz auf function(){ meineFktion(text);}. Dies ist
eine anonyme, innere Funktion (ein Closure) und die liefert einen Zeiger auf die Funk-
tion – wie gewünscht und notwendig. Aber damit haben wir bei einem flüchtigen Blick
doch scheinbar exakt die Situation vorliegen, als ob wir eine anonyme Funktion hin-
geschrieben hätten. Sie können sich dann natürlich fragen, wozu man überhaupt diese
benannten Callback-Funktionen benutzt beziehungsweise warum man eine solche Tech-
nik unterstützt? Man kann sie wiederverwenden, weil man ja im Grunde eine „normale“
Funktion deklariert. Auch kann es interessant sind, wenn man eine komplexere Logik
baut und/oder verschiedene return-Anweisungen liefern möchte. Man hat zudem eben
auch explizit einen konventionellen Funktionsaufruf und bekommt einen Funktions-
zeiger. Übergabeparameter lassen sich so manchmal einfacher verwenden. Dennoch
haben benannte Callbacks in JavaScript etwas an Bedeutung verloren. Insbesondere
Lambda-Ausdrücke sind oft einfacher und schneller notiert.
7.5.9 JavaScript-Generatoren
EinJavaScript-Generator ist eine Funktion, die in der Lage ist, ihre Ausführung zu pau-
sieren und Werte Schritt für Schritt zu generieren, anstatt sie alle auf einmal zurück-
zugeben. Dabei wird das Schlüsselwort yield verwendet und bei der Deklaration dem
Schlüsselwort function ein Stern nachgestellt. Der *-Operator zeigt damit explizit an,
dass es sich um eine Generatorfunktion handelt, und ermöglicht damit erst die Ver-
wendung des yield-Schlüsselworts innerhalb der Funktion. Hier ist ein einfaches Beispiel
für einen JavaScript-Generator ([Link]):
"use strict";
function* zahlenGenerator() {
yield 1;
yield 2;
yield 3;
}
const generator = zahlenGenerator();
[Link]([Link]().value); // Gibt 1 aus
138 7 Funktionen, Prozeduren und Methoden
In diesem Beispiel definiert die Funktion zahlenGenerator einen Generator, welche die
Zahlen 1, 2 und 3 zurückgibt und dabei das yield-Schlüsselwort verwendet. Wenn der
Generator mit der Methode next() aufgerufen wird, pausiert er seine Ausführung beim
ersten yield und gibt den Wert zurück. Bei jedem weiteren Aufruf von next() wird die
Ausführung fortgesetzt, bis alle yield-Statements abgearbeitet sind. Danach wird { value:
undefined, done: true} zurückgegeben, was anzeigt, dass der Generator keine weiteren
Werte erzeugen kann.
Bei der Deklaration von Funktionen in JavaScript gibt es noch einige weitere Features
und Besonderheiten, die Sie kennen sollten.
[Link] Bedingte Deklaration
Eine Funktionsdeklaration kann von einer Bedingung abhängig gemacht werden. Auf
diese Weise können Sie z. B. Funktionen mit gleichen Namen, aber unterschiedlichen
Funktionalitäten definieren ([Link]).
[Link]();
});
7.5 Besonderheiten bei JavaScript im Zusammenhang mit Funktionen 139
Die Wirkungsweise der Funktion operation wird vom Inhalt der Variablen name ab-
hängig gemacht, die wieder einmal aufgrund einer interaktiven Benutzereingabe in
[Link] erfolgt, um das Beispiel etwas interessanter zu machen. Die eigentliche be-
dingte Deklaration ist auf den if-else-Block eingeschränkt. Beachten Sie, dass wir hier
explizit keinen strikten Modus verwenden, denn der Funktionsbezeichner wird dyna-
misch angelegt und er entspricht damit einer bis dahin nicht vorhanden Variable, die
ohne var oder let angelegt wird. Das Problem kann man zwar durchaus lösen, denn im
Hintergrund wird mit einem Function-Konstruktor gearbeitet, aber die Möglichkeit, die
Funktionsdeklaration von Bedingungen abhängig zu machen (ab JavaScript 1.5 verfüg-
bar), wird in der Praxis selten eingesetzt.
"use strict";
function multi(a, b = 1) {
return a * b;
}
[Link](multi(4, 5))
[Link](multi(4))
Wenn der zweite Parameter beim Aufruf der Funktion nicht angegeben wird, wird dafür
der Wert 1 verwendet.
"use strict";
var colors = ["rot", "gruen", "blau"];
[Link](function(color, index) {
[Link]("Farbe an Index " + index + ": " + color);
});
In diesem Beispiel wird auf dem Array colors die Callback-Funktion wird für jedes Ele-
ment im Array ausgeführt. Die Argumente in der Callback-Funktion entsprechen dem
aktuellen Wert und Index des Elements während der Iteration.
140 7 Funktionen, Prozeduren und Methoden
JavaScript unterstützt sogenannte Selbstaufrufe. Dies nennt man auch Rekursion oder
einen rekursiven Aufruf. Rekursive Aufrufe haben eine gewisse Ähnlichkeit mit Schlei-
fen, da auch damit Anweisungen wiederholt werden. Beachten Sie aber, dass rekur-
sive Aufrufe zwar ähnlich wie Schleifen funktionieren, aber die rekursiven Funktions-
aufrufe intern auf dem sogenannten Stack (Stapel) des Rechners abgelegt und von dort
wieder genommen werden, während Schleifen damit nichts zu tun haben und über einen
Speicherbereich mit Namen Heap (Haufen) arbeiten. Dieser Unterschied kann sich auf
die Performance auswirken, aber vor allen Dingen ist die Reihenfolge einer Verarbeitung
von Bedeutung. Denn während eine Schleife chronologisch bei jedem Durchlauf An-
weisungen ausführt, erfolgt die Abarbeitung bei einer Rekursion nach dem FILO-Prin-
zip. FILO steht für „first in last out“, was uns schon bei der JavaScript-Datenstruktur
Stack begegnet ist. Die Namensgleichheit ist nicht zufällig. Wie bei einem realen Karten-
stapel, auf dem Sie Karten einzeln auf einen Kartenstapel ablegen und wieder einzeln
wegnehmen, werden Sie diese von oben wegnehmen, bis die letzte Karte entnommen
wurde und der Stapel weg ist. Die Karte, die als Erstes (first in) auf den Stapel gelegt
wurde, ist also vor dem „Abarbeiten“ des Stapels ganz unten und wird als letzte Karte
verarbeitet (last out).
[Link] Allgemeine Rekursion
Nun ist die FILO-Reihenfolge bei Rekursion nicht immer von Bedeutung. Im einfachen
Fall ruft sich eine Funktion im Laufe der Abarbeitung bloß selbst wieder auf und macht
irgendetwas, was vom vorherigen Aufruf nicht abhängt. Aber wenn ein Aufruf von dem
vorherigen abhängt, dann ist die Reihenfolge natürlich massiv von Bedeutung. Gerade
wenn eine rekursive Funktion einen Rückgabewert liefert, ist das FILO-Prinzip wichtig.
Die nachfolgenden Listings zeigt sowohl die einfache Situation von unabhängigen Auf-
rufen als auch einen klassischen Algorithmus für Rekursion.
"use strict";
var i=0, j=0;
function rekFkt() {
j += [Link]();
i++;
if (i < 5) rekFkt();
}
rekFkt();
[Link](i, j);
In dem Beispiel ([Link]) gibt es zwei globale Variablen. Eine davon wird als Zähl-
variable (i) verwendet, die andere mittels eines Zufallsmechanismus bei jedem Aufruf
um einen Wert zwischen 0 und 1 erhöht. Sofern der Wert der Zählvariable kleiner 5 ist,
7.5 Besonderheiten bei JavaScript im Zusammenhang mit Funktionen 141
wird die Funktion wieder selbst aufgerufen. Nach 5 Aufrufen sind die Selbstaufrufen be-
endet und die Werte von i und j werden ausgeben. Die Ausgabe wird etwas der Art sein:
5 4.059594159793312.
Das Ergebnis ist also etwas, was wir auch sehr gut mit einer Schleife hinbekommen
und was nicht wirklich das Verhalten und die Bedeutung von Rekursion ausnutzt oder
deutlich macht. Kommen wir zu einer Modifikation ([Link]):
"use strict";
var i=0, j=0;
function rekFkt() {
j += [Link]();
i++;
[Link](i, j);
if (i < 5) rekFkt();
}
rekFkt();
Die Ausgabe von i und j erfolgt jetzt innerhalb der rekursiven Funktion. Beachten Sie,
dass diese vor (!) dem Selbstaufruf eines neuen Funktionsaufrufs erfolgt. Sie erhalten
eine Ausgabe der Art:
1 0.9639843195432956
2 1.1839506183493862
3 1.8131400423823207
4 1.9862675394910094
5 2.35612602231419
Das ist eine chronologische Erhöhung von i und j, die Sie hier sehen. Aber auch diese
Variante verschleiert, was Rekursion eigentlich ausmacht. Die nächste Modifikation
([Link]) zeigt aber den Kern unter einem ersten, speziellen Blickwinkel.
"use strict";
var i=0, j=0;
function rekFkt() {
j += [Link]();
i++;
if (i < 5) rekFkt();
[Link](i, j);
}
rekFkt();
Die Ausgabe von i und j erfolgt wieder innerhalb der rekursiven Funktion, aber nach
(!) dem Selbstaufruf eines neuen Funktionsaufrufs. Sie erhalten fünfmal die gleiche
142 7 Funktionen, Prozeduren und Methoden
usgabe. Wie kann das sein? Die Ausgaben erfolgen, nachdem i und j fünfmal erhöht
A
wurden und geben dann fünfmal den Endwert (!) von i und j aus. Jetzt lösen wir mit der
letzten Variation dieses Beispiels die Situation auf. Dazu nutzen wir Parameter ([Link]).
"use strict";
function rekFkt(i=0, j=0) {
j += [Link]();
i++;
if (i < 5) rekFkt(i,j);
[Link](i, j);
}
rekFkt();
Das ist eine chronologische Verminderung (!) des Werts von i und j, die Sie hier sehen.
Besprechen wir den genauen Ablauf.
1. Wir haben jetzt keine globalen Variablen, sondern zwei Parameter i und j bei der
Funktion. Wenn die Funktion ohne Argumente aufgerufen wird, werden i und j auf
den Standardwert von 0 gesetzt (Vorgabeparameter).
2. In der Funktion wird zu j wieder ein zufälliger Wert zwischen 0 und 1 addiert und i
inkrementiert.
3. Anschließend wird überprüft, ob i kleiner als 5 ist. Wenn dies der Fall ist, wird die
Funktion rekFkt erneut aufgerufen, wobei die aktuellen Werte von i und j übergeben
werden. Bei der Ausgabe von oben ist das für i 1 und j 0.3152071516397168.
4. Beim nächsten Aufruf sind das für i 1 und j 0.3275076856749135.
5. Beim letzten Aufruf sind das für i 5 und j 2.161980400889223.
6. Bis zu der Stelle ist keine einzige Ausgabe erfolgt. Aber alle fünf Funktionsaufrufe
sind mit den jeweilig aktuellen Werten von i und j auf dem Stack gelandet.
7. Der Stack wird jetzt erst mit den jeweilig aktuellen Werten von i und j vom Stack ge-
nommen und dabei die noch offene Ausgabe getätigt.
Damit sollte das Verhalten von Rekursion klar sein. Aber man kann auch noch den Rück-
gabewert einer Funktion einbeziehen. Eine klassische Anwendung der Rekursion – die
Fakultätsberechnung – soll das demonstrieren ([Link]).
7.5 Besonderheiten bei JavaScript im Zusammenhang mit Funktionen 143
"use strict";
function fak(n) {
if (n == 0) {
return 1;
} else {
return n * fak(n - 1);
}
}
[Link](fak(5));
Die Funktion besitzt einen Übergabeparameter und einen Rückgabewert. Wenn die
Funktion aufgerufen wird (fak(5)), hat der Parameter n den Wert 5. Nun wird im Inneren
der Funktion geprüft, ob der Wert von n 0 ist. Das ist nicht der Fall und der else-Zweig
wird „ausgeführt“, wobei „ausgeführt“ hier eben bedeutet, dass der Aufruf der Funktion
auf den Stack gelegt wird, mit dem Wert 5 für den Parameter. Und der else-Zweig sorgt
dafür, dass die nächste „Karte“ auf dem Stapel der Aufruf von fak(4) ist (fak(n – 1)).
Dann kommen fak(3), fak2(), fak(1) und zuletzt fak(0) auf den Stapel.
Zuerst wird fak(0) dann aufgerufen und vom Stapel genommen. Diese Funktion lie-
fert den Rückgabewert 1, was im Moment uninteressant ist. Aber beim nächsten Auf-
ruf (fak(1)) gelangen wir in den else-Zweig. Und dort wird der Wert von n (1) mit dem
Rückgabewert von fak(0) (1) multipliziert. Das Ergebnis (1) ist der Rückgabewert von
fak(1). Nun liegt fak(2) oben im Stapel. Und dort wird der Wert von n (2) mit dem Rück-
gabewert von fak(1) (1) multipliziert. Das Ergebnis (2) ist der Rückgabewert von fak(2).
Jetzt ist fak(3) oben im Stapel und hier wird der Wert von n (3) mit dem Rückgabewert
von fak(2) (2) multipliziert. Das Ergebnis (6) ist der Rückgabewert von fak(3). So geht es
weiter. Bei fak(4) wird der Wert von n (4) mit dem Rückgabewert von fak(3) (6) multi-
pliziert. Das Ergebnis (24) ist der Rückgabewert von fak(4). Der letzte Funktionsaufruf
auf dem Stapel nimmt den Wert 5 für n und multipliziert ihn mit 24. Und diese Rückgabe
von 120 ist letztendlich das, was der Aufrufer von fak(5) bekommt und verarbeiten kann.
Der Stack ist jetzt auch wieder leer.
Beispiel
"use strict";
var a = [[1,2],[3,4]];
var b = [[1,2],[[3],[4]],[5,6,7,8]];
var c = [[1,2],[[3],[4]],[9,[5,6,7,8]]];
var person = { name:"Otto", titel: ["Prof", "Dr"], adresse: {
plz:12345, ort:"Irgendwo", strasse: { name:"Milchstrasse", nr:42 } }};
function recursiveIteration(obj) {
for (var key in obj) {
// Optional: Test, ob Schlüssel Eigenschaft hat
//if ([Link](key)) {
if (typeof obj[key] === "object") {
[Link](key + " (Object):");
recursiveIteration(obj[key]); // Rekursiver Aufruf für Objekte
} else if ([Link](obj[key])) {
[Link](key + " (Array):");
for (var i = 0; i < obj[key].length; i++) {
recursiveIteration(obj[key][i]); // Rekursiver Aufruf
}
} else {
[Link](key + ": "+obj[key]+", Type: " + typeof obj[key]);
}
//}
}
}
[Link]("Array a:");
recursiveIteration(a); // Iteration über Array a
[Link]("Array b:");
recursiveIteration(b); // Iteration über Array b
[Link]("Array c:");
recursiveIteration(c); // Iteration über Array c
[Link]("Person:");
recursiveIteration(person); // Iteration über das Person-Objekt
7.6 Vordefinierte Funktionen in JavaScript 145
In diesem Code haben wir eine Funktion recursiveIteration definiert, die ein Objekt ent-
gegennimmt und rekursiv durch seine Eigenschaften iteriert. Für jedes Eigenschaftspaar
wird überprüft, ob der Wert ein Objekt oder ein Array ist. Wenn es sich um ein Objekt
handelt, wird die Funktion rekursiv aufgerufen. Wenn es sich um ein Array handelt, wird
eine Schleife verwendet, um rekursiv über jedes Element des Arrays zu iterieren. Wenn
der Wert weder ein Objekt noch ein Array ist, wird er einfach ausgegeben, zusammen
mit seinem Typ.
Array a:
0 (Object):
0: 1, Type: number
1: 2, Type: number
1 (Object):
0: 3, Type: number
1: 4, Type: number
Array b:
…
3: 8, Type: number
Person:
name: Otto, Type: string
titel (Object):
0: Prof, Type: string
1: Dr, Type: string
adresse (Object)
…
nr: 42, Type: number
Es gibt in JavaScript einige vordefinierte Funktionen, die Sie direkt in Ihren Skrip-
ten aufrufen können. Sie gehören unmittelbar zum globalen Objekt. Es gibt einmal die
Funktionen, über die sie einen Parameter konvertieren können. Beispielsweise Number(),
Boolean() oder String(). Dazu gibt es Funktionen, die konkret Objekte erzeugen können
und deren Verwendung wir beim Abschnitt über OOP beleuchten. Aber sie sollen schon
an dieser Stelle angegeben werden, zumal sie auch teils schon verwendet wurden:
Hintergrundinformation
Erzeugen von Objekten in JavaScript kann literal über ein Array-Literal oder JSON erfolgen. Oder
man verwendet einen Konstruktor. Im Zusammenhang mit den genannten Funktionen wird man
dabei meist ein vorangestelltes new finden. Nun stellt sich die Frage, ob es in JavaScript einen
Unterschied zwischen dem Aufruf von Array() und new Array() oder den anderen Funktionen zum
Erzeugen eines Objekts gibt? Zuerst einmal – die Verwendung mit vorangestelltem new ist üb-
licher. Aber wenn Sie Array() ohne das new-Schlüsselwort aufrufen, wird die Funktion wie eine
reguläre Funktion aufgerufen. Nur gibt sie einfach ein neues Array zurück. Aber das erhalten Sie
auch beim Voranstellen von new. Der Hauptunterschied besteht also hier eigentlich nur darin, wie
Sie Arrays erstellen:
Aber es kann sich auch weitgehender auswirken, ob man mit oder ohne new arbeitet. Wenn Sie
Date() ohne das new-Schlüsselwort aufrufen, wird die Funktion wie eine reguläre Funktion be-
handelt, und sie gibt eine Zeichenkette mit dem aktuellen Datum und der aktuellen Uhrzeit zurück.
Wenn Sie new Date() verwenden, wird ein neues Date-Objekt erstellt, das das aktuelle Datum und
die aktuelle Uhrzeit darstellt und einen numerischen Timestamp sowie typische Datumsmethoden
bereitstellt ([Link]). Dieses Verhalten hat man auch für andere eingebaute JavaScript-
Konstruktoren. Wenn Sie die Konstruktoren ohne new aufrufen, können sie ein anderes Verhalten
haben, abhängig von der Implementierung, während die Verwendung von new ein neues Objekt
des entsprechenden Typs erstellt ([Link]). In der Regel eröffnet die Verwendung mit new spä-
ter mehr Möglichkeiten. Wir werden das Verhalten auch bei selbstdefinierten Konstruktormethoden
nachbilden.
"use strict";
var a = Array();
var b = new Array();
[Link](a, typeof a);
[Link](b, typeof b);
var c = Date();
var d = new Date();
[Link](c, typeof c);
[Link](d, typeof d);
Die folgenden Funktionen sind ebenso in JavaScript bereits vorhanden (Tab. 7.1):
"use strict";
var a = "50";
var b = "5.6abc";
var c = "§$%&/()?ß";
var d = "[{'alter':42}]";
[Link]('a=', a, '; b=', b);
[Link]('a + b:',a + b);
[Link]('parseInt(a) + parseFloat(b)',parseInt(a) + parseFlo-
at(b));
7.6 Vordefinierte Funktionen in JavaScript 147
• Nach der Initialisierung und der Ausgabe der Variablenwerte werden die beiden
Zeichenketten a und b zunächst aneinandergefügt.
• Erst durch die beiden Funktionen parseInt und parseFloat können die Zeichen-
ketten in Zahlen umgewandelt und arithmetisch addiert werden. Die Variable a wird
in die Ganzzahl 50 und die Variable b durch parseFloat in die Dezimalzahl 5.6 um-
gewandelt. Der Zeichenkettenteil abc wird abgeschnitten. Dadurch ergibt sich der ma-
thematische Wert 50 + 5.6 = 55.6.
• Die Funktionen werden mit vertauschten Werten aufgerufen. Zuerst wird a als
Dezimalzahl interpretiert und b als Ganzzahl. Die Konvertierung für die Variable b
wird nach dem Punkt abgeschnitten. Dadurch ergibt sich der mathematische Wert
50 + 5 = 55.
7.6 Vordefinierte Funktionen in JavaScript 149
• Das Kodieren von einem String sehen Sie mit zwei verschiedenen Funktionen. Be-
achten Sie die unterschiedlichen Wege. Das veraltete escape erzeugt die Maskierun-
gen teils auf eine andere Art wie encodeURIComponent. Prinzipiell wird jedes mas-
kierte Zeichen mit einem Prozentzeichen und einem zweistelligen Hex-Code der Uni-
code-Kodierung dargestellt.
• Die Anwendung von eval erfolgt auf einen String, der die Deklaration eines Arrays
mit JSON als Inhalt enthält. Nach dem Evaluieren erhalten Sie ein Objekt, dessen
Eigenschaften Sie verwenden können.
In HTML gibt es keine Eingabefelder, die speziell für Zahlen genutzt werden
können. Die Eingaben werden immer als Zeichenkette betrachtet. Möchten
Sie mit den Eingaben rechnen, können Sie mit isNaN z. B. überprüfen, ob die
Eingabe als Zahl interpretiert werden kann. Danach haben Sie die Möglich-
keit, die Zeichenkette über parseFloat oder parseInt in eine Zahl des ent-
sprechenden Typs umzuwandeln.
Module
8
Übersicht
Modularisierung bedeutet das Zusammenfassen von Codeteilen, um diese später
besser wiederverwenden zu können. Das beginnt bei Unterprogrammen und hat in
JavaScript vor einiger Zeit zu einer weiteren, neueren Form der Strukturierung von
JavaScript-Quellcode geführt – Modulen.
Vereinzelt sind wir in vorherigen Beispielen schon mit Modulen in Berührung ge-
kommen. Damit war es z. B. möglich, in einem Skript unter [Link] eine Benutzer-
eingabe von der Konsole entgegen zu nehmen. In JavaScript bezieht sich der Begriff
„Modul“ auf eine Möglichkeit, Code zu organisieren, indem man ihn in separate Dateien
aufteilt, die unabhängig voneinander funktionieren können. Das hat von der Idee durch-
aus Ähnlichkeiten zu Unterprogrammen, aber auch Klassen bzw. Objekten. Auch hier
spricht man von einer „Modularisierung“, aber die Erstellung von Modulen ist in Java-
Script eine Dateitechnik und relativ unabhängig davon, was man genau an Strukturen in
dem Modul bereitstellt. Dennoch ist die Strukturierung und Wiederverwendbarkeit von
Code auch hier der Fokus. Ebenso kann man Konflikte zwischen verschiedenen Teilen
eines Codes zu vermeiden. Es gibt verschiedene Standards und Techniken, um Module in
JavaScript zu erstellen. Zwei betrachten wir etwas näher.
• CommonJS
• ES6-Module
© Der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH, ein 151
Teil von Springer Nature 2025
R. Steyer, Programmieren mit JavaScript, [Link]
152 8 Module
ES6-Module bieten einige Vorteile gegenüber CommonJS, wie zum Beispiel die sta-
tische Struktur, die zu einem besseren Tree-Shaking führen kann (entfernen nicht ver-
wendeter Codepassagen), und die Unterstützung für asynchrone Module. Allerdings sind
sie möglicherweise nicht in allen Umgebungen vollständig unterstützt. In beiden Fällen
helfen Module, Code sauber zu organisieren, Abhängigkeiten klar zu definieren und die
Wartbarkeit von Projekten zu verbessern.
8.2 CommonJS
Wichtig
Wenn Sie diese Art von Modulen in einem Browser verwenden, werden Sie
normalerweise eine Fehlermeldung der Art erhalten: Uncaught ReferenceError: re-
quire is not defined
CommonJS war ursprünglich nicht für ein Browserumfeld gedacht, aber es gibt
mittlerweile mehrere Konstellationen, in denen das doch geht (Abschn. 8.4).
• Bei einer absoluten Referenzierung beginnt die Dateiangabe beginnt mit dem Datei-
pfad, der von der Wurzel des Projekts ausgeht. Zum Beispiel: /pfad/zum/[Link].
• Bei der relativen Dateiangabe beginnt man mit./ (für das aktuelle Verzeichnis). Be-
achten Sie auf jeden Fall den vorgestellten Punkt. Oder Sie verwenden zwei voran-
gestellte Punkte (../) für übergeordnete Verzeichnisse, gefolgt von dem Pfad zum
Modul relativ zur aktuellen Datei. Zum Beispiel./[Link]. oder./modulDir/mein-
[Link].
Es ist wichtig zu beachten, dass die Dateiendung (in der Regel *.js) normalerweise weg-
gelassen werden kann, da [Link] automatisch nach Dateien mit der angegebenen Er-
weiterung sucht, wenn keine Erweiterung angegeben ist.
Hier ist ein Beispiel mit mehreren Exports in einer Datei [Link], die sich in einem
Unterverzeichnis commonJSmodule der importierenden Datei befinden soll, und die
8.2 CommonJS 153
Funktionen add, multi, sub und div, aber nicht pot exportiert. Dabei gibt es verschiedene
Wege. Man kann einmal exports um die jeweiligen Funktionszeiger erweitern. Oder man
weist [Link] einen Funktionszeiger oder in geschweiften Klammern mehrere
Funktionszeiger zu:
Von den fünf deklarierten Funktionen werden vier exportiert und die fünfte Funktion
(pot) bleibt privat. Beim Exportieren kann man – wie hier – für jede Funktion eine
einzelne Anweisung angeben oder aber – wie hier auskommentiert – alle zusammen in
geschweiften Klammern. Nutzen Sie aber nur eine der Varianten.
Hier ist ein Beispiel mit einem Import ([Link]), bei dem sich das Modul
math relativ im Unterverzeichnis commonJSmodule befindet.
154 8 Module
"use strict";
// Funktionen in Modul importieren. Funktion sub wird nicht importiert
// Einzelne Anweisungen für jede Funktion
/*
const { add } = require('./commonJSmodule/math');
const { multi } = require('./commonJSmodule/math');
const { div } = require('./commonJSmodule/math');
*/
// Eine Anweisung für alle importierten Funktionen
const { add, multi, div } = require('./commonJSmodule/math');
// Funktionen aus dem Modul verwenden
[Link](add(2, 3)); // Ausgabe: 5
[Link](multi(2, 3)); // Ausgabe: 6
[Link](div(3, 2)); // Ausgabe: 1.5
Man kann die exportierten Funktionen einzeln importieren (hier auskommentiert) oder
mehrere in geschweiften Klammern. Das ist analog dem Export. Beachten Sie, dass das
Modul zwar vier Funktionen exportiert, hier aber zu Demonstrationszwecken nur drei
davon importiert werden. Das ist natürlich möglich und sogar ein Kernkriterium von
Modulen, nur einen Teil der bereitgestellten Funktionalität zu nutzen. Wie Sie die im-
portierten Elemente nutzen, ist dann vollkommen Ihre Sache. Funktionszeiger werden
meist Konstanten zugewiesen, aber das ist keineswegs zwangsläufig. Typischerweise ex-
portieren Module Funktionen. Allerdings können Sie auch Variablen etc. bis hin zu Ob-
jekten und Klassen1 exportieren. Im Grunde alles, was Sie in JavaScript deklarieren kön-
nen. Das wäre als Beispiel das Modul [Link]:
"use strict";
const { pot, wert2 } = require('./commonJSmodule/[Link]');
var { wert1 } = require('./commonJSmodule/werte');
// Funktionen aus dem Modul werden verwendet
[Link](pot(2, 3)); // Ausgabe: 8
[Link](wert2); // Ausgabe: 42
[Link](wert1); // Ausgabe: 21
wert1 *= 2;
[Link](wert1); // Ausgabe: 42
Beachten Sie, dass aus didaktischen Gründen bei der einen Importanweisung die Datei-
endung angegeben wurde. Obwohl es nicht notwendig und sogar unüblich ist, ist das
nicht verboten.
8.3 ES6-Module
Wichtig
Die Dateierweiterung von ES6-Modulen ist [Link] [Link]. Die Wahl der
Dateierweiterungen hängt von der Art des Moduls ab und wie es im Browser oder
in [Link] verwendet wird.
nutzen, sofern diese vorhanden ist. Dies ist besonders nützlich, wenn um
eine Datei als ES6-Modul zu kennzeichnen, aber keine Konfigurations-
dateien geändert werden soll oder kann. So etwas kann allerdings sinnvoll
oder auch notwendig sein. Etwa in der Datei [Link] eines Projekts
das Feld type auf "module" setzen. Dadurch wird [Link] angewiesen,
[Link]-Dateien standardmäßig als ES6-Module zu behandeln.
Vor jeder zu exportierenden Funktion steht einfach eine export-Anweisung. In dem im-
portierenden Modul steht dann die import-Anweisung:
"use strict";
import { add, multi } from './es6module/[Link]';
import { div } from './es6module/[Link]';
// Funktionen aus dem Modul werden verwendet
[Link](add(2, 3)); // Ausgabe: 5
[Link](multi(2, 3)); // Ausgabe: 6
[Link](div(3, 2)); // Ausgabe: 1.5
Auch bei ES6-Modulen kann man mehrere Exportanweisungen in einer einzelnen An-
weisung zusammenfassen (Objekt-Destrukturierung) bezeichnet. Das geht analog wie
bei den CommonJS-Modulen:
8.4 Module in Browsern 157
Beispiel
function eins() { …}
function zwei() { …}
export { eine, zwei}; // Mehrere Exportanweisungen in einer einzigen Anweisung zu-
sammen ◄
Moderne Browser bieten Unterstützung für die Arbeit mit Modulen – insbesondere
ES6-Module, aber auch eingeschränkt CommonJS-Module, obwohl diese ursprüng-
lich nur für die Verwendung mit [Link] entwickelt wurden. Dies wurde hauptsächlich
durch die Einführung von Tools wie Browserify, webpack und RequireJS ermöglicht,
die CommonJS-Module für den Browser kompatibel machen. Bei Bedarf informie-
ren Sie sich über die Verwendung eines Modul-Bundlers oder die Transpilierung mit
Babel. Wir werden uns auf ES6-Module beschränken, da zudem die Verwendung von
CommonJS-Modulen im Browser einige Einschränkungen haben kann, insbesondere
wenn es um die asynchrone Natur von [Link]-Modulen geht. In vielen Fällen sind
ES6-Module eine bessere Option, da sie nativ von vielen modernen Browser unterstützt
werden und modernere Features bieten. Damit ein Browser das Modulsystem samt der
import-Anweisungen korrekt versteht, verwendet man das type = "module"-Attribut im
<script>-Tag.
Ältere Browser benötigen u. U. die Verwendung eines Build-Tools, was hier wie
gesagt nicht verfolgen.
Auf der Seite des Webservers muss sichergestellt werden, dass die Dateien korrekt
bereitgestellt werden. Das betrifft vor allen Dingen die Pfadangaben, die wir gleich in
dem Beispiel noch einmal thematisieren. Zudem gilt beim Verwenden von Modulen im
Browser das CORS (Cross-Origin Resource Sharing)-Konzept. Das bedeutet, dass Mo-
dule aus verschiedenen Domains oder Dateisystempfaden möglicherweise nicht geladen
werden können, es sei denn, die Servereinstellungen erlauben dies explizit.
Nachfolgend sehen Sie ein Beispiel mit nativer Unterstützung im Browser (moduleIm-
[Link]):
…
<body><script type="module" src="[Link]"></script>
<script type="module" src="es6module/[Link]"></script></body></html>
Sie finden hier gleich zwei Module, die in der Webseite eingebunden werden. Diese ver-
wenden beide ein weiteres Modul. Beachten Sie, dass sich die Datei [Link] im gleichen
Verzeichnis und die Datei [Link] im Unterverzeichnis es6module in Bezug auf die
Webseite befinden. Das ist dann interessant, wenn man von da aus eine weitere Modul-
datei referenziert. Hier ist die Datei [Link] im gleichen Verzeichnis, die ein Modul im
Unterverzeichnis es6module importiert. Beachten Sie, dass hier./ (der Punkt ist wichtig)
die Basis des Pfades ist.
// [Link]
import { drei, vier } from './es6module/[Link]';
drei(); // Ausgabe: foo
vier(); // Ausgabe: bar
// [Link]
import { eins, zwei } from './[Link]';
eins(); // Ausgabe: foo
zwei(); // Ausgabe: bar
Da sich diese Datei im gleichen Verzeichnis wie die Datei [Link] befindet, entfällt
die Verzeichnisangabe. Das exportierende Modul enthält nur die Deklaration von Funk-
tionen, denen export vorangestellt wird.
// [Link]
export function eins() {
[Link](1);
}
8.4 Module in Browsern 159
Übersicht
Der wesentliche (wenn nicht gar ausschließliche) Nutzen von JavaScript besteht
darin, dass Sie damit browser- und computerbezogene Objekte abfragen und mani-
pulieren können. Das wurde bereits in sämtlichen Quellcodes bisher auch gemacht.
Selbst für die primitive Ausgabe von Informationen muss man ein passendes Ob-
jekt verwenden. Auch sonst haben wir immer wieder am Rande mit Objekten ge-
arbeitet, wenngleich JavaScript auch sehr viel mit Funktionen arbeitet. In dem Ka-
pitel gehen wir jetzt an die Grundlagen. Sie lernen hier etwas über
Ein Paradigma bezeichnet ein grundlegendes Konzept, eine Denkweise oder Me-
thode, die ein bestimmtes Fachgebiet prägt. Im Kontext der Informatik und insbesondere
der Softwareentwicklung bezieht sich „Paradigma“ auf grundlegende Ansätze oder
© Der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH, ein 161
Teil von Springer Nature 2025
R. Steyer, Programmieren mit JavaScript, [Link]
162 9 Objekte und JavaScript
JavaScript ist zumindest eine objektbasierende1 Sprache, die grundsätzlich mit Objekten
umgehen kann. Entweder werden die Objekte vom System bereitgestellt oder man kann
diese aus JavaScript erzeugen und vielfältig verwenden. Die selbst erzeugten oder vom
System bereitgestellten Objekte werden durch Eigenschaften/Attribute und Methoden
(verallgemeinert Member) sowie einen spezifischen Zustand charakterisiert. Dieser Zu-
stand kann dafür verantwortlich sein, ob bestimmte Methoden eines spezifischen Objekts
temporär verfügbar sind oder nicht. Wie Member in JavaScript verwendet werden, haben
wir schon behandelt – über die Punktnotation. Diese entspricht dem Senden einer Bot-
schaft oder Nachricht an das Objekt.
Der Zustand ist kein Kriterium, das in einer Klasse eine Rolle spielt, sondern rein auf das konkrete
Objekt bezogen. Allerdings kann man in einer Klassen Anfangszustände vorbelegen.
Zentrale Bedeutung in der vollständigen OOP hat bei der Bildung von Klassen eine
hierarchische Struktur der Gruppierungen. Gemeinsame Dinge sollen in der objekt-
orientierten Philosophie möglichst in einer übergeordneten Klasse – einer sogenannten
Superklasse – zusammengefasst werden (Verallgemeinerung). Erst wenn Unter-
scheidungen möglich beziehungsweise notwendig sind, welche nicht für alle Mitglieder
einer Klasse gelten, werden Untergruppierungen – untergeordnete Klassen – gebildet
(Spezialisierung). Jede (gewöhnliche) Klasse kann eine Vielzahl von Unterklassen (Sub-
klassen) und konkrete Instanzen (Objekte) haben. Die voneinander abgeleiteten Klassen
bilden einen Klassenbaum. Genau genommen wird der Klassenbaum meist in der Dar-
stellung gestürzt bzw. auf den Kopf gestellt. „Oben“ im Baum ist dann die Wurzel (engl.
root).
Die Beziehung einer übergeordneten Klasse zu ihren Subklassen ist streng hierar-
chisch und heißt Vererbung. Jede Subklasse bekommt alle Member der Superklasse ver-
erbt, die dafür vorgesehen sind. Jede Subklasse sollte sinnvollerweise mindestens einen
Member mehr haben, welche die Superklasse nicht hat, oder vererbte Dinge irgend-
wie verändern. Abgeleitete Klassen übernehmen die freigegebenen Member aller über-
geordneten Klassen. Die abgeleitete Klasse verwendet die Member der Superklasse. Die
Auswahl der Member in der Klassenhierarchie ist so geregelt, dass de gewünschte Mem-
ber in der nächsthöheren Superklasse gesucht wird, wenn ein Member in einer konkreten
Objektklasse nicht vorhanden ist. Ist ein Member dort ebenfalls nicht vorhanden, erfolgt
die Suche in der nächsthöheren Klasse im Klassenbaum, bis die oberste Superklasse des
Klassenbaums erreicht ist. Der Zugriff erfolgt also in der ersten Klasse, in der ein Treffer
erfolgt.
Der Begriff einer Klasse ist in JavaScript etwas gefährlich, denn historisch arbeitet JavaScript
nicht mit klassischen Klassen, sondern sogenannten Prototypen. Dabei ist die Anwendung eines
Prototyps zur Objekterzeugung in der JavaScript-Programmierung nicht von der Instanzierung
einer Klasse relevant zu unterscheiden. Um ein Beispiel aus dem realen Leben anzuführen. Eine
Klasse ist wie ein Bauplan, den ein Architekt erstellt hat und nach dem sich beim Bau eines Hau-
ses gerichtet wird. Ein Prototyp ist ein bereits existierendes Haus, von dem eine Kopie erstellt
wird. An dem neuen Haus können Sie nicht erkennen, ob es nach einem Bauplan oder einem kon-
kret schon vorhandenen Muster erstellt wurde. Aber selbst bei der Erstellung kann man in Java-
Script nicht erkennen, ob nach einem Bauplan oder einem konkret schon vorhandenen Muster vor-
gegangen wird. Das sehen wir noch bei der konkreten Syntax.
Das (!) Argument für die OOP und der Grund dafür, dass sie sich vielfach durchgesetzt
hat, ist Wiederverwendbarkeit. In der althergebrachten Programmierung2 können die
Datenebene und die Anweisungsebene aufgetrennt werden. Das Problem dieser Art der
Programmierung ist, dass bei einer solchen Aufteilung Änderungen in der Datenebene
Auswirkungen auf die unterschiedlichsten Programmsegmente haben können und damit
die Wiederverwendbarkeit von Unterprogrammen sehr eingeschränkt ist, da oft Ab-
hängigkeiten zu anderen Bestandteilen eines Programms existieren (beispielsweise zu
2 Die historisch erste Generation der Programmierung mit Maschinensprache und die zweite Ge-
neration mit Assembler sollen hier nicht beachtet werden, sondern nur die höheren Programmier-
sprachen.
164 9 Objekte und JavaScript
Die objektorientierte Programmierung verfolgt noch ein Ziel. Bei der Entwicklung kom-
plexer Software-Systeme ist die wichtigste Aufgabe, eine möglichst hohe Softwarequali-
tät zu erreichen.
• Objekte dienen als Abstraktion (Vereinfachung) eines realen Gegenstands oder Kon-
zepts mit einem spezifischen Zustand und einem spezifischen Verhalten. Sie nennt
man Instanzen einer Klasse.
• Alle Bestandteile, die direkt zu einem Objekt gehören, nennt man Instanzelemente.
Der Name resultiert daraus, dass „Instanz“ oft als Synonym für „Objekt“ genommen
wird. Man bezeichnet diese Bestandteile oft auch als,,nicht-statisch'' oder,,dyna-
misch'', da sie für jede Instanz dynamisch erstellt werden.
• Klassen dienen als Baupläne für Objekte. In der umgekehrten Sichtweise „klassi-
fizieren“ sie alle Gemeinsamkeiten, die verwandte Objekte auszeichnen. Man nennt
sie auch die Objekttypen oder kurz Typen. Bei JavaScript arbeitet man aber auch mit
Prototypen, was schon angedeutet wurde und worauf wir noch genauer eingehen.
• Objekte werden immer aus Klassen erzeugt. Aber viele Programmiersprachen bre-
chen diese Vorgabe und erlauben die Erzeugung von Objekten ohne Klassen. Arrays
sind etwa in Java oder C# explizit klassenlose Objekte.
• In einer streng objektorientierten Welt gibt es nur Objekte. Damit müssen auch Klas-
sen selbst Objekte sein. Nur wenn Objekte aus Klassen erzeugt werden – woraus wer-
den Klassen erzeugt? Die Antwort sind Metaklassen, für die es in vielen Sprachen
zwar nicht explizit eine Syntax zur Notation als Quellcode gibt, die aber das Konzept
konsistent halten.
• Ebenso darf es in einer streng objektorientierten Welt keine primitiven Datentypen
geben, was auch eine Berechnung von 1 + 1 oder 1 * 1 unmöglich macht. Es darf nur
Objekte geben, die Botschaften austauschen. Diese Vorgabe ist absolut praxisfern und
hat zum Scheitern von OO-Sprachen geführt, die dies erzwungen haben. Jede aktuelle
9.1 Grundlagen der OOP 165
OO-Sprache gestattet den Umgang mit primitiven Datentypen, aber tatsächlich han-
delt es dabei dann im Hintergrund meist dennoch um Objekte.
• Globale Variablen und Funktionen existieren außerhalb von Objekten. Diese darf es
in einer streng objektorientierten Welt nicht geben. JavaScript umgeht das Problem
durch die Definition eines globalen Objekts.
• Alle Bestandteile, die nicht direkt zu einem spezifischen Objekt, sondern einer Klasse
gehören, nennt man Klassenelemente oder auch statische Elemente, wobei man hier
in JavaScript eben wegen der Prototypen aufpassen muss. In manchen Sprachen gibt
es auch Unterschiede zwischen Klassenelementen und statischen Elementen.
• Attribute sind eine Beschreibung objekt- und klassenbezogener Daten. Oft wird auch
der Bezeichner „Eigenschaft“ für „Attribut“ verwendet, wobei das einige Sprachen
differenzieren.
• Methoden sind die Beschreibung objekt- und klassenbezogener Funktionalität.
• Assoziationen und Aggregationen sind Bezeichnungen von Mechanismen zum Aus-
druck von Objektbeziehungen.
• Vererbung ist eine besondere Form der Assoziation und ein Mechanismus zum Gene-
ralisieren und Spezialisieren von Objekttypen.
• Der Begriff Polymorphie ist nicht eindeutig und wird in verschiedenen Quellen
unterschiedlich streng ausgelegt. Generell bedeutet er Vielgestaltigkeit. Polymorphie
dient in einigen Sprachen zur flexiblen Auswahl geeigneter Methoden identischen Na-
mens anhand des Objekttyps und der Argumentenliste. Das geht in JavaScript ja ex-
plizit nicht, denn die Parameter spielen keine Rolle bei der Auswahl, wie wir gesehen
haben. Aber der Begriff kann auch weiter gefasst werden und dann weitere Mechanis-
men zum vielgestaltigen Verhalten umfassen. Damit versteht man das Konzept auch
wieder so wie im philosophischen Ansatz.
• Schnittstellen implementieren in einigen Sprachen einen Mechanismus zur Struktu-
rierung von Klassenbeziehungen, den es in JavaScript nicht gibt.
• Abstrakte Klassen implementieren in einigen Sprachen einen Mechanismus zum
Erzwingen bestimmter Operationen in spezialisierten Klassen, den es in JavaScript
ebenfalls nicht gibt.
• Generischen Klassen dienen zur Darstellung von Klassenfamilien. Auch das Kon-
zept gibt es in JavaScript nicht und wäre wegen der losen Typisierung auch wenig
hilfreich.
Auch Sprachen, die explizit als (streng) objektorientiert eingestuft werden, implementieren nicht
unbedingt alle diese Charakteristika oder brechen Sie ganz gezielt (primitive Datentypen, klassen-
lose Objekte, …).
In vielen Schulungen zu OOP frage ich Teilnehmer, seit wann es das Konzept der
Objektorientierung gibt. Nach einer dramaturgischen Pause schiebe ich die Bemerkung
166 9 Objekte und JavaScript
nach, dass die Antwort nicht so genau sein muss und es mir auf ein paar Hundert Jahre
nicht ankommt. Der Effekt ist immer gleich. Aber tatsächlich muss sich man beim Kon-
zept der Objektorientierung von der IT lösen und hin zur Philosophie über die reale
Welt gehen. Das wurde mir erstmals klar, als ich auf dem Weg zu einer Java-Schulung
im Auto ein Hörbuch zu griechischer Philosophie gehört habe. Plötzlich ging es da um
Abstraktion, Klassen, Instanzen und Objekte, Metaklassen, Eigenschaften, Methoden,
Polymorphie und weitere Fachbegriffe, die ich intuitiv der OOP zugeordnet habe. Aber
„natürlich“ ist das Konzept der Objektorientierung nicht auf IT und Programmierung be-
schränkt. Das Gegenteil ist der Fall – die Programmierung mit objektorientiertem Ansatz
wird wie der Umgang mit Objekten in der realen, „natürlichen“ Welt verstanden. Auch
wenn man lange Zeit anders programmiert hat.
Aber auch die OOP selbst ist kein wirklich neuer Ansatz. Im Gegenteil – die Idee der
OOP reicht sogar bereits bis Anfang der 70iger Jahre und teilweise noch weiter zurück.
Sehr frühe Vertreter von Programmiersprachen mit objektorientiertem Denkansatz sind
Lisp oder Logo. In den 70iger Jahren entstand als wichtigster Vertreter erster objekt-
orientierter Sprachen Smalltalk, das sich in mehreren Zyklen entwickelte und als einer
der geistigen Väter von Java gilt. Aber erst mit Java in der Mitte der 90iger-Jahree
hat sich die OOP richtig durchgesetzt3. Seit der Zeit entstanden eine Reihe moderner,
eigenständiger Programmiersprachen, die sich ausdrücklich als rein objektorientierte
Programmiersprachen verstehen und mit prozeduralen Erblasten vollkommen bre-
chen. Java zählt wie gesagt explizit dazu. Oder von Microsoft die Sprache C#. Diese
Programmiersprachen erzwingen einen streng-objektorientierten Ansatz und unterstützen
im Allgemeinen die wichtigsten Konzepte der objektorientierten Programmierung – al-
lerdings in unterschiedlichem Maße. Andere Sprachen, die in dieser Zeit oder auch spä-
ter entstanden, verfolgen einen hybriden Ansatz und erzwingen keine strenge objekt-
orientierte Programmierung – z. B. JavaScript, Python PHP oder F#.
JavaScript war von Anfang an so konzipiert, dass ein hybrides Konzept aus funktionalen
und objektbasiertem Programmierparadigma die Basis gebildet hat. Dabei k onzentriert
3 C/C + + warzwar zu der Zeit auch schon sehr populär, aber eine hybride Sprache, in der man
sowohl objektorientiert als auch funktional programmieren konnte. Und oft wurde in C/C + + -Pro-
jekten nicht wirklich objektorientiert programmiert – was ich auch eigener Erfahrung weiß.
9.1 Grundlagen der OOP 167
man sich beim funktionalen Ansatz auf die Verwendung von Funktionen als grund-
legende Bausteine zur Problemlösung. Die funktionale Programmierung betont die Aus-
wertung von Ausdrücken und Funktionen. Dabei sind Funktionen als First-Class Citizen
implementiert. Damit werden Funktionen genauso behandelt wie Daten. Sie können als
Argumente an andere Funktionen übergeben, von Funktionen zurückgegeben und in Va-
riablen gespeichert werden. Auf der anderen Seite kann man – wie in JavaScript – glo-
bale Funktionen ebenso als Methoden eines globalen Objekts sehen und verbindet damit
widerspruchsfrei das funktionale und objektorientierte Paradigma. Rein funktionale
Sprachen machen das nicht, aber hybride Sprachen wie JavaScript oder auch Python. Ein
Kernkonzept bei funktionaler Programmierung ist auch die Unveränderlichkeit (Immuta-
bility) von Daten. Das bedeutet, dass sie nach ihrer Erstellung nicht mehr geändert wer-
den können. Statt Daten zu ändern, werden neue Datenstrukturen erstellt. Auch Rekur-
sion ist ein häufig verwendetes Konzept in der funktionalen Programmierung und es gibt
Funktionen höherer Ordnung. Damit können Funktionen andere Funktionen als Argu-
mente akzeptieren und/oder Funktionen zurückgeben.
Die Umsetzung von Objekten in JavaScript ist genial, effizient und einfach, da sowohl
funktionale als auch objektorientierte Techniken kombiniert werden. Wir hatten ja schon
bei den Arrays festgehalten, dass jedes Array ein Objekt, aber auch jedes Objekt im
Grunde ein (assoziiertes) Array ist – eine Hash-Liste, in der die Keys die Eigenschaften
darstellen. Und da Funktionen in der funktionalen Programmierung als Werte verstanden
werden, die Variablen und damit auch Eigenschaften zugewiesen werden können, ist
damit im Grunde schon alles bekannt, wie ein Objekt in JavaScript aussieht. Etwa so,
wenn man eine JSON-Notation verwendet ([Link]):
"use strict";
let kunde = { name: "Max Mustermann", alter: 42,
reden: function() { [Link]("Guten Tag.") }
}
[Link]([Link], kunde["alter"]);
[Link]();
Es gibt zwei Eigenschaften (name und alter) sowie eine Methode reden. Diese wird als
innere, anonyme Funktion deklariert und der Rückgabewert als Funktionszeiger dem
Bezeichner reden zugewiesen. Damit wird das zu einer Methode des Objekts, ist aber
gleichzeitig eine Eigenschaft. Der Aufruf eines Members kann sowohl über die Punkt-
notation als auch die Array-Notation erfolgen, was in dem Beispiel aus didaktischen
Gründen beides gemacht wird. Sie können das Objekt aber auch als Array deklarieren
und haben das gleiche Resultat, wobei wir hier das Array nach der Erzeugung erweitern
([Link]):
"use strict";
let kunde = [];
kunde["name"] = "Max Mustermann";
[Link]= 42;
kunde["reden"] = function() { [Link]("Guten Tag.") };
[Link]([Link], kunde["alter"]);
[Link]();
9.2 Wie Objekte in JavaScript realisiert werden 169
Diese maximale Flexibilität sowohl beim Erstellen von Objekten als auch dem Zugriff
auf die Member kann an vielen Stellen interessant sein. Allerdings besteht auch das Ri-
siko, dass der Quellcode beim schlechter les- und wartbar wird.
Tipp Es gibt ein paar Techniken, die Ihnen im Zusammenhang mit Objekten
nützlich sein können.
1. Test auf Existenz eines Objekts oder einer Eigenschaft ist in JavaScript
ganz einfach. Über eine Anweisung, die eine Bedingung prüft – meist die if-
Anweisung – können Sie testen, ob ein Objekt eine bestimmte Eigenschaft
besitzt oder ob das Objekt vorhanden ist. Existiert die Eigenschaft bzw. das
Objekt (und nur dann), erhalten Sie als Ergebnis true.
2. Die with-Anweisung erlaubt einen verkürzten Zugriff auf mehrere Mem-
ber eines Objekts. Dazu wird nach dem Schlüsselwort with das Objekt in
Klammern angefügt. Dadurch muss im folgenden Anweisungsblock nur
der Name der Eigenschaft ohne den Variablennamen angegeben werden.
Dabei wird für jeden Bezeichner (Variable) zuerst nach einer Eigenschaft in
dem Objekt gesucht, dann nach einem lokalen und globalen Bezeichner.
Die with-Anweisung gilt allerdings als schlecht für die Performance. Vorteil
ist die Reduzierung des Schreibaufwands und der Codegröße. Sie müssen
bei einer Verwendung die Vor- und Nachteile abwägen.
3. Sie können über Objekte wie jedes Array iterieren. Mit normalen Schleifen,
aber vor allen Dingen die for-in-Anweisung. Auch for-of kann manchmal
nützlich sein. Dabei ist es wesentlich, dass Sie dabei die Array-Notation ver-
wenden können. Beachten Sie, dass Standardobjekte von JavaScript aller-
dings in der Regel ihre innere Struktur verbergen. Auf der anderen Seite ist
zu beachten, dass for-in etwa nicht nur über die direkt definierten Eigen-
schaften iteriert, sondern auch über die vererbten Eigenschaften des Proto-
typs. Daher ist es oft ratsam, zusätzliche Bedingungen zu verwenden, um
sicherzustellen, dass Sie nur die gewünschten Eigenschaften erhalten.
4. Mit dem instanceof-Operator können Sie prüfen, ob ein Objekt vom einem
bestimmten Typ ist. In diesem Fall – und nur dann – wird als Ergebniswert
true geliefert.
Wie Sie gesehen haben, ist jedes Array ein Objekt und jedes Objekt ein Array. Zu-
mindest kann man auf alle Eigenschaften eines Objekts auch über die Array-Notation
zugreifen und auf alle Elemente in einem assoziierten Array über die Punktnotation.
Dennoch gibt es Unterschiede zwischen „normalen“ Arrays und JSON-Objekten. Auf
170 9 Objekte und JavaScript
der einen Seite sind numerisch indizierte Arrays zu sehen, die keine „gültigen“ Keys für
Member bei der Punktnotation bereitstellen. Der andere Punkt ist die „Herkunft“ und aus
dieser ergeben sich Unterschiede hinsichtlich der verfügbaren Member der resultieren-
den Objekte. Schauen wir das an und nutzen dazu den Operator instanceof ([Link]):
"use strict";
let kunde1 = [];
let kunde2 = new Array();
let kunde3 = {};
[Link](kunde1 instanceof Array);
[Link](kunde2 instanceof Array);
[Link](kunde3 instanceof Array);
Wie Sie an der Ausgabe erkennen können, ist ein JSON-Objekt keine Instanz von Array.
Damit fehlen sämtliche spezifischen Member eines Arrays. Sowohl JSON-Objekte als
auch assoziierte Arrays können daher zwar immer die gleichen Datenstrukturen ab-
bilden, aber nur echte Arrays haben darüber hinausgehende Funktionalitäten. Das wer-
den wir im nächsten Kapitel besprechen.
Wir haben bisher schon zahlreiche Objekte erzeugt. Etwa Arrays über new Array() oder
auch ein Datumsobjekt über new Date() oder als literal über die JSON-Notation als auch
ein Array-Literal (Objektinitialisierer). Bei der Anwendung von einem Konstruktor kön-
nen Sie nicht erkennen, ob dieser eine Klasse oder ein Prototyp verwendet. Zumindest
in neuen Versionen, wo überhaupt erst Klassen syntaktisch hingeschrieben werden kön-
nen. In älteren Versionen von JavaScript hat sich die Frage deshalb gar nicht gestellt. In
JavaScript gibt es die zwei Hauptmechanismen zur Erstellung von Objekten und ihrer
Strukturierung, die im Resultat gleiche Ergebnisse liefern. Aber von der Schreibweise
und dem Konzept selbst gibt es ein paar Unterschiede:
• In JavaScript basiert die OOP von Natur aus auf Prototypen. Jedes Objekt in Java-
Script hat einen Prototyp, von dem es Member erbt. Entwickler können den Prototyp
eines Objekts direkt manipulieren, um die Struktur und das Verhalten des Objekts zu
ändern. Der oberste Prototyp (die Wurzel) ist Object.
• „Echte“ Klassen stehen erst ab ECMAScript 6 (ES6) in JavaScript zur Verfügung. Sie
bieten aber im Grunde nur eine syntaktische Abstraktion oberhalb des Prototypen-
basierten Ansatzes und erleichtern hauptsächlich die Erstellung von Objekten und ins-
besondere die Vererbung auch ohne tiefere Kenntnis von JavaScript. Mit der Klassen-
syntax können Entwickler OOP in einem Stil betreiben, der ähnlich zu Sprachen wie
Java, C# oder C + + ist. Oft hört man, dass Klassen damit eine klarere und syntaktisch
ansprechendere Möglichkeit bieten, Objekte zu definieren.
9.2 Wie Objekte in JavaScript realisiert werden 171
Zuerst klären wir die Frage, was eigentlich in JavaScript eine Methode von einer nor-
malen Funktion unterscheidet? Diese Frage dürfte vor allen Dingen Leser mit Hinter-
grundwissen in der objektorientierten Programmierung ziemlich auf den Nägeln bren-
nen, obwohl die Lösung bereits im Buch mehrfach angedeutet wurde. Die Antwort ist:
NICHTS! Jede Methode ist bloß eine (besondere) Funktion und in JavaScript kann jede
gewöhnliche Funktion unmittelbar als Methode verwendet werden. Sie muss bloß über
eine Funktionsreferenz an ein Objekt oder einen Objekttyp gebunden werden. Entweder
als benannte oder als anonyme Funktion, wie wir es oben gemacht haben.
In der objektorientierten Philosophie ist alles ein Objekt. Außerhalb von Objekten kann damit
nichts existieren, was globale Variablen als auch Funktionen im Grunde unmöglich macht. Obwohl
JavaScript diese Inkonsistenz wie erwähnt durch die Definition des globalen Objekts samt First-
Class Citizens konzeptionell auflöst – eine benannte Funktion, die als Methode verwendet wird,
steht außerhalb einer Objektdeklaration und kann immer auch ohne ein vorangestelltes Objekt als
gewöhnliche Funktion aufgerufen werden kann. Bei der Verwendung einer anonymen Funktion
(Closure) als Methode vermeiden Sie dieses Problem.
Das Schlüsselwort this ist eine Referenz auf das aktuelle Objekt.
Das Schlüsselwort this erlaubt einen Aufbau von Funktionen, die allen Ansprüchen der
OOP genügen und zudem nur (sinnvoll) über ein Objekt verwendet werden können.
Das globale Objekt in JavaScript ist window im Browserumfeld und global in Node.
js-Umgebungen & Co.
172 9 Objekte und JavaScript
Wir schauen einige Beispiele zur Verdeutlichung an. Angenommen, wir wollen in den
ersten beiden Beispielen ([Link] und [Link]) den Namen und das Alter des Kunden
innerhalb der Methoden reden ausgeben. Also etwa so ([Link]):
Beispiel
Das wird nicht gehen. Denn sowohl name als auch alter sind Instanzelemente des Ob-
jekts kunde. Wenn wir die beiden Bezeichner so in einer inneren Funktion verwenden,
werden diese als globale oder lokale Variablen gesucht. Und die gibt es beide nicht. Wür-
den Sie diese jedoch als globale oder auch lokale Variablen in der Methode deklarieren,
„verdecken“ diese die Instanzelemente mit gleichem Namen.
In JavaScript ist nicht wirklich ein Verdecken, wie es etwa in Java oder C# der Fall ist. Ohne
vorangestelltes Objekt sind die Instanzelemente einfach syntaktisch nicht zugänglich.
Betrachten wir zuerst diese Variation der Beispiele weiter oben ([Link]):
"use strict";
var name, alter;
let kunde = { name: "Max Mustermann", alter: 42,
reden: function() { [Link](name, alter); }
}
[Link]();
Die Ausgabe wir zweimal undefined sein. Wir geben in der Methode die Werte der glo-
balen Variablen aus – nicht die Werte der Eigenschaften. Doch damit kommen Sie aber
an die Werte von name und alter ([Link]).
"use strict";
let kunde = { name: "Max Mustermann", alter: 42,
reden: function() { [Link]([Link], [Link]); }
}
[Link]();
Sie stellen den Namen des Objekts auch innerhalb der Methode voran. Nur der Nach-
teil dieser Vorgehensweise ist gravierend. Sie brauchen den Namen des aktuellen Ob-
jekts und den haben Sie nicht immer. Er kann sich sogar erst zur Laufzeit ergeben oder
sich ändern. Genau deshalb verwendet man this – eine Referenz auf das aktuelle Objekt.
Ein Stellvertreterbegriff, der vollkommen unabhängig von dem Namen des Objekts ist
([Link]).
9.2 Wie Objekte in JavaScript realisiert werden 173
"use strict";
let kunde = { name: "Max Mustermann", alter: 42,
reden: function() { [Link]([Link], [Link]); }
}
[Link]();
Betrachten wir nun den Fall, dass die eigentliche Deklaration der Methode reden außer-
halb von der Objektdeklaration notiert wird. Das kombinieren wir mit einem Zugriff auf
this und global. Sowohl innerhalb des Objekts als auch außerhalb. Denn außerhalb einer
konkreten Objektdeklaration haben wir ja das globale Objekt. Was meinen Sie, was this
und global in dem Fall darstellen ([Link]):?
meinGlobaleVa = 42;
let kunde = { name: "Max Mustermann", alter: 42, reden: bla }
function bla() {
for(let i in this){
[Link](i,this[i]);
}
for (var prop in global) {
[Link](prop + ": " + global[prop]);
}
}
[Link]();
bla();
Wir iterieren in der extern deklarierten Methode sowohl über this als auch global. Wenn
die Funktion als Methode aufgerufen wird, werden über this die drei Eigenschaften (!)
name, alter und reden aufgegeben. Ich habe bei reden explizit von einer Eigenschaft ge-
sprochen, denn das ist ja aus Sicht der Hash-Liste einfach ein Wert, der einem Key zu-
geordnet ist. Die Ausgabe zeigt deutlich, dass es eine Funktionsreferenz ist. Wenn Sie
die Funktion ohne das Objekt aufrufen, hat this keine Eigenschaften. Die Iteration über
global hingegen liefert beim Aufruf über das Objekt als auch als „normale“ Funktion
das gleiche Ergebnis. Beachten Sie, dass der Name für die Eigenschaft reden nicht (!)
identisch mit dem Bezeichner der referenzierten Funktion ist. Dies wurde hier in dem
Beispiel bewusst so gewählt. In der Praxis wird man die Bezeichner jedoch oft identisch
wählen, damit die Beziehung leichter nachzuvollziehen ist. Aber das ist – wie Sie hier
sehen – nicht zwangsläufig notwendig. Wir verwenden in diesem Beispiel bewusst kei-
nen strikten Modus. Die Variable meinGlobalVa landet damit im globalen Objekt und
wird bei der Iteration über global auch ausgegeben. Verwenden Sie "use strict", werden
Sie zur Deklaration mit var oder let gezwungen und die Variablen steht nicht über global
zur Verfügung.
174 9 Objekte und JavaScript
Der Rest der Ausgabe sind die Eigenschaften des globalen Objekts. Damit kann man
this also verwenden, um eine Funktion zu deklarieren, die nur als Methode Sinn macht.
Diese Beschränkung erhöht damit nebenher auch die Softwarequalität.
Nähern wir uns dem OOP-Konzept von JavaScript weiter über die nachträgliche Ma-
nipulation von einem Objekt. Ein Objekt in JavaScript als eine dynamische Hash-Liste
lässt sich durch die interne tabellenartige Struktur und die lose Typisierung einfach da-
durch erweitern, in dem man den Bezeichner des Objekts notiert und über die Punkt-
oder Array-Notation die neu gewünschte Eigenschaft samt Wertzuweisung hinschreibt.
Es entsteht dann einfach ein neuer Eintrag in der Tabelle, die die Struktur von dem Ob-
jekt abbildet. Das Schlüssel-Werte-Paar wird in der Tabelle angehängt, wobei wir so
etwas auch schon mit dem Array-Beispiel von oben gemacht haben. Damit kann man
natürlich auch neue Methoden ergänzen, denn diese werden ja als Funktionsreferenz
beim Objekt verankert und die Funktionsreferenz wird einer neuen Objekteigenschaft
zugewiesen. Betrachten wir zuerst, wie ein einzelnes Objekt als Instanz von Object er-
weitert wird ([Link]):
"use strict";
var obj = new Object();
function getWS() {
return "[Link]
}
[Link] = "RJS EDV-KnowHow";
[Link] = getWS;
[Link] = function() { return "[Link] }
[Link]("Firma:", [Link]);
[Link]("Webseite:", [Link]());
[Link]("Blog:", [Link]());
var obj2 = new Object();
for(var i in obj2){
[Link](obj2[i]);
}
Zuerst erzeugen wir ein Objekt vom Typ Object und speichern die Referenz darauf in
der Variable obj. Dieses stellt damit nur die ganz elementaren Eigenschaften bereit,
9.3 Details zu Prototyping 175
die jedes Objekt in JavaScript hat. In der Folge wird die Eigenschaft firma dem Objekt
hinzugefügt. Danach sehen Sie eine neu hinzugefügte Funktionsreferenz auf die Funk-
tion getWS(). Der Name für die Eigenschaft wurde aus didaktischen Gründen auch
hier nicht (!) identisch mit dem Bezeichner der referenzierten Funktion gewählt. Eine
innere Deklaration verwenden wir für Methode getBlog(), die damit ebenfalls über das
erweiterte Objekt zur Verfügung steht. Danach werden die erweiterten Member des Ob-
jekts genauso benutzt, wie Sie Standardeigenschaften und Standardmethoden verwenden
würden. Die Erweiterung erfolgte in dem Beispiel aber explizit nur für ein Objekt. An-
dere Objekte vom Typ Object (und erst Recht von anderen Typen) im gleichen Skript
werden nicht die neuen Eigenschaften haben. Das testen wir einfach indem ein weiteres
Objekt vom Typ Object erzeugen und darüber iterieren. Es wird keine Ausgabe erzeugt.
Der Zugriff auf [Link] würde den Wert undefined liefern.
Für Leser mit Erfahrung in der OOP – die gerade beschriebene Vorgehensweise entspricht der Er-
weiterung der Instanzelemente.
Sie haben gerade gesehen, wie einfach Sie in JavaScript bestehende Objekte individuell
erweitern können. Aber geht das auch mit allen Objekten eines Typs in einem Skript?
Ja – über die Erweiterung der Objektdeklaration (also im weiteren Sinn der Klasse) mit-
tels Prototyping. Wenn Sie in JavaScript mit Prototypen arbeiten bedeutet das, dass Ko-
pien bestehender Objekte erzeugt werden. Diese können anschließend manipuliert wer-
den. Und diese Manipulation bedeutet wiederum, dass eine Klasse beziehungsweise alle
darauf aufbauenden Objekte mit neuen Eigenschaften und/oder Methoden erweitert oder
verändert werden. Eine bereits fertige Klasse – bzw. genauer die darauf basierenden Ob-
jekte – kann um eventuell fehlende Funktionalität erweitert werden. Besonders interes-
sant ist Prototyping, wenn die Erweiterung auf eine Objektdeklaration angewendet wird,
wenn bereits ein Objekt existiert. Für ein existierendes Objekt eines Typs bedeutet Proto-
typing, dass es erweitert werden kann, nachdem (!) es bereits erzeugt wurde. Der Grund
ist die schon angedeutete Struktur eines Objekts in JavaScript. Ein Objekt in JavaScript
verhält sich wie gesagt wie eine Hashtabelle, bei der der Name eines Eintrags immer
eine Zeichenkette ist und die einfach durch Hinzufügen eines weiteren Eintrags erweitert
werden kann. Und diese Struktur wirkt sich auf alle Objekte eines Typs aus – egal wann
sie erzeugt werden, denn auch ein Prototyp selbst hat diese Hashstruktur. In JavaScript
besitzt jede Objektdeklaration eine Eigenschaft prototype. Die allgemeine Syntax dazu
ist die folgende:
Beispiel
[Objekttyp].prototype.[Eigenschaft/Methode] = [Wert]; ◄
176 9 Objekte und JavaScript
Sobald diese Aktion durchgeführt wurde, besitzen alle zukünftig erzeugten, aber auch
alle vorher bereits erzeugten Objekte die neue Eigenschaft beziehungsweise Methode.
Und weil es immer wieder gerne falsch gemacht wird – wenden Sie Ihre Aufmerksam-
keit noch einmal auf den Adressaten der Botschaft – es ist beim Einsatz von prototype
der Objekttyp, nicht die Instanz, wie im vorherigen Abschnitt! Des Weiteren ist zu be-
achten, dass nach der prototype-Anweisung erzeugte Objekte für die zugefügte Eigen-
schaft immer den über prototype angegebenen Vorgabewert besitzen, solange dieser
nicht explizit mit einer Zuweisung verändert wurde.
Wir wollen in dem konkreten Beispiel zum Prototyping nun auf Basis einer Standard-
klasse von JavaScript einen Prototyp erweitern. Dabei wollen wir die Klasse Object
(die Wurzel der gesamten Objekthierarchie in JavaScript) um die Eigenschaft ort sowie
eine Methode getZufall erweitern, allerdings ein Objekt vom Typ Date erzeugen und
darüber die Eigenschaften verwenden. Das gesamte Verfahren zur Erweiterung geht so
([Link]):
"use strict";
[Link] = "Feldberg";
[Link]= function(dat) { return [Link](); };
var b1 = new Date();
[Link]([Link], [Link]())
Zuerst wird die Standardeigenschaft prototype der Klasse Object verwendet und darüber
die Eigenschaft und die Methode deklariert und gleich ein Wert zugewiesen. Nun wird
ein Date-Objekt erzeugt und in der Folge über dieses Objekt auf die erweiterten Eigen-
schaften zugegriffen. Dies funktioniert, da ja Object die „Superklasse“ von Date ist und
damit auch dort hinzugefügten Eigenschaften zur Verfügung stehen.
Wenn Sie Erfahrung mit OOP haben – wir scheinen durch die Erweiterung des Prototyps
Klassenelemente bzw. statische Elemente anzulegen. Aber es handelt sich nur um spe-
zielle JavaScript-Varianten der Klassenelemente bzw. statischen Elemente, die gewisse
Besonderheiten besitzen und damit nicht ganz dem üblichen Verständnis entsprechen.
Oder genauer – die konkrete Anwendung spielt wieder eine Rolle, ob es wirklich stati-
sche Elemente sind oder nicht. Schauen wir uns das folgende Skript [Link] an:
"use strict";
var obj1 = new Object();
[Link] = "RJS EDV-KnowHow";
var obj2 = new Object();
[Link]([Link], [Link]);
Zuerst wird ein Objekt vom Typ Object erzeugt, danach (!) die Klasse Object per Proto-
typing erweitert und danach erst ein zweites Objekt vom Typ Object erzeugt. Sie sehen
an der Ausgabe, dass sowohl der Zugriff über das erste als auch das zweite Objekt den
gleichen Wert der Eigenschaft firma liefert. Auch wenn das erste Objekt bereits vor der
Erweiterung der Klasse erzeugt wurde. Hätten wir nun echte statische Elemente, würde
Folgendes gelten: wenn wir den Wert der Eigenschaft firma über den Typ oder aber auch
eines der Objekte ändern, wäre der Wert auch unmittelbar in allen anderen Objekten die-
ses Typs geändert – das typische Verhalten eines Klassenelements, sofern die Änderung
über ein Objekt in der Syntax einer Sprache erlaubt ist (etwa in Java – C# gestattet das
nicht). Betrachten Sie jedoch folgende Erweiterung des Beispiels ([Link]):
"use strict";
var obj1 = new Object();
[Link] = "RJS EDV-KnowHow";
var obj2 = new Object();
[Link]="Weingut St Urban";
[Link]([Link], [Link]);
Das Beispiel ist analog dem Vorgänger aufgebaut. Nur wird über eines der Objekte
(obj1) der Wert der Eigenschaft firma geändert. Die nachfolgende Ausgabe beweist, dass
der Wert von firma in obj2 dadurch jedoch nicht geändert wurde. Wenn wir jedoch über
die Eigenschaft prototype der Klasse/Prototyp den Wert von firma ändern, ist er für alle
Objekte geändert, wie wir davor gesehen haben. Tatsächlich haben wir in JavaScript eine
Art Zwittersituation zwischen Klassen- und Instanzelementen.
Objekte in JavaScript sind – analog normaler Vererbung – mit den Objekten verknüpft, von denen
Sie abstammen. Diese Verkettung endet im Vererbungsbaum spätestens bei [Link]. Wenn
ein Member über ein Objekt aufgerufen und dort nicht gefunden wird, werden über diese interne
178 9 Objekte und JavaScript
Verbindung alle übergeordneten Objekte nach einer passenden Member durchsucht. Sollte die
Suche bis zu [Link] erfolglos verlaufen sein, wird das Ergebnis undefined geliefert.
Sie konnten früher in JavaScript wie gesagt Klassen im strengen Sinn der OOP nicht
hinschreiben und auch heute ist das nur eine Abstraktion des darunterliegenden Prototyp-
konzepts. Im Sinn von Prototypen können Sie jedoch eigene Objektdefinitionen schrei-
ben und daraus dann Objekte erstellen. Damit Sie in JavaScript nun ein Objekt nach
eigenen Vorstellungen anlegen können, sind effektiv zwei Schritte nötig.
Um aus einer Objektdeklaration explizit mit einem Konstruktor eine neue Objektinstanz
anzulegen, stellt man in einer Anweisung das reservierte JavaScript-Schlüsselwort new
voran, gefolgt von dem Bezeichner der Objektdeklaration und anschließend einem
Klammerpaar (eventuell mit Parametern darin). Dies sieht von der Syntax her schema-
tisch so aus:
Beispiel
Der Konstruktor hat in JavaScript immer den gleichen Bezeichner wie die Objekt-
deklaration. Die einzige Aufgabe eines Konstruktors ist die Erzeugung eines Ob-
jekts. Dabei werden bei Bedarf Initialisierungen vorgenommen, sonstige notwendige
Schritte ausgeführt und vor allen Dingen Speicherplatz für das Objekt reserviert. So-
fern ein Objekt seine Arbeit nicht dadurch vollständig erledigt hat, dass es erzeugt
wurde und unmittelbar dabei einige Arbeitsschritte durchgeführt hat, weist man einer
Variablen eine Referenz auf das Objekt zu. Über den Namen der Variablen können Sie
dann bei Bedarf auf das Objekt zugreifen. Die Zuweisung erfolgt in einem Schritt mit
der Erzeugung. Das reine Zuweisungsverfahren unterscheidet sich nicht von dem Vor-
gang, wie wir ihn bei primitiven Datentypen verwenden. Nur beinhaltet die Variable
eine Referenz auf das erzeugte Objekt (man nennt das in anderen Sprachen auch
einen Pointer – Zeiger).
9.3 Details zu Prototyping 179
Beispiel
In JavaScript ist bei jedem Objekt die Eigenschaft constructor standardmäßig vor-
handen. Sie verweist auf den Konstruktor, mit dem das Objekt erstellt wurde.
Jetzt ist das Verwirrende für Umsteiger aus vielen Programmiersprachen wie Java oder
C#, dass an eine Funktion überhaupt keine Anforderungen gestellt werden, damit Sie als
Konstruktor dienen kann! Genau wie bei Methoden. Die „Adelung“ einer gewöhnlichen
Funktion zum Konstruktor erfolgt ausschließlich über die Anwendung mittels des new-
Operators. Wird eine Funktion damit aufgerufen, agiert sie als Konstruktor. Oder anders
ausgedrückt. Jede (!) Funktion kann im Prinzip als Konstruktor verwendet werden. Also
geht sogar so etwas ([Link]):
"use strict";
function meinKonstruktor() {}
var o = new meinKonstruktor();
[Link](typeof o, [Link]);
In dem Code wird einfach eine leere Funktion definiert. Sie hat überhaupt keinen sinn-
vollen Inhalt und ist in keiner Weise besonders gekennzeichnet. Dennoch kann Sie zur
Erzeugung von einem Objekt verwendet werden, was wir new meinKonstruktor(); ma-
chen. Sie sehen an der Ausgabe object [Function: meinKonstruktor], dass die Eigen-
schaft constructor von dem erzeugten Objekt auf diese Funktion referenziert. Zwar wird
das so erzeugte Objekt über keine spezifischen Member verfügen, was die nachfolgende
Iteration über das Objekt zeigt. Aber wir haben ein voll funktionales Objekt erzeugt, was
typeof bestätigt.
vorangestellten new dazu, dass intern die Variable this auf das gerade neu erstellte Ob-
jekt gesetzt wird. Wenn Sie in JavaScript in der Form this.[variable] in der Konstruktor-
methode eine Variable deklarieren, ist das ein Zugriff auf eine Eigenschaft von dem
irgendwann daraus erzeugten Objekt. Nun schreibt man per Konvention den Namen des
Konstruktors groß. Allgemein beginnen Funktionen in JavaScript per Übereinkunft mit
einem Kleinbuchstaben. Es macht sehr viel Sinn, auch in JavaScript die Namenskon-
ventionen zu übernehmen. Wenn Sie die Regel einhalten kann jeder sofort aufgrund des
Bezeichners einer Funktion bereits erkennen, dass dies ein Konstruktor sein soll (kons-
[Link]).
"use strict";
function reden(was) { return [Link] + ": " + was;}
function Person(personalnummer, rolle) {
[Link] = personalnummer;
[Link] = rolle;
[Link] = reden;
}
var firma = new Array();// Objekterzeugung
firma["chef"] = new Person("001", "Boss");
firma["projektleiter"] = new Person("007", "Projektleiter");
firma["mitarbeiter"] = new Person("010", "Programmierer");
[Link](firma["chef"].reden("Was macht das Projekt?"));
[Link](firma["projektleiter"].reden("Da muss ich meine Mit-
arbeiter fragen."));
[Link](firma["mitarbeiter"].reden("Alles im grünen Bereich."));
[Link] Klassendefinitionen
Zwar kann man in gewisser Weise in JavaScript Klassen definieren, indem man
Konstruktormethoden wie eben geeignet aufbaut. Nur sind das für OO-Puristen keine
„echten“ Klassen im strengen Sinn. Ungeachtet der schon erwähnten Tatsache, dass
auch die neue Klassensyntax in JavaScript unter der Oberfläche weiter Prototyping ist,
kann man zumindest syntaktisch Klassen in der allgemein üblichen Notation erstellen,
bei dem das Schlüsselwort class dem Klassenbezeichner vorangestellt wird. Im Inneren
werden als wichtigsten Part der neuen Syntax die bisherige Notation eines Konstruktors
als innere Funktion notieren mit dem Bezeichner constructor erledigt, nur werden man-
che bisher mehr oder weniger freiwilligen Verhaltensweisen im Umgang mit solchen
Funktionen in ein festes Konzept für die Verwendung eingezwängt, was der Sache mehr
Sicherheit und Konsistenz gibt. Innere „Hilfsfunktionen“ werden ohne function in die
Klasse geschrieben. Nehmen wir einmal einen Konstruktor, den wir hier gerade definiert
haben und verlagern ihn in eine Klasse und ändern ein bisschen die Funktionalität (klas-
[Link]).
"use strict";
class Person {
ausgabe(){
[Link]("Zuteilen des Mikros");
}
constructor(personalnummer, rolle) {
[Link] = personalnummer;
[Link] = rolle;
[Link] = function (was) {
[Link]();
return [Link] + ": " + was;
};
}
}
var p = new Person("007", "Projektleiter");;
[Link]([Link]("Heiße Luft"));
Sie haben bei Bedarf also eine Reihe von lokalen Hilfsfunktionen und dazu finden Sie
als Kern wieder den Konstruktor, mit dem das Objekt erstellt wird. Ganz wie bisher und
Sie sehen, dass die Unterschiede eigentlich marginal sind und man auch deshalb heute
schon nahezu widerspruchsfrei bei der Objektbeschreibung mit einem Konstruktor von
einer Art Klasse sprechen kann. Allerdings sind die Möglichkeiten so einer expliziten
Klassendeklaration umfangreicher, da es z. B. Vererbung auf einfache Weise gibt.
182 9 Objekte und JavaScript
9.4 Vererbung
Früher hatten Sie in JavaScript hinsichtlich Vererbung einigen Aufwand zu betreiben und
keinen einfachen Weg über Schlüsselwort extends wie in Java oder C# zur Verfügung,
um darüber die Superklasse von einer Klasse bei deren Deklaration explizit anzugeben.
Das wird mittlerweile aber unterstützt. Aber der klassische Weg mittels der Eigen-
schaften constructor und prototype eines jeden Objekts steht immer noch zur Verfügung,
um eine Vererbung aufzubauen. Nicht nur das. Wie erwähnt, wird die Abstraktion über
extends im Hintergrund immer noch diesen Weg geht.
Betrachten wir erst die JavaScript-Datei zur Vererbung in JavaScript per Prototyping
([Link]):
"use strict";
//Konstruktormethode Superklasse
function Person(name, vorname) {
[Link] = name;
[Link] = vorname;
}
//Konstruktormethode Subklasse
function Mitarbeiter(name, vorname, pid) {
[Link](name, vorname);
[Link] = pid;
}
[Link] = new Person();
var pfoertner = new Mitarbeiter("Feuerstein", "Fred", "007");
[Link]([Link] + " " + [Link] + " - Kennung:
" + [Link] + "\n" + "Ist 'vorname' eine eigene Eigenschaft von
dem Objekt? " + [Link]("vorname"));
Zuerst sehen Sie eine Funktion Person(name, vorname), die als Konstruktormethode
für eine „Superklasse“ dienen soll. Die Methode macht nicht mehr als, dass generier-
ten Instanzvariablen eines daraus erzeugten Objekts die Werte der Übergabeparameter
zugewiesen werden. In den folgenden Zeilen finden Sie eine weitere Funktion Mit-
arbeiter(name, vorname, pid), die als Konstruktormethode für die „Subklasse“ geplant
ist und die von der Superklasse erben soll. Beachten Sie, dass die ersten beiden Para-
meter denen der Funktion Person() entsprechen. Aus dem dritten Parameter dieser Funk-
tion wird eine neue Eigenschaft geschaffen. Die ersten beiden Parameter der Funktion
9.4 Vererbung 183
In modernen Versionen von JavaScript bzw. ECMAScript kann man das sonst übliche
syntaktische Konzept der Vererbung bei Klassen nutzen. Wenn Sie in JavaScript eine
Subklasse von einer Superklasse erzeugen wollen, nutzen Sie dazu bei der Deklaration
der Subklasse das Schlüsselwort extends, gefolgt von der Superklasse. Im Konstruktor
der Subklasse greifen Sie mit der Methode super auf den Konstruktor der Superklasse zu
und reichen damit die Werte aus der Subklasse weiter. Beispiel: [Link].
"use strict";
class Person {
constructor(name, alter, geschlecht) {
[Link] = name; [Link] = alter; [Link] = geschlecht;
}
}
class Mitarbeiter extends Person {
constructor(name, alter, geschlecht, id) {
super(name, alter, geschlecht);
[Link] = id;
}
}
let mitarbeiter1 = new Mitarbeiter("Max Mustermann", 25, "männlich",
4711);
[Link](mitarbeiter1);
Die Klasse Person deklariert bereit drei Eigenschaften. Die Subklasse Mitarbeiter er-
gänzt eine weitere Eigenschaft. Damit benötigt der Konstruktor von Mitarbeiter vier An-
gaben.
184 9 Objekte und JavaScript
9.5 Datenkapselung
Bei der echten objektorientierten Programmierung ist ganz wesentlich, dass ein Ob-
jekt seine innere Struktur vollständig vor anderen Objekten oder überhaupt einem di-
rekten Zugriff von außen verstecken kann. Man nennt dies Information Hiding oder
Datenkapselung. Um das zu gewährleisten, stellen viele objektorientierte Sprachen so-
genannte Zugriffsmodifizierer bzw. Zugriffsmodifier zu einer sehr feingliedrigen Steue-
rung des Zugriffs auf die Objektinterna zur Verfügung. Etwa die Zugänglichkeit über die
Schlüsselworte private, protected oder public, wenn man an Java oder C# denkt. Diese
gibt es in JavaScript zwar auch als reservierte Schlüsselworte4, aber für sie gibt es keine
Implementierung. Oder kurz und brutal – sie werden in JavaScript nicht unterstützt. Aber
das ist gut so! Denn meiner Meinung nach ist das JavaScript-Konzept schlanker, einfach
und effizienter und der Verzicht auf Sichtsbarkeitsmodifierer keinerlei Einschränkung,
sondern ein Gewinn5. Um jetzt aber bereits eine Zugriffsregelung bei JavaScript-
Objekten durchzuführen, muss man in JavaScript eine besondere Art der Programmie-
rung wählen. Ein Weg führt über den Einsatz von lokalen Variablen. Denn mit var oder
let spezifizierte Variablen in Funktionen sind nur dort zugänglich. Und wenn man dann
noch bei den Funktionsreferenzen zur Implementierung von Methoden anonyme oder in-
nere Funktionen (Closures) verwendet, bekommt man ohne die Notwendigkeit von Zu-
griffsmodifizierern eine echte Zugriffsreglementierung hin. Dann können Sie auch über
gewisse Konventionen Techniken wie Getter oder Setter samt Nur-lese-Eigenschaften
(read-only) als auch Nur-schreibe-Eigenschaften (write-only) auch in JavaScript um-
setzen.
So könnte man bei einem Konstruktor Datenkapselung mit Gettern und Settern um-
setzen:
4 Im strikten Modus wird eine Variable mit Namen private beispielsweise einen Fehler auslösen.
Ohne den strikten Modus könnten Sie diesen Bezeichner sogar verwenden, aber das ist echte „Sa-
botage“ des Quellcodes.
5 Wie jeder Verzicht auf redundante oder überflüssige Syntaxkonstrukte.
9.5 Datenkapselung 185
Beispiel
function RJS() {
var firmaInternerName = "RJS EDV KnowHow";
var urlInternerName = "";
[Link] = function() { return urlInternerName;};
[Link] = function(name) { urlInternerName = name;};
[Link] = firmaInternerName;
}◄
Eigentlich ist es nicht notwendig, hier irgendetwas zu erklären, so einfach und logisch ist
das in JavaScript, wenn die genialen Konstruktionen von Closures und Funktionen sowie
lokalen Gültigkeitsbereichen konsequent angewendet werden. Dennoch ganz kurz:
• Alles, was nicht nach außen gehen soll, wird im lokalen Gültigkeitsbereich gehalten.
• Alles, was nach außen gehen soll, wird über this oder gegebenfalls ein intern er-
zeugtes Objekt als Rückgabewert zugänglich gemacht.
• Durch die Namenskonvention von Gettern und Settern ist der Zugang für die An-
wendung streng logisch und offensichtlich.
Nun soll aber noch eine kleine Abwandlung einen Trick zeigen, der dem Fall vorbeugt,
dass der Konstruktor wie eine „normale“ Funktion (also ohne new) aufgerufen wird.
Beispiel
function RJS() {
var name = new Object();
var firmaInternerName = "RJS EDV KnowHow";
var urlInternerName = "";
[Link] = function() { return urlInternerName;};
[Link] = function(name) { urlInternerName = name;};
[Link] = firmaInternerName;
return name;
}◄
In dieser Objektdeklaration wird mit var name = new Object(); ein einfaches Objekt an-
gelegt und in einer lokalen Variable gespeichert. Da name ein Objekt repräsentiert bzw.
auf ein Objekt verweist, können wir dieses einfach durch Hinzufügen einer neuen Eigen-
schaft erweitern. Die lokale Variable wird also anstelle von this verwendet. Sonst bleibt
alles wie vorher. Der Trick ist nur, dass der Konstruktor mit return name; das Objekt
zurückgeben wird. Und zwar egal, ob der Aufruf mit new RJS() oder nur RJS() erfolgt.
Hier sehen Sie erneut, dass sich Konstruktormethoden und konventionelle Funktionen
in JavaScript nicht unterscheiden. Nur die Art des Aufrufs macht den Unterschied. Da
186 9 Objekte und JavaScript
wir aber unsere Funktion hier speziell gebaut haben, agiert die Funktion bei dieser Ver-
wendung ohne new im Sinn einer sogenannte Factory bzw. so, wie wir es schon bei ei-
nigen Standardfunktionen in JavaScript gesehen haben, die mit und ohne new ein Objekt
zurückgegeben haben. Unsere Funktion ist also sowohl als Konstruktor mit einer sinn-
vollen Datenkapselung als auch Funktion zum Erzeugen eines Objekts identisch zu ge-
brauchen.
Sie sehen also, dass eine Datenkapselung in JavaScript nicht nur möglich, sondern
sogar sehr effizient umzusetzen ist. Aber das ist– wie die Vererbung – in JavaScript nur
durch etwas trickreiche Programmierung möglich, bei der man sich strikt an eine ge-
wisse Art der Programmierung halten muss. Die Freiheit und Flexibilität von JavaScript
lässt als Preis auch Programmierung zu, die in vielen anderen objektorientierten Spra-
chen durch ein umfangreiches Gitter als Zwangsbedingungen unterbunden wird.
Im letzten Kapitel wurden Module bereits besprochen. Diese können auch objekt-
orientiert eingesetzt werden, um eine weitere Strukturierungsmöglichkeit zu schaffen.
Sie können Objekte, Konstruktoren bis hin zu Klassen auch in Modulen bereitstellen.
Da muss nichts wirklich Neues gezeigt werden. Ganz einfach wäre das so, wenn man
ein kurzes Beispiel zu ES6-Modulen ansehen will. Die Datei [Link] importiert einen
Konstruktor und eine Klasse aus dem Modul myModule im Verzeichnis es6module.
// [Link]
import { meinKonstruktor, MeineKlasse } from './es6module/myModule.
mjs';
new meinKonstruktor();
new MeineKlasse();
In der Datei [Link] sind der Konstruktor und die Klasse wie gewohnt deklariert,
nur mit einem vorangestellten export versehen.
// [Link]
export function meinKonstruktor() {}
export class MeineKlasse{}
RIAs mit ihren reichhaltigen Möglichkeiten verändern die Art der Nutzung des WWW.
Ebenso wird die Bedeutung von klassischen Desktop-Applikationen neu positio-
niert. Viele früher nur als Desktop-Applikation genutzte Programmtypen finden sich
9.7 Frameworks und POJO 187
p lötzlich im Web und werden mit dem Browser ausgeführt. Seien es persönliche Ka-
lender, vollständige Office-Programme, Spiele, Routenplaner, ganz integrierte Ent-
wicklungsumgebungen oder Kommunikationsprogramme. Auch mobile Webseiten
oder Anwendungen für Handys oder Smartphones basieren mehr und mehr auf Web-
Technologie. Dies verändert nicht zuletzt das Anwenderverhalten wie auch die An-
wendererwartung bei Internet-Applikationen im Allgemeinen sowie der Verfügbarkeit
von Leistungen. RIAs stehen auf der einen Seite als klassische Web-Applikationen (aber
mit einem gewissen Mehrwert) immer zur Verfügung, wenn man einen halbwegs schnel-
len Internetzugang und einen modernen Browser hat. Andererseits sind sie teilweise von
der Bedienung, der Performance wie auch Optik mittlerweile fast gar nicht mehr von
klassischen Desktop- oder Mobil-Applikationen zu unterscheiden.
Über die letzten Jahre hat sich die Einstellung durchgesetzt, dass eine Erstellung von
RIAs ohne den Einsatz von Web-Frameworks oder Toolkits nur schwer zu bewerk-
stelligen, zumindest nicht mit einem adäquaten Aufwand. Diese Tendenz hat sich zwar
abschwächt und wieder häufiger findet man Aussagen, dass man die Abhängigkeit von
Frameworks und den teils hohen Overhead vermeiden möchte, aber dennoch sind Frame-
works im Web immer noch sehr verbreitet. Was genau ein Framework ist und wie es sich
von einem Toolkit unterscheidet, ist nicht standardisiert. Sowohl eine verbindliche Defi-
nition als auch eine Abgrenzung ist nicht einfach. Allgemein versteht man unter einem
Framework ein Programmiergerüst, das bestimmte Funktionalitäten bereitstellt. Ein Fra-
mework ist selbst kein fertiges Programm, sondern stellt den Rahmen (Frame) zur Ver-
fügung, innerhalb dessen ein oder mehrere Programmierer eine Anwendung erstellen.
Ein Framework beinhaltet in der Regel eine Bibliothek mit nützlichen vorgegebenen
Code-strukturen (meist auf Basis einer spezifischen Sprache wie JavaScript), legt aber –
im Gegensatz zu einer reinen Bibliothek – auch eine Steuerung von Verhaltensweisen
bei der Verwendung fest (z. B. eine Syntax bzw. Grammatik). Frameworks können auf
verschiedene Weise arbeiten. Beliebt sind JavaScript-Frameworks, die dem Programmie-
rer im Browser Unterstützung bei JavaScript-Aktionen liefern. Aber auch aufseiten des
Webservers gibt es Frameworks und im Browser bekommen Besucher meist gar nicht
mit, dass Webseiten im Hintergrund von einem Framework generiert wurden. Dazu gibt
es Frameworks, die bestimmte Entwurfsmuster bereitstellen sowie spezielle Techniken
auf dem Server fordern.
Bei einem Toolkit steht dagegen mehr die Sammlung an Programmen (Tools) im
Fokus, die aber durchaus auch auf spezifischen Bibliotheken und einem Syntaxkonzept
aufsetzen können. Sowohl ein Framework, aber vor allen Dingen Toolkits stellen viel-
fach sogenannte Widgets beziehungsweise Komponenten zur Verfügung. Dabei handelt
es sich in der Regel um Elemente, aus denen eine grafische Benutzerschnittstelle (UI –
User Interface oder GUI – Graphical User Interface) zusammengesetzt wird.
188 9 Objekte und JavaScript
Nun gibt es verschiedene Arten von Frameworks. Ein sehr populäres, reines Java-
Script-Framework ist jQuery ([Link] Das clientseitige Framework ist auch
Grundlage weiterer darauf aufbauender Frameworks und unterstützt beim Programmie-
ren von JavaScript durch eine darauf aufbauende zusätzliche Syntax und sowie kom-
fortable erweiterte Funktionalitäten. Andere JavaScript-Frameworks setzen auf Ent-
wurfsmuster wie etwa [Link] ([Link] Dabei werden meist die Logik der An-
wendung von der Benutzeroberfläche getrennt. Es gibt zahlreiche Frameworks mit noch
einigen anderen Ansätzen, aber im Kern wird bei allen Web-Frameworks zumindest im
Client auf JavaScript gesetzt.
9.7.2 POJOs
Im Zusammenhang mit OOP fällt in der letzten Zeit verstärkt der Begriff POJO. Das
steht für „Plain Old JavaScript Object“ und bezeichnet ein einfaches JavaScript-Objekt,
das keine besonderen Methoden oder Eigenschaften hat, die von speziellen Klassen oder
Prototypen abgeleitet sind. POJOs sind im Grunde genommen nur Container für Daten.
Sie enthalten normalerweise nur Eigenschaften (auch bekannt als Schlüssel-Wert-Paare),
die einfache JavaScript-Typen wie Zeichenketten, Zahlen, Booleans, Arrays oder andere
POJOs sein können. POJOs sind in JavaScript weit verbreitet und gewinnen immer mehr
Bedeutung, da sie flexibel und einfach zu erstellen sind und oft als Datenstrukturen ver-
wendet werden, um Informationen zu speichern und zu übertragen. Sie sind in gewisser
Weise eine Gegenbewegung zu der vorherigen Tendenz, Objekte immer aufwändiger und
komplexer zu machen. Gerade viele Frameworks im Umfeld von JavaScript haben mit
der Zeit sehr komplexe Objekte entwickelt, wobei auch gerade in Bezug auf Frameworks
POJOs eine wichtige Rolle spielen können, wenn diese auf dem Prinzip der „Konvention
vor Konfiguration“ basieren oder die Datenmodellierung und Datenaustausch verein-
fachen möchten. Ein Beispiel in der JavaScript-Welt ist Angular. Einige Frameworks er-
möglichen auch die Bindung von POJOs an Benutzeroberflächenelemente oder Seriali-
sierung und Deserialisierung über POJOs.
Eingebaute Objektdeklarationen
10
Übersicht
JavaScript beinhaltet einige „eingebaute Klassen“, aus denen native Objekte er-
zeugt werden oder die bereits ohne Instanziierung direkt genutzt werden können.
In JavaScript sind die Objektdeklarationen und damit auch sämtliche nativen
Objekte alle von einer Grunddeklaration abgeleitet – der Wurzel Object. Diese
Objektdeklaration ist also eine Art Superklasse aller Objektdeklarationen im
JavaScript-Klassenbaum, auch wenn man – wie wir mehrfach schon besprochen
haben – genaugenommen einen Prototyp vorliegen hat. Bereits dieser globale
Objekttyp enthält alle Grundeigenschaften und -methoden, die jedes Objekt dann
beinhaltet. Dies sind die Grundfunktionalitäten, die eine Struktur braucht, um
im System überhaupt als Objekt zu agieren. Alle Objekte bei JavaScript erben
also von wenigstens einem anderen Objekt. In diesem Kapitel betrachten wir die
Standardobjekte von JavaScript, welche vom API bereits verfügbar sind.
Bei JavaScript ist im Grunde sowieso fast alles ein Objekt. Sogar alle einfachen Typen,
mit Ausnahme von Sondertypen wie null oder undefined (was ja auch keine echten Typen
sind), werden wie Objekte behandelt. Damit haben auch diese einfachen Typen Member.
Die build-in-Funktionen sind von der Philosophie her sowieso Methoden des globalen
Objekts. Etwas inkonsistent in JavaScript ist jedoch, dass numerische Variablen zwar
als Objekte verstanden werden, numerische Literale aber nicht, die damit keine Mem-
ber bereitstellen. Das ist meines Erachtens deshalb inkonsistent, weil String-Literale
und auch boolesche Literale explizit Objekte darstellen und man direkt darüber Member
© Der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH, ein 189
Teil von Springer Nature 2025
R. Steyer, Programmieren mit JavaScript, [Link]
190 10 Eingebaute Objektdeklarationen
v erwenden kann. Aber die Notation zum Zugriff auf Member wäre bei numerischen Li-
teralen auch schwierig, weil die Punktnotation ausscheidet. Der Punkt ist ja Bestandteil
eines numerischen Gleitkommaliterals.
Beispiel
Beispiel
var a = 5;
alert([Link]());
alert([Link]());
alert("das geht".toUpperCase()); ◄
• Entweder ein passendes Literal (Array-Literal oder JSON) oder meist eine Objekt-
deklaration bzw. Prototyp/Klasse als Bauplan für das zu erzeugende Objekt und seine
Member.
• Einen Mechanismus, der im nicht-literalen Fall mithilfe dieses Bauplans ein konkre-
tes Objekt erstellt. Also einen Konstruktor.
JavaScript stellt nun eine Reihe interner Klassen/Prototypen bereit, aus denen Sie auf
diese Weise Objekte erzeugen können. Schauen wir uns ein paar konkrete Beispiele an,
welche Standardobjekte von JavaScript erzeugen:
Beispiel
d = new Date();
b = new Array();
datum = new Date(2025,11,31,1,0,0);
resObj = new XMLHttpRequest();
meinBild = new Image(); ◄
Zur Erzeugung von Objekten gibt es in ECMAScript auch eine Methode [Link], um ein
Prototype-Objekt ohne die Definition einer Konstruktorfunktion auszuwählen. Wir gehen darauf
nicht weiter ein.
10.3 Klassenmethoden und Klasseneigenschaften verwenden 191
Um Member von Objekten nutzen zu können, muss man wie gesagt allgemein zu-
erst eine Objektinstanz auf dem beschriebenen Weg erzeugen. Danach kann man über
die Objektinstanz die verfügbaren Instanzelemente verwenden. Es gibt aber in der all-
gemeinen OOP auch das Konzept sogenannter Klassenelemente. Hierbei wird ein Mem-
ber direkt von einer Klasse zur Verfügung gestellt. Dies bedeutet mit anderen Worten,
dass Sie solche Elemente nutzen können, ohne vorher mit einem Konstruktor oder lite-
ral eine konkrete Instanz erzeugt zu haben. Bei der Botschaft stellen Sie stattdessen die
Klasse als Adressat voran. Solche Elemente werden bei JavaScript dann zur Verfügung
gestellt, wenn die konkrete Erzeugung einer Objektinstanz wenig Sinn macht oder un-
nötig aufwendig wäre. Dabei ist es irrelevant, ob wir diese Elemente Klassenelemente
oder Elemente eines Prototyps nennen. Im Fall von Object gibt es etwa die Eigenschaft
prototype, die wir schon im letzten Kapitel gesehen haben. Weitere Beispiele stehen vor
allen Dingen über Math zur Verfügung. Diese Objektdeklaration stellt Konstanten und
Methoden für mathematische Vorgänge bereit. Es wäre wenig sinnvoll, wenn Sie zuerst
ein Objekt erzeugen müssten, um eine Methode zur Verfügung zu haben, die beispiels-
weise eine Kommazahl auf eine ganze Zahl runden soll. Der Vorgang ist grundsätzlich
für alle Zahlen identisch und es hängt nicht davon ab, was für eine konkrete Objekt-
instanz Sie verwenden würden. Das Objekt selbst wäre vollkommen uninteressant für
die beabsichtigte Aktion. Hier macht es viel mehr Sinn, wenn Sie einfach die Objekt-
deklaration und dann die passende Methode verwenden. Beispiel:
Beispiel
[Link]
[Link]()
[Link]()
Math.E
Math.SQRT2 ◄
Klassennamen bzw. die Bezeichner von Prototypen beginnen in JavaScript grundsätzlich mit
einem Großbuchstaben. Das ist zwar nicht verbindlich, aber eine Konvention, die bei offiziellen
Quellen immer eingehalten wird. Wenn Sie also im Quelltext eine solche Notation finden, bei der
der Adressat einer Botschaft großgeschrieben wird, sollte es sich um eine Klasse bzw. einen Proto-
typ handeln und nicht eine selbst erzeugte Objektinstanz oder den Bezeichner für ein automatisch
generiertes Objekt. Denn diese werden ebenfalls per Konvention immer mit einem Kleinbuch-
staben begonnen. Dies gilt natürlich nur, wenn sich der Programmierer an die allgemein gültigen
Namensregeln hält.
192 10 Eingebaute Objektdeklarationen
• mathematische Objektdeklarationen,
• Datenfelder (kompakt und nur als Erinnerung),
• Datumsoperationen,
• Wahrheitsobjekte,
• Zeichenkettenoperationen,
• Bildobjekte und
• kurz Funktionsobjekte und reguläre Ausdrücke
kümmern. Allen diesen Objekten und Typen ist gemein, dass sie zum Kern von Java-
Script selbst gehören und unmittelbar in jedem JavaScript genutzt werden können. Aller-
dings ist die praktische Bedeutung einiger dieser Objekte eher gering, andere sind recht
abstrakt und andere wiederum sind nicht unbedingt trivial anzuwenden. Aber ein paar
Objekte bzw. ausgewählte Member davon sind von absolut grundlegender Bedeutung.
10.4.1 Math
Ein eingebauter Typ in JavaScript ist Math. Darüber stehen Ihnen diverse Methoden,
Konstanten und Eigenschaften für verschiedene Berechnungen bzw. auf mathematischen
Grundlagen basierende Operationen zur Verfügung.
Math stellt keinen Konstruktor zur Verfügung. Selbst wenn Sie es wollten, könnten
Sie mit new Math() keine Objekte vom Typ Math erzeugen.
[Link] Methoden
Math besitzt ebenso eine ganze Reihe von nützlichen Methoden, obwohl auch diese
natürlich mathematisch motiviert sind und wir sie in diesem Rahmen ebenfalls nicht alle
bis ins Detail diskutieren wollen (Tab. 10.2).
10.4 Native Objektdeklarationen in JavaScript 193
"use strict";
[Link]("Cosinus (40)",[Link](40));
[Link]("Zufallszahl", [Link]());
[Link]("Quadratwurzel 2",Math.SQRT2);
[Link]("Abrunden von PI",[Link]([Link]));
Auch Number dient in JavaScript mathematischen Zwecken. Wir haben damit schon ge-
arbeitet. Sie stellt wie Math Klassenelemente zur Verfügung, allerdings können Sie im
Gegensatz zu Math auch Objekte vom Typ Number erstellen. Im Fall der Anwendung
von Methoden müssen Sie das sogar machen, während Eigenschaften als Klassen-
elemente angesprochen werden. Number hat vor allen Dingen als Typ des Ergebnisses
von mathematischen Operationen und mathematischen Literalen als auch Umgang mit
den Grenzen von mathematischen Wertebereichen Bedeutung. Die Erstellung einer Ins-
tanz von Number geht sowohl über einen Konstruktor als auch deklarativ durch einfache
Zuweisung eines Zahlenliterals oder eines numerischen Ausdrucks zu einer Variablen.
Sie werden vermutlich nur selten Number über einen Konstruktor instanziieren, sondern
meist einfach mit Variablen oder Literalen vom Typ Number arbeiten und darüber kom-
men Sie an die entsprechenden Methoden und – obwohl meist nicht sinnvoll – auch die
Eigenschaften. In der Regel können Sie bei Eigenschaften auch auf das Voranstellen der
Klasse verzichten und einfach den Bezeichner der Eigenschaft notieren. Dieser repräsen-
tiert einen eingebauten Token (Tab. 10.3).
Auf die Eigenschaften von Number können Sie in Vergleichen prüfen, etwa mit if
(ausdruck > = MIN_VALUE) { … }. Allerdings haben wir die Besonderheit von NaN
schon behandelt. Darauf können Sie nur indirekt mit der Toplevel-JavaScript-Funktion
isNaN prüfen (Tab. 10.4).
Beachten Sie, dass dies wie erwähnt allesamt keine (!) Klassenmethoden sind. Ein
Aufruf der Art [Link](4); ist also nicht möglich. Stattdessen wird ein Ob-
jekt vom Typ Number der Methode vorangestellt. Dieses wird entweder deklarativ oder
mit einem Konstruktor erzeugt oder kann auch einfach ein Zahlenliteral sein.
Erinnern wir uns noch einmal an Arrays. In JavaScript werden alle Objekte über eine
Array-Struktur repräsentiert und umgekehrt wird jedes Array explizit als Objekt gesehen.
10.4 Native Objektdeklarationen in JavaScript 195
Damit gibt es auch implementierte Member. Die für eine Iteration über das Array wich-
tigste (und einzige) Eigenschaft ist length, was bei indizierten Datenfeldern die Anzahl
der Elemente im Datenfeld enthält. Ganz konkret haben Datenfelder darüber hinaus
noch einige Methoden, wobei deren Bedeutung in vielen Skripten – im Gegensatz zur
Wichtigkeit von Arrays selbst – eher gering ist. Hier ist eine Auswahl wichtiger Metho-
den, wobei diese ausdrücklich eben nicht bei JSON-Objekten bereitstehen (Tab. 10.5):
Die Anwendung der Methoden ist einfach, weshalb wir auf Beispiele hier verzichten.
Allerdings werden die Methoden in einigen Beispielen im Buch teils angewendet.
Grundsätzlich werden Wahrheitswerte in JavaScript durch die Token true und false re-
präsentiert. Die Verwendung dieser Token genügt auch in den meisten Fällen! Aber es
gibt auch Wahrheitsobjekte, die in gewissen Programmiersituationen statt der Token
verwendet werden können, und insbesondere sind die Token selbst vom Typ Boolean,
was auch von dem Operator typeof geliefert wird (boolean). Mit dem Konstruktor von
Boolean kann man auch die Token true und false erzeugen. Aber die Besonderheit ist,
dass man dabei statt der Token selbst auch andere Datentypen als Parameter übergeben
kann. Die Zahl 0, der Token null, eine leere Zeichenkette, der Token false selbst oder
auch der leere Konstruktor stehen für false, alle anderen Parameter in der Konstruktor-
methode repräsentieren den Wert true. Damit kann man zum Beispiel automatisierte
Entscheidungsstrukturen oder Rückgabewerte für Funktionen vereinfachen. Dennoch ist
die Anwendung von Wahrheitsobjekten eher selten. Der hauptsächliche Nutzen besteht
darin, dass man damit Wahrheitswerte sauber in ein objektorientiertes Konzept integrie-
ren kann.
Einer der wichtigsten Objekttypen, die Sie selbst unter JavaScript erzeugen können, ist
ein Datumsobjekt. Dieses wird als Instanz von Date erzeugt. Ein Objekt vom Typ Date
stellt in JavaScript für alle Berechnungen mit Datum und Zeit Funktionalitäten bereit.
Dafür gibt es die unterschiedlichsten Anwendungen.
Der 1. Januar 1970, 0.00 Uhr ist der interne Zeitnullpunkt (The epoch), der in nahezu allen
Skript- und Programmiersprachen als Speicherungs- und Berechnungsbasis für alle Operationen
mit einem Datum dient. Ab diesem Zeitpunkt wird die Zeit numerisch gezählt. Die Einheit, in der
in JavaScript intern Zeit verwaltet bzw. berechnet wird, ist die Millisekunde. Da jedes Datum in-
tern in einer ganzen Zahl verwaltet wird, können Sie mit Datumsobjekten wie mit gewöhnlichen
Zahlen rechnen. Beachten Sie, dass die Methoden eines Datumsobjekts Werte nach dem anglo-
amerikanischen System liefern und etwa bei Monaten der Januar durch eine 0 und nicht wie ge-
wohnt eine 1 repräsentiert wird. Ebenso steht bei Wochentagen die 0 für einen Sonntag.
10.4 Native Objektdeklarationen in JavaScript 197
(Fortsetzung)
198 10 Eingebaute Objektdeklarationen
Zur Erzeugung einer Objektinstanz von Date stehen mehrere Möglichkeiten in Form ver-
schiedener Konstruktoren zur Verfügung:
Beispiel
Die erste Variante erzeugt eine Objektinstanz, in der das zum Zeitpunkt der Skriptaus-
führung aktuelle Datum und die aktuelle Uhrzeit (die Systemzeit des Rechners eines
Besuchers) gespeichert wird. Die zweite und die dritte Variante initialisieren das neue
Date-Objekt mit bestimmten vorgegebenen Werten (also einem bestimmten Datum und
im dritten Fall auch einer bestimmten Uhrzeit). Alle Initialisierungswerte (Monat, Stun-
den oder Minuten) müssen in Zahlenform angegeben werden. Die vierte Version ist im
deutschsprachigen Raum eher von geringem Interesse, denn Sie geben die Parameter als
Zeichenketten in englischer Schreibweise an. Die fünfte Variante ist dann interessant,
wenn Sie über eine Funktion oder Berechnung die Millisekunden eines Datumswerts
erhalten und daraus ein Datumsobjekt erzeugen wollen. Dementsprechend steht new
Date(0) genau für den internen Zeitnullpunkt.
(Fortsetzung)
200 10 Eingebaute Objektdeklarationen
Beachten Sie, dass ein Datumsobjekt immer Zahlen repräsentiert! Damit sind auf den ersten Blick
eigenartig anmutende Verhaltensweisen bei den Setter-Methoden für Datumseigenschaften wieder
logisch. Aber wie gesagt – im ersten Moment erscheinen sie oft eigenartig, weshalb sie hier be-
sprochen werden sollen. So können Sie mit setHours die Stunden in einem Datumsobjekt setzen.
Nur sollten nach dem Verständnis aus dem täglichen Leben die möglichen Werte zwischen 0 und 23
liegen. Man kann bei der Methode aber auch größere und kleinere Werte verwenden. Das Verhalten
von JavaScript ist jedoch einfach und folgerichtig, wenn man darüber nachdenkt. Ebenso werden
damit einige Datumsoperationen extrem vereinfacht, was das Verfahren letztendlich genial macht,
wenn man sich daran gewöhnt und der Aussagelogik des Bezeichners einer Methode etwas gelöst
hat. Wenn Sie beispielsweise setHours(25) angeben, ist das einfach 2 Uhr am Folgetag. Vollkommen
analog verhält es sich bei den anderen Methoden wie setMinutes, setMonth oder setSeconds. Be-
achten Sie wie gesagt, dass diese Methoden nur ein Intervall an Millisekunden beeinflussen und die
letztendlich damit berechnete Zahl immer einen sinnvollen Wert ab dem 1. Januar 1970 darstellt.
Wir werden jetzt einige weitere Übungen durchführen, in der diverse der oben be-
schriebenen Methoden ausprobiert werden können und die auch für die Praxis einige Re-
levanz haben.
10.4 Native Objektdeklarationen in JavaScript 201
"use strict";
var a = new Date(2024, 10, 5, 12, 45, 59, 4);
var b = new Date(2024, 10, 5, 12, 45, 59);
[Link](new Date());
[Link]("new Date(\"october 03, 2028 12:11:03\") " +
new Date("october 03, 2028 12:11:03") );
[Link]("new Date(2026, 10, 5) " + new Date(2026, 10, 5));
[Link]("a: new Date(2014, 10, 5, 12, 45, 59, 4) " + a);
[Link]("b: new Date(2014, 10, 5, 12, 45, 59) " + b);
[Link]("(a - b): new Date(2026, 10, 5, 12, 45, 59, 4) " + (a -
b));
Zuerst erzeugen wir ein Datumsobjekt a mithilfe des Konstruktors mit sieben numeri-
schen Parametern und dann ein Datumsobjekt b mit dem Konstruktor mit sechs Para-
metern. Wenn die jeweiligen Werte ausgegeben werden und scheinen sie sich nicht
wesentlich zu unterscheiden. Aber die Ausgabe kann in die Irre führen. Die Ausgabe, in
der wir die Werte der beiden Datumsobjekte voneinander abziehen, zeigt, dass sie sich
sehr wohl unterscheiden. Weiter haben wir in dem Listing einen Konstruktor ohne Para-
meter für das aktuelle Systemdatum, mit dem String-Parameter und einen Konstruktor
mit drei numerischen Parametern verwendet.
Sie können natürlich ganz einfach die Millisekunden vergleichen, aber auch
mit den Methoden zur Bestimmung von einzelnen Bestandteilen des Datums
wie den Tagen, Stunden oder Minuten arbeiten.
Spielen wir ein Beispiel durch, bei dem eine Schulungsveranstaltung ankündigt wird.
Beim Erreichen des Ablaufdatums wird eine Meldung angezeigt, dass die Schulung be-
reits läuft. Wenn der Zeitraum für die Schulung vorbei ist, sehen Sie eine weitere Mel-
dung ([Link]):
…
<body><script>
var beginn = new Date(2025, 11, 21);
var ende = new Date(2025, 11, 23);
var besuch = new Date();
if ((beginn - besuch) > 0) {
[Link](
"<h4><a href = '[Link] Schulung</a></h4>");
} else if ((ende - besuch) > 0) {
[Link]("<h4>Die Schulung wird gerade durchgeführt.</h4>");
}
} else {
[Link]("<h4>Die Schulung ist beendet.</h4>");
}
</script></body></html>
Zuerst erzeugen wir ein Datumsobjekt beginn, das den Ablauftermin der Zeit vor dem
Termin repräsentieren soll. Danach wird ein weiteres Datumsobjekt angelegt, das den
Ablauftermin der Veranstaltung angibt, und als drittes Datumsobjekt wird der Besuchs-
termin der Webseite – das aktuelle Systemdatum – erzeugt und über besuch bereit-
gestellt. In der if-else-Anweisung wird mit der Differenz zwischen zwei Datumsobjekten
überprüft, ob ein bestimmter Termin zum Zeitpunkt des Besuchs erreicht ist oder nicht.
Dabei vergleichen wir jeweils ganz einfach den Wert der Millisekunden der beiden
Datumsobjekte. In Abhängigkeit von dem Wert zeigen wir einen von drei möglichen
Texten an, der mit [Link] in die Webseite geschrieben wird.
"use strict";
var a = new Date();
[Link](15);
[Link](42)
10.4 Native Objektdeklarationen in JavaScript 203
[Link](a);
var b = new Date(2024,16,34);
[Link](b);
Das Datumsobjekt a mit dem aktuellen Systemdatum wird hinsichtlich der Monate als
auch Stunden nach der Erzeugung geändert. Beachten Sie die bewusste „Sprengung“
des jeweilige Ranges. Die naive Annahme, dass sich die Stunden eines Tages zwischen
den Werten 0 und 23 bewegen müssen, wird widerlegt. Größere Zahlen erweitern die
Datumsangabe einfach auf den folgenden Tag oder auch im Grunde beliebig viele Tage
weiter. Vollkommen analog ist das bei den anderen Angaben zu Minuten, Sekunden etc.
Es ist nur ein Multiplikationsfaktor, der eine gewisse Anzahl an Millisekunden fest-
legt. Aus der Summe aller Werte kann man die Gesamtheit der Millisekunden seit dem
01.01.1970 bestimmen und das ist dann irgendein Datum. Auch der Konstruktor von
Date ist hinsichtlich der Parameter nicht künstlich beschränkt. Das Datum von b ist ein-
fach 02.06.2025. Der Monat 16 ist Mai und 34 Tage im Mai erzeugen den „Überlauf“ in
den Juni.
10.4.6 String-Operationen
Objekte vom Typ String zählen ebenfalls zu den nativen JavaScript-Objekten. Sie re-
präsentieren Zeichenketten, aber durch die Repräsentation als Objekte haben Sie damit
204 10 Eingebaute Objektdeklarationen
natürlich wieder diverse Member zum Umgang mit diesen Zeichenketten zur Verfügung.
Insbesondere können Sie Zeichenketten direkt über deren Methoden manipulieren. Um
die explizite Erstellung einer Instanz von einem String mit dem Aufruf eines Konstruk-
tors brauchen Sie sich meist nicht eigens zu kümmern, da eine Zuweisung der folgenden
Form automatisch ein String-Objekt erzeugt:
Beispiel
Sie erzeugen hiermit implizit das Objekt über eine deklarative Notation. Eine Ver-
wendung des new-Schlüsselworts in der folgenden Form zur expliziten Erzeugung eines
Objekts über einen Konstruktor funktioniert aber auch, obwohl das vollkommen unüb-
lich ist:
Beispiel
Die Variable, die auf eine Zeichenkette verweist, ist Ihr Zugang zu den Member. Wie
schon erwähnt, können Sie aber auch direkt auf String-Literale wie auf jedes andere
Objekt zugreifen. Ein String-Objekt verfügt nur über eine wichtige Eigenschaft. Über
length erhalten Sie die Anzahl der Zeichen bzw. die Länge in einer Zeichenkette.
Allgemein erstellen Sie konventionell eine Funktion in JavaScript, indem Sie das
Schlüsselwort function, den Namen, eventuelle Parameter und eine Implementierung no-
tieren. In diesem Fall haben Sie erst einmal nichts mit Objekten zu tun. Es gibt aber auch
den Fall, dass Sie eine Funktion in Objektform haben möchten. Und dies geht über die
Klasse Function (beachten Sie die Großschreibung!). Damit können Sie eine Funktion
überall verwenden, wenn Sie ein Objekt benötigen und mit einer klassischen Funktion die
notwendigen Bedingungen für eine Anwendung nicht erfüllt werden. Ebenso haben Sie
10.4 Native Objektdeklarationen in JavaScript 205
(Fortsetzung)
206 10 Eingebaute Objektdeklarationen
dann natürlich alle grundlegenden Member zur Verfügung, die jedes Objekt bereitstellt.
Sinnvoll ist so ein Function-Objekt beispielsweise im Zusammenhang mit einer variab-
len Parameteranzahl von einer Funktion, wobei auch eine klassisch erzeugte Funktion hier
verwendet werden kann. Die hauptsächliche Bedeutung von Objekten vom Typ Function
ist jedoch, dass Sie damit Funktionen formal sauber in eine objektorientierte Konzeption
integrieren können, wobei die Idee des globalen Objekts das Konzept mehr oder weni-
ger obsolet macht. Die Erstellung eines Objekts vom Typ Function erfolgt wie üblich mit
einem Konstruktor. Schema für die Erstellung eines Objekts vom Typ Function;
10.4 Native Objektdeklarationen in JavaScript 207
Beispiel
new Function([Parameterliste]) ◄
In der Regel wird der Verweis auf das Objekt natürlich wieder in einer Variablen ge-
speichert, denn sonst könnte man es nach der Erstellung natürlich nicht weiterverwenden
(sprich die Funktion aufrufen). Die Variable wird damit zu einer Funktionsreferenz!
Objekte dieses Typs sind in der Praxis eher unwichtig, denn man verwendet stattdessen klassische
Notation von (meist anonymen) Funktionen oder Lambda-Ausdrücken. Aus Sicht der Performance
eines Skripts ist das Erstellen von Funktionsobjekten zudem nicht zu empfehlen. Mehr wollen wir
zu dem Thema deshalb nicht ausführen.
In JavaScript können Sie mit Image und dem Konstruktor über new Image() Bildobjekte
erzeugen, denen als Eigenschaften alle Attribute zur Verfügung stehen, die Sie vom
<img>-Element kennen. Mehr braucht man dazu nicht zu sagen. Beachten Sie jedoch,
dass die Bildobjekte nach ihrer Erzeugung noch nicht der Webseite hinzugefügt oder
sonst irgendwo angezeigt wurden. Das müssen Sie selbst machen. Wir lernen die Metho-
den zum Manipulieren des DOM im nächsten Kapitel kennen.
Über die String-Klasse stehen Ihnen mehrere Techniken zur Verfügung, mit denen Sie
in einer Zeichenkette einzelne Zeichen oder mehrere Zeichen suchen und/oder ersetzen
können. Dazu müssen Sie den entsprechenden Methoden einfach den zu durchsuchenden
String voranstellen und entsprechend angeben, was Sie suchen oder ersetzen wollen. Die
Möglichkeiten zur Suche in Texten gehen aber weiter, wenn Sie sogenannte reguläre Aus-
drücke (regular expressions) einsetzen. Diese basieren entweder direkt auf einem Objekt
vom Typ RegExp bzw. der Objektdeklaration selbst oder speziell formatierten Token.
Reguläre Ausdrücke sind nicht trivial. Wir werden hier die Möglichkeiten nur
anreißen können. In der Anfangsphase Ihrer JavaScript-Karriere werden Sie
mit regulären Ausdrücken weniger in Berührung kommen und es gibt zahl-
reiche professionelle JavaScript-Programmierer, die niemals damit arbeiten.
Dennoch – sie können in manchen Fällen hilfreich sein und Skripte erheblich
kompakter machen.
[Link] Pattern
Suchmuster (sogenannte Pattern) werden bei der direkten Notation von regulären Aus-
drücken in einfache Schrägstriche /…/ eingeschlossen (keine Hochkommata außen
herum). Darin enthalten ist der zu suchende Ausdruck, der aus Suchtext und Steuer-
zeichen bestehen kann. So könnte eine Variable einen regulären Suchausdruck in Schräg-
strichen samt einer Modifikation zugewiesen bekommen.
Beispiel
regausdruck = /bin/i; ◄
Diese Notation bildet also vom beginnenden / bis zum abschließenden / einen Token und
stellt damit eine deklarative Erzeugung eines Objekts vom Typ RegExp dar. Gegebenen-
falls kann man – wie im Beispiel – mit einem Modifikator den Ausdruck genauer spezi-
fizieren. Bei der Formulierung eines Suchausdrucks über ein Objekt vom Typ RegExp
verzichten Sie auf die Schrägstriche und notieren als Parameter des Konstruktors eine
explizite Zeichenkette. Um dieses zu verwenden, erzeugen Sie mit dem Konstruktor
sowie einem Suchpattern in Hochkommata eine Instanz. Egal, wie Sie das Objekt er-
zeugen – Sie können anschließend die gewünschten Member des RegExp-Objekts an-
wenden oder das Objekt an eine Methode übergeben, die damit umgehen kann. Beispiele
für eine Erzeugung eines Suchmusters über den Konstruktor:
Beispiel
Bei Eingabe der Steuerzeichen in einem Pattern ist unbedingt auf Groß- und Klein-
schreibung zu achten. Ebenso wird bei einem Pattern in der Grundeinstellung beim
Suchtext zwischen Groß- und Kleinschreibung unterschieden, wenn Sie dies nicht
mit einer entsprechenden Option ausschalten.
denen Sie einen regulären Ausdruck formulieren können. Steuerzeichen und Optionen
können in einem Ausdruck kombiniert werden. Sofern keine Widersprüche ausgelöst
werden, kann man auch mit dem Pipe-Symbol | als Oder-Verbindung mehrere Ausdrücke
in einem Pattern verwenden.
Beispiel
[12345]
[1–5] ◄
Wenn eine Lücke in einer Sequenz notwendig ist, kann man das so formulieren, wie bei
dem Pattern, bei dem an der Stelle kann eine Zahl zwischen 1 und 3 oder 7 bis 9 stehen
darf.
Beispiel
[1–37–9] ◄
Selbstverständlich kann man so etwas auch für Buchstaben formulieren. Das ist ein Pat-
tern für einen zweistelligen Ausdruck, bei dem Zahlen und Kleinbuchstaben von a bis f
als Treffer erlaubt sind.
Beispiel
[0-9a-f][0-9a-f] ◄
Das wäre ein Beispiel für ein vierstelliges Suchpattern. Zuerst kommt eine Zahl, dann
ein Kleinbuchstabe gefolgt von zwei beliebigen alphanumerischen Zeichen.
Beispiel
[0–9][a-z]\w\w ◄
Mit dem Zeichen ^ können Sie auch eine Nicht-Bedingung formulieren. Das ist ein Pat-
tern, dass alle Zeichen außer den in den Klammern erlaubt.
210 10 Eingebaute Objektdeklarationen
Beispiel
[^abc] ◄
Wenn Sie eine Auswahl in runde Klammern notieren und dazwischen das |-Zeichen set-
zen, dann geben Sie eine Oder-Beziehung an. So formulieren Sie ein Pattern, bei dem
entweder das eine oder das andere Zeichen erlaubt ist.
Beispiel
(x|y) ◄
"use strict";
function sucheErstesVorkommen(text) {
// Suche nach dem ersten Vorkommen von a, d, g, Z oder 8
var match = [Link](/[adgZ8]/);
// Überprüfe, ob ein Treffer gefunden wurde
if (match) {
return match[0];
} else {
return "Kein Treffer gefunden";
}
}
// Beispieltext
var text = "Das ist ein Beispieltext mit Zahlen 123 und Buchstaben a,
b und c";
var ergebnis = sucheErstesVorkommen(text);
[Link]("Erstes Vorkommen:", ergebnis);
In diesem Beispiel wird der reguläre Ausdruck [adgZ8] verwendet, um nach dem ers-
ten Vorkommen eines der Zeichen "a", "d", "g", "Z" oder "8" im Text zu suchen. Die
match-Methode gibt ein Array mit den gefundenen Zeichen zurück, und match[0] gibt
das erste gefundene Zeichen zurück. Wenn kein Treffer gefunden wird, wird eine pas-
sende zurückgegeben.
function sucheErstesVorkommen(text) {
// Erstelle einen regulären Ausdruck mit dem Konstruktor new RegExp
var regex = new RegExp("[adgZ8]");
// Suche nach dem ersten Vorkommen des regulären Ausdrucks im Text
var match = [Link](regex);
…
212 10 Eingebaute Objektdeklarationen
Hier wird der reguläre Ausdruck [adgZ8] als Zeichenkette an den Konstruktor new Re-
gExp übergeben, um ein Objekt zu erstellen. Dann wird dieses verwendet, um nach dem
ersten Vorkommen im Text zu suchen. Das übrige Verfahren bleibt gleich.
"use strict";
function ersetzeZahlen(text) {
return [Link](/\d+/g, '*');
}
// Beispieltext
var text = "Dies ist eine Zahl: 123 und eine andere Zahl: 456. Auch
das ist eine Zahl: 7890";
var neuerText = ersetzeZahlen(text);
[Link](neuerText);
In diesem Skript wird die replace-Methode mit einer RegExp verwendet, die nach einer
oder mehreren aufeinanderfolgenden Ziffern sucht (\d+ ). Das Flag g bewirkt, dass die
Suche global im gesamten Text durchgeführt wird. Alle gefundenen Zahlen werden dann
durch "*" ersetzt.
function testEMail(feld) {
var korrekt = true;
var regausdruck = new RegExp('[a-zA-Z][0-9a-zA-Z]*@.{3}');
if () korrekt = false;
return korrekt;
}
Der reguläre Ausdruck beginnt mit einer Klammerangabe, dass das erste Zeichen zwin-
gend und ein Buchstabe sein muss. Das zweite Zeichen ist optional, was mit dem Stern
10.4 Native Objektdeklarationen in JavaScript 213
angegeben wird. Der Klammerausdruck erlaubt wieder Buchstaben oder Zahlen. Nun
folgt zwingend das Zeichen @. Der nachstehende Punkt erlaubt ein beliebiges Zeichen.
Die Angabe in den geschweiften Klammern legt fest, dass mindestens drei beliebige Zei-
chen dort stehen.
Dann wird mit der Methode test geprüft, ob der Wert in dem Parameter den Ver-
gleichswert enthält. Falls ja, liefert die Methode true und im Fehlerfall false.
Der DOM – das Mysterium der
JavaScript-Welt 11
Sie werden oft Objekte erzeugen oder vorhandene Objekte und Typen verwenden,
wie wir es in den letzten Kapiteln gesehen haben. So gut wie immer werden Sie
Objekte nutzen, die Ihnen automatisch zur Verfügung gestellt werden. Entweder
von einer Laufzeitumgebung wie [Link] oder vom Browser. Browser haben
wir bisher eher am Rande verwendet, aber historisch ist der Browser das „natür-
liche“ Habitat von JavaScript gewesen und auch heute noch die Umgebung, wo
das meiste JavaScript aufgeführt wird. Browser wurden auch bisher nicht ignoriert,
sondern fast alle bisherigen Ausführung gelten auch da. In dem Kapitel kommen
wir aber zu speziellen Objekten, die unter [Link] oder allgemein serverseitiger
Umgebung nicht verfügbar sind, sondern explizit nur im Browser. Diese Objekte
basieren auf einem Objektmodell, das Strukturen von weitgehend beliebigen
baumartig aufgebauten Dokumenten beschreibt. Dieses nennt sich DOM – das Do-
cument Object Model – und wird beim W3C standardisiert. Diese Objektbibliothek
kann mittels diverser Techniken genutzt werden, sowohl aus Programmier- und
Skriptsprachen als auch aus Anwendungen heraus. Dem Zugriff auf eine Webseite
unter diesem Objektgesichtspunkt liegt mit DOM ein globales Konzept zugrunde,
das sowohl eine plattform- als auch programmiersprachenübergreifende Schnitt-
stelle bezeichnet.
DOM bezeichnet eine Programmier-API für Dokumente, die stark der Struktur der von
ihm modellierten Dokumente in Form eines Baums ähnelt. Der Kern des DOM-Konzepts
basierte anfänglich auf einer Überlegung zum Aufbau einer Webseite aus HTML. In
© Der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH, ein 215
Teil von Springer Nature 2025
R. Steyer, Programmieren mit JavaScript, [Link]
216 11 Der DOM – das Mysterium der JavaScript-Welt
d ieser ersten Überlegung wird eine HTML-Seite nicht als statisch aufgebaute, fertige und
nicht unterscheidbare Einheit, sondern als differenzierbare Struktur betrachtet. Damit
sind deren einzelne Bestandteile Programmen und Skripten dynamisch zugänglich, wenn
es eine Syntax für einen Zugriff gibt. Dieser Ansatz, den ursprünglich im Wesentlichen
Netscape inklusive seiner Layer-Technologie vorangetrieben hat, ermöglicht die indivi-
duelle Behandlung von Bestandteilen einer Webseite auch dann, wenn die Webseite be-
reits in den Browser geladen ist. Allerdings konnten sich die ersten Umsetzungen solch
eines Konzepts nicht einheitlich in den verschiedenen Browsern durchsetzen, weshalb
Netscape die Verantwortung für die Standardisierung an das W3C übertragen hat. Die
ersten Varianten des DOM-Konzepts sind daher als Versuche des W3C zu verstehen, die
verschiedenen proprietären Zugriffstechniken auf Teile einer Webseite zusammenzu-
führen, die sich während der Zeit der Browserkriege gebildet haben, und sie mit einem
einheitlichen Konzept abzulösen. Um eine präzise, sprachunabhängige Spezifikation
der DOM-Schnittstellen bereitzustellen, ist die Spezifikationen beim W3C in OMG IdL
definiert. Zusätzlich zur OMG IdL-Spezifikation gibt es Sprachbindungen für Java und
ECMAScript. Das DOM-Konzept beinhaltet mittlerweile verschiedene Teilaspekte. Es
veranlasst beispielsweise einen Browser, eine Webseite zwar wie eine gewöhnliche Text-
datei zu parsen, entsprechende HTML-Anweisungen auszuführen und die Seite darzu-
stellen. Darüber hinaus wird der Browser jedoch beim Laden der Webseite alle ihm im
Rahmen des Konzepts bekannten und einzeln identifizierbaren Elemente einer Webseite
bezüglich ihres Typs, ihrer relevanten Eigenschaften/Methoden und ihrer Position inner-
halb der Webseite indizieren. Wenn man es anders ausdrücken möchte – aus den Elemen-
ten der Webseite werden Objekte gebildet, sobald diese identifiziert wurden, Referenzen
darauf gespeichert und zugänglich gemacht. Das DOM-Konzept ist mittlerweile auch auf
andere baumartige Dokumente wie etwa XML anwendbar. In der DOM-Spezifikation
wird der Begriff „Dokument“ im weitesten Sinne verwendet. Man kann Dokumente er-
stellen, durch ihre Struktur navigieren und Elemente und Inhalte hinzufügen, ändern oder
löschen. Alles, was in einem HTML- oder XML-Dokument gefunden wird, kann mit
dem Document Object Model aufgerufen, geändert, gelöscht oder hinzugefügt werden,
mit wenigen Ausnahmen.
Die Baumstruktur eines Dokuments aus Sicht des DOM-Konzepts wird im Haupt-
speicher des Rechners beim Laden eines Dokuments aufgebaut. Ein solcher wird beim
Verlassen der Seite wieder gelöscht. Ähnliche Elemente werden bei der Indizierung auf
gemeinsame dynamische Datenstapel (Stacks) für das Dokument abgelegt. Wer jetzt
an automatisch indizierte Arrays denkt, ist auf dem richtigen Weg. Auf diese Weise hat
ein Browser nach dem Laden der Webseite genaue Kenntnis über alle relevanten Daten
11.1 Was ist das DOM-Konzept? 217
sämtlicher eigenständig für ihn ansprechbarer Elemente (Objekte) in der Webseite. Wel-
che das jedoch sind und was er damit anstellen kann, das hat sich in der Vergangenheit je
nach Browser unterschieden. Diese Unterschiede nehmen aber massiv ab.
Im DOM-Konzept stehen mehrere Objekte für eine Verwendung in einer Webseite als
auch dem umgebenden Browser zur Verfügung. Beachten Sie, dass nicht alle Brow-
ser sämtliche Objekte unterstützen, Objekte obsolet werden können und es auch in der
Art der Unterstützung zahlreiche Unterschiede gibt. Man kann die DOM-Objekte in all-
gemein (Tab. 11.1) und spezifisch für bestimmte HTML-Elemente unterteilen (Tab. 11.2).
Es gibt im Rahmen des DOM-Konzepts sogenannte Objektfelder. Es sind Arrays mit DOM-
Objekten eines spezifischen Typs als Inhalt. Charakteristisch dafür ist, dass die darin enthaltenen
Objekte wie bei „normalen“ Arrays über einen Feldnamen sowie eine Indexnummer identifiziert
werden. Sie können aber auch als Index einen Namen verwenden, wenn es ein name-Attribut gibt.
Ansonsten ist die Anwendung von Member vollkommen identisch wie bei anderen Objekten. Die
nachfolgenden DOM-Objekte für die Repräsentation von Bestandteilen einer Webseite stehen
meist über Objektfelder bereit. Diese Objektfelder, die Sie in JavaScript verwenden, entstehen
automatisch, wenn eine Webseite geladen wird und Objekte eines bestimmten Typs darin enthalten
sind. Beachten Sie, dass sich die Namen der Objektfelder aus den Namen der enthaltenen Objekt-
typen ergeben und immer nur ein s (für die Pluralform) angefügt wird. Ein Array mit Objekten
vom Typ image heißt also beispielsweise images.
Nachfolgend sehen Sie weitere DOM-Objekte, die Teile einer HTML-Seite repräsentieren.
Wenn diese als Objektfelder geführt werden, erhalten Sie Zugriff auf die einzelnen Ele-
mente anhand ihres Namens oder Index und das Objektfeld oder mit den üblichen Zugriffs-
techniken auf einzelne Objekte innerhalb der Webseite, die wir noch behandeln.
Hintergrundinformation
Eher selten genutzt werden die Objekte text (Repräsentation des Texts innerhalb eines HTML-Ele-
ments), comment (Kommentar im HTML-Dokument), stylesheet (CSS-Stylesheet im Dokument),
table, row und cell (Tabellen und Strukturen), documentType (Doctype eines HTML-Dokuments)
und documentFragment (temporäres Dokumentobjekt).
Dazu gibt es auch einige veraltete Objekte. Ein paar sollten Sie der Vollständigkeit halber vom
Namen noch kennen.
Tab. 11.2 Unter dem DOM-Konzept verfügbare Objekte, die Teile der Webseite repräsentieren
Objekte Beschreibung
anchor Eine Referenz auf einen Verweisanker in einer Webseite, die über das Objektfeld an-
chors zugänglich ist.
attribute Repräsentiert ein Attribut eines HTML-Elements und die Sammlung aller Attribute
wird in einem Objektfeld bereitgestellt.
element Ein Objekt vom Typ element bezieht sich auf die Sammlung von HTML-Elementen
innerhalb eines Formulars. Es ist ein Eigenschaftsobjekt des Formularelements und
ermöglicht den Zugriff auf die einzelnen Formularelemente anhand ihres Namens
oder Index und das Objektfeld elements.
form Eine Referenz auf ein Objekt, das ein Formular in einer HTML-Seite repräsentiert.
Es wird unter forms gesammelt und enthält selbst wieder Steuerelemente wie Ein-
gabefelder, Schaltflächen usw., die für die Interaktion mit Besuchern und auch zum
Sammeln von Daten zur Serverübermittlung genutzt werden.
frame Eine Referenz auf einen Frame in einer HTML-Seite. Alle Frames werden unter
frames gesammelt.
image Eine Referenz auf eine Grafik in einer Webseite (images).
link Eine Repräsentation von einem Hyperlink (links).
node Objekte vom Typ node sind ein Sonderfall im DOM. Es gibt einmal kein Objektfeld,
sondern es wird immer nur genau Objekt damit bezeichnet. Dieses Objekt stellt den
Zugang zu jedem einzelnen Element in einem baumartig strukturierten Dokument
zur Verfügung. Es ist das Schlüsselelement für den Einsatz von modernem DHTML
und RIAs allgemein. Beachten Sie, dass Sie den Token node für einen Zugriff auf
ein node-Objekt nicht (!) hinschreiben. Es gibt sogar gar keinen Token node! Sie
erhalten einen Knoten auf eine der im Buch beschriebenen Zugriffsarten (etwa mit
getElementById() oder über ein Objektfeld) und werden damit indirekt auf ein node-
Objekt zugreifen.
option Das Objekt ist eine Eigenschaft einer Auswahlliste. Es repräsentiert die Sammlung
von <option>-Elementen innerhalb des <select>-Elements und wird unter options
zusammengefasst.
220 11 Der DOM – das Mysterium der JavaScript-Welt
• Microsoft hatte vor vielen Jahren ein Objekt all eingeführt, mit dem der direkte Zugriff auf alle
Elemente einer Webseite in seinem Browser möglich war. Es wurde durch Objekte vom Typ
node ersetzt.
• Zu den Hochzeiten von Java-Applets war das Objekt applet als Referenz auf ein Java-Applet in
einer Webseite verfügbar.
• Historisch ist das Objekt layer von Netscape als Repräsentation eines Layers in einer Webseite
eine Art frühe Version von node.
• Mit plugin gab/gibt es ein Objekt, das die vorhandenen Plug-Ins in einem Browser repräsen-
tiert. Die Verwendung von Plug-Ins ist heutzutage jedoch rückläufig, da moderne Webtechno-
logien wie HTML5 viele ihrer Funktionen übernommen haben.
Einige der DOM-Objekte sind weitgehend historisch von Interesse, während die Be-
deutung anderer in der modernen Webprogrammierung schwindet. Zwei DOM-Objekte
hingegen zeichnen sich durch eine wachsende Bedeutung aus – document und node. Die
beiden DOM-Objekte bilden den Kern fast aller modernen RIAs. Dazu ist window als
globales Objekt herausgehoben.
Beispiel
[Link][0].elements[1] ◄
Das Verfahren setzt sich analog auf Member der Formularelemente fort. Vollkommen
analog der Situation bei selbst erzeugten Objekten stellt jedes Objekt im DOM seine
11.3 Eventhandling 221
Sie können beim Zugriff auf den DOM-Baum meist auf das Voranstellen von
window verzichten. Statt [Link]() notiert man in der Praxis einfach
prompt() oder oder document statt [Link]. Wir werden im Buch so
gut wie immer auf die Notation von window verzichten.
11.3 Eventhandling
Beispiel
[Link]=meinefunktion; ◄
In diesem Beispiel sehen Sie den Zugriff auf eine Eigenschaft onclick eines Objekts mit
Namen document. Streng formal ist das nichts Neues. Aber dabei habe ich Ihnen einen
JavaScript-Eventhandler „untergejubelt". Denn nichts Anderes ist der Einsatz eines sol-
chen Eventhandlers unter JavaScript selbst – eine Zuweisung einer Funktionsreferenz zu
einer passenden Eigenschaft des Objekts, bei dem das Auslösen des Ereignisses über-
wacht werden soll. Der Name der Eigenschaft ist äquivalent zum Bezeichner des HTML-
Ereignisattributs, wenn beide vorhanden sind. Alternativ können Sie in so einer Situation
natürlich auch eine anonyme Funktion verwenden. Beispiel:
Beispiel
[Link]=function() { … }; ◄
Früher hat man Eventhandler als Attribute bei HTML-Tags angeben. Das ist heutzutage nicht mehr
üblich und mit vielen Nachteilen verbunden. Ereignisbehandlung sollte nur mit JavaScript vor-
genommen werden.
222 11 Der DOM – das Mysterium der JavaScript-Welt
Die verfügbaren Ereignisse, auf die Sie unter JavaScript reagieren können, umfassen ver-
schiedene Stituationen, die im Laufe des „Lebens“ einer Webseite im Browser auftreten
können. Wir gehen sie nur ganz kurz durch, denn die grundsätzliche Art der Reaktion ist
immer identisch – man bindet einen Callback an ein bestimmtes Ereignis.
Beispiel
[Link]=Callback; ◄
Der Eventhandler kann theoretisch auch bei einigen anderen Elementen verwendet wer-
den. Das macht man in der Praxis aber so gut wie gar nicht und kann sogar Probleme
bereiten.
Worauf Sie achten müssen, ist, dass Sie onload nicht zu früh verwenden. Wie
schon erwähnt, sind Zugriffe auf Bestandteile des DOM ein massives Problem,
wenn sie erfolgen, bevor der DOM vollständig aufgebaut ist. Diverse Frameworks
im Umfeld von JavaScript und des DOM unternehmen massive Anstrengungen,
um diesen Problemen Herr zu werden. Die meisten Dokumentationen der Frame-
works weisen deutlich darauf hin, dass sie von der Verwendung des onload-Event-
handlers abraten, weil dieser in einigen Browsern (hauptsächlich alte Versionen
des Internet Explorers) fehlerhaft implementiert ist. Die Probleme nehmen in
neueren Browsern ab. Der Aufruf einer Initialisierungsfunktion ganz am Ende der
Webseite ist als Alternative dennoch zuverlässiger.
Die Anwendung des Eventhandlers onunload ist das Parallelereignis zum Laden einer
Seite, wenn eine Seite wieder aus dem Speicher entfernt wird, etwa wenn eine neue Seite
geladen wird. Mehr gibt es eigentlich dazu gar nicht zu sagen, nur sollten Sie beachten,
dass die Ausführung des Eventhandlers nicht garantiert werden kann. Zum Beispiel kann
ein Anwender den Browser einfach abschießen.
[Link] Mausereignisse
Selbstverständlich sind Klicks auf Referenzen mit der Maus Ereignisse und ideale Kan-
didaten, um ein JavaScript aufzurufen. Ebenso gibt es zum Überstreichen eines Elements
einer Webseite mit dem Mauszeiger nichts Spannendes zu sagen, zumindest nicht so
lange, wie wir nicht das Ereignisobjekt verwerten, um etwa die Position des Mauszeigers
zu verwenden. Dieses werden wir bei der Behandlung des Ereignisobjekts tun.
11.3 Eventhandling 223
[Link] Tastaturereignisse
Die Reaktion auf das Betätigen einer (beliebigen) Taste auf der Tastatur bildet einen
weiteren Kandidaten für aufzufangende Ereignisse und auch hierzu gibt es nichts Be-
sonderes zu sagen, solange wir nicht das Ereignisobjekt verwerten.
[Link] Formularereignisse
Es gibt eine Reihe von Ereignissen, die hauptsächlich bzw. ausschließlich im Zu-
sammenhang mit Webformularen sinnvoll sind. Das sind zum einen die Änderung, die
Aktivierung und das Verlassen eines Elements in einer Webseite. Diese Aktionen sind
nahezu ausschließlich bei Elementen zur Entgegennahme von Benutzereingaben sinn-
voll. Zum anderen geht es um das Verschicken von Formulareingaben bzw. das Zurück-
setzen des Formulars. Gerade im Zusammenhang mit Formularen zeigen JavaScript-
Eventhandler ihre Stärken. Wir werden Webformulare in einem eigenen Kapitel noch
ausführlich behandeln und dort nur mit JavaScript-Eventhandlern arbeiten.
[Link] Weitere Ereignisse
In JavaScript stehen Ihnen eine Reihe weiterer Eventhandler wie onselect, onerror oder
onabort zur Verfügung und die ganzen Eventhandler in spezifischen Situationen wie dem
Nachladen von Daten und für die es oft in HTML kein Gegenstück gibt.
Hinter der Ereignisbehandlung in JavaScript liegt ein spezielles Objekt – das Ereignis-
objekt bzw. Event-Objekt, das ein Mitteilungsobjekt mit Informationen über die Art des
ausgelösten Ereignisses darstellt. Ereignisobjekte werden vom Browser unablässig er-
zeugt und ein solches Objekt kann zur Reaktion auf Ereignisse gezielt verwertet werden.
Dabei dürfen Sie nicht nur an die wenigen offensichtlichen Ereignisse wie den Klick des
Besuchers mit der Maus oder eine Tastatureingabe denken. Einfaches Verschieben des
Mauszeigers über den Bereich des Browsers findet quasi permanent statt. Da können in
kürzester Zeit Tausende von Ereignisobjekten entstehen. Jedes Verschieben einer Maus-
zeigerposition um wenige Pixel bewirkt in der Regel bereits ein eigenständiges Ereignis-
objekt. Oder denken Sie an das Scrollen des Textes. Auch hier kann das Scrollen eines
Textes bereits um wenige Millimeter Ereignisobjekte generieren. Das Ereignisobjekt
stellt dazu eine Reihe interessanter Eigenschaften mit spezifischen Informationen sowie
diverse Methoden bereit. Betrachten wir als Beispiel die Situation, wenn ein Anwender
mit der Maus in irgendeinen Bereich der Webseite klickt. Bei einem Mausklick erzeugt
der Browser ein Ereignisobjekt, das unter anderem folgende Informationen enthält:
Andere Ereignisobjekte, die bei weiteren Ereignissen erzeugt werden, beinhalten natür-
lich andere Informationen, die dem Ereignis angepasst sind. So steht beispielsweise bei
einem Tastendruck die gedrückte Taste als abzufragende Information bereit. Allgemein
beinhaltet ein Ereignisobjekt zahlreiche sinnvolle Informationen, die Sie zur Erstellung
gut angepasster Applikationen nutzen können.
Ein behandeltes Ereignisobjekt wird nach der Handhabung in einem Handler sofort
vernichtet.
Das innerste Element ist in der Hierarchie des DOM-Baums am weitesten von der Wur-
zel entfernt. Hat dieses Element keinen geeigneten Eventhandler, wird das Ereignisobjekt
an das nächsthöhere Objekt im DOM-Baum (dem direkten Vorfahren) weitergeleitet usw.
Es steigt wie eine Blase (Bubble) über alle Vorfahren hinauf bis zur Wurzel. Es blubbert
durch den Baum, bis es behandelt wird.
Nicht alle Typen von Ereignisobjekten durchlaufen die Bubble-Phase. Details muss man in speziel-
len Quellen bei Bedarf eruieren.
11.3 Eventhandling 225
Wenn es bis zum Wurzelelement keine Behandlung des Ereignisobjekts gab, wird das
Ereignisobjekt bei einer Variante des Event-Bubblings vernichtet. Bei einer alternativen
Variante wird ein unbehandeltes Ereignisobjekt nach Erreichen der Wurzel wieder bis
zum auslösenden Element zurückblubbern und kann auf dem Weg zurück behandelt
werden. Das kann interessant sein, wenn man mehrere Ereignisobjekte sammeln und ge-
meinsam behandeln möchte.
Der erste Parameter eines Callbacks, der an ein Ereignis gebunden wird, enthält
immer die Referenz auf das Ereignisobjekt.
Hier sind ein paar Eigenschaften, die Sie bei einem Ereignisobjekt auswerten können
(Tab. 11.3).
Beachten Sie, dass wir in dem Kapitel meist mit internen JavaScripts arbeiten. Das ist für die Er-
klärung oft leichter und wird aus didaktischen Gründen gemacht. In der Praxis sollten Sie so gut
wie immer (!) mit externen JavaScripten arbeiten. Zudem werden wir wiederholende Passagen im
Quellcode und im kontext uninteressante Teile (insbesondere zu HTML und CSS) weglassen und
den Quellcode um Zeilenumbrüche und Leerzeichen teils reduzieren, um Platz zu sparen.
…
<body><h1>Drücken Sie eine beliebige Taste!</h1></body>
<script>
[Link] = function(ev) {
[Link]("Gedrückter Tastencode: " + [Link]);
}
</script></html>
1 Wennwir die Zugriffstechniken auf Inhalte der Webseite genauer behandelt haben, werden diese
Ausgaben innerhalb der Webseite erfolgen.
11.3 Eventhandling 227
…
<h1>Klicken Sie in die Webseite</h1></body>
<script language="JavaScript">
function pos(ev) {
var text = "Koordinatenangaben relativ zum Fenster (pageX und
pageY): " + [Link] + ", " + [Link] + ".\nBildschirmkoordinaten
(screenX und screenY): " + [Link] + ", " + [Link] + ".\nTasta-
turcode bzw. Maustastencode (which): " + [Link] + ".\nArt des Ereig-
nisses (type): " + [Link];
[Link](text);
}
[Link] = pos;
</script></body></html>
In dem Beispiel wird der Eventhandler onmouseup überwacht und über die Funktions-
referenz die Funktion pos referenziert. In der Funktion setzen wir einen Antworttext mit
diversen Werten aus dem Ereignisobjekt zusammen. Diese werden wieder in die Konsole
geschrieben (Abb. 11.2).
[Link] Globale Ereignisbehandlung
Man kann eine globale Ereignisbehandlung aufbauen, wenn man das möchte. Dabei mo-
dularisiert bzw. kapselt man die Ereignisbehandlung weiter und verzichtet sogar gänz-
lich auf Eventhandler. Im Gegensatz zum Aufruf eines Eventhandlers per JavaScript bei
einem – aufgrund der DOM-Objektrepräsentation eines HTML-Elements verfügbaren –
Objektgegenstück zu einem HTML-Element können Sie den Reaktionsmechanismus
losgelöst von einem einzelnen HTML-Tag oder einer per JavaScript bei einem Element
aufgerufenen Aktion implementieren. Es handelt sich hier um die direkte Verwertung
des Ereignisobjekts in passenden Methoden. In der Vergangenheit gab es auch hier
• Ein Ereignis tritt bei einem Quellobjekt (der sogenannten Event Source) auf.
• Das entstandene Ereignisobjekt wird an einen registrierten Zuhörer (besagtem Event
Listener oder kurz Listener) weitergeleitet.
• Erst durch den Listener wird über die konkrete Ereignisbehandlung entschieden und
die Reaktion auch ausgelöst.
Im DOM-Eventmodell der Version 2 wird zur Registrierung eines Listeners eine Me-
thode addEventListener definiert. Die schematische Syntax sieht so aus:
Beispiel
Der Ereignistyp ist einer der Token, die wir auch bisher schon als Eventhandler ver-
wenden, allerdings ohne vorangestelltes on, also etwa click oder mouseover. Die An-
weisung ist einfach ein Callback und der dritte Parameter ist ein Boolean-Wert, der fest-
legt, ob das Ereignisobjekt auf dem Hin- (true) oder Rückweg (false) durch die Ereignis-
hierarchie (Stichwort Bubble-Phase) abgefangen werden soll. Beispiele:
Beispiel
Das ganze Verfahren ist leistungsfähig, sauber und modular zu strukturieren, gut lesbar
und wartbar. In dem Beispiel (event_3.html) greifen wir etwas vor und selektieren so-
wohl das Bild als auch den Button über eine Id, um daran die Ereignisbehandlung für
einen Mausklick jeweils zu binden (Abb. 11.3).
…
<body><h1>Klicken Sie auf das Bild und auf die Schaltfläche</h1>
<img id="b1" src="images/[Link]" /><br />
<button id="bt1">Klick da</button><hr /></body>
<script>
function kDiv() { [Link]("Klick auf den Bildbereich"); }
function kH1() { [Link]("Klick auf den Button"); }
[Link]('b1').addEventListener("click", kDiv, false);
[Link]('bt1').addEventListener("click", kH1, true);
</script></html>
11.4 Generelle Zugriffsmöglichkeiten auf DOM-Objekte 229
Wie wir gerade gesehen haben, können Sie aus JavaScript heraus im Rahmen des DOM-
Modells auf die einzelnen Elemente einer Webseite zugreifen. Dazu gibt es verschiedene
Möglichkeiten, die aber immer das gleiche Objekt referenzieren und unabhängig von
der Art des Zugriffs die gleichen Member bereitstellen. So gibt es neben den gerade be-
handelten Zugriffen über einen Objektnamen für Standardobjekte des DOM oder ein
Objektfeld noch die folgenden Methoden:
• getElementById
• getElementsByTagName
• getElementsByName
Die Methoden werden sowohl von dem DOM-Objekt document (alle drei) als auch von
node (getElementsByTagName) bereitgestellt. Ebenso gibt es in modernen Browsern
mit Unterstützung von HTML5 und dem neuen DOM5 den Zugriff über einen Klassen-
namen (getElementsByClassName). Die verschiedenen Zugriffsmethoden können in
einem verschachtelten Zugriff problemlos gemischt werden, wenn das sinnvoll ist und
alle Zielbrowser den Weg unterstützen.
230 11 Der DOM – das Mysterium der JavaScript-Welt
Wenn Sie für den Zugriff auf ein Element der Webseite die Methode [Link]-
mentsByName verwenden wollen, müssen Sie den Namen des Elements (in der Regel
als HTML-Attribut name beim Tag notiert) als String-Parameter angeben. Allerdings
müssen Sie für den Zugriff auf ein konkretes Element noch einen Index angeben, da die
Methode ein Array als Rückgabewert liefert. Beachten Sie den Plural im Bezeichner der
Methode. Beispiel:
Beispiel
[Link](passwort)[0] ◄
Alternativ kann man den Namen auch direkt angeben, wenn er eindeutig ist. Das ist aber
nicht immer zuverlässig.
Beispiel
[Link] ◄
Der Zugriff über einen Namen bietet sich bei klassischen Webformularen an,
denn um die eingegebenen Daten in den Eingabefeldern des Formulars an
den Server schicken zu können, müssen diese Eingabefelder sowieso einen
Namen besitzen. Umgekehrt gilt aber auch, dass der Zugriff über Namen auch
nur bei Formularen zuverlässig funktioniert. Bei anderen Elementen kann es
Komplikationen geben.
…
<body><h1>Zugriff auf DOM-Elemente</h1>
<form name="formular1"><input name="user"/> User<br/>
<input type="password" name="passwort"/> Passwort<br/>
<input type="button" value="OK" name="ok" /></form>
<script>
[Link]("ok")[0].onclick=function(){
alert([Link] + "\n" +
[Link]("passwort")[0].value);
};
</script></body></html>
Die Elemente in dem Formular sowie das Formular selbst besitzen ein name-Attribut.
Für den Zugriff auf den User notieren wir die direkte Punktnotation mit Namen und für
die beiden anderen Zugriffe im Skript [Link]. Beachten Sie,
11.4 Generelle Zugriffsmöglichkeiten auf DOM-Objekte 231
dass wir damit für jeden Namen ein Array erhalten und deshalb immer einen Index an-
geben müssen. Auch wenn es jeweils nur ein Element gibt, das zu einem spezifischen
Namen von der Methode geliefert wird.
Sollte für ein Element im HTML-Tag ein id-Attribut spezifiziert sein, können Sie mit
[Link] darauf zugreifen. Sie geben als String-Parameter einfach die
Id an. Beispiel:
Beispiel
[Link](pw) ◄
Diese Variante des Zugriffs ist sehr zuverlässig und beim Zugriff auf genau ein Element
in der Webseite ideal.
Gerade bei komplexeren RIAs ist der Zugriff über eine Id fast immer die beste
Wahl. Nur damit haben Sie auch bei Umstrukturierungen des DOM-Baums
eine vollständige Kontrolle über den Zugriff auf ein Element. Man kann sich
aus dem Fenster lehnen und sagen, dass der Zugriff über die Id der Königsweg
ist, wer er denn möglich ist.
Die Methode getElementById liefert immer genau ein Element und kein Array.
Nur gibt es ein unkalkulierbares Risiko, das aus HTML bzw. dem Verhalten der
Browser selbst stammt. Eine Id sollte vom Prinzip her in einer Webseite eine
eindeutige Kennung darstellen. Aber wegen des Prinzips der Fehlertoleranz
werden Browser keinen Fehler melden, wenn es eine Id mehrfach in einer
Webseite gibt. Sie sind leider darauf angewiesen, selbst aufzupassen, dass es
in einer Webseite keine Id mehrfach gibt. Bei mehreren gleichen Ids ist das
Verhalten verschiedener Browser beim Zugriff über getElementById auf ein Ele-
ment nicht kalkulierbar.
Modifizieren wir unser Formularbeispiel nun so, dass wir Ids verwenden können (dom2.
html):
…
<form name="formular1"><input name="user" id="userid"/> User<br/>
<input type="password" name="passwort" id="pw" /> Passwort<br/>
<input type="button" value="OK" id="ok" /></form>
<script>
[Link]("ok").onclick=function(){
232 11 Der DOM – das Mysterium der JavaScript-Welt
alert([Link]("userid").value + "\n" +
[Link]("pw").value);
};
</script></body></html>
Sie sehen, dass die Elemente in dem Webformular eine Id haben. Im Skript verwenden
wir dementsprechend nur einfach [Link] zum Zugriff auf die
Formularfelder.
Sie können bei einem Element natürlich gleichzeitig ein name- und ein id-
Attribut vergeben, was wir im Beispiel ja auch machen. Das sind einfach ver-
schiedene Attribute. Nur gibt es bei verschiedenen Browsern recht seltsame
(unlogische) Verhaltensweisen beim Zugriff auf so ein Element über den
Namen bzw. die Id, wenn die Werte des name- und des id-Attributs gleich
sind. Am besten vermeiden Sie solche Gleichheit der Werte, was ja kein Prob-
lem ist und keinerlei Einschränkung darstellt. Im Beispiel sind deshalb bewusst
die Werte des name- und des id-Attributs verschieden gewählt.
Beispiel
[Link](input)[10] ◄
Um auch dieses Zugriffsverfahren über den Elementnamen bzw. Tag-Namen in der Praxis
zu zeigen, modifizieren wir wieder unser Beispiel mit dem Webformular ([Link]):
…
<form name="formular1"><input name="user" id="userid"/> User<br/>
<input type="password" name="passwort" id="pw" /> Passwort<br/>
<input type="button" value="OK" id="ok" /></form>
<script>
[Link]("input")[2].onclick=function(){
alert([Link]("input")[0].value + "\n" +
[Link]("input")[1].value);
};
</script></body></html>
Wie Sie sehen, erfolgt der Zugriff im Skript über getElementsByTagName und den
Namen für die Eingabeelemente in einem Formular – input. Das dritte Element dieses
11.4 Generelle Zugriffsmöglichkeiten auf DOM-Objekte 233
Typs steht für die Schaltfläche, das erste für den Benutzer und das zweite für das Pass-
wortfeld.
…
<form><input name="user" class="userid"/> User<br/>
<input type="password" class="passwort"/> Passwort<br/>
<input type="button" value="OK" class="ok"/></form>
<script>
[Link]("ok")[0].onclick=function(){
alert([Link]("userid")[0].value + "\n" +
[Link]("passwort")[0].value);
};
</script></body></html> ◄
…
<form><input/>User<br/></form>
<script>
[Link]("input")[0].onblur=function(){
[Link]([Link]);
};
</script></body></html>
234 11 Der DOM – das Mysterium der JavaScript-Welt
Wenn der Anwender bei dem Webformular das Eingabefeld verlässt (etwa mit dem Tabu-
lator, steht dieser in der Event-Methode über this zur Verfügung. Damit ist [Link] der
Inhalt des Eingabefeldes.
Der Zugriff auf den Inhalt von Elementen in einer Webseite lässt sich im Wesentlichen in
drei Kategorien unterscheiden:
Sie können bei jedem Element in der Webseite, das einen Text als Inhalt hat, diesen
per JavaScript ansprechen und austauschen, sofern Sie den zugehörigen Knoten selek-
tiert haben. Dazu gibt es innerHTML. Als Alternative steht innerText zur Verfügung.
Beide Eigenschaften gehörten schon lange zum Werkzeugkasten von JavaScript-
Programmierern, obwohl sie lange nicht zum offiziellen DOM-Sprachstandard gezählt
haben. Dennoch funktionierte der Zugriff über innerHTML in fast allen Browser-Gene-
rationen dieses Jahrtausends. Diese Eigenschaften vereinfachen die Zugriffswege gegen-
über der Verwendung der direkten node-Methoden und -Eigenschaften wie erheblich und
sind zudem zuverlässiger. Der Unterschied zwischen innerHTML und innerText ist recht
gering. Sowohl über innerHTML als auch über innerText haben Sie Zugang zum Text-
inhalt eines HTML-Elements. Wenn Sie beim dynamischen Ändern des gespeicherten
Inhalts bei innerHTML jedoch HTML-Tags notieren, werden diese bei der Aktualisie-
rung des Elementinhalts interpretiert. Das ist offiziell bei innerText nicht der Fall. In der
Praxis wird innerText fast nie verwendet. Die ebenfalls in einigen Browsern verfügbare
Eigenschaft outerText gibt Ihnen Zugang zum gleichen Wert wie innerText. Nur werden
beim Ändern umgebende HTML-Tags entfernt und durch Text ersetzt. Als weitere Alter-
nativen zum Inhalt eines Elements gibt es outerHTML. Mit outerHTML erhalten Sie Zu-
gang zum Inhalt eines HTML-Tags samt der umgebenden Tags mit allen Angaben.
Die genannten Eigenschaften sollten nicht direkt beim Laden der HTML-Datei
zum Einsatz kommen, sondern erst nach dem vollständigen Laden der Seite.
Erst dann kann deren Verfügbarkeit gewährleistet werden.
11.5 Zugriff auf Inhalte von Elementen in der Webseite 235
[Link] Das Formularobjekt
Wie schon beschrieben, werden Formulare in einer Webseite im Rahmen des DOM-Kon-
zepts in einem Objektfeld mit Namen forms geführt, dessen Bestandteile Objekte vom
Typ form sind. Natürlich können Sie ein Formularobjekt über die üblichen DOM-Metho-
den und -Techniken wie getElementById oder getElementsByTagName etc. ansprechen.
Für das Objektfeld forms selbst ist im Grunde nur die Eigenschaft length mit der An-
zahl der Elemente des Formulars interessant. Allgemein stehen Ihnen jedoch eine ganze
Reihe von Member bei einem solchen einzelnen Formularobjekt vom Typ form zur Ver-
fügung. Die Eigenschaften eines einzelnen Formularobjekts ergeben sich nahezu zwin-
gend aufgrund der HTML-Struktur eines Webformulars bzw. der erlaubten Attribute
des <form>-Tags. Von daher sollten die Eigenschaften bereits bei elementaren HTML-
Kenntnissen weitgehend klar oder zumindest nachvollziehbar sein (Tab. 11.4).
Ein Formularobjekt besitzt nicht viele Methoden. Wesentliche Aufgabe von einem
Formular ist ja die Datenübermittlung und die zentrale Methode erledigt dies. Viel mehr
benötigen Sie nicht an Methoden (Tab. 11.5).
Mit Ausnahme des Containers einer Auswahlliste besitzt jede Objektrepräsentation
eines Formularelements die Eigenschaft value. Damit können Sie mit Ausnahme eines
Auswahllistencontainers den Wert eines beliebigen Formularelements abfragen oder set-
zen. Der Datentyp der Eigenschaft ist in jedem Fall ein String.
Eine der wichtigsten Eigenschaften eines Formularobjekts ist sicher dessen Objektfeld
elements. Darüber haben Sie Zugriff auf alle einzelnen Bestandteile innerhalb von Formu-
laren, also Eingabefelder, Auswahllisten, Kontrollkästchen etc. Einige der Eigenschaften
der enthaltenen Objekte vom Typ element sind bereits verfügbar, wenn der Browser
236 11 Der DOM – das Mysterium der JavaScript-Welt
enigstens ein Formular mit wenigstens einem Element in der HTML-Datei vorfindet.
w
Andere Eigenschaften sind jedoch nur für bestimmte Formularelemente verfügbar (das
heißt, wenn ein Element des zugeordneten Typs überhaupt vorhanden ist) (Tab. 11.6).
Die Methoden von Objekten vom Typ element sind folgende (Tab. 11.7):
Mit dem Objektfeld options, das in der Objekthierarchie wiederum unter elements
als Eigenschaft angeordnet ist, haben Sie Zugriff auf Auswahllisten innerhalb eines For-
mulars oder genauer – auf die Einträge in einer Auswahlliste, denn der Container wird
über elements direkt bereitgestellt. Das Objektfeld selbst bestimmt über selectedIndex,
welche Auswahlmöglichkeit in einer Auswahlliste aktiviert ist. Wenn kein Eintrag der
Liste ausgewählt ist, dann ist in der Eigenschaft der Wert -1 gespeichert. Ein Objekt in
der Auswahlliste, das einen einzelnen Eintrag repräsentiert, hat folgende Eigenschaften
(Tab. 11.8):
11.5 Zugriff auf Inhalte von Elementen in der Webseite 237
Achten Sie auf die Unterschiede zwischen den Bezeichnern der Selektions-
eigenschaften von Radiobuttons und Checkboxen sowie Einträgen in Aus-
wahllisten. Bei Radiobuttons und Checkboxen wird in den Token die Zeichen-
kette checked und bei Einträgen in Auswahllisten selected verwendet. Obwohl
ein Aktivierungszustand streng formal nicht das Gleiche wie ein Selektions-
zustand ist, ist das dennoch eine tückische kleine Fehlerquelle, weil man das
leicht durcheinanderbringt.
aber mit JavaScript können Sie hierbei natürlich bei Bedarf Programmierlogik einfließen las-
sen. Beispiel (form_1.html):
…
<body><h1>Die Felder in dem Formular sind vorbelegt</h1>
<form action="" method="get" name="a">
<input name="a" size="40" /><br /><input name="b" size="40" /><br
/>
<input name="c" size="40" /><br /><input name="d" size="40" /><br
/>
Sie sehen hier ein ganz einfaches Formular. Die Callback-Funktion, die nach dem Fertig-
stellen des DOM-Baums aufgerufen wird, belegt die fünf einzeiligen Eingabefelder in
dem Formular über die Eigenschaft value mit Werten vor. Zur Demonstration wird der
Zugriff sowohl beim Formular selbst als auch den Formularfeldern unterschiedlich ge-
handhabt. Solch ein Mischen von Zugriffsformen ist definitiv praxisfern. In der Praxis
sollten Sie sich immer für eine Variante entscheiden und diese dann konsequent ver-
wenden, sofern es nicht zwingende Gründe für einen unterschiedlichen Zugriff gibt.
Beachten Sie in dem Beispiel, dass es in der HTML-Seite sowohl ein Formular mit
dem Namen a als auch ein Formularelement mit dem identischen Namen a gibt. Zwar
sollten die Namen in einer Webseite eindeutig sein, aber in diesem Fall befinden sich die
beiden Elemente der Webseite in getrennten Namensräumen (der Name des Eingabefelds
ist im Grunde a.a). Diese Wahl gleicher Namen sowohl für das Formular als auch eines
der darin enthaltenen Formularelemente wurde nur aus Demonstrationszwecken gezeigt
und sollte in der Praxis tunlichst unterbleiben. Denn insbesondere beim Zugriff über do-
[Link] ist die Sache nicht mehr unkritisch, da man leicht mit der
Indizierung durcheinanderkommen kann.
…
<body><h1>Selektionszustand von Radiobutton und Checkboxen</h1>
<form>Herr <input type="Radio" name="a" value="mann">
240 11 Der DOM – das Mysterium der JavaScript-Welt
In diesem Beispiel finden Sie in dem Webformular zwei Optionsfelder und ein Kontroll-
kästchen per HTML definiert. In der Funktion check, die über den Eventhandler onclick
bei jedem Element des Webformulars aufgerufen wird, wird je nach Zustand des aus-
lösenden Formularelements eine unterschiedliche Meldung angezeigt. Zur Registrie-
rung verwenden wir eine Schleife innerhalb der Initialisierungsfunktion, in der zusätz-
lich beim Laden der Webseite der Defaultselektionszustand des ersten Radiobuttons auf
true gesetzt wird. Sie sehen hier, dass das Setzen einer Aktivierungs- bzw. Selektions-
eigenschaft aus JavaScript nur über den passenden booleschen Wert erfolgen kann. In der
Funktion check wird der Zustand des jeweils beim Klick aktuellen Elements ausgegeben.
Wir können hier mit this arbeiten, denn der Klick wurde ja bei einem spezifischen Radio-
button oder der Checkbox ausgelöst und this steht damit in der aufgerufenen Funktion
jeweils für das entsprechende Formularelement. Natürlich können Sie in der Funktion
auch alle alternativen Wege zum Zugriff auf Formularelemente verwenden, also etwa ein
Formularelement mit [Link][0].elements[0].checked aktivieren. Nur
dann müssen Sie den Index des Elements der aufgerufenen Funktion übergeben, wenn
Sie das Beispiel verwenden wollen.
Wenn Sie die Eigenschaft checked für ein Element einer Gruppe aus Optionsfeldern
auf true setzen, werden automatisch alle anderen Elemente der Gruppe deselektiert. Das
ist nichts weiter als das typische Verhalten von zusammengehörenden Optionsfeldern.
um ein Objekt handelt, stellt dieses auch weitere spezifische eigene Eigenschaften bereit,
die auch erst den konkreten Nutzen darstellen.
Darüber hinaus gibt es freilich einige sehr spezielle Feinheiten beim Umgang mit
Auswahllisten aus JavaScript heraus. Oder anders ausgedrückt – der Zugriff bei Aus-
wahllisten ist etwas tückisch. Denn wenn Sie bei einer Auswahlliste einen JavaScript-
Zugriff über Objektfelder wählen, referenzieren Sie mit dem elements-Objektfeld den
<select>-Container, jedoch noch nicht die einzelnen Einträge in den <option>-Contai-
nern! Andererseits wird die Selektion der Einträge in der Aufzählungsliste einige über-
raschende Probleme bereiten. Dementsprechend müssen Sie ziemlich aufpassen, wie Sie
die Ereignisse auslösen und was Ihnen wann und wie bereitgestellt wird.
Obwohl es nun verschiedene Ansätze gibt, wie man auf Klicks auf Einträge in Aus-
wahllisten reagieren kann (etwa mit onclick bei den Elementen eines <select>-Contai-
ners oder auch bei den einzelnen Einträgen in den <option>-Containern), sollten Sie in
der Regel den folgenden Weg gehen, wenn die Reaktion universell funktionieren soll.
Vergegenwärtigen wir uns nun erst einmal, was wir eigentlich wollen? Wir wollen
testen oder festlegen, welcher Eintrag in einer Auswahlliste selektiert ist. Die Objekt-
repräsentation einer Auswahlliste (also des Containers selbst) besitzt dazu die Eigen-
schaft selectedIndex. Damit können Sie ermitteln, welcher Eintrag in einer Auswahlliste
aktiviert ist, oder auch einen Eintrag in der Auswahlliste aus JavaScript heraus selektieren.
Allerdings werden Sie feststellen, dass Sie damit auch Probleme bekommen, wenn Sie
das auslösende Element ungeschickt verwerten wollen. Wenn Sie jedoch this beim Aus-
lösen eines Ereignisses aus einer Auswahlliste als Parameter übergeben, sind Sie auf der
sicheren Seite. Einen Eintrag als selektiert zu markieren, ist auch nicht kompliziert. Sie
müssen nur den passenden numerischen Index eines Eintrags verwenden. Dazu setzen Sie
die selectedIndex-Eigenschaft auf den numerischen Wert des gewünschten Eintrags und
damit ist das Element der Auswahlliste selektiert. Alternativ können Sie aber auch mit der
Eigenschaft selected eines einzelnen Eintrags in der Auswahlliste arbeiten, die Sie selbst-
verständlich ebenso abfragen wie zum Selektieren setzen können. So etwas für Selektion
eines Eintrags in der Auswahlliste über die Eigenschaft selected würde also funktionieren:
Beispiel
function welcherIndex(i) {
[Link][i].selected=true;
} ◄
In diesem Fall setzt man die Eigenschaft selected einfach auf den Wert true beziehungs-
weise einen numerischen Wert ungleich 0 – dann ist das Element selektiert – oder den
Wert false beziehungsweise 0 – in diesem Fall ist das Element deselektiert. Wenn ein
Eintrag als selektiert gesetzt wird, deselektiert das alle anderen Einträge, sofern keine
Mehrfachselektion vorgesehen ist. Unsere zweite Überlegung betrifft den Event-
handler, den wir bei der Auswahlliste verwenden. Wir werden statt onclick auf onchange
242 11 Der DOM – das Mysterium der JavaScript-Welt
umsteigen. Mit onchange werden wir ein Ereignis erst dann auslösen, wenn ein Eintrag
in der Auswahlliste selektiert (oder genauer geändert) wird. Betrachten Sie das nach-
folgende Beispiel (form_3.html):
…
<body><h1>Selektion bei Auswahllisten</h1>
<form><select name="ticket" id="t">
<option id="e">Erwachsener</option>
<option id="k">Kind</option>
<option id="f">Familie</option></select></form><hr />
<div id="info"></div>
<script>
function init() {
[Link][0].elements[0].defaultChecked = true;
[Link]("t").onchange = function () {
welcherIndex(this);
}
}
function welcherIndex(i) {
[Link]("info").innerHTML = "Ausgewählter Eintrag: "
+ [Link];
}
[Link] = init;
</script></body></html>
Sie sehen eine einfache Auswahlliste (Abb. 11.5). In der Funktion init können Sie er-
kennen, dass der Eventhandler onchange beim Container der Auswahlliste verwendet
wird. Dieser Container hat die Id t. Sie sehen, dass wir das auslösende Objekt der Funk-
tion welcherIndex als Parameter übergeben und mit der Eigenschaft selectedIndex den
Index des ausgewählten Eintrags in der Auswahlliste bestimmen können.
Nun stellt sich noch die Frage, wie man an den Text kommt, der hinter den Einträgen
einer Auswahlliste liegt und dem Anwender angezeigt wird? In der Regel können Sie
einfach die Eigenschaft value des auslösenden Elements verwenden. Früher ging das
nicht immer und im Grunde ist der Zugriff über value auch nicht ganz logisch. Denn
es gibt ja noch die Eigenschaft text. Werfen wir also nochmals einen genauen Blick auf
die Eigenschaft value eines Eintrags in einer Auswahlliste. Damit greifen wir gezielt auf
den Wert eines Eintrags in einer Auswahlliste zu. Beachten Sie aber, dass der Wert eines
Eintrags in einer Auswahlliste nicht die Bedeutung hat, die er bei den meisten anderen
Formularelementen hat.
Der Wert repräsentiert im Allgemeinen nicht die sichtbare Beschriftung eines Eintrags
in einer Auswahlliste, sondern den Wert, der beim Versenden eines Formulars an den
Server übermittelt wird. Wenn value nicht angegeben wird (weder in HTML noch per
JavaScript), wird aber der Text aus dem <option>-Container an den Server übermittelt
11.5 Zugriff auf Inhalte von Elementen in der Webseite 243
und damit value indirekt durch den dort angezeigten Text belegt. Wenn jedoch die Eigen-
schaft value bei einem Eintrag in einer Auswahlliste angegeben ist, steht der angezeigte
Text nicht über value, sondern nur über die Eigenschaft text bereit.
Beispiel
Aber hier ist man schon deshalb auf einem falschen Weg, weil man keine HTML-Event-
handler mehr verwenden sollte, sondern die gesamte Logik in JavaScript verlagert
wird. Jede Objektrepräsentation eines Webformulars besitzt eine zugeordnete Methode
submit. Deren Aufruf schickt ebenfalls die Werte in dem Formular ab. Die Alternative
ist reset. Wenn Sie die Daten nicht verschicken oder das Formular nicht zurücksetzen
wollen, rufen Sie einfach die Methoden nicht auf. Sie können damit das Abschicken der
Formulardaten dem Klick auf einem Hyperlink, einem einfachen Button oder jeder ande-
ren Situation zuordnen, die sich mit einem Eventhandler beschreiben lässt. Und n atürlich
244 11 Der DOM – das Mysterium der JavaScript-Welt
lässt sich damit leicht eine Logik implementieren, ob Daten verschickt werden oder
nicht.
Das gesamte Verfahren kann man hervorragend mit dem Plausibilisieren von Formu-
laren kombinieren. Die wichtigste Anwendung von JavaScript auf Formulare ist sicher
die Plausibilisierung von Benutzereingaben. Das Plausibilisieren eines Webformulars
bedeutet, die Schlüssigkeit der Anwendereingaben vor einem Versenden der Daten zu
kontrollieren und bei Widersprüchen zu den geforderten Vorgaben Gegenmaßnahmen zu
ergreifen. Die Sache ist jedoch alles andere als trivial und die wesentlichen Dinge be-
treffen nicht die Programmierung selbst. Plausibilisierung eines Webformulars ist nur als
globales Konzept schlüssig, das auch die Weiterverwendung der Daten nach dem Ver-
senden sowie diverse weitere Fakten beinhalten muss. Bei der Plausibilisierung eines
Webformulars müssen Sie sich zahlreicher Problemstellungen bewusstwerden. Obwohl
wir das Thema nur anreißen wollen, kann man einige Fragen recht pauschal zusammen-
fassen:
Sie sehen, dass man bei einem Plausibilisierungskonzept viel beachten muss. Hier eine
vernünftige Abwägung zwischen notwendigen Prüfungen, deren tatsächlicher Um-
setzung und realisierbarem Aufwand zu finden, ist alles andere als einfach. Verfolgen
wir kurz die Überlegung, wann ein Webangebot zu plausibilisieren ist. Bei klassischen
Webapplikationen hat es sich durchgesetzt, das Formular in der Regel erst beim Ab-
schicken der Formulardaten zum Server zu kontrollieren. Wenn wir allerdings den asyn-
chronen Datenaustausch mit Ajax einbeziehen, wird sich die Plausibilisierung nach und
nach etwas mehr zum Zeitpunkt des Verlassens eines Felds oder sogar einer einzelnen
Tastatureingabe verlagern. Ajax erlaubt es beispielsweise in Verbindung mit DHTML,
einem Anwender eine Rückmeldung zu geben, wenn beim Verlassen eines Eingabe-
felds ein offensichtlicher Fehler vorliegt, ohne ihn durch Mitteilungsfenster oder Neu-
laden der Seite in seinem Arbeitsfluss zu behindern. So wird Ajax nach und nach dafür
sorgen, dass sich Webapplikationen auch bei der Plausibilisierung mehr dem Verhalten
von Desktop-Applikationen annähern. Dennoch ist das Abschicken von Formulardaten
11.5 Zugriff auf Inhalte von Elementen in der Webseite 245
}
if ([Link] < 10) { // Kontrolle Passwort
[Link]("info").innerHTML =
"Das Passwort besteht aus mindestens 10 Zeichen";
[Link] = "";
[Link]();
return false;
}
[Link] = "[Link]";
[Link]();
}
function zuruecksetzen() {
if (confirm("Das Formular wird zurückgesetzt.\nSind Sie sicher?")) {
[Link][0].reset();
[Link]("info").innerHTML = "";
return false;
}
return false;
}
function init() {
[Link]("frage").onclick = maxAnzahl;
[Link]("zurueck").onclick = zuruecksetzen;
}
[Link] = init;
</script>
</body></html>
Das Webformular beinhaltet zwei einzeilige Eingabefelder (eines davon ist ein Passwort-
feld) und neben der Schaltfläche, die wir zum Verschicken der Daten verwenden wollen,
gibt es eine Schaltfläche zum Zurücksetzen des Formulars. Beide werden mit dem <but-
ton>-Tag umgesetzt (Abb. 11.6). Die Benutzereingaben werden so plausibilisiert, dass
eine Kontrolle der zwei Eingabefeldern beim Verlassen des Formulars erfolgt. Bei beiden
Schaltflächen finden Sie den onclick-Eventhandler, worüber die Funktionen maxAnzahl
und zuruecksetzen aufgerufen werden. Schauen wir uns die Funktion maxAnzahl im De-
tail an. In der lokalen Variablen f speichern wir eine Referenz auf das Formularobjekt
(dieses Mal über das Objektfeld, was aber vollkommen egal ist). In der Funktion wird
dann mit den zwei nacheinander notierten if-Abfragen und namentlicher Angabe des zu
testenden Eingabefelds ein Kontrollsystem aufgebaut.
Zuerst wird die Länge des ersten Eingabefelds kontrolliert (if([Link] < 8))).
Gibt der Anwender zu wenige Zeichen ein, erhält er eine Fehlermeldung, der Wert in dem
Formularfeld wird geleert, der Fokus wird auf das Feld gesetzt und mit return wird die
Funktion verlassen. Dabei wird der Wert false zurückgegeben. Die Prozedur wiederholt
sich für das zweite Eingabefeld analog. Sollte die Userid die erlaubte Anzahl der Zeichen
nicht unterschreiten, muss das Passwort die gleiche Hürde nehmen. Nur mit 10 Zeichen
als minimaler Länge. Beachten Sie, dass die beiden Tests nichts miteinander zu tun haben
(kein if-else). Die zweite Überprüfung findet nur statt, wenn die erste Überprüfung kein
Problem entdeckt hat (andernfalls wird die Funktion ja mit return verlassen und die
zweite if-Abfrage wird nicht erreicht). Das können Sie natürlich auch anders lösen und
etwa dem Anwender gleich alle Probleme zusammen anzeigen. Wenn die beiden Prüfun-
gen keine Probleme ermittelt haben, wird das Ziel für die Formulardaten gesetzt und die
Formulardaten werden abgeschickt. Beim Zurücksetzen des Formulars wird zuerst eine
Bestätigung des Anwenders eingeholt und dann entweder das Formular zurückgesetzt
oder nicht. In jedem Fall geben wir aber wieder false zurück, um das Verschicken der
Formulardaten zu verhindern (was ja in der Situation auf gar keinen Fall geschehen darf).
Die per DOM verfügbaren Elemente einer Webseite verfügen in der Regel über die glei-
chen Eigenschaften, die als Attribute bei dem zugrunde liegenden HTML-Tag vorhanden
sind. Dazu kommen noch einige globale Eigenschaften wie style für den Zugriff auf
Style Sheets oder Eventhandler. Dementsprechend verfügt auch ein DOM-Objekt, das
ein Bild in der Webseite repräsentiert, über diese Eigenschaften. Indem Sie die Namen
der Eigenschaften angeben, können Sie lesend und meist auch schreibend darauf zu-
greifen. Beispiel ([Link]):
…
<body><h1>Zugriff auf klassische HTML-Attribute über DOM</h1>
<table><tr>
<td><img src="images/[Link]" alt="Hardware" width="200" /></td>
<td id="info" width="200"></td></tr></table>
<button id="b1">Groß</button><button id="b2">Klein</button>
<button id="b3">Neues Bild</button><button id="b4">Altes Bild</but-
ton>
<script>
248 11 Der DOM – das Mysterium der JavaScript-Welt
with (document) {
getElementsByTagName("img")[0].onmouseover = function() {
getElementById("info").innerHTML =
getElementsByTagName("img")[0].alt;
};
getElementsByTagName("img")[0].onmouseout = function() {
getElementById("info").innerHTML = "";
};
getElementsByTagName("img")[0].onclick = function() {
getElementById("info").innerHTML =
getElementsByTagName("img")[0].src;
};
getElementById("b1").onclick = function() {
getElementsByTagName("img")[0].width = "400";
};
getElementById("b2").onclick = function() {
getElementsByTagName("img")[0].width = "200";
};
getElementById("b3").onclick = function() {
getElementsByTagName("img")[0].src = "images/[Link]";
getElementsByTagName("img")[0].alt = "Kater im Korb";
};
getElementById("b4").onclick = function() {
getElementsByTagName("img")[0].src = "images/[Link]";
getElementsByTagName("img")[0].alt = "Hardware";
};
}
</script></body></html>
Die Webseite in dem Beispiel enthält in einer Tabelle ein Bild und daneben eine leere
Zelle mit einer Id, in der bei gewissen Aktionen des Anwenders Informationen angezeigt
werden sollen (Abb. 11.7). Darunter befinden sich vier Schaltflächen. Bei einem Klick
sollen gewisse Aktionen ausgelöst werden.
Beachten Sie, dass wir in dem Skript explizit mit with arbeiten, denn wir haben hier
genau so einen Fall vorliegen, wo das sinnvoll ist. Wir müssten sonst ohne with bei jeder
Eigenschaft und Methode document voranstellen. In den folgenden Erklärungen notieren
wir aber document zum besseren Verdeutlichen. Beim Bildobjekt, das wir mit document.
getElementsByTagName("img")[0] ansprechen, werden die Eventhandler onmouseover,
onmouseout und onclick registriert. Damit wird beim Überstreichen des Bilds mit der
Maus und bei einem Klick im Bildbereich in der rechten Tabellenzelle eine Information
angezeigt und beim Verlassen des Bildbereichs mit dem Mauszeiger der Anzeigebereich
wieder gelöscht. In mehreren Zeilen sehen Sie, wie man innerHTML nutzt (mit docu-
[Link]("info").innerHTML) und wie auf den Wert des alt-Attributs des
Bilds zugegriffen wird ([Link]("img")[0].alt). Vollkommen
11.6 Das Objekt document 249
analog sehen Sie den Zugriff auf ein weiteres Attribut von img – die URL der Datei (do-
[Link]("img")[0].src).
Die anderen Zeilen zeigen nun noch weitere schreibende Zugriffe auf Attribute eines
HTML-Tags. Hier werden mit den ersten beiden Schaltflächen konkret die Breite des Bilds
über die Eigenschaft width und mit der dritten und vierten Schaltfläche die Werte von src
und alt verändert, was zu einem neuen Bild inklusive neuem Alternativtext führt (Abb. 11.8).
Die ausführenden Funktionen werden jeweils beim Klick auf eine der Schaltflächen
aufgerufen.
Wenn die Bedeutung der Eigenschaften von document in modernen Webseiten ra-
pide abnimmt – wie kann das mit der oben im Kapitel geäußerten Aussage in Überein-
stimmung gebracht werden, dass document neben node den Kern aller modernen Web-
applikationen darstellt? Nun – es gibt ja noch die Methoden. Diese sind zum Teil eben-
falls von der Entwicklung im Web überholt worden (gerade die Methoden zur Gestaltung
der Webseite, aber auch der Ereignisbehandlung), zum Teil sind sie aber auch so elemen-
tar, dass ich das Objekt document so herausheben möchte. Die wichtigsten Methoden des
Objekts document dürften die Methoden zum Schreiben in die Seite und für den Zugriff
auf Bestandteile der Webseite sowie vor allen Dingen das Erstellen von Knoten sein. Das
Objekt document besitzt die folgenden immer noch interessanten Methoden (Tab. 11.9).
Mehrere Methoden von document sind dazu da, dass Sie damit Knoten erzeugen, um Web-
seiten dynamisch per Skript aufzubauen. Um diese Methoden überhaupt zu verstehen,
muss Ihnen aber zum einen das Knotenkonzept im DOM geläufig sein. Das haben wir
schon behandelt, aber das nachfolgende Beispiel vertieft die theoretischen Grundlagen
und wendet die Methoden createAttribute, createElement und createTextNode an. Beispiel
([Link]):
11.6 Das Objekt document 251
…
<body><h1>Zugriff auf DOM-Elemente</h1>
<form><input type="button" value="OK" name="ok" /></form><hr />
<script>
[Link]("ok")[0].onclick = function() {
252 11 Der DOM – das Mysterium der JavaScript-Welt
In dem Beispiel sehen Sie eine Webseite, die bis auf das Grundgerüst und ein Formular
mit einer Schaltfläche keinerlei statische HTML-Strukturen aufweist (Abb. 11.9).
Wenn Anwender jedoch die Schaltfläche anklicken, werden im Körper der Webseite
eine Überschrift der Ordnung 1 mit einem spezifischen Textinhalt auftauchen. Ebenso
ist die Hintergrund- und Vordergrundfarbe der Webseite spezifiziert, obwohl im <body>-
Tag kein Attribut dafür angegeben wurde und auch keine CSS-Regeln verwendet werden
(Abb. 11.10).
In Entwicklertools eines Browsers können Sie schön sehen, wie die tatsächlich vom
Browser verarbeitete Webseite aussieht. Dynamisch wurden neben der Überschrift auch
die Attribute des <body>-Tags hinzugefügt. Wenn man mehrfach die Schaltfläche an-
klickt, wird immer eine weitere Überschrift an den vorhandenen Inhalt angehängt.
Abb. 11.10 Die Entwicklertools eines Browsers zeigen die dynamisch hinzugefügten Strukturen
Schauen wir uns an, wie das gemacht wurde. Zuerst wird mit [Link]-
te("bgcolor") ein Attributknoten (ein node-Objekt) erstellt, der in der Variable hinter-
grundfarbe gespeichert wird. Der Attributknoten steht damit für die Angabe einer
Hintergrundfarbe bei einer Webseite über das Attribut bgcolor des <body>-Tags. Der
Attributknoten repräsentiert bis jetzt noch keinen expliziten Wert, aber in der folgen-
den Zeile wird der Wert dieses Attributknotens mit der Eigenschaft nodeValue gesetzt
([Link] = "blue";). Anschließend erzeugen wir so einen weiteren
Attributknoten für die Vordergrundfarbe mittels des Attributs text und setzen auch dafür
einen Farbwert. Nun sind bis dahin zwar die Attributknoten erzeugt, aber noch nicht der
Webseite hinzugefügt worden! Das muss mit einer expliziten Anweisung erfolgen – der
node-Methode setAttributeNode. Die beiden Attribute werden dem <body>-Tag und
damit der Webseite hinzugefügt. Im nächsten Schritt erzeugen wir mit [Link]-
teElement einen Elementknoten und speichern das resultierende node-Objekt in der Va-
riablen u1 (var u1 = [Link]("h1");).
Der Folgeschritt erzeugt mit [Link] einen Textknoten und spei-
chert auch das resultierende node-Objekt in einer Variablen – text. Jetzt haben wir aber
auch die Situation, dass weder der Elementknoten noch der Textknoten der Webseite
hinzugefügt wurden, noch besteht irgendeine Verbindung zwischen dem Elementknoten
und dem Textknoten. Widmen wir uns dem zweiten Problem zuerst. Offensichtlich ent-
hält der Textknoten den gewünschten Inhalt des Elementknotens. Wie kommt der Inhalt
nun da hinein? Diese Verbindung schaffen wir, indem wir den Textknoten als Kind an
den Elementknoten anhängen. Dazu verwenden wir die node-Methode appendChild (u1.
appendChild(text);). Zum Abschluss müssen wir den nun gefüllten Elementknoten der
254 11 Der DOM – das Mysterium der JavaScript-Welt
Webseite hinzufügen bzw. in den Knotenbaum einfügen. Das machen wir wieder mit
der Methode appendChild über [Link]("body")[0].append-
Child(u1);. Damit ist die dynamisch generierte Seite fertig. Das dynamische Generieren
von Webseiten funktioniert in allen modernen Browsern. Nur ältere Browser machen hier
Ärger. Aber es sollte aufgefallen sein, dass das Verfahren recht aufwendig ist. Mit in-
nerHTML können Sie die Schritte erheblich vereinfachen.
Kommen wir nun zur Klärung der Hintergründe, die eben schon beim dynamischen Er-
zeugen einer Webseite relevant waren, wobei diese noch in einem größeren Umfeld ge-
sehen werden sollten. Wenn Sie im Browser die Webseite als ein strukturiertes Dokument
betrachten, steht Ihnen mit dem node-Objekt ein Objekt zur Verfügung, um über dessen
Member auf den Elementen im Baum zu navigieren und sogar Teile einer Webseite zu
ergänzen, wie wir es gerade gesehen haben. Um so etwas aber machen zu können, be-
nötigen Sie einen Knoten, der bereits angesprochen und explizit verwendet wurde.
11.7.1 Knotenarten
Um effektiv mit einem node-Objekt und auch den Zugriffstechniken auf DOM-Elemente
arbeiten zu können, muss man sich Gedanken um die verschiedenen Arten von Knoten
machen, die in einem Dokument vorkommen können. Denn diese werden im DOM-Kon-
zept hinsichtlich der verfügbaren Eigenschaften deutlich unterschieden. Wir wissen bereits,
dass es etwa Dokumentknoten gibt, die die gesamte Baumstruktur darstellen, Dokument-
fragmentknoten für nur einen Teil der Baumstruktur, Elementknoten als Gegenstück von
einem HTML-Element, Textknoten mit dem Textinhalt eines Elements oder Attributs und
nicht zuletzt Attributknoten, die einem Attribut eines HTML-Elements entsprechen. Uns
sind ja im letzten Beispiel diese unterschiedlichen Knotenarten explizit begegnet.
Der Umgang mit diesem Knotenkonzept ist nun leider etwas sperrig, denn Textknoten
und Attributknoten sind definitiv nicht ganz konsistent in die Konzeption integriert. Für
viele Anwender erscheint es zum Beispiel etwas seltsam, dass man den Textinhalt eines
Elements nicht direkt über eine Eigenschaft von einem Elementknoten erhält, sondern
nur über einen als Kindelement untergeordneten Textknoten drankommt. Und Attribut-
knoten sind insofern eigenartig, da sie nicht direkt als Knoten in der Baumstruktur vor-
kommen, die vor allem durch Elementknoten gebildet wird. Attributknoten sind dem-
entsprechend keine Kinder von Elementknoten, sondern werden als Eigenschaften von
ihnen im DOM-Konzept geführt. Diese Inkonsistenz gefällt vielen Anwendern des DOM
zwar nicht, aber letztendlich wollen und müssen wir mit node-Objekten arbeiten und
wenn man sich diese beiden Besonderheiten merkt, ist das auch gar nicht so schwer.
11.7 Das node-Objekt 255
[Link] Knotentypen
Knoten werden im DOM-Konzept über Nummern gekennzeichnet. Hier sehen Sie die
verfügbaren Knotentypen (Tab. 11.10):
In der Praxis verwendet man in JavaScript beim Zugriff auf eine Webseite meist nur die
Typen 1 bis 3 und vielleicht noch 8 und 9. Die anderen Knotentypen kommen teilweise
auch nur im Rahmen von XML vor (etwa eine PI).
Ein Objekt vom Typ node stellt nun die folgenden Eigenschaften bereit, die vielfach an
den Verwandtschaftsbeziehungen zwischen Knoten orientiert sind (Tab. 11.11):
Die Attribute eines Objekts vom Typ node eröffnen Ihnen viele Möglichkeiten
und sind im Grunde einfach. Aber der Umgang damit ist nicht trivial. Es be-
darf ein abstraktes Vorstellungsvermögen bezüglich des Baums einer Web-
seite. Entwicklertools von Browsern sind hier eine wirklich große Hilfe, um zu-
mindest dieser abstrakten Vorstellung des Baums eine optische Unterstützung
zu geben.
Kommen wir nun zu den Methoden eines Objekts vom Type node (Tab. 11.12).
Betrachten wir ein Beispiel, um rekursiv alle Knoten im Körper eine Webseite zu
durchlaufen ([Link]).
…
<body><h1>Rekursive Ausgabe der Knoten im Body</h1>
<img src="images/[Link]" alt="Bild 1" />
<img src="images/[Link]" alt="Bild 2" />
<div class="fl"><img src="images/[Link]" alt="Bild 3" />
<img src="images/[Link]" alt="Bild 4" />
<div class="fl"><img src="images/[Link]" alt="Bild 5" />
<img src="images/[Link]" alt="Bild 6" /></div></div>
<div id="anzeige"></div> <!-- Der Ausgabebereich -->
<script>
function liste(knten) {
var ausgabe;
ausgabe = '<li>';
11.7 Das node-Objekt 257
switch ([Link]) {
case 1:
ausgabe += [Link] + '-Element<br />';
if ([Link]()) {
ausgabe += '<span class="mark">' + [Link] +
' Knoten</span>';
}
break;
case 3:
var kntenwert = [Link](
/</g, '<').replace(/\n/g, '\\n');
ausgabe += 'Textknoten: ' + kntenwert + '<br />';
break;
case 8:
var kntenwert = [Link](
/</g, '<').replace(/\n/g, '\\n');
ausgabe += 'Kommentarknoten: ' + kntenwert + '<br />';
break;
default:
ausgabe += 'Knotentyp ' + [Link] + ', Wert: ' +
[Link] + '<br />';
}
if ([Link]()) {
ausgabe += '\n<ol>\n';
for (var i = 0; i < [Link]; i++) {
kntenneu = [Link][i];
ausgabe += liste(kntenneu); // Rekursion
}
ausgabe += '</ol>\n';
}
ausgabe += '</li>\n';
return ausgabe;
11.7 Das node-Objekt 259
}
[Link] = function () {
var obj = [Link]("body")[0];
[Link]('anzeige').innerHTML = liste(obj);
}
</script></body ></html >
Sie sehen in der Webseite einige verschachtelte Strukturen mit verschiedenen Knoten-
typen (Abb. 11.11). Es gibt Elementknoten, Textknoten (inklusive Zeilenumbrüche) und
Kommentarknoten. Die Elemente werden mit Stylesheets formatiert, was für die Anzahl
der Knoten keine wirkliche Rolle spielt. Dennoch wird sich die Formatierung auswirken,
denn mit der Klasse fl werden Div-Elemente als display:inline-block formatiert und die
Zeilenumbrüche optisch weggenommen. Die Zeilenumbrüche sind jedoch im DOM
immer noch explizit vorhanden und werden bei der Ausgabe der Knoten auch mitgezählt
und angegeben. Die Funktion liste ist rekursiv aufgebaut und liefert einen Rückgabe-
wert mit einer Auflistung der Knoten in dem als Parameter übergebenen DOM-Objekt.
Wir übergeben das Body-Element in dem Beispiel. In der Funktion werden die einzel-
nen Knoten in eine Liste geschrieben. Aufgrund des Knotentyps ([Link]) kön-
nen wir die Art des Knotens unterscheiden. Bei Textknoten wissen wir ja, dass der Inhalt
das erste Kindelement ist bzw. über nodeValue zur Verfügung steht. Das gilt auch für
Kommentarknoten. Bei Elementknoten ist die Anzahl der Kindknoten interessant und die
erhalten wir mit childNodes und dessen Eigenschaft length ([Link]).
Nun ist es von entscheidender Bedeutung, ob ein Knoten einen Kindknoten besitzt und
dieser wieder Elementknoten mit Kindelementen, denn dann müssen wir rekursiv in die
Tiefe gehen. Das machen wir in der Schleife, in der wir das Knoten-Array durchlaufen
und jeden aktuellen Knoten der Funktion liste übergeben. Das sorgt für eine rekursive
Abarbeitung des Baums.
11.8 Fenstertechniken
Bis vor wenigen Jahren war das Objekt window, das die oberste Ebene der sichtbaren
Bestandteile eines Browsers (globales Objekt) repräsentiert, eines der wichtigsten Ob-
jekte bei Webanwendungen. Die direkte Arbeit mit den Member von window hat jedoch
in der modernen Webprogrammierung stark abgenommen. DHTML und Ajax haben
viele Techniken überflüssig gemacht, die man lange Zeit mit Member von window um-
gesetzt hat, etwa das Öffnen eines neuen Fensters. Die Möglichkeit, neue Fenster, die
oft als Popups konstruiert wurden, mit dem window-Objekt zu erstellen und zu öffnen,
wurde – gerade auf unseriösen Seiten und von Werbetreibenden – auch massiv miss-
braucht und das hat dazu geführt, dass viele Anwender mit Popup-Blockern arbeiten.
Aber auch Standardeigenschaften vieler Browser unterbinden das Öffnen oder Verändern
von Fenstern mit JavaScript. Dennoch zählt das Objekt immer noch zu den Kernbestand-
teilen des DOM und ist dessen globales Objekt.
Ein Unterobjekt von window mit analogen Member ist frame. Frames und die Fra-
metechnik zur optischen Aufteilung des Browserfensters sind schon seit etwa dem Jahr
2000 veraltet. So hat auch das Objekt heute kaum noch eine Bedeutung. Allerdings muss
man sogenannte IFrames (Inline-Frames) gesondert betrachten. Denn für diese gibt es
auch – und gerade – in der modernen RIA-Programmierung wichtige Anwendungen und
auch die werden als frame-Objekt repräsentiert.
Des Weiteren existieren noch ein paar weitere Unterobjekte von window, die ver-
schiedene Fenstertechniken gestatten. Dazu zählen die Weiterleitung zu anderen Adres-
sen, der Zugriff auf die Historie und Statuszeile des Browsers und der Zugriff auf den
Bildschirm des Besuchers. Da jedes Browserfenster automatisch einem window-Objekt
entspricht, haben Sie in jedem JavaScript auf jeden Fall diese Member für dieses Ob-
jekt zur Verfügung. Schauen wir uns zunächst die Member von einem window-Objekt in
einem Überblick an.
Die Eigenschaften von window reichen von Statusinformationen, ob ein Fenster mit
einer Referenz wieder geschlossen ist (closed) über den Standardzustand (defaultStatus),
11.8 Fenstertechniken 261
Einige der Methoden sind äußerst wichtig (setTimeout), andere werden noch gelegentlich ein-
gesetzt (alert, prompt, confirm, open), aber viele Methoden haben wie die Eigenschaften von
window massiv an Bedeutung verloren. Single Page-Applikationen mit DHTML und Ajax machen
diese Techniken seit Jahren obsolet.
Wir schauen uns ein paar Member eines Fensters an, die in der Praxis Bedeutung haben.
Beispiel
<a href="javascript:[Link]()">Zurück</a>
Alternativ rufen Sie mit einem Eventhandler die Methode direkt in JavaScript mit
[Link]() auf. So kurz und knapp das Beispiel ist, so genial ist dessen Anwendungs-
262 11 Der DOM – das Mysterium der JavaScript-Welt
möglichkeit. Wenn ein Besucher auf eine Seite verzweigt und er möchte wieder zu der
zuletzt besuchten Seite zurück, wird ihn ein Hyperlink oder eine Schaltfläche mit der
Methode an Hand der Historie dorthin führen. Die Vorteile gegenüber einem statisch ko-
dierten Hyperlink sind groß – Sie müssen nicht wissen, von wo der Besucher kam. Die
Syntax ist ohne Änderungen (höchstens vielleicht der Text) universell für alle Situatio-
264 11 Der DOM – das Mysterium der JavaScript-Welt
nen einsetzbar, in denen der Anwender wieder zu einer gerade besuchten Seite zurück
möchte. Das kann man in vielen Navigationsfällen hervorragend gebrauchen.
Das history-Objekt stellt auch das Gegenstück zu der Methode back zur Verfügung –
die Methode forward. Die Methode macht einen Rückschritt in der Historie rückgängig.
Sie geht also in der Historie vorwärts, wenn das möglich ist. Ebenso können Sie die
Methode go verwenden. Mit dieser laden Sie eine beliebige Seite der History-Liste. Sie
übergeben der Methode als Parameter die Seitenzahl. Bezugspunkt ist dabei die aktuelle
Seite. Sie können die Anzahl mit Vorzeichen verwenden und damit also sowohl zurück-
gehen als auch vorwärts, wenn Sie vorher zurückgegangen sind.
Beispiel
Alle Parameter sind optional, wie oben in der Tabelle schon beschrieben. Wenn sie aber
verwendet werden, beschreibt die URL eine beliebige Datei, die in dem Fenster oder Tab
angezeigt werden soll. Es kann eine HTML- oder Grafikdatei sein, aber auch jede andere
Ressource, welche der Browser darstellen kann oder die beim Anwender gespeichert
werden soll. Dabei gelten die üblichen Regeln für Verweise. Die Adresse muss in An-
führungszeichen stehen. Ein neues leeres Fenster wird erzeugt, wenn Sie anstelle einer
URL-Adresse einfach "" angeben oder eben den Parameter weglassen.
Der Fenstername ist ein frei wählbarer Name. Bei der Namensvergabe gelten die glei-
chen Regeln wie bei Variablennamen. Sie können den Namen eines Fensters im weiteren
Ablauf des Skripts bei verschiedenen Aktionen und auch über die Eigenschaft name ver-
wenden.
Die Angabe von Optionen ist optional. Entfallen sie, wird beim Öffnen eines Fens-
ters dieses in der Regel in der gleichen Größe wie das aufrufende Fenster oder in einer
scheinbar zufälligen Größe dargestellt werden und die Position (wenn kein Vollbild vor-
liegt) mehr oder weniger zufällig relativ zur Position des Elternfensters gesetzt. Oder
aber die Folgefenster von Browsern, die mehrere Unterfenster (Tabs) verwalten können,
werden einfach übereinander in Form von überlagernden oder parallelen Registern an-
geordnet. Dieses Verhalten lässt sich jedoch individuell anpassen, wozu einige Member
des window-Objekts genutzt werden können. Wenn Sie allerdings Optionen angeben,
muss der gesamte Ausdruck in Anführungszeichen stehen oder aus Strings bestehen, die
11.8 Fenstertechniken 265
mit dem Plus-Operator zu einem String zusammengefügt werden. Die Optionen bilden
also einen einzigen Parameter. Angaben zu einzelnen Optionen werden durch Kommata
getrennt. Beispiel für das Öffnen eines Fensters mit Optionen zur Angabe der Höhe und
Breite eines Fensters:
Beispiel
Die möglichen Optionen von open sollen an dieser Stelle nicht genauer ausgeführt
werden, da einerseits selbsterklärend und andererseits mittlerweile in der Praxis obso-
let. Sinnvoll (wenngleich nicht zwingend) ist es, bei der Erstellung einer Fensterobjekt-
instanz eine Referenz auf dieses Objekt in einer Variablen zu speichern. Sie können dann
an späterer Stelle noch darauf zugreifen (zum Beispiel zum Schließen des Fensters über
die Methode close) oder bei der nachträglichen Veränderung der Größe oder Position
eines Fensters.
• Sie können von verschiedenen Domains oder Einstiegsseiten in ein Webprojekt Be-
sucher auf eine zentrale Website führen. Dazu müssen Sie bloß eine entsprechende
[Link]- oder [Link]-Anweisung beim Laden einer Webseite ausführen
(Landing page).
• Einem Projekt können Sie auf der Einstiegsseite einen Test voranstellen, ob bei einem
Browser JavaScript aktiviert ist. Dabei führen Sie mit JavaScript eine Weiterleitung zu
einem anderen URL aus. Funktioniert der, muss auch JavaScript aktiviert sein. Und
zusätzlich gibt es wieder eine solche Weiterleitung für den Fall von deaktiviertem Ja-
vaScript, die den Besucher automatisch zu einem Projekt führt, das ohne JavaScript
auskommt. Wie wir schon gesehen haben, funktioniert eine solche Weiterleitung ohne
JavaScript mit folgendem Meta-Tag (<meta http-equiv="refresh" content="[Zei-
t];URL=[Ziel]">).
• In einer Browserweiche überprüfen Sie den Browser eines Anwenders und leiten dann
zu passenden Seiten weiter. Ebenso könnten Sie die Bildschirmauflösung abfragen.
Es repräsentiert die komplette URL der gerade in einem Browser angezeigten Webseite.
Jede der Eigenschaften des location-Objekts steht für einen anderen Bestandteil des
URL. Wenn man die allgemeine Struktur einer URL zugrunde legt, sieht sie so aus:
Beispiel
protocol://user:passwort@host:port/pathname#hash?search
Jeder der einzelnen Bestandteile der URL – wir vernachlässigen die Zugangsdaten –
steht als Bezeichner einer Eigenschaft des location-Objekts zur Verfügung und kann di-
rekt verwendet werden (Tab. 11.14).
Schauen wir uns einige vollständige Beispiele an, die mit location sinnvolle Aktionen
ausführen.
…
<body><h3>Zugriff auf die Informationen in location</h3>
<button>Zerlegen von location</button></body>
<script>
function locationZerlegen() {
alert("protocol: " + [Link] + "\nhost: " +
[Link] + "\nport: " + [Link] + "\npathname: " +
[Link] + "\nhash: " + [Link] + "\nsearch: " +
[Link] + "\nhref: " + [Link] + "\nhostname: " +
[Link]);
}
[Link]("button")[0].onclick = locationZerle-
gen;
</script></html>
Mit der Schaltfläche in der Webseite wird die Funktion locationZerlegen aufgerufen.
Darin werden die besprochenen Eigenschaften von location ausgegeben. Beachten Sie,
dass nicht jeder Browser alle Eigenschaften mit Werten belegt und die Art des Aufrufs
von Bedeutung ist (etwa über file oder http). Die search-Eigenschaft können Sie belegen,
wenn Sie etwa an die URL der Datei einfach ?user=meier oder etwas der Art anhängen.
Dieses Problem lösen sogenannte Single page- Webapplikationen, die (wie der Name schon sagt)
nur aus einer Webseite bestehen und dynamisch alle neuen Daten laden. Die Seite und das Java-
Script bleiben also permanent erhalten.
Wenn Sie den Wert einer Variablen in mehreren Webseiten benötigen, können Sie diesen
auf verschiedene Weise beim Wechsel einer Seite weitergeben:
• Sie können mit einem Cookie arbeiten. So gehen viele Websites vor, aber Cookies
sind oft deaktiviert.
• Sie können mit den neuen Möglichkeiten von HTML5 (Local Data Storage und Ses-
sion Data Storage oder clientseitige Datenbanken) arbeiten.
• Sie weisen den Wert einer Variablen einem Formularfeld (am besten versteckt) zu
und schicken ihn zum Server. Wenn Sie sowieso Daten in einem Formular entgegen-
nehmen, ist das besonders naheliegend. Oder Sie schicken direkt die Variablenwerte
zum Server. Der Server gibt den Wert dann zurück in die neue Seite. Die Lösung er-
fordert aber, dass Sie eben auch auf Serverseite programmieren können.
• Sie hängen den Wert der Variablen samt einem Bezeichner an den URL der neu auf-
gerufenen Seite an. Damit können Sie diese Werte auch in Lesezeichen speichern.
Mit der vierten Variante sind wir genau beim Thema, das wir jetzt angehen. Uns interes-
siert dazu die Eigenschaft search, denn darin steht normalerweise der Inhalt von Such-
abfragen inklusive dem vorangestellten Trennzeichen?. Der Suchstring besteht aus Va-
riablen und Wertepaaren, jedes Paar durch & getrennt. Angenommen, Sie wollen nun die
Variable antwort mit dem Wert 42 an die Datei [Link] übergeben, müssen Sie nur
einen Link (hier relativ) so zusammensetzen:
Listing 1.32 Das Anhängen einer Variablen an den URL
Beispiel
[Link]?antwort=42
268 11 Der DOM – das Mysterium der JavaScript-Welt
Dieses Zusammensetzen des URL können Sie natürlich von Hand vornehmen. Dabei
müssen Sie jedoch beachten, dass Sie Sonderzeichen maskieren, denn diese sollten nicht
direkt in einem Pseudo-URL verwendet werden. Dazu gibt es die Standardfunktionen
escape, encodeURI oder am besten encodeURIComponent.
Aber wenn Sie mit einem kleinen Trick arbeiten, brauchen Sie sich die Arbeit des
Maskierens und des manuellen Zusammensetzens des Pseudo-URL gar nicht zu machen.
Verwenden Sie einfach versteckte Formularfelder, die mit den zu übergebenden Wer-
ten gefüllt werden, und rufen Sie eine Folgeseite mit der submit-Methode und der Ver-
sandmethode get sowie der Angabe der Zieldatei im action-Parameter auf. Dann wird
der Browser automatisch die übergebenen Daten maskieren. Betrachten wir eine Seite, in
der Variablen mit gewissen Werten versehen werden, die dann an eine neue Seite weiter-
gereicht werden sollen ([Link]):
…
<script>
var a = [Link]();
var b = "ß%$§&";
function neueSeite() {
[Link] = a;
[Link] = b;
[Link]();
}
</script></head>
<body><h3>Daten an eine neue Seite weitergeben</h3>
<form name="f1" action="[Link]">
<input type="hidden" name="a"><input type="hidden" name="b"></form>
<a href="javascript:neueSeite()">Daten an eine neue Seite</a>
</body></html>
In dem Skript werden zwei Variablen mit Werten belegt. Die Variable b bekommt be-
wusst Sonderzeichen zugewiesen, um den Effekt der Maskierung zu zeigen. In dem
Formular finden Sie ausschließlich zwei versteckte Felder. Der Anwender bekommt
also von dem gesamten Formular nichts mit und sieht nur einen gewöhnlichen Hyper-
link. Beachten Sie, dass die Namen der Formularfelder wie die Bezeichner der Variab-
len gewählt wurden. Damit übergeben wir bereits mit den Namen der Formularfelder
die Variablenbezeichner. Das ist nicht zwingend, aber sinnvoll. Im Kopf des Formulars
sehen Sie den Namen der Datei, an die die Daten beim Verschicken des Formulars ge-
sendet werden (action="[Link]"). Außerhalb von dem Formular sehen eine In-
line-Referenz zum Aufruf der Funktion neueSeite. In dieser Funktion werden den ver-
steckten Feldern die Werte der Variablen zugewiesen. Mit [Link]();
werden die Daten des Formulars dann abgeschickt und damit die über action angegebene
Seite in den Browser geladen. Schauen wir uns diese Seite an, in der die Daten wieder
ausgelesen werden können ([Link]):
11.8 Fenstertechniken 269
…
<script>
function werte() {
retur = new Array();
[Link]("Das steht in der search-Eigenschaft: " +
[Link] + "<br />");
splt1 = [Link](1);
[Link]("Beseitigen des ?: " + splt1 + "<br />");
splt2 = [Link]("&");
for ( i = 0; i < [Link]; i++) {
[Link]("Das steht in splt2[" + i + "]: " +
splt2[i] + "<br />");
splt3 = splt2[i].split("=");
[Link]("Das steht in splt3[0]: " + splt3[0] + "<br />");
[Link]("Das steht in splt3[1]: " + splt3[1] + "<br />");
[Link]("Das ergibt decodeURIComponent(splt3[1]): "
+ decodeURIComponent(splt3[1]) + "<br />");
retur[2 * i] = splt3[0];
retur[2 * i + 1] = decodeURIComponent(splt3[1]);
}
[Link](
"<hr><h4>Resultate für die einzelnen Wertepaare</h4>");
return retur;
}
</script></head>
<body><h3>Daten von einer Seite übernehmen</h3>
<h4>Zerlegung Schritt für Schritt</h4>
<script>
wertepaar = werte();
for ( i = 0; i < [Link]; i++) {
if (i % 2 == 0) [Link]("Variablenname: ");
else [Link](" - Wert: ");
[Link](wertepaar[i]);
if (i % 2 != 0) [Link]("<br />");
}
</script></body></html>
Die zentrale Funktionalität, um die Werte der Variablen wieder aus der URL auszulesen,
steckt in der Funktion werte. Die Ausgaben in der Funktion werden für die praktische
Anwendung nicht von Interesse sein. Sie sollen nur die schrittweise Extrahierung ver-
deutlichen. Im Grunde ist nur der Rückgabewert der Funktion interessant. Dazu wird die
Variable retur als Array eingeführt (retur = new Array();). Darin wird der Rückgabe-
wert der Funktion gespeichert. Zunächst zeigen wir an, was beim Aufruf der Seite in lo-
[Link] steht. Danach wird das führende Fragezeichen beseitigt und das Ergebnis
in der Variablen splt1 zwischengespeichert (splt1 =[Link](1);). Die
270 11 Der DOM – das Mysterium der JavaScript-Welt
folgende Zeile zeigt das Resultat. Das verbleibende Resultat wird am kaufmännischen
Und in ein Array mit Namen splt2 aufgespalten (splt2 = [Link]("&");). In der folgen-
den Schleife werden die Datenfeldelemente von splt2 ausgegeben und dann bei jedem
Durchlauf noch einmal splt (am Gleichheitszeichen – splt3 = splt2[i].split("=");). Die
verbleibenden Datenfeldelemente enthalten immer abwechselnd den Variablennamen
und dann den zugehörigen Wert.
In dem Beispiel wenden wir auch die Funktion decodeURIComponent an, um den
Wert einer Variablen wieder zu dekodieren. Die wichtigsten Stellen sind retur[2*i] =
splt3[0]; und retur[2*i + 1] = decodeURIComponent(splt3[1]);, womit das Array auf-
gebaut wird, das nach Abarbeiten der Schleife mit return retur; zurückgegeben wird. In
dem zweiten Skript-Container wird die Funktion werte aufgerufen und der Rückgabe-
wert der Variablen wertepaar zugewiesen (wertepaar = werte();). Damit ist wertpaar
ein Array, das danach mit einer Schleife durchlaufen wird (for(i = 0; i < wertepaar.
length; i++){). Darin werden zwar nur der jeweilige Variablenname und der zugehörige
Wert ausgegeben, aber Sie erkennen, wie Sie an die Variablenbezeichner und die jeweili-
gen Werte durch Aufruf der Funktion werte kommen.
Wenn Sie in die URL einen JSON-String schreiben, können Sie sich die Sache
vereinfachen.
Wenn der Server das Fragezeichen nicht als Trennung des Query ansieht,
funktioniert das Beispiel nicht. Dann findet der Server die aufgerufene Datei
nicht, denn der Query wird noch als Teil des Dateinamens gesehen. Das ist bei
einigen Serverkonfigurationen, die aber in der Praxis eher selten vorkommen.
Beispiel
setTimeout([Link], 2000); ◄
11.8 Fenstertechniken 271
Frames stehen für die Aufteilung des Anzeigebereichs eines Browsers in einzelne Seg-
mente, die unabhängig mit Inhalt gefüllt werden können. Frames zählen jedoch schon
seit zig Jahren zu den veralteten Webtechniken. Aber das bedeutet nicht, dass es keine
Webseiten mit Frames mehr gibt und sie nicht in vereinzelten Situationen auch noch
in neuen Webpräsenzen sinnvoll sein können. Insbesondere sogenannte Inline-Frames
oder kurz IFrames erfreuen sich großer Beliebtheit und dies, obwohl man sie kaum
noch sieht! Das scheint natürlich ein Widerspruch zu sein. Aber das scheint nur so!
Denn die Inline-Frames werden nicht mehr zur Anzeige von Inhalten, sondern zur un-
sichtbaren Zwischenspeicherung von Inhalten verwendet. Viele Frameworks und APIs
von Google arbeiten mit solchen (unsichtbaren) Inline-Frames, was Sie mit einer Ana-
lyse des Quellcodes und der Suche nach dem Token frames schnell sehen können. Aber
auch viele der anderen modernen JavaScript-Frameworks nutzen diese Möglichkeit, um
Daten zwischenzuspeichern. Es scheint derzeit definitiv die beste Möglichkeit zu sein,
um Daten in gewissen Situationen im Client temporär vorzuhalten. Selbstverständlich
können Sie mit JavaScript auf Frames zugreifen und diese beeinflussen. Dazu steht Ihnen
das Objekt frame – ein Unterobjekt von window – zur Verfügung. Darüber haben Sie
vollen Zugriff auf Frames (unterteilte Fensterstrukturen), also auf ein Frameset und seine
Frame-Fenster, die in einer HTML-Datei definiert sind. Ein frame-Objekt wird auto-
matisch erzeugt, wenn der Browser ein Frameset oder ein IFrame in einer HTML-Datei
vorfindet. Das frame-Objekt steht dann in der Datei zur Verfügung, in der das Frameset
oder IFrame definiert wird, sowie in allen Dateien, die in einem Frame-Fenster des Fra-
mesets angezeigt werden. Der Zugriff auf frame-Objekte erfolgt über die üblichen Wege
wie Objektfelder, eine Id, einen Namen oder den Tag-Namen. In dem Objektfeld frames
werden die einzelnen frame-Objekte abgelegt. Jedes Frame in einer Frameset-Webseite
wird also in einem Array-Element gespeichert. Da das Objektfeld frames window unter-
geordnet ist, kann ein Frame samt seiner Member über die Punktnotation angesprochen
werden:
Beispiel
[Fensterreferenz].[frame].[Eigenschaft/Methode()] ◄
…
<link rel="stylesheet" type="text/css" href="lib/css/[Link]" />
<script src="lib/js/[Link]"></script></head>
<body><h1>Unsichtbare IFrames als Datenspeicher</h1>
<iframe src="frame1_1.html"></iframe>
<iframe src="frame1_2.html"></iframe>
<button>Bild 1</button><button>Bild 2</button>
<button>Bild 3</button><button>Bild 4</button><button>Bild 5</button>
<button>Text 1</button><button>Text 2</button><hr/>
<div id="td1"><img id="b1" /></div> <div id="td2"></div>
</body></html>
In dem Beispiel arbeiten wir mit zwei IFrames, in denen zwei HTML-Dateien geladen
werden. Diese IFrames werden mit CSS unsichtbar gemacht (Abb. 11.12), was in der
Datei [Link] so umgesetzt wird:
…
iframe { display:none; }
In den Entwicklertools eines Browsers kann man aber deutlich erkennen, dass die IFra-
mes im DOM vorhanden und welche Dateien darin geladen sind (Abb. 11.12). Dieses
Mal arbeiten wir mit einer externen JavaScript-Datei [Link] und dort erfolgt der Zu-
griff auf den Inhalt von IFrames:
11.8 Fenstertechniken 273
function bild(i) {
[Link]("b1").src =
[Link][0].[Link][i].src;
}
function text(i) {
[Link]("td2").innerHTML = [Link][1].document.
getElementsByTagName("h1")[i].innerHTML;
}
function init() {
for (let i=0; i < [Link]("button").length;
i++) {
[Link]("button")[i].onclick = function () {
[Link](i);
if (i < 5) bild(i);
else text(i - 5);
}
}
}
[Link] = init;
Für die sieben Schaltflächen in der Webseite werden onclick-Eventhandler registriert. Bei
den ersten fünf wird die Funktion bild mit einem spezifischen numerischen Parameter
aufgerufen. Die letzten beiden Schaltflächen rufen die Funktion text ebenfalls mit einem
spezifischen Parameter auf.
Sie erkennen, dass sich Codestrukturen zum Registrieren eines Eventhandlers mehrfach wieder-
holen und sowohl die Bezeichner mit der Nummerierung als auch die Indizes der Arrays auf-
einander abgestimmt sind. Da drängt sich eine Schleife auf, wie es hier gemacht wird. Doch Vor-
sicht, wenn Sie hier mit Schleifen arbeiten wollen. Das sind Funktionsreferenzen und dement-
sprechend bekommen Sie Probleme, wenn Sie innerhalb der anonymen Funktionen nicht sauber
auf die Zählvariable zugreifen. Die referenzierten Funktionen werden vom Interpreter zum Zeit-
punkt der Referenzierung nicht (!) verarbeitet und dementsprechend steht der Zähler der Schleife
dort bei einer unsauberen Programmierung nicht zur Verfügung. Wenn Sie bei der Deklaration der
Schleifenvariablen var statt let verwenden, werden Sie dieses Problem bekommen. Dann wird der
Interpreter erst beim tatsächlichen Aufruf der Funktion den Funktionscode verarbeiten und alle (!)
Funktionsaufrufe von bild und text werden mit dem Wert 7 ausgeführt, was natürlich fehlerhaft ist.
In der Funktion bild sehen Sie, dass der Eigenschaft src des Bildobjekts mit der Id b1 ein
Wert zugewiesen wird. Das Bildobjekt wird von dem DOM über das <img>-Tag in der
HTML-Datei bereitgestellt. Der zugewiesene Wert kommt aber aus dem ersten IFrame.
Mit [Link][0] sprechen wir ihn an und erhalten damit ein DOM-Fensterobjekt.
Und dann stellt document wie gehabt die Objektrepräsentation der Webseite darin dar.
Schauen wir uns den Aufbau der in dem IFrame geladenen Datei frame1_1.html an, denn
dieser Aufbau ist massiv für die Art unseres Zugriffs von Bedeutung:
274 11 Der DOM – das Mysterium der JavaScript-Welt
Abb. 11.12 Die IFrames sind unsichtbar, aber in den Entwicklertools als geladene Struktur zu
sehen
…
<body><img src="images/[Link]" /><img src="images/[Link]" />
<img src="images/[Link]" /><img src="images/[Link]" />
<img src="images/[Link]" /></body></html>
11.8 Fenstertechniken 275
Sie erkennen, dass darin fünf Grafiken geladen werden. Und deshalb kommen wir
über das Objektfeld images in der Funktion bild an die einzelnen Grafiken in der Web-
seite, die im IFrame geladen ist, und können diese dann in der eigentlichen Webseite an-
zeigen (Abb. 11.13).
Die Funktion text hat einen analogen Aufbau. Nur wird hier der Textinhalt einer
Überschrift der Ordnung h1 aus einer Datei im zweiten IFrame ausgelesen und in eine
Tabellenzelle geschrieben. Der Aufbau der Datei, die im zweiten IFrame geladen wird,
sieht so aus (frame2_2.html):
Abb. 11.13 Ein Bild wurde aus dem IFrame-Datenspeicher in die Webseite geholt
276 11 Der DOM – das Mysterium der JavaScript-Welt
…
<body><h1>[Link]</h1><h1>[Link]</h1></body></html>
Die Wahl der Überschriften vom Typ h1 zur Strukturierung des Inhalts ist willkürlich.
Sie können beliebige Strukturen hier nehmen. Etwa auch XML. Sie sehen ja, dass in
dem Beispiel bewusst der Inhalt einer Überschrift einer Tabellenzelle zugewiesen wird.
Sie müssen bloß eindeutig die gewünschte Textinformation in der IFrame-Datei adressie-
ren können.
Diejenigen Leser, die sich bereits mit Ajax auskennen, werden hier gewisse Ähnlichkeiten zu
den Möglichkeiten sehen, die Ihnen Ajax bietet. Zumindest dann, wenn man über die IFrames
im Hintergrund Daten vom Server holt. In der Tat hat man vor Ajax sehr oft IFrames (und auch
Frames) eingesetzt, wenn man eine Webseite nicht vollständig austauschen wollte. Aber eben auch
heute wird diese Technik noch eingesetzt, wenngleich meist in etwas abgewandelter Form (Vor-
halten von binären Ressourcen wie Bildern oder allgemein Multimediadateien, um darüber bei Be-
darf eine schnellere Anzeige zu gewährleisten), aber manchmal auch wirklich als explizite Alter-
native zu Ajax.
…
<body><h1>IFrames als Datenspeicher für Benutzeraktionen</h1>
<iframe></iframe><hr /><button>1</button>
<button>2</button><button>3</button><div id="t1"></div></body>
<script>
function text(i) {
var text;
switch (i) {
case 0: text = [Link](); break;
case 1: text = new Date().toLocaleString(); break;
case 2: text = new Date().getSeconds();
}
[Link]("t1").innerHTML = text;
var u6 = [Link]("h6");
var textknten = [Link](text);
[Link](textknten);
[Link][0].document.
getElementsByTagName("body")[0].appendChild(u6);
}
11.9 Browserauswertung und das Objekt navigator 277
Sie sehen wieder einen versteckten IFrame, dessen Inhalt man in den Entwicklertools
des Browsers verfolgen kann (Abb. 11.14). Bei einem Klick auf eine der Schaltflächen
in der Webseite wird die Funktion text aufgerufen. Dort wird aufgrund des übergebenen
Parameters mit switch-case entschieden, welcher Text in der Webseite angezeigt wird.
Aber das ist der für uns hier nicht wichtige Part. Die entscheidende Stelle ist window.
frames[0].[Link]("body")[0]. appendChild(u6);. Wir hän-
gen hier einen Textknoten an den Körper der Datei, die in dem IFrame geladen ist. Als
Inhalt des Textknotens wählen wir hier den angezeigten Text in der Webseite, der in
eine h6-Struktur eingefügt wurde. Aber Sie können natürlich beliebige Informationen
so speichern. Entscheidend ist, dass die Datei im IFrame als Protokolldatei verwendet
wird. Natürlich können Sie hier auch leicht JSON- oder XML-Daten verwenden. Die ge-
speicherten Daten können Sie wieder analog des vorherigen Beispiels aus einer Webseite
heraus auslesen, wenn das sinnvoll ist. Und natürlich können Sie die Daten auch zum
Server schicken, wobei da die lokale Zwischenspeicherung in einem IFrame eigentlich
der falsche Weg ist.
In der Praxis ist die genaue Kenntnis über den Browser und das Betriebssystem des Be-
suchers in vielen Fällen von Bedeutung. Beim Datenaustausch zwischen Webserver und
Browser werden mit dem HTTP-Protokoll viele Hintergrundinformationen ausgetauscht
und unter anderem eben auch zahlreiche Details über den Browser und die Plattform des
Besuchers. Diese Informationen können dann auf dem Webserver verwertet werden. Aber
bereits aufseiten des Clients kann man ebenfalls mit diesen Informationen arbeiten. Denn
über das DOM-Objekt navigator können Sie aus JavaScript heraus auf zahlreiche Informa-
tionen über das Clientprogramm und die Plattform eines Besuchers zugreifen. Wohlgemerkt
sind das jedoch nur lesende Zugriffe, im Gegenteil zu den meisten Eigenschaften von
Fensterobjekten und dessen Bestandteilen, die auch per JavaScript gesetzt werden können.
Beachten Sie, dass das Objekt navigator im DOM nicht window untergeordnet ist.
Die Informationen zum Browser und zur Plattform bilden einen eigenständigen
DOM-Zweig.
278 11 Der DOM – das Mysterium der JavaScript-Welt
Mit einem einfachen Skript können wir uns alle Member von navigator ausgeben lassen.
Dazu verwenden wir einfach eine for…in-Schleife und die Tatsache, dass alle Objekte in
JavaScript als Arrays bzw. Hash-Listen verfügbar sind. Das gilt damit natürlich auch für
sämtliche DOM-Objekte und diese Möglichkeit wollen wir jetzt nutzen ([Link]).
…
<body><h1>Das navigator-Objekt</h1>
<script>
for (let i in navigator) {
[Link](i + ": " + navigator[i] + "<br />");
};
</script>
</body></html>
11.9 Browserauswertung und das Objekt navigator 279
Abb. 11.15 Über navigator kommen Sie an alle relevanten Daten zu einem Browsers
Sie sehen in der Webseite nach dem Laden des Beispiels alle verfügbaren Member
eines Browsers (Abb. 11.15), wobei Sie bei manchen Eigenschaften wieder selbst Ob-
jekte vorliegen haben (etwa geolocation), die Sie aber ebenfalls auf diese Weise durch-
laufen können (gegebenenfalls rekursiv). Beachten Sie jedoch, dass sich diese Angaben
in verschiedenen Browsern erheblich unterscheiden. Das Objekt navigator stellt in den
meisten Browsern nur die Methode javaEnabled bereit, mit der Sie testen können, ob
280 11 Der DOM – das Mysterium der JavaScript-Welt
der Browser eines Anwenders Java-fähig ist (true) oder nicht (false) und die mittlerweile
kaum noch interessant ist. Eventuell weiter verfügbare Methoden sind entweder depre-
cated oder browserabhängig. Wichtiger als die Methoden sind die Eigenschaften von na-
vigator. Hier gibt es auch verschiedene browserabhängige Eigenschaften, aber die nach-
folgend notierten sind fast immer verfügbar (Tab. 11.15):
Ausnahmebehandlung
12
Obwohl man mit Ausnahmebehandlung oft auf das Auftreten von Fehlersituationen (oder
genauer – nichtplanbaren Situationen) in einem Skript reagieren möchte, ist die grund-
sätzliche Idee des Konzepts nicht darauf beschränkt und wie gesagt eng verwandt mit
der Reaktion auf Ereignisse. Auch beim Auftreten einer Ausnahme wird eine ganz be-
stimmte Reaktion durchgeführt werden (müssen). Nur ist das Ignorieren einer Ausnahme
im Gegensatz zum Vernachlässigen eines Ereignisses nicht ohne Konsequenzen möglich.
Auch werden Ausnahmen viel, viel seltener auftreten als Ereignisse, was ob der zwin-
genden Reaktion auch unabdingbar ist.
Besprechen wir, was eine Ausnahme im Verständnis von JavaScript darstellt. Die prin-
zipielle Idee baut massiv auf der OOP auf. Auch Ausnahmen sind Mitteilungsobjekte.
Wir haben das Konzept von Ereignisobjekten und deren Verwertung schon gesehen. Was
© Der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH, ein 281
Teil von Springer Nature 2025
R. Steyer, Programmieren mit JavaScript, [Link]
282 12 Ausnahmebehandlung
passiert da? Wenn während der Ausführung eines Programms oder Skripts eine „be-
merkenswerte“ Situation eintritt, wird vom ausführenden System automatisch ein Ereig-
nisobjekt (Mitteilungsobjekt) erzeugt. Solch ein Mitteilungsobjekt enthält alle relevanten
Informationen, was für eine Situation gerade aufgetreten ist, wo und warum. Neben den
klassischen Ereignissen wie das Abschicken von Formulardaten, die grundsätzliche Be-
wegung der Maus oder das Drücken einer Taste auf der Tastatur könnten „bemerkens-
werte“ Situationen desgleichen die fehlerhafte Erzeugung eines Objekts, ein mathema-
tischer Fehler bei einer Berechnung oder etwas Ähnliches sein. Für das Entstehen von
solchen Mitteilungsobjekten ist es nun erst einmal egal, um was für einen genauen Typ
es sich handelt. Im Laufzeitsystem von JavaScript wird die Erzeugung von solchen Ob-
jekten vollkommen automatisch geschehen. Dieser Automatismus kann weder vom Pro-
grammierer beim Erstellen des Programms beziehungsweise Skripts noch Anwender
bei der Ausführung beeinflusst bzw. verhindert werden. Es handelt sich wie gesagt um
essenzielle Kernelemente einer bestimmten Programm- bzw. Laufzeitumgebung (gleich-
wertig solcher Verfahren wie der automatischen Typumwandlung und Ähnliches). Aus-
nahmen beziehungsweise Exceptions bezeichnen also Situationen zur Laufzeit eines
Skripts oder Programms, die eine unmittelbare Reaktion und damit die Generierung
von passenden Mitteilungsobjekten erzwingen. Solche Ausnahmen können entweder
durch den JavaScript-Interpreter automatisch ausgeworfen werden, wenn es in einer be-
stimmten Situation notwendig ist, oder aber von dem Programmierer selbst, wenn es er-
forderlich erscheint. Das Resultat ist aber immer gleich – eine Ausnahme tritt auf und
ein entsprechendes Mitteilungsobjekt wird in das Laufzeitsystem hineingefeuert bzw. ge-
worfen.
Der Begriff „Feuern“ ist bei Ausnahmen ungewöhnlich, soll aber die Ähnlichkeit zu einem Ereig-
nisobjekt andeuten. Ein Ereignisobjekt wird abgefeuert (fire an event). Bei einer Ausnahme spricht
man im Allgemeinen vom „Werfen“ (wegen dem Schlüsselwort throw und dem „Auffangen“ einer
Ausnahme in einem catch-Block).
Wesentlicher Unterschied zwischen der Reaktion auf ein Ereignisobjekt und ein Aus-
nahmeobjekt ist, dass die Kenntnisnahme sowie die Reaktion auf ein Ereignis (etwa
Klick mit der Maus in der Webseite) weitgehend „freiwillig“ ist, während die Kenntnis-
nahme und Reaktion auf eine Ausnahme vom Konzept generell erzwungen wird oder es
hat Konsequenzen (das Skript wird beendet).
[Link](eval("5 * 5"));
[Link](eval("a * b"));
[Link]("Weitere Ausgaben");
ufgrund dieser Information kann man gezielte Gegenmaßnahmen ergreifen oder aber
A
auch einfach nur auf die pure Existenz des Mitteilungsobjekts eine Standardgegenmaß-
nahme ergreifen. Eine Ausnahme ist aber noch mehr als ein reines Mitteilungsobjekt
mit spezifischen Informationen. Das Auftreten eines Ausnahmeobjekts unterbricht wie
gesagt unmittelbar einen normalen Skriptablauf und veranlasst den ausführenden Inter-
preter, nach einer geeigneten Behandlungsstruktur zu suchen. Dabei entsteht im Skript
ein Laufzeitfehler, der sich relativ standardisiert beschreiben lässt. Die ECMAScript Edi-
tion stellt ein Fehlerobjekt Error bereit und Instanzen von Error-Objekten werden als
Ausnahmen ausgeworfen, wenn ein bekannter Laufzeitfehler auftritt.
Wenn eine Ausnahme aufgetreten ist, müssen Sie diese wie gesagt zwingend behandeln
bzw. auffangen, sonst wird das Skript abgebrochen. Um nun eine ausgeworfene Aus-
nahme behandeln zu können, umgibt man den Aufruf einer kritischen Funktion, Methode
oder Anweisungsstruktur, die möglicherweise eine Ausnahme auswirft, mit einem um-
gebenden try-catch-Block. Dies sieht schematisch so aus:
Beispiel
• Alle potenziell kritischen Anweisungen stehen bei dieser Konstruktion in dem try-
Zweig. Sie sollten da immer alle Anweisungen notieren, die nur zusammen einen Vor-
gang beschreiben, also nicht nur die kritische Anweisung selbst. In den try-Block ge-
hört ein kompletter Vorgang, der entweder vollständig abgearbeitet werden muss oder
bei dem man eine komplette Gegenreaktion im Fall einer Ausnahme implementieren
muss.
12.1 Was ist Ausnahmebehandlung? 285
• Der optionale Ausdruck in den Klammern von dem catch-Zweig ist ein Bezeichner
für das konkrete Ausnahmeobjekt – eine Variable innerhalb des catch-Blocks.
• In dem catch-Zweig selbst stehen einfach innerhalb geschweifter Klammern die An-
weisungen, die beim Auftreten der Ausnahme durchgeführt werden sollen.
Wenn also eine der Anweisungen innerhalb des try-Blocks eine Ausnahme erzeugt, wird
diese durch die passenden catch-Anweisungen aufgefangen und entsprechend behandelt,
sofern es dafür die passende Behandlung gibt. Werden hingegen alle Anweisungen in
dem try-Block ohne Probleme abgearbeitet, wird der catch-Block übersprungen. Das
Auftreten einer Ausnahme innerhalb eines try-catch-Konstrukts führt nun auch nicht
mehr dazu, dass das Skript beendet wird. Es läuft mit den Anweisungen hinter dem try-
catch-Konstrukt weiter, nachdem die Anweisungen im catch-Teil abgearbeitet wurden.
Das Ausnahmeobjekt ist damit auch vom Laufzeitsystem beseitigt. Man sagt, die auf-
getretene Ausnahme wurde konsumiert.
Der Begriff try sagt übrigens bereits sehr treffend, was in diesem Block passiert. Es wird „versucht“,
den Code innerhalb des try-Blocks auszuführen. Wenn ein Problem auftauchen sollte (sprich, es
wird eine Ausnahme ausgeworfen), wird diese sofort entsprechend im passenden catch-Block auf-
gefangen und alle nachfolgenden Schritte in diesem try-Block werden nicht mehr durchgeführt.
Wenn Sie in JavaScript einen try-Block notieren, muss zwingend ein catch-Block fol-
gen. Fehlt dieser, ist die Reaktion der Interpretersysteme jedoch uneinheitlich. Einige
Systeme zeigen eine Fehlermeldung beim Fehlen des catch-Blocks an, andere gar nichts
mehr an. Der try-Block wird einfach ignoriert. Das ist für den Besucher einer Webseite
besser, denn Besucher sind nicht an Fehlermeldungen interessiert, aber schlecht für Pro-
grammierer, denn man erkennt das Problem möglicherweise nicht.
Beachten Sie, dass JavaScript lose typisiert ist und nur vier Grunddatentypen
kennt. Eine Unterscheidung des Typs des Ausnahmeobjekts über einen spezi-
fischen Referenztyp, wie man es etwa in Java oder C# macht, ist nicht möglich.
Deshalb kann man auch nicht mehrere catch-Blöcke alternativ einem try-Block zu-
ordnen, wie es etwa in Java oder C# üblicherweise gemacht wird, um damit eine
unterschiedliche Reaktion je nach Typ der Ausnahme zu implementieren. Statt-
dessen notieren Sie einfach getrennte try-catch-Strukturen für jeden einzelnen Fall.
Oder Sie verschachteln try-catch-Strukturen, indem Sie in einem catch-Block wie-
der try-catch-Blöcke notieren.
Was Sie nun im catch-Teil konkret tun, ist nicht weiter festgelegt. Sie können jede Ihnen
sinnvoll erscheinende Maßnahme ergreifen, die das Skript folgerichtig weiterlaufen lässt.
Oft ist das eine Fehlermeldung mit anschließendem Rücksprung zu der Situation, die mit
der ausgelösten Ausnahme sinnvoll umgehen lässt, etwa im Fall einer Benutzereingabe
286 12 Ausnahmebehandlung
ein Fokussieren des kritischen Eingabefelds. Oder aber Sie können einen alternativen
Weg zur Durchführung einer Aktion probieren, wenn der erste Weg gescheitert ist. In
letzterem Fall werden Sie oft auch try-catch-Strukturen verschachteln.
Das nachfolgende Beispiel zeigt den Fall, in dem eine Ausnahme auftritt (hier eine ein-
fache Situation, wo in jedem Fall eine Ausnahme auftritt – das ist in der Praxis natür-
lich nicht sinnvoll) und allgemein abgefangen wird. Es wird wie im Beispiel zuvor
einfach versucht, die Variablen a und b zu multiplizieren, die vorher nicht eingeführt
wurden. Im catch-Teil wird das dabei erzeugte Ausnahmeobjekt aufgefangen. Die
Maßnahmen im catch-Block bestehen nur daraus, die im Ausnahmeobjekt enthaltenen
Standardmeldungen nacheinander anzuzeigen. Die nach dem try-catch-Block stehen-
den Anweisungen werden trotz der aufgetretenen Ausnahme ausgeführt. Das bedeutet,
das Skript wird nicht abgebrochen, sondern mit unkritischen Anweisungen fortgesetzt.
Betrachten wir die Abwandlung unseres letzten Beispiels und fangen wir hier die Aus-
nahme ab ([Link]):
try {
[Link](eval("5 * 5"));
[Link](eval("a * b"));
[Link]("Im try-Block nach Ausnahme")
} catch (e) {
[Link]("Information in dem Ausnahmeobjekt: ", [Link]());
}
[Link]("Nach dem Auftreten der Ausnahme.");
Die beiden eval-Anweisungen sowie die reine Textausgabe sind nun von einem try-
Block umgeben. Wieder wird die erste Ausgabe problemlos erzeugt. Die folgende An-
weisung löst dann wie im vorherigen Beispiel die Ausnahme aus. Die Anweisung wird
nicht durchgeführt und der try-Block wird unmittelbar verlassen. Die nachfolgende Aus-
gabe wird nicht erreicht. Der Programmfluss verzweigt unmittelbar in den catch-Block.
Dort wird der Inhalt des generierten Ausnahmeobjekts angezeigt. Wenn der catch-Block
abgearbeitet wurde, läuft das Skript mit der nächsten Anweisung hinter dem try-catch-
Konstrukt weiter. Das ist die Ausgabe:
12.2 Selbstdefinierte Ausnahmen erzeugen und verwenden 287
25
Information in dem Ausnahmeobjekt: ReferenceError: a is not defined
Nach dem Auftreten der Ausnahme
Optional können Sie auch einen finally-Block bei der Ausnahmebehandlung notieren
([Link]):
try {
[Link](eval("5 * 5"));
[Link](eval("a * b"));
[Link]("Im try-Block nach Ausnahme")
} catch (e) {
[Link]("Information in dem Ausnahmeobjekt: ", [Link]());
}
finally{
[Link]("Kommt immer");
}
[Link]("Nach dem Auftreten der Ausnahme.");
Dieser wird immer ausgeführt. Unabhängig davon, ob eine Ausnahme aufgetreten ist
oder nicht. Vor allen Dingen wird dieser auch ausgeführt, wenn man mit einer Sprung-
anweisung innerhalb der try- oder catch-Blocks arbeitet.
Man kann auch in JavaScript selbstdefinierte Ausnahmen erzeugen und diese dann ähn-
lich wie die Standardausnahmen von JavaScript verwenden. Damit erweitern Sie die
Flexibilität der Anwendung von Ausnahmen.
Die Klasse Error kann sowohl als Funktion über Error() als auch – wie jede Funktion
in JavaScript – als Konstruktor über new Error() verwendet werden. Damit erzeugt man
dann ein neues Error-Objekt. Interessant ist dabei, dass man mit Error([Meldung]) eine
selbstdefinierte Fehlermeldung für das Objekt aufbauen kann, die sich mit der Eigen-
schaft message auswerten lässt. Daraus resultiert der hauptsächliche Nutzen von selbst-
definierten Ausnahmen. Beispiel zum Erzeugen einer selbstdefinierten Ausnahme mit
Meldung:
288 12 Ausnahmebehandlung
Beispiel
Nun braucht man in JavaScript durch die wenigen Datentypen und die lose Typisierung
sogar nicht einmal explizit ein Ausnahmeobjekt zu erstellen. Man kann jeden (!) Daten-
typ in JavaScript als Ausnahme werfen. Es kommt nur auf die Anwendung an – eben das
Werfen eines Datentyps.
Beispiel
throw [Ausdruck]; ◄
Nachfolgend sehen Sie ein einfaches Skript, in dem wir Callbacks registrieren, die mit
throw verschiedene Datentypen als Ausnahmen werfen sollen ([Link]). Deren Meldungen
werden dann im Ausgabebereich angezeigt:
"use strict";
try {
throw new Error("Nein! ");
} catch(e) {
catchMeldung(e);
}
try {
throw "Doch!";
} catch(e) {
catchMeldung(e);
}
try {
throw new Date();
} catch(e) {
catchMeldung(e);
}
12.2 Selbstdefinierte Ausnahmen erzeugen und verwenden 289
try {
throw 42;
} catch(e) {
catchMeldung(e);
}
function catchMeldung(e) {
[Link]("Typ: " + (typeof e) + ", Meldung: " + e);
}
In den Callbacks werfen wir direkt Ausnahmen unterschiedlicher Datentypen und fan-
gen sie wieder auf, was in der Praxis so natürlich nicht sinnvoll ist und immer mit einer
gewissen Logik gekoppelt sein sollte. Aber dieses Beispiel macht die Anwendungs-
möglichkeiten deutlich.
Asynchrone Programmierung
13
Ein Thread ist vereinfacht in der Informatik der kleinste ausführbare Programm-
bereich innerhalb eines Prozesses. Prozesse sind Instanzen von Programmen, die im
Betriebssystem ausgeführt werden. Innerhalb eines Prozesses können mehrere Threads
gleichzeitig existieren und arbeiten. Threads teilen sich den Speicher und andere Res-
sourcen eines Prozesses, was ihre Kommunikation und Zusammenarbeit erleichtert. Ein
Thread kann unabhängig oder in Zusammenarbeit mit anderen Threads arbeiten, um eine
Aufgabe zu erledigen. Threads können parallel oder nacheinander ausgeführt werden, je
nachdem, wie das Betriebssystem die Ressourcen verwaltet und verteilt. Threads werden
oft für Multitasking und Multithreading verwendet, um effizient Aufgaben zu erledigen,
die gleichzeitig oder nebenläufig ausgeführt werden können. Sie sind in vielen modernen
Betriebssystemen und Programmiersprachen integraler Bestandteil der Entwicklung von
Anwendungen und Systemen.
Traditionell war JavaScript eine Sprache, die auf einer einzigen Thread-Ausführung ba-
siert. Das bedeutet, dass sie synchron ist. Wenn eine Operation ausgeführt wird, hält die
Ausführung des restlichen Codes an, bis diese Operation abgeschlossen ist. Dies kann
© Der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH, ein 291
Teil von Springer Nature 2025
R. Steyer, Programmieren mit JavaScript, [Link]
292 13 Asynchrone Programmierung
zu Problemen führen, insbesondere wenn es um Operationen geht, die eine lange Zeit
zum Abschluss benötigen, wie beispielsweise das Laden von Daten von einem Server.
Asynchrones JavaScript wurde eingeführt, um dieses Problem zu lösen. Es ermöglicht,
dass bestimmte Operationen im Hintergrund ausgeführt werden, während der restliche
Code weiterhin ausgeführt wird. Dies wird oft durch die Verwendung von Callback-
Funktionen, Promises oder asynchronen Funktionen erreicht. Populär wurde diese Art
der Programmierung vor allen Dingen mit Ajax (Asynchronous JavaScript and XML)1,
was wir ausführlich behandeln und mit dem wir auch nach ein paar Grundlagen begin-
nen wollen. Asynchrones JavaScript ist allgemein besonders wichtig in Umgebungen
wie Webbrowsern, wo Operationen wie das Laden von Bildern, das Abrufen von Daten
von einem Server oder das Verarbeiten von Benutzereingaben oft Zeit benötigen und die
Benutzeroberfläche nicht blockieren sollten. Durch die Verwendung von asynchronem
JavaScript können solche Operationen im Hintergrund ausgeführt werden, während die
Benutzeroberfläche weiterhin reagiert und andere Operationen ausführt. Nachfolgend
schauen wir uns ein einfaches Beispiel an, das als gute Simulation für asynchrones Java-
Script dienen kann und das Konzept einer Callback-Funktion nutzt ([Link]):
[Link]("Start");
setTimeout(function() {
[Link]("Timeout abgelaufen");
}, 2000); // Warte 2 Sekunden, bevor die Callback-Funktion ausgeführt
wird
[Link]("Ende");
In diesem Beispiel wird Start zuerst ausgegeben, dann Ende, und schließlich, nachdem
2 s vergangen sind, wird Timeout abgelaufen ausgegeben. Während die Ausführung des
Callbackes mit setTimeout eine Verzögerung von 2 s hat, blockiert dies nicht die Aus-
führung des restlichen Codes. Das ist zwar nur eine Simulation, aber das Kernkonzept ist
damit bereits demonstriert.
Beachten Sie, dass setTimeout sowohl als Methode vom DOM-Objekt window als auch als Build-
in-Funktion von [Link] zur Verfügung steht. Das Beispiel funktioniert also sowohl im Browser als
auch unter [Link].
Asynchrone Vorgänge entstehen im Web alleine schon deswegen, weil Skripte im Brow-
ser und Skripte bzw. Anwendungen in einem Webserver nicht synchron sein können.
Sie „wissen“ nichts voneinander und zudem sind die Programme meist auch räumlich
getrennt, was eine Verbindung über ein Netzwerk erzwingt. Im Internet im Allgemeinen
und dem WWW im Speziellen werden Daten und Ressourcen von einem Webserver zur
Verfügung gestellt und jeweils gezielt von seinen Klienten über ein solches Netzwerk an-
gefordert. Es handelt sich hier im WWW also immer um ein Client–Server-System. Der
Webserver (Server kommt aus dem lateinischen von Servus und das bedeutet Sklave oder
Diener) stellt in dieser Beziehung den „Diener“ dar und der Anwender mit seinem Brow-
ser seinen „Herrn“. Dieser „Diener“ hat die Wünsche seines „Herrn“ zu befriedigen. Der
Webserver ist also ein (dummer) Diener, der – sofern keine Zugangsbeschränkungen
oder Probleme es verhindern – einfach nur auf eine Anforderung eine gewisse Leistung
(oder genauer – Ressource) zur Verfügung stellt, nicht mehr und nicht weniger. Erst
wenn ein Applikation-Server im weiteren Sinn ins Spiel kommt, kann man von einer ge-
wissen eigenständigen „Intelligenz“ sprechen.
Diese Spezifizierung der Kommunikationsform wird also nicht auf der Transportebene,
sondern oberhalb davon jeweils mit spezifischen Protokollen realisiert, die auf TCP/IP
aufsetzen. Solche Protokolle sind etwa ssh zur Fernsteuerung von entfernten Rechnern,
ftp (File Transfer Protocol) zur Dateiübertragung oder eben auch HTTP. Diese auf dem
eigentlichen Transportprotokoll aufsetzenden Protokolle sind besagte Dienstprotokolle.
Bei einer verbindungsorientierten Datenkommunikation wird oberhalb der Daten-
pakete in der Ebene der Dienstprotokolle zwischen Sender und Empfänger eine tempo-
räre virtuelle/logische Verbindung aufgebaut. Das bedeutet, obwohl rein physikalisch
keine dauerhafte Verbindung zwischen den Kommunikationspartnern vorhanden ist, wird
über ein Dienstprotokoll ein Mechanismus bereitgestellt, um eine dauerhafte Verbindung
zu gewährleisten. Das ist zum Beispiel bei einer Echtzeitkommunikation wie VoIP (Voice
over IP – Internettelefonie) oder beim Fernsteuern von einem Remoterechner der Fall.
Bei der nichtverbindungsorientierten Form der Datenübertragung verschickt ein Sen-
der hingegen einfach Datenpakete mit allen notwendigen Informationen zum Empfänger
und kümmert sich nicht um eine virtuelle Verbindung. Jede Anfrage durch einen Client an
einen Server erzeugt dort genau eine Sendung einer Antwort (die durchaus in viele Einzel-
pakete zerlegt sein kann, die erst beim Empfänger wieder zusammengefügt werden). Da-
nach „vergessen“ beide Kommunikationspartner die Verbindung. Das zur Kommunikation
von einem Webserver und einem Webclient (in der Regel ein Webbrowser) entwickelte
HTTP ist nun genau so ein zustandsloses Protokoll. Es erlaubt einen einfachen Datenaus-
tausch zwischen den beteiligten Parteien, bei dem der Client Daten vom Server anfordert
und als Antwort vom Server eine Webseite gesendet bekommt. Aber es wird keine Ver-
bindung zwischen Webserver und Webbrowser aufrecht gehalten. Das erschwert auch die
Verfolgung zusammengehörenden Kommunikationsschritten zwischen einem Client und
dem Webserver und die ressourcenschonende Nachforderung von Daten. Hier ist ein Di-
lemma angesiedelt, das Ajax mittlerweile gelöst hat, während bereits davor sogenannte
Sessions zusammengehörende Schritte protokolliert haben – etwa mit Cookies.
[Link].1 Der HTTP-Request
Bei einer typischen HTTP-Anfrage (HTTP-Request) durch einen Client besteht das ge-
samte Datenpaket, das an den Webserver gesendet wird, ausschließlich aus dem HTTP-
Header. Schauen wir uns so einen typischen HTTP-Request exemplarisch an:
13.2 Die Beziehung zwischen Webserver und Browser 295
[Link]
GET / http/1.1
Host: [Link]
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:[Link])
Gecko/20.091.221 Firefox/3.5.7 (.NET CLR 3.5.30729)
Accept: text/html,application/xhtml + xml,application/
xml;q = 0.9,*/*;q = 0.8
Accept-Language: de-de,de;q = 0.8,en-us;q = 0.5,en;q = 0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859–1,utf-8;q = 0.7,*;q = 0.7
Keep-Alive: 300
Connection: keep-alive
Mit einem Sniffer wie Wireshark oder dem Netzwerkmodul von modernen
Browsern können Sie die verschickten Daten vom Browser sowie die empfan-
genen Daten vom Server analysieren.
Deutlich sind als zweite Information im Header die Methode der Datenanforderung
(hier GET) und die Protokollversion (hier http 1.1) zu erkennen. Auf die Startzeile des
Requests folgen eine Reihe von Feldern in mehr oder weniger beliebiger Reihenfolge,
die die Anfrage genauer beschreiben. Jedes Header-Feld besteht aus einem Namen und
einem Wert. Das Wertepaar wird durch einen Doppelpunkt voneinander getrennt. Die
Werte gleichnamiger Header-Felder können in einem Header zusammengefasst werden,
indem sie mit einem Komma getrennt werden. Sie können auch eigene Felder definieren
können – der Webserver muss nur diese auch verstehen.
Bei HTTPS gibt es weitere Felder, die hier nicht verfolgt werden. Grundsätzlich be-
schränken wir uns in der Folge auf HTTP.
geklickt haben. Ist die Anfrage erfolgreich, dann enthält die Antwort des Servers die
angefragte Ressource zur Darstellung. Bei einer klassischen Webseite ohne Anwender-
interaktion ist der Frage-Antwort-Zyklus damit beendet. Ein solcher Zyklus läuft aber
ebenso ab, wenn Benutzereingaben in einem HTML-Formular durch den Client ab-
geschickt werden oder auch wenn per Ajax Daten vom Server angefordert werden. Das
soll im Fall von Ajax möglicherweise unbemerkt vom Anwender erfolgen. Bevor die
Daten vom Client an den Webserver gesendet werden, werden sie zunächst vom Brow-
ser zu einer einzigen Zeichenkette verpackt. Die Art und Weise der weiteren Behandlung
kann durch die Angabe von GET oder POST gesteuert werden. Bei der GET-Methode
werden die Daten vor dem Versenden zusammen an das Ziel der Datenübertragung (die
URL) angehängt, wobei die Daten in der Regel durch ein ? von der ursprünglichen URL
abgetrennt werden. Diese entstehende Pseudo-URL sieht ein Anwender nach dem Ab-
schicken eines Formulars im Adressfeld des Browsers. So eine Pseudo-URL können Sie
auch manuell im Adressfeld des Browsers zur Ergänzung einer normalen Adresse eines
Servers eingeben oder in einem Hyperlink aufrufen.
Wenn Sie beim Versenden von Daten jedoch die Methode POST verwenden, werden
die zu verschickenden Daten in eine Umgebungsvariable gepackt, die im HTTP-Rumpf
versteckt ist. Das empfangende Programm beziehungsweise Skript auf dem Server wer-
tet so oder so die übergebenen Daten aus (sofern es entsprechend programmiert ist),
wobei einige Arbeit zu leisten ist. Wir werden das genauer sehen, wenn wie einen voll-
ständigen Webserver selbst programmieren (Abschn. [Link]).
[Link].1 Die URL-Kodierung
Ein Pseudo-URL mit der Übergabe von Werten an ein Skript beziehungsweise Pro-
gramm enthält die Zeichen der Benutzereingabe nicht unverändert. Bei der Generierung
des Pseudo-URL wird in der Regel der Prozess der URL-Kodierung durchlaufen, was
beim klassischen Versenden von Formulardaten automatisch erfolgt. Diese URL-Ko-
dierung erfolgt hauptsächlich, damit bei der Übertragung von Sonderzeichen über das
Internet/Intranet keine Probleme entstehen. Dabei werden alle Leerzeichen in dem
String, der aus den Benutzereingaben zusammengesetzt wird, durch Pluszeichen ersetzt.
Zusätzlich werden sämtliche reservierten Zeichen, die ein Benutzer beispielsweise im
Formular eingegeben hat (etwa das Gleichheitszeichen oder das kaufmännische Und),
in hexadezimale Äquivalente konvertiert. Ein so konvertiertes Zeichen wird jeweils
mit dem Prozentzeichen eingeleitet. Danach folgt der Hexadezimalcode. In dem String
des Pseudo-URL können also weder das Gleichheitszeichen, das Prozentzeichen, das
Fragezeichen, das Pluszeichen oder das kaufmännische Und (Ampersand) durch Be-
nutzereingaben vorkommen. Auch zahlreiche andere Zeichen werden verschlüsselt.
Wenn die Daten an ein auswertendes Skript beziehungsweise Programm auf dem Ser-
ver übermittelt werden, kommen alle Daten als ein Satz von Name-Wert-Paaren an. Der
Name repräsentiert eine Variable, die beispielsweise über ein entsprechendes Tag in der
HTML-Seite festgelegt wurde. Die Werte sind das, was die Variable für einen Wert an
den Server übermitteln soll. Das kann z. B. eine Anwendereingabe sein. Dieser Satz von
13.2 Die Beziehung zwischen Webserver und Browser 297
Name-Wert-Paaren wird in einem Teilstring des URL übermittelt, den ein Webskript
wieder auflösen muss. Ein solcher String (Teilstring mit zwei Wertepaaren) ist ungefähr
so aufgebaut:
Beispiel
Der String mit den Wertepaaren muss vom auswertenden Skript beziehungsweise Pro-
gramm beim kaufmännischen UND (&) sowie dem Gleichheitszeichen (=) in einzelne
Stücke zerlegt werden. Die entstandenen Teilstücke müssen dann noch dekodiert werden.
"use strict";
const http = require('http');
const fs = require('fs');
const url = require('url');
const path = require('path');
let queryAntwort = "";
const server = [Link]((req, res) => {
const parsedUrl = [Link]([Link], true);
let filePath = '.' + [Link];
if (filePath === './') {
filePath = './[Link]'; // Standardseite
}
const extname = [Link](filePath);
let contentType = 'text/html';
switch (extname) {
case '.txt': contentType = 'text/plain'; break;
case '.html': contentType = 'text/html'; break;
case '.css': contentType = 'text/css'; break;
case '.js': contentType = 'text/javascript'; break;
case '.json': contentType = 'text/javascript'; break;
case '.xml': contentType = 'text/xml'; break;
default: contentType = 'text/plain';
}
[Link](filePath, (err, content) => {
if (err) {
if ([Link] === 'ENOENT') { // Datei nicht gefunden
298 13 Asynchrone Programmierung
[Link](404);
[Link]('Datei nicht gefunden');
} else {
[Link](500); // Anderer Fehler
[Link]("Interner Serverfehler: "+ [Link]);
}
} else { // Datei gefunden, sende Inhalt oder Reaktion auf Query
[Link](queryAntwort); // Query
[Link](200, { 'Content-Type': contentType });
// Wenn kein Query vorhanden
if([Link] < 5)
[Link](content, 'utf-8'); // Sende Datei
else // Sende Query-Daten
[Link](queryAntwort, 'utf-8');
}
});
});
[Link]('error', (err) => {
[Link]('Serverfehler:', err);
});
// Daten über GET oder POST entgegennehmen
[Link]('request', (req, res) => {
if ([Link] === 'GET') {
[Link]('GET-Anfrage empfangen');
const queryData = [Link]([Link], true).query;
[Link]('Daten:', queryData);
queryAntwort = [Link](queryData);
} else if ([Link] === 'POST') {
[Link]('POST-Anfrage empfangen');
let body = '';
[Link]('data', (chunk) => {
body += [Link]();
});
[Link]('end', () => {
[Link]('Daten:', body);
queryAntwort = body;
// Hier auf die POST-Daten reagieren und entsprechend queryAntworten
// [Link](200, { 'Content-Type': 'application/json' });
// [Link]([Link]({ message: 'POST-Erfolg' }));
});
}
});
// Server starten
const PORT = [Link] || 3210;
13.2 Die Beziehung zwischen Webserver und Browser 299
[Link](PORT, () => {
[Link]("Server läuft auf Port " + PORT);
});
Auch wenn der Quellcode nicht ganz trivial ist, sind die Techniken bekannt und decken
das ab, was wir gerade zu Datenübertragung ausgeführt haben. In der Konsole gibt der
Server auch wesentlichen Kommunikationsschritte aus.
Zuerst werden die benötigten [Link]-Module importiert:
• http: Zur Erstellung des HTTP-Servers und zur Verarbeitung von HTTP-Anfragen.
• fs: Zur Arbeit mit dem Dateisystem, um statische Dateien zu lesen.
• url: Zur Analyse von URL-Objekten für die Anfrageverarbeitung.
• path: Zur Bearbeitung von Dateipfaden.
Abb. 13.1 Der [Link]-Server läuft und nimmt Anfragen von einem Browser entgegen, die pro-
tokolliert werden
Kommen wir nun zu Ajax und dem Web 2.0, also die Grundlage dessen, was in RIAs
gipfelt – dynamische Applikationen, die sehr interaktiv und asynchron mit dem Webserver
kommunizieren und Inhalte der Webseite immer wieder durch nachgeladene Informationen
13.3 Ajax & Web 2.0 301
austauschen und dies meist im Hintergrund. Das soll bedeuten, dass dieser Datenaus-
tausch zwischen Client und Server im Gegensatz zu einer klassischen Webapplikation
oft gar nicht bemerkt wird, denn die Webseite, die zum Zeitpunkt der Veränderung im
Browser angezeigt wird, wird nicht mehr komplett ausgetauscht. Stattdessen werden nur
die Teile einer Webseite ausgetauscht, die zu aktualisieren sind. Und ob die neuen Daten
schon vorher im Client vorhanden sind und nicht angezeigt werden (beispielsweise über
das Vorausladen von Daten und Vorhalten in JavaScript) oder erst bei Bedarf gezielt vom
Webserver nachgeladen werden, spielt keine Rolle. Für die Applikation nicht und für
Anwender schon gar nicht. Den programmiertechnischen Kern dieser „reichhaltigen“
Möglichkeiten rein im Browser (als aufseiten des Clients) bildet JavaScript respektive
Ajax.
Bei der Datenanforderung mit Ajax, aber auch dem konventionellen Datenaustausch zwi-
schen Webserver und Browser, ist es von Bedeutung, welches Datenformat beim Aus-
tausch verwendet wird. Dabei wird die Anfrage inklusive aller HTTP-Header immer aus
reinem Klartext bestehen. Die Antwort des Servers besteht zwar auch aus Klartext (die
HTTP-Header-Informationen), aber es gibt auch die eigentliche Nutzlast. Dies sind Daten
und Ressourcen, die der Client eigentlich vom Server möchte. Metainformationen in Form
von HTTP-Headern sind normalerweise im Client uninteressant, solange kein Fehler bei
der Transaktion aufgetreten ist. Die Nutzlast kann nun in verschiedenen Datenformaten
vorliegen. Grundsätzlich können Sie also beliebige Ressourcen von einem Webserver an-
fordern, natürlich als Referenzen im Rahmen einer Webseite, aber auch direkt (also ohne
Webseite). Sofern ein anfordernder Browser mit einem spezifischen Datenformat direkt
umgehen kann, wird er die entsprechende Ressource direkt oder mit einem installierten
Plugin bzw. Add-on verarbeiten. Dies betrifft auch binäre Ressourcen wie beispielsweise
Bilder, Videos oder andere Multimediadateien. Kann der Browser mit dem Datenformat
einer gesendeten Ressource nicht unmittelbar umgehen, bietet er in der Regel über einen
Dialog die Speicherung auf dem lokalen System oder das Öffnen mit einem externen Pro-
gramm an. Im Rahmen der Webseite werden binäre Ressourcen wie gesagt natürlich als
Referenzen über HTML verankert. In Hinsicht auf die Datennachforderung mit Ajax oder
allgemein die Verarbeitung mit JavaScript sind binäre Datenformate allerdings nicht im
Detail von Bedeutung, denn hier werden Daten nur als Klartext verwendet.
[Link] Klartextdatenformate
Im WWW setzt man zum Austausch von interpretierbaren Informationen fast ausschließ-
lich auf das Klartextformat, denn dieses ist plattformneutral und wird auf nahezu jedem
System verstanden und identisch interpretiert, sofern man die Zeichensätze richtig
festgelegt. Wie konkret der Klartext auf dem Server entsteht, ist aus Sicht des Clients
302 13 Asynchrone Programmierung
Die Verlagerung von Logik in einer Webapplikation in den Client ist teilweise kri-
tisch. Sie sollten gründlich darüber nachdenken, ob das für Ihre Applikation wirk-
lich der richtige Weg ist.
Der Marketingbegriff Web 2.0 kann nicht klar technisch definiert werden. Er beschreibt
mehr eine Art des Umgangs mit dem WWW. Statt passiv zu konsumieren, fasst man unter
den zum Web 2.0 gefassten Diensten die Teile des Internets zusammen, bei denen Konsu-
menten gleichzeitig auch Produzenten von Inhalten sind. Teilnehmer können im Web 2.0
also aktiv werden und das ohne die Erstellung einer Webseite im klassischen Sinn. Man
redet deshalb beim Web 2.0 auch vom „Mit-Mach-Web“. So eine Konstellation trifft man
in Wikis, in Blogs, in File-Sharing-Portalen, sozialen Netzwerken und ähnlichen Diensten.
müssen. Vereinfacht gesagt werden Daten bei der Verwendung von Ajax also erst dann
angefordert, wenn sie benötigt werden, dann für JavaScript verfügbar gemacht und an-
schließend mit DHTML in die bereits in den Browser geladene Webseite eingebaut. Wenn
Sie Ajax einsetzen, wollen Sie in jedem Fall einen Teil einer bereits geladenen Webseite
austauschen oder dort Daten ergänzen, ohne die gesamte Webseite wieder neu zu laden.
Der grundsätzliche Ablauf einer Ajax-Anfrage folgt dabei in der Regel immer dem glei-
chen Schema:
1. Zuerst wird ein Objekt (XMLHttpRequest oder kurz XHR) erzeugt, über das die Kom-
munikation am Browser vorbei mit dem Server erfolgen wird, in der Regel asynchron
zur eigentlichen bewussten Kommunikation des Anwenders mit dem Server. Dieses
Kommunikationsobjekt ist eine Erweiterung des Objektmodells von JavaScript.
2. Ein Callback wird bei diesem Kommunikationsobjekt über einen Eventhandler als
Funktionsreferenz registriert. Diese Funktion wird dann bei jeder Zustandsänderung
der Transaktion aufgerufen. Die angegebene Funktion wird nach der Registrierung für
jede Statusänderung des Kommunikationsobjekts aufgerufen. Eine Eigenschaft des
Kommunikationsobjekts gibt dabei Aufschluss über den aktuellen Status der Trans-
aktion beim Aufruf dieses Callbacks. So lassen sich einzelne Phasen der Datenüber-
tragung unterscheiden.
3. Die Verbindung wird geöffnet. Das ist aber noch nicht die konkrete Anfrage. Deshalb
ist es auch unerheblich, ob Schritt 2 und 3 vertauscht werden.
4. Die Anfrage wird abgeschickt und die Ajax-Applikation wartet auf die Antwort vom
Webserver.
5. Die Antwort des Webservers wird im Browser verwertet. Um eine vollständige Ant-
wort zu erkennen, kann die Statusänderung des Kommunikationsobjekts explizit ge-
nutzt werden.
Um mit einem XHR-Objekt arbeiten zu können, muss dieses erzeugt werden. Mittler-
weile geht das in JavaScript einheitlich über den Einsatz einer passenden Konstruktor-
methode.
13.3 Ajax & Web 2.0 305
Beispiel
Ein Objekt vom Typ XMLHttpRequest stellt dann alle relevanten Methoden und Eigen-
schaften zur asynchronen Kommunikation per Ajax bereit. Dabei können auf Basis der
üblichen Übertragungsmethoden wie GET und POST Daten verschickt bzw. Informatio-
nen angefordert und Antworten empfangen sowie der Request-Header gesetzt und aus-
gewertet werden.
Beachten Sie, dass mit Ajax-Anfragen aus Sicherheitsgründen in der Regel nur Daten von der glei-
chen Domain angefordert werden dürfen, von der die anfordernde Webseite stammt. Das ist ein
oft zu findendes Sandkastenprinzip (Sandbox), das wir beim Thema Sicherheit ebenfalls berühren
(Same Origin Policy). Andernfalls würde man einen sogenannten Cross-Domain-Zugriff durch-
führen und dieser würde Missbrauch und Manipulation Tür und Tor öffnen. Allerdings ist die Ein-
schränkung erheblich und es gibt einige Workarounds, diese Beschränkungen aufzuheben, ohne
die Sicherheit zu gefährden.
Beispiel
function erzXHRObjekt(){
var resObjekt = null;
try {
resObjekt = new XMLHttpRequest();
} catch(e){
alert("Erzeugung XHR-Objekt nicht möglich");
}
return resObjekt;
}◄
Wenn wir ein XHR-Objekt erzeugen, können wir darüber Daten asynchron nachladen.
Zudem können Sie auf alle Header-Felder von der Anfrage an den Server und der Ant-
wort durch den Server zugreifen. Ein XHR-Objekt stellt Ihnen alle relevanten Methoden
und Eigenschaften bereit. Wie Sie diese Daten in der Webseite präsentieren, ist dann rei-
nes DHTML.
306 13 Asynchrone Programmierung
Wie oben beschrieben, werden Daten mit der open-Methode vom Server nachgefordert.
Obwohl in der Abkürzung Ajax explizit XML steckt, kann man für den asynchro-
nen Datenaustausch jede Form von Klartextdaten nutzen, wie wir es schon besprochen
haben. Es ist genau genommen so, dass man oft die wenigsten Probleme bei Ajax-Ap-
plikationen hat, wenn die angeforderten Daten bereits als HTML-Fragmente oder als rei-
ner Text zum Client geschickt und dort eingefügt werden. Die Aufbereitung der Daten
erfolgt in diesem Fall vollständig auf dem Server und der Client zeigt die fertigen Daten
nur unverändert (maximal in gewisser Weise formatiert) an.
Beachten Sie, dass Sie bei Ajax keine vollständige Webseite nachfordern sollten.
Das Grundgerüst der nachgeladenen Webseite würde unter Umständen zu Kom-
plikationen führen, wenn es in ein bestehendes Grundgerüst eingefügt und vom
Browser interpretiert wird. Ebenso sollten Sie beachten, dass bei Ajax keine bi-
nären Dateien direkt nachgefordert werden. Wenn Sie Bilder oder andere binäre
Ressourcen nachladen wollen, fordern Sie per Ajax nur die Referenzen auf diese
binären Ressourcen an. Der Browser fordert die neuen binären Ressourcen dann
über die Referenzen als neue Anfrage vom Webserver nach. Das XHR-Objekt
stellt keine Methoden oder Eigenschaften zur Verfügung, um direkt mit binä-
ren Ressourcen umzugehen. Das wäre auch unsinnig. Wobei es rein theoretisch
die Möglichkeit gäbe, in sogenannten CDATA-Bereichen von XML binäre Daten
unterzubringen und damit per Ajax anzufordern. Aber auch das wäre in der Regel
der falsche Weg.
Wenn Sie XML oder JSON zum Client schicken, müssen Sie diese Daten dort ver-
arbeiten. XML-Daten werden im Client als node-Objekt verfügbar gemacht und können
dort verarbeitet werden. JSON wird entweder bereits als Objekt interpretiert oder kann
über [Link] aus einer stringifizierten Form reproduziert werden. Dabei wird JSON
konsistenter und problemloser in den verschiedenen Browsern verarbeitet als XML.
Tipp Wenn Sie mit Ajax arbeiten, müssen Sie die Daten von einem Webserver
anfordern. Sie können Apache nehmen, was etwa die Ausführung eines PHP-
Skripts erlaubt, um Daten entgegenzunehmen. Aber auch der von uns bereits
selbst programmierte [Link]-Webserver eignet sich und dieser soll die Basis
darstellen. Beachten Sie, dass man diesen Webserver auch erweitern könnte,
um auf Spezifika von Ajax-Anfragen zu reagieren. Etwa so:
Die Prüfung auf XMLHttpRequest bei den Anfrage-Headern ist historisch be-
dingt. XMLHttpRequest war die erste API in JavaScript, die es ermöglichte,
HTTP-Anfragen aus dem Browser heraus zu senden, aber heutzutage nicht
mehr die einzige Möglichkeit. Es gibt auch moderne Alternativen wie die
Fetch API, die Promises verwendet. Die Erweiterung ist aber nicht unbedingt
notwendig, denn die bisherige Version von dem Webserver liefert die ge-
wünschten Daten auch bei Ajax-Anfragen. Die Prüfung akann jedoch nützlich
sein, wenn eine spezielle Reaktion auf Ajax-Anfragen erfolgen soll.
…
<link rel="stylesheet" type="text/css" href="lib/css/[Link]" />
<script src="lib/js/[Link]" type="text/javascript"></script>
<script src="lib/js/ajax_1.js" type="text/javascript"></script></
head>
<body><h1>Frage den weisen Mann</h1>
<button id="frage">Was ist das Geheimnis ewiger Jugend?</button>
<div id="antwort"></div></body></html>
Sie sehen, dass die Webseite neben einer Schaltfläche einen leeren <div>-Container ent-
hält, der im Folgenden mit Ajax gefüllt werden soll. Der <div>-Container hat ein Attribut
id und dieses ist der Schlüssel zum Zugriff. Im Header finden Sie die Referenzen auf eine
CSS-Datei, die einfach nur ein bisschen die Gestaltung vornimmt und hier nicht weiter
ausgeführt werden soll. Dann finden Sie die externe JavaScript-Datei zum Erzeugen eines
XHR-Objekts, wie wir es gerade besprochen haben, und die Referenz auf die zweite ex-
terne JavaScript-Datei ajax_1.js. Diese enthält die Funktionalität, um Daten auf Basis
eines XHR-Objekts asynchron vom Webserver anzufordern und diese über die Ajax-En-
gine in die Webseite zu integrieren, ohne eine neue Webseite laden zu müssen. Beachten
Sie, dass alle eingebundenen JavaScript-Dateien sowie in der Webseite selbst befindliche
JavaScript-Container dem gleichen Scope zugeordnet sind (wir werden in den folgen-
den Beispielen grundsätzlich so eine Verteilung der externen JavaScript-Dateien und des
310 13 Asynchrone Programmierung
internen Skriptbereichs vornehmen). Dies ist die externe JavaScript-Datei zur Daten-
anforderung per Ajax:
Die externe JavaScript-Datei enthält zuerst den Aufruf für die Erzeugung des XHR-Ob-
jekts, das in einer globalen Variablen referenziert wird. Wir verwenden dazu die uni-
verselle Funktion zum Erzeugen eines XHR-Objekts aus der anderen externen Java-
Script-Datei. Darüber werden in den nachfolgend definierten Funktionen Daten vom Ser-
ver angefordert und mit JavaScript wieder bereitgestellt. Beachten Sie den Eventhandler
[Link]("frage").onclick = sndReq;. Damit wird bei einem Klick
auf die Schaltfläche die JavaScript-Funktion sndReq aufgerufen. Darin wird zuerst eine
HTTP-GET-Verbindung zum Webserver durch [Link] initialisiert. Der erste Parameter
in der open-Methode spezifiziert die Art der HTTP-Anforderung. In unserem Fall wird
die GET-Methode gewählt. Der zweite Parameter ist die Zieladresse der Anfrage. Dies
ist in diesem Fall eine gewöhnlicher URL auf eine statische Textdatei (eine relative Pfad-
angabe). Der dritte Parameter gibt an, ob die Kommunikation asynchron erfolgen soll
oder nicht. Der Wert true gibt an, dass die Kommunikation asynchron laufen soll. Sobald
die HTTP-Antwort beim Client wieder eintrifft, muss man nun darauf reagieren können.
Dazu definieren wir eine Funktion zum Umgang mit der Antwort. Diese wird allgemein
vor dem Aufruf der send-Methode an die onreadystatechange-Eigenschaft des XHR-Ob-
jekts gebunden. Die hier angegebene anonyme Callback-Funktion wird bei jeder Status-
änderung der HTTP-Anfrage durch den Server ausgeführt.
Im Callback wird einfach mit der Eigenschaft readyState ein bestimmter Status der
Datenübertragung überprüft. Der Wert 4 steht, wie wir besprochen haben, für COMPLE-
TED (vollständig)2. Wenn also die angeforderten Daten vollständig übertragen wurden,
greifen wir mit [Link] auf das Element der Webseite zu, dem der
2 Mankann auch auf den Statuscode des Servers prüfen, was wir an späterer Stelle noch machen
werden.
13.3 Ajax & Web 2.0 311
Parameter id mit dem Wert antwort zugeordnet ist. Das ist in der Webseite der <div>-
Container. Das Attribut innerHTML erlaubt den Zugriff auf den Inhalt von diesem Con-
tainer. Wir arbeiten hier also im Grunde mit reinem DHTML – nur die Daten kommen
dynamisch nachgeladen vom Server. Der Wert wird über das Objekt xhr zugewiesen.
Dieses Objekt enthält über die Eigenschaft responseText die Antwort des Webservers.
Die angeforderte Datei besteht nur aus reinem Text (auf den Abdruck kann guten Ge-
wissens verzichtet werden). Ein Blick auf einen Sniffer zeigt, dass die Daten tatsächlich
beim Klick auf die Schaltfläche nachgeladen werden. Beachten Sie, dass im Sniffer unter
Umständen nur dann etwas zu sehen ist, wenn die Daten nicht schon einmal geladen und
vom Browser im Cache zwischengespeichert wurden.
…
<script src="lib/js/ajax_2.js" type="text/javascript"></script></
head>
<body><h1>Zusatzinformationen</h1>
<table><tr><td><img src="images/[Link]" /></td>
<td><img src="images/[Link]" /></td>
<td><img src="images/[Link]" /></td></tr>
<tr><td id="info0"></td><td id="info1"></td><td id="info2"></td></tr>
</table></body></html>
Sie haben hier eine Webseite mit mehreren Bildern in einer Tabelle, bei denen Event-
handler in der externen JavaScript-Datei registriert werden. Mit den aufgerufenen Funk-
tionen sollte beim Überstreichen eines Bilds mit der Maus in der Zelle darunter eine Zu-
satzinformation angezeigt werden. Wenn der Mauszeiger den Bereich wieder verlässt,
wird der Anzeigebereich gelöscht (Abb. 13.2).
Hier sehen Sie die JavaScript-Datei (ajax_2.js):
}
getElementsByTagName("img")[0].onmouseout = out;
getElementsByTagName("img")[1].onmouseout = out;
getElementsByTagName("img")[2].onmouseout = out;
}
}
[Link] = init;
Die JavaScript-Datei enthält nun ein paar sehr interessante Stellen. Beachten Sie zuerst,
dass wir in der init-Funktion anonyme Funktionen bei dem Eventhandler registrieren.
Wir können damit der Funktion sndReq einen Parameter übergeben. Auf jedem Bild-
bereich wird ein spezifischer Parameter der Funktion übergeben. Dieser dient zur Steue-
rung, welche Information wo angezeigt wird. Die Tabelle enthält dazu in der zweiten
Zeile Zellen mit jeweils einer spezifischen Id. Diese Id ist nach einem gewissen Muster
vergeben, das eine Unterscheidung über einen einfachen numerischen Algorithmus er-
laubt, der an dem übergebenen Parameter an die Funktion sndReq orientiert ist. Da wir
der Funktion einen Parameter übergeben, können wir im Inneren der Funktion diesen
Parameter dazu nutzen, gezielt eine spezifische Textdatei auszuwählen, die je nach Para-
meter angefordert wird. Dazu verwenden wir einfach switch-case.
Die Namen der angeforderten Dateien könnten Sie übrigens auch über einen
analogen numerischen Algorithmus zusammensetzen, der etwas weiter unten
zum automatischen Bestimmen der Id verwendet wird. Das Listing ist hier nur
etwas allgemeiner formuliert.
Die angeforderten Textdateien enthalten übrigens zum Teil HTML-Fragmente, wie Sie
an der Ausgabe erkennen können. Diese HTML-Anweisungen werden dann beim Nach-
laden der Textdatei vom Browser direkt interpretiert. Dies eröffnet alle Möglichkeiten
zur Gestaltung der Antwort des Webservers sowie z. B. zum Nachladen von externen
Ressourcen über HTML-Referenzen. Die nächste interessante Stelle finden Sie beim Re-
gistrieren einer anonymen Funktion beim Eventhandler onreadystatechange. Damit kön-
nen wir der registrierten Funktion handleResponse einen Parameter übergeben und zwar
den Wert, der bereits an sndReq übergeben wurde. Dieser wird in der Funktion handle-
Response zum Zusammensetzen der Id genutzt, welche die Tabellenzelle spezifiziert, in
der die Antwort des Servers angezeigt werden soll. Diese Id war ja bewusst so gewählt,
dass man sie über einen einfachen numerischen Algorithmus aus dem Übergabepara-
meterwert an sndReq bestimmen kann. Die Funktion zum Löschen der Anzeigebereiche
leert einfach alle Anzeigebereiche. Wir brauchen hier keine individuelle Logik! Sie sehen
an diesem Beispiel, dass Sie bereits mit reinen statischen Textdateien auf dem Server
eine komplexe Logik in die Ajax-Applikation einbauen können. Man muss diese Logik
eben nur in JavaScript implementieren und auf dem Client ausführen.
314 13 Asynchrone Programmierung
…
<script src="lib/js/ajax_3.js" type="text/javascript"></script></
head>
<body><h1>Geben Sie Ihren Namen ein</h1>
<form>Name: <input type="text" id="n" /></form><hr />
<button id="frage">OK</button><div id="antwort"></div></body></
html>
In der externen JavaScript-Datei zur Kommunikation mit dem Webserver müssen wir nur
ein paar wenige Stellen verändern (ajax_3.js):
In der open-Methode wird einfach ein Pseudo-URL aus einem Namen für das Ein-
gabefeld, über den der Wert auf dem Server entgegengenommen werden kann (n),
und dem Wert zusammengesetzt (über die Id abgefragt). Zusätzlich wird der Wert mit
encodeURIComponent umgewandelt, um Sonderzeichen zu maskieren. Das war alles.
Die Anfrage wird an eine Dummy-Datei geschickt, die aber durch den Query aufgrund
13.3 Ajax & Web 2.0 315
der individuellen Logik unseres [Link]-Webserver ignoriert wird. Man kann aber auch
etwa an ein ein passendes PHP-Skript die Anfrage schicken, wenn man mit Apache
arbeitet oder sonst eine individuelle Logik auf Serverseite ausbauen.
…
[Link]('get', 'serverantworten/[Link]',true);
…
Ansonsten entspricht das Beispiel in allen relevanten Bereichen unserer ersten Version
ajax_1.js.
<seite><titel>Fliegerblog</titel><url>[Link]
url>
<bemerkung>Flugtagebuch</bemerkung></seite></webseiten>
Wenn Sie die XML-Daten allerdings einfach so in die Webseite befördern, werden Ihnen
nur die Inhalte der XML-Datei unformatiert aneinandergereiht dargestellt. Die Elemente
selbst sehen Sie nicht. Genau genommen werden die XML-Elemente als unbekannte
Tags verstanden. Wenn die XML-Elemente nun zufällig oder bewusst wie HTML-Ele-
mente genannt werden, werden sie interpretiert und ausgeführt. Andernfalls werden sie
als unbekannte HTML-Tags nach dem Prinzip der Fehlertoleranz ignoriert. Deshalb
sehen Sie nur die Inhalte der XML-Elemente. An eventuelle Attribute, die bei den Ele-
menten angegeben wurden, kommen Sie auf diese Weise überhaupt nicht. Ein Blick in
die Entwicklertools eines Browsers zeigt aber die tatsächliche Struktur, wie sie im Brow-
ser vorliegt. Wir könnten nun den Versuch wagen und die Namen der XML-Elemente in
die CSS-Datei aufnehmen. Wir würden also neben den HTML-Elementen, die von An-
fang an in der Webseite vorhanden waren (oder auch per Ajax nachgeladen werden), dem
Browser unbekannte Elemente zur Formatierung anbieten, etwa so, um einen Auszug aus
der Datei [Link] zu zeigen:
…
seite{ display:block; background:yellow; }
titel, url, bemerkung{ display:inline-block; width:250px; }
Wenn Sie sich das im Browser ansehen, sieht das vielversprechend aus (Abb. 13.3).
Dennoch bleibt bei der Formatierung über CSS das Problem, dass Sie ausschließlich
an die Inhalte der Elemente kommen, aber wie gesagt nicht an eventuell vorhandene At-
tribute oder die Elementnamen. Von daher bleibt oft sowieso nur die gezielte Navigation
auf dem DOM-Baum des nachgeforderten XML-Dokuments.
Ausgehend davon können Sie die Attributknoten, Textknoten und weitere Kindknoten
eines Knotens in der Antwort des Servers ansprechen. Selbst wenn der Server HTML-
Fragmente als Antwort schickt, können Sie diese in der Regel über die responseXML-
Eigenschaft im Client verwerten und dann auf dem resultierenden Knoten die nach-
folgenden Techniken einsetzen. Verwenden wir eine Abwandlung des letzten Beispiels,
um XML-Daten anzufordern und darauf im Client zu navigieren. In der neuen HTML-
Datei ajax_5.html müssen Sie bloß gegenüber ajax_4.html die externe JavaScript-Datei
ajax_5.js referenzieren, die Sie hier sehen:
…
[Link] = function() {
if ([Link] == 4) {
var erg = [Link];
[Link]("antwort").innerHTML =
[Link]("titel")[0].[Link] + "<br />" +
[Link]("url")[0].[Link] + "<hr />" +
[Link]("titel")[1].[Link] + "<br />" +
[Link]("url")[1].[Link];
}
318 13 Asynchrone Programmierung
};
[Link](null);
}
…
In dem Beispiel navigieren wir auf einem node-Objekt, das über responseXML geliefert
wird. Dabei nutzen wir die schon bekannten Methoden und Eigenschaften des node-Ob-
jekts, wie wir sie schon an diversen Stellen im Buch verwendet haben, nur eben auf einem
auf XML basierenden node-Objekt statt einem erzeugenden HTML-Fundament. Aller-
dings kann diese Navigation in älteren Browsern Probleme machen. Den meisten Proble-
men gehen Sie aus dem Weg, wenn Sie zur Selektion eines Elements in der Serverantwort
mit getElementById oder getElementsByTagName arbeiten und konkret damit den Knoten
selektieren, den Sie aktuell in einem Schritt verarbeiten (Textinhalt oder Attributwerte)
wollen. Über die Knoteneigenschaft firstChild erhalten Sie den Textknoten eines Element-
knotens und dessen Eigenschaft data ist der Inhalt. Genau das nutzen wir hier. Auf Attri-
bute und deren Werte greifen Sie mit getAttributeNode und dann der Eigenschaft nodeVa-
lue zu. Sie können auch mit innerHTML etc. arbeiten (Abb. 13.4). Im Grunde können Sie
alle Techniken anwenden, die Sie bei der Navigation auf einem DOM-Baum kennen.
{ "webseiten": { "seite": [ {
"titel": "RJS EDV-KnowHow", "url": "[Link]
"bemerkung": "Homepage"
}, { "titel": "RJS-Blog", "url": "[Link]
"bemerkung": "Blog"
}, { "titel": "Fliegerblog", "url": "[Link]
"bemerkung": "Flugtagebuch" } ] } }
Sie sehen auch in der JSON-Struktur ein Root-Element webseiten, das als Inhalt ein
Array mit Namen seiten enthält. Und jedes Element im Array seiten besteht aus den
Elementen titel, url und bemerkung. Die Webseite ajax_6.html bleibt bis auf irrelevante
Änderungen gleich. Von Interesse ist nur die hier jetzt referenzierte JavaScript-Datei
ajax_6.js:
Sie sehen, dass wir im Fall von JSON wieder responseText verwenden – im Gegensatz
zu der Verwertung von XML. Sie sehen aber auch, dass wir über die Klasse JSON einen
Parser verwenden, der aus dem JSON-String ein Objekt erzeugt. Denn indem wir diese
Funktion auf die JSON-Struktur in responseText anwenden, erhalten wir ein Objekt,
320 13 Asynchrone Programmierung
auf dem wir navigieren können. Die verschachtelten JSON-Strukturen sind in so einem
Objekt über eine gewöhnliche Punktnotation zugänglich, einfach und vollkommen ana-
log zur normalen objektorientierten Vorgehensweise. Sie gehen also vom Root-Element
webseiten zum entsprechenden Array-Element (über den Index) und dann zu dessen
Inhaltselement, das Sie interessiert (Abb. 13.5).
[Link] Header-Auswertung
Es kann interessant sein, wenn man die vom Server gesendeten Header-Felder auswertet.
Das kann man bei einer Ajax-Anfrage gut machen, wenn man die Methode getAllRe-
sponseHeaders verwendet ([Link]).
…
<script src="lib/js/ajax_7.js" type="text/javascript"></script></
head>
<body><h1>Klicken Sie auf den Button</h1><button id="frage">OK</
button>
<div id="antwort"></div></body></html>
Abb. 13.5 JSON-Daten lassen sich noch einfacher und zuverlässiger wie XML-Daten im Brow-
ser verarbeiten
13.4 Deferred Object und Promises 321
Je nach verwendetem Server erhalten Sie eine Menge aussagekräftiger Daten wie hier
bei unserem selbstprogrammierten [Link]-Webserver (Abb. 13.6).
Bei asynchroner Kommunikation wie bei Ajax oder Web Workern kommt es unter Um-
ständen zu „Verzögerungen“, denn ein angefragter „Server“ antwortet oft nicht unmittel-
bar, sondern wenn es die Bedingungen gestatten. Oder durch Engpässe im Netzwerk
322 13 Asynchrone Programmierung
kommt eine Antwort verzögert an. Darauf muss man reagieren. Deferred Objects und
Promises sind Konzepte, die in JavaScript verwendet werden, um asynchrone Opera-
tionen zu verwalten und zu steuern. Sie ermöglichen es, besser mit asynchronen Code-
Strukturen umzugehen und vermeiden dabei sogenannte „Callback-Höllen“. Das so-
genannte „verzögerte Objekt“ (Deferred Object) ist eine Möglichkeit. Es ist ein verkett-
bares Dienstprogrammobjekt. Es kann mehrere Callbacks in Callback-Warteschlangen
registrieren, Callback-Warteschlangen aufrufen und den Erfolgs- oder Fehlerstatus einer
synchronen und vor allen Dingen auch asynchronen Funktion weiterleiten. Eine Promise
ist ein Objekt, das eine asynchrone Operation darstellt und entweder den Abschluss oder
das Scheitern dieser Operation darstellt. Es repräsentiert einen Wert, der möglicherweise
jetzt, später oder niemals verfügbar sein kann.
13.4.1 Promises
Wenn man verzögerte Aktionen betrachtet, kommt man in modernem JavaScript fast un-
weigerlich zu Promises. Promises bieten allgemein einen Mechanismus zum Verfolgen
des Status einer asynchronen Aufgabe mit bedeutend besserer Robustheit und weniger
13.4 Deferred Object und Promises 323
Chaos als reine Eventhandler. Die Syntax zur Umsetzung in JavaScipt ist dabei aus mei-
ner Sicht eine Mischung aus einer Entscheidungsstruktur und Ausnahmebehandlung.
Erst einmal braucht man ein Promise-Objekt und das wird einfach mit dem Konstruktor
erzeugt, dem ein Callback übergeben wird. Dieser bekommt selbst zwei Callback-Para-
meter übergeben. Diese beiden Callbacks stehen für den Erfolgsfall und den Fehlerfall.
Man kann sie in dem Konstruktor redefinieren, muss es aber nicht. Schematisch sieht das
in der Regel so aus:
Beispiel
Die Promise-Syntax bietet einen sauberen und lesbaren Stil für asynchrone Operatio-
nen, indem sie eine Kette [Link]-Methoden mit optionalem catch ermöglichen, um auf
den Erfolg oder das Scheitern einer Operation zu reagieren. Promises können auch mit
async/await verwendet werden, um asynchronen Code synchroner aussehen zu lassen.
Das Promise-Objekt wird in der Praxis meist für asynchrone Aktionen wie den Um-
gang mit Web Workern oder Ajax-Anfragen verwendet. Ein Promise kann sich in einem
von drei oder vier (je nach Quelle) Zuständen befinden:
Ein Promise mit dem Status pending kann entweder zu fulfilled mit einem Wert oder re-
jected mit Fehlergrund werden. Wenn einer dieser Fälle eintritt, werden die assoziierten
Handler aufgerufen. Ist ein Promise bereits in dem Zustand fullfilled oder rejected und
wird erst dann ein entsprechender Handler hinzugefügt, dann wird dieser Handler auf-
gerufen.
Wann immer Sie ein Promise-Objekt instanziieren, stehen Ihnen darüber dann zwei
Methoden zur Verfügung, die daran gekettet werden können:
• Die Methode then kann man aufrufen um anzugeben, was passiert, wenn die Aufgabe
erfolgreich gelöst wurde. Diese kann wie gesagt auch verkettet werden.
324 13 Asynchrone Programmierung
• Mit der Methode catch, deren Aufruf äquivalent zum Aufruf von then ist, kann man
vor allen Dingen Fehler bequem behandeln. Man kettet dazu den Aufruf von catch
einfach an den Aufruf von then hinten dran.
Damit können Sie entscheiden, was als nächstes nach dem Abschluss einer asynchronen
Aufgabe passiert. Angenommen, Sie wollen ein Bild laden und das machen Sie in einer
Methode. Dann ginge eine Zusicherung der nächsten Schritte im Erfolgsfall mit then
schematisch so:
Beispiel
getImage('[Link]').then(
function(successurl){
[Link]('info').innerHTML = 'Bild geladen';
},
function(errorurl){
[Link]('info').innerHTML = 'Fehler beim Laden';
}
); ◄
Soll eine Fehlerbehandlung mit catch erfolgen, geht das etwa schematisch so:
Beispiel
getImage('[Link]').then(function(successurl){
[Link]('info').innerHTML = 'Bild geladen';
}).catch(function(errorurl){
[Link]('info').innerHTML = 'Fehler beim Laden';
}) ◄
"use strict";
[Link]("Erzeuge Promise.");
const promise = new Promise((resolve, reject) => {
// Asynchrone Operation, z.B. API-Aufruf
setTimeout(() => {
// Erfolgreiche Beendigung
resolve('Daten erfolgreich geladen.');
// Oder im Fehlerfall
reject(new Error('Fehler beim Laden der Daten.'));
}, 2000);
});
13.4 Deferred Object und Promises 325
[Link]("Promise in Wartestellung.");
[Link]((result) => {
[Link](result);
}).catch((error) => {
[Link](error);
});
Die Verzögerung wird natürlich simuliert. Beachten Sie, dass auch [Link] eine Methode
setTimeout kennt und das nicht (!) die DOM-Methode [Link] ist.
Das ist die Ausgabe nach erfolgreichem Abschluss der verzögerten Aktion:
Erzeuge Promise.
Promise in Wartestellung.
Daten erfolgreich geladen.
Deferred Objects bezeichnet ein Muster bzw. Pattern, das die Erstellung und Verwaltung
von Promises vereinfachen soll. Es handelt sich also im Grunde um ein „gewöhnliches“
Objekt, das aber als Eigenschaft ein Promise verwaltet. Im Wesentlichen stellt ein Defer-
red-Objekt eine Promise dar, die von externem Code manuell aufgelöst oder abgelehnt
werden kann. Das ermöglicht die manuelle Kontrolle über den Status und das Ergebnis
einer asynchronen Operation.
"use strict";
function fetchData() {
const deferred = {};
[Link] = new Promise((resolve, reject) => {
[Link] = resolve;
[Link] = reject;
});
setTimeout(() => {
[Link]('Daten erfolgreich geladen');
// Oder im Fehlerfall
[Link](new Error('Fehler beim Laden der Daten'));
}, 2000);
return deferred;
}
[Link]("Erzeuge Deferred Object");
const deferred = fetchData();
[Link]((result) => {
[Link](result); // Ausgabe: Daten erfolgreich geladen
}).catch((error) => {
13.5 Web Worker 327
[Link](error);
});
[Link]("Wir warten");
Beachten Sie, dass die Benennungen der Eigenschaft, aber auch Parameter der Callback-
Funktion zwar sprechend, aber willkürlich sind. Das Verhalten resultiert ausschließlich
aus dem Promise. Das ist die Ausgabe nach dem Abschluss der verzögerten Aktion:
Erzeuge Deferred Object.
Wir warten.
Daten erfolgreich geladen.
In der Praxis werden Promises häufiger verwendet als Deferred-Objekte, da Promises
einen standardisierten und einfacheren Ansatz bieten. Deferred-Objekte können jedoch
in speziellen Fällen nützlich sein, z. B. wenn Sie eine asynchrone Operation manuell
steuern müssen.
Bei Ajax kommunizieren ein Client via HTTP mit einem Webserver, aber man kann sich
auch vorstellen, dass so eine asynchrone, ereignisgesteuerte Kommunikation zwischen
zwei Skripten auf einem Rechner oder gar in einem Programm oder Skript ausgeführt
wird. Etwa mit Web Workern.
Bei einem Web Worker handelt es sich laut ECMAScript um ein Skript, das im Hinter-
grund laufen kann. Der wesentliche Vorteil ist, dass so ein Skript die normale Benutzer-
interaktion beziehungsweise Webseite nicht blockiert. Es läuft fast wie im Fall von ech-
tem Multithreading parallel dazu ab.
Multithreading nennt man auch Nebenläufigkeit und der Begriff bezeichnet das gleichzeitige Ab-
arbeiten mehrerer Ausführungsstränge (Threads) innerhalb eines einzelnen Prozesses. Diese Aus-
führungsstränge sind nicht zwingend voneinander abgeschottet und müssen gegebenfalls vom Pro-
zess selbst synchronisiert werden. Bei einer scheinbar gleichzeitigen Abarbeitung von Aufgaben
kann auch durch geschickte (und meist schnelle) Wechsel zwischen sequentiell ausgeführte Auf-
gaben gewechselt werden, was insbesondere bei nur einem Prozessor oder allgemein einer nicht
multithreadingfähigen Umgebung auch so gemacht werden muss. Für den Anwender oder An-
wendungsprogrammierer ist dieses Hintergrundprozedere aber in der Regel weder erkennbar noch
interessant.
Es obliegt natürlich der Browser-Engine und der Plattform, ob das Verfahren als ech-
tes Multithreading durchgeführt wird und wie genau die parallelen Prozesse organisiert
und abgearbeitet werden. Was letztendlich für JavaScript-Programmierer gleichgültig ist,
328 13 Asynchrone Programmierung
da „oberhalb“ dieser internen Vorgänge programmiert wird. Natürlich muss aber voraus-
gesetzt werden, dass Web Worker überhaupt unterstützt werden.
Erzeugt werden Web Workers ganz einfach als Objekte vom Typ der Klasse (oder ge-
nauer Prototyp) Worker. Man instanziiert also einfach den Prototypen mit einem Konst-
ruktor. Beispiel:
Beispiel
Als Parameter wird ein Skript übergeben, das im Hintergrund ausgeführt werden soll.
Dabei adressieren Sie das Worker-Skript relativ zur Position der Webseite bzw. dem
Wurzelskript! Die Anweisung zur Erzeugung von dem Objekt startet auch unmittelbar
den Worker-Prozess!
Das Nachladen eines Skripts, was ja bei einem Web Worker stattfindet, ist in
ein Sicherheitskonzept eingegliedert. Wenn Sie etwa über das file-Protokoll
arbeiten, wird das Nachladen normalerweise unterbunden.
Was Sie konkret für eine Aufgabe an einen Web Worker übergeben, ist vollkommen frei-
gestellt. In der Regel wird es aber eine Aufgabe sein, die relativ zeitaufwändig ist und den
normalen Ablauf der Interaktion mit einem Anwender unnötig blockieren würde. Etwa
eine umfangreiche Berechnung. Sie können Web Worker schon beim Laden der Webseite
starten, aber in der Regel ist es sinnvoll, einen Web Worker wie eine klassische Funktion
aufgrund irgendeines Ereignisses (etwa den Klick auf eine Schaltfläche) zu starten.
Aus einem Web Worker heraus haben Sie keinerlei Zugriff auf den DOM. Sie sind
auf pures JavaScript beschränkt. Das entspricht der Situation, die Sie ja auch bei
[Link] haben. Wenn Sie Aktionen durchführen wollen, die sich auf den DOM
auswirken sollen, müssen Sie Nachrichten an den Aufrufer des Workers schicken
und der muss sich darum kümmern.
Sie müssen sich beim Einsatz eines Web Workers darüber klar sein, dass Sie im Grunde
auch hier wie bei Ajax einen Masterprozess haben, der die normale Logik der Appli-
kation steuert (etwa die Anwenderaktionen überwacht), und einen parallelen Prozess
13.5 Web Worker 329
(Slave), der diesem Masterprozess zuarbeiten soll. Nur läuft der halt nicht auf dem Web-
server. Dabei wird der Masterprozess nicht nur den Worker meist irgendwie überwachen,
steuern und ggfls. beenden – diese beiden Prozesse müssen in der Regel auch mit einan-
der kommunizieren. Die Kommunikation mit so einem Hintergrundprozess erfolgt mit
einem JavaScript-Eventhandler onmessage, der onstatereadychange bei Ajax ähnlich ist
und bei dem wie üblich ein Callback registriert wird.
Beispiel
[Link] = function(ev){…}; ◄
Wenn von dem Web Worker ein Ergebnis geliefert wird, wird die entsprechend referen-
zierte Funktion ausgeführt. Sollte der Callback einen Parameter haben, ist darüber ein
Mitteilungsobjekt verfügbar.
Das Worker-Objekt selbst kann nun mittels postMessage Nachrichten an seinen Er-
zeuger versenden und von diesem auch erhalten. Dazu dient ein Parameter, der der Me-
thode übergeben werden kann. Wenn man vom Masterprozess aus Daten an den Wor-
ker übergeben will, notiert man die Methode postMessage im Masterprozess selbst über
das vorangestellte Objekt des Workers. Im Worker kann man die Methode ohne voran-
gestelltes Objekt notieren oder this bzw. self verwenden.
Ein gestarteter Web Worker kann auf Befehle und Nachrichten von seinem Master-
prozess warten. Deshalb muss man einen Web Worker in der Regel wieder explizit
anhalten – sofern er nicht so programmiert ist, dass es nach einer gewissen Aufgabe sich
selbst beseitigt. Wir werden das in einem Beispiel sehen. Um einen Hintergrundprozess
anzuhalten beziehungsweise zu beenden, stellen Worker-Objekte die Methode terminate
zur Verfügung.
Kommen wir zu ein paar Varianten, wie man Web Worker einsetzen kann. Diese werden
in modernen Browsern unterstützt. Die Art der Nutzung von Web Workers ist nur durch
Ihre Fantasie vorgegeben. Hier finden Sie also nur ein paar Anregungen, wie man da vor-
gehen kann.
330 13 Asynchrone Programmierung
…
<link rel="stylesheet" type="text/css" href="lib/css/[Link]" />
<script type="text/javascript" src="lib/js/ww_1.js"></script></head>
<body><h1>Web Worker</h1><button>Starte Hintergrundprozess</button>
<button>Ende Hintergrundprozess</button>
<button>Weitere Webseitenaktivität</button>
<div id="antwort1"></div><div id="antwort2"></div></body></html>
Über den ersten Button wollen wir einen Hintergrundprozess starten, der über den zweiten
Button wieder angehalten werden kann. Die dritte Schaltfläche soll eine parallel weiter
mögliche Interaktion mit dem Anwender demonstrieren. Wir werden darüber parallel zur
Ausführung des Hintergrundprozesses Daten vom Anwender entgegennehmen und in der
Webseite ausgeben. Die CSS-Datei ist irrelevant und sorgt nur für etwas Gestaltung. In der
referenzierten JavaScript-Datei ww_1.js wird das Worker-Objekt erzeugt und gesteuert.
var th = null;
function init() {
[Link]("button")[0].onclick = function() {
th = new Worker("lib/js/worker/[Link]");
[Link] = function(ev) {
[Link]("antwort1").innerHTML = [Link];
};
[Link] = function(ev) {
[Link]("antwort1").innerHTML = [Link];
};
};
[Link]("button")[1].onclick = function() {
[Link]();
alert("Ende Web Worker");
};
[Link]("button")[2].onclick = function() {
[Link]("antwort2").innerHTML = [Link]();
};
}
[Link] = init;
Wie beschrieben wird ein Worker-Objekt erzeugt und zwar dann, wenn man in der Web-
seite auf die erste Schaltfläche klickt. Die Referenz auf den Web Worker ist in einer
globalen Variablen th abgelegt. Darüber wird eine anonyme Funktion mit onmessage bei
13.5 Web Worker 331
diesem Objekt registriert. Diese wird die vom Web Worker zurückgegebenen Daten in
der Webseite anzeigen. Dazu dient der Parameter der anonymen Funktion, der in diesem
Konzept über data die Antwort enthält. So ein Standardverhalten finden Sie ja in Java-
Script sehr oft. Vollkommen analog implementieren wir mit onerror eine Fehlerreaktion.
Solch ein Fehlereignis hat drei interessante Eigenschaften.
Tipp Statt mit onmessage können Sie zur Registrierung eines Callback auch
mit addEventListener für das Event message oder das Event error arbeiten. Das
ist gleichwertig. Beispiel:
[Link]('message', function(ev) {
…
}, false);
Bei der Ereignisbehandlung für den Klick auf die zweite Schaltfläche sehen Sie die Be-
endigung des Hintergrundprozesses über die Methode terminate. Da der Web Worker
in einer globalen Variablen th referenziert wird, können sie darüber auf den Web Wor-
ker zugreifen. Die Reaktion auf die dritte Schaltfläche zeigt in diesem Beispiel ziemlich
deutlich, dass der Hintergrundprozess auch weiterläuft, wenn eine andere Aktion auf der
Webseite ausgeführt wird. Hier ist das Listing des Quellcodes [Link], der
die Funktionalität des Web Workers beschreibt:
var i = 0;
var zufall = 0;
while (true) {
i++;
zufall = [Link]();
postMessage("Schritt " + i + ", Wert der Zufallszahl: " + zufall);
if (i > 1000000) {
postMessage("Fertig");
break;
}
}
Das Skript macht nicht viel Spannendes. In einem Zufallsprozess werden Zahlen er-
mittelt und das Skript abgebrochen, wenn ein Counter größer als 1.000.000 ist. Die ein-
zig interessanten Stellen sind die mit Rückgabe einer Meldung an den Aufrufer des Web
Workers mit postMessage. Diese Meldung an den Aufrufer des Web Workers beendet
den Hintergrundprozess nicht (Abb. 13.7).
332 13 Asynchrone Programmierung
Abb. 13.7 Ein Hintergrundprozess bei der Arbeit – trotzdem kann der Anwender mit der Web-
seite interagieren
Beachten Sie, dass Sie während der Arbeit des Web Worker natürlich nicht perma-
nent Meldungen zurückgeben müssen, ganz im Gegenteil. Nur zeigt diese perma-
nente Ausgabe in dem Beispiel deutlich, wie Web Worker arbeiten, und sie macht
die Ausführung des Hintergrundprozesses ausreichend zeitintensiv, dass Sie etwas
erkennen können. Bei vielen Aufgaben in der Praxis wird es jedoch ausreichend
sein, wenn die Methode postMessage zur Rückgabe eines Ergebnisses nur einmal
am Ende des Algorithmus aufgerufen wird. Theoretisch muss die Methode post-
Message gar nicht aufgerufen werden, wenn die Arbeit des Web Worker ohne eine
Ergebnismeldung erledigt werden kann. Das ist aber eher unüblich.
…
<script type="text/javascript" src="lib/js/ww_2.js"></script></head>
<body>
<h1>Web Worker</h1><div id="antwort1"></div><div id="antwort2"></
div>
<button id="a">Eins</button><button id="b">Zwei</button></body></
html>
13.5 Web Worker 333
Sie sehen hier zwei Buttons und zwei Ausgabebereiche. In den einen Ausgabebereich
wollen wir rekursiv, aber mit setTimeout verzögert, aus dem Hauptskript schreiben. Das
hat noch nichts mit dem Worker zu tun, sondern soll die Aktionen in dem Hauptstrang
simulieren.
Mit Klicks auf die beiden Schaltflächen soll jeweils ein Web Worker gesteuert wer-
den, der in den anderen Ausgabebereich schreibt. Dem Web Worker wollen wir dabei
gezielt einen Wert übergeben. Das ist erst einmal das Skript ww_2.js:
Beim Laden des Skripts wird automatisch bereits ein Web Worker gestartet. Wenn auf
den ersten Button geklickt wird, bekommt der Worker mit postMessage den Wert 0 über-
geben und beim zweiten Button den Wert 1. Parallel zu dem Web Worker läuft die rekur-
sive Funktion rek, die mit setTimeout eingebremst wird. Schauen wir uns die Worker-
Syntax an ([Link]):
i++;
if (i % nachrichtwert == 0)
postMessage("Wert von i: " + i);
if (i > abbruchwert) {
postMessage("Funktion 1 ist fertig"); break;
}
}
}
function fkt2() {
var i = 0;
while (true) {
i++;
if (i % nachrichtwert * 3 == 0)
postMessage("Zufallszahl: " + [Link]());
if (i > abbruchwert) {
postMessage("Funktion 2 ist fertig"); break;
}
}
}
Der Worker führt beim Start noch keine weitere Aktion aus, als auf ein message-Si-
gnal zu hören, das vom Starter per postMessage gesendet wird. Statt der direkten Ab-
arbeitung einer Schleife, wie im letzten Beispiel, haben wir hier also einen Worker, der
bloß als eine Art „Server“ mit einem Listener auf Eingaben wartet, um dann erst gezielt
Aufgaben auszuführen. Es wird auch in der Zeit keine Ausgabe oder sonst ein Ergebnis
einer sinnvollen Aktion geliefert (Abb. 13.8). Kommen dann die Eingaben bzw. Signale
vom Starter, wenn ein Anwender auf eine der beiden Schaltflächen klickt, wird je nach
dem Wert in [Link] eine andere Funktion aufgerufen. Den Wert von [Link] sendet der
Worker in diesem Beispiel zu Demonstrationszwecken wieder an den Aufrufer, der ihn
dann anzeigen kann. Beide Funktionen in dem Beispiel erfüllen keine wirklich sinnvollen
Aufgaben, sind aber wieder ausreichend zeitintensiv, dass Sie die Ausführung beobachten
können (die Funktionen geben auch wieder Zwischenergebnisse zurück). Ist eine Funk-
tion fertig, wird eine abschließende Meldung zum Aufrufer geschickt. Beachten Sie, dass
mit dem Ende der jeweiligen Funktion der Worker nicht beendet wird. Jeder Klick auf
einen Button sendet ein Signal an den Worker und er wird die entsprechende Funktion
aufrufen. Da diese im Worker selbst allerdings nicht in verschiedene Prozesse aufgeteilt
sind, blockiert die Ausführung einer Funktion erst einmal die Ausführung der nächsten
Funktion. Diese werden in eine Aufrufschlange einsortiert und sukzessive abgearbeitet.
Sie sehen, dass wir in dieser Variante zwar auch eine globale Variable (th1) zur Referen-
zierung auf den Worker haben, diese aber zuerst mit null initialisiert wird. Erst beim Klick
auf eine der Schaltflächen erzeugen wir jeweils den Worker und schicken ihm direkt das
336 13 Asynchrone Programmierung
gewünschte Signal. Wichtig ist, dass wir vorher bei jedem Klick zuerst einen eventuell lau-
fenden Worker mit terminate beenden. Beachten Sie, dass die Variable immer den gleichen
Worker referenziert. Damit können wir die Aufgaben des Worker gezielt abbrechen und
eine neue Aufgabe unmittelbar starten, wobei auch das natürlich ein spezielles Verhalten
ist, das nicht immer sinnvoll sein muss. Sie müssen Ihre eigene Logik hier anwenden.
13.6 WebSockets
Grundsätzlich existiert bei RIAs das Problem, dass die Verbindung zwischen Client und
Server mit einer gewissen Latenz (Verzögerungszeit) behaftet ist. Das liegt im Wesent-
lichen an der Arbeitsweise von HTTP. Grundsätzlich arbeitet man dort ja bekanntlich mit
einem Anforderungs- und Antwortmuster, bei dem immer der Client die Anforderung star-
tet. Selbst beim Einsatz von Ajax ändert sich dieser grundsätzliche Vorgang nicht – die
gesamte HTTP-Kommunikation wird beharrlich vom Client gesteuert. Damit ist eine
Ajax-Applikation zwar in vielen Situationen viel schneller als eine konventionelle Web-
applikation, aber es gibt immer noch relevante Verzögerungen, da jede neue Aktion vom
Client angestoßen werden muss und viel überflüssige Protokolldaten hin- und hergeschickt
werden. Mit WebSockets versucht man eine möglichst geringe Latenz zu implementieren,
die auch im Web (fast) eine Echtzeitverbindung zwischen Client und Server aufbauen.
Socket-Verbindungen allgemein kennt man schon lange aus vielen Programmiersprachen. Sockets
gehen im Kern auf UNIX zurück. UNIX-Sockets stellen eine Art rudimentäres (und damit leicht-
gewichtiges und performantes) Grundobjekt des Betriebssystems für die Internetkommunikation dar,
welches das TCP-Protokoll unterstützt. Sockets verfügen dazu über diverse leichtgewichtige Daten-
strommethoden zur Ein- und Ausgabe, was das Auslesen und Schreiben von Daten recht einfach macht.
Um allgemein eine Verbindung über Sockets aufzubauen, erstellen Sie in den meisten
Programmiersprachen für den Client eine Instanz eines Socket. Dabei geben Sie meist
einfach nur den gewünschten Zielserver und einen Port für die Kommunikation sowie
eventuell eine Information über die Art der ausgetauschten Daten an. Auf der Gegen-
seite (dem Server) muss nun auf dem angegebenen Port ein ServerSocket lauschen, das
13.6 WebSockets 337
durch verschiedene Techniken wie Cross-Origin Resource Sharing ausgehebelt, wenn das
ein Webbrowser unterstützt. Die SOP-Begrenzung betrifft aber theoretisch auch die Web-
Socket-Verbindung, wenn eine Kommunikation über die Begrenzung auf den Ursprungs-
ort des Clients möglich sein soll. Allerdings hat man sich bei der Spezifikation der Web-
Socket-Technik dafür entschieden, dass die Kommunikation zwischen Parteien in jeder
Domain möglich ist. Damit muss man bei einer Kommunikation sich selbst darum küm-
mern, dass nur vertrauenswürdige Clients und Server kommunizieren. Grundsätzlich kann
ein Server entscheiden, ob sein Service für alle Clients verfügbar wird oder nur für die-
jenigen, die auf einer Gruppe gut definierter Domains residieren.
Um mit JavaScript ein WebSocket aufzubauen, erzeugen Sie eine Instanz über den
WebSocket-Konstruktor und geben dabei als ersten Parameter den Host samt optional
dem Port sowie als zweiten Parameter optional ein gewünschtes Subprotokoll für das
Kommunikationsprotokoll zwischen Client und Server an. Dabei kann es sich beim
zweiten Parameter um einen String oder um ein Array von Strings handeln. Jeder String
sollte einen Subprotokollnamen darstellen. Der Server akzeptiert nur eines der weiter-
gegebenen Subprotokolle im Array. Die Subprotokollnamen müssen den registrierten
Subprotokollnamen in der IANA-Registrierung entsprechen. Zum Beispiel den seit Fe-
bruar 2012 registrierten Subprotokollnamen soap. Das wäre das Erzeugen einer Web-
Socket-Instanz mit Port und Subprotokoll:
Beispiel
Und so geht das Erzeugen einer WebSocket-Instanz ohne Port und Subprotokoll:
Beispiel
Beachten Sie das Protokoll ws. Das ist das URL-Schema für WebSocket-Verbin
dungen. Außerdem gibt es das Protokoll wss für sichere WebSocket-Verbindungen.
Zu Beginn einer WebSocket-Verbindung führen Server und Client wie immer einen
Handshake durch, bei dem Header-Informationen getauscht werden. Dieser Handshake
13.6 WebSockets 339
ähnelt auch bei ws vom Aufbau dem HTTP-Header (bzw. bei wss dem HTTPS-Header)
und ist vollständig abwärtskompatibel zu diesem, was die Nutzung des Standard-HTTP-
Ports 80 sowohl für normale HTTP-Kommunikation als auch für die Websocket-Nutzung
ermöglicht.
Das Senden von Daten an den Server erfolgt mit einer Methode send, wie man sie von
Ajax kennen sollte. Als Parameter geben Sie die zu sendenden Daten an.
Beispiel
[Link]('Ping-Pong'); ◄
Die Methode unterstützt Strings, kann aber in neuen Spezifikationen auch Binärmit-
teilungen senden.
Für die Reaktion auf die Antwort des serverseitigen WebSockets implementieren Sie –
ähnlich wie bei Ajax oder Web Workern – ein Reaktionsverfahren über Callbacks, die
Sie einfach an das Verbindungsobjekt per Eventhandler anfügen. Erst nachdem der Call-
back zum Öffnen der Verbindung registriert wurde, sollte die Versendung der ersten An-
frage an den Server erfolgen.
Beispiel
[Link] = function () {
[Link](…);
…
}
Beispiel
Beispiel
Um einem WebSocket jetzt eine Gegenstelle zur Verfügung zu stellen, benötigen wir
natürlich einen passenden Server. Nun ist die Nutzung von WebSockets samt dem ws-
Protokoll noch relativ neu und herkömmliche Webserver können manchmal noch nicht
damit umgehen bzw. sind ineffektiv bei der Verwaltung von mehreren offenen Web-
Socket-Verbindungen. [Link] bietet aber entsprechende Unterstützung, wobei Sie u. U.
die benötigten Module noch mit npm installieren müssen. Betrachten wir nun eine ein-
fache WebSocket-Anwendung.
[Link] Die Clientseite
Hier wäre zunächst der Client und dessen Basis-Webseite [Link].
…
<script src="js/[Link]"></script></head><body>
<h1>Ein WebSocket nutzen</h1><button id="btn">Frage den Server</button>
<hr /><label>Antwort des Servers: </label><div id="info"></div></body>
</html>
Sie sehen eine Webseite mit einer Schaltfläche und einem leeren Div-Bereich, in dem wir
die Antworten vom Server anzeigen wollen. Das folgende Listing zeigt nun eine Java-
Script-Datei, in der eine WebSocket-Verbindung aufgebaut wird. Sowohl beim Laden der
JavaScript-Datei (ws_1.js) als auch nach einem Klick auf den Button wird eine Anfrage
an den Server geschickt.
};
[Link] = function() {
[Link]("Login");
};
[Link] = function(error) {
[Link]('info').innerHTML = 'WebSocket-Fehler: ' +
error;
};
[Link] = function(e) {
[Link]('info').innerHTML = [Link];
};
Sie sehen die Erzeugung eines WebSocket-Objekts mit einer URL und einer Portangabe und
dann die Einrichtung der Callbacks für die Etablierung der Verbindung (ein String "Login"
wird dann mit send zum Server geschickt), einen Fehler und die Ankunft von Nachrichten
vom Server. In den letzten beiden Fällen wird die Meldung vom Server einfach in dem
Div-Element in der Webseite angezeigt. Nach dem Fertigstellen des DOM-Baums wird ein
Eventhandling für den Klick auf die Schaltfläche eingerichtet, die einfach eine Zufallszahl
an den Server schickt. Damit kann man jederzeit weitere Anfragen an den Server schicken.
Beispiel
});
[Link](1337, function() { });
wsServer = new WebSocketServer({
httpServer: server
});
[Link]('request', function(request) {
var connection = [Link](null, [Link]);
[Link]('message', function(message) {
if ([Link] === 'utf8') {
if (message.utf8Data=="Login") {
[Link]("Verbindung etabliert");
}else {
[Link](new Date().toDateString() + ": "
+ message.utf8Data);
}
}
});
[Link]('close', function(connection) {
});
});
Der Code basiert in der Grundstruktur auf dem Webserver, den wir bereits nutzen.
Der Server wird nun in einer Konsole mit node [NameDerJavaScriptDatei] gestartet.
Anschließend können wir aus dem Browser heraus Anfragen an den Server schicken
(Abb. 13.9).
13.7 Webservices 343
Die eigentliche Logik auf der Serverseite ist natürlich in dem Beispiel begrenzt, aber
Sie sehen hier sowohl die Kommunikation per Socket als auch die grundsätzliche Ver-
wendung von JavaScript auf dem Server.
13.7 Webservices
• Eine Schnittstelle, die definiert, wie auf die Dienste zugegriffen werden kann.
• Eine Implementierung, die die eigentliche Funktionalität bereitstellt.
Die Kommunikation zwischen Anwendungen und Webservices erfolgt in der Regel über
das HTTP-Protokoll, obwohl auch andere Protokolle wie SOAP (Simple Object Access
Protocol) und REST (Representational State Transfer) verwendet werden können.
Um einen Webservice mit [Link] zu erstellen, benötigen wir zunächst ein Modul
wie [Link], das die Erstellung von HTTP-Servern und das Routing von Anfragen er-
leichtert. Bei Bedarf erfolgt die Installation von [Link] wie üblich:
Beispiel
• [Link] ist ein beliebtes Framework für [Link], das die Erstellung von Web-
anwendungen und APIs vereinfacht, indem es eine einfache Schnittstelle für Routen
und Middleware bereitstellt. Das http-Modul, was wir schon verwendet haben, erlaubt
eigene HTTP-Server zu erstellen und [Link] baut auf diesem Modul auf und bietet
eine abstraktere und benutzerfreundlichere API.
• Mit Express können Sie Endpunkte definieren, auf die der Server reagieren soll, und
die entsprechenden Handler-Funktionen bereitstellen. Das nennt man Routing.
344 13 Asynchrone Programmierung
• Über die [Link]-Methode von Expressjs kann man JSON-Daten als Antwort auf An-
fragen zu senden.
• Die Client-Anfrage basiert auf dem node-fetch-Modul. Damit kann eine HTTP-An-
frage an den Webservice gesendet und die Antwort verarbeitet werden.
• Für eine nichtstandardisierte Kommunikation benötigt man eine Port-Zuweisung. Der
Port, auf dem der Server lauscht, wird entweder durch die Umgebungsvariable PORT
oder standardmäßig auf Port 3000 festgelegt.
Erstellen wir also eine Datei namens [Link] mit folgendem Code:
Dieser Code erstellt einen einfachen [Link]-Server, der eine Liste von Büchern
zurückgibt, wenn eine GET-Anfrage an `/api/books` gesendet wird. Kommen wir nun
zum Erstellen des Clients über eine Datei namens [Link] im gleichen Verzeichnis wie
[Link]:
}).catch(error => {
[Link]('Fehler:', error);
});
Dieser Client verwendet das Modul node-fetch, um eine GET-Anfrage an den Webser-
vice zu senden und die Liste der Bücher auszugeben. Wie Sie sehen, gibt es eine Pro-
mise-Logik mit der Verkettung ü[Link] [Link]. Bei Bedarf erfolgt die Installation
von node-fetch wie üblich:
Beispiel
Das Ausführen des Webservice und des Clients erfolgt so, dass Sie zuerst im Terminal
mit node [Link] den Server bzw. Service und dann in einem anderen Terminal mit
node [Link] den Client starten (Abb. 13.10).
Selbstverständlich ist es üblich, dass in der Praxis eher nicht localhost verwendet
wird.
Die Tatsache, dass man mittlerweile mit JavaScript weit mehr machen kann als
„nur“ Webseiten manipulieren oder Benutzereingaben im Browser zu plausibilisie-
ren, ist zwar noch nicht überall bekannt, aber informierten Stellen klar. Wir haben
ja in den letzten Kapiteln viele interessante Anwendungen jenseits des ursprüng-
lichen Einsatzgebietes von JavaScript kennengelernt. In diesem Kapitel ergänzen
wir noch interessante erweiterte Features, die meist mit HTML5 assoziiert sind.
Ein Browser arbeitet mit einer Sandbox. Das beschreibt im Wesentlichen einen Sicher-
heitsmechanismus, der den Browser und seine Prozesse isoliert, um das Betriebs-
system und andere Anwendungen vor potenziell schädlichem Code zu schützen, der
auf einer Webseite ausgeführt wird. Das Prinzip der Sandbox beruht auf der Idee, dass
jede Webseite in einer eigenen, begrenzten Umgebung ausgeführt wird, die von ande-
ren Webseiten und dem Hauptsystem getrennt ist. Zudem gibt es Beschränkungen für
die Aktionen, die eine Webseite ausführen kann sowie eine Ressourcenkontrolle und eine
Rechteverwaltung samt einer kontrollierten, sicheren Laufzeitumgebung für die Aus-
führung von Skriptsprachen. Die Sandbox unterbindet u. a. auch Zugriffe auf das Datei-
system eines Rechners. Was wiederum bedeutet, dass Schreiben und Lesen von Daten
von einem Rechner via Browser und JavaScript nicht frei möglich ist. Allerdings im
Rahmen von vorgegebenen Strukturen. Damit kann man dann Daten in einem Browser
durchaus speichern als auch wieder auslesen.
© Der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH, ein 347
Teil von Springer Nature 2025
R. Steyer, Programmieren mit JavaScript, [Link]
348 14 Erweiterte Techniken
Über die Entwicklertools moderner Browser können Sie sehen, was sich in den
lokalen Speicherbereichen eines Browsers befindet (Abb. 14.2).
14.1.1 Cookies
Beispiel
[Link] = "meinCookie=42";.
14.1 Daten im Browser speichern 349
Eine wichtige optionale Eigenschaft ist die expires-Eigenschaft, mit der ein Gültigkeits-
datum eines Cookies gesetzt werden kann oder domain für den URL, für welchen ein
Cookie gültig ist. Cookie-Werte dürfen keine Leerzeichen und keine Sonderzeichen ent-
halten. Deshalb müssen solche Zeichen mit den JavaScript-Toplevel-Funktionen beim
Schreiben maskiert und beim Lesen wieder entschlüsselt werden. Des Weiteren müssen
verschiedene Aktionen wie das Bestimmen der Länge eines Werts oder das Trennen an
bestimmten Zeichen im String durchgeführt werden (dies sind recht analoge Vorgänge,
wie man sie beim Übertragungsprozess mit Formulardaten anwendet). Nun ist es auch
nicht ganz so einfach, wenn man komplexere Informationen in einem Cookie speichern
und wieder auslesen möchte. JavaScript bietet da erst einmal keine große Unterstützung.
Aber es hat sich viele Jahre eine Bibliothek für das Cookie-Management einen Satz von
freien Funktionen etabliert, die ursprünglich ein Programmierer namens Bill Dortch ent-
wickelt hat und kostenlos bereitgestellt wurden. Das soll es zu Cookies aber gewesen
sein, denn diese Technologie ist sowohl veraltet, unzuverlässig und viel zu beschränkt.
Mit HTML5 wurde lokale Speicherung von Daten persistenter, mächtiger, leichter zu
handhaben und schneller abrufbar als mit Cookies. Vor allen Dingen wurde die Menge
der speicherbaren Daten erheblich größer werden. Im Konzept von HTML5 werden drei
Möglichkeiten dem lokalen Speichern von Daten beim Benutzer zugeordnet:
• Local Data Storage oder kurz Local Storage. Grundsätzlich sollten diese Daten per-
manent im Speicher des Browsers bleiben, bis sie mit speziellen Methoden oder auto-
matischen Bereinigungen vom Browser beseitigt werden.
• Session Data Storage oder kurz Session Storage. Hiermit speichern Sie Informationen
nur so lange, wie eine Sitzung im Browser dauert. Da in den Browsern auch jeder
Tab bzw. jedes Browserfenster als eigene Sitzung verwaltet wird, gibt es auch jeweils
einen eigenen Sitzungsspeicher. Deshalb kann man von einem Tab/Fenster nicht auf
die Daten in einem anderen Tab/Fenster zugreifen, was beim Local Data Storage
möglich ist.
• Web SQL Database (worauf wir hier nicht weiter eingehen)
Das Konzept des Local Storage ist wirklich lokal. Das bedeutet, serverseitige Applika-
tionen können diese gespeicherten Daten nicht direkt auslesen bzw. verändern oder er-
stellen. Die gespeicherten Daten werden nicht wie Cookies zum Server geschickt. Der
Weg des Zugriffs führt immer über JavaScript und damit rein clientseitige Programmie-
rung. Zudem sind gespeicherte Daten allgemein nur für jeweils eine spezifische Domain
und die eigene Browserinstanz zugänglich, wobei sich Browser hier in den genauen De-
tails unterscheiden.
350 14 Erweiterte Techniken
Hintergrundinformation
Ganz alte Browser unterstützen diese Form des lokalen Speicherns nicht. Aber man kann Zugriffe
absichern bzw. testen, ob Unterstützung vorhanden ist. Das sieht dann so aus:
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch(e){
return false;
}
}
Sofern der Browser das lokale Speichern unterstützt, erhalten Sie den Wert true, andernfalls false.
Im Erfolgsfall können Sie das lokale Speichern durchführen, im Fehlerfall greifen Sie z. B. auf
Cookies zurück oder serverseitiges Speichern per Ajax. Analog gehen Sie beim Speichern im Ses-
sionfall vor.
…
<script src="lib/js/ls_1.js"></script></head>
<body><h1>Local Data Storage mit HTML5</h1>
<button id="b1">Speichere Zufallswert</button>
<button id="b2">Lese lokalen Speicher aus</button>
<button id="b3">Lösche lokalen Speicher</button>
<div id="ausgabe"></div></body></html>
Sie sehen eine einfache Webseite mit drei Schaltflächen. Damit wollen wir Daten lokal
speichern, auslesen und löschen. Hier ist die referenzierte externe JavaScript-Datei ls_1.js:
var counter = 0;
function testlokaleSpeicherung() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch(e) {
return false;
}
}
function init() {
if (testlokaleSpeicherung() == false) {
alert("Die lokale Speicherung ist nicht möglich"); return;
}
[Link]("b1").onclick = function() {
[Link]("key" + counter++, [Link]());
};
[Link]("b2").onclick = function() {
[Link]("ausgabe").innerHTML = "";
var j = 0;
for (i in localStorage) {
[Link]("ausgabe").innerHTML +=
[Link](j++) + ": " + [Link](i) + "<br />";
}
};
[Link]("b3").onclick = function() {
counter = 0;
[Link]();
};
}
[Link] = init;
352 14 Erweiterte Techniken
Mit dem Klick auf die erste Schaltfläche speichern wir eine Zufallszahl. Deren Schlüssel
ergibt sich aus dem Token key und dem Wert einer globalen Variablen counter, die wir
am Beginn des Skripts anlegen und nach jedem Speichern eines Werts mittels setItem
um den Wert 1 erhöhen. Mit jedem Klick auf den ersten Button wird also ein neuer Wert
im lokalen Speicher persistent angelegt. Mit Klick auf die dritte Schaltfläche löschen wir
den lokalen Speicher mittels clear und setzen den Zähler zurück, damit über die erste
Schaltfläche neu angelegte Schlüssel-Werte-Paare wieder mit dem Schlüssel key0 be-
ginnen werden. Die Funktionalität zum Auslesen des Inhalts des lokalen Speichers ist
etwas umfangreicher. Wir wollen hier nicht gezielt einen Wert auslesen (was man natür-
lich auch leicht machen kann – beachten Sie das folgende Beispiel), sondern alle ge-
speicherten Werte inklusive der Namen des jeweiligen Schlüssels auslesen und einfach
ausgeben. Dazu arbeiten wir mit einer for…in-Schleife, bei der i den Schlüssel repräsen-
tiert. Synchron zum jeweiligen Schleifendurchlauf zählen wir die lokale Variable j hoch,
die wir als Index in der Methode key verwenden, um darüber den Namen des Schlüssels
abzufragen. Mit getItem(i) ermitteln wir den jeweils gespeicherten Wert.
…
<script src="lib/jsl1_2.js"></script></head>
<body><h1>Session Data Storage mit HTML5</h1>
<form><div id="label">Name</div><input id="name"/><br />
<div id="label">Vorname</div><input id="vorname"/><br />
<div id="label">Strasse</div><input id="strasse"/><br />
<div id="label">PLZ</div><input id="plz"/><br />
<div id="label">Ort</div><input id="ort"/><br />
<div id="label2">Kommentar</div>
<textarea id="kommentar" rows="10" cols="50"></textarea>
</form><hr /><button id="b1">Weiter</button></body></html>
Sie sehen in der Webseite ein konventionelles Formular – aber ohne die Angabe von ac-
tion etc., denn wir wollen die Daten ja nicht zum Server schicken (Abb. 14.1).
Beim Klick auf die Schaltfläche sollen die Daten in dem Sitzungsspeicher abgelegt
und eine neue Seite im Browser aufgerufen werden, die mit diesen Daten dann arbei-
ten kann. Das kennt man so ja von der Sitzungsverfolgung mit serverseitigen Techniken,
aber hier geht das wirklich explizit nur über den Client. Hier ist nur die Datei ls_2.js, in
der die Daten gespeichert werden:
14.1 Daten im Browser speichern 353
function testlokaleSpeicherung() {
try {
return 'sessionStorage' in window && window['sessionStorage'] !==
null;
} catch(e) {
return false;
}
}
function init() {
if (testlokaleSpeicherung() == false) {
alert("Die lokale Speicherung ist nicht möglich"); return;
}
[Link]("b1").onclick = function() {
with (sessionStorage) {
setItem("name", [Link]("name").value);
setItem("vorname", [Link]("vorname").value);
354 14 Erweiterte Techniken
setItem("strasse", [Link]("strasse").value);
setItem("plz", [Link]("plz").value);
setItem("ort", [Link]("ort").value);
setItem("kommentar", [Link]("kommentar").
value);
setItem("metadaten", [Link]({
'browser' : [Link], 'zeit' : new Date().toS-
tring()
}));
}
[Link] = "ls_3.html";
};
}
[Link] = init;
Wie gehabt testen wir, ob die Speicherung überhaupt möglich ist, nur hier auf Basis des
sessionStorage-Objekts, was aber wie schon erwähnt sonst vollkommen identisch mit
einem persistenten Speichern ist. Danach setzen wir für jedes Formularfeld ein eigenes
Schlüssel-Werte-Paar, das wir jeweils mit setItem in dem Sitzungsspeicher ablegen. Das
könnten wir durch die potenziell riesige Datenmenge natürlich auch in einem Schlüssel-
Werte-Paar machen, wenn wir in dem Wert eine geeignete Struktur anlegen – wozu sich
JSON anbietet. Wie das geht, demonstrieren wir hier mit den Metadaten. Mittels JSON.
stringify erzeugen wir aus der JavaScript-Datenstruktur mit zwei Schlüssel-Werte-Paaren
einen JSON-Text, den wir dem Schüssel "metadaten" zuweisen. Das können Sie natür-
lich beliebig komplex werden lassen.
Nach der Speicherung der Daten im Sitzungsspeicher leiten wir mit [Link] zu
einer neuen Webseite weiter. Wenn nun die neue Seite ls_3.html im Browser geladen
wird, werden beim Laden der Seite die gespeicherten Sitzungsdaten ausgelesen und ein-
fach in der Webseite angezeigt. Dabei kann man auch in den Entwicklertools sehen, was
sich gerade in den verschiedenen verfügbaren lokalen Speichern des Browsers befindet
(Abb. 14.2).
Das machen wir so (ls_3.js):
function init() {
[Link]("ausgabe").innerHTML = "";
with (sessionStorage) {
[Link]("ausgabe").innerHTML += getItem("name") +
"<br />" + getItem("vorname") + "<br />" + getItem("strasse") + "<br
/>" + getItem("plz") + "<br />" + getItem("ort") + "<br />" + getI-
tem("kommentar") + "<hr />" + [Link](getItem("metadaten")).browser
+ "<hr />" + [Link](getItem("metadaten")).zeit;
}
}
[Link] = init;
14.2 Geolocation 355
Sie sehen, dass wir mit getItem gezielt die einzelnen Schlüssel auslesen. Insbesondere
die Zeilen sind interessant, wo wir den JSON-Text jeweils mit [Link] in ein JSON-
Objekt umwandeln, um darüber dann wie bei JSON üblich per Punktnotation gezielt die
einzelnen Eigenschaften des JSON-Objekts zu verwerten.
14.2 Geolocation
Nun stellt sich die Frage, wie die Lokalisierung ohne GPS überhaupt erfolgt? Denn ins-
besondere dynamisch von einem Provider zugewiesene IP-Nummern ändern sich ja
und viele Anwender glauben immer noch, dass sie damit nicht zu identifizieren seien.
Das ist ein Trugschluss, wie die Geolokalisierung erschreckend deutlich zeigt. Die ver-
schiedenen Techniken zur Ortsbestimmung sind ziemlich ausgefeilt. Dabei geht es
grundsätzlich darum, automatisiert herauszubekommen, wo Sie (oder genauer – Ihr
Gerät) sich gerade aufhält und wohin Ihr Weg im Fall eines mobilen Endgeräts führt.
Die Geolokalisierung umfasst von der Bedeutung alle denkbaren Verfahren zur Loka-
lisierung, sei es die lokale Zuordnung eines Besuchers einer Webseite, sei es die Orts-
bestimmung eines Anwenders, der eine App auf einem Smartphone ausführt, sei es die
Routenführung mit einem Navigationsgerät.
[Link] GPS
Besonders einfach wird Lokalisierung, wenn GPS (Global Positioning System) verfüg-
bar ist. Dies ist bei mobilen Geräten fast immer der Fall. Lokalisierung per GPS ist exakt
(in der Regel auf wenige Meter genau) und man kann auch die Geschwindigkeit eines
Objekts ziemlich genau bestimmen.
[Link] Funkzellen
Mobile Geräte sind oft in Funkzellen angemeldet und auch darüber zu orten, allerdings
nicht so genau wie mit GPS und die Geschwindigkeit kann nicht bestimmt werden. Die
Genauigkeit der Ortsbestimmung über Funkzellen hängt an der Infrastruktur der Funk-
masten. Bei einer großen Dichte an Funkmasten (etwa in der Stadt) kann die Position
unter Umständen auf einige Meter genau ermittelt werden, während es bei großen Funk-
zellen möglichweise eine Abweichung von mehreren Kilometern geben kann.
[Link] W-LAN
Eine weitere Möglichkeit zu Lokalisierung verwendet W-LAN-Router, wenn deren
Positionen bekannt sind. Diese Technik funktioniert bei allen Geräten, wenn diese mit
dem W-LAN-Router verbunden sind. Die Genauigkeit der Lokalisierung eines einzel-
nen Geräts kann auf wenige Meter genau sein, wobei in einem großen lokalen Netz-
werk (etwa in einer Firma) durchaus große Abweichungen von der tatsächlichen Position
eines einzelnen Geräts auftreten (auch wenn der Router selbst auf wenige Meter genau
lokalisiert werden kann). Nun stellt sich die Frage, wie die Bestimmung der Position
eines W-LAN-Routers und damit der angeschlossenen Geräte überhaupt erfolgt? Und
wer überhaupt diese Positionen kennt? Die Beantwortung der Fragen führt unter ande-
rem zuz MAC-Adresse (Media-Access-Control), die man berücksichtigen kann, und
natürlich die IP-Nummer, und die Angabe, wo diese gesammelt und lokalisiert werden.
14.2 Geolocation 357
Die MAC-Adresse ist eine (im Prinzip) eindeutige Hardwareadresse jedes einzelnen
Netzwerkadapters auf der Welt. Diese kann zur Identifizierung des zugehörigen Geräts
in einem Rechnernetz verwendet werden. Wenn so eine MAC-Adresse eines W-LAN-
Routers an einen Empfänger gesendet wird, kann dieser den Ort bestimmen, woher die
Sendung kam. Aber woher kennt man den Ort des W-LAN-Routers und wer kennt ihn?
Es gibt spezielle GeoLocation Services, etwa von Apple den GeoLocation-Dienst Sky-
hook bzw. WiFi Access Point Database, von Nokia oder Google Gears bzw. die Google
GeoLocation-Dienste. Diese liefern die genaue Position eines W-LAN-Routers, wenn
Sie dessen MAC-Adresse bei einer Anfrage zugesendet bekommen.
Nur wie können diese GeoLocation-Dienste die Position von einem W-LAN-Router
kennen? Am einfachsten ist offensichtlich die manuelle Eingabe einer Adresse durch An-
wender in einem Webformular. Es gibt aber auch Scanner, die die MAC-Adressen von
Routern ausspionieren und dann samt den geografischen Positionen den GeoLocation-
Services-Betreibern zukommen lassen. Letzteres ist nicht etwa an den Haaren herbei-
gezogen und ein Ausnahmefall – das ist der Regelfall. Darüber erhalten die GeoLocation
Services die geografischen Positionen von fast allen W-LAN-Routern weltweil. Denn
einmal senden viele mobile Geräte mit aktiviertem GPS und eingeschaltetem W-LAN
automatisch die MAC-Adressen aller W-LANs in Reichweite und ihre aktuelle Position
an die besagten GeoLocation-Dienste. Eine explizite Verbindung mit den jeweiligen
W-LANs ist dazu nicht vonnöten, denn zur Identifizierung senden die W-LAN-Router die
MAC-Adressen offen mit. Viele Millionen Smartphone-Besitzer betätigen sich damit –
oft unbewusst – als Zuträger für die GeoLocation-Dienste. Selbst wenn sich die Posi-
tion eines bereits bekannten W-LAN-Routers ändert (etwa durch Umzug) – irgendwann
kommt eine Person mit Smartphone und angeschaltetem W-LAN-Scanner/GPS vorbei
und die Position kann in den Datenbanken der GeoLocation-Dienste korrigiert werden.
Aber auch Projekte wie Google StreetView scannen gezielt die Positionen von Routern.
[Link] IP-Adressen
Wenn alle diese Quellen nicht verfügbar sind – selbst über IP-Nummern kann man über
Laufzeitmessungen und Triangulierung Positionen bestimmen und das geht dann selbst
358 14 Erweiterte Techniken
für reine PCs, die ganz ohne W-LAN-Router etc. online gehen. Die Genauigkeit ist da
in der Regel zwar recht gering, aber zumindest die Region lässt sich bestimmen und teils
sogar mehr, wenn bestimmte Voraussetzungen gegeben sind.
Kommen wir nun zu den konkreten Möglichkeiten der Geolokalisierung. In der Geo-
lokalisierung [Link] gibt es eine Methode getCurrentPosition. Darüber
wird die aktuelle Position eines Besuchers einer Webseite ermittelt. Die Methode get-
CurrentPosition bekommt als Parameter zwei Callback-Funktionen übergeben. Der erste
Callback steht für den Erfolgsfall bei der Lokalisierung und der zweite für den Fehler-
fall. Wird der Callback für den Erfolgsfall aufgerufen, so bekommt die dann aufgerufene
Funktion ein Positionsobjekt mit Geodaten als Parameter übergeben. Dieses Objekt ent-
hält eine Reihe von Geoattributen, wobei das Unterobjekt coords mit den Koordinaten
wohl am interessantesten ist. In coords können Sie etwa auf latitude oder longitude für
die Breite und Länge zugreifen. Im Fehlerfall erhalten Sie ein Fehlerobjekt.
Latitude und Longitude ergeben zusammen eine geokodierte Position. Breiten- (La-
titude) und Längengrade (Longitude) werden in Form von Zahlen mit einer Genauigkeit
von bis zu sechs Dezimalstellen angegeben. Die Angabe für die Länge basiert auf der
Entfernung vom Ort Greenwich in England (der Nullmeridian). Breitengrade können
einen beliebigen Wert zwischen –90 und 90 und Längengrade einen beliebigen Wert zwi-
schen –180 und 180 annehmen.
Wenn eine Webseite versucht, eine Ortsbestimmung vorzunehmen, wird im Browser eine ent-
sprechende Meldung angezeigt (Abb. 14.4), wenn der Browser nicht unverantwortlich unsicher
eingerichtet ist.
…
<script src="lib/js/geo_1.js"></script></head>
<body><h1>Geolocation</h1><div id="karte"></div></body></html>
14.2 Geolocation 359
function success(position) {
[Link]('karte').innerHTML = "Latitude: " +
[Link] + "<br />Longitude: " +
[Link];
}
function error(msg) {
[Link]('karte').innerHTML = msg;
}
function init() {
if ([Link]) {
[Link](success, error);
} else {
alert("Fehler bei der Geolokalisierung!");
}
}
[Link] = init;
Zuerst erzeugen wir ein Lokalisierungsobjekt mit den zwei Callbacks. Zur Absicherung
nutzen wir eine Browserweiche, die auf die Verfügbarkeit von [Link]
prüft. Im Callback für den Erfolgsfall verwenden wir coords, um Länge und Breite des
Besucherstandpunkts auszuwerten und anzuzeigen (Abb. 14.3). Beachten Sie, dass Sie
vor dem Lokalisieren in der Regel eine Warnung des Browsers sehen (Abb. 14.4) und
die Lokalisierung bestätigen müssen sowie, dass die Lokalisierung durchaus einige Se-
kunden dauern kann. Was letztendlich auch ein Beweis ist, dass die angesprochenen
Lokalisierungsdienste genutzt werden.
Verfügung stehen. Die Bibliotheken dienen im Kern dazu, dass Sie RIAs auf Basis von
CSS, JavaScript, HTML und letztendlich Ajax erstellen bzw. Ihre Webseiten um inter-
aktive, dynamische Funktionalitäten erweitern können. Unter anderem gibt es meist
JavaScript-Bibliotheken mit Satellitenbildern, Landkarten, Stadtplänen etc. und ver-
schiedenen Steuerelementen. Gekopplet mit CSS-Ressourcen sind diese Karten oft dy-
namisch. Man kann sie verschieben, vergrößern, verkleinern, etc. Solche Karten werden
in einem Bereich der Webseite in einem Gitter mit mehreren kleineren Einzelbildern und
ergänzenden Zusatzinformationen zusammengesetzt, die aufgrund von Benutzeraktionen
oder programmtechnischen Vorgaben vom Server gesendet werden und von Ihnen mit
JavaScript- und DHTML-Techniken in Ihre Seiten eingefügt werden können. Wir wol-
len jetzt mit einem Geodienst in einer Karte die Position eines Besuchers auswerten und
darüber seinen Ort in einer Karte anzeigen. Wie das genau geht, ist in den meisten Kar-
ten-APIs weitgehend ähnlich, aber im Detail natürlich spezifisch. Wir werden auf Details
nicht eingehen und es sei explizit auf die Dokumentationen der APIs verwiesen, aber ein
konkretes Beispiel mit dem API von OpenStreetMap sehen Sie hier. Das ist die HTML-
Basis ([Link]):
…
<link rel="stylesheet" href="[Link]
css" />
<style>#karte { height: 400px;width:400px; }</style></head>
<body><h1>Geolocation mit OpenStreetMap</h1><div id="karte"></div>
<script src="[Link]
<script src="lib/js/geo_2.js"></script></body></html>
Beachten Sie die Einbindungen der externen Ressourcen, die so von dem API gefordert
sind. Das Beispiel verwendet [Link], eine JavaScript-Bibliothek für interaktive
14.3 DHTML, Zeichnen und Animationen 361
Die konkreten JavaScript-Anweisungen sind wie gesagt API-spezifisch, aber recht gut
nachvollziehbar. Wenn der Benutzer die Seite besucht, wird nach Erlaubnis gefragt,
die Position zu verwenden, denn im Hintergrund wird [Link] genutzt
(Abb. 14.4).
Dann wird die Position auf der Karte angezeigt, zusammen mit einem Marker und
einem Kreis, der die Genauigkeit der Positionierung darstellt (Abb. 14.5).
Zum Abschluss des Kapitels widmen wir uns dem Thema DHTML (Dynamic HTML),
was wir schon vielfach berührt haben, sowie verschiedener grafischer Techniken all-
gemein. DHTML ist ein Begriff, der eng mit dem DOM-Konzept zusammenhängt, aber
weder eindeutig noch standardisiert oder geschützt ist. Die wichtigsten Protagonisten im
Internet verstehen unter dynamischen HTML die Veränderungen einer Webseite, nach-
dem die Seite bereits beim Client (also im Browser) angelangt ist. Die konkrete Technik,
wie die Veränderung dabei realisiert wird, ist irrelevant, obgleich DHTML fast immer als
Verbindung von HTML, JavaScript und Style Sheets bezeichnet wird. Natürlich brau-
chen nicht zwingend alle drei Techniken beteiligt zu sein. Auch Animationen kann man
im Grunde zu DHTML zählen, wenn man für die Animation die gleichen Techniken
(HTML, CSS und JavaScript) verwendet. Wenn man überhaupt eine Differenzierung
362 14 Erweiterte Techniken
Abb. 14.5 Eine interaktive Karte mit der Position des Besuchers
zwischen DHTML und Animationen vornehmen will, dann geht das über den Zeit-
faktor. Denn Animationen laufen über einen längeren Zeitraum ab und erzielen ihre Wir-
kung durch diese zeitorientierten Veränderungen. Allgemein zählt die dynamische Ver-
änderung einer Webseite zu den eindrucksvollsten Anwendungen von JavaScript, aber
rein von der Programmierung zu den einfachsten. Die einzige Krux war die hohe In-
kompatibilität verschiedener Browsermodelle, aber diese Probleme nehmen in modernen
Browsern immer mehr ab.
Viele Effekte, die DHTML zuzuordnen sind, lassen sich rein mit JavaScript durch-
führen, zum Beispiel der Austausch und die Größenveränderung von Bildern. Wir haben
schon mehrfach gesehen, dass Sie einfach die klassischen HTML-Attribute des <img>-
Tags manipulieren und mit geeigneten Eventhandlern – etwa für die Reaktion auf das
Überstreichen mit dem Mauszeiger – koppeln müssen. Auch Animationen lassen sich
rein durch die zeitgesteuerte Manipulation von Grafiken durchführen. Schlüssel ist der
14.3 DHTML, Zeichnen und Animationen 363
rekursive Einsatz von [Link]. Auch CSS selbst bietet einige dynamische
Effekte an, die man als DHTML oder gar Animation bezeichnen kann. Gerade in den
neuen Versionen von CSS mittels sogenannter Keyframes. Aber bereits zu Beginn der
Verwendung von CSS gab es den Hover-Effekt. In so einem Fall ergibt sich eine Än-
derung eines Bereichs in der Webseite, wenn dieser Bereich mit dem Mauszeiger über-
strichen wird. Ganz einfach kann man Hover-Effekte für Hyperlinks mit der CSS-
Pseudo-Klasse :hover erzeugen. Meist verwendet man diesen Effekt bei Hyperlinks über
a:hover, aber im Prinzip kann man ihn auch bei anderen Elementen verwenden. Der Ho-
ver-Effekt wirkt sich aber immer nur auf das Element aus, das gerade mit dem Maus-
zeiger überstrichen wird. Von daher ist er unflexibler als eine Programmierung mit Ja-
vaScript. Rein mit CSS kann man auch den Mauszeiger verändern, indem an eine Wert-
zuweisung für die Eigenschaft cursor vornimmt. Dennoch – richtig interessant, flexibel
und leistungsfähig sind grafische Effekte immer dann, wenn HTML, CSS und JavaScript
kombiniert werden.
Im DOM gibt es seit Version 2.0 das style-Objekt, worüber alle Stilinformationen von
sämtlichen Elementen bzw. Elementknoten mit einer sichtbaren Repräsentation in der
Webseite abgefragt und meist auch geändert werden können. Unabhängig vom konkreten
Zugriff auf ein Element werden Sie in allen Fällen die gleichen Eigenschaften und Me-
thoden zum Umgang mit dem Element zur Verfügung haben und dementsprechend auch
auf style zugreifen können.
Man sollte auch verkürzte Angaben aus CSS wie background statt background-
color bzw. backgroundColor beim Umgang mit dem style-Objekt vermeiden.
Achten Sie beim Umgang mit den Werten von Eigenschaften des style-Objekts
darauf, dass viele Werte mit Einheiten (etwa px oder pt) angegeben werden
364 14 Erweiterte Techniken
müssen. Wenn man diese Regel einhält, werden in den meisten Fällen Eigen-
schaften des style-Objekts direkt und trivial aus CSS-Attributen herzuleiten
sein (und umgekehrt).
Sie können mit dem style-Objekt eine Webseite komplett statisch formatieren, als ob Sie
Regeln direkt mit CSS setzen. Etwa so:
Beispiel
[Link]("h2")[0].[Link] = "red";
[Link]("h2")[1].[Link] =
"blue";
[Link]("body")[0].[Link] = "center";
[Link]("div")[1].[Link] = "36px";
Diese Anweisungen müssen einfach nach dem Laden des DOM-Baums ausgeführt
werden, etwa mit [Link] = init;, wenn diese in einer entsprechenden Funktion
notiert sind. Aber man sollte vermeiden, dass Sie im allgemeinen Fall so Ihre Web-
seiten statisch formatieren! Warum das? Nun – Sie geben damit die Trennung von
Funktionalität und Layout auf! Es ist primäres Ziel einer modernen Webapplikation,
dass Struktur, Layout und Funktionalität streng getrennt werden, und das haben wir
eben nicht. Und warum sollten Sie mit JavaScript und DOM-Objekten formatieren,
wenn das genauso gut direkt mit CSS (am besten mit externen Dateien) geht? Aber es
gibt einen Grund. Logik! Solange Sie rein auf Basis von CSS-Selektoren beim Laden
der Webseite alle notwendigen Formatierungen vornehmen können, sollten Sie die For-
matierung per style meiden wie der Teufel das Weihwasser. Aber wenn Sie Logik be-
nötigen, die Ihnen die CSS-Selektoren nicht bieten, oder aber Sie dynamisch Formatie-
rungen ändern wollen, nachdem die Seite bereits geladen wurde, haben Sie mit style das
perfekte Hilfsmittel.
…
<link rel="stylesheet" type="text/css" href="lib/css/[Link]"
/>
<script src="lib/js/[Link]"></script></head>
<body><h3>Animationen</h3><div id="ani1"></div> <hr />
<img src="images/[Link]" alt="IT" id="ani2"/></body></html>
In der referenzierten CSS-Datei [Link] werden vor allen Dingen der DIV-Bereich und
das Bild in der Breite auf 0 Pixel festgelegt. Dazu selektieren wir die Objekte über die
jeweilige Id:
Die Festlegung der Breite über CSS ist wichtig, denn diese CSS-Werte wollen wir per
JavaScript verändern und dazu ist es notwendig, dass der Wert vorher explizit initialisiert
wurde. Die Animationen werden in der JavaScript-Datei [Link] definiert und auch
dort über JavaScript-Eventhandler aufgerufen:
var i = 0, j = 0;
function animation1() {
i++;
[Link]("ani1").[Link] = i + "px";
if (i > 500) i = 0;
setTimeout(animation1, 2);
}
function animation2() {
j++;
[Link]("ani2").[Link] = j + "px";
if (j > 400) j = 0;
setTimeout(animation2, 10);
}
function init() {
animation1();
animation2();
}
[Link] = init;
Wir animieren die beiden Objekte mit vollkommen analogen Funktionen, die aber be-
wusst nicht (!) synchronisiert werden. Das bedeutet, dass die Veränderung der Breite
beider Objekte explizit in unterschiedlichen Zeitintervallen erfolgt. Das Beispiel hat
zwei strukturell interessante Stellen. Mit [Link]("ani1").style.
width = i + "px"; und [Link]("ani2").[Link] = j + "px"; werden je-
weils die Breite des DIV-Containers und die Breite des Bildobjekts jeweils in Abhängig-
keit von einer globalen Zählvariable gesetzt. Dazu kommt das style-Objekt zum Einsatz,
um darüber die CSS-Eigenschaften zu manipulieren. Die zweite interessante Stelle be-
trifft den zeitverzögerten Selbstaufruf der Funktionen animation1 und animation2. Die
beiden Funktionen werden beim Laden der Webseite unmittelbar mit der Funktion init
14.3 DHTML, Zeichnen und Animationen 367
gestartet, die mit dem Eventhandler [Link] aufgerufen wird. Das bedeutet, dass
die beiden Animationen unmittelbar (und endlos – also bis zum Verlassen der Webseite)
ablaufen.
Schauen wir uns noch ein Beispiel an, in dem über die Manipulation von style auf
einfache Weise Animationen entstehen. Die Webseite [Link] ist ganz einfach auf-
gebaut und enthält im Wesentlichen zwei Überschriften, die animiert werden. Dazu
werden in der Überschrift der Ordnung h3 der Inhalt dynamisch ausgetauscht und die
Hintergrundfarbe der Webseite verändert:
…
<script src="lib/js/[Link]"></script></head>
<body><h1>Interessante Webseiten</h1><h2 id="dyn1">[Link]</h2>
<h3 id="dyn2">[Link]</h3></body></html>
var i = 0;
function ani() {
var seite = [Link]("body")[0];
var u1 = [Link]("dyn1");
var u2 = [Link]("dyn2");
switch(i++) {
case 0 : [Link] = "red";
[Link] = "blue";
[Link] = "42px";
[Link] = "center";
[Link] = "gray";
[Link] = "yellow";
[Link]="[Link]";
break;
case 1 : [Link] = "yellow";
[Link] = "black";
[Link] = "12px";
[Link] = "right";
[Link] = "red";
[Link] = "white";
[Link]="[Link]";
break;
case 2 :
[Link] = "red";
[Link] = "white";
[Link] = "22px";
[Link] = "left";
[Link] = "lightgray";
368 14 Erweiterte Techniken
[Link] = "blue";
[Link]="[Link]";
break;
default : [Link] = "cyan";
[Link] = "gray";
[Link] = "cyan";
[Link]="[Link]";
}
i %= 4;
setTimeout(ani,'2500');
}
[Link]=ani;
Der Code ist etwas länger, weil viel einzelne CSS-Eigenschaften dynamisch gesetzt wer-
den. Das Beispiel ist als Endlosanimation konzipiert, denn beim Laden der Webseite
wird mit dem Eventhandler onload die Funktion ani aufgerufen, die eine zeitgesteuerte
Animation startet, die solange weiterläuft, bis die Webseite verlassen wird. Dabei grei-
fen wir für die Animation wieder auf einen rekursiven Aufruf zurück, der über window.
setTimeout verzögert wird. Die Webseite beinhaltet eine Überschrift der Ordnung 1. In
der Funktion ani werden style die Stilinformationen der Überschriften sowie der Inhalt
der Überschrift vom Typ h3 verändert. Zusätzlich wird die Hintergrundfarbe des Körpers
der Webseite geändert. Die Animation benutzt zum Austausch der Stilinformationen den
Modulo-Operator, um eine Zahl zwischen 0 und 4 zu berechnen, die in der folgenden
switch-case-Anweisung zur Auswahl der tatsächlichen Stilinformation verwendet wird.
Im Abstand von 2,5 Sekunden wechseln so die Schriftfarbe und Hintergrundfarbe der
Überschrift und zusätzlich die Hintergrundfarbe der Webseite.
Zum Abschluss soll noch eine Animation durch Manipulation von Grafiken folgen.
Das ist erst einmal die Basiswebseite ([Link]):
…
<style>h1 { background-color: #EEE; }
#p1 {position: absolute; top: 150px; left: 100px; }
#b1 { width: 40px; height: 30px; }</style>
<script src="lib/js/[Link]"></script></head>
<body><h1>Eine Bildanimation</h1><button>Start</button>
<button>Zurücksetzen</button><div id="p1"><img src="images/
[Link]"
id="b1" /></div><div id="ausgabe"></div></body></html>
Wir haben hier eine einfache Webseite mit einer Grafik und zwei Schaltflächen vorliegen,
die wie internem mit CSS formatiert wird. Für dieses Beispiel ist die CSS-Formatierung
wieder zum Teil von Bedeutung. Die Breite und Höhe von dem Bild werden mit Style
14.3 DHTML, Zeichnen und Animationen 369
Sheets festgelegt und der umgebende DIV-Bereich positioniert. Kommen wir zu Java-
Script-Datei [Link]:
Wenn Besucher nach Laden der Seite auf die erste Schaltfläche klicken, werden die Gra-
fik vergrößert und die Breite und Höhe der Grafik jeweils ausgegeben (Abb. 14.7). Die
Animation erfolgt durch die Zeitverzögerung durch den Einsatz von [Link]
und den rekursiven Aufruf von ani. Beachten Sie aber die unscheinbare Stelle bei der
370 14 Erweiterte Techniken
Abb. 14.7 Die Animation läuft – beachten Sie die Pixel-Angaben der Größe
Ausgabe der Breite und Höhe. Sie sehen hier die Größenangaben mit der Einheit px.
Und dahinter steckt viel mehr als nur eine optische Feinheit. Am Ende der Animation
wird die Grafik ausgetauscht (Abb. 14.7).
In der Funktion ani wird bei jedem Aufruf wieder die Variable counter um den Wert
1 erhöht (counter + + ;). Und damit wird die Breite und Höhe des Bildes verändert. Aber
dazu greifen wir hier auf das style-Objekt zu. Beachten Sie, dass die Breite und Höhe
von dem Bild jedoch mit Style Sheets festgelegt wurden. Beim Zuweisen der Breite und
Höhe auf diesem Weg müssen wir deshalb die Einheit "px" anhängen. Das ist ein ent-
scheidender Punkt, denn wenn Sie mit CSS einmal die Maße von einem Objekt in der
Webseite festgelegt haben, hat diese Festlegung eine höhere Priorität als die Festlegung
der Ausmaße in HTML mittels Attributen. Die Funktion zum Zurücksetzen der Anima-
tion stellt wieder den Originalzustand beim Laden der Webseite wieder her. Beachten Sie
auch hier die Notwendigkeit zur Angabe der Einheit (Abb. 14.8).
Ein optisches Highlight von HTML5 ist, dass Sie mithilfe von Canvas-Objekten oder
SVG zeichnen können. Die Idee hinter solchen Bereichen zum Zeichnen ist auch keines-
falls neu, sondern in vielen Programmiersprachen schon seit zig Jahren etabliert. Die
14.3 DHTML, Zeichnen und Animationen 371
Abb. 14.8 Die Animation ist beendet und das Bild wurde ausgetauscht
Methoden, die etwa ein Canvas-Objekt bereitstellt, dürften daher für Java-Programmierer
kalter Kaffee und ohne große Erklärung anzuwenden sein. Sie werden auch zwischen
dem Zeichnen mit Canvas-Methoden und SVG eine hohe Ähnlichkeit kennenlernen.
Wahrscheinlich werden in Zukunft Web-Frameworks und Grafikprogramme geeignete
Methoden bereitstellen, um auf einer höheren Ebene diese Zeichenoperationen auszu-
führen. Wir wollen uns dennoch hier ganz nativ dem Canvas-Objekt und dem Umgang
mit SVG nähern.
Neue Zeichenoperationen mit CSS3 – insbesondere Keyframes – ermöglichen oft sehr ähnliche
Dinge.
[Link] Canvas
Betrachten wir zuerst den Umgang mit Canvas-Techniken. Die meisten Ausführungen
dazu werden sich auf SVG (und auch CSS3) übertragen lassen. Allerdings gibt es einige
Besonderheiten, die bereits damit anfangen, dass ein Canvas-Bild eine Pixelgrafik
ist, während SVG eine Vektorgrafik darstellt. In beiden Fällen wird aber nur die Be-
rechnungsvorschrift in den Client geladen und die Grafik dort erzeugt. Das spart gegen-
über der Übertragung einer vollständigen Grafik vom Server natürlich viel Daten und
damit wird auch die Performance einer Seite gewaltig verbessert. Nur muss der Client
die Ressourcen aufbringen, die Grafik dynamisch und schnell zu generieren.
372 14 Erweiterte Techniken
[Link].1 Das Canvas-Element
Als Erstes müssen Sie zum Zeichnen ein Canvas-Objekt zur Verfügung haben. HTML5
stellt dazu das Tag <canvas> zur Verfügung. Das ist im Grunde ein normales Block-
element in einer Webseite, in dem man aber über die Methoden der Objektrepräsentation
im DOM5-Baum zeichnen kann. Obwohl man die Breite und Höhe von Blockelementen
in modernen Webseiten mittels CSS festlegen soll, gibt man bei Canvas-Elementen der-
zeit die Breite und Höhe zudem mit HTML-Attributen an. Dies sorgt aktuell für einen
zuverlässigeren Aufbau in Browsern. Beispiel:
Beispiel
[Link].2 Der Grafikkontext
Ein Canvas-Objekt stellt sogenannte Grafikkontexte zur Verfügung. Das sind erst ein-
mal einfach die Bereiche, in denen man konkret zeichnen kann, allerdings im Sinn der
Objektorientierung mit implementierten Methoden. Der Bereich zum Zeichnen ist
nach der Initialisierung erst einmal leer. Im Konzept von HTML5 sind sowohl zwei-
dimensionale als auch dreidimensionale Bereiche vorgesehen, wobei zum Zeitpunkt
der Bucherstellung nur die zweidimensionalen Bereiche richtig unterstützt werden.
Zugriff auf den Grafikkontext erfolgt über ein Canvas-Objekt, das Sie wie immer als
node adressieren, und dessen Methode getContext. Als Parameter geben Sie die Art des
Grafikkontextes an (zweidimensional oder dreidimensional). Derzeit macht nur "2d"
Sinn. Etwa so:
Beispiel
var zb = [Link]('canvas')[0].getContext('2d');
In der Regel werden Sie mit einer Id auf das Canvas-Objekt zugreifen.
Eine Eigenheit von Canvas-Bereichen ist, dass eine einmal dort gezeichnete Form
erhalten bleibt, wenn sie nicht verdeckt wird, auch wenn Sie eine neue Form in
dem Canvas-Bereich erzeugen. Wenn Sie immer einen leeren Bereich zum Zeich-
nen einer neuen Form benötigen, müssen Sie sich selbst darum kümmern, dass der
Canvas-Bereich beim Zeichnen der neuen Form leer ist.
Koordinatensystem lässt sich durch die Angabe von zwei Werten (sogenannte Vektoren,
die vom Ursprung des Koordinatensystems ausgehen) ein beliebiger Punkt in dem Ko-
ordinatensystem eindeutig festlegen. Dies sind ein x-Wert und ein y-Wert, wie man es
meist aus der Schule kennt. Die Maßeinheit ergibt sich aus dem Grid, wobei normaler-
weise eine Einheit im Grid mit einem Pixel im Canvas-Element korrespondiert. Das
hier verwendete Koordinatensystem unterscheidet sich aber ein wenig von dem im
Mathematikunterricht oft verwendeten System. Die obere linke Ecke des Canvas-Be-
reichs wird mit (0, 0) – also dem Koordinatensystemursprung – abgebildet (sonst ist es
meist die untere linke Ecke). Dabei ist der x-Wert die Anzahl der Bildschirmpixel von
links ausgehend, also in der Waagerechten, und y ist die Zahl der Pixel, von oben an-
gefangen, also in der Senkrechten. Das Koordinaten-Tupel (42, 10) beschreibt z. B. einen
Punkt, der 42 Pixel vom linken Rand und 10 Pixel vom oberen Rand des Canvas-Be-
reichs entfernt ist. Im Grunde sollten Sie das aber kennen, wenn Sie an die Positionie-
rung von Elementen mit CSS und den Eigenschaften top und left denken. In der Grund-
einstellung liegt der Ursprung des Grid auf dem Ursprung des Koordinatensystems.
Den Ursprung eines Grid können Sie allerdings innerhalb des Canvas-Elements mit ge-
eigneten Methoden verschieben, rotieren oder skalieren.
Jede Methode von einem Grafikkontext mit einer konkreten Ausgabe verwendet
irgendwelche Koordinatenangaben, denn es muss ja festgelegt werden, wo eine Ausgabe
beginnt, und oft noch, bis wohin die Ausgabe erfolgen soll.
Dazu kommen noch Methoden zur Angabe von Farben, Farbverläufen, Füllungen, Um-
randungen usw. Das 2D-API behandelt Formen, Text und Bilder im Grunde gleich und
unterstützt damit einen einheitlichen Mechanismus für Veränderungen (etwa Rotation,
Verzerrung, Verschiebung und Skalierung). Allerdings versteht man im Canvas-Konzept
nur Rechtecke als sogenannte Grafikprimitive. Das unterscheidet sich von den Grafik-
konzepten unter Java oder SVG, wo auch andere Formen als Grafikprimitive verstanden
werden. Diese anderen Formen müssen bei Canvas-Objekten aus sogenannten Pfaden
kombiniert werden, wobei das API glücklicherweise Methoden bereitstellt, mit denen
man einfach Formen erstellen kann. In jedem Fall kann man für jedes Grafikobjekt
spezifische Charakteristiken wie Höhe und Breite, Farbe, Füllmuster, Transparenz, Um-
randungen usw. angeben, aber auch Überblendungen und ähnlich Effekte.
374 14 Erweiterte Techniken
Wir werden hier im Buch nur punktuelle Schwerpunkte setzen, um die grundlegenden Vorgänge zu
besprechen und das Konzept als solches zu verstehen.
[Link].5 Rechtecke
Ein Rechteck ist im Canvas-Konzept eine herausgehobene Form, denn Sie wird als grafi-
sches Primitives verstanden. Betrachten wir kurz die genauen Details. Es gibt im API
drei Funktionen zum Zeichnen:
Die ersten beiden Parameter sind jeweils die X- und Y-Koordinate von der linken oberen
Ecke des Bereichs (relativ zum Ursprung) und die anderen beiden die Breite und Höhe
des Bereichs.
…
<script src="lib/js/[Link]"></script></head>
<body><h1>Canvas</h1><canvas width="250" height="150">
<img src="images/[Link]" alt="Alternative zu Canvas"/></canvas>
</body></html>
In der Webseite sehen Sie ein Canvas-Element mit einem Alternativinhalt (hier ein Bild),
der angezeigt werden soll, wenn ein Browser dieses neue Canvas-Element nicht unter-
stützt. Hier ist die referenzierte JavaScript-Datei [Link]:
14.3 DHTML, Zeichnen und Animationen 375
function zeichnen() {
var zb =
[Link]('canvas')[0].getContext('2d');
[Link] = "rgb(200,0,0)";
[Link](15, 15, 155, 50);
[Link] = "rgba(0, 20, 250, 0.1)";
[Link](30, 40, 255, 250);
for (var i = 3; i < 255; i++) {
[Link] = 'rgba(' + i + ',' +
(255 - i) + ',' + i + ',' + (1 / (i / 3)) + ')';
[Link](i * 2, i, 25, 25 + i);
}
for (var i = 3; i < 255; i++) {
[Link] = 'rgb(' + i + ',' + (255) + ',' + i + ')';
[Link](200 + i * 2, i, 55 + i, 40);
}
};
function init() {
zeichnen();
}
[Link] = init;
In der Funktion zeichnen, die beim Laden der Webseite ausgeführt wird, selektieren wir
den Grafikkontext und speichern die Referenz darauf in der Variablen zb. In den folgen-
den Zeilen wird eine Methode dieses zweidimensionalen Grafikkontextes verwendet, die
bereits durch den Namen fillRect klar sein dürfte. Es werden hier gefüllte Rechtecke ge-
zeichnet (Abb. 14.9). Wir legen auch mit CSS-Standardfunktionen die Farbfüllung von
Objekten fest (einmal mit Transparenz). Hierzu verwenden wir fillStyle. In der folgen-
den Schleife werden die Werte für die CSS-Funktion rgba in Abhängigkeit vom Zähl-
index der Schleife berechnet (sowohl die Rot-, Grün- und Blauanteile als auch die Trans-
parenz). Darüber hinaus werden die Position und die Höhe des Rechtecks, das in jedem
Schleifendurchlauf gezeichnet wird, in Abhängigkeit von dem Zählindex verändert. Das
führt dazu, dass sich die Farbe in kleinen Schritten verändert sowie in der Transparenz
abschwächt und die Grafik zu einer neuen perspektivischen Form verschwimmt, die mit
einer Art Farbverlauf gefüllt ist (Abb. 14.9). Hier zeigt sich ganz deutlich, dass eine neue
Form im Grafikkontext nicht dazu führt, dass die vorhandenen Formen gelöscht werden.
Neue Formen werden einfach hinzugefügt. In der zweiten Schleife setzen wir die Eigen-
schaft strokeStyle in einer Schleife über die CSS-Funktion rgb. Damit wir einen Rahmen
sehen, verwenden wir statt fillRect für ein gefülltes Rechteck strokeRect, was nur einen
Rahmen zeichnet (Abb. 14.9).
Wir haben gerade so eine Art Farbverlauf über Schleifen demonstriert, aber bei Canvas-
Objekten muss man das gar nicht von Hand erledigten. Es gibt bereits implementierte
376 14 Erweiterte Techniken
[Link] = function() {
var zb = [Link]('canvas')[0].getCon-
text('2d');
var lg1 = [Link](00, 00, 400, 175);
[Link](0, 'white');
[Link](1, 'black');
[Link] = lg1;
[Link](0, 0, 650, 195);
14.3 DHTML, Zeichnen und Animationen 377
Mit [Link] erzeugen wir einen linearen Gradienten, der von der Ko-
ordinate 0,0 bis zur Koordinate 400,175 verläuft. Formen, die kleiner sind, werden die-
sen Gradienten beschneiden, wenn er zugewiesen wird. Sind die Formen jedoch größer,
werden die Bereiche außerhalb des Umfangs des Gradienten mit der Endfarbe deckend
gefüllt. Beachten Sie, dass wir keinen horizontalen Farbverlauf gewählt haben, sondern
durch den vierten Parameter einen Zielpunkt in Y-Richtung angeben, der tiefer als der
Y-Startpunkt liegt. Damit erhalten wir einen Farbverlauf nach rechts unten (Abb. 14.10).
Mit [Link] legen wir zwei Stopps fest, die man minimal braucht, um von
einer expliziten Startfarbe zu einer expliziten Endfarbe zu kommen (Vorgabefarbe wäre
immer Schwarz). In unserem Beispiel haben wir also einen Farbverlauf von Weiß nach
Schwarz. Der zweite lineare Gradient ist rein horizontal. Das erkennen Sie daran, dass
der Wert des zweiten und des vierten Parameters (die Y-Angaben) gleich sind. Dafür ver-
wenden wir dieses Mal mehrere Stopps, um über verschiedene Farben zu gehen.
Sie sollten sich bei Farbverläufen immer klarmachen, dass Sie nicht die Form selbst
damit füllen. Eine Form wird an einer gewissen Stelle gezeichnet und der Farbverlauf
scheint quasi durch. Wenn sich also ein Farbverlauf von einer gewissen Koordinatenangabe
zu einer anderen erstreckt, wird eine Form nur den Teil von dem Farbverlauf anzeigen, in
dem sie diesen Bereich überdeckt.
Zu den wichtigen Neuerungen von HTML5 zählt definitiv die Unterstützung von SVG
(Scalable Vector Graphics – skalierbare Vektorgrafiken), wobei SVG schon lange vor
HTML5 verfügbar war. Sie werden viele Ähnlichkeiten zu Canvas bei der konkreten An-
wendung sehen. Aber es gibt auch gravierende Unterschiede in der technischen Grund-
ebene.
[Link] Grundlagen zu SVG
Bei SVG handelt es sich um die vom W3C schon recht lange empfohlene XML-
Spezifikation zur Beschreibung zweidimensionaler Vektorgrafiken. Das beginnt bei
einfachen grafischen Formen wie Kreise oder Rechtecke und führt hin zu komple-
xeren Strukturen wie etwa Diagramme zur Darstellung von Messwerten, aber auch
die Beschreibung von abgerundeten Schaltflächen oder sonstigen per Formel zu be-
schreibenden Grafikobjekten. Auch Animationen werden von SVG unterstützt (mittels
einer anderen XML-Spezifikation namens SMIL – Synchronized Multimedia Integ-
ration Language). Ebenso kann der SVG-DOM nach der offiziellen Spezifikation mit-
hilfe eingebetteter Funktionen über ECMAScript und ähnliche Sprachen manipuliert
werden. Auch so kann man Animationen programmieren. Da SVG ein XML-basiertes
Dateiformat ist, sind Inhalte von SVG-Dateien leicht zugänglich. Sie können prinzipiell
auch direkt mit einem Texteditor bearbeitet werden. Die empfohlene Dateiendung von
SVG-Dateien ist .svg und der MIME-Typ ist image/svg + xml. Anzeigen können Sie es
entweder, wenn Sie es direkt in einem Browser laden (Abb. 14.11) oder als Teil einer
Webseite. Als XML-Dokument wird eine SVG-Datei in einer Baumstruktur aus ver-
schiedenen Elementen und diesen Elementen zugewiesenen Attributen aufgebaut. Jede
SVG-Datei beginnt damit mit der XML-Deklaration und der Dokumenttypdeklaration,
die den benutzten Namensraum beschreibt. Dazu wird bei Letzterem ein Verweis auf die
entsprechende DTD-Datei eingefügt.
Dem Prolog folgt natürlich das Root-Element, das im Fall von SVG <svg> lautet. Das
wäre eine schematische SVG-Datei:
Beispiel
Mit den Attributen width und height des SVG-Starttags <svg> wird die Größe der Grafik
definiert, die mit SVG beschrieben wird, wenn sie in eine Webseite integriert wird.
[Link].1 Das Koordinatensystem
SVG ist eine Vektorgrafik und erlaubt damit sowohl ganzzahlige als auch gebrochene
Koordinatenangaben, die wie üblich vom Koordinatenursprung in der linken oberen
Ecke des Zeichenbereichs ausgehen. Als Einheiten können in SVG sowohl relative (wie
em oder Prozent) als auch absolute Angaben (wie Meter oder Zoll) gewählt werden.
Grundsätzlich stehen in SVG alle von CSS bekannten Einheiten zur Verfügung.
[Link].2 Die Elemente
SVG unterstützt drei grundsätzlich unterschiedliche Typen von Elementen:
Wie bei fast allen grafischen Systemen bauen alle grafischen Elemente in SVG auf ein-
fachen Grundelementen (grafischen Primitiven) auf. Durch Kombination mehrerer grafi-
scher Primitive lassen sich komplexe Objekte erzeugen. Für alle grafischen Elemente
können Füllung, Art der Umrisslinie und Transparenz durch Attribute festgelegt wer-
den. In SVG sind im Wesentlichen alle grafischen Elemente verfügbar, die man auch
aus anderen Konzepten wie Java2D, JavaFX oder eben der Canvas-Technologie kennt,
etwa Rechtecke, Kreise, Ellipsen, Pfade, Polygone etc. Die Elemente werden durch
380 14 Erweiterte Techniken
• Das Element <path /> definiert eine Linie bestehend aus einer beliebigen Kombination
von Strecken, Ellipsenbögen, quadratischen und kubischen Bézierkurven sowie Neu-
positionierungen, die mithilfe von relativen oder absoluten Koordinaten beschrieben
werden. Der Pfad kann geschlossen, gefüllt und als Clipping-Pfad verwendet werden.
Der Pfad ist das mächtigste Grafikelement in SVG. Praktisch alles, was durch an-
dere Grafikelemente gezeichnet werden kann, kann durch ein Pfadelement äquivalent
dargestellt werden. Damit ist die Anwendung aber auch nicht ganz einfach und die
weiteren Grafikelemente vereinfachen die Anwendung.
• Das Element <circle /> definiert einen Kreis, der durch seinen Radius und durch die
Position des Mittelpunkts beschrieben wird.
• Das Element <ellipse /> definiert eine Ellipse, die durch ihre zwei Halbachsenradien
sowie, analog zum Kreis-Element, durch die Position ihres Mittelpunkts beschrieben
wird.
• Sehr einfach ist das Rechteck, das durch das Element <rect /> über die Position seiner
oberen linken Ecke, seiner Breite und seiner Höhe definiert wird. Optional können die
Ecken des Rechtecks abgerundet werden.
• Das Element <line /> definiert eine einfache gerade Linie (Strecke), die über die Ko-
ordinaten ihrer beiden Endpunkte beschrieben wird.
• Der Polygonzug, der über das Element <polyline /> definiert wird, gibt eine beliebige
Zahl von Punkten an, die als Polygonzug durch Strecken verbunden werden. Die
Punkte werden über Koordinatenpaare beschrieben.
• Das Element <polygon /> definiert ein Vieleck, das über seine Eckpunkte beschrieben
wird. Wie beim Polygonzug werden auch hier die Eckpunkte als Koordinatenpaare
angegeben und in der Reihenfolge ihrer Definition miteinander verbunden.
• Mit dem Element <text /> kann man in SVG Text in die Grafik einbetten. Das Text-
element kann über Attribute, wie zum Beispiel Schriftgröße und Schriftart, Lauf-
richtung und Wortabstand formatiert werden. Text kann zudem über Unterelemente
(<tspan />) strukturiert werden.
• Das Element (<image />) bindet eine Rastergrafik ein, die über eine Pfadangabe,
Breite und Höhe sowie die Position beschrieben wird.
• Man kann in SVG auch eine Gruppierung von Elementen vornehmen. Dazu dient das
Gruppenelement (<g />). Transformationen und Styling, die auf das Gruppenelement an-
gewendet werden, gelten für alle damit zusammengefassten untergeordneten Elemente.
[Link].3 Stilinformationen
Jedes Element in SVG kann durch Angabe von Styling-Eigenschaften in seiner optischen
Erscheinung angepasst werden. Das geschieht durch das style-Attribut oder Attribute zu
14.3 DHTML, Zeichnen und Animationen 381
einzelnen Eigenschaften wie fill oder stroke. Das sollte mit den Erfahrungen aus dem
Canvas-Konzept und DHTML sofort zu verstehen sein. Verändert werden können zum
Beispiel Füllung, Konturlinie und Transparenz der Elemente sowie bei Text die Eigen-
schaften der Schrift. Die Styling-Eigenschaften richten sich dabei in weiten Teilen nach
XSL und CSS. Über zusätzliche Elemente können nicht nur Farben, sondern auch Farb-
verläufe und Muster verwendet werden. Ein Beispiel für eine SVG-Datei mit Linien,
Rechtecken und Kreisen (Abb. 14.11) – [Link]:
[Link].4 Transformationen
Jedes Element und jede Gruppe von Elementen kann in SVG durch eine Reihe affiner
Transformationen in Position, Orientierung und Form verändert werden (Abb. 14.12).
Die Transformation wird einem beliebigen Element als Attribut transform angehängt
([Link]):
…
<rect x="100" y="100" width="500" height="200" fill="blue"
stroke="black" stroke-width="10px" transform="rotate(-10 525 15)"/>
…
[Link].5 Hyperlinks in SVG
SVG-Elemente können in ein HTML a-Tag eingesetzt werden. Sie machen damit also
Elemente in der SVG-Grafik sensitiv und verwenden die auch in HTML übliche Technik.
Hier sind zwei Elemente als Hyperlinks ausgezeichnet ([Link]):
…
<a href="[Link] cx="150" cy="75" r="50" stroke="red"
fill="green" stroke-width="5px"/></a>
382 14 Erweiterte Techniken
Für die Unterstützung von älteren Browsern ist die Verwendung des object-Tags ganz
sinnvoll, aber eigentlich auch nur noch für den Zweck.
Beispiel
Die restlichen Arten der Einbindung sollten klar sein, weil sie sich in Nichts von der
Einbindung anderer Ressourcen unterscheiden. Allerdings kann die Wahl der Einbindung
14.3 DHTML, Zeichnen und Animationen 383
relevant werden, wenn Sie per JavaScript den Zugriff auf den DOM-Baum des SVG-Ob-
jekts vornehmen wollen. SVG direkt im HTML-Markup zu notieren, ist dazu eine sehr
komfortable Variante. Man nutzt das <svg>-Element direkt in einer Webseite und das ist
dann so etwas wie eine XML-Dateninsel. Bei den anderen Arten der Einbindung wird
ein Zugriff auf Bestandteile der SVG-Grafik aufwendiger.
…
<script src="lib/js/[Link]"></script></head><body><h1>SVG</h1>
<img src="[Link]" width="200" id="svg1"/>
<svg xmlns="[Link] version="1.1" baseProfi-
le="full"
width="700px" height="400px" viewBox="0 0 700 400">
<!-- Eine Line -->
<line x1="0" y1="200" x2="700" y2="200" stroke="black" stroke-
width="10px" />
<!-- Ein Rechteck -->
<rect x="100" y="100" width="300" height="100" fill="blue" stro-
ke="black" stroke-width="2px" transform="rotate(-15 525 25)" />
<!-- Eine weitere Line -->
<line x1="180" y1="370" x2="500" y2="50" stroke="black" stroke-
width="10px" />
<!-- Ein Kreis -->
<circle cx="150" cy="75" r="60" stroke="red" fill="green" stroke-
width="5px" />
…
Sie sehen, dass sowohl mit dem img-Tag als auch mit dem svg-Tag SVG-Grafiken in der
Webseite eingebunden sind. Der Zugriff aus JavaScript auf den circle-Tag innerhalb des
svg-Elements ist leicht ([Link]):
[Link] = function () {
[Link]("circle")[0].onmouseover = function
() {
[Link] = "cyan";
};
384 14 Erweiterte Techniken
[Link]("circle")[0].onmouseout = function ()
{
[Link] = "green";
};
[Link]([Link]("svg1").nextElementSibling.
getElementsByTagName("circle")[0]);
};
Sie sehen, dass der Zugriff auf den Kreis fast unverschämt einfach ist. Denn da wir XML
vorliegen haben, arbeiten wir mit einem node-Objekt und darauf können Sie mit den ge-
wohnten Methoden zugreifen, sowohl was die Selektion angeht als auch die dynamische
Gestaltung über das style-Objekt. Aber beachten Sie die Indizierung. Der Kreis in dem
svg-Element hat den Index 0, obwohl das img-Element in der Webseite vorher notiert ist.
Auf das Element, das über den img-Tag eingebunden ist, kann man zwar auch zugreifen,
wie die Ausgabe in der Konsole zeigt. Aber Sie erkennen sicher, dass das aufwendiger
ist, da die Bestandteile der SVG-Grafik nicht in den „normalen“ DOM der Webseite ge-
rendert werden (Abb. 14.13).
Abb. 14.13 Die Farbe des oberen Kreises wird dynamisch geändert, wenn sich der Mauszeiger in
dem Bereich befindet
Eine Frage der Qualität
15
Softwarequalität bezieht sich auf die Gesamtheit der Eigenschaften und Merkmale einer
Software, die ihre Fähigkeit bestimmen, bestimmte Anforderungen zu erfüllen und die
Bedürfnisse der Benutzer zufriedenstellend zu erfüllen. Hier sind einige wichtige As-
pekte der Softwarequalität im Zusammenhang mit dem Quellcode, wobei diese nicht
immer alle einzuhalten sind, sich teils sogar ausschließen können und auch nicht ver-
bindlich zu definieren sind. Aber die Mehrheit der IT-Experten stimmt diesen Punkten im
Wesentlichen zu:
• Zuverlässigkeit: Der Quellcode sollte fehlerfrei sein und die erwarteten Ergebnisse
liefern.
• Wartbarkeit: Der Quellcode sollte leicht zu verstehen, zu ändern und zu erweitern
sein. Man kann auch sagen, dass Codestrukturen „erwartbar“ sein sollen. Was ein
Entwickler geschrieben hat, sollten andere Entwickler sofort verstehen, weil sie es
© Der/die Autor(en), exklusiv lizenziert an Springer Fachmedien Wiesbaden GmbH, ein 385
Teil von Springer Nature 2025
R. Steyer, Programmieren mit JavaScript, [Link]
386 15 Eine Frage der Qualität
genauso machen würden. Das erleichtert die Fehlerbehebung, neue Funktionen hinzu-
zufügen und die Software im Laufe der Zeit anzupassen.
• Lesbarkeit: Der Quellcode sollte klar und verständlich sein. Auch dies trägt zu „er-
wartbarem“ Code bei. Dies umfasst die Verwendung aussagekräftiger Klassen-, Me-
thoden-, Funktions- oder Variablennamen samt Konventionen der Schreibweise, kon-
sistente Formatierung und gut strukturierten Code.
• Effizienz und Performance: Der Quellcode sollte ressourceneffizient sein und keine
unnötige Verwendung von Speicher oder Rechenleistung verursachen. Effizienter
Code führt zu schnelleren Ausführungszeiten und einer besseren Leistung der An-
wendung. Bei einigen Programmiersprachen kann diese Anforderung allerdings mit
Wartbarkeit und Lesbarkeit kollidieren. Zudem ist gerade Performance nicht immer
im Fokus.
• Skalierbarkeit: Quellcode sollte so gestaltet sein, dass er mit zunehmender Komplexi-
tät und Datenmenge der Anwendung umgehen kann. Bei JavaScript sind insbesondere
Module und das Kapseln von Details einer Struktur dazu geeignet. Dennoch ist bei
JavaScript die Skalierbarkeit gerade bei kleineren Aufgaben meist nebensächlich bis
irrelevant.
• Sicherheit: Quellcode sollte gegen Sicherheitslücken und Angriffe geschützt sein.
• Dokumentation: Quellcode sollte angemessen dokumentiert sein.
Es gibt ein paar Konzepte, die für Softwarequalität sorgen können. Teils passen Sie
sehr gut zum Erstellen von JavaScript, zum Teil macht JavaScript die Umsetzung eher
schwierig oder sie sind irrelevant.
15.2.1 KISS-Prinzip
Eine Regel für gute Software ist als KISS-Prinzip bekannt und dieses ist universell. KISS
steht für „Keep it simple, stupid“ (englisch für „Halte es einfach und dumm“) oder ähn-
liche Aussagen. Das ist kein Widerspruch zu einer intelligenten Programmierung – im
Gegenteil. Denn intelligent bedeutet in der Programmierung einfach und langweilig.
Damit ist die Erstellung des Codes erst einmal wenig fehlerträchtig, aber der Code ist
auch leichter wartbar, was ja als Kernfeatures von Softwarequalität schon thematisiert
wurde. „Einfach“ bedeutet, dass Sie nicht unnötig komplexe oder lange Strukturen
15.2 Allgemeine Konzepte für bessere Qualität 387
schreiben sollten. Lieber schreiben Sie z. B. zwei kurze Funktionen als eine lange Funk-
tion.
Als recht leicht merkbare Regel können Sie festhalten, dass Sie immer so program-
mieren sollten, dass sich jemand Fremdes mit analogem Wissenstand über Ihren Code
nicht wundern würde. Erwartbare Strukturen sind gut, unerwartete Strukturen schlecht.
Wenn Sie etwa an einer Stelle eine while-Schleife verwenden und in einer vergleich-
baren folgenden Situation eine for-Schleife, dann ist das unerwartet und schlecht, wenn
es keinen unvermeidbaren Grund gibt. Ein erfahrener Programmierer, der den Code
sieht, würde sich fragen, warum einmal die eine Schleife und das andere Mal die an-
dere Schleife verwendet wurde. Und dies vollkommen überflüssig, wenn es eben kei-
nen zwingenden Grund gibt. Oder wenn Sie einmal eine Konstante vollkommen groß-
schreiben und das andere Mal nur den ersten Buchstaben großschreiben, ist das un-
erwartet und damit schlecht (was auf Konventionen zurückkommt).
Gerne wird das KISS-Prinzip auch so erklärt: „Wenn es mehrere gleichwertige Lö-
sungen für ein Problem gibt, wähle die einfachste Variante!“
DRY steht für „Don’t repeat yourself“ – Wiederholen Sie sich nicht. Nutzen Sie (ge-
testete) Strukturen (Module, Funktionen, Objekte, Methoden, etc.), statt sie selbst zu
schreiben. Und wenn Sie etwas selbst programmieren müssen, machen Sie dies Wieder-
verwendbar und nutzen es später bei Bedarf selbst.
Neben dem KISS-Prinzip und dem DRY-Prinzip gibt es eine Vielzahl anderer Konzepte
und Prinzipien, die dazu beitragen können, Softwarequalität beim Programmieren zu
steigern. Diese Konzepte und Prinzipien sind wichtige Leitlinien für die Gestaltung und
Implementierung von hochwertigem und wartbarem Code. Allerdings sind diese oft für
JavaScript schwer umzusetzen oder führen an der Stelle zu weit. Hier sollen deshalb ei-
nige weitere Konzepte nur kurz genannt werden:
• Single Responsibility Principle (SRP): SRP besagt, dass eine Klasse oder ein Modul
nur eine einzige Verantwortung haben sollte. Durch die Aufteilung des Codes in klei-
nere, spezifischere Teile wird der Code einfacher zu verstehen, zu testen und zu warten.
• Open/Closed Principle (OCP): OCP besagt, dass Softwareeinheiten offen für Er-
weiterungen, aber geschlossen für Modifikationen sein sollten. Dies bedeutet, dass
der Code so entworfen sein sollte, dass neue Funktionen hinzugefügt werden können,
ohne bestehenden Code zu ändern.
388 15 Eine Frage der Qualität
Rückblickend auf den Stoff des Buchs, sollen einige Regeln zu Syntaxstrukturen – ins-
besondere Schleifen – nachgetragen werden. Konventionen für Bezeichner sind ja schon
bekannt, aber auch für Zählvariablen von Schleifen gibt es Konventionen.
• Wenn man zählergesteuerte Schleifen verwendet, gibt es übliche Konventionen für die
Benennung der Zählvariable, die Sie einhalten sollten. Wenn man while oder do-while
verwendet, wählt man für den Bezeichner der Zählvariablen entweder i oder counter.
Die Zählvariable bei der for-Schleife heißt so gut wie immer i.
• Wenn man zählergesteuerte, verschachtelte Schleifen verwendet, heißt die äußere
Zählvariable i, die Zählvariable für erste innere Schleife j, dann folgt k usw.
• Nach Konvention sollte man Zählvariable immer initialisieren.
• Initialisierungswert von Zählvariablen ist der Wert 0 und sie werden um den Wert 1
erhöht. Bei Bedarf wird Logik in den Schleifenkörper statt in den Schleifenkopf ver-
lagert.
Der letzte Punkt soll mit drei Beispielen verdeutlicht werden, welche alle drei die Lö-
sung einer Aufgabe darstellen. Aufgabe ist, dass man mit einer Schleife alle Zahlen von
57 bis 41 (inklusive) herunterzählt und eine Schrittweite von 0.5 wählt ([Link]).
In dieser Lösung initialisieren wir schleifenlokal eine Variable imit Startwert 57. Die for-
Schleife läuft, solange igrößer oder gleich dem Endwert ist 41, und in jedem Schleifen-
durchlauf wird i um die Schrittweite 0.5 reduziert. Die aktuelle Zahl wird in jedem
Durchlauf in der Konsole ausgegeben. Die Schleife endet, wenn i kleiner als der End-
wert wird. Das ist ein kompakter, einfacher Code und die Aufgabenstellung suggeriert
bewusst eine solche Herangehensweise als nahe liegende Lösung. Aber diese Lösung
bricht die Konventionen, die oben genannt werden. Denken Sie an eine, Ihnen fremde,
Schleife, die umfangreicher ist und wo der Schleifenkopf nicht gemeinsam mit dem In-
halt des Schleifenkörpers zu sehen ist. Oder wenn wir eine while-Schleife haben und die
Initialisierung der Zählvariablen erfolgt viele Quellcodezeilen oberhalb der eigentlichen
Schleife. Woher sollten Sie wissen, wie der Initialisierungswert ist?
15.3 Ergänzende Regeln für JavaScript-Kodierung 389
Vereinfacht auf unser Beispiel, würden Sie etwa nur wissen, dass Sie einen Teil einer
Schleife sehen und darin die Logik, die Sie nachfolgend sehen, wiederholend ausgeführt
wird.
Beispiel
[Link](i);
Was würden Sie erwarten, was da gemacht wird? Ich würde erwarten, dass alle Zahlen
von 0 bis zu einem Endwert mit der Schrittweite 1 hochgezählt und ausgegeben werden.
Ich hoffe es wird deutlich, wo das Problem bei dieser – zugegeben einfachen und kom-
pakten – Lösung ist. Das wäre eine Alternative, in der Logik aus dem Schleifenkopf in
den Schleifenkörper verlagert wird ([Link]):
"use strict";
let zahl = 57; // Startwert
for (let i = 0; zahl >= 41; i++) {
[Link](zahl);
zahl -= 0.5; // den Wert um 0.5 verringern
}
Das Ergebnis ist identisch, wobei hier aufgrund des reinen Schleifenkörpers immer noch
nicht ganz klar ist, welchen Wert zahl beim Start der Schleife hat, wenn man deren Ini-
tialisierung nicht sehen kann. Ebenso haben wir im Schleifenkopf eine Abhängigkeit von
dem Wert von zahl und wenn dessen Initialisierung viele Zeilen im Quellcode entfernt
steht, ist auch das Abbruchverhalten der Schleife nicht wirklich sofort zu sehen. Aber
man kann auch so die Aufgaben lösen ([Link]):
"use strict";
let zahl; // Startwert
for (let i = 0; i <= (57 - 41) * 2; i++) {
zahl = 57 - (i * 0.5);
[Link](zahl);
}
Die gesamte Logik ist nun in den Schleifenrumpf verlagert und die Anzahl der
Schleifendurchläufe hängt unmittelbar an der Zählvariable i, die um den Wert 1 erhöht
wird. Beachten Sie, dass der Algorithmus zum Ermitteln des Endwerts ((57–41) * 2)
natürlich auch hartkodiert hier notiert werden kann. In dem Beispiellisting soll nur nach-
vollziehbar sein, wie sich der Wert ergibt. In der Tat ist die Logik im Schleifenrumpf
jedoch jetzt aufwendiger, aber in der Regel ist diese Lösung dennoch von der Software-
qualität besser, weil eben alles in dem Schleifenrumpf gekapselt ist.
390 15 Eine Frage der Qualität
Wenn es in einem Skript zu einem Fehler kommt, muss man diesen finden und damit
umgehen. Dabei sind insbesondere Webseiten ein ganz besonderes Umfeld, aber auch
JavaScript macht das Auffinden von Fehlern nicht immer leicht. Webseiten werden von
Browsern nach dem Prinzip der Fehlertoleranz interpretiert. Wenn Sie in HTML einen
Fehler machen, werden Webseiten dennoch im Browser zu sehen sein. Im schlimms-
ten Fall werden einige Informationen unterdrückt oder Teile einer Steueranweisung als
Klartext im Browser ausgegeben. In anderen Situationen werden Tags vergessen und
der Browser ergänzt sie nach gewissen Vorgaberegeln oder strukturiert die Webseite
im Hintergrund um. In den allermeisten Fällen merkt ein Anwender (und oft auch ein
Webseitenersteller) gar nichts von dem Fehler, weil der Browser die Fehler auf diese
Weise automatisch korrigiert und – zumindest optisch – für ein Resultat sorgt, wie es
der fehlerfreie Code wahrscheinlich auch bewirkt hätte. Nur bei JavaScript handelt es
sich um echte Programmierung und JavaScript kann deshalb nicht so fehlertolerant wie
eine Beschreibungssprache sein. Fehler in JavaScript können sowohl den Browser des
Anwenders abschießen als auch im schlimmsten Fall den ganzen Rechner negativ beein-
flussen. Im harmlosesten Fall läuft Ihr Skript nicht oder nicht korrekt (was viel schlim-
mer sein kann).
Die Suche nach Fehlern in einem JavaScript ist jedoch ein recht mühseliges Geschäft.
Denn als Teil einer Webseite haben Sie einzukalkulieren, dass der Browser durch das
Prinzip der Fehlertoleranz eine Basis, die Sie vermuten, im Hintergrund so verändert hat,
dass Sie von falschen Voraussetzungen ausgehen. Zudem ist JavaScript keine strenge
Sprache. Mit der losen Typisierung und laxen Syntaxregeln legte JavaScript von Anfang
an den Fokus auf den leichten Einstieg in die Sprache und die Möglichkeit zur schnellen
Erstellung von schlanken Skripten. Den Preis zahlt man bei der Wartung und der Suche
nach Fehlern. Glücklicherweise gibt es mittlerweile mächtige Tools zur Suche als auch
Vermeidung von Fehlern, die mit den Tools in anderen Sprachen wie Java oder C durch-
aus mithalten können.
Wir müssen uns bei den möglichen Fehlern unter JavaScript (als auch den meisten ande-
ren Skript- und Programmiersprachen) mit verschiedenen Situationen auseinandersetzen:
Die Beseitigung der Fehlerpunkte 1 bis 3 ist hauptsächlich das, was man unter De-
bugging versteht. Punkt 4 zählt teilweise ebenfalls dazu, was man Debuggen nennt,
wird jedoch bei Skripts weitgehend dem Interpreter überlassen oder mit Ausnahme-
behandlung behandelt.
[Link] Typografische Fehler
Wenn Sie sich beim Schreiben des Quellcodes vertippt oder ein Schlüsselwort, eine Va-
riable oder sonst etwas Entscheidendes falsch geschrieben haben, können im Wesent-
lichen zwei Fälle auftreten.
1. Sofern Sie ein Wort komplett falsch geschrieben haben, ist die Situation oft leicht in
den Griff zu bekommen. Wenn der Schreibfehler zu keinem vernünftigen Ausdruck
(Token) führt, wird der Ausdruck in der Regel einen syntaktischen Widerspruch
innerhalb des Skripts erzeugen und das System wird bei seiner Arbeit den Fehler ent-
decken. Danach reagiert der Interpreter im Fall von JavaScript mit Abbruch der Ver-
arbeitung von dem Skript und gibt meist eine Information zurück, wie Sie den Fehler
finden können. Falls Sie direkt an dieser Adressangabe den Fehler finden, haben Sie
Glück, denn leider ist diese Adressangabe nur die Stelle, wo sich ein Fehler auswirkt
und nicht unbedingt der Entstehungsort. Dies muss nicht immer identisch sein, aber
zumindest ist der Fehler schon einmal eingegrenzt und muss vorher im Quellcode
irgendwo gemacht worden sein.
2. Schlimmer ist, wenn Sie zufällig einen solchen Schreibfehler gemacht haben, sodass
der falsche Begriff wieder einen sinnvollen Token und keinen syntaktischen Wider-
spruch ergibt. Denn das wird ein viel ernsteres Problem. Der Parser oder Interpreter
würde kein Problem melden, wenn Sie etwa nur Token vertauschen und sich kein syn-
taktischer Widerspruch ergibt. Gerade JavaScript ist hier durch die lose Typisierung
im Zusammenhang mit Variablen extrem fehleranfällig. In so Fall haben Sie ohne
Hilfsmittel nur die Chance, dass Sie den Fehler im Quelltext durch aufmerksames
Lesen oder Analyse des Resultats finden (sehr schwer – lassen Sie sich nicht durch
dieses extrem einfache Exempel täuschen). Oder aber Sie greifen auf Testausgaben
oder gar einen Debugger zurück (Abschn. 1.4.3).
[Link] Syntaktische Fehler
Bei syntaktischen Fehlern in einem Skript (Fehler bei der Verwendung der Sprachsyntax)
wird der Interpreter in der Regel die Abarbeitung unterbrechen. Damit sind solche Feh-
ler meist sehr einfach zu finden. Viele syntaktische Fehler beruhen auf typografischen
Fehlern, aber beileibe nicht alle. Denn dieser Typ von Fehler beinhaltet auch die fal-
sche Verwendung von korrekten Ausdrücken oder Token (beispielsweise so etwas wie
for (i = = 0; i < 256;i + + ) – ein Vergleich statt einer Wertzuweisung der Zählvariable).
Leider gilt auch hier, dass nicht unbedingt die Adressangabe des Fehlers in der Fehler-
anzeige eines Browsers der Entstehungsort des Fehlers sein muss.
392 15 Eine Frage der Qualität
[Link] Laufzeitfehler
Es gibt auch Programmierfehler, die sich erst zur Laufzeit des Skripts auswirken. Unter
Umständen sogar nur in einer ganz spezifischen Konstellation. Solche Fehler werden auf
logische Unzulänglichkeiten im Programmaufbau zurückzuführen sein. Hierbei haben
Sie ein syntaktisch vollkommen korrektes Skript geschrieben, das aber Fehler zu Lauf-
zeit zulässt. Dies geschieht zum Beispiel, wenn eine Variable einen falschen Wert be-
kommt.
Sie müssen zur Beseitigung der Fehlersituation die Stelle im Quelltext suchen, an der
diese Variable (Funktion, Methode usw.) verwendet wird. Dort müssen Sie dann ver-
suchen, den Aufbau und mögliche Fehler noch einmal durchzuspielen. Es gibt ebenso
Programmfehler zur Laufzeit, welche auf äußere Umstände (Umgebung des Programms
oder den Anwender) zurückzuführen sind. Es handelt sich also um Fehler, die mehr auf
konzeptioneller Seite zu suchen sind und weniger um technische Fehler.
Fehler beim Programmieren können nie vollständig verhindert werden. Selbst Profis
unterlaufen permanent Fehler. Aber man kann die Anzahl der Fehler beim Kodieren re-
duzieren und gleichzeitig die Wartbarkeit von Projekten im Griff halten. Wartbarkeit und
Reduktion der Fehler beim Kodieren gehen direkt einher. Diese Maßnahmen, die oben
genannt wurden, können auch Einsteiger gleich von Anfang an berücksichtigen. Auch
bei einfachen Projekten und Quellcodes, um sich gleich einen guten Programmierstil an-
zugewöhnen.
Wenn nun im Skript trotz sorgfältiger Arbeit Fehler vorhanden sind, bleibt nur die
Fehlerbeseitigung. Dabei stellt sich zuerst das Problem, den Fehler überhaupt zu loka-
lisieren. Dies ist der größte und wichtigste Teil dessen, was Debugging genannt wird –
das Lokalisieren und Identifizieren eines Fehlers. Der meist einfachere Teil ist die dann
folgende Beseitigung des Fehlers. Sie können auch ohne Hilfsmittel erfolgreich auf die
Jagd nach Käfern gehen. Das funktioniert ganz gut, wenn man gewisse Konzepte zur
Suche verwendet. Die Fehlersuche ohne Debugger hat die wesentlichen Vorteile, dass
Sie außer dem Editor und dem Browser kein zusätzliches Programm benötigen und sich
vor allem nicht in dessen Bedienung einarbeiten müssen. Zudem lernen Sie eine Menge
über JavaScript.
Die Stelle, wo sich ein Fehler auswirkt, muss wie schon erwähnt nicht die Stelle sein, wo Sie einen
Fehler machen. Das gilt vor allen Dingen im Interpreter-Fall. Wenn Sie mit einem Auto aus einer
Kurve fliegen, unterscheiden sich auch die Stelle, an der Sie den Fehler machen und die Stelle, wo
er sich auswirkt (wenn das Auto dann wieder irgendwo aufschlägt). Sie werden in der Konsole
meist nur die Stelle erkennen, wo ein Fehler zum Tragen kommt. Sie können aber davon ausgehen,
dass Sie irgendwo vorher im Skript einen Fehler gemacht haben.
[Link].2 Kontrollausgaben
Mit Hilfe der Ausgaben in der Fehlerkonsole können Sie bereits Fehler recht genau lo-
kalisieren und auch klassifizieren. Der klassische Weg, um nicht offensichtliche Fehler
ohne einen Debugger zu finden, sind jedoch Kontrollausgaben, welche im endgültigen
Quellcode beseitigt werden. Gängige Praxis ist, vor einem vermuteten Fehler eine Bild-
schirmausgabe zu erzeugen. Sie können z. B. den Wert einer Variablen, in der man die
Fehlerursache vermutet, anzeigen. Oder man notiert einfach eine Meldung um zu sehen,
ob das Skript eine bestimmte Stelle erreicht. Für solche Kontrollausgaben verwendet
man in JavaScript als Teil einer Webseite oft ein alert-Fenster oder die Ausgabe in der
Konsole über [Link].
HTML ist wie ein Notenblatt und der Browser spielt die Noten. Das kann einmal sehr unterschied-
lich klingen, zum anderen interessiert Sie nicht das Notenblatt, sondern die erzeugte Musik.
• Sie können beispielsweise ein Skript schrittweise verfolgen (so genanntes Steppen.
• Man kann ein Skript gezielt anhalten (mit Haltepunkten beziehungsweise Breakpo-
ints).
• Man kann einzelne Variablen und den Zustand des Programms oder Skripts aus-
werten.
• In leistungsfähigen Tools können Sie auch während das Skript oder Programm unter-
brochen ist einen Wert in einem Ausdruck manuell ändern.
Innerhalb eines Debuggers können Sie in der Regel durch verschiedene Schaltflächen
bzw. Menübefehle den Ablauf des Skripts verfolgen und kontrollieren. Als Werkzeuge
bietet das Programm unter anderem eine Übersicht der aktuell geöffneten Dokumente
und eine Möglichkeit Befehle einzugeben.
Ist ein Fehler lokalisiert, müssen Sie den Fehler beseitigen. Anhand der Fehlermeldung
in der JavaScript-Konsole oder aber auch Rückmeldungen von einem Debugger kön-
nen Sie in etwa sehen, was für ein Fehler vorliegt und was zu tun ist. JavaScript selbst
beschreibt mehrere Fehlertypen, die oft auch in der Fehlerkonsole eines Browsers oder
einem Debugger unmittelbar oder umschrieben zu sehen sind. Wichtig sind die folgen-
den (Tab. 15.1):
15.5 Erweiterte Techniken 395
Tab. 15.1 Fehlerarten
Fehlermeldung Bedeutung
ConversionError Ein Fehler beim Konvertieren eines Objektes
RangeError Überschreitung eines erlaubten Bereiches
ReferenceError Ein Referenzfehler
RegExpError Fehler bei einem regulären Ausdruck
SyntaxError Ein allgemeiner Fehler in der JavaScript-Syntax
TypeError Ein Fehler im Datentyp
URIError Ein Fehler beim Zugriff auf eine Datei
Beispiel
[Link]=function(){
... // Fehlergegenmaßnahme
}. ◄
Zum Abschluss sollen für erfahrene Programmierer und/oder ambitionierte Leser noch
Tools, Techniken und Frameworks vorgestellt werden, die Fehlersuche und Fehlerver-
meidung effektiv unterstützen können und vor allen Dingen Qualität und Wartbarkeit von
Quellcodes verbessern. Allerdings ist der Umgang mit den Tools teils nicht ganz trivial.
Gegebenenfalls können Sie später bei Bedarf darauf zurückkommen. Wobei ich betonen
396 15 Eine Frage der Qualität
möchte, dass dieser Abschnitt auch für Einsteiger viele Tipps, Tricks und Techniken ent-
hält, damit gleich ein sauberer Programmierstil mit JavaScript angewöhnt wird.
Dokumentation von Quelltext ist sowohl für die Fehlervermeidung (gerade von logi-
schen Fehlern) wichtig, denn man denkt bei der Dokumentation noch einmal über den
Quellcode nach. Vor allen Dingen aber wird die Wartbarkeit verbessert, wenn man selbst
oder ein anderer Programmierer später noch einmal den Quellcode anfassen muss. Es
gibt nun in JavaScript eine Spezialversion von einem mehrzeiligen Kommentar, der zu
automatischen Dokumentationszwecken verwendet werden kann. Dabei wird der mehr-
zeilige Kommentar einfach mit /** begonnen (also zwei Sternchen). Aus Sicht von Ja-
vaScript ist das dann nur ein gewöhnlicher mehrzeiliger Kommentar. Aber es gibt Doku-
mentationstools, die dies als Token verstehen und nachfolgenden Kommentar verarbeiten
können, um eine Dokumentation zu erstellen. Beachten Sie, dass diese Kommentare
immer unmittelbar vor der Struktur zu notieren sind, die damit kommentiert werden sol-
len. Mit geeigneten Tools wie jsdoc bzw. jsdoc3 ([Link] oder
jGrouse Doc ([Link] geht das Erzeugen von Kommenta-
ren aus mittlerweile auch JavaScript automatisiert.
Im Grunde arbeiten die Tools alle ähnlich. Ein Programm, das mit diesen speziellen
Kommentaren umgehen kann, erzeugt auf der Basis von diesen speziellen Kommentaren
innerhalb einer JavaScript-Quelldatei oder eines entsprechenden JavaScript-Bereichs in
einer Webseite eine Anzahl HTML-Dateien als API-Dokumentation der dokumentierten
Strukturen. Vereinfacht ausgedrückt schreibt so ein Tool die innerhalb der Datei in spe-
ziellen Kommentaren eingeschlossenen Texte in eine HTML-Datei. Darin können sich
beliebige Informationen befinden. Innerhalb des Kommentarcontainers /** … */ befindet
sich wie gesagt der Kommentartext, der neben reinem Text spezielle Steuerbefehle be-
inhalten kann, die automatisiert zur Erstellung bestimmter Standardelemente in der
Dokumentation verwendet werden. Die meisten Tools verwenden weitgehend analoge
Token. Sie werden dort innerhalb des Kommentarcontainers mit dem speziellen Zeichen
@ eingeleitet und sollten immer in einer einzelnen Zeile innerhalb des Containers ste-
hen. Für Details muss man sich die Dokumentation eines konkreten Tools ansehen, denn
die tatsächlich von einem konkreten Tool genau unterstützen.
Ein mächtiges Tool oder genauer ein Skript zur automatischen Kontrolle von JavaScript-
Code nennt sich JSLint ([Link]
15.5 Erweiterte Techniken 397
Linting hat sich in der Programmierung als Fachbegriff für allgemeine Kontrolle der
Softwarequalität etabliert.
JSLint soll unabhängig von der internen Arbeitsweise eines speziellen Browsers oder
einer Engine Programmierfehler und auch stilistische und qualitative Mängel in Ja-
vaScripts anzeigen und damit die Qualität des Codes erhöhen. Es geht also neben der
Suche nach echten Fehlern auch um eine explizite Prüfung, ob die strengen Kodierungs-
regeln von JavaScript wirklich vollständig eingehalten werden, obwohl es möglicher
Weise gar keine sichtbaren Fehlereffekte in Testsytemen, einer Konsole noch ggf. in
einem Debugger. Um so einen Qualitätssicherungstest auszuführen, können Sie auf der
Webseite des Projekts einfach den Skript-Code in ein Formular einfügen und prüfen las-
sen. Soweit ist der Umgang mit dem Tool vollkommen trivial. Aber die Interpretation
der Meldungen ist es ganz und gar nicht und zum Nutzen von diesem Skript gibt es sehr
kontroverse Ansichten. Natürlich werden echte Fehler gefunden und das ist wirklich eine
große Hilfe. Das Tool findet in der Tat auch viele Fehler, zeigt jedoch auch viele Stellen
im Code an, die zwar Regeln brechen, von Browsern zum Teil aber automatisch korri-
giert werden. Des Weiteren muss man bei JavaScript auch oft bestimmte Dinge program-
mieren, die JSLint als nicht konform mit dem Standard bewertet und als Fehler anzeigt.
In gewisser Weise hat JSLint das gleiche Problem wie der Validator vom W3C (https://
[Link]/) – im Code befindet sich oft nach einem theoretischen Standard ein
Fehler, aber in der Praxis geht allen, und wenn man den Fehler beseitigt, wird der theo-
retische Standard erfüllt und in der Praxis gibt es Probleme. Dazu erschlägt die Vielzahl
an Anmerkungen und viele der monierten Fehlerstellen sind nicht wirklich relevant bis
kleinlich. Etwa das Fehlen eines Leerzeichens vor einer Klammer. Ebenso werden etwa
oft Bezeichner als fehlerhaft angemerkt, da sie angeblich verwendet werden, bevor sie
deklariert wurden. Das passiert auf jeden Fall dann, wenn man nicht alle Skripts einer
Seite gemeinsam prüft und in anderen Skriptdateien die Deklaration erfolgt ist. Mit einer
recht umfangreichen Konfiguration können Sie allerdings das Prüfungsverhalten von die-
sem Skript steuern.
Ich möchte folgende – zugegeben sehr subjektiv geprägte – Empfehlung aussprechen:
nutzen Sie JSLint, aber korrigieren Sie – vor allen Dingen bei bereits funktionieren-
den Skripts – nicht blind jeden angeblichen Fehler. Überlegen Sie bei jeder einzelnen
Stelle, ob eine Korrektur wirklich notwendig ist. Wobei genau hier das Hauptproblem
von JSLint angesiedelt ist – man braucht Erfahrung und Sicherheit im Umgang mit Ja-
vaScript, um ein so professionelles Tool zu überstimmen. Ich rate dazu nur dann einen
angeblichen „Fehler“ zu korrigieren, wenn Sie genau verstanden haben, was da falsch
sein soll. Im Zweifelsfall lassen Sie es besser, wenn Sie ein bereits funktionierendes
Skript testen. Dieses kann oft zwar qualitativ verbessert, aber auch leicht unbrauchbar
gemacht werden. Aber selbst wenn Sie bei hunderten von angeblichen Fehlerstellen in
einem funktionierenden Skript nur ein oder zwei echte Probleme verbessern, hat sich der
Einsatz gelohnt.
398 15 Eine Frage der Qualität
15.5.3 xUnit-Testing
Dieser Abschnitt ist vor allen Dingen für Leser mit fundiertem Background oder Ambi-
tionen hinsichtlich professioneller Programmierung interessant. Die Technik ist zudem
hauptsächlich bei einer besonderen Vorgehensweise der Projektarbeit bzw. Program-
mierung von Bedeutung. Aber es ist zumindest interessant zu wissen, dass es auch unter
JavaScript einige Projekte gibt, die xUnit als auch testgetriebene Entwicklung (TDD –
Test-Driven Development) unterstützen. Mit TDD wird eine Methode bezeichnet, bei der
konsequent im Rahmen des Entwicklungszyklus Softwaretests von zu testenden Kom-
ponenten einer Applikation (programmtechnische Einheiten oder Units) erstellt wer-
den. Die Tests selbst sollten dabei weitgehend automatisiert ablaufen und bereits vor
(!) der eigentlichen Kodierung des Quellcodes einer Unit erstellt worden sein. Mit die-
sem Test- und gleichzeitig Entwicklungsverfahren sollen sowohl Probleme klassischer
manueller Tests oder auch Abschlusstests des Gesamtsystems als auch Probleme schon
bei der Entwicklung selbst kompensiert werden. Allgemein werden bei solch einer test-
getriebenen Entwicklung Tests im Kleinen (Unit-Tests) und Tests im Großen (System-
tests, Akzeptanztests) unterschieden. Aber man kann unabhängig von strenger TDD auch
rein den automatisierten Testaspekt in den Fokus stellen. Im JavaScript-Umfeld gibt
es mit dem JsUnit-Framework ([Link] oder das zu
jQuery zählende QUnit ([Link] und ähnlichen Systemen mittlerweile sol-
che Testmöglichkeit.
1. Zuerst schreibt man die Tests, die das erwünschte fehlerfreie Verhalten für eine be-
stimmte Funktionalität prüfen. Zu dem Zeitpunkt werden diese Tests von dem be-
stehenden Programmcode normalerweise noch nicht erfüllt bzw. es gibt diesen
Programmcode sogar noch gar nicht.
2. Nächster Schritt ist das Ändern oder Erstellen des Programmcodes, der die Tests er-
füllen soll. Dabei sollte man diesen Code zu dem Zeitpunkt mit möglichst wenig Auf-
wand erstellen. Der Code braucht nicht optimiert zu sein. Ziel in dieser Phase ist nur,
dass der anschließende Testdurchlauf alle Tests besteht. Grundsätzlich ist die Philo-
sophie von TDD darauf ausgelegt, Tests zu bestehen, nicht eine konkrete Software zu
erstellen. Die Erstellung der Software ist quasi ein „Nebenprodukt“.
3. Wenn die Tests bestanden sind, erfolgt die (vorsichtige) Optimierung des Codes
ohne Änderung der Funktionalität. Dies bezeichnet man als Refactoring (deutsch
Refaktorisierung, Restrukturierung oder Umgestaltung). Diese manuelle oder auto-
matisierte Strukturverbesserung von Programmquelltexten soll die Lesbarkeit, Ver-
ständlichkeit, Wartbarkeit und Erweiterbarkeit (und unter Umständen auch die Quali-
tät, Stabilität und Performance des Programms) verbessern.
4. Mit einem abschließenden Testen wird überprüft, ob die Refaktorisierung keine (un-
erwünschte) Veränderung des Programmverhaltens bewirkt hat.
Diese Schritte werden beim Vorantreiben der Applikation für jeden neuen Bestandteil der
Applikation so lange wiederholt, bis die gewünschte Funktionalität der ganzen Applika-
tion erreicht oder ein bekanntes Fehlverhalten bereinigt ist.
Es gibt keine Vorgaben, wie Tests bei einer TDD auszusehen haben. Es liegt in der Verantwortung
des Entwicklers sich alle potenziellen Fehlverhalten einer Einheit im Vorfeld zu überlegen und
sinnvolle Tests auszudenken. Die Konzeption von Tests wird bei konsequenter Anwendung der
TDD so lange vorangetrieben, bis keine sinnvollen weiteren Tests mehr einfallen. Sie sehen damit
jetzt schon, dass der Erfolg einer TDD stark von der Erfahrung eines Programmierers abhängt.
Eine letztendlich als fehlerfrei getestete und entwickelte Unit wird bis auf Weiteres als
fertig angesehen und kann der Testfunktionalitäten entledigt oder aus dem Testszenario
extrahiert werden.
die kann sich als korrekt oder falsch erweisen. Das Misslingen des Tests kann auf einem
echten Fehler (Error) oder einem falschen Ergebnis des Tests (Failure) beruhen. Beide
Situationen lösen in einem xUnit-Framework eine Ausnahme im Sinne des Exception-
Handlings aus. Ein falsches Ergebnis, das in der Konzeption einer Unit einkalkuliert
werden kann, löst eine abfangbare Ausnahme aus, während alle anderen Fehler vom Fra-
mework als echte Fehler geliefert werden. Dabei liegt es an der konkreten Umsetzung,
wie ein xUnit-Framework reagiert.
[Link] Konkretes xUnit-Testing
Schauen wir uns ein paar praktische Beispiele an, welche das Konzept klarmachen soll-
ten. Dazu verwenden wir QUnit, wobei alle xUnit-Frameworks ähnlich arbeiten. [Link]
soll die JavaScript-Umgebung darstellen, wobei es auch Testrunner gibt, die über einen
Browser genutzt werden. Aber die Verwendung von [Link] hat Vorteile, ist schlanker
und schneller. Zuerst einmal muss aber QUnit installiert werden, wenn es noch nicht vor-
handen ist. Etwa mit npm, was wir im Buch ja konsequent nutzen:
Beispiel
Nun erstellen wir eine einfache Funktion, die wir testen wollen. Das macht man z. B. in
einem Modul ([Link]), das in einem eigenen Verzeichnis xunit1 gespeichert werden soll.
Nun brauchen wir eine Datei für den Testrunner. In einem Unterverzeichnis test erstellen
wir dazu eine weitere Datei – ebenfalls mit dem Namen [Link], wobei der Name der
Datei bei der nachfolgend gezeigten Art des Testaufrufs keine Rolle spielt.
Wir importieren zuerst das Modul mit der zu testenden Funktion aus dem Elternverzeich-
nis. Der folgende Aufruf von [Link] definiert eine Testsuite für die Funktion add mit
einem einzigen Testfall, der das Ergebnis der Addition zweier Zahlen überprüft. Es wird
geschaut, ob die Addition von 1 und 3 den Wert 3 ergibt. Detaillierte Informationen zum
15.5 Erweiterte Techniken 401
Organisieren von Tests und zum Erstellen anderer Aussagen finden Sie in der API-Doku-
mentation von QUnit.
Wir können jetzt den Test über einen Testrunner im Web als auch die QUnit-CLI aus-
führen. Letzteres werden wir machen. Es wird in der Dokumentation von QUnit emp-
fohlen, den qunit-Befehl über ein npm-Skript auszuführen, das das qunit-Programm
automatisch in Ihrem lokalen Ordner node_modules findet, in dem npm die von Ihnen
heruntergeladenen Abhängigkeiten speichert. Dazu benötigen wir noch eine package.
json-Datei in unserem Projektordner wie folgt:
Mit der Anweisung npm test wird der QUnit-Test dann in einem Terminal ausgeführt
(Abb. 15.1).
Nun ist ein Kennzeichen von TDD, dass sehr viele Tests automatisiert durchgeführt
werden. Das kann durchaus viele Millionen Testdurchläufe umfassen. Aber auch ohne
strenge TDD ist es meist sinnvoll, sehr viele Testdurchläufe auszuführen. In einem Ja-
vaScript-Testframework können Sie Tests in einer Schleife ausführen, um sie mehrmals
zu wiederholen oder auch in manchen xUnit-Frameworks spezielle Methoden zum Para-
metrisieren von Tests oder zum Wiederholen von Tests nutzen. Ein einfacher Ansatz er-
zeugt viele Tests in einer Schleife. Etwa so:
Beispiel
Eine andere Möglichkeit besteht darin, die Schleife zu verwenden, um den Testcode
innerhalb eines einzelnen QUnit-Tests zu wiederholen. Etwa so, was meist performanter
als der erste Ansatz ist:
Beispiel
Nun sind die getesteten Behauptungen bisher trivial und deshalb werden alle Tests
erfolgreich sein. Deshalb soll jetzt zwei neue Funktionen getestet werden, die nicht
so trivial die Tests bestehen werden. Genaugenommen soll eine Funktion sogar häufi-
ger „versagen“. Betrachten wir diese Funktion, die wir in zwei Schritten testen wollen
([Link]):
funktion lotto1() {
var zahlen=[];
for(var i=0;i< 6; i++){
zahlen[i] = 1 + [Link]([Link]()*49);
}
return zahlen;
}
Diese Funktion soll die Ziehung der Lottozahlen simulieren und sechs zufällige Zahlen
zwischen 1 und 49 (jeweils inklusive) liefern. Zwei Dinge sind zu testen:
Zuerst testen wir in dem Verzeichnis xunit2 den Algorithmus, ob alle Zahlen zwischen
1 und 49 liegen. Das wäre der Testaufbau nur für den ersten Test, bei dem 100.000 Test
durchgeführt werden und der Test in einem eigenen Verzeichnis gefahren wird:
Der Aufruf erfolgt am besten wieder mit npm test. Wenn Sie mögen, können Sie die Aus-
gabe aktivieren, um die einzelnen Tests zu sehen. Wenn Sie die getesteten Grenzen ver-
ändern und etwa schauen, ob der Algorithmus nur Zahlen kleiner als 43 liefert, werden
Ausnahmen geworfen und der Test war nicht erfolgreich (Abb. 15.2). Aber unser Grund-
algorithmus ist ok.
Betrachten wir diese Abwandlung im Verzeichnis xunit3:
Hier wird bei jedem Schleifendurchlauf ein neuer Test erstellt. Der Testdurchlauf dau-
ert dann erheblich länger und kann bei zu vielen so erzeugten Testobjekten sogar das
System einfrieren. Allerdings erhalten Sie so eine Aussage, in wie vielen Fällen der Test
einen Fehler ergeben hat (Abb. 15.3).
Das kann interessant sein, wenn man mit einer gewissen Fehlerhäufigkeit leben kann.
In der Regel wird eine vollständige Fehlerfreiheit natürlich das Ziel sein, aber so etwas
ist nicht immer mit einem vertretbaren Aufwand zu garantieren1. Denken Sie an Farbein-
stellungen oder das Laden bestimmter Schriftarten. Wenn das mit einer geringen Fehler-
wahrscheinlichkeit schiefgeht, kann man dennoch die Software u. U. einführen.
1 Ich habe die provokante These gelesen, dass es nur zwei Situationen gibt, in denen man unter
allen Umständen 100 % Fehlerfreiheit garantieren muss. Bei Software in Flugzeugen und Software
zum Steuern von Atomkraftwerken.
404 15 Eine Frage der Qualität
Zurück zum getesteten Algorithmus. In der Originalversion ist der Algorithmus kor-
rekt und kann als getestet verwendet werden. Kommen wir nun dazu, dass wir die ge-
samte Funktion testen wollen, ob alle gezogenen Zahlen verschieden sind. Das ist ziem-
lich offensichtlich nicht der Fall und das muss ein Test auch entdecken. Denn unser
Algorithmus simuliert ein Ziehen mit Zurücklegen.
Sie werden an den Testergebnissen sehen, dass immer wieder Zahlen in dem Array
mehrfach auftauchen und damit kann dieser Algorithmus die geforderte Aufgabe nicht
zuverlässig lösen. Aber die nachfolgende Version ist eine Verbesserung, die ebenfalls ge-
testet werden soll (xunit5):
Bei diesem Test wird kein einziger Fall vorkommen, dass Zahlen mehrfach in dem Array
auftauchen. Mit den beiden erfolgreichen Tests kann man also zuverlässig schließen,
dass die programmierte Funktion in der zweiten Variante die geforderte Aufgabe erfüllt.
Wenn Sie eine fortgeschrittene Steuerung über die Wiederholung von Tests
benötigen, könnten Sie einen Testrunner wie Mocha verwenden, der eine um-
fangreichere Funktionalität für die Testausführung bietet und beispielsweise
Hooks für Wiederholungen oder Konfigurationen für parallele Ausführung von
Tests ermöglicht.
Bei der Suche nach Fehlern sollte man Cross-Engine-Probleme in JavaScript nicht ganz
vergessen, obwohl sie mittlerweile stark abnehmen. Dies sind Probleme, die auftreten
können, wenn JavaScript-Code in verschiedenen JavaScript-Engines oder Browser-
umgebungen ausgeführt wird. Jede JavaScript-Engine implementiert den ECMAScript-
Standard auf ihre eigene Weise, was zu Inkompatibilitäten und unerwartetem Verhalten
führen kann, wenn derselbe Code in verschiedenen Umgebungen ausgeführt wird. Hier
sind einige häufige Cross-Engine-Probleme und wie sie sich manifestieren können:
Der fundamentale Grundsatz in der Programmierung ist, dass Funktion vor Optimierung
geht. Ein Programm/Skript muss erst einmal fehlerfrei seine Aufgabe erledigen. Aber
dann kann man sich gegebenenfalls darum kümmern, dass es optimiert wird. Das be-
deutet, dass man die Performance, den Ressourcenbedarf, die Wartbarkeit oder noch di-
verse andere Dinge verbessert. Gerade im Web gibt es da eine Menge zu beachten. Es
gibt diverse Möglichkeiten, wie Sie eine Webseite im Allgemeinen verbessern können.
Das beginnt ganz banal bei der Reduzierung der Anzahl der Zeichen im Quellcode, be-
ginnend bei Leerzeichen und anderen Whitespace-Zeichen in der gesamten Webseite
(auch der reinen HTML-Bereiche), und führt über eine kompaktere Verwendung von
HTML-Tags bis hin zu der Kompression von Style Sheets und Skripten. Aber auch
andere Maßnahmen wie die Auslagerung von Style Sheets und Skripten in externe
Dateien und/oder Module, das zeitversetzte Laden von zusätzlichen Ressourcen, die
optimale Wahl der Größe von Bildern (in Hinsicht auf HTML5 der Einsatz von Vektor-
grafiken anstelle von Pixelgrafiken), das korrekte Schließen von Tags, das saubere Ver-
schachteln von Tags oder die passende Angabe von im Grunde optionalen Attributen
bei HTML-Tags bzw. – noch besser – deren Festlegung in Style Sheets sind klassische
Optimierungsschritte für eine Webseite im Allgemeinen.
Um die Größe des Quelltextes bei HTML-, aber auch CSS und JavaScript-
Dokumenten zu reduzieren, gibt es sogenannte Kompressoren. Dazu gibt
es einige Anbieter, die das direkt über Webformulare ermöglichen und von
denen Sie hier zwei Vorschläge sehen ([Link]
pressor/ oder [Link] Der Einsatz von Kom-
pressoren geht meist mit zwei verschiedenen Versionen Ihrer Quelltexte ein-
her. Es gibt eine Version, mit der Sie programmieren und die Sie ausführlich
mit Zeilenumbrüchen, Kommentaren und Einrückungen versehen, sowie eine
Variante, die Sie ausliefern und vorher durch einen Kompressor jagen. Die Ver-
sion, die Sie dann veröffentlichen, ist eine minimierte Version, die um Kom-
mentare und überflüssige Leerzeichen sowie Zeilenumbrüche reduziert ist.
Funktional ist diese aber immer identisch mit der unkomprimierten Version.
408 15 Eine Frage der Qualität
Nun wollen wir uns hier auf die JavaScript-Seite konzentrieren und uns anschauen, wie
Sie die JavaScripts optimieren können, sowohl in Hinsicht auf die Größe als auch vor
allen Dingen auf die Geschwindigkeit der Verarbeitung im Browser. Hier gibt es für Sie
eine Reihe von Rezepten, die mehr oder weniger effektive Verbesserungen bewirken.
Dabei hängt es sowohl an der Art und Komplexität des Skripts als auch dem jeweiligen
Browser, ob ein Rezept überhaupt einen Vorteil bewirkt und wenn ja wie effektiv. Einige
Maßnahmen sind jedoch Browser übergreifend sinnvoll. Wir werden einige der populärs-
ten Rezepte besprechen und die wichtigsten bzw. effektivsten Maßnahmen ausdrücklich
kennzeichnen.
Optimierungsmaßnahmen können schnell dazu führen, dass der Code so weit opti-
miert wird, bis er nicht mehr läuft. Leider passiert das oft. Ebenso können manche
Maßnahmen sich gegenseitig negativ beeinflussen und andere Maßnahmen sind
unter gewissen Konstellationen sogar grundsätzlich wenig sinnvoll. Optimieren
Sie nur so, dass die Funktionalität vollkommen gewährleistet ist und durch Op-
timierung nicht beeinträchtigt wird. Sichern Sie vor allen Dingen immer (!) den
funktionstüchtigen Stand, bevor Sie mit der Optimierung beginnen. Optimierung
ist ein Vorgang, der mit äußerster Vorsicht angegangen werden sollte und zwingend
eine Versionierung voraussetzt. Entweder machen Sie das manuell, indem Sie das
alte, funktionierende Projekt unter einem anderen Namen sichern, oder Sie ver-
wenden am besten Versionierungstechniken und -tools wie CVS, SVN oder Cloud-
Systeme wie Git.
Betrachten wir nun verschiedene Rezepte zur Optimierung von Skripten, die zum Teil
Dinge wiederholen, zum Teil aber auch ergänzen.
Ein etwas ausführlichere Maßnahme betrifft Memory Leaks (Speicherlecks). Diese kön-
nen auftreten, wenn eine Anwendung Speicher reserviert, ihn aber nicht mehr freigibt,
obwohl er nicht mehr benötigt wird. Ungenutzten Speicherbereiche akkumulieren sich
im Laufe der Zeit und können dazu führen, dass die Anwendung mehr Speicher ver-
braucht als erforderlich ist. Dies kann zu Problemen wie einer verringerten Leistung,
erhöhtem Ressourcenverbrauch und im schlimmsten Fall zum Absturz der Anwendung
führen. In JavaScript können Memory Leaks aus verschiedenen Gründen auftreten:
A Betriebssystem, 4
Abstraktion, 164 Bezeichner, 37, 40
addEventListener, 228 BigInt, 48, 58
Aggregationen, 165 Bildobjekte, 207
Ajax, 292, 300 Bit-Operatoren, 70
Promise, 325 Blockanweisungen, 44
alert-Methode, 17 Boolean, 41, 48, 196
Anweisung, 43 Botschaft, Objekt, 162
leere, 79 break, 41, 83, 91
Apache, 8 Breakpoint, 394
Apache Friends, 8 Browser, Daten speichern, 347
arguments, 125 Browserweiche, 33
Array, 49, 93, 194 Bubble-Phase, 224
Anzahl der Elemente, 98
assoziiertes, 100
Konstruktor, 95 C
mehrdimensionales, 97 Callback, 81, 136
nullindiziertes, 94 Callback-Hölle, 322
verschachteltes, 95 Call-by-Reference, 124
Array-Literal, 96 Call-by-Value, 124
Arrow Functions, 134 Camelnotation, 43
ASCII, 25 Canvas, 371
Assoziationen, 165 case, 41
Attribute, 14, 165 Casting, 46, 47
Ausdruck, 44 catch, 41, 284
regulärer, 207 Promise, 324
Ausnahmebehandlung, 281 Chakra, 6
Ausnahmen konsumieren, 285 ChakraCore, 6
class, 41, 181
Client-Server-System, 293
B Closures, 133
Befehlszeileninteraktion, 80 Code, unerreichbarer, 119
Beispiele Download, 2 Comet-Service, 337
Benutzereingabe, 19 CommonJS, 151, 152
Befehlszeile, 80 Compiler, 6
[Link], 14 E
console Eclipse, 5
Objekt für die Konsole, 14 ECMA, 24
const, 41, 57 ECMA International, 20
constructor, 179 ECMAScript, 24
continue, 41, 91 ECMAScript-Module s. ES6-Module
ConversionError, 395 Editor, 4
Cookies, 348 Eigenschaften, 14, 165
coords, 358 Elemente, statische, 165
CORS, 157 else, 41, 80
createServer, 299 encodeURI, 147
Cross-Domain-Zugriff, 305 encodeURIComponent, 147
Cross-Engine-Probleme, 406 Endlosschleife, 86, 91
Cross-Origin Resource Sharing s. CORS Engines, JavaScript, 5
Cross-Site-Scripting, 25 Entscheidungsanweisungen, 79
Entwicklertools, Browser, 5
Error, 284
D ES6-Module, 151, 155
Date, 196 escape, 147
Dateierweiterung für JavaScript, 14 Escape-Sequenzen, 60
Datenfeld s. Array Eulersche Konstante, 193
Datenkapselung, 134, 184 eval, 147
Datentypen, 45 EVA-Prinzip, 14
explizit, 46 event, 218
Datumsobjekte, 196 Event-Bubbling, 224
Debugger, 394 Eventhandler, 120, 129
debugger, 41 Eventhandling, 221
Debugging, 391, 392 Exceptionhandling, 281
decodeURI, 147 export, 41, 152, 155
decodeURIComponent, 147 extends, 41, 171
default, 41 Extensible Markup Language s. XML
Defaultparameter, Funktionen, 139
Deferred Object, 322
Definition s. Deklaration F
Deklaration, 31 Factory, 186
bedingte Funktionen, 138 Fakultätsberechnung, 142
delete, 41, 63, 110 Fall-Through-Anweisung, 83
Dialogfenster, 17 false, 41, 61
do, 41 Farbverlauf, 375
[Link], 348 Fehler, 390
document, 218 Laufzeitfehler, 392
Document Object Model s. DOM syntaktischer, 391
Dokumentationskommentare, 396 typografischer, 391
DOM, 17, 215 Fehlerkonsole, 393
do-while-Schleife, 87 Feld s. Array
DRY-Prinzip, 387 FIFO, 115
Duck-Typing, 46 FILO, 140
Dynamic HTML (DHTML), 361 finally, 41
Stichwortverzeichnis 413
X
V XAMPP, 8
V8, 5 XHR, 304
Validator vom W3C, 397 XML, 17
var, 42, 52, 55 XMLHttpRequest, 218, 304
Variablen, 45, 52 xUnit, 398
lokale, 121
schleifenlokale, 88
Vektorgrafik, 378 Y
Verallgemeinerung, 162 yield, 42, 137
Vererbung, 165
Vergleichsoperatoren, 69
Verknüpfungsoperator, 63 Z
Versionierung, 408 Zeichenketten, 60
Vielgestaltigkeit, 165 Zeichensatz, 25
Visual Studio (VS), 5 Zeichenverkettungsoperator, 48
VS Code, 5 Zufallszahl, 193
void, 42 Zugriffsmodifizierer, 184
volatile, 40 Zuweisungsoperatoren, 77
Voraussetzungen, 3 Zwischencode, 6