Zum Inhalt springen

1. JavaScript und TypeScript

Zu Zen-Modus wechseln

Die Sprachen dieses Schuljahres sind JavaScript und sein typisierter Aufsatz TypeScript. Der Grund ist das Jahresthema: Client-Server-Anwendungen im Web. JavaScript ist die einzige Sprache, die im Browser läuft, und mit Node.js läuft sie ebenso auf dem Server; Client und Server lassen sich damit in einer Sprache entwickeln, und genau das nutzt das Framework Next.js, mit dem wir ab der Jahresmitte arbeiten.

Dieses Kapitel legt die Basis: Werkzeuge, die Grundkonstrukte der Sprache im Schnelldurchlauf und die modernen Sprachmittel, die im Rest des Jahres ständig gebraucht werden. Wer die Programmierkonzepte (Variablen, Verzweigungen, Schleifen, Funktionen, Datenstrukturen) bereits aus einer anderen Sprache beherrscht, liest den ersten Teil als kompakte Wiederholung in neuer Syntax. Danach kommt mit TypeScript etwas wirklich Neues: statische Typen als Werkzeug gegen eine ganze Fehlerklasse.

  1. Node.js installieren. Laden Sie die aktuelle LTS-Version von nodejs.org herunter (unter Linux über den Paketmanager). Node.js ist die JavaScript-Laufzeitumgebung außerhalb des Browsers; der Paketmanager npm wird mitinstalliert.

  2. Installation prüfen.

    Terminal-Fenster
    node --version # v22.x or newer
    npm --version
  3. Erstes Skript ausführen. Legen Sie eine Datei hello.js an und starten Sie sie im Terminal:

    const name = "Ada";
    console.log(`Hello, ${name}!`);
    Terminal-Fenster
    node hello.js
  4. VS Code: JavaScript- und TypeScript-Unterstützung ist eingebaut, es braucht keine Erweiterung. Empfehlenswert ist Prettier für einheitliche Formatierung.

console.log() schreibt ins Terminal (im Browser: in die Konsole der DevTools). Eine Tastatureingabe im Konsolenstil spielt in diesem Jahr kaum eine Rolle; Eingaben kommen aus Web-Formularen und HTTP-Anfragen.

Node bringt auch eine REPL mit: node ohne Dateinamen startet sie, Ausdrücke werden sofort ausgewertet, Strg+D beendet. Wie in jeder Sprache gilt: Sprachdetails klärt man am schnellsten durch Ausprobieren in der REPL.

Die folgende Übersicht ist bewusst dicht; sie dient als Referenz und Wiederholung. Jedes Konstrukt gehört einmal selbst getippt und ausgeführt (Aufgabe 01).

let attempts = 0; // variable, reassignable
const MAX_ATTEMPTS = 3; // constant, cannot be reassigned
const name = "Ada"; // const is the default; let only when needed
typeof 42 // "number" (no int/float distinction!)
typeof "text" // "string"
typeof true // "boolean"
typeof undefined // "undefined"

Auffälligkeiten: Es gibt nur einen Zahlentyp number (Gleitkomma; 7 / 2 ist 3.5, Ganzzahldivision schreibt sich Math.floor(7 / 2)). Neben null (“bewusst leer”) existiert undefined (“nie befüllt”). Als Standard gilt const; let nur, wenn die Variable tatsächlich neu zugewiesen wird. var ist Alt-Syntax und tabu.

1 === 1 // true -> always use === and !==
1 == "1" // true?! -> == converts types; never use it
"" || "default" // "default"
value ?? "default" // "default" only if value is null/undefined

JavaScript wandelt bei == Typen still um; die Regeln dafür sind schwer zu merken und eine häufige Fehlerquelle. Deshalb die eiserne Regel: immer === und !==. Der Operator ?? (Nullish Coalescing) liefert den Ersatzwert nur bei null/undefined, während || schon bei 0 oder "" zuschlägt; für Standardwerte ist ?? fast immer der richtige.

if (score >= 90) {
grade = 1;
} else if (score >= 75) {
grade = 2;
} else {
grade = 5;
}
for (const color of colors) { // iterate over elements
console.log(color);
}
while (attempts < MAX_ATTEMPTS) { attempts += 1; }
function rectangleArea(width, height) {
return width * height;
}

Blöcke werden mit geschweiften Klammern gebildet, die Einrückung ist Konvention (Code Linter erledigt sie). Namen schreiben sich in camelCase, Klassen in PascalCase, Konstanten in UPPER_CASE.

JavaScript kennt zwei Schreibweisen für Funktionen. Beide sind in Gebrauch, und die Wahl zwischen ihnen ist keine reine Geschmacksfrage - an einer Stelle verhalten sie sich unterschiedlich, und das kostet regelmäßig Zeit bei der Fehlersuche.

// 1. function declaration
function rectangleArea(width, height) {
return width * height;
}
// 2. arrow function, assigned to a constant
const double = (x) => x * 2; // implicit return, no braces
const half = (x) => { return x / 2; }; // with braces: return is mandatory
const greet = () => console.log("hi"); // no parameter: empty parentheses
const makePoint = (x, y) => ({ x, y }); // returning an object literal needs ( )

Drei Unterschiede sind der Rede wert:

  • Kurzform: Ohne geschweifte Klammern gibt eine Arrow Function ihren Ausdruck automatisch zurück. Das macht sie als Argument für map, filter oder reduce lesbar - dort steht oft nur ein einziger Ausdruck.
  • Verfügbarkeit im Code: Eine function-Deklaration ist in ihrem gesamten Gültigkeitsbereich verwendbar, auch oberhalb ihrer Definition (hoisting). Eine Arrow Function ist eine Zuweisung an eine Konstante und existiert erst ab der Zeile, in der sie steht.
  • Bedeutung von this: Das ist der eigentliche Grund für die Unterscheidung, siehe unten.

Eine function bekommt ihr this beim Aufruf zugewiesen - abhängig davon, wie sie gerufen wird. Eine Arrow Function hat kein eigenes this; sie übernimmt das ihrer Umgebung, festgelegt beim Schreiben.

const timer = {
label: "render",
seconds: 0,
// method: called as timer.report(), so this === timer
report() {
console.log(`${this.label}: ${this.seconds}s`);
},
startBroken() {
setInterval(function () {
this.seconds += 1; // this is NOT timer here - silently does nothing
}, 1000);
},
startWorking() {
setInterval(() => {
this.seconds += 1; // arrow keeps the surrounding this: timer
}, 1000);
},
};

startBroken wirft keinen Fehler und zählt trotzdem nichts - der Klassiker unter den this-Fehlern.

SituationWahlGrund
Benannte Funktion auf Modulebenefunctiongut lesbar, per hoisting überall im Modul verfügbar, im Stacktrace mit Namen
Argument für map/filter/reduce und andere RückruffunktionenArrowkurz, meist ein einziger Ausdruck
Rückruffunktion, die auf das umgebende this zugreift (Timer, Event-Handler in Klassen)Arrowübernimmt this der Umgebung
Methode in einem Objektliteralfunction beziehungsweise Kurzform report() { }braucht ihr eigenes this auf das Objekt
Konstruktorfunktion, GeneratorfunctionArrow Functions können das nicht

Als Faustregel für dieses Jahr: Auf Modulebene function, als Argument Arrow. In React-Komponenten (ab Kapitel 7) sind Ereignisbehandler durchgängig Arrow Functions, und Sie werden sehen, warum das dort die einzige praktikable Wahl ist.

const durations = [240, 180, 320];
durations.push(150); // append
durations[0]; // 240
durations.length; // 4
durations.includes(320); // true
// the big three: transform, filter, aggregate
const doubled = durations.map((d) => d * 2);
const long = durations.filter((d) => d >= 180);
const total = durations.reduce((sum, d) => sum + d, 0);

map, filter und reduce nehmen Funktionen als Argumente und ersetzen einen Großteil der klassischen Schleifen. Sie sind der Alltagsstil in JavaScript-Projekten und tauchen ab dem React-Kapitel in praktisch jedem Codebeispiel auf; investieren Sie hier Übungszeit.

const clip = {
filename: "intro.mp4",
durationSeconds: 90,
tags: ["opener", "draft"],
};
clip.filename; // dot access
clip["filename"]; // bracket access
clip.fps = 25; // add a property

Das Objektliteral ist der universelle Datencontainer von JavaScript, vergleichbar mit Dictionaries oder Maps anderer Sprachen, nur mit Punktzugriff. JSON, das Datenformat des ganzen Jahres, ist nichts anderes als diese Syntax in Textform.

Vier Sprachmittel, ohne die kein aktuelles JavaScript-Projekt auskommt:

Destructuring holt Werte aus einem Objekt oder Array heraus und legt sie in eigene Variablen.

// without destructuring
const filename = clip.filename;
const durationSeconds = clip.durationSeconds;
// with destructuring - same result, one line
const { filename, durationSeconds } = clip;

Die Variablennamen entsprechen dabei den Feldnamen; die Reihenfolge in den geschweiften Klammern spielt keine Rolle. Bei Arrays ist es umgekehrt: Dort entscheidet die Position, und die Namen sind frei wählbar.

const [first, second] = durations; // by position
const [, , third] = durations; // skip entries with commas

Vier Varianten, die im Jahresverlauf ständig vorkommen:

// 1. rename while unpacking (the field is called filename, the variable name)
const { filename: name } = clip;
console.log(name); // "intro.mp4"
// 2. default value, applied when the field is undefined
const { fps = 25 } = clip;
console.log(fps); // 25
// 3. unpack nested structures
const { metadata: { codec } } = clip;
console.log(codec); // "h264"
// 4. rest: collect everything not named above into one object
const { filename, ...rest } = clip; // rest has all remaining fields
console.log(rest); // { durationSeconds: 90, fps: 25, tags: ["opener", "draft"] }

Besonders häufig steht Destructuring direkt in der Parameterliste. Die Funktion nimmt dann weiterhin ein Objekt entgegen, benennt aber in der Signatur, welche Felder sie tatsächlich braucht:

function describe({ filename, fps = 25 }) {
return `${filename} @ ${fps} fps`;
}
describe(clip); // called with the whole object

Das hat zwei praktische Folgen: Der Aufrufer muss sich keine Parameterreihenfolge merken, und die Signatur dokumentiert die Abhängigkeiten der Funktion. Genau in dieser Form tauchen ab dem React-Kapitel die Props jeder Komponente auf.

Beide schreiben sich ..., arbeiten aber in entgegengesetzte Richtungen: Spread packt aus, Rest sammelt ein. Welches von beiden gemeint ist, verrät die Position - links von einem = oder in einer Parameterliste sammelt es, sonst packt es aus.

Spread schreibt den Inhalt eines Objekts oder Arrays in ein neues hinein:

const copy = { ...clip }; // shallow copy
const changed = { ...clip, fps: 50 }; // copy with one field overridden
const merged = { ...defaults, ...userSettings }; // later wins on conflict
const all = [...listA, ...listB]; // concatenate
const withExtra = [...durations, 150]; // append without touching the original
const maximum = Math.max(...durations); // spread as function arguments

Zwei Punkte, die man dabei wissen muss. Erstens gilt bei Objekten: Bei gleichem Feldnamen gewinnt der spätere Eintrag - deshalb steht fps: 50 hinter dem Spread und nicht davor. Zweitens ist die Kopie flach (shallow): Verschachtelte Objekte und Arrays werden nicht mitkopiert, sondern weiterhin geteilt.

const copy = { ...clip };
copy.filename = "outro.mp4"; // fine: the original keeps its filename
copy.tags.push("final"); // careful: clip.tags changed too - same array

Für verschachtelte Daten wird deshalb auf jeder betroffenen Ebene gespreadet ({ ...clip, tags: [...clip.tags, "final"] }) oder gleich structuredClone(clip) verwendet.

Rest ist die Gegenrichtung: Er sammelt, was übrig bleibt.

function sum(...values) { // collect all arguments into an array
return values.reduce((total, v) => total + v, 0);
}
sum(1, 2, 3); // values is [1, 2, 3]
function logRequest(method, ...details) { } // rest must come last

Das Muster { ...object, field: newValue } - Kopie mit geänderten Feldern statt Änderung am Original - ist im React-Kapitel die Standardtechnik für Zustandsänderungen. Der Grund dafür ist dort ein handfester: React erkennt Änderungen daran, dass ein neues Objekt vorliegt; eine Änderung am bestehenden bleibt unbemerkt. Es lohnt sich, das Muster jetzt schon zu verinnerlichen.

const city = order?.customer?.address?.city; // undefined instead of crash
const fps = clip.metadata?.fps ?? 25; // combined with ??

?. bricht die Kette ohne Fehler ab, sobald ein Glied null/undefined ist. Zusammen mit ?? ergibt das den robusten Zugriff auf verschachtelte, möglicherweise unvollständige Daten, wie sie von fremden APIs kommen.

timecode.js
export function framesToTimecode(frames, fps = 25) { /* ... */ }
export const DEFAULT_FPS = 25;
// main.js
import { framesToTimecode, DEFAULT_FPS } from "./timecode.js";

Jede Datei ist ein Modul; exportiert wird explizit, importiert namentlich. Damit Node die import-Syntax akzeptiert, gehört in die package.json des Projekts der Eintrag "type": "module" (oder die Datei heißt .mjs). Was es mit dieser package.json auf sich hat, behandelt das nächste Kapitel.

JavaScript prüft Typen erst zur Laufzeit, und ist dann (sehr, sehr) tolerant:

function totalDuration(clips) {
return clips.reduce((sum, clip) => sum + clip.durationSeconds, 0);
}
totalDuration([{ filename: "intro.mp4", durationSecond: 90 }]); // NaN

Der Tippfehler durationSecond (ohne s) fällt nicht auf: Der Zugriff liefert undefined, die Addition NaN, und das Programm läuft mit diesem Unsinn weiter. Solche Fehler kosten in wachsenden Projekten die meiste Debugging-Zeit, und genau gegen sie ist TypeScript gebaut: JavaScript plus Typangaben, geprüft vor dem Programmlauf vom Compiler tsc. TypeScript wird zu gewöhnlichem JavaScript übersetzt; im Browser und in Node läuft am Ende immer JavaScript.

function totalDuration(clips: Clip[]): number {
return clips.reduce((sum, clip) => sum + clip.durationSeconds, 0);
}
let attempts: number = 0; // explicit
let name = "Ada"; // inferred as string; annotation unnecessary

Die Syntax folgt dem Muster wert: Typ, der Rückgabetyp steht nach der Parameterliste. TypeScript inferiert Typen, wo es sie sehen kann; annotiert werden vor allem Funktionsparameter und Datenstrukturen, nicht jede lokale Variable.

interface Clip {
filename: string;
durationSeconds: number;
tags: string[];
fps?: number; // optional property
}
const intro: Clip = {
filename: "intro.mp4",
durationSeconds: 90,
tags: ["opener"],
};

Ein interface beschreibt die Form eines Objekts: welche Felder, welche Typen, was optional ist (?). Der Tippfehler von oben ist damit ein Compile-Fehler mit präziser Meldung (Property 'durationSeconds' is missing), noch bevor das Programm startet. Interfaces sind zugleich Dokumentation: Wer Clip liest, kennt den Datenvertrag, ohne Code zu lesen. Genau in dieser Rolle begleiten sie das ganze Jahr, als Verträge für API-Daten.

type RenderState = "queued" | "running" | "done" | "failed";
function label(state: RenderState): string {
if (state === "done") return "finished";
return state;
}
label("dne"); // compile error: not assignable to RenderState

Ein Union Type zählt die erlaubten Möglichkeiten auf. Der Klassiker sind Zustands- und Statusfelder: Statt beliebiger Strings gibt es vier gültige Werte, und jeder Tippfehler fliegt beim Kompilieren auf. Auch Kombinationen wie string | null (“String oder bewusst leer”) sind Union Types; TypeScript zwingt dann an jeder Verwendungsstelle zur Behandlung des null-Falls.

function firstOrNull<T>(items: T[]): T | null {
return items.length > 0 ? items[0] : null;
}
const clip = firstOrNull(clips); // type: Clip | null
const tag = firstOrNull(["a", "b"]); // type: string | null

Ein Generic ist ein Typ-Platzhalter: firstOrNull funktioniert für jede Elementart, und TypeScript rechnet den konkreten Typ am Aufrufort aus. In diesem Jahr lesen Sie Generics vor allem (Array<Clip>, ab dem nächsten Kapitel Promise<Response>); selbst schreiben werden Sie sie selten. Es genügt zu verstehen: Der Platzhalter in spitzen Klammern wird beim Verwenden durch einen echten Typ ersetzt.

Terminal-Fenster
npm install --save-dev typescript
npx tsc --init # creates tsconfig.json
npx tsc # type-check and compile to JavaScript
npx tsx timecode.ts # run directly during development (tsx package)

Im Alltag dieses Jahres übernimmt Next.js die komplette TypeScript-Werkzeugkette unsichtbar; tsc von Hand zu bedienen gehört trotzdem einmal erlebt, damit klar ist, was das Framework automatisiert. In der tsconfig.json bleibt die Voreinstellung "strict": true unangetastet: Der strenge Modus findet die meisten Fehler, und sich später an Strenge zu gewöhnen ist erfahrungsgemäß viel schwerer.

Ein kleines typisiertes Werkzeug, das die Bausteine kombiniert: Aus einer Liste von Videoclips soll die Gesamtdauer je Tag berechnet werden.

interface Clip {
filename: string;
durationSeconds: number;
tags: string[];
}
function durationByTag(clips: Clip[]): Map<string, number> {
const totals = new Map<string, number>();
for (const clip of clips) {
for (const tag of clip.tags) {
totals.set(tag, (totals.get(tag) ?? 0) + clip.durationSeconds);
}
}
return totals;
}
const clips: Clip[] = [
{ filename: "intro.mp4", durationSeconds: 90, tags: ["opener"] },
{ filename: "interview.mp4", durationSeconds: 845, tags: ["main", "voice"] },
{ filename: "outro.mp4", durationSeconds: 45, tags: ["opener"] },
];
for (const [tag, seconds] of durationByTag(clips)) {
console.log(`${tag}: ${seconds} s`);
}

Beachtenswert: das Interface als Datenvertrag, Map<string, number> als Generic in Verwendung, ?? 0 für den ersten Treffer eines Tags und Destructuring in der Ausgabeschleife. Jeder Tippfehler in einem Feldnamen wäre ein Compile-Fehler.

Zum Schluss zwei Typen, die auf den ersten Blick dasselbe bedeuten - “der Typ steht nicht fest” - und die sich in der Praxis so unterschiedlich verhalten, dass an ihnen die Qualität eines TypeScript-Projekts hängt.

any bedeutet nicht “irgendein Typ”, sondern “hör auf zu prüfen”. Auf einem any-Wert ist jeder Zugriff, jeder Aufruf und jede Zuweisung erlaubt:

const value: any = "not a number";
const total: number = value; // accepted - and wrong
console.log(value.toFixed(2)); // accepted - crashes at runtime
console.log(value.whatever.x); // accepted - crashes at runtime

Der Compiler schweigt zu allen drei Zeilen. Damit ist genau die Fehlerklasse zurück, gegen die TypeScript angetreten ist - und zwar unsichtbar, denn im Code steht ja ein Typ.

Verschärfend kommt hinzu: any ist ansteckend. Der Rückgabewert einer Funktion, die mit any arbeitet, ist wieder any, und so wandert der blinde Fleck durch das Programm. Ein einziges any an der falschen Stelle kann einen ganzen Modulpfad ungeprüft lassen.

Der any-Typ in TypeScript: ein Typ, der alle Werte akzeptiert
Abb. 1.1: Der any-Typ in TypeScript. Wenn Sie TypeScript Compiler Fehler mit any 'lösen', statt die Ursache zu beheben, sind sehr oft Fehler und Probleme zu erwarten.

unknown sagt dasselbe über den Wert aus, zieht aber die umgekehrte Konsequenz: Solange nicht geklärt ist, was der Wert ist, ist nichts damit erlaubt.

const raw: unknown = JSON.parse(text);
const name = raw.filename; // error TS18046: 'raw' is of type 'unknown'

Das ist keine Schikane, sondern die korrekte Aussage: Woher soll der Compiler wissen, dass in diesem JSON ein Feld filename steht? Der Weg zum Wert führt über eine Prüfung, und nach der Prüfung weiß auch der Compiler Bescheid (narrowing):

if (typeof raw === "object" && raw !== null && "filename" in raw) {
console.log((raw as { filename: string }).filename);
}

Lesbarer wird das mit einer eigenen Prüffunktion, deren Rückgabetyp value is Clip dem Compiler das Ergebnis der Prüfung mitteilt (type predicate):

function isClip(value: unknown): value is Clip {
return typeof value === "object" && value !== null
&& "filename" in value && typeof (value as Clip).filename === "string";
}
if (isClip(raw)) {
console.log(raw.filename.toUpperCase()); // raw is a Clip in here
}
anyunknown
Aussage”prüf nicht mehr""noch nicht geklärt”
Zugriff ohne PrüfungerlaubtCompile-Fehler
Zuweisung an andere Typenüberallhinnur nach Prüfung
Wirkung auf den Restansteckendeingesperrt

any verlagert das Problem in die Laufzeit, unknown erzwingt seine Lösung beim Schreiben. Deshalb gilt: An jeder Stelle, an der man any schreiben möchte, ist unknown die bessere erste Wahl.

Man sucht sich unknown selten aus - es kommt an den Rändern des Programms von selbst:

  • JSON.parse() liefert per Definition Daten unbekannter Form. Was ein Server schickt, weiß der Compiler nicht.
  • catch (error) ist unter "strict": true vom Typ unknown, denn in JavaScript kann alles geworfen werden, nicht nur Error. Daher der Pflichtgriff if (error instanceof Error), bevor error.message gelesen wird.
  • Fremde Pakete ohne Typinformationen liefern any; hier lohnt der Blick, ob es ein Paket @types/... gibt.
  • Formulardaten und HTTP-Anfragen kommen von außen und sind damit grundsätzlich unbekannt.

Genau hier liegt die Brücke zum Rest des Jahres: Die Frage “wie wird aus unbekannten Daten ein geprüfter, typisierter Wert?” beantwortet Kapitel 12 mit Schema-Validierung - ein Werkzeug, das die Prüfung schreibt und den passenden Typ gleich mitliefert.

Bisher haben wir Typen von Hand geschrieben. TypeScript kann Typen aber auch aus anderen Typen berechnen. Die dafür mitgelieferten Bausteine heißen Utility-Types: eingebaute Generics, die einen bestehenden Typ nehmen und einen abgewandelten zurückgeben. Der Vorteil ist immer derselbe - es gibt eine Quelle der Wahrheit, und die Abwandlungen bleiben automatisch synchron.

Als Ausgangspunkt dient ein Datenvertrag, wie er uns im Jahr ständig begegnen wird:

interface Item {
id: number;
name: string;
price: number; // in cents
available: number;
}

Partial<T> macht alle Felder optional - passend für ein Update, das nur einzelne Felder ändert:

// { id?: number; name?: string; price?: number; available?: number }
function updateItem(id: number, changes: Partial<Item>): void { /* ... */ }
updateItem(1, { price: 350 }); // only the price, the rest stays untouched

Omit<T, K> entfernt Felder, Pick<T, K> behält nur die genannten. Ein neues Item hat noch keine id - die vergibt der Server:

type NewItem = Omit<Item, "id">; // everything except id
type ItemPreview = Pick<Item, "id" | "name">; // only id and name

Readonly<T> friert alle Felder ein; eine spätere Zuweisung ist dann schon zur Compile-Zeit ein Fehler:

const frozen: Readonly<Item> = { id: 1, name: "Cola", price: 200, available: 5 };
frozen.price = 250; // error: cannot assign to 'price' because it is read-only

Record<K, V> beschreibt ein Objekt, das Schlüssel auf Werte abbildet - nützlich für Nachschlagetabellen:

// a lookup from item id to its ordered quantity
const quantities: Record<number, number> = { 1: 2, 4: 1 };

Diese Bausteine lassen sich kombinieren (Partial<Omit<Item, "id">> ist “alle Felder außer id, und die optional”) - genau diese Kombination werden wir im Kapitel Mehrschichtarchitektur für Repository-Updates wiedersehen. Das folgende Video führt die wichtigsten Utility-Types an Beispielen vor:

Abspielen
  1. Standard ist unknown, wenn der Typ tatsächlich offen ist - besonders an den Grenzen zu fremden Daten.

  2. Prüfen statt behaupten: aus unknown wird ein brauchbarer Typ durch typeof, instanceof, in oder eine eigene Prüffunktion.

  3. any nur als bewusste Notlösung, und dann mit einem Kommentar, der begründet, warum hier nicht geprüft wird. Ein unkommentiertes any ist im Review eine Anmerkung wert.

  4. Nie any gegen eine Fehlermeldung einsetzen. Der Compiler meldet an dieser Stelle etwas Richtiges; any löscht die Meldung, nicht das Problem.

  1. == statt ===: stille Typumwandlung mit absurden Ergebnissen; immer ===/!==.
  2. undefined statt Fehler: Der Zugriff auf ein nicht existierendes Feld liefert undefined und knallt erst später. In TypeScript wird daraus ein Compile-Fehler; einer der Hauptgründe für die Sprache.
  3. const verhindert Neuzuweisung, nicht Veränderung: const list = [] erlaubt list.push(1), aber nicht list = [1].
  4. Nur ein Zahlentyp: 0.1 + 0.2 === 0.3 ist false (Gleitkomma); Geldbeträge rechnet man in Cent.
  5. Falsches this in einer Rückruffunktion: Eine als function geschriebene Rückruffunktion bringt ihr eigenes this mit und greift damit ins Leere - ohne Fehlermeldung. Arrow Function verwenden.
  6. Vergessenes "type": "module": import schlägt in Node mit Cannot use import statement outside a module fehl.

Lernergebnisse: Was Sie nach diesem Kapitel können sollten

Abschnitt betitelt „Lernergebnisse: Was Sie nach diesem Kapitel können sollten“

Nach Abschluss dieses Kapitels sollten Schülerinnen und Schüler in der Lage sein:

  • Anwenden: Node.js einrichten und JavaScript-Dateien ausführen.
  • Anwenden: die Grundkonstrukte (Variablen, Kontrollstrukturen, Funktionen, Arrays, Objekte) sowie map/filter/reduce sicher einsetzen.
  • Anwenden: moderne Sprachmittel (Destructuring, Spread, ?., ??, Module) lesen und schreiben.
  • Erklären: den Nutzen statischer Typen erklären und die Fehlerklasse benennen, die TypeScript vor dem Programmlauf abfängt.
  • Anwenden: Datenverträge als interface definieren, Funktionen annotieren und Union Types für Zustandsfelder einsetzen.
  • Nennen: die Rolle von Generics beim Lesen von Typen wie Array<T> und Promise<T> nennen.
  • Beurteilen: any und unknown unterscheiden, den Umgang mit Daten unbekannter Form begründen und den Einsatz von any und as im eigenen wie im fremden Code kritisch bewerten.
  • Aufgabe 01 - TypeScript-Spickzettel
  • Aufgabe 02 - Typen retten den Tag
  • Aufgabe 03 - TypeScript-Katas