🏛️Klassen, Module und Fehler

Klassen bündeln Daten und Verhalten, Module teilen Code in Dateien auf, und Fehlerbehandlung sorgt dafür, dass ein Problem nicht das ganze Programm abreißt.

🧬Klassen

class (ES2015) ist eine lesbarere Schreibweise für Konstruktorfunktionen mit Prototypen. Felder, private #-Elemente und static-Blöcke kamen mit ES2022 dazu.

class, Felder, Getter, static

1class Punkt {
2 x; y; // öffentliche Felder
3 static ursprung = new Punkt(0, 0);
4 constructor(x, y) { this.x = x; this.y = y; }
5 get laenge() { return Math.hypot(this.x, this.y); }
6 toString() { return `(${this.x}|${this.y})`; }
7}
8const p = new Punkt(3, 4);
9console.log(p.laenge, String(p), String(Punkt.ursprung));
10console.log(typeof Punkt);
Konsole (echt ausgeführt)
wird ausgeführt …

Klassen sind „syntaktischer Zucker“ über Konstruktorfunktionen und Prototypen – typeof liefert function.

Private Felder und Methoden (#)

1class Tresor {
2 #code;
3 constructor(code) { this.#code = code; }
4 #pruefe(v) { return v === this.#code; }
5 oeffnen(v) { return this.#pruefe(v) ? "offen" : "zu"; }
6 static istTresor(o) { return #code in o; }
7}
8const t = new Tresor("beispiel-passwort");
9console.log(t.oeffnen("falsch"), t.oeffnen("beispiel-passwort"));
10console.log(Object.keys(t), Tresor.istTresor(t));
Konsole (echt ausgeführt)
wird ausgeführt …

#-Felder (ES2022) sind wirklich privat – von außen nicht lesbar, auch nicht über Object.keys.

extends und super

1class Tier {
2 constructor(name) { this.name = name; }
3 laut() { return "…"; }
4 vorstellen() { return `${this.name} sagt ${this.laut()}`; }
5}
6class Hund extends Tier {
7 constructor(name) { super(name); this.art = "Hund"; }
8 laut() { return "Wuff"; }
9}
10const h = new Hund("Bello");
11console.log(h.vorstellen(), h instanceof Tier);
Konsole (echt ausgeführt)
wird ausgeführt …

Im Konstruktor einer abgeleiteten Klasse muss super() vor dem ersten Zugriff auf this stehen.

Was steckt dahinter?

h ──[[Prototype]]──▶ Hund.prototype ──▶ Tier.prototype ──▶ Object.prototype ──▶ null
│                    │ laut()             │ laut()             │ toString() …
│ name: "Bello"      │ constructor        │ vorstellen()
│ art:  "Hund"

Methoden liegen nicht im Objekt selbst, sondern auf dem Prototyp. h.vorstellen() wird entlang der Kette gesucht; this.laut() findet zuerst Hund.prototype.laut.

Prototypenkette interaktiv →

📦Module: import und export

ES-Module (ESM) sind der Standard in Browsern und Node.js. Jede Datei ist ein eigenes Modul mit eigenem Scope und läuft automatisch im Strict Mode.
1// mathe.js
2export const PI = 3.14159;
3export function flaeche(r) {
4 return PI * r * r;
5}
6export default class Rechner { /* … */ }
1// main.js
2import Rechner, { flaeche, PI as kreiszahl } from "./mathe.js";
3import * as mathe from "./mathe.js";
4
5console.log(flaeche(2), kreiszahl, mathe.PI);
6
7// dynamisch (lädt erst bei Bedarf, liefert ein Promise):
8const { flaeche: f } = await import("./mathe.js");

Eigenschaften von Modulen

  • Benannte Exporte (beliebig viele) und höchstens einen Default-Export.
  • Imports sind Live-Bindungen: ändert das exportierende Modul den Wert, sieht der Importeur ihn.
  • Imports werden gehoistet und vor dem Code ausgewertet; jedes Modul läuft nur einmal, egal wie oft es importiert wird.
  • Top-Level-await (ES2022) ist nur in Modulen erlaubt.
  • Import-Attribute (ES2025): import d from "./d.json" with { type: "json" }
1<!-- im Browser -->
2<script type="module" src="main.js"></script>
💡 CommonJS in Node.js
Ältere Node-Pakete nutzen require() und module.exports. Mit "type": "module" in der package.json oder der Endung .mjs nutzt Node ES-Module.
🔗 MDN: JavaScript-Module

🧯Fehlerbehandlung

throw bricht die aktuelle Funktion ab und wandert den Call Stack hinauf, bis ein catch den Fehler fängt. Ohne catch endet das Skript mit „Uncaught …“.

try / catch / finally

1function teile(a, b) {
2 if (b === 0) throw new RangeError("Division durch 0");
3 return a / b;
4}
5try {
6 console.log(teile(6, 3));
7 console.log(teile(1, 0));
8 console.log("wird nicht erreicht");
9} catch (e) {
10 console.log(e.name, "–", e.message);
11} finally {
12 console.log("finally läuft immer");
13}
Konsole (echt ausgeführt)
wird ausgeführt …

Eigene Fehlerklasse und cause

1class ValidierungsFehler extends Error {
2 constructor(feld, options) {
3 super(`Ungültig: ${feld}`, options);
4 this.name = "ValidierungsFehler";
5 }
6}
7try {
8 try { JSON.parse("{kaputt"); }
9 catch (e) { throw new ValidierungsFehler("email", { cause: e }); }
10} catch (e) {
11 console.log(e.name, e.message, e instanceof Error);
12 console.log("Ursache:", e.cause.name);
13}
Konsole (echt ausgeführt)
wird ausgeführt …

Mit der Option cause (ES2022) lässt sich die ursprüngliche Ausnahme anhängen.

Fehlertyptypischer Auslöser
ReferenceErrorVariable existiert nicht oder steht noch in der TDZ
TypeErrorundefined.x, Aufruf eines Nicht-Funktions-Werts, Zuweisung an const
SyntaxErrorTippfehler im Code, ungültiges JSON bei JSON.parse
RangeErrorungültige Array-Länge, zu tiefe Rekursion, toFixed(101)
AggregateErrormehrere Fehler auf einmal, z. B. von Promise.any

Fehler in asynchronem Code (Promises, async/await) funktionieren etwas anders – siehe Asynchron → Fehler.