ECMA-262SprachreferenzStand ES2025

JavaScript im Detail Die Sprache, ihre Laufzeit und alles dazwischen

Von der Syntax über das Typsystem und die Prototypenkette bis zur Event Loop: eine durchgehende Erklärung, wie JavaScript aufgebaut ist – und warum es sich an einigen Stellen so eigenwillig verhält.

SprachnormECMAScript 2025 (ECMA-262, 16. Ausgabe)
GremiumTC39, Ecma International
Kapitel22 in 4 Teilen
VoraussetzungGrundlegende Programmiererfahrung

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:

StufeBezeichnungBedeutung in der Praxis
0StrawpersonIdee, formlos eingebracht
1ProposalEin Champion arbeitet daran; Problem und Lösungsskizze stehen
2DraftFormale Spezifikationssprache, Syntax weitgehend festgelegt
3CandidateSpezifikation vollständig; Browser beginnen zu implementieren. Ab hier praxistauglich, aber noch änderbar
4FinishedZwei 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
  • ES2017async/await – asynchroner Code wird linear lesbar
  • ES2020?., ??, BigInt, dynamisches import(), Promise.allSettled
  • ES2022Private Klassenfelder #x, at(), Top-Level await, Error-Ursachen (cause)
  • ES2023Nicht-mutierende Array-Methoden: toSorted, toReversed, with, findLast
  • ES2024Object.groupBy, Promise.withResolvers, Array.fromAsync, RegExp-v-Flag
  • ES2025Iterator-Helfer (.map/.filter/.take direkt auf Iteratoren), Mengenoperationen auf Set, 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:

  1. 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“).
  2. Bytecode. Der Interpreter Ignition erzeugt Bytecode und führt ihn direkt aus. Das startet schnell, läuft aber vergleichsweise langsam.
  3. 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“.
  4. 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.

Blockierung des einzigen Threads
// 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.

CALL STACK main() verarbeite() summe() ▲ oben zuerst EVENT LOOP leer? MIKROTASKS .then() · await · queueMicrotask MutationObserver → komplett leergeräumt, immer zuerst MAKROTASKS setTimeout · setInterval I/O · UI-Ereignisse → genau eine pro Durchlauf dazwischen: Rendern (~60×/s)
Ein Durchlauf: Stack leerräumen → alle Mikrotasks abarbeiten (auch neu hinzugekommene) → ggf. rendern → eine Makrotask nehmen → von vorn.

Die Konsequenz dieser Regel lässt sich in fünf Zeilen zeigen:

Reihenfolge der Ausführung
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.

Zwei Fälle, in denen ASI das Falsche tut
// 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 = 1 ist gültig.
  • JavaScript unterscheidet Groß- und Kleinschreibung: wert und Wert sind verschiedene Variablen.
  • Konvention: camelCase für Variablen und Funktionen, PascalCase für Klassen und Konstruktoren, SCREAMING_SNAKE_CASE fü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 modeMit strict mode
x = 5 erzeugt still eine globale VariableReferenceError
Schreiben auf schreibgeschützte Eigenschaften schlägt still fehlTypeError
this in einem freien Funktionsaufruf ist das globale Objektthis ist undefined
Doppelte Parameternamen erlaubtSyntaxError
delete x auf Variablen erlaubtSyntaxError

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“.

EigenschaftSkriptModul
Einbindung<script src>, .cjs<script type="module">, .mjs, "type": "module"
Strict Modenur auf Anforderungimmer
Gültigkeitsbereich oberster Ebeneglobalmodul-lokal
import/exportnicht erlaubterlaubt
Ausführungszeitpunkt im Browsersofort, blockierendverzögert wie defer
Top-Level awaitneinja

3.5Kommentare und JSDoc

Kommentarformen
// 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

varletconst
GültigkeitsbereichFunktionBlockBlock
Neu zuweisbarjajanein
Erneut deklarierbarjaneinnein
Vor der DeklarationundefinedReferenceError (temporale tote Zone)
Legt globale Eigenschaft anja (im Skript)neinnein

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).

Drei Verhaltensweisen beim Hoisting
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.

Closure als gekapselter Zustand
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.

TyptypeofBeispielwerteAnmerkung
Number"number"42, 3.14, NaN, InfinityIEEE-754 Gleitkomma, 64 Bit
BigInt"bigint"9007199254740993nbeliebig 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.

Die drei Eigenheiten von Number
// 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.

Strings, Template Literals und Unicode
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

Symbol als kollisionsfreier Schlüssel
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

Kopie oder Referenz
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:

Die vollständige Liste der falsy-Werte
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.

Warum lose Gleichheit ein Problem ist
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.

ToPrimitive in Aktion
[] + [];        // ''                — 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

Umwandlung, die man beim Lesen erkennt
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

Rechnen
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.

Kurzschluss und Nullish Coalescing
'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

Sicherer Zugriff auf verschachtelte Strukturen
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

Dieselben drei Punkte, zwei Bedeutungen
// 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, ternär, switch
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

FormLäuft überEinsatz
for (let i = 0; …)Zählerwenn der Index gebraucht wird
for…ofWerte iterierbarer ObjekteStandard für Arrays, Strings, Map, Set
for…inSchlüssel inkl. geerbternur für einfache Objekte – nie für Arrays
while / do…whileBedingungunbekannte Anzahl Durchläufe
.forEach()Array-Elementewenn kein break nötig ist
for await…ofasynchrone IterierbareStreams, seitenweise APIs
for…of gegen for…in
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

Aus verschachtelten Schleifen springen
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

Vier Wege, dieselbe Funktion zu definieren
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

Defaults, Rest, Destructuring
// 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:

  1. newthis ist das neu erzeugte Objekt.
  2. call / apply / bindthis ist das explizit übergebene Objekt.
  3. Methodenaufruf obj.fn()this ist obj.
  4. Freier Aufruf fn()undefined im 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.

Der Verlust von this — und drei Auswege
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

Funktionen, die Funktionen verarbeiten
// 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

Objektliterale
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

Werte gezielt herausziehen
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

Die Object-Werkzeuge
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

Die drei Verfahren und ihre Grenzen
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

Auflösung über die Kette
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

Vollständige Klassensyntax
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

extends und super
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.

Erzeugen und Eigenheiten
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 OriginalGibt 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] = wertwith(i, wert)
fill, copyWithinmap, filter, flat, flatMap

12.3Die wichtigsten Methoden

Suchen, transformieren, zusammenfassen
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

ObjectMap
SchlüsseltypenString, Symbolbeliebig, auch Objekte
ReihenfolgeGanzzahlen zuerst, dann Einfügereihenfolgestrikt Einfügereihenfolge
GrößeObject.keys(o).lengthmap.size
Iterierbarnein (nur über Object.entries)ja, direkt
Geerbte Schlüsselja – Kollisionsgefahrnein
Viele Einfügungen/Löschungenlangsamerdafür ausgelegt
JSON-fähigjanein (Umweg nötig)
Map, Set und die Mengenoperationen aus ES2025
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

Grundform
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

TypWird geworfen bei
TypeErrorOperation auf einem Wert des falschen Typs – null.foo, undefined()
ReferenceErrorUnbekannter Bezeichner, Zugriff in der TDZ
SyntaxErrorUngültiger Quelltext, auch bei JSON.parse
RangeErrorWert außerhalb des Zulässigen – new Array(-1), Stack Overflow
AggregateErrorMehrere Fehler zugleich – von Promise.any
DOMExceptionBrowser-APIs, z. B. AbortError bei abgebrochenem fetch

13.3Eigene Fehler mit Ursachenkette

Kontext erhalten statt verschlucken
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

Die letzten Auffangnetze
// 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

Das Problem, das gelöst werden musste
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.
Erzeugen und verketten
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

MethodeErfüllt sich, wennErgebnis
Promise.allalle erfolgreichArray der Werte; bricht beim ersten Fehler sofort ab
Promise.allSettledalle fertig, egal wieArray aus {status, value} / {status, reason}
Promise.racedas erste beendet istdessen Ergebnis – auch ein Fehler
Promise.anydas erste erfolgreich istdessen Wert; sonst AggregateError
Parallel statt nacheinander
// 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.

Dieselbe Logik, linear geschrieben
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

Laufende Operationen beenden
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.

Von Hand implementiert
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.

Generatoren, auch unendliche
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

Seitenweise abrufen, ohne alles zu laden
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)

Ketten ohne Zwischenarrays
// 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

Export und Import
// ---- 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

Das ältere Node-Format
// ---- 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
ESMCommonJS
Ladenasynchron, statisch analysierbarsynchron, zur Laufzeit
BindungenliveWertkopie zum Zeitpunkt des require
Top-Level awaitjanein
Tree Shakingjaeingeschränkt
Im Browsernativnur mit Bundler
Aktivierung in Node"type": "module" oder .mjsStandard 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

Serialisieren mit Kontrolle
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

Date — und warum Temporal kommt
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

RegExp mit benannten Gruppen
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

Intl — eingebaut und oft übersehen
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

Häufig gebraucht
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.

Zugriffe abfangen
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

SymbolGreift ein bei
Symbol.iteratorfor…of, Spread, Destructuring
Symbol.asyncIteratorfor await…of
Symbol.toPrimitiveUmwandlung in Zahl oder String
Symbol.toStringTagObject.prototype.toString.call(x)
Symbol.hasInstanceinstanceof
Symbol.disposeusing-Deklarationen (Ressourcenfreigabe, Stage 3)

18.3Weitere Einblicke zur Laufzeit

Introspektion
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. setInterval ohne clearInterval, Event-Listener auf entfernten Elementen. Abhilfe: AbortController als gemeinsamer Ausschalter.
  • Unbegrenzte Caches. Eine Map, die nur wächst. Abhilfe: Obergrenze und Verdrängungsstrategie, oder WeakMap, 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:

Werkzeuge zur 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

  1. Weniger Arbeit tun. Der beste Algorithmus schlägt jede Mikro-Optimierung. Eine Map-Suche statt find in einer Schleife verwandelt O(n²) in O(n).
  2. Den Hauptthread freihalten. Lange Berechnungen in einen Web Worker verlagern oder in Teilstücke zerlegen. Alles über ~50 ms ist im Browser spürbar.
  3. Weniger laden. Code-Splitting über dynamisches import(), Tree Shaking, Komprimierung. Übertragene Bytes kosten mehr Zeit als die Ausführung.
  4. Layout-Thrashing vermeiden. Lesen (offsetHeight) und Schreiben von DOM-Eigenschaften trennen, statt sie abzuwechseln.
  5. 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:

package.json — die wichtigsten Felder
{
  "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:test ohne 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.

AusdruckErgebnisGrundBesser
0.1 + 0.2 === 0.3falseIEEE-754-BinärdarstellungDifferenz gegen Number.EPSILON; Geld in Cent
typeof null'object'Fehler von 1995x === null
NaN === NaNfalseso in IEEE-754 festgelegtNumber.isNaN(x)
[10,9,1].sort()[1,10,9]vergleicht als StringstoSorted((a,b) => a-b)
[] == falsetruebeide werden zu 0immer ===
0 || 'vorgabe''vorgabe'0 ist falsy0 ?? 'vorgabe'
for…in über ein ArrayString-Indizes + geerbteläuft über Schlüsselfor…of
this in loser MethodeundefinedAufrufort bestimmt thisbind, Arrow oder Klassenfeld
Arrow als Objektmethodethis falschArrows haben kein eigenes thisMethodenkurzform
forEach(async …)wartet nichtRückgabe wird ignoriertfor…of + await oder Promise.all
{...a} bei tiefen Objektengeteilte ReferenzenSpread kopiert flachstructuredClone
return + ZeilenumbruchundefinedAutomatic Semicolon InsertionWert in dieselbe Zeile
new Date(2026, 8, 12)SeptemberMonate sind 0-basiertISO-Strings; künftig Temporal
new Array(3)3 leere PlätzeLänge statt InhaltArray.from({length:3}, …)
regex.test() mit /gwechselt ablastIndex bleibt erhaltenFlag weglassen oder zurücksetzen
parseInt('08') ohne Basisuneinheitlich (alt)Basis wurde geratenparseInt(s, 10)

21.1Empfehlungen für den Alltag

  • const als Standard, let bei Bedarf, var nie.
  • === 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}) statt zeichne(x, y).
  • Früh zurückkehren statt tief zu verschachteln.
  • Fehler mit cause weiterreichen 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 lange switch-Kaskaden.
  • Explicit Resource Managementusing mit automatischem Aufräumen über Symbol.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.