Zum Inhalt springen
aviral gupta

// B3.3 · ca. 30 Min. · Einstieg

instanceof und Type Predicates

Nach dieser Lektion grenzen Sie mit instanceof ein, nutzen einen gefangenen Fehler sicher, sagen den Typ einer Variablen nach Zuweisungen und returns voraus und schreiben ein Type Predicate, das auch Arrays filtert.

Lektion 3 von 5 in B3 Eingrenzung (Narrowing)

Danach können Sie

  • Mit instanceof eingrenzen, auch den unknown-Wert in einer catch-Klausel
  • Den Typ einer Variablen nach Zuweisungen, returns und throws vorhersagen (Kontrollflussanalyse)
  • Type Predicates wie pet is Fish schreiben und Arrays damit filtern
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus JavaScript: Welche dieser Ausdrücke sind wahr? Wählen Sie alle zutreffenden.

    Wählen Sie alle zutreffenden aus.

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: values hat den Typ (number | null)[]. Was passiert bei npx tsc und node main.ts?

    const values = [3, null, 7, null];
    const numbers = values.filter((v) => v !== null);
    console.log(numbers.map((n) => n * 2));
  3. Üben · Aufgabe 3 von 7

    Setzen Sie den Rückgabetyp von isFish ein, damit tsc move annimmt und das Programm swims und flies ausgibt.

    type Fish = { name: string; swim: () => string };
    type Bird = { name: string; fly: () => string };
    type Pet = Fish | Bird;
    
    function isFish(pet: Pet): ____ {
      return "swim" in pet;
    }
    
    function move(pet: Pet): string {
      return isFish(pet) ? pet.swim() : pet.fly();
    }
    
    console.log(move({ name: "Nemo", swim: () => "swims" }));
    console.log(move({ name: "Tweety", fly: () => "flies" }));
    function isFish(pet: Pet): {
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie mit den Typen unten jeder Situation den Typ zu, den die Variable an dieser Stelle hat.

    type Fish = { swim: () => void };
    type Bird = { fly: () => void };
    function isFish(pet: Fish | Bird): pet is Fish {
      return "swim" in pet;
    }
  5. Üben · Aufgabe 5 von 7

    Welche Zeilen lehnt tsc ab?

    type Id = string | number;
    let id: Id = "a7";
    id = 7;
    console.log(id.toFixed(1));
    id = "b9";
    console.log(id.toUpperCase());
    id = true;
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. tsc meldet für diese Datei keine Fehler. Was passiert beim Ausführen?

    function isNumber(x: unknown): x is number {
      return typeof x === "string";
    }
    
    const input: unknown = "42";
    if (isNumber(input)) {
      console.log(input.toFixed(1));
    }
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe. Ein Blog hat Entwürfe ({ title }) und veröffentlichte Beiträge ({ title, url }). Schreiben Sie ein Type Predicate isPublished und links(posts), das mit filter(isPublished) für jeden veröffentlichten Beitrag "title: url" liefert. Schreiben Sie dann countPosts(text), das JSON-Text parst und die Länge des Arrays liefert, sonst 0; prüfen Sie im catch mit instanceof SyntaxError, bevor Sie den Fehler lesen. Prüfen Sie es im Editor oder mit npx tsc und node main.ts.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Ein Ereignisprotokoll

Ein Protokoll enthält Datumswerte, Fehler und zwei Arten einfacher Objekte. instanceof wählt das Date und den Error (ein RangeError ist auch ein Error). Einfache Objekte haben keine Klasse, deshalb ist isClick ein Type Predicate; danach bleibt nur KeyPress übrig. Dasselbe Predicate gibt log.filter(isClick) den Typ Click[]. Führen Sie npx tsc und dann node main.ts aus. Ändern Sie danach den Rückgabetyp von isClick in boolean und führen Sie tsc erneut aus.

main.ts

// An event log: instanceof for built-in classes, a type predicate for plain objects.
type Click = { x: number; y: number };
type KeyPress = { key: string };
type LogEntry = Date | Error | Click | KeyPress;

function isClick(entry: LogEntry): entry is Click {
  return "x" in entry && "y" in entry;
}

function describe(entry: LogEntry): string {
  if (entry instanceof Date) return "time " + entry.toISOString().slice(11, 19);
  if (entry instanceof Error) return "error " + entry.message;
  if (isClick(entry)) return "click at " + entry.x + "," + entry.y;
  return "key " + entry.key; // only KeyPress is left
}

const log: LogEntry[] = [
  new Date(Date.UTC(2026, 9, 4, 9, 30, 0)),
  { x: 10, y: 20 },
  { key: "Enter" },
  new RangeError("volume above 100"),
  { x: 4, y: 2 },
];

for (const entry of log) {
  console.log(describe(entry));
}

const clicks = log.filter(isClick); // Click[]: the predicate narrows the array too
console.log(clicks.length + " clicks, x values: " + clicks.map((c) => c.x).join(", "));

Ausführen mit

npx tsc
node main.ts

Ausgabe

time 09:30:00
click at 10,20
key Enter
error volume above 100
click at 4,2
2 clicks, x values: 10, 4
  • Mit boolean statt entry is Click werden entry.x und entry.key abgelehnt, ebenso c.x in der letzten Zeile.
  • instanceof Error trifft auch den RangeError, weil RangeError Error erweitert.
  • Das Date wird mit toISOString in UTC ausgegeben, deshalb ist die Ausgabe auf jedem Rechner gleich.
  • filter(isClick) behält die Elemente, für die isClick true liefert, und das Predicate sagt tsc, was sie sind.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Beschreiben, was geworfen wurde

errorText bekommt den Wert, den eine catch-Klausel erhalten hat, sein Typ ist also unknown. tsc meldet error TS18046: 'error' is of type 'unknown'. Liefern Sie für einen Error (jeder Art) die message, für einen geworfenen String den Text selbst und für alles andere "unknown error".

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    unknown erlaubt nichts, bis Sie eingrenzen. Welche Prüfung sagt Ihnen, dass ein Wert ein Error ist?

  2. Hinweis 2

    error instanceof Error ist auch für einen TypeError wahr, weil TypeError Error erweitert.

  3. Hinweis 3

    Ergänzen Sie nach der instanceof-Prüfung typeof error === "string" für geworfenen Text und liefern Sie am Ende "unknown error".

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export function errorText(error: unknown): string {
  if (error instanceof Error) {
    return error.message;
  }
  if (typeof error === "string") {
    return error;
  }
  return "unknown error";
}
Auf dem eigenen Computer ausführen

Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.ts

export function errorText(error: unknown): string {
  return error.message;
}

main.test.ts

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {errorText} from './main.ts';

test('ein Error liefert seine message', () => {
  assert.equal(errorText(new Error('disk full')), 'disk full', 'errorText(new Error("disk full")) sollte "disk full" sein');
});

test('ein TypeError liefert ebenfalls seine message', () => {
  assert.equal(errorText(new TypeError('bad input')), 'bad input', 'errorText(new TypeError("bad input")) sollte "bad input" sein');
});

test('ein geworfener String kommt unverändert zurück', () => {
  assert.equal(errorText('timeout'), 'timeout', 'errorText("timeout") sollte "timeout" sein');
});

test('alles andere ergibt "unknown error"', () => {
  assert.equal(errorText(42), 'unknown error', 'errorText(42) sollte "unknown error" sein');
  assert.equal(errorText(null), 'unknown error', 'errorText(null) sollte "unknown error" sein');
});

package.json

{
  "type": "module"
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "nodenext",
    "lib": [
      "esnext",
      "dom"
    ],
    "types": [],
    "strict": true,
    "noEmit": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "allowImportingTsExtensions": true
  },
  "include": [
    "**/*.ts"
  ],
  "exclude": [
    "**/*.test.ts"
  ]
}

npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.

Programm ausführen:

npx tsc
node main.ts

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

npx tsc
node --test
learnrun.js herunterladen

Übung 2 von 2

Die Seiten der Bücher zählen

Ein Regal enthält Bücher und Filme. totalPages filtert die Bücher mit isBook, aber tsc meldet error TS2339: Property 'pages' does not exist on type 'Item', weil isBook nur sagt, dass es einen Boolean liefert. Ändern Sie eine Sache, damit filter(isBook) Book[] liefert und die Datei die Typprüfung besteht.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Der Rumpf von isBook ist in Ordnung. Das Problem ist, was sein Rückgabetyp tsc sagt.

  2. Hinweis 2

    Ein Type Predicate hat die Form parameterName is Type, mit dem Parameter dieser Funktion.

  3. Hinweis 3

    export function isBook(item: Item): item is Book {

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export type Book = { title: string; pages: number };
export type Film = { title: string; minutes: number };
export type Item = Book | Film;

export function isBook(item: Item): item is Book {
  return "pages" in item;
}

export function totalPages(items: Item[]): number {
  let total = 0;
  for (const book of items.filter(isBook)) {
    total += book.pages;
  }
  return total;
}
Auf dem eigenen Computer ausführen

Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.ts

export type Book = { title: string; pages: number };
export type Film = { title: string; minutes: number };
export type Item = Book | Film;

export function isBook(item: Item): boolean {
  return "pages" in item;
}

export function totalPages(items: Item[]): number {
  let total = 0;
  for (const book of items.filter(isBook)) {
    total += book.pages;
  }
  return total;
}

main.test.ts

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {isBook, totalPages} from './main.ts';

const book = {title: 'Dune', pages: 412};
const film = {title: 'Alien', minutes: 117};

test('isBook erkennt ein Buch und einen Film', () => {
  assert.equal(isBook(book), true, 'isBook(book) sollte true sein');
  assert.equal(isBook(film), false, 'isBook(film) sollte false sein');
});

test('totalPages addiert nur die Seiten der Bücher', () => {
  const shelf = [book, film, {title: 'Emma', pages: 474}];
  assert.equal(totalPages(shelf), 886, 'totalPages sollte 412 + 474 = 886 sein');
});

test('totalPages eines Regals ohne Bücher ist 0', () => {
  assert.equal(totalPages([film]), 0, 'totalPages([film]) sollte 0 sein');
});

package.json

{
  "type": "module"
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "nodenext",
    "lib": [
      "esnext",
      "dom"
    ],
    "types": [],
    "strict": true,
    "noEmit": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "allowImportingTsExtensions": true
  },
  "include": [
    "**/*.ts"
  ],
  "exclude": [
    "**/*.test.ts"
  ]
}

npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.

Programm ausführen:

npx tsc
node main.ts

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

npx tsc
node --test
learnrun.js herunterladen

Häufige Fehler

.message vom gefangenen Wert lesen

try {
  JSON.parse("{oops");
} catch (e) {
  console.log("Could not read the settings: " + e.message);
}

Was tsc oder Node.js ausgibt

main.ts(4,49): error TS18046: 'e' is of type 'unknown'.

Warum, und die Lösung

Geworfen werden kann alles, nicht nur Fehler, deshalb ist die catch-Variable mit strict unknown. Grenzen Sie sie zuerst ein: if (e instanceof Error) { console.log("Could not read the settings: " + e.message); }. Andere Werte behandeln Sie in einem else-Zweig oder machen sie mit String(e) zu Text.

Ein Type Guard, der nur boolean liefert

type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Pet = Fish | Bird;

function isFish(pet: Pet): boolean {
  return "swim" in pet;
}

function move(pet: Pet) {
  if (isFish(pet)) {
    pet.swim();
  }
}

move({ swim: () => console.log("swimming") });

Was tsc oder Node.js ausgibt

main.ts(11,9): error TS2339: Property 'swim' does not exist on type 'Pet'.

Warum, und die Lösung

Die Prüfung in isFish grenzt nur innerhalb von isFish ein. Für den Aufrufer sagt boolean nichts über pet, pet bleibt also ein Pet. Deklarieren Sie den Rückgabetyp als Type Predicate: function isFish(pet: Pet): pet is Fish. Dann grenzt if (isFish(pet)) pet auf Fish ein und den else-Zweig auf Bird.

instanceof mit einem primitiven Typnamen

function shout(value: unknown): string {
  if (value instanceof string) {
    return value.toUpperCase();
  }
  return "not text";
}

console.log(shout("hi"));

Was tsc oder Node.js ausgibt

main.ts(2,24): error TS2693: 'string' only refers to a type, but is being used as a value here.

Warum, und die Lösung

Rechts von instanceof muss eine Klasse stehen, die zur Laufzeit existiert. string ist nur ein Typ; Node.js bräche mit ReferenceError: string is not defined ab. Für primitive Werte gibt es keine Klasse zum Prüfen: Nehmen Sie typeof value === "string". instanceof String würde kompilieren, ist für "hi" aber falsch, denn das ist ein primitiver Wert und kein String-Objekt.

TypeScript im Browser: der Compiler von TypeScript 6.0.3, Apache-2.0, danach die eigene Engine Ihres Browsers. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

instanceof und der Fehler in catch

x instanceof Foo prüft, ob Foo.prototype in der Prototypkette von x liegt, und TypeScript grenzt damit ein: Bei x: Date | string ist x in if (x instanceof Date) ein Date und im else-Zweig ein String. Das funktioniert für Werte, die mit new entstehen: Date, Map, Error und seine Unterklassen (ein RangeError ist auch ein Error). Am häufigsten braucht man es in catch. Mit strict hat die Variable in catch (e) den Typ unknown, denn geworfen werden kann jeder Wert; if (e instanceof Error) macht e.message sicher. Rechts muss eine Klasse stehen, ein Wert: x instanceof string ist error TS2693, weil string nur ein Typ ist. Für primitive Werte nehmen Sie typeof.

Zuweisungen und Kontrollflussanalyse

Eine Variable hat einen deklarierten Typ und an jeder Stelle einen beobachteten Typ. Mit type Id = string | number und let id: Id = "a7" ist id ein String, bis id = 7 ihn zur Zahl macht: Dann kompiliert id.toFixed(1), id.toUpperCase() aber nicht. Die Zuweisbarkeit wird immer gegen den deklarierten Typ geprüft, also ist id = "b9" wieder in Ordnung und id = true error TS2322: Type 'boolean' is not assignable to type 'Id'. tsc verfolgt, welcher Code erreichbar ist: Nach einem Zweig, der returnt oder wirft, ist das Geprüfte für den Rest der Funktion weg. Wo Zweige zusammenlaufen, verschmelzen die Typen: ein String aus dem einen und eine Zahl aus dem anderen ergeben string | number.

Type Predicates: eigene Type Guards

Eine Funktion, deren Rückgabetyp ein Type Predicate ist, wirkt wie eine eingebaute Prüfung: function isFish(pet: Pet): pet is Fish { return "swim" in pet; }. Der Name vor is muss ein Parameter sein, sonst meldet tsc error TS1225. if (isFish(pet)) grenzt pet auf Fish ein, den else-Zweig auf Bird, und zoo.filter(isFish) liefert Fish[]. Mit dem Rückgabetyp boolean wird nichts eingegrenzt. tsc prüft nicht, ob der Rumpf die Wahrheit sagt: return "fly" in pet kompiliert auch. Seit TypeScript 5.5 leitet tsc für eine Funktion wie v => v !== null ohne Annotation ein Predicate ab, für v => !!v aber nicht: false könnte auch 0 bedeuten.

Quellen

Zuletzt geprüft am 4. Oktober 2026