Teil I · Kapitel 01Was JavaScript ist
JavaScript ist eine dynamisch typisierte, multiparadigmatische Programmiersprache mit automatischer Speicherverwaltung. Sie wird zur Laufzeit kompiliert, kennt Funktionen als vollwertige Werte, organisiert Vererbung über Prototypen statt über Klassen und führt Code in einem einzelnen Thread aus, der durch eine Ereignisschleife gesteuert wird.
1.1Herkunft und Name
Brendan Eich entwarf die Sprache 1995 bei Netscape in rund zehn Tagen. Sie hieß zunächst Mocha, dann LiveScript, und wurde schließlich aus Marketinggründen in JavaScript umbenannt – Java war damals populär, und Netscape hatte ein Kooperationsabkommen mit Sun Microsystems. Technisch haben die beiden Sprachen nichts miteinander zu tun.
Die prägenden Einflüsse sind andere: die Funktionen als Werte und die Closures stammen von Scheme, die prototypische Objektorientierung von Self, und die oberflächliche Syntax mit geschweiften Klammern von Java und C. Diese Mischung erklärt vieles: JavaScript sieht aus wie eine klassenbasierte C-artige Sprache, verhält sich aber in wesentlichen Punkten wie eine funktionale Sprache mit dynamischen Objekten.
1.2ECMAScript, TC39 und der Versionszyklus
„JavaScript“ ist der umgangssprachliche Name; die normierte Sprache heißt ECMAScript und ist im Standard ECMA-262 festgeschrieben. Gepflegt wird der Standard vom Komitee TC39, in dem Browserhersteller, Laufzeitumgebungen und weitere Beteiligte vertreten sind.
Jeder Sprachvorschlag durchläuft einen fünfstufigen Prozess:
| Stufe | Bezeichnung | Bedeutung in der Praxis |
|---|---|---|
| 0 | Strawperson | Idee, formlos eingebracht |
| 1 | Proposal | Ein Champion arbeitet daran; Problem und Lösungsskizze stehen |
| 2 | Draft | Formale Spezifikationssprache, Syntax weitgehend festgelegt |
| 3 | Candidate | Spezifikation vollständig; Browser beginnen zu implementieren. Ab hier praxistauglich, aber noch änderbar |
| 4 | Finished | Zwei Implementierungen, Tests bestanden – kommt in die nächste Jahresausgabe |
Seit 2015 erscheint jedes Jahr im Juni eine neue Ausgabe, benannt nach dem Jahr. Eine Auswahl der wichtigsten Einschnitte:
- ES5 (2009)
strict mode,JSON,Object.defineProperty,Array.prototype.map/filter/reduce– die Basis, auf der das moderne JavaScript aufsetzt - ES2015 (ES6)Der große Bruch:
let/const, Arrow Functions, Klassen, Module, Promises,Map/Set, Destructuring, Template Literals, Generatoren,Symbol,Proxy - ES2017
async/await– asynchroner Code wird linear lesbar - ES2020
?.,??,BigInt, dynamischesimport(),Promise.allSettled - ES2022Private Klassenfelder
#x,at(), Top-Levelawait,Error-Ursachen (cause) - ES2023Nicht-mutierende Array-Methoden:
toSorted,toReversed,with,findLast - ES2024
Object.groupBy,Promise.withResolvers,Array.fromAsync, RegExp-v-Flag - ES2025Iterator-Helfer (
.map/.filter/.takedirekt auf Iteratoren), Mengenoperationen aufSet,Promise.try, Import-Attribute,RegExp.escape
1.3Was zur Sprache gehört – und was nicht
Ein häufiges Missverständnis: Vieles, was man täglich in JavaScript benutzt, steht gar nicht in
ECMA-262. Die Sprachnorm beschreibt nur den Kern – Syntax, Typen, Objekte wie
Array, Math, JSON, Promise. Alles andere kommt
von der Laufzeitumgebung.
| Gehört zur Sprache (ECMA-262) | Kommt von der Umgebung |
|---|---|
Object, Array, Function, Promise, Map, Symbol, Math, JSON, RegExp, Error |
document, fetch, setTimeout, console, localStorage, structuredClone, AbortController (Browser/WHATWG); fs, process, Buffer (Node.js) |
Selbst setTimeout und console.log – die beiden Dinge, die jeder
Anfänger zuerst tippt – sind keine JavaScript-Funktionen, sondern Angebote der Umgebung. Das
erklärt, warum derselbe Code im Browser läuft und in Node.js abstürzt.
1.4Laufzeitumgebungen
- Browser
- V8 (Chrome, Edge), SpiderMonkey (Firefox), JavaScriptCore (Safari). Zusätzlich: DOM, CSSOM, Netzwerk-APIs, Web Workers, Canvas, WebGL.
- Node.js
- Serverseitige Laufzeit auf V8-Basis, seit 2009. Bringt Dateisystem, Netzwerk-Sockets, Prozesssteuerung und das npm-Ökosystem mit.
- Deno
- Ebenfalls V8, vom selben Autor wie Node.js. Rechte müssen explizit freigegeben werden, TypeScript läuft ohne Zwischenschritt, die Standardbibliothek orientiert sich an Web-APIs.
- Bun
- Basiert auf JavaScriptCore statt V8; optimiert auf Startzeit und Durchsatz, mit eingebautem Bundler, Test-Runner und Paketmanager.
- Eingebettet
- QuickJS, Hermes (React Native), engine-agnostische Varianten in Datenbanken, CDN-Edge-Workern und Embedded-Systemen.
Teil I · Kapitel 02Wie Code ausgeführt wird
Wer versteht, was zwischen dem Einlesen einer Datei und der Ausgabe auf dem Bildschirm passiert, kann die meisten überraschenden Verhaltensweisen von JavaScript selbst herleiten – insbesondere die Reihenfolge asynchroner Operationen.
2.1Interpretiert oder kompiliert?
Die Frage ist falsch gestellt. Moderne Engines machen beides. V8 etwa arbeitet in Stufen:
- Parsen. Der Quelltext wird in einen abstrakten Syntaxbaum (AST) überführt. Funktionen, die noch nicht gebraucht werden, werden nur oberflächlich geprüft („lazy parsing“).
- Bytecode. Der Interpreter Ignition erzeugt Bytecode und führt ihn direkt aus. Das startet schnell, läuft aber vergleichsweise langsam.
- Optimierung. Code, der oft läuft („hot“), wird vom optimierenden Compiler (Maglev, TurboFan) in Maschinencode übersetzt. Dabei trifft die Engine Annahmen: „diese Variable war bisher immer eine Zahl“.
- Deoptimierung. Trifft eine Annahme nicht mehr zu – die Variable enthält plötzlich einen String – wird der optimierte Code verworfen und auf Bytecode zurückgefallen.
Daraus folgt eine praktische Regel: konsistente Typen sind schneller als wechselnde Typen. Ein Array, das nur Zahlen enthält, ist für die Engine ein anderes Ding als eines, in das nachträglich ein String geschrieben wurde.
2.2Ein Thread, ein Call Stack
JavaScript-Code läuft pro Kontext in genau einem Thread. Es gibt einen Aufrufstapel
(Call Stack): jeder Funktionsaufruf legt einen Rahmen darauf, jedes return
nimmt ihn wieder herunter. Solange auf dem Stapel etwas liegt, kann nichts anderes laufen –
im Browser bedeutet das: keine Animation, keine Reaktion auf Klicks, kein Neuzeichnen.
// Diese Schleife friert die gesamte Seite für 3 Sekunden ein.
const ende = Date.now() + 3000;
while (Date.now() < ende) { /* nichts tun, aber beschäftigt */ }
// Ein Stack Overflow entsteht, wenn der Stapel nicht mehr abgebaut wird:
function endlos() { return endlos(); }
// endlos(); → RangeError: Maximum call stack size exceeded
2.3Die Event Loop
Wenn nur ein Thread existiert, wie kann dann etwas „gleichzeitig“ passieren? Die Antwort: Es
passiert nicht in JavaScript. Ein setTimeout, eine Netzwerkanfrage oder ein
Dateizugriff wird an die Umgebung abgegeben – und die arbeitet sehr wohl parallel,
in C++ oder Rust, mit Threadpool. Ist sie fertig, legt sie eine Rückruffunktion in eine
Warteschlange.
Die Event Loop ist die Schleife, die diese Warteschlangen abarbeitet. Entscheidend ist: Sie nimmt sich erst dann eine Aufgabe, wenn der Call Stack leer ist. Und sie unterscheidet zwei Sorten von Aufgaben.
Die Konsequenz dieser Regel lässt sich in fünf Zeilen zeigen:
console.log('1 – synchron');
setTimeout(() => console.log('4 – Makrotask'), 0);
Promise.resolve().then(() => console.log('3 – Mikrotask'));
console.log('2 – synchron');
// Ausgabe: 1, 2, 3, 4
// Obwohl setTimeout mit 0 ms vorher registriert wurde, kommt der
// Promise-Callback zuerst: Mikrotasks haben immer Vorrang.
Teil I · Kapitel 03Syntax-Grundlagen
3.1Anweisungen, Ausdrücke, Semikolons
JavaScript unterscheidet Ausdrücke (etwas, das einen Wert ergibt:
2 + 3, f(), x ? a : b) von Anweisungen
(etwas, das etwas tut: if, for, return). Wo ein Ausdruck
erlaubt ist, darf kein if stehen – deshalb existiert der ternäre Operator.
Semikolons beenden Anweisungen, sind aber meist optional: Die Engine fügt sie nach Regeln der Automatic Semicolon Insertion (ASI) selbst ein. Das funktioniert erstaunlich gut, hat aber scharfe Kanten.
// 1) return beendet die Zeile — immer.
function falsch() {
return
{ wert: 42 }; // unerreichbar
}
falsch(); // undefined, nicht { wert: 42 }
// 2) Eine Zeile, die mit ( oder [ beginnt, hängt sich an die vorherige.
const a = 1
const b = 2
const c = a + b
;[a, b, c].forEach(n => console.log(n)) // führendes ; nötig
Beide Lager – „immer Semikolon“ und „nie Semikolon“ – sind in der Praxis tragfähig, solange ein Formatierer wie Prettier die Entscheidung konsequent durchsetzt. Die schlechte Variante ist, es dem Zufall zu überlassen.
3.2Bezeichner, Groß-/Kleinschreibung, Konventionen
- Bezeichner dürfen Buchstaben (auch Unicode), Ziffern,
_und$enthalten, aber nicht mit einer Ziffer beginnen.const lösung = 1ist gültig. - JavaScript unterscheidet Groß- und Kleinschreibung:
wertundWertsind verschiedene Variablen. - Konvention:
camelCasefür Variablen und Funktionen,PascalCasefür Klassen und Konstruktoren,SCREAMING_SNAKE_CASEfür echte Konstanten, führender_historisch für „privat gemeint“ (heute ersetzt durch#-Felder).
3.3Der Strict Mode
ES5 führte einen strengeren Sprachmodus ein, der eine Reihe historischer Fehlentscheidungen
abschaltet. Aktiviert wird er durch 'use strict'; als erste Anweisung einer Datei oder
Funktion – in Modulen und Klassenkörpern gilt er automatisch.
| Ohne strict mode | Mit strict mode |
|---|---|
x = 5 erzeugt still eine globale Variable | ReferenceError |
| Schreiben auf schreibgeschützte Eigenschaften schlägt still fehl | TypeError |
this in einem freien Funktionsaufruf ist das globale Objekt | this ist undefined |
| Doppelte Parameternamen erlaubt | SyntaxError |
delete x auf Variablen erlaubt | SyntaxError |
3.4Skript oder Modul
Derselbe Quelltext wird unterschiedlich ausgewertet, je nachdem, ob er als klassisches Skript oder als Modul geladen wird. Das ist eine der häufigsten Ursachen für „läuft bei mir nicht“.
| Eigenschaft | Skript | Modul |
|---|---|---|
| Einbindung | <script src>, .cjs | <script type="module">, .mjs, "type": "module" |
| Strict Mode | nur auf Anforderung | immer |
| Gültigkeitsbereich oberster Ebene | global | modul-lokal |
import/export | nicht erlaubt | erlaubt |
| Ausführungszeitpunkt im Browser | sofort, blockierend | verzögert wie defer |
Top-Level await | nein | ja |
3.5Kommentare und JSDoc
// einzeilig
/* mehrzeilig,
nicht schachtelbar */
/**
* JSDoc: wird von Editoren und TypeScript ausgewertet.
* @param {string} name Anzuzeigender Name
* @param {number} [alter] Optional
* @returns {string}
*/
function begruessung(name, alter) {
return alter ? `${name} (${alter})` : name;
}
Teil II · Kapitel 04Variablen, Scope und Closures
4.1Die drei Deklarationen
var | let | const | |
|---|---|---|---|
| Gültigkeitsbereich | Funktion | Block | Block |
| Neu zuweisbar | ja | ja | nein |
| Erneut deklarierbar | ja | nein | nein |
| Vor der Deklaration | undefined | ReferenceError (temporale tote Zone) | |
| Legt globale Eigenschaft an | ja (im Skript) | nein | nein |
Faustregel: const als Standard, let wenn eine
Neuzuweisung wirklich gebraucht wird, var nie mehr in neuem Code.
4.2Hoisting und die temporale tote Zone
Hoisting beschreibt, dass Deklarationen beim Betreten eines Scopes zuerst registriert
werden, bevor eine einzige Zeile läuft. Bei var und Funktionsdeklarationen wird dabei
auch schon ein Wert hinterlegt; bei let/const/class
existiert die Bindung zwar, ist aber bis zur Deklarationszeile gesperrt – die
temporale tote Zone (TDZ).
console.log(a); // undefined — var ist da, aber leer
console.log(fn()); // "ok" — Funktionsdeklaration ist vollständig da
console.log(b); // ReferenceError: Cannot access 'b' before initialization
var a = 1;
function fn() { return 'ok'; }
let b = 2;
4.3Scope-Ketten
Trifft die Engine auf einen Bezeichner, sucht sie ihn im aktuellen Scope, dann im umschließenden, und so weiter bis zum globalen Scope. Gefunden wird die erste Übereinstimmung – äußere Variablen werden von inneren gleichen Namens verdeckt (shadowing).
Entscheidend: Die Kette ergibt sich aus der Position im Quelltext, nicht aus dem
Aufrufpfad. JavaScript nutzt lexikalisches Scoping. (Die einzige Ausnahme ist
this – siehe Kapitel 09.)
4.4Closures
Eine Closure entsteht, wenn eine Funktion Zugriff auf Variablen ihres Entstehungs-Scopes behält, obwohl dieser längst verlassen wurde. Das ist kein Sonderfall, sondern die normale Funktionsweise – jede Funktion in JavaScript ist eine Closure.
function zaehlerErzeugen(start = 0) {
let stand = start; // lebt weiter, ist aber von außen unerreichbar
return {
hoch: () => ++stand,
runter:() => --stand,
lesen: () => stand,
};
}
const z = zaehlerErzeugen(10);
z.hoch(); // 11
z.hoch(); // 12
z.lesen(); // 12
z.stand; // undefined — kein Zugriff von außen
Teil II · Kapitel 05Datentypen und Werte
JavaScript kennt acht Typen: sieben primitive und den Objekttyp. Primitive Werte sind unveränderlich und werden beim Zuweisen kopiert; Objekte werden über Referenzen gehandhabt.
| Typ | typeof | Beispielwerte | Anmerkung |
|---|---|---|---|
| Number | "number" | 42, 3.14, NaN, Infinity | IEEE-754 Gleitkomma, 64 Bit |
| BigInt | "bigint" | 9007199254740993n | beliebig große Ganzzahlen |
| String | "string" | 'Hallo', `x=${x}` | UTF-16, unveränderlich |
| Boolean | "boolean" | true, false | |
| undefined | "undefined" | undefined | „noch kein Wert gesetzt“ |
| null | "object" ⚠ | null | „absichtlich kein Wert“ |
| Symbol | "symbol" | Symbol('id') | garantiert eindeutiger Schlüssel |
| Object | "object" / "function" | {}, [], new Map() | alles Zusammengesetzte |
5.1Zahlen
Es gibt nur einen Zahlentyp: 64-Bit-Gleitkomma. Kein int, kein
float, kein double. Daraus folgen drei Eigenheiten, die man kennen
muss.
// 1) Dezimalbrüche sind binär nicht exakt darstellbar
0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 === 0.3; // false
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON; // true — so vergleicht man
// 2) Oberhalb von 2^53 gehen Ganzzahlen verloren
Number.MAX_SAFE_INTEGER; // 9007199254740991
9007199254740992 === 9007199254740993; // true (!)
9007199254740993n; // BigInt — exakt
// 3) NaN ist zu nichts gleich, auch nicht zu sich selbst
NaN === NaN; // false
Number.isNaN(NaN); // true — korrekt
isNaN('abc'); // true — wandelt vorher um, irreführend
Object.is(NaN, NaN); // true
Für Geldbeträge deshalb: in der kleinsten Einheit rechnen (Cent als Ganzzahl), oder eine
Dezimalbibliothek einsetzen. Niemals 0.1 + 0.2 auf einer Rechnung.
5.2Strings
Strings sind unveränderlich – jede „Änderung“ erzeugt einen neuen String. Intern sind sie UTF-16-Code-Unit-Folgen, was bei Zeichen außerhalb der Basic Multilingual Plane (Emoji, historische Schriften) zu Überraschungen führt.
const name = 'Welt';
// Template Literal: Interpolation und Mehrzeiligkeit
const text = `Hallo ${name},
es ist ${new Date().getFullYear()}.`;
// Unveränderlichkeit
let s = 'abc';
s[0] = 'x'; // still wirkungslos (bzw. TypeError im strict mode)
s = 'x' + s.slice(1); // so geht es
// UTF-16 zeigt sich bei Emoji
'😀'.length; // 2 — zwei Code Units
[...'😀'].length; // 1 — der Iterator läuft über Code Points
Intl.Segmenter && [...new Intl.Segmenter().segment('👨👩👧')].length; // 1 — echte Graphem-Cluster
// Nützliche Methoden
' x '.trim(); // 'x'
'a-b-c'.split('-'); // ['a','b','c']
'abc'.at(-1); // 'c'
'5'.padStart(3, '0'); // '005'
'ab'.repeat(3); // 'ababab'
'Straße'.localeCompare('Strasse', 'de'); // sprachbewusster Vergleich
5.3undefined gegen null
- undefined
- Von der Sprache vergeben: nicht zugewiesene Variablen, fehlende Argumente, nicht vorhandene
Eigenschaften, Funktionen ohne
return. - null
- Vom Entwickler gesetzt: „hier steht bewusst nichts“. Auch die Rückgabe von APIs wie
document.querySelector, wenn nichts gefunden wurde.
Zusammen bilden sie die „nullish“-Werte, auf die ?? und ?. reagieren.
Eine praktikable Konvention: nur undefined verwenden und null allein dort
akzeptieren, wo fremde APIs es liefern.
5.4Symbole
const id = Symbol('id'); // Der Text ist nur eine Beschreibung
Symbol('id') === Symbol('id'); // false — jedes Symbol ist eindeutig
const nutzer = { name: 'Ada', [id]: 7 };
Object.keys(nutzer); // ['name'] — Symbole tauchen nicht auf
JSON.stringify(nutzer); // '{"name":"Ada"}'
nutzer[id]; // 7
// Well-known Symbols greifen in Sprachmechanismen ein
class Bereich {
constructor(a, b) { this.a = a; this.b = b; }
*[Symbol.iterator]() { for (let i = this.a; i <= this.b; i++) yield i; }
get [Symbol.toStringTag]() { return 'Bereich'; }
}
[...new Bereich(1, 4)]; // [1, 2, 3, 4]
5.5Wert- gegen Referenzsemantik
let a = 1, b = a; b++; // a bleibt 1 — Primitive werden kopiert
const o1 = { n: 1 };
const o2 = o1; o2.n = 99;
o1.n; // 99 — beide zeigen auf dasselbe Objekt
o1 === o2; // true — Identität, nicht Inhalt
{ n: 1 } === { n: 1 }; // false — zwei verschiedene Objekte
// Flache Kopie
const flach = { ...o1 };
// Tiefe Kopie (Browser & Node ≥ 17, kein Teil der Sprachnorm)
const tief = structuredClone({ a: { b: [1, 2] } });
Teil II · Kapitel 06Typumwandlung und Vergleiche
JavaScript wandelt Typen bereitwillig und oft unaufgefordert um. Dieses Verhalten ist vollständig spezifiziert und nicht zufällig – aber es ist überraschend genug, dass die Regel „vermeide implizite Umwandlung“ in der Praxis mehr wert ist als deren Beherrschung.
6.1Wahrheitswerte
An jeder Stelle, die einen Boolean erwartet, wird umgewandelt. Genau acht Werte sind falsy, alles andere ist truthy:
false, 0, -0, 0n, '', null, undefined, NaN
// Alles andere ist truthy — auch das hier:
Boolean([]); // true — leeres Array
Boolean({}); // true — leeres Objekt
Boolean('0'); // true — nicht-leerer String
Boolean('false'); // true
Boolean(function(){});// true
6.2== gegen ===
=== vergleicht ohne Umwandlung: unterschiedliche Typen sind nie gleich.
== wandelt vorher um, nach Regeln, die sich niemand merken kann.
0 == ''; // true
0 == '0'; // true
'' == '0'; // false ← nicht transitiv!
null == undefined; // true
null == 0; // false
null >= 0; // true ← Vergleich wandelt anders um als Gleichheit
[] == false; // true
[] == ![]; // true
'\n\t ' == 0; // true
Regel: Immer ===. Die einzige vertretbare Ausnahme ist
x == null als Kurzform für „ist null oder undefined“.
6.3Wie Objekte zu Primitiven werden
Soll ein Objekt in einem numerischen oder String-Kontext verwendet werden, ruft die Engine
intern ToPrimitive auf: erst Symbol.toPrimitive falls vorhanden, sonst
– je nach gewünschtem Ergebnis – valueOf und toString.
[] + []; // '' — beide werden zu ''
[] + {}; // '[object Object]'
[1,2] + [3]; // '1,23' — Array.toString() fügt Kommas ein
'5' - 2; // 3 — Minus kennt nur Zahlen
'5' + 2; // '52' — Plus bevorzugt Strings, sobald einer beteiligt ist
+'5'; // 5 — unäres Plus wandelt in eine Zahl
const geld = {
betrag: 12.5,
[Symbol.toPrimitive](hinweis) {
return hinweis === 'number' ? this.betrag : `${this.betrag} €`;
},
};
+geld; // 12.5
`${geld}`; // '12.5 €'
6.4Explizite Umwandlung
Number('42'); // 42
Number('42px'); // NaN — streng
parseInt('42px', 10); // 42 — liest, solange es passt; Basis immer angeben
parseFloat('3.5rem'); // 3.5
String(42); // '42'
(42).toString(2); // '101010' — Binärdarstellung
(1234.5).toFixed(2); // '1234.50' — als String, kaufmännisch nicht exakt
Boolean(wert); // explizit
!!wert; // dasselbe, kürzer, verbreitet
// Zahlen lesbar formatieren — sprachbewusst
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' })
.format(1234.5); // '1.234,50 €'
Teil II · Kapitel 07Operatoren
7.1Arithmetik und Zuweisung
7 / 2; // 3.5 — keine Ganzzahldivision
7 % 3; // 1 — Rest, behält das Vorzeichen des Dividenden
-7 % 3; // -1 (nicht 2, wie in Python)
2 ** 10; // 1024 — Potenz, rechtsassoziativ
Math.trunc(7 / 2); // 3 — Ganzzahlanteil
let n = 5;
n += 2; n **= 2; n %= 10; // alle Operatoren haben eine Zuweisungsform
n++; // gibt den alten Wert zurück, erhöht dann
++n; // erhöht, gibt dann den neuen Wert zurück
7.2Logische Operatoren und Kurzschlussauswertung
&& und || geben keinen Boolean zurück, sondern
einen ihrer Operanden – und werten den rechten nur aus, wenn nötig.
'a' || 'b'; // 'a' — erster truthy Wert
null || 'b'; // 'b'
'a' && 'b'; // 'b' — letzter Wert, wenn alle truthy
0 && 'b'; // 0
// ?? reagiert NUR auf null und undefined — nicht auf 0 oder ''
const limit1 = eingabe || 10; // 0 wird zu 10 ← meist ein Bug
const limit2 = eingabe ?? 10; // 0 bleibt 0 ← meist gemeint
// Logische Zuweisung (ES2021)
optionen.tiefe ??= 3; // nur setzen, wenn null/undefined
cache.wert ||= teuer(); // nur setzen, wenn falsy
flag &&= pruefe(); // nur setzen, wenn truthy
7.3Optional Chaining
antwort?.daten?.nutzer?.name; // undefined statt TypeError
antwort?.liste?.[0]; // auch für Indexzugriffe
objekt.methode?.(); // ruft nur auf, wenn die Methode existiert
// Kombination mit ?? ist das übliche Muster
const name = antwort?.daten?.nutzer?.name ?? 'Unbekannt';
7.4Spread und Rest
// Spread — auseinanderziehen
const zusammen = [...a, ...b];
const kopie = { ...original, ueberschrieben: 1 }; // spätere gewinnen
Math.max(...zahlen);
[...'abc']; // ['a','b','c']
// Rest — einsammeln
function summe(...werte) { return werte.reduce((s, w) => s + w, 0); }
const [erstes, ...restliche] = liste;
const { id, ...uebrige } = objekt;
7.5Operator-Vorrang
Die vollständige Tabelle hat über zwanzig Stufen. Für den Alltag reichen diese Regeln:
unäre Operatoren binden am stärksten, dann **, dann * / %, dann
+ -, dann Vergleiche, dann &&, dann || und
??, zuletzt ?: und Zuweisungen.
Teil II · Kapitel 08Kontrollfluss
8.1Verzweigungen
if (punkte >= 90) note = 1;
else if (punkte >= 75) note = 2;
else note = 3;
// Ternär — ein Ausdruck, also überall verwendbar
const status = punkte >= 50 ? 'bestanden' : 'nicht bestanden';
// switch vergleicht mit === und fällt ohne break durch
switch (typ) {
case 'a':
case 'b': // gewollter Durchfall: a und b gleich behandeln
behandleAB();
break;
case 'c': {
const temp = 1; // Block nötig, sonst gilt die Deklaration
behandleC(temp); // für den ganzen switch
break;
}
default:
unbekannt();
}
8.2Schleifen
| Form | Läuft über | Einsatz |
|---|---|---|
for (let i = 0; …) | Zähler | wenn der Index gebraucht wird |
for…of | Werte iterierbarer Objekte | Standard für Arrays, Strings, Map, Set |
for…in | Schlüssel inkl. geerbter | nur für einfache Objekte – nie für Arrays |
while / do…while | Bedingung | unbekannte Anzahl Durchläufe |
.forEach() | Array-Elemente | wenn kein break nötig ist |
for await…of | asynchrone Iterierbare | Streams, seitenweise APIs |
const liste = ['a', 'b', 'c'];
liste.extra = 'x';
for (const wert of liste) console.log(wert); // 'a','b','c' — nur Elemente
for (const key in liste) console.log(key); // '0','1','2','extra' — Strings!
// Index und Wert zugleich
for (const [i, wert] of liste.entries()) console.log(i, wert);
// Objekte durchlaufen
for (const [k, v] of Object.entries({ a: 1, b: 2 })) console.log(k, v);
8.3Sprungmarken
aussen:
for (const zeile of matrix) {
for (const zelle of zeile) {
if (zelle === ziel) break aussen; // verlässt beide Schleifen
if (zelle == null) continue aussen; // nächste Zeile
}
}
Teil II · Kapitel 09Funktionen und this
Funktionen sind in JavaScript gewöhnliche Werte: übergebbar, zurückgebbar, in Variablen speicherbar, mit Eigenschaften versehbar. Das ist die Grundlage für Callbacks, Closures und den gesamten funktionalen Stil.
9.1Die Schreibweisen
function deklaration(a, b) { return a + b; } // gehoistet, hat eigenes this
const ausdruck = function (a, b) { return a + b; }; // nicht gehoistet
const pfeil = (a, b) => a + b; // kein eigenes this, implizites return
const methode = { addiere(a, b) { return a + b; } };// Kurzform in Objekten
// Arrow-Feinheiten
const quadrat = n => n * n; // ein Parameter: Klammern optional
const leer = () => ({}); // Objektliteral in Klammern, sonst Block
const async_ = async () => await f();
9.2Parameter
// Default greift nur bei undefined, nicht bei null oder 0
function gruss(name = 'Welt') { return `Hallo ${name}`; }
gruss(); // 'Hallo Welt'
gruss(undefined); // 'Hallo Welt'
gruss(null); // 'Hallo null'
// Defaults dürfen frühere Parameter verwenden
function bereich(start, ende = start + 10) { /* … */ }
// Benannte Parameter per Destructuring — die übliche Lösung für viele Optionen
function verbinde(url, { timeout = 5000, wiederholungen = 3, kopfzeilen = {} } = {}) {
// …
}
verbinde('/api', { timeout: 1000 });
// Rest sammelt den Überhang
function protokolliere(stufe, ...teile) { console.log(stufe, ...teile); }
9.3this – die vier Regeln
this ist in JavaScript nicht lexikalisch. Sein Wert hängt davon
ab, wie eine Funktion aufgerufen wird, nicht wo sie steht. Die Regeln, von der stärksten
zur schwächsten:
new→thisist das neu erzeugte Objekt.call/apply/bind→thisist das explizit übergebene Objekt.- Methodenaufruf
obj.fn()→thisistobj. - Freier Aufruf
fn()→undefinedim strict mode, sonst das globale Objekt.
Arrow Functions stehen außerhalb dieser Regeln: Sie haben kein eigenes this,
sondern übernehmen es aus dem umgebenden Scope – und zwar endgültig,
bind ändert daran nichts.
const zaehler = {
stand: 0,
hoch() { this.stand++; },
};
zaehler.hoch(); // ✓ this === zaehler
const losgeloest = zaehler.hoch;
losgeloest(); // ✗ TypeError — this ist undefined
setTimeout(zaehler.hoch, 100); // ✗ dasselbe Problem
// Auswege
setTimeout(() => zaehler.hoch(), 100); // 1) Arrow als Hülle
setTimeout(zaehler.hoch.bind(zaehler), 100); // 2) fest binden
class Z { stand = 0; hoch = () => { this.stand++; }; } // 3) Feld mit Arrow
9.4Funktionen höherer Ordnung
// Funktion als Argument
const verdoppelt = zahlen.map(n => n * 2);
// Funktion als Rückgabe
const multiplizierer = faktor => n => n * faktor;
const verdreifache = multiplizierer(3);
verdreifache(7); // 21
// Praktische Bausteine
function entprellen(fn, ms = 300) {
let marke;
return (...args) => {
clearTimeout(marke);
marke = setTimeout(() => fn(...args), ms);
};
}
function merken(fn) {
const cache = new Map();
return (...args) => {
const schluessel = JSON.stringify(args);
if (!cache.has(schluessel)) cache.set(schluessel, fn(...args));
return cache.get(schluessel);
};
}
Teil II · Kapitel 10Objekte
10.1Erzeugen und zugreifen
const schluessel = 'dynamisch';
const wert = 42;
const obj = {
name: 'Ada', // normale Eigenschaft
wert, // Kurzform für wert: wert
[schluessel]: true, // berechneter Schlüssel
begruesse() { return `Hallo ${this.name}`; }, // Methodenkurzform
get initialen() { return this.name[0]; }, // Getter
set initialen(v) { this.name = v; }, // Setter
};
obj.name; // Punktnotation
obj['name']; // Klammernotation — nötig für dynamische/ungültige Schlüssel
obj?.fehlt?.tief; // sicher
'name' in obj; // true — auch geerbte
Object.hasOwn(obj, 'name'); // true — nur eigene (ES2022)
10.2Destructuring
const { name, alter = 0, adresse: { stadt } = {} } = nutzer;
// ^ direkt ^ Default ^ verschachtelt + umbenannt
const [erstes, , drittes = 0, ...rest] = werte;
// ^ übersprungen
// Tausch ohne Hilfsvariable
[a, b] = [b, a];
// In Parametern und Schleifen
function zeichne({ x = 0, y = 0 } = {}) { /* … */ }
for (const { id, name } of nutzerListe) { /* … */ }
// Rückgabe mehrerer Werte
function teilen(text) { return { kopf: text[0], rumpf: text.slice(1) }; }
const { kopf, rumpf } = teilen('Hallo');
10.3Objekte untersuchen und umbauen
Object.keys(obj); // ['a','b'] eigene, aufzählbare String-Schlüssel
Object.values(obj); // [1, 2]
Object.entries(obj); // [['a',1], ['b',2]]
Object.fromEntries(paare); // Rückweg — auch für Map und URLSearchParams
Object.assign(ziel, q1, q2); // mutiert das Ziel
const neu = { ...q1, ...q2 }; // erzeugt ein neues Objekt — meist besser
Object.freeze(obj); // flach einfrieren; Verschachteltes bleibt änderbar
Object.isFrozen(obj);
// Feinsteuerung über Property Descriptors
Object.defineProperty(obj, 'id', {
value: 7,
writable: false, // nicht überschreibbar
enumerable: false, // taucht nicht in Object.keys / for…in auf
configurable: false, // nicht löschbar, nicht neu definierbar
});
// Objekt ganz ohne Prototyp — sicher als Wörterbuch
const woerterbuch = Object.create(null);
woerterbuch.toString; // undefined — keine geerbten Überraschungen
10.4Kopieren: flach gegen tief
const original = { a: 1, tief: { b: 2 }, datum: new Date() };
// 1) Flach — verschachtelte Objekte bleiben geteilt
const flach = { ...original };
flach.tief.b = 99;
original.tief.b; // 99 — dieselbe Referenz
// 2) JSON-Umweg — funktioniert, verliert aber viel
const json = JSON.parse(JSON.stringify(original));
json.datum; // String, kein Date
// verliert außerdem: undefined, Funktionen, Symbole, Map, Set, BigInt (wirft),
// und scheitert an zyklischen Strukturen
// 3) structuredClone — der richtige Weg (Browser & Node ≥ 17)
const tief = structuredClone(original);
tief.datum instanceof Date; // true — Date, Map, Set, zyklische Referenzen bleiben
// Ausnahme: Funktionen und DOM-Knoten können nicht geklont werden
Teil II · Kapitel 11Prototypen und Klassen
JavaScript hat keine klassenbasierte Vererbung. Jedes Objekt besitzt eine interne
Verknüpfung zu einem anderen Objekt – seinem Prototyp. Findet die Engine eine
Eigenschaft nicht, folgt sie dieser Verknüpfung weiter, bis sie fündig wird oder bei
null ankommt. Die class-Syntax ist eine lesbarere Schreibweise für genau
diesen Mechanismus.
11.1Die Prototypenkette
const tier = {
atmet: true,
beschreibe() { return `${this.name} atmet: ${this.atmet}`; },
};
const hund = Object.create(tier); // hund.[[Prototype]] === tier
hund.name = 'Rex';
hund.atmet; // true — nicht eigen, über die Kette gefunden
hund.beschreibe(); // 'Rex atmet: true' — this bleibt hund
Object.hasOwn(hund, 'atmet'); // false
Object.getPrototypeOf(hund) === tier; // true
Object.getPrototypeOf(tier) === Object.prototype; // true
Object.getPrototypeOf(Object.prototype); // null — Ende der Kette
// Schreiben legt IMMER eine eigene Eigenschaft an, nie auf dem Prototyp
hund.atmet = false;
tier.atmet; // true — unberührt
11.2Klassen
class Konto {
#saldo = 0; // privat — nur innerhalb der Klasse sichtbar
static #anzahl = 0; // privat und statisch
static WAEHRUNG = 'EUR'; // öffentliches statisches Feld
static { /* statischer Initialisierungsblock, ES2022 */ }
constructor(inhaber, start = 0) {
this.inhaber = inhaber;
this.#saldo = start;
Konto.#anzahl++;
}
get saldo() { return this.#saldo; } // nur lesbar von außen
einzahlen(betrag) {
if (betrag <= 0) throw new RangeError('Betrag muss positiv sein');
this.#saldo += betrag;
return this; // ermöglicht Verkettung
}
static anzahl() { return Konto.#anzahl; }
static von(daten) { return new Konto(daten.inhaber, daten.start); } // Factory
toString() { return `${this.inhaber}: ${this.#saldo} ${Konto.WAEHRUNG}`; }
}
const k = new Konto('Ada', 100).einzahlen(50);
k.saldo; // 150
k.#saldo; // SyntaxError — echte Kapselung, nicht nur Konvention
11.3Vererbung
class Sparkonto extends Konto {
constructor(inhaber, start, zins) {
super(inhaber, start); // MUSS vor jedem this-Zugriff stehen
this.zins = zins;
}
verzinsen() {
this.einzahlen(this.saldo * this.zins);
}
toString() {
return `${super.toString()} (${this.zins * 100} %)`; // Basisversion aufrufen
}
}
const s = new Sparkonto('Grace', 1000, 0.02);
s instanceof Sparkonto; // true
s instanceof Konto; // true — instanceof prüft die ganze Prototypenkette
// Eingebaute Klassen sind erweiterbar
class ValidierungsFehler extends Error {
constructor(feld, ursache) {
super(`Feld "${feld}" ist ungültig`, { cause: ursache });
this.name = 'ValidierungsFehler';
this.feld = feld;
}
}
11.4Klasse oder einfaches Objekt?
Klassen lohnen sich, wenn viele Instanzen mit gemeinsamen Methoden und eigenem, veränderlichem
Zustand gebraucht werden – die Methoden liegen dann einmal auf dem Prototyp statt pro
Instanz. Für Konfigurationen, Datenstrukturen und reine Funktionssammlungen sind schlichte
Objekte, Factory-Funktionen und Module meist einfacher, besser testbar und frei von
this-Problemen.
Teil II · Kapitel 12Arrays, Map und Set
12.1Arrays
Arrays sind in JavaScript Objekte mit numerischen Schlüsseln und einer besonderen
length-Eigenschaft. Sie sind nicht typisiert, nicht fester Länge und dürfen Lücken
enthalten.
const a = [1, 2, 3];
Array.from({ length: 5 }, (_, i) => i * i); // [0,1,4,9,16]
Array.of(7); // [7]
new Array(7); // [ leer × 7 ] — Falle, kein [7]
a.length = 1; // kürzt destruktiv: a ist jetzt [1]
a[10] = 'x'; // erzeugt Lücken; a.length ist 11
Array.isArray(a); // true — typeof a wäre nur 'object'
12.2Mutierend gegen nicht-mutierend
Die wichtigste Unterscheidung im Umgang mit Arrays. Seit ES2023 gibt es zu jeder mutierenden Methode eine kopierende Entsprechung – entscheidend für React, Redux und jeden Code mit unveränderlichem Zustand.
| Verändert das Original | Gibt ein neues Array zurück |
|---|---|
push, pop, shift, unshift | [...a, x], a.slice(1), a.concat(b) |
sort() | toSorted() |
reverse() | toReversed() |
splice(i, n, …) | toSpliced(i, n, …) |
a[i] = wert | with(i, wert) |
fill, copyWithin | map, filter, flat, flatMap |
12.3Die wichtigsten Methoden
const nutzer = [
{ name: 'Ada', alter: 36, rolle: 'admin' },
{ name: 'Grace', alter: 45, rolle: 'nutzer' },
{ name: 'Linus', alter: 28, rolle: 'admin' },
];
// Transformieren
nutzer.map(n => n.name); // ['Ada','Grace','Linus']
nutzer.filter(n => n.rolle === 'admin'); // zwei Einträge
nutzer.flatMap(n => [n.name, n.rolle]); // map + eine Ebene flach
// Suchen
nutzer.find(n => n.alter > 40); // das Objekt Grace
nutzer.findIndex(n => n.name === 'Ada'); // 0
nutzer.findLast(n => n.rolle === 'admin'); // Linus (ES2023)
nutzer.some(n => n.alter > 40); // true — mindestens einer
nutzer.every(n => n.alter > 20); // true — alle
[1,2,3].includes(2); // true — findet auch NaN
// Zusammenfassen
nutzer.reduce((summe, n) => summe + n.alter, 0); // 109
nutzer.map(n => n.name).join(', '); // 'Ada, Grace, Linus'
// Gruppieren (ES2024)
Object.groupBy(nutzer, n => n.rolle);
// { admin: [Ada, Linus], nutzer: [Grace] }
// Zugriff
nutzer.at(-1); // letztes Element (ES2022)
[[1,[2]],[3]].flat(2); // [1,2,3]
12.4Map und Set
Object | Map | |
|---|---|---|
| Schlüsseltypen | String, Symbol | beliebig, auch Objekte |
| Reihenfolge | Ganzzahlen zuerst, dann Einfügereihenfolge | strikt Einfügereihenfolge |
| Größe | Object.keys(o).length | map.size |
| Iterierbar | nein (nur über Object.entries) | ja, direkt |
| Geerbte Schlüssel | ja – Kollisionsgefahr | nein |
| Viele Einfügungen/Löschungen | langsamer | dafür ausgelegt |
| JSON-fähig | ja | nein (Umweg nötig) |
const m = new Map([['a', 1], ['b', 2]]);
m.set(objektAlsSchluessel, 'geht auch').get('a'); // 1
m.has('a'); m.delete('a'); m.size;
for (const [k, v] of m) { /* … */ }
Object.fromEntries(m); // zurück in ein Objekt
const s = new Set([1, 2, 2, 3]); // [1,2,3] — Duplikate entfallen
[...new Set(liste)]; // der Standardweg zum Entduplizieren
// Mengenoperationen (ES2025)
const a = new Set([1,2,3]), b = new Set([2,3,4]);
a.union(b); // {1,2,3,4}
a.intersection(b); // {2,3}
a.difference(b); // {1}
a.symmetricDifference(b);// {1,4}
a.isSubsetOf(b); // false
// WeakMap / WeakSet: Schlüssel dürfen vom Garbage Collector eingesammelt werden.
// Ideal für Metadaten zu Objekten, ohne deren Lebensdauer zu verlängern.
const meta = new WeakMap();
meta.set(domKnoten, { besucht: true }); // hält den Knoten nicht am Leben
Teil II · Kapitel 13Fehlerbehandlung
13.1try, catch, finally
try {
riskant();
} catch (fehler) { // Bindung seit ES2019 weglassbar: catch { … }
if (fehler instanceof TypeError) behandle(fehler);
else throw fehler; // nicht Zuständiges weiterreichen
} finally {
aufraeumen(); // läuft immer — auch nach return oder throw
}
13.2Die eingebauten Fehlertypen
| Typ | Wird geworfen bei |
|---|---|
TypeError | Operation auf einem Wert des falschen Typs – null.foo, undefined() |
ReferenceError | Unbekannter Bezeichner, Zugriff in der TDZ |
SyntaxError | Ungültiger Quelltext, auch bei JSON.parse |
RangeError | Wert außerhalb des Zulässigen – new Array(-1), Stack Overflow |
AggregateError | Mehrere Fehler zugleich – von Promise.any |
DOMException | Browser-APIs, z. B. AbortError bei abgebrochenem fetch |
13.3Eigene Fehler mit Ursachenkette
class ApiFehler extends Error {
constructor(nachricht, { status, ...rest } = {}) {
super(nachricht, rest); // rest kann { cause } enthalten (ES2022)
this.name = 'ApiFehler';
this.status = status;
}
}
try {
await abrufen('/nutzer/7');
} catch (ursache) {
throw new ApiFehler('Nutzer konnte nicht geladen werden', {
status: 502,
cause: ursache, // die ursprüngliche Ursache bleibt erhalten
});
}
13.4Nicht abgefangene Fehler
// Browser
window.addEventListener('error', e => melde(e.error));
window.addEventListener('unhandledrejection', e => melde(e.reason));
// Node.js — hier sollte der Prozess danach kontrolliert enden
process.on('uncaughtException', (err) => { melde(err); process.exit(1); });
process.on('unhandledRejection', (grund) => { melde(grund); process.exit(1); });
Teil III · Kapitel 14Asynchrone Programmierung
Das wichtigste Kapitel für die tägliche Arbeit. Netzwerk, Dateien, Timer, Benutzereingaben – alles Langsame läuft asynchron, und JavaScript hat drei Generationen von Werkzeugen dafür hervorgebracht.
14.1Generation 1: Callbacks
ladeNutzer(id, (fehler, nutzer) => { // Node-Konvention: Fehler zuerst
if (fehler) return behandle(fehler);
ladeBestellungen(nutzer, (fehler, best) => {
if (fehler) return behandle(fehler);
ladeDetails(best[0], (fehler, detail) => { // „Callback Hell“
if (fehler) return behandle(fehler);
zeige(detail);
});
});
});
14.2Generation 2: Promises
Ein Promise ist ein Objekt, das für ein Ergebnis steht, das später eintrifft. Es
hat genau drei Zustände und wechselt genau einmal:
- pending
- Das Ergebnis steht noch aus.
- fulfilled
- Erfolgreich abgeschlossen, mit einem Wert.
- rejected
- Fehlgeschlagen, mit einem Grund.
const warte = (ms) => new Promise(aufloesen => setTimeout(aufloesen, ms));
// Jedes .then gibt ein neues Promise zurück — daher die flache Kette
ladeNutzer(id)
.then(nutzer => ladeBestellungen(nutzer)) // Rückgabe wird „ausgepackt“
.then(best => ladeDetails(best[0]))
.then(zeige)
.catch(behandle) // fängt Fehler der ganzen Kette
.finally(() => ladeanzeigeAus()); // läuft immer
// ES2024: Auflöser von außen zugänglich machen
const { promise, resolve, reject } = Promise.withResolvers();
14.3Mehrere Promises kombinieren
| Methode | Erfüllt sich, wenn | Ergebnis |
|---|---|---|
Promise.all | alle erfolgreich | Array der Werte; bricht beim ersten Fehler sofort ab |
Promise.allSettled | alle fertig, egal wie | Array aus {status, value} / {status, reason} |
Promise.race | das erste beendet ist | dessen Ergebnis – auch ein Fehler |
Promise.any | das erste erfolgreich ist | dessen Wert; sonst AggregateError |
// Nacheinander: 300 ms
const a = await ladeA(); // 100 ms
const b = await ladeB(); // 100 ms
const c = await ladeC(); // 100 ms
// Gleichzeitig: 100 ms
const [a, b, c] = await Promise.all([ladeA(), ladeB(), ladeC()]);
// Wenn Teilergebnisse genügen
const ergebnisse = await Promise.allSettled(ids.map(ladeNutzer));
const geladen = ergebnisse
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
// Zeitlimit per race
await Promise.race([
abrufen(url),
warte(5000).then(() => { throw new Error('Zeitüberschreitung'); }),
]);
14.4Generation 3: async / await
Syntaktischer Zucker über Promises – und zwar hervorragender. Eine
async-Funktion gibt immer ein Promise zurück; await hält die Funktion an,
bis das Promise erledigt ist, ohne den Thread zu blockieren.
async function zeigeDetails(id) {
try {
const nutzer = await ladeNutzer(id);
const best = await ladeBestellungen(nutzer);
const detail = await ladeDetails(best[0]);
zeige(detail);
} catch (fehler) {
behandle(fehler); // fängt alles in diesem Block
} finally {
ladeanzeigeAus();
}
}
// In Modulen auch auf oberster Ebene
const konfig = await (await fetch('/konfig.json')).json();
14.5Abbrechen mit AbortController
const steuerung = new AbortController();
const antwort = fetch('/gross.json', { signal: steuerung.signal })
.catch(e => {
if (e.name === 'AbortError') return; // bewusst abgebrochen, kein Fehlerfall
throw e;
});
steuerung.abort(); // bricht sofort ab
// Praktisch: Zeitlimit ohne eigene Logik
fetch(url, { signal: AbortSignal.timeout(5000) });
// Dasselbe Signal räumt auch Ereignis-Listener ab
element.addEventListener('click', fn, { signal: steuerung.signal });
Teil III · Kapitel 15Iteratoren und Generatoren
15.1Das Iterationsprotokoll
for…of, Spread und Destructuring funktionieren nicht auf Arrays, sondern auf allem,
was iterierbar ist: einer Methode unter dem Schlüssel
Symbol.iterator, die ein Objekt mit einer next()-Methode liefert.
next() gibt { value, done } zurück.
const bisDrei = {
[Symbol.iterator]() {
let n = 0;
return { next: () => (n < 3 ? { value: n++, done: false } : { value: undefined, done: true }) };
},
};
[...bisDrei]; // [0, 1, 2]
15.2Generatoren
Ein Generator (function*) schreibt dasselbe wesentlich kürzer. Er kann seine
Ausführung an jedem yield anhalten und später fortsetzen – mit erhaltenem
Zustand.
function* bisDrei() { yield 0; yield 1; yield 2; }
[...bisDrei()]; // [0, 1, 2]
// Unendlich — kein Problem, weil nur bei Bedarf gerechnet wird
function* fibonacci() {
let [a, b] = [0, 1];
while (true) { yield a; [a, b] = [b, a + b]; }
}
function* nimm(iter, n) {
let i = 0;
for (const wert of iter) { if (i++ >= n) return; yield wert; }
}
[...nimm(fibonacci(), 8)]; // [0,1,1,2,3,5,8,13]
// yield* delegiert an ein anderes Iterierbares
function* alles() { yield* [1, 2]; yield* bisDrei(); }
// Generatoren nehmen auch Werte entgegen
function* dialog() {
const name = yield 'Wie heißt du?';
yield `Hallo ${name}`;
}
const d = dialog();
d.next().value; // 'Wie heißt du?'
d.next('Ada').value; // 'Hallo Ada'
15.3Asynchrone Iteration
async function* seiten(url) {
let naechste = url;
while (naechste) {
const antwort = await fetch(naechste);
const daten = await antwort.json();
yield* daten.eintraege; // einzeln herausgeben
naechste = daten.weiter;
}
}
for await (const eintrag of seiten('/api/nutzer')) {
verarbeite(eintrag); // beginnt bei der ersten Seite
if (genug()) break; // bricht sauber ab, lädt nicht weiter
}
15.4Iterator-Helfer (ES2025)
// map/filter/take arbeiten jetzt direkt auf Iteratoren — bedarfsgesteuert
const ergebnis = fibonacci()
.filter(n => n % 2 === 0)
.map(n => n * n)
.take(5)
.toArray(); // [0, 4, 64, 1156, 20164]
// Auf einem Array bräuchte .filter().map() erst eine vollständige Kopie —
// bei einer unendlichen Folge ginge es gar nicht.
Teil III · Kapitel 16Module
16.1ES-Module
// ---- mathe.js ----
export const PI = 3.14159; // benannter Export
export function flaeche(r) { return PI * r * r; }
export default class Kreis { /* … */ } // höchstens einer pro Modul
export { intern as oeffentlich }; // umbenennen
export * from './geometrie.js'; // weiterreichen
// ---- app.js ----
import Kreis, { PI, flaeche } from './mathe.js'; // Default + benannte
import { flaeche as area } from './mathe.js'; // umbenennen
import * as mathe from './mathe.js'; // alles als Namensraum
import './nebenwirkung.js'; // nur ausführen
// Dynamisch — gibt ein Promise zurück, ermöglicht Code-Splitting
const { schwer } = await import('./schwer.js');
// Import-Attribute (ES2025)
import daten from './daten.json' with { type: 'json' };
16.2CommonJS
// ---- mathe.cjs ----
const PI = 3.14159;
module.exports = { PI, flaeche };
// oder: exports.PI = PI;
// ---- app.cjs ----
const { PI, flaeche } = require('./mathe.cjs');
const schwer = require('./schwer.cjs'); // synchron, dynamisch erlaubt
| ESM | CommonJS | |
|---|---|---|
| Laden | asynchron, statisch analysierbar | synchron, zur Laufzeit |
| Bindungen | live | Wertkopie zum Zeitpunkt des require |
Top-Level await | ja | nein |
| Tree Shaking | ja | eingeschränkt |
| Im Browser | nativ | nur mit Bundler |
| Aktivierung in Node | "type": "module" oder .mjs | Standard oder .cjs |
ESM ist der Standard für neuen Code. CommonJS bleibt relevant, weil ein großer Teil des
npm-Bestands darauf aufbaut – ESM kann CommonJS importieren, umgekehrt geht es nur über
dynamisches import().
Teil III · Kapitel 17Die Standardbibliothek
17.1JSON
JSON.stringify(obj, null, 2); // eingerückt
JSON.stringify(obj, ['a', 'b']); // nur diese Felder
JSON.stringify(obj, (k, v) => typeof v === 'bigint' ? String(v) : v);
JSON.parse(text, (k, v) => k === 'datum' ? new Date(v) : v); // Reviver
// Was verloren geht: undefined, Funktionen und Symbole (entfallen),
// Date (wird String), Map/Set (werden {}), NaN/Infinity (werden null),
// BigInt (wirft TypeError), zyklische Strukturen (werfen TypeError).
// Ein Objekt kann per toJSON() selbst bestimmen, wie es serialisiert wird.
17.2Datum und Zeit
const jetzt = new Date();
Date.now(); // Millisekunden seit 1970
new Date('2026-09-12T10:00:00Z'); // ISO 8601 — einziges sicheres Format
jetzt.toISOString();
// Die bekannten Schwächen von Date:
new Date(2026, 8, 12); // Monat 8 ist SEPTEMBER — Monate sind 0-basiert
jetzt.setDate(1); // mutiert das Objekt
// keine Unterstützung für Zeitzonen außer UTC und der lokalen
// Temporal (Stage 3, wird ausgeliefert) löst all das:
// Temporal.PlainDate.from('2026-09-12').add({ days: 30 });
// Temporal.Now.zonedDateTimeISO('Europe/Berlin');
// Bis dahin: date-fns oder Luxon statt eigener Rechnerei.
17.3Reguläre Ausdrücke
const muster = /(?<jahr>\d{4})-(?<monat>\d{2})-(?<tag>\d{2})/u;
const { jahr, monat, tag } = '2026-09-12'.match(muster).groups;
'a1b2'.replace(/\d/g, 'X'); // 'aXbX'
'a1b2'.replaceAll('1', 'X'); // ohne /g möglich
[...'a1b2'.matchAll(/(\w)(\d)/g)]; // alle Treffer mit Gruppen
// Flags: g global · i ignoriert Groß/Klein · m mehrzeilig
// s Punkt trifft Zeilenumbruch · u Unicode · y klebrig
// d Indizes der Treffer · v erweiterte Mengen (ES2024)
RegExp.escape(nutzereingabe); // ES2025 — Eingaben entschärfen
17.4Internationalisierung
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(1234.5);
// '1.234,50 €'
new Intl.DateTimeFormat('de-DE', { dateStyle: 'long', timeStyle: 'short' }).format(new Date());
// '12. September 2026 um 10:00'
new Intl.RelativeTimeFormat('de', { numeric: 'auto' }).format(-1, 'day'); // 'gestern'
new Intl.ListFormat('de').format(['a', 'b', 'c']); // 'a, b und c'
new Intl.PluralRules('de').select(1); // 'one'
// Korrekte Sortierung — 'ä' gehört bei 'a', nicht ans Ende
['Zeder', 'Äpfel', 'Birne'].sort(new Intl.Collator('de').compare);
17.5Math und Zahlen
Math.round(2.5); // 3 — .5 rundet immer auf, auch bei -2.5 → -2
Math.trunc(-2.7); // -2 — schneidet ab
Math.floor(-2.7); // -3 — rundet nach unten
Math.hypot(3, 4); // 5
Math.clz32, Math.sign, Math.cbrt, Math.log2;
const zufall = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
crypto.randomUUID(); // kryptografisch sicher, Browser & Node
crypto.getRandomValues(new Uint8Array(16));
Number.isInteger(5.0); // true
Number.isFinite('5'); // false — im Gegensatz zum globalen isFinite('5')
Teil III · Kapitel 18Metaprogrammierung
18.1Proxy und Reflect
Ein Proxy schiebt sich vor ein Objekt und fängt grundlegende Operationen ab –
Lesen, Schreiben, Löschen, in, Aufzählen. Reflect stellt dieselben
Operationen als normale Funktionen bereit, um das Standardverhalten weiterzureichen.
const beobachtet = new Proxy({ a: 1 }, {
get(ziel, schluessel, empfaenger) {
console.log('lese', schluessel);
return Reflect.get(ziel, schluessel, empfaenger);
},
set(ziel, schluessel, wert, empfaenger) {
if (typeof wert !== 'number') throw new TypeError('nur Zahlen');
return Reflect.set(ziel, schluessel, wert, empfaenger);
},
has(ziel, schluessel) { return !schluessel.startsWith('_') && schluessel in ziel; },
});
beobachtet.a; // protokolliert 'lese a', liefert 1
beobachtet.b = 'x'; // TypeError
Das ist die Technik hinter der Reaktivität in Vue 3, hinter Validierungsschichten und hinter Mock-Objekten in Testbibliotheken. Für Anwendungscode ist sie selten die richtige Wahl: Proxies sind langsamer als direkte Zugriffe und erschweren das Debuggen.
18.2Well-known Symbols
| Symbol | Greift ein bei |
|---|---|
Symbol.iterator | for…of, Spread, Destructuring |
Symbol.asyncIterator | for await…of |
Symbol.toPrimitive | Umwandlung in Zahl oder String |
Symbol.toStringTag | Object.prototype.toString.call(x) |
Symbol.hasInstance | instanceof |
Symbol.dispose | using-Deklarationen (Ressourcenfreigabe, Stage 3) |
18.3Weitere Einblicke zur Laufzeit
fn.length; // Zahl der Parameter vor dem ersten Default/Rest
fn.name; // Name der Funktion — auch bei const f = () => {}
Object.getOwnPropertyNames(obj); // auch nicht-aufzählbare
Object.getOwnPropertySymbols(obj);
Object.getOwnPropertyDescriptors(obj);
// Ein zuverlässigerer Typtest als typeof
const typVon = (w) => Object.prototype.toString.call(w).slice(8, -1);
typVon([]); // 'Array'
typVon(null); // 'Null'
typVon(new Date()); // 'Date'
Teil III · Kapitel 19Speicher und Performance
19.1Speicherbereinigung
JavaScript verwaltet Speicher automatisch. Der Garbage Collector gibt frei, was vom Programmzustand aus nicht mehr erreichbar ist – nicht, was „nicht mehr gebraucht“ wird. Moderne Engines arbeiten generationell: Kurzlebige Objekte werden in einem kleinen Bereich häufig und billig eingesammelt; was überlebt, wandert in einen größeren Bereich, der seltener aufgeräumt wird.
Typische Ursachen dafür, dass Speicher nicht freigegeben wird:
- Vergessene Timer und Listener.
setIntervalohneclearInterval, Event-Listener auf entfernten Elementen. Abhilfe:AbortControllerals gemeinsamer Ausschalter. - Unbegrenzte Caches. Eine
Map, die nur wächst. Abhilfe: Obergrenze und Verdrängungsstrategie, oderWeakMap, wenn Objekte als Schlüssel dienen. - Closures, die zu viel festhalten. Eine kleine Rückruffunktion kann eine große Datenstruktur aus dem umgebenden Scope am Leben erhalten.
- Detachte DOM-Knoten. Aus dem Dokument entfernt, aber noch in einem Array referenziert.
19.2Messen statt raten
Die meisten Mikro-Optimierungen in JavaScript sind wirkungslos oder kontraproduktiv, weil der optimierende Compiler ohnehin umschreibt. Belastbar ist nur die Messung:
performance.now(); // hochauflösend, in ms
performance.mark('start');
performance.measure('arbeit', 'start');
console.time('schleife'); /* … */ console.timeEnd('schleife');
// Im Browser: der Performance-Tab der DevTools zeigt Flame-Graph,
// Haupt-Thread-Blockaden und Layout-Thrashing.
// In Node: node --prof, --cpu-prof oder der eingebaute Inspector.
19.3Was tatsächlich hilft
- Weniger Arbeit tun. Der beste Algorithmus schlägt jede Mikro-Optimierung.
Eine
Map-Suche stattfindin einer Schleife verwandelt O(n²) in O(n). - Den Hauptthread freihalten. Lange Berechnungen in einen Web Worker verlagern oder in Teilstücke zerlegen. Alles über ~50 ms ist im Browser spürbar.
- Weniger laden. Code-Splitting über dynamisches
import(), Tree Shaking, Komprimierung. Übertragene Bytes kosten mehr Zeit als die Ausführung. - Layout-Thrashing vermeiden. Lesen (
offsetHeight) und Schreiben von DOM-Eigenschaften trennen, statt sie abzuwechseln. - Typen stabil halten. Objekte mit gleichbleibender Form und Arrays mit einheitlichem Inhalt lassen sich von der Engine deutlich besser optimieren.
Teil IV · Kapitel 20Das Ökosystem
20.1Pakete
npm ist die Registry und der mitgelieferte Paketmanager von Node.js;
pnpm spart Plattenplatz durch einen gemeinsamen Speicher und ist bei Monorepos
im Vorteil, yarn und bun sind weitere Alternativen. Zentral ist
in allen Fällen die package.json:
{
"name": "mein-projekt",
"type": "module",
"scripts": { "dev": "vite", "test": "vitest", "build": "vite build" },
"dependencies": { "zod": "^3.23.0" },
"devDependencies": { "vitest": "^2.0.0" },
"engines": { "node": ">=20" }
}
Zur Versionsangabe: ^3.23.0 erlaubt alles unter 4.0.0, ~3.23.0 nur
Patch-Versionen, 3.23.0 genau diese. Die Lock-Datei
(package-lock.json, pnpm-lock.yaml) hält fest, was tatsächlich
installiert wurde – sie gehört in die Versionsverwaltung, und in CI installiert man mit
npm ci statt npm install.
20.2Werkzeuge
- Bundler
- Vite (Standard für neue Projekte), esbuild, Rollup, Rspack/webpack. Fassen Module zusammen, entfernen ungenutzten Code und übersetzen für ältere Zielumgebungen.
- Transpiler
- Babel, SWC, esbuild. Übersetzen moderne Syntax in ältere – heute seltener nötig als früher, aber unverzichtbar für JSX.
- Typen
- TypeScript ist faktisch Standard in größeren Projekten: statische Typen als Aufsatz auf JavaScript, die beim Übersetzen entfallen. Wer keine Build-Stufe will, bekommt über JSDoc-Kommentare denselben Editor-Komfort.
- Qualität
- ESLint für Regeln, Prettier für Formatierung, Biome und oxlint als schnellere Kombinationswerkzeuge.
- Tests
- Vitest und Jest für Unit-Tests,
node:testohne Zusatzpaket, Playwright für Tests im echten Browser. - Frameworks
- Oberfläche: React, Vue, Svelte, Angular, Solid. Vollstack: Next.js, Nuxt, SvelteKit, Remix. Server: Express, Fastify, Hono, NestJS.
Teil IV · Kapitel 21Fallstricke im Überblick
Eine Sammlung der Stellen, an denen JavaScript sich anders verhält, als die Syntax vermuten lässt – zum Nachschlagen.
| Ausdruck | Ergebnis | Grund | Besser |
|---|---|---|---|
0.1 + 0.2 === 0.3 | false | IEEE-754-Binärdarstellung | Differenz gegen Number.EPSILON; Geld in Cent |
typeof null | 'object' | Fehler von 1995 | x === null |
NaN === NaN | false | so in IEEE-754 festgelegt | Number.isNaN(x) |
[10,9,1].sort() | [1,10,9] | vergleicht als Strings | toSorted((a,b) => a-b) |
[] == false | true | beide werden zu 0 | immer === |
0 || 'vorgabe' | 'vorgabe' | 0 ist falsy | 0 ?? 'vorgabe' |
for…in über ein Array | String-Indizes + geerbte | läuft über Schlüssel | for…of |
this in loser Methode | undefined | Aufrufort bestimmt this | bind, Arrow oder Klassenfeld |
| Arrow als Objektmethode | this falsch | Arrows haben kein eigenes this | Methodenkurzform |
forEach(async …) | wartet nicht | Rückgabe wird ignoriert | for…of + await oder Promise.all |
{...a} bei tiefen Objekten | geteilte Referenzen | Spread kopiert flach | structuredClone |
return + Zeilenumbruch | undefined | Automatic Semicolon Insertion | Wert in dieselbe Zeile |
new Date(2026, 8, 12) | September | Monate sind 0-basiert | ISO-Strings; künftig Temporal |
new Array(3) | 3 leere Plätze | Länge statt Inhalt | Array.from({length:3}, …) |
regex.test() mit /g | wechselt ab | lastIndex bleibt erhalten | Flag weglassen oder zurücksetzen |
parseInt('08') ohne Basis | uneinheitlich (alt) | Basis wurde geraten | parseInt(s, 10) |
21.1Empfehlungen für den Alltag
constals Standard,letbei Bedarf,varnie.===statt==;??statt||für Vorgabewerte.- Nicht-mutierende Methoden bevorzugen – Funktionen, die ihre Argumente verändern, sind schwer zu verfolgen.
- Objekte als Parameter, sobald es mehr als zwei sind:
zeichne({x, y})stattzeichne(x, y). - Früh zurückkehren statt tief zu verschachteln.
- Fehler mit
causeweiterreichen statt neu erfinden. - Asynchrones durchgängig mit
async/await– keine Mischung mit.then-Ketten. - ESLint und Prettier von Anfang an; diese Diskussionen muss man nicht führen.
- Typen ergänzen, sobald ein Projekt über ein Wochenende hinauswächst – TypeScript oder JSDoc.
Teil IV · Kapitel 22Wie es weitergeht
22.1Nachschlagen
- MDN Web Docs
- developer.mozilla.org – die maßgebliche Referenz, mit Kompatibilitätstabellen zu jeder Methode. Teilweise auf Deutsch, vollständig auf Englisch.
- ECMA-262
- tc39.es/ecma262 – die Spezifikation selbst. Sperrig, aber die letzte Instanz bei „warum verhält sich das so?“.
- TC39 Proposals
- github.com/tc39/proposals – was als Nächstes kommt, mit Stufenangabe.
- Can I use
- caniuse.com – Browserunterstützung auf einen Blick.
- Zum Vertiefen
- You Don't Know JS Yet (Kyle Simpson, frei auf GitHub), JavaScript for impatient programmers (Axel Rauschmayer, frei online), Eloquent JavaScript (Marijn Haverbeke, frei online).
22.2Was gerade entsteht
- Temporal – der Ersatz für
Date. Stage 3, wird bereits ausgeliefert. - Records und Tuples – unveränderliche Werttypen mit Inhaltsvergleich.
- Pattern Matching – ein
match-Ausdruck als Ersatz für langeswitch-Kaskaden. - Explicit Resource Management –
usingmit automatischem Aufräumen überSymbol.dispose. Stage 3. - Decorators – Metaprogrammierung an Klassen, in TypeScript bereits verfügbar.
- Type Annotations – TypeScript-artige Typen, die die Engine als Kommentare überliest.
22.3Ein Schlusswort
JavaScript ist in zehn Tagen entstanden und trägt die Spuren davon bis heute –
typeof null, die lose Gleichheit, die drei Arten, this zu verlieren. Weil
das Web nichts kaputt machen darf, wurde nie etwas entfernt; alles Neue kam hinzu.
Das Ergebnis ist eine Sprache mit einem eigenartigen Erdgeschoss und einem sehr gut gebauten
Oberbau. Wer die Altlasten kennt und meidet, arbeitet heute mit Modulen, Klassen, Destructuring,
async/await und Iterator-Helfern in einer Sprache, die auf Servern,
im Browser, auf dem Telefon und in der Kaffeemaschine läuft. Die Eigenheiten der ersten zehn Tage
zu kennen, ist dabei kein Selbstzweck – es ist der Unterschied zwischen Code, der
funktioniert, und Code, von dem man weiß, warum er funktioniert.
JavaScript im Detail · Stand ECMAScript 2025 · September 2026
Alle Codebeispiele sind für moderne Laufzeiten geschrieben (Node ≥ 20, aktuelle Browser).
Angaben zu Vorschlägen im Stage-3-Status können sich noch ändern.