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.
|
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 |
Richtig gut programmieren lernen –
Von der ersten Codezeile bis zum eigenen Projekt
Marijn Haverbeke
Lektorat: Melanie Feldmann
Übersetzung & Satz: G&U Language & Publishing Services GmbH, Flensburg, www.GundU.com
Copy-Editing: Alexander Reischert, www.aluan.de
Herstellung: Stefanie Weidner
Umschlaggestaltung: Helmut Kraus, www.exclam.de
Bibliografische Information der Deutschen Nationalbibliothek
Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.
ISBN: |
|
978-3-86490-728-9 |
|
978-3-96088-911-3 |
|
ePub |
978-3-96088-912-0 |
mobi |
978-3-96088-913-7 |
1. Auflage 2020
Translation Copyright für die deutschsprachige Ausgabe © 2020 dpunkt.verlag GmbH
Wieblinger Weg 17
69123 Heidelberg
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:
Dieses Buch wurde auf PEFC-zertifiziertem Papier aus nachhaltiger Waldwirtschaft gedruckt. Der Umwelt zuliebe verzichten wir zusätzlich auf die Einschweißfolie.
Schreiben Sie uns:
Falls Sie Anregungen, Wünsche und Kommentare haben, lassen Sie es uns wissen: hallo@dpunkt.de
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.
Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen.
Alle Angaben und Programme in diesem Buch wurden mit größter Sorgfalt kontrolliert. Weder Autor noch Verlag noch Übersetzer können jedoch für Schäden haftbar gemacht werden, die in Zusammenhang mit der Verwendung dieses Buches stehen.
5 4 3 2 1 0
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
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)
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.
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.
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:
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.
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.
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.
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.
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!
»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
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.
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.
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.
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.
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.
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