image

Marijn Haverbeke ist ein begeisterter polyglotter Programmierer. Er hat an einer breiten Palette von Softwaresystemen gearbeitet, von Datenbanken über Compiler bis hin zu Editoren. Außerdem führt er ein kleines Unternehmen rund um seine Open-Source-Projekte.

image

Zu diesem Buch – sowie zu vielen weiteren dpunkt.büchern – können Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei dpunkt.plus+:

www.dpunkt.plus

Marijn Haverbeke

JavaScript

Richtig gut programmieren lernen –
Von der ersten Codezeile bis zum eigenen Projekt

image

Marijn Haverbeke

Lektorat: Melanie Feldmann

Bibliografische Information der Deutschen Nationalbibliothek

1. Auflage 2020

Copyright © 2019 by Marijn Haverbeke. Title of English-language original: Eloquent JavaScript, 3rd Edition: A Modern Introduction to Programming, ISBN 978-1-59327-950-9, published by No Starch Press. German-language edition copyright © 2020 by dpunkt.verlag GmbH. All rights reserved.

Hinweis:

image

Schreiben Sie uns:

Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.

5 4 3 2 1 0

Inhaltsübersicht

Einleitung

Teil IDie Sprache

Kapitel 1Werte, Typen und Operatoren

Kapitel 2Programmstruktur

Kapitel 3Funktionen

Kapitel 4Datenstrukturen: Objekte und Arrays

Kapitel 5Funktionen höherer Ordnung

Kapitel 6Das geheime Leben der Objekte

Kapitel 7Projekt: Ein Roboter

Kapitel 8Bugs und Fehler

Kapitel 9Reguläre Ausdrücke

Kapitel 10Module

Kapitel 11Asynchrone Programmierung

Kapitel 12Projekt: Eine Programmiersprache

Teil IIDer Browser

Kapitel 13JavaScript im Browser

Kapitel 14DOM (Document Object Model)

Kapitel 15Umgang mit Ereignissen

Kapitel 16Projekt: Ein Jump’n’Run-Spiel

Kapitel 17Zeichnen auf Leinwand

Kapitel 18HTTP und Formulare

Kapitel 19Projekt: Editor für Pixelgrafiken

Teil IIINode.js

Kapitel 20Einführung in Node.js

Kapitel 21Projekt: Eine Website zur Wissensvermittlung

Kapitel 22Leistung

Hinweise zu den Übungen

Stichwortverzeichnis

Inhaltsverzeichnis

Einleitung

Teil IDie Sprache

1Werte, Typen und Operatoren

1.1Werte

1.2Zahlen

1.3Strings

1.4Unäre Operatoren

1.5Boolesche Werte

1.6Leere Werte

1.7Automatische Typumwandlung

1.8Zusammenfassung

2Programmstruktur

2.1Ausdrücke und Anweisungen

2.2Bindungen

2.3Namen von Bindungen

2.4Die Umgebung

2.5Funktionen

2.6Die Funktion console.log

2.7Rückgabewerte

2.8Ablaufsteuerung

2.9Bedingte Ausführung

2.10While- und do-Schleifen

2.11Einrückungen

2.12for-Schleifen

2.13Eine Schleife abbrechen

2.14Kurzschreibweisen für die Aktualisierung von Bindungen

2.15Werte mithilfe von switch auswählen

2.16Groß- und Kleinschreibung

2.17Kommentare

2.18Zusammenfassung

2.19Übungsaufgaben

3Funktionen

3.1Funktionen definieren

3.2Gültigkeitsbereiche von Bindungen

3.3Funktionen als Werte

3.4Schreibweise von Deklarationen

3.5Pfeilfunktionen

3.6Der Aufrufstack

3.7Optionale Argumente

3.8Closures

3.9Rekursion

3.10Funktionen einführen

3.11Seiteneffekte

3.12Zusammenfassung

3.13Übungen

4Datenstrukturen: Objekte und Arrays

4.1Das Wereichhörnchen

4.2Datenmengen

4.3Eigenschaften

4.4Methoden

4.5Objekte

4.6Veränderbarkeit

4.7Das Tagebuch des Wereichhörnchens

4.8Korrelationen berechnen

4.9Array-Schleifen

4.10Die endgültige Analyse

4.11Arrayologie für Fortgeschrittene

4.12Eigenschaften von Strings

4.13Restparameter

4.14Das Objekt Math

4.15Zerlegung

4.16JSON

4.17Zusammenfassung

4.18Übungen

5Funktionen höherer Ordnung

5.1Abstraktion

5.2Wiederholungen abstrahieren

5.3Funktionen höherer Ordnung

5.4Die Schriftbeispieldaten

5.5Arrays filtern

5.6Transformationen mit map

5.7Zusammenfassungen mit reduce

5.8Komponierbarkeit

5.9Strings und Zeichencodes

5.10Text erkennen

5.11Zusammenfassung

5.12Übungen

6Das geheime Leben der Objekte

6.1Kapselung

6.2Methoden

6.3Prototypen

6.4Klassen

6.5Klassenschreibweise

6.6Abgeleitete Eigenschaften überschreiben

6.7Maps

6.8Polymorphismus

6.9Symbole

6.10Die Iteratorschnittstelle

6.11Get-, Set- und statische Methoden

6.12Vererbung

6.13Der Operator instanceof

6.14Zusammenfassung

6.15Übungen

7Projekt: Ein Roboter

7.1Meadowfield

7.2Die Aufgabe

7.3Persistente Daten

7.4Simulation

7.5Die Route des Postautos

7.6Routen finden

7.7Übungen

8Bugs und Fehler

8.1Die Rolle der Sprache

8.2Strikter Modus

8.3Typen

8.4Tests

8.5Debugging

8.6Fehlerweiterleitung

8.7Ausnahmen

8.8Aufräumen nach Ausnahmen

8.9Selektives Abfangen von Ausnahmen

8.10Assertions

8.11Zusammenfassung

8.12Übungen

9Reguläre Ausdrücke

9.1Reguläre Ausdrücke erstellen

9.2Auf Übereinstimmungen prüfen

9.3Mengen von Zeichen

9.4Teile eines Musters wiederholen

9.5Teilausdrücke gruppieren

9.6Übereinstimmungen und Gruppen

9.7Die Klasse Date

9.8Wort- und String-Grenzen

9.9Alternative Muster

9.10Der Vergleichsmechanismus

9.11Rückverfolgung

9.12Die Methode replace

9.13Gierige Operatoren

9.14RegExp-Objekte dynamisch erstellen

9.15Die Methode search

9.16Die Eigenschaft lastIndex

9.17Eine INI-Datei analysieren

9.18Internationale Zeichen

9.19Zusammenfassung

9.20Übungen

10Module

10.1Module als Bausteine

10.2Pakete

10.3Module

10.4Daten als Code auswerten

10.5CommonJS

10.6ECMAScript-Module

10.7Compiler, Bundler und Minifier

10.8Moduldesign

10.9Zusammenfassung

10.10Übungen

11Asynchrone Programmierung

11.1Asynchronität

11.2Crow Tech

11.3Callbacks

11.4Promises

11.5Fehlschläge

11.6Netzwerke sind schwierig

11.7Kombinationen von Promises

11.8Flooding

11.9Nachrichtenrouting

11.10Async-Funktionen

11.11Generatoren

11.12Die Ereignisschleife

11.13Asynchronitätsbugs

11.14Zusammenfassung

11.15Übungen

12Projekt: Eine Programmiersprache

12.1Der Parser

12.2Der Evaluierer

12.3Sonderformen

12.4Die Umgebung

12.5Funktionen

12.6Kompilierung

12.7Schummeln

12.8Übungen

Teil IIDer Browser

13JavaScript im Browser

13.1Netzwerke und das Internet

13.2Das Web

13.3HTML

13.4HTML und JavaScript

13.5Ausführung in einer Sandbox

13.6Die Browserkriege und das Problem der Kompatibilität

14DOM (Document Object Model)

14.1Die Dokumentstruktur

14.2Bäume

14.3Der Standard

14.4Den Baum durchlaufen

14.5Elemente finden

14.6Das Dokument ändern

14.7Knoten erstellen

14.8Attribute

14.9Layout

14.10Formatierung

14.11CSS

14.12Abfrageselektoren

14.13Positionierung und Animation

14.14Zusammenfassung

14.15Übungen

15Umgang mit Ereignissen

15.1Ereignis-Handler

15.2Ereignisse und DOM-Knoten

15.3Ereignisobjekte

15.4Weiterleitung

15.5Standardaktionen

15.6Tastaturereignisse

15.7Zeigeereignisse

15.8Scrollereignisse

15.9Fokusereignisse

15.10Ladeereignisse

15.11Ereignisse und die Ereignisschleife

15.12Timer

15.13Entprellen

15.14Zusammenfassung

15.15Übungen

16Projekt: Ein Jump’n’Run-Spiel

16.1Das Spiel

16.2Die Technologie

16.3Levels

16.4Ein Level lesen

16.5Akteure

16.6Kapselung als zusätzliche Belastung

16.7Zeichnen

16.8Bewegungen und Kollisionen

16.9Akteure aktualisieren

16.10Tastenbetätigungen verfolgen

16.11Das Spiel ausführen

16.12Übungen

17Zeichnen auf Leinwand

17.1SVG

17.2Das Canvas-Element

17.3Linien und Flächen

17.4Pfade

17.5Kurven

17.6Ein Tortendiagramm zeichnen

17.7Text

17.8Bilder

17.9Transformationen

17.10Transformationen speichern und löschen

17.11Zurück zu unserem Spiel

17.12Auswahl einer Grafikschnittstelle

17.13Zusammenfassung

17.14Übungen

18HTTP und Formulare

18.1Das Protokoll

18.2Browser und HTTP

18.3Fetch

18.4HTTP-Sandboxing

18.5Die Möglichkeiten von HTTP nutzen

18.6Sicherheit durch HTTPS

18.7Formularfelder

18.8Fokus

18.9Deaktivierte Felder

18.10Das Formular als Ganzes

18.11Textfelder

18.12Kontrollkästchen und Optionsschalter

18.13Auswahlfelder

18.14Dateifelder

18.15Clientseitige Datenspeicherung

18.16Zusammenfassung

18.17Übungen

19Projekt: Editor für Pixelgrafiken

19.1Komponenten

19.2Der Status

19.3Aufbau des DOM

19.4Die Leinwand

19.5Die Anwendung

19.6Zeichenwerkzeuge

19.7Speichern und Laden

19.8Der Undo-Verlauf

19.9Die Anwendung einrichten

19.10Warum ist das so schwer?

19.11Übungen

Teil IIINode.js

20Einführung in Node.js

20.1Hintergrund

20.2Der Befehl node

20.3Module

20.4Installation mit NPM

20.5Das Dateisystemmodul

20.6Das HTTP-Modul

20.7Streams

20.8Ein Dateiserver

20.9Zusammenfassung

20.10Übungen

21Projekt: Eine Website zur Wissensvermittlung

21.1Design

21.2Long Polling

21.3Die HTTP-Schnittstelle

21.4Der Server

21.5Der Client

21.6Übungen

22Leistung

22.1Stufenweise Kompilierung

22.2Graphzeichnen

22.3Definition eines Graphen

22.4Kräftebasiertes Graphzeichnen

22.5Arbeit vermeiden

22.6Profiling

22.7Inline-Ersetzung

22.8Weniger überflüssige Objekte erzeugen

22.9Garbage Collection

22.10Dynamische Typen

22.11Zusammenfassung

22.12Übungen

Hinweise zu den Übungen

Stichwortverzeichnis

Für Lotte und Jan

»Wir glauben, dass wir das System zu unserem eigenen Nutzen erstellen. Wir glauben, dass wir es nach unserem eigenen Bilde erschaffen. […] Aber der Computer ist nicht wie wir. Er ist eine Projektion eines sehr kleinen Teils von uns: des Teils, der Logik, Ordnung, Regeln und Klarheit zugetan ist.«

– Ellen Ullman, Close to the Machine: Technophilia and its Discontents (auf Deutsch erschienen als Close to the Machine: Mein Leben mit dem Computer)

image

Einleitung

In diesem Buch geht es darum, Computern Anweisungen zu geben. Computer sind heutzutage so alltäglich geworden wie Schraubenzieher, allerdings deutlich komplizierter. Deshalb ist es nicht immer einfach, sie auch wirklich das tun zu lassen, was man will.

Wenn Sie Ihren Computer für eine übliche und klar umrissene Aufgabe einsetzen möchten, z.B. um E-Mails anzuzeigen oder Berechnungen wie mit einem Taschenrechner durchzuführen, können Sie einfach die entsprechende Anwendung öffnen und loslegen. Für besondere Aufgaben gibt es dagegen möglicherweise noch keine Anwendung.

An dieser Stelle kommt die Programmierung ins Spiel. Dabei handelt es sich um den Vorgang, ein Programm zu erstellen, also eine Folge genauer Anweisungen, die dem Computer sagen, was er tun soll. Da Computer stumpfsinning und pedantisch sind, ist Programmierung im Grunde zunächst mühselig und frustrierend.

Wenn Sie jedoch darüber hinwegkommen und vielleicht sogar Freude an einer Denkweise in strengen Bahnen finden, die auch eine stumpfe Maschine versteht, kann Programmierung lohnenswert sein. Denn damit lassen sich in Sekunden Dinge erledigen, die sonst ewig dauern würden. Sie bietet eine Möglichkeit, Ihr Werkzeug, den Computer, Aufgaben ausführen zu lassen, die er zuvor nicht beherrschte. Und außerdem ist es eine hervorragende Übung für abstraktes Denken.

Programmierung erfolgt mithilfe einer Programmiersprache. Dabei handelt es sich um eine künstliche Sprache, die dazu dient, Computern Anweisungen zu erteilen. Es ist schon bemerkenswert, dass sich die effektivste Möglichkeit zur Kommunikation mit Computern, die wir erfunden haben, so stark an die Art und Weise anlehnt, wie wir miteinander kommunizieren. Ebenso wie in menschlichen Sprachen können auch in Computersprachen Wörter und Ausdrücke kombiniert werden, um Ideen auszudrücken.

Textschnittstellen wie die BASIC- und DOS-Eingabeaufforderungen der 80er und 90er bildeten einst die Hauptmethode für die Kommunikation mit Computern. Mittlerweile wurden sie größtenteils durch grafische Schnittstellen ersetzt, die leichter zu erlernen sind, aber weniger Freiheiten bieten. Die Computersprachen jedoch sind immer noch vorhanden. Sie müssen nur wissen, wo Sie danach zu suchen haben. Eine dieser Sprachen, nämlich JavaScript, ist in jeden modernen Webbrowser eingebaut und steht daher auf fast jedem Gerät zur Verfügung.

Dieses Buch soll Ihnen helfen, sich so weit mit dieser Sprache vertraut zu machen, dass Sie sie für nützliche und unterhaltsame Zwecke einsetzen können.

Programmierung

Neben JavaScript werde ich auch die Grundlagen der Programmierung erklären. Programmieren ist schwer. Die Grundregeln sind zwar einfach und deutlich, aber die nach diesen Regeln aufgebauten Programme geraten gewöhnlich so vielschichtig, dass sie ihre eigenen Regeln und eine eigene Komplexität aufweisen. In gewissem Sinne bauen Sie ein Labyrinth, in dem Sie sich selbst auch verirren können.

Bei der Lektüre dieses Buches werden Sie sich hin und wieder furchtbar frustriert fühlen. Wenn Programmierung ganz neu für Sie ist, gibt es viel Stoff zu verdauen. Eine Menge dieses Stoffs wird dann auf eine Weise miteinander kombiniert, die es erfordert, zusätzliche Verbindungen herzustellen.

Dazu müssen Sie sich anstrengen. Wenn Sie Schwierigkeiten haben, dem Text zu folgen, dann gehen Sie nicht vorschnell mit sich selbst ins Gericht. Mit Ihnen ist alles in Ordnung – Sie dürfen nur nicht aufgeben! Legen Sie eine Pause ein, lesen Sie etwas erneut und achten Sie darauf, die Beispielprogramme und Übungen nachzuvollziehen und zu begreifen. Lernen ist harte Arbeit. Aber alles, was Sie lernen, gehört zu Ihnen und macht das weitere Lernen einfacher.

»Wenn Aktionen unrentabel werden, sammeln Sie Informationen; wenn Informationen unrentabel werden, legen Sie sich schlafen.«

– Ursula K. LeGuin, The Left Hand of Darknes (auf Deutsch erschienen als Winterplanet und als Die linke Hand der Dunkelheit)

Ein Programm ist vieles zugleich: ein von einem Programmierer geschriebener Text; ein Datenpaket im Arbeitsspeicher des Computers, wo es Aktionen desselben steuert – und damit die lenkende Kraft, die dafür sorgt, dass der Computer das macht, was er tut. Alle Analogien, die Programme mit vertrauten Objekten des Alltags vergleichen, greifen zu kurz. Ein oberflächlicher Vergleich ist der mit einer Maschine: Viele einzelne Teile arbeiten zusammen, damit das Ganze läuft, und wir müssen Möglichkeiten finden, um diese Teile zu verzahnen, damit sie ihren Beitrag zum Funktionieren der Gesamtheit leisten können.

Ein Computer ist eine physische Maschine, die als Wirt solcher immateriellen Maschinen dient. Für sich allein genommen können Computer lediglich ganz einfache Dinge tun. Sie sind nur deshalb so nützlich, weil sie das mit unglaublich hoher Geschwindigkeit erledigen. Ein Programm kann nun auf raffinierte Art und Weise enorme Mengen solcher einfachen Aktionen kombinieren, um so äußerst komplizierte Aufgaben zu erfüllen.

Ein Programm ist ein Gedankenkonstrukt. Seine Erstellung kostet nichts, es ist gewichtslos und wächst rasch, während Sie die Tastatur bearbeiten. Wenn Sie nicht aufpassen, können Größe und Komplexität eines Programms rasch unkontrolliert zunehmen, sodass schließlich selbst die Person, die es geschrieben hat, den Überblick verliert und es nicht mehr richtig versteht. Das größte Herausforderung der Programmierung besteht darin, die Programme unter Kontrolle zu halten. Wenn ein Programm funktioniert, ist es schön. Die Kunst der Programmierung ist die Fähigkeit, die Komplexität im Zaum zu halten. Großartige Programme sind schlicht – also in all ihrer Komplexität möglichst einfach gestaltet.

Manche Programmierer sind der Meinung, dass sie dieser Komplexität am besten Herr werden, indem sie nur einige wenige, gut verstandene Techniken für ihre Programme verwenden. Dazu haben sie strenge Regeln (»empfohlene Vorgehensweisen«, auch »Best Practices«) aufgestellt, die vorschreiben, welche Form Programme haben sollen, und achten sorgfältig darauf, nicht aus dieser eng begrenzten Sicherheitszone auszubrechen.

Das ist jedoch nicht nur langweilig, sondern auch ineffektiv. Neue Probleme erfordern oft neue Lösungen. Programmierung ist eine junge Disziplin, die sich immer noch rasant weiterentwickelt, und sie ist vielgestaltig genug, um Platz für sehr unterschiedliche Vorgehensweisen zu bieten. Es gibt viele furchtbare Fehler, die einem bei der Programmgestaltung unterlaufen können. Sie sollten diese Fehler ruhig machen, damit Sie sie verstehen. Ein Gespür dafür, wie ein gutes Programm auszusehen hat, entwickeln Sie durch praktische Anwendung. Aus einem Satz von Regeln lernen Sie das nicht.

Warum es auf die Sprache ankommt

In den Anfangstagen der Computer gab es noch keine Programmiersprachen. Programme sahen damals wie folgt aus:

00110001 00000000 00000000

00110001 00000001 00000001

00110011 00000001 00000010

01010001 00001011 00000010

00100010 00000010 00001000

01000011 00000001 00000000

01000001 00000001 00000001

00010000 00000010 00000000

01100010 00000000 00000000

Dieses Programm (für einen sehr einfachen, hypothetischen Computer) addiert die Zahlen von 1 bis 10 und gibt das Resultat aus: 1 + 2 + ... + 10 = 55. Um die ersten Computer zu programmieren, musste man lange Reihen von Schaltern in die richtige Stellung bringen oder Löcher in Kartonkarten knipsen und in die Maschine einspeisen. Sie können sich vorstellen, wie mühselig und fehleranfällig das war. Selbst um einfache Programme zu schreiben, waren sehr viel Geschick und Disziplin erforderlich. Komplexe Programme waren damals geradezu unvorstellbar.

Allerdings verlieh die manuelle Eingabe dieser obskuren Muster aus Bits (Nullen und Einsen) den Programmierern das tiefe Gefühl, mächtige Zauberer zu sein. Das war schon viel wert, was die Zufriedenheit am Arbeitsplatz anging.

Jede Zeile des vorigen Programms bildet eine einzige Anweisung. Ins Deutsche übersetzt sehen diese Anweisungen wie folgt aus:

  1. Speichere die Zahl 0 am Speicherort 0.
  2. Speichere die Zahl 1 am Speicherort 1.
  3. Speichere den Wert von Speicherort 1 im Speicherort 2.
  4. Subtrahiere die Zahl 11 von dem Wert im Speicherort 2.
  5. Wenn der Wert in Speicherort 2 die Zahl 0 ist, fahre mit Anweisung 9 fort.
  6. Addiere den Wert von Speicherort 1 zu Speicherort 0.
  7. Addiere die Zahl 1 zum Wert von Speicherort 1.
  8. Fahre mit Anweisung 3 fort.
  9. Gib den Wert von Speicherort 0 aus.

Das ist zwar schon besser lesbar als die Bit-Suppe, aber immer noch ziemlich unverständlich. Es wird besser, wenn wir für die Anweisungen und Speicherorte Namen statt Zahlen verwenden:

Set "total" to 0.

Set "count" to 1.

[loop]

Set "compare" to "count".

Subtract 11 from "compare".

If "compare" is zero, continue at [end].

Add "count" to "total".

Add 1 to "count".

Continue at [loop].

[end]

Output "total".

Können Sie erkennen, wie das Programm abläuft? Die ersten beiden Zeilen geben zwei Speicherorten ihre Startwerte: total wird verwendet, um nach und nach das Ergebnis der Berechnung aufzubauen, während sich count jeweils merkt, welche Zahl wir uns gerade ansehen. Die Zeilen, in denen compare verwendet wird, sind wahrscheinlich die absonderlichsten. Das Programm prüft hier, ob count gleich 11 ist, um zu entscheiden, ob es die Ausführung beenden kann. Da unser hypothetischer Computer ziemlich primitiv ist, kann er nur prüfen, ob eine Zahl gleich 0 ist, und seine Entscheidung darauf gründen. Daher verwendet er den Speicherort compare, um den Wert von count - 11 zu berechnen, und stellt den Vergleich damit an. Die nächsten beiden Zeilen addieren den Wert von count zu dem Ergebnis und setzen count um 1 herauf, solange dieser Wert noch ungleich 11 ist.

In JavaScript sieht das gleiche Programm wie folgt aus:

let total = 0, count = 1;

while (count <= 10) {

total += count;

count += 1;

}

console.log(total);

// → 55

Diese Version bietet einige weitere Verbesserungen. Vor allem haben wir es hier nicht mehr nötig, das Programm ausdrücklich anzuweisen, vor- oder zurückzuspringen. Darum kümmert sich das while-Konstrukt. Es setzt die Ausführung des dahinter stehenden Blocks (in geschweiften Klammern) so lange fort, wie die ihm übergebene Bedingung zutrifft. Diese Bedingung lautet count <= 10, was bedeutet: »count ist kleiner oder gleich 10.« Wir müssen hier nicht mehr einen Zwischenwert berechnen und mit 0 vergleichen, was ohnehin ein für das eigentliche Ziel unbedeutender Detailmechanismus war. Programmiersprachen sind unter anderem deshalb so leistungsfähig, weil sie uns solche uninteressanten Einzelheiten abnehmen. Am Ende des Programms, nach dem Abschluss des while-Konstruktors, sorgt die Operation console.log dafür, dass das Resultat ausgegeben wird.

Wenn uns die bequemen Operationen range und sum zur Verfügung stehen, die eine Zahlenmenge aus einem gegebenen Bereich zusammenstellen bzw. die Summe einer Menge von Zahlen berechnen können, sind wir in der Lage, das Programm wie folgt zu schreiben:

console.log(sum(range(1, 10)));

// → 55

Die Moral von der Geschichte ist, dass sich Programme auf verschiedene Weisen ausdrücken lassen – lang und kurz, unverständlich und verständlich. Die erste Version des Programms war extrem obskur, während die letzte schon fast wie Deutsch aussieht: »Protokolliere die Summe des Bereichs der Zahlen von 1 bis 10.« Wie sich Operationen wie sum und range definieren lassen, werden wir uns in den späteren Kapiteln noch ansehen.

Eine gute Programmiersprache unterstützt Programmierer, indem sie ihnen erlaubt, die groben Aktionen, die der Computer durchführen soll, zu beschreiben, und die Details wegzulassen. Sie stellt komfortable Bausteine bereit (wie while und console.log), ermöglicht die Definition eigener Bausteine (wie sum und range) und erleichtert es, diese Bausteine zu kombinieren.

Was ist JavaScript?

JavaScript wurde 1995 als eine Möglichkeit eingeführt, um Webseiten im Netscape Navigator Programme hinzuzufügen. Die Sprache wurde seitdem von allen anderen wichtigen grafischen Browsern übernommen. Sie hat moderne Webanwendungen möglich gemacht, mit denen Sie direkt arbeiten können, ohne nach jeder Aktion auf einen neuen Seitenaufbau warten zu müssen. JavaScript wird auch auf herkömmlichen Webseiten eingesetzt, um verschiedenen Formen der Interaktivität und ausgeklügelte Funktionen zu bieten.

Beachten Sie jedoch, dass JavaScript so gut wie nichts mit der Programmiersprache Java zu tun hat. Der Name wurde eher aus Marketinggründen gewählt. Bei der Einführung von JavaScript wurde Java stark angepriesen und nahm an Beliebtheit zu. Jemand hielt es für eine gute Idee, auf der Erfolgswelle mitzuschwimmen. Bei dem Namen ist es dann geblieben.

Nach der Übernahme in andere Browser als Netscape wurde ein Normdokument verfasst, um zu beschreiben, wie sich JavaScript verhalten soll, damit auch tatsächlich immer die gleiche Sprache gemeint ist, wenn Software behauptet, JavaScript zu unterstützen. Dieser Standard wird nach der Organisation Ecma International, die die Normung vorgenommen hat, als ECMAScript bezeichnet. In der Praxis können die Begriffe ECMAScript und JavaScript synonym verwendet werden – es sind zwei Bezeichnungen für dieselbe Sprache.

Manche Personen werden Ihnen fürchterliche Dinge über JavaScript erzählen. Viele davon sind wahr. Als ich zum ersten Mal etwas in JavaScript schreiben musste, lernte ich die Sprache sehr schnell zu hassen. Sie akzeptierte fast alles, was ich eingab, deutete es dann aber völlig anders, als ich es gemeint hatte. Dies lag zu einem großen Teil natürlich auch daran, dass ich damals keine Ahnung hatte, was ich da eigentlich tat. Aber es ist auch das Symptom eines echten Problems: JavaScript ist absurd freizügig. Dahinter stand die Idee, dass es das Programmieren in JavaScript für Anfänger erleichtern würde. In Wirklichkeit aber erschwert dies, Probleme in Programmen zu finden, da das System Sie nicht darauf hinweist.

Diese Flexibilität hat jedoch auch ihre Vorteile. Sie erlaubt viele Techniken, die in strengeren Sprachen unmöglich sind. Wie Sie noch sehen werden (etwa in Kapitel 10), kann sie auch dazu genutzt werden, einige der Mängel von JavaScript auszubügeln. Nachdem ich die Sprache gründlich gelernt und eine Weile damit gearbeitet hatte, begann ich JavaScript sogar zu mögen.

Es gibt verschiedene Versionen von JavaScript. ECMAScript Version 3 war während des Aufstiegs von JavaScript etwa zwischen 2000 und 2010 sehr weit verbreitet. In dieser Zeit wurde an der anspruchsvolleren Version 4 gearbeitet, bei der es einige radikale Verbesserungen und Erweiterungen geben sollte. Es erwies sich jedoch als politisch schwierig, eine lebendige, weiträumig genutzte Sprache so einschneidend zu verändern, weshalb die Arbeiten an Version 4 im Jahre 2008 eingestellt wurden. Stattdessen wurde 2009 die weniger ambitionierte Version 5 veröffentlicht, die nur einige nichtkontroverse Verbesserungen bot. 2015 kam die stark überarbeitete Version 6 heraus, die einige der für Version 4 geplanten Aspekte enthielt. Seitdem gab es jedes Jahr kleinere Aktualisierungen.

Da sich die Sprache weiterentwickelt, müssen die Browser mithalten. Wenn Sie einen älteren Browser verwenden, kann es sein, dass er nicht alle Funktionen unterstützt. Die Entwickler der Sprache achten sorgfältig darauf, keine Änderungen einzuführen, die bestehende Programme funktionsunfähig machen würden. Daher können neue Browser immer noch ältere Programme ausführen. In diesem Buch verwendete ich die JavaScript-Version von 2017.

Browser sind jedoch nicht die einzigen Plattformen, auf denen JavaScript eingesetzt wird. Auch einige Datenbanken wie MongoDB oder CouchDB nutzen JavaScript als Skripterstellungs- und Abfragesprache. Verschiedene Plattformen für die Desktop- und Serverprogrammierung, insbesondere das Projekt Node.js (um das es in Kapitel 20 geht), bieten eine Umgebung für die JavaScript-Programmierung außerhalb des Browsers.

Die Codebeispiele

Code ist der Text eines Programms. Die meisten Kapitel dieses Buches enthalten eine Menge Code. Ich bin der Meinung, dass das Lesen und Schreiben von Code unverzichtbar ist, um Programmieren zu lernen. Überfliegen Sie die Beispiele nicht einfach, sondern lesen Sie sie aufmerksam, um sie auch wirklich zu verstehen. Das kann zu Anfang langsam und verwirrend sein. Aber ich verspreche Ihnen, dass Sie den Bogen schon bald heraushaben werden. Das Gleiche gilt auch für die Übungen. Sie haben diese erst dann richtig verstanden, wenn Sie eine Lösung dafür geschrieben haben.

Ich empfehle Ihnen, Ihre Lösungen zu den Übungsaufgaben tatsächlich in einem JavaScript-Interpreter auszuprobieren. Dadurch erhalten Sie unmittelbar eine Rückmeldung, ob sie funktionieren, und werden hoffentlich auch dazu animiert, über die Aufgabenstellung hinaus noch weiter zu experimentieren.

Die einfachste Möglichkeit, den Beispielcode in diesem Buch auszuführen und damit zu experimentieren, besteht darin, ihn in der Online-Version des Buchs auf https://eloquentjavascript.net anzusehen. Dort können Sie auf die Codebeispiele klicken, um sie zu bearbeiten, auszuführen und sich die Ausgaben anzusehen. Um die Übungen zu bearbeiten, besuchen Sie https://eloquentjavascript.net/code. Dort erhalten Sie den Ausgangscode für alle Programmierübungen und können auch einen Blick auf die Lösungen werfen.

Wollen Sie die Programme aus diesem Buch außerhalb der Begleitwebseite ausführen, ist etwas Vorsicht geboten. Viele der Beispiele sind eigenständig und sollten in jeder JavaScript-Umgebung funktionieren. Code in den hinteren Kapiteln ist jedoch oft für eine bestimmte Umgebung geschrieben (den Browser oder Node.js) und kann nur dort ausgeführt werden. Außerdem werden in einigen Kapiteln umfangreichere Programme erstellt, deren einzelne Teile voneinander und manchmal auch von externen Dateien abhängen. Die Sandbox auf der Webseite stellt Links zu Zip-Dateien mit allen Skript- und Datendateien bereit, die erforderlich sind, um den Code zu einem Kapitel auszuführen.

Übersicht über dieses Buch

Dieses Buch besteht aus drei Teilen. Die ersten zwölf Kapitel beschreiben die Sprache JavaScript, in den nächsten sieben geht es um Webbrowser und ihre Programmierung mit JavaScript, und die beiden letzten sind Node.js gewidmet, einer weiteren Programmierumgebung für JavaScript.

Über das ganze Buch verstreut sind fünf Projektkapitel, in denen umfangreiche Beispielprogramme beschrieben werden, um Ihnen einen Vorgeschmack auf die tatsächliche Programmierarbeit zu geben. In diesen Kapiteln programmieren wir einen Zustellroboter, eine Programmiersprache, ein Jump’n’Run-Spiel, ein Malprogramm und eine dynamische Webseite.

Der erste Teil des Buches beginnt mit vier Kapiteln, die die Grundstruktur von JavaScript vorstellen. Sie geben eine Einführung in Steuerstrukturen (z. B. das while-Konstrukt, das Sie bereits in dieser Einleitung kennengelernt haben), Funktionen (von Ihnen selbst geschriebene Bausteine) und Datenstrukturen. Damit sind Sie schon in der Lage, einfache Programme zu schreiben. Anschließend werden in den Kapiteln 5 und 6 Techniken eingeführt, um mithilfe von Funktionen und Objekten abstrakteren Code zu schreiben und die Komplexität zu reduzieren.

Nach einem ersten Projektkapitel geht es im ersten Teil des Buches mit Kapiteln über Fehlerbehandlung und -behebung, reguläre Ausdrücke (ein wichtiges Instrument für die Arbeit mit Text), Modularität (eine weitere Maßnahme gegen zu hohe Komplexität) und asynchrone Programmierung (Umgang mit Ereignissen, die einige Zeit dauern) weiter. Das zweite Projektkapitel schließt den ersten Teil ab.

Der zweite Teil – die Kapitel 13 bis 19 – beschreibt die Werkzeuge, auf die JavaScript im Browser Zugriff hat. Sie erfahren hier, wie Sie etwas auf dem Bildschirm darstellen (Kapitel 14 bis 17), wie Sie auf Benutzereingaben reagieren (Kapitel 15) und wie Sie über das Netzwerk kommunizieren (Kapitel 18). Auch in diesem Teil gibt es wieder zwei Projektkapitel.

Im Anschluss daran wird in Kapitel 20 Node.js beschrieben und in Kapitel 21 eine kleine Webseite damit erstellt.

In Kapitel 22 finden Sie schließlich einige Überlegungen dazu, wie Sie JavaScript-Programme optimieren können, um die Ausführung zu beschleunigen.

Schreibweisen

Text in einer nichtproportionalen Schrift kennzeichnet in diesem Buch Programmelemente – sowohl eigenständige Fragmente als auch »Zitate« aus einem in der Nähe abgedruckten Programm. Die Programme selbst (von denen Sie inzwischen ja schon einige gesehen haben) werden wie folgt dargestellt:

function factorial(n) {

if (n == 0) {

return 1;

} else {

return factorial(n - 1) * n;

}

}

Um die erwartete Ausgabe eines Programms zu zeigen, wird sie manchmal hinter zwei Schrägstrichen und einem Pfeil angegeben:

console.log(factorial(8));

// → 40320

Viel Glück!

Teil I

Die Sprache

»Unter der Oberfläche der Maschine bewegt sich das Programm. Mühelos dehnt es sich aus und zieht sich zusammen. In großer Harmonie zerstreuen sich Elektronen und kommen wieder zusammen. Die Formen auf dem Monitor sind nur Wellen auf dem Wasser. Das Wesentliche bleibt unsichtbar darunter.«

– Meister Yuan-Ma, The Book of Programming

image

1Werte, Typen und Operatoren

In der Welt der Computer gibt es nichts anderes als Daten. Daten können gelesen, geändert und neu erstellt werden, aber es ist nicht möglich, Dinge, die keine Daten sind, auch nur zu erwähnen. Alle diese Daten werden als lange Folgen von Bits gespeichert und sind einander dadurch grundsätzlich ähnlich.

Bits sind alle Dinge, die zwei Werte annehmen können, wobei diese Werte gewöhnlich als 0 und 1 dargestellt werden. Innerhalb des Computers nehmen sie die Form einer hohen oder niedrigen elektrischen Ladung, eines starken oder schwachen Signals bzw. eines hellen oder dunklen Punkts auf einer CD an. Jede diskrete Information kann auf eine Folge von Nullen und Einsen reduziert und damit durch Bits dargestellt werden.

Beispielsweise können wir eine Zahl wie 13 durch Bits ausdrücken. Das funktioniert genauso wie bei Dezimalzahlen, allerdings verwenden wir dazu nur zwei statt zehn verschiedene Ziffern und der Betrag wächst von rechts nach links mit jeder Stelle um den Faktor 2. Die folgende Darstellung zeigt die Bits, die die Zahl 13 bilden, mit dem Betrag der jeweiligen Stellen darunter:

0  0  0  0 1 1 0 1

128 64 32 16 8 4 2 1

Die Binärzahl lautet also 00001101. Die von 0 verschiedenen Stellen stehen darin für die Beträge 8, 4, und 1, deren Addition 13 ergibt.

1.1Werte

Stellen Sie sich ein Meer von Bits vor. Der flüchtige Datenspeicher (Arbeitsspeicher) eines typischen modernen Computers enthält über 30 Milliarden Bits. Im nichtflüchtigen Speicher (also auf der Festplatte oder einer vergleichbaren Einrichtung) sind es gewöhnlich einige Größenordnungen mehr.

Um solche Mengen von Bits handhaben zu können, ohne die Orientierung zu verlieren, müssen wir sie daher in Blöcke aufteilen, die für einzelne Informationen stehen. In einer JavaScript-Umgebung nennen wir diese Blöcke Werte. Obwohl alle Werte aus Bits bestehen, können sie verschiedene Rollen einnehmen, wobei die Rolle eines Werts durch seinen Typ bestimmt wird. Einige Werte sind Zahlen, andere sind Texte, wieder andere Funktionen usw.

Um einen Wert zu erstellen, müssen Sie lediglich seinen Namen angeben. Das ist praktisch, denn dies bedeutet, dass es nicht nötig ist, Material für Werte heranzuschaffen oder gar dafür zu bezahlen. Sie fordern einfach den Wert an und – Simsalabim – da ist er. Natürlich werden die Werte in Wirklichkeit nicht so einfach aus dem Hut gezaubert. Jeder Wert muss irgendwo gespeichert werden, und wenn Sie gleichzeitig eine riesige Menge von Werten verwenden, geht Ihnen irgendwann der Speicher aus. Glücklicherweise ist das jedoch nur dann ein Problem, wenn Sie die Werte wirklich alle gleichzeitig benötigen. Wenn Sie einen Wert nicht brauchen, löst er sich auf und lässt seine Bits zurück, die dann als Baumaterial für die nächste Generation von Werten wiederverwendet werden können.

In diesem Kapitel lernen Sie die kleinsten Elemente von JavaScript-Programmen kennen, nämlich die einfachen Wertetypen und die Operatoren, die diese Werte bearbeiten können.

1.2Zahlen

Werte, die Zahlen darstellen, sind sogenannte numerische Werte. In einem JavaScript-Programm werden sie gewöhnlich wie folgt geschrieben:

13

Wenn Sie das in ein Programm eingeben, wird innerhalb des Computerarbeitsspeichers das Bitmuster für die Zahl 13 gebildet.

Um einen numerischen Wert zu speichern, verwendet JavaScript stets eine feste Anzahl von Bits, nämlich 64. Daraus lässt sich nur eine begrenzte Anzahl von Mustern bilden, was die Menge der darstellbaren Zahlen beschränkt. Mit n Dezimalstellen lassen sich 10n Zahlen ausdrücken, mit 64 Binärstellen 264 Zahlen, also 18 Trillionen (eine 18 mit 18 Nullen). Das ist eine ganze Menge.

Computerarbeitsspeicher waren früher viel kleiner, weshalb zur Darstellung von Zahlen gewöhnlich 8 oder 16 Bits verwendet wurden. Dabei war es leicht möglich, versehentlich einen Überlauf zu verursachen, also eine Zahl zu bekommen, die nicht in die gegebene Anzahl von Bits passt. Heute haben sogar Computer für die Westentasche eine Menge Arbeitsspeicher, sodass es möglich ist, 64-Bit-Blöcke zu verwenden. Um Überläufe muss man sich nur dann Gedanken machen, wenn mit wirklich astronomischen Zahlen gearbeitet wird.

Allerdings passen nicht alle Zahlen kleiner als 18 Trillionen in den numerischen Typ von JavaScript. Da es auch möglich sein muss, negative Zahlen zu speichern, wird ein Bit zur Angabe des Vorzeichens verwendet. Ein noch größeres Problem ist die Darstellung nicht ganzzahliger Werte. Dann müssen einige der Bits dafür genutzt werden, die Position des Kommas festzuhalten. Die größte ganze Zahl, die tatsächlich gespeichert werden kann, liegt daher im Bereich von 9 Billiarden (15 Nullen), was immer noch angenehm groß ist.

Bruchzahlen werden mit einem Punkt (statt Komma) geschrieben:

9.81

Für sehr große und sehr kleine Zahlen kann auch die wissenschaftliche Schreibweise verwendet werden. Dazu schreiben Sie e (für Exponent), gefolgt vom Exponenten:

2.998e8

Das entspricht 2,998 × 108 = 299.800.000.

Berechnungen mit ganzen Zahlen (sogenannten Integer-Zahlen) kleiner als die zuvor genannten 9 Billiarden sind garantiert präzise, Berechnungen mit Bruchzahlen im Allgemeinen jedoch nicht. Ebenso wie es nicht möglich ist, π durch eine endliche Anzahl von Dezimalstellen genau darzustellen, verlieren viele Zahlen etwas von ihrer Präzision, wenn nur 64 Bits zu ihrer Speicherung zur Verfügung sehen. Das ist schade, führt in der Praxis aber nur in besonderen Situationen zu Problemen. Wichtig ist, dass Sie sich dieses Umstands bewusst sind und Bruchzahlen nur als Näherungen und nicht als exakte Werte betrachten.

Berechnungen

Das Wichtigste, was man mit Zahlen anstellt, sind Berechnungen. Arithmetische Operationen wie Addition oder Multiplikation nehmen zwei numerische Werte entgegen und produzieren daraus eine neue Zahl. In JavaScript sieht das wie folgt aus:

100 + 4 * 11

Die Symbole + und * werden Operatoren genannt. Der erste steht für die Addition, der zweite für die Multiplikation. Ein Operator zwischen zwei Werten wird auf diese beiden angewendet und produziert einen neuen Wert.

Aber bedeutet dieses Beispiel »addiere 100 und 4 und multipliziere das Ergebnis mit 11« oder erfolgt die Multiplikation vor der Addition? Wie Sie wahrscheinlich wissen, wird die Multiplikation zuerst ausgeführt. Allerdings können Sie wie in der Mathematik die Reihenfolge ändern, indem Sie die Addition in Klammern setzen:

(100 + 4) * 11

Für die Subtraktion gibt es den Operator - und für die Division den Operator /.

Wenn keine Klammern gesetzt sind, werden Operatoren in der Reihenfolge angewendet, die durch ihren Rang bestimmt ist. In unserem Beispiel erfolgt die Multiplikation vor der Addition. Der Operator / hat den gleichen Rang wie *. Auch + und - haben den gleichen Rang. Folgen Operatoren des gleichen Rangs aufeinander, z. B. in 1 - 2 + 1, werden sie von links nach rechts ausgewertet: (1 - 2) + 1.

Machen Sie sich aber keine Sorgen über die Rangfolge von Operatoren. Im Zweifelsfall fügen Sie einfach Klammern hinzu.

Es gibt noch einen weiteren arithmetischen Operator, den Sie aber möglicherweise nicht sofort erkennen. Das Symbol % wird oft für die Restoperation verwendet. X % Y ist der Rest der Division von X durch Y. Beispielsweise ergibt 314 % 100 den Wert 14 und 144 % 12 den Wert 0. Der Restoperator hat denselben Rang wie Multiplikation und Division. Meistens wird er als Modulo bezeichnet.

Besondere Zahlen

In JavaScript gibt es drei besondere Werte, die zwar als Zahlen angesehen werden, sich aber nicht so verhalten.

Die ersten beiden sind Infinity und -Infinity, die für positive bzw. negative Unendlichkeit stehen. Infinity - 1 ist immer noch Infinity usw. Verlassen Sie sich aber nicht zu sehr auf Berechnungen mit unendlichen Werten. Sie sind mathematisch zweifelhaft und führen schnell zu der dritten besonderen Zahl, nämlich NaN.

NaN steht für »not a number«, also »keine Zahl«, obwohl dieser Wert tatsächlich zu den numerischen Werten gehört. Dieses Ergebnis erhalten Sie beispielsweise, wenn Sie versuchen, 0 / 0 zu berechnen (null dividiert durch null), Infinity - Infinity oder irgendeine andere numerische Operation durchführen, die kein sinnvolles Ergebnis liefert.

1.3Strings

Den nächsten grundlegenden Datentyp bilden die Strings. Sie dienen zur Darstellung von Text. Um sie zu schreiben, wird der Inhalt in Anführungszeichen gesetzt:

`Down on the sea`

"Lie on the ocean"

'Float on the ocean'

Um Strings zu kennzeichnen, können Sie Backticks, doppelte oder einfache Anführungszeichen verwenden. Wichtig ist, dass Sie am Anfang und am Ende des Strings jeweils das gleiche Zeichen setzen.

Zwischen Anführungszeichen können Sie fast alles schreiben. JavaScript macht automatisch einen String-Wert daraus. Allerdings gibt es bei einigen Zeichen Probleme. Sie können sich vorstellen, dass es knifflig wird, wenn Sie versuchen, Anführungszeichen zwischen Anführungszeichen zu setzen. Zeilenumbrüche (also die Zeichen, die Sie erhalten, wenn Sie die Eingabetaste drücken) lassen sich nur dann ohne Maskierung eingeben, wenn der String in Backticks (`) eingeschlossen ist.

Um auch solche problematischen Zeichen in Strings einschließen zu können, wird die folgende Schreibweise verwendet: Tritt innerhalb von Text in Anführungszeichen ein Backslash (umgekehrter Schrägstrich, \) auf, dann bedeutet dies, dass das darauffolgende Zeichen eine besondere Bedeutung hat. Auf diese Weise wird das Zeichen maskiert. Ein Anführungszeichen mit einem vorausgehenden Backslash beendet den String nicht, sondern ist ein Teil von ihm. Steht das Zeichen n hinter einem Backslash, wird es als Zeilenumbruch gedeutet, und ein t hinter einem Backslash steht für den Tabulator. Betrachten Sie als Beispiel den folgenden String:

"This is the first line\nAnd this is the second"

Der enthaltene Text lautet:

This is the first line

And this is the second

Es kann natürlich auch vorkommen, dass ein Backslash in einem String als Backslash wiedergegeben werden soll, anstatt eine besondere Funktion auszuüben. Wenn zwei Backslashes aufeinander folgen, maskiert der erste den zweiten, sodass im resultierenden String-Wert nur der zweite übrigbleibt. Um beispielsweise den String "A newline character is written like "\n"." auszudrücken, können Sie Folgendes schreiben:

"A newline character is written like \"\\n\"."

Um innerhalb eines Computers existieren zu können, müssen auch Strings als Folge von Bits modelliert werden. JavaScript zieht dazu den Unicode-Standard heran. Er weist Zahlen praktisch jedem Zeichen zu, das Sie jemals brauchen könnten, darunter auch griechischen, arabischen, japanischen und armenischen Buchstaben usw. Wenn es für jedes Zeichen eine Zahl gibt, kann ein String als Folge von Zahlen ausgedrückt werden.

16S. 64