Zum Inhalt springen
aviral gupta

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

typeof, Truthiness, Gleichheit und in

Nach dieser Lektion grenzen Sie eine Union mit typeof, einer Truthiness-Prüfung, einem Gleichheitsvergleich oder in ein und wissen, welche Werte jede Prüfung durchlässt oder verwirft.

Lektion 2 von 5 in B3 Eingrenzung (Narrowing)

Danach können Sie

  • Mit typeof eingrenzen und dabei seine acht Ergebnis-Strings kennen und wissen, dass "object" auch null und Arrays umfasst
  • Truthiness-Prüfungen und ?? einsetzen und erkennen, wo eine Truthiness-Prüfung 0 oder den leeren String fälschlich verwirft
  • Mit ===, == null, switch und dem in-Operator eingrenzen, auch bei optionalen Eigenschaften
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus JavaScript: if (value) { … } überspringt seinen Block für manche Werte. Für welche dieser Werte überspringt es ihn? Wählen Sie alle zutreffenden.

    Wählen Sie alle zutreffenden aus.

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: tsc meldet für diese Datei keine Fehler. Was gibt node main.ts aus?

    function scoreLabel(score: number | null): string {
      if (score) {
        return "Score: " + score;
      }
      return "Not played yet";
    }
    
    console.log(scoreLabel(0));
  3. Üben · Aufgabe 3 von 7

    Setzen Sie den String ein, damit tsc die Funktion annimmt und das Programm 1.50 EUR ausgibt.

    function format(value: string | number): string {
      if (typeof value === "____") {
        return value.toFixed(2);
      }
      return value.toUpperCase();
    }
    
    console.log(format(1.5), format("eur"));
    if (typeof value === "") {
  4. Üben · Aufgabe 4 von 7

    v hat den Typ Setting unten. Ordnen Sie jeder Prüfung den Typ zu, den v in ihrem if-Block hat.

    type Setting = string | number | string[] | null | undefined;
  5. Üben · Aufgabe 5 von 7

    Welche Zeile lehnt tsc ab?

    function show(v: string | string[] | Date): string {
      if (typeof v === "object") {
        return v.join(", ");
      }
      return v.toUpperCase();
    }
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Ein Mensch kann mit der richtigen Ausrüstung schwimmen oder fliegen, also ist beides optional. Welchen Typ hat animal im if?

    type Fish = { swim: () => void };
    type Bird = { fly: () => void };
    type Human = { swim?: () => void; fly?: () => void };
    
    function move(animal: Fish | Bird | Human) {
      if ("swim" in animal) {
        // what is animal here?
      }
    }
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe. Schreiben Sie describeInput(value: Input) für type Input = string | number | string[] | null | undefined. Die Funktion liefert "nothing" für null und undefined, "N items" für ein Array, "the number N" für eine Zahl, "empty text" für "" und "text …" für jeden anderen String. Achten Sie darauf, dass 0 und "" eigene Antworten bekommen, und geben Sie vier Aufrufe mit console.log aus. 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 Einstellungsbericht

show macht aus jeder Art von Einstellung Text. == null entfernt null und undefined mit einer Prüfung, dann wählt typeof string, number und boolean, und für join bleibt nur string[]. Achten Sie auf die Reihenfolge: 0, false und "" erreichen ihre eigenen Zweige, weil die erste Prüfung == null ist und keine Truthiness-Prüfung. connection unterscheidet mit in ein Gerät mit Kabel von einem mit WLAN; password ist optional und braucht deshalb noch eine Prüfung. Führen Sie npx tsc und dann node main.ts aus. Ändern Sie danach die erste Zeile von show in if (!value) und vergleichen Sie die Ausgabe.

main.ts

// A settings report. Each check narrows value for the code it guards.
type Setting = string | number | boolean | string[] | null | undefined;

function show(value: Setting): string {
  if (value == null) return "(not set)"; // == null removes null and undefined
  if (typeof value === "string") return '"' + value + '"';
  if (typeof value === "number") return value.toFixed(1);
  if (typeof value === "boolean") return value ? "on" : "off";
  return value.join(", "); // only string[] is left
}

type Wired = { port: number; cable: string };
type Wireless = { port: number; network: string; password?: string };

function connection(device: Wired | Wireless): string {
  if ("network" in device) {
    // here device is Wireless; password may still be missing
    return "wifi " + device.network + (device.password ? " (locked)" : " (open)");
  }
  return "cable " + device.cable; // here device is Wired
}

const settings: Setting[] = ["dark", 0, false, ["de", "en"], "", null, undefined];
for (const value of settings) {
  console.log(show(value));
}
console.log(connection({ port: 1, cable: "LAN" }));
console.log(connection({ port: 2, network: "home", password: "secret" }));
console.log(connection({ port: 3, network: "cafe" }));

Ausführen mit

npx tsc
node main.ts

Ausgabe

"dark"
0.0
off
de, en
""
(not set)
(not set)
cable LAN
wifi home (locked)
wifi cafe (open)
  • Mit if (!value) als erster Prüfung würden 0, false und "" alle (not set) ausgeben, und tsc würde trotzdem keine Fehler melden.
  • Nachdem die drei typeof-Prüfungen zurückgekehrt sind, kann value nur noch ein string[] sein, also kompiliert value.join ohne Prüfung.
  • Im wahren Zweig von "network" in device ist device Wireless, im Rest der Funktion Wired.
  • device.password ist optional, die in-Prüfung macht es also nicht definiert; der Ausdruck mit ? : prüft es.
Ä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

Die Null behalten

stockLabel soll nur dann "unknown" sagen, wenn der Bestand null oder undefined ist, und startVolume soll nur dann auf 50 zurückfallen, wenn nichts gespeichert wurde. Beide nutzen eine Truthiness-Prüfung, deshalb kommen ein Bestand von 0 und eine stummgeschaltete Lautstärke von 0 falsch heraus. tsc meldet nichts: Die Typen stimmen. Korrigieren Sie die beiden Prüfungen, sodass die Tests bestehen.

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

    Für welche Werte ist !count wahr? 0 gehört dazu, aber 0 ist ein echter Bestand.

  2. Hinweis 2

    count == null ist genau für null und undefined wahr und für nichts sonst.

  3. Hinweis 3

    saved || 50 fällt bei jedem falsy Wert zurück. Der Operator, der nur bei null und undefined zurückfällt, ist ??.

Eine Lösung zeigen

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

export function stockLabel(count: number | null | undefined): string {
  if (count == null) {
    return "unknown";
  }
  return count + " in stock";
}

export function startVolume(saved: number | undefined): number {
  return saved ?? 50;
}
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 stockLabel(count: number | null | undefined): string {
  if (!count) {
    return "unknown";
  }
  return count + " in stock";
}

export function startVolume(saved: number | undefined): number {
  return saved || 50;
}

main.test.ts

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

test('stockLabel(5) ist "5 in stock"', () => {
  assert.equal(stockLabel(5), '5 in stock', 'stockLabel(5) sollte "5 in stock" sein');
});

test('stockLabel(0) ist "0 in stock"', () => {
  assert.equal(stockLabel(0), '0 in stock', 'stockLabel(0) sollte "0 in stock" sein: 0 ist ein echter Bestand');
});

test('stockLabel(null) und stockLabel(undefined) sind "unknown"', () => {
  assert.equal(stockLabel(null), 'unknown', 'stockLabel(null) sollte "unknown" sein');
  assert.equal(stockLabel(undefined), 'unknown', 'stockLabel(undefined) sollte "unknown" sein');
});

test('startVolume(undefined) ist 50', () => {
  assert.equal(startVolume(undefined), 50, 'startVolume(undefined) sollte auf 50 zurückfallen');
});

test('startVolume(0) ist 0', () => {
  assert.equal(startVolume(0), 0, 'startVolume(0) sollte 0 bleiben: gespeichert wurde "stumm"');
});

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

Einen Kontakt erreichen

Ein Contact hat entweder eine E-Mail oder ein Telefon, und ein Telefon kann als Text oder als Zahl gespeichert sein. tsc meldet error TS2339: Property 'email' does not exist on type 'Contact'. Sorgen Sie dafür, dass reach "write to <email>" für eine E-Mail liefert, "call <phone>" für ein Telefon als Text und "call +<phone>" für ein Telefon als Zahl.

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

    Nur Email deklariert email, deshalb ist contact.email auf einem Contact nicht erlaubt. "email" in contact grenzt contact auf Email ein.

  2. Hinweis 2

    Nachdem dieses if zurückgekehrt ist, ist contact ein Phone. typeof contact.phone === "number" grenzt dann die Eigenschaft ein.

  3. Hinweis 3

    Drei returns: "write to " + contact.email, "call +" + contact.phone für eine Zahl und "call " + contact.phone für Text.

Eine Lösung zeigen

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

export type Email = { email: string };
export type Phone = { phone: string | number };
export type Contact = Email | Phone;

export function reach(contact: Contact): string {
  if ("email" in contact) {
    return "write to " + contact.email;
  }
  if (typeof contact.phone === "number") {
    return "call +" + contact.phone;
  }
  return "call " + contact.phone;
}
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 Email = { email: string };
export type Phone = { phone: string | number };
export type Contact = Email | Phone;

export function reach(contact: Contact): string {
  return "write to " + contact.email;
}

main.test.ts

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

test('ein E-Mail-Kontakt ergibt "write to …"', () => {
  assert.equal(reach({email: 'ada@example.com'}), 'write to ada@example.com', 'reach({email: "ada@example.com"}) sollte "write to ada@example.com" sein');
});

test('ein Telefon als Text ergibt "call …"', () => {
  assert.equal(reach({phone: '+49 30 1234567'}), 'call +49 30 1234567', 'reach({phone: "+49 30 1234567"}) sollte "call +49 30 1234567" sein');
});

test('ein Telefon als Zahl ergibt "call +…"', () => {
  assert.equal(reach({phone: 49301234567}), 'call +49301234567', 'reach({phone: 49301234567}) sollte "call +49301234567" 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

in auf einem Wert, der ein String sein kann

type Tags = string | string[];

function count(tags: Tags): number {
  if ("length" in tags) {
    return tags.length;
  }
  return 1;
}

console.log(count(["a", "b"]));

Was tsc oder Node.js ausgibt

main.ts(4,19): error TS2322: Type 'Tags' is not assignable to type 'object'.

Warum, und die Lösung

in funktioniert nur mit Objekten. tags kann ein String sein, und zur Laufzeit bräche count("news") mit TypeError: Cannot use 'in' operator to search for 'length' in news ab. Seit TypeScript 4.9 prüft tsc, dass rechts von in ein Objekt steht. Wählen Sie die Mitglieder mit einer Prüfung, die für jeden Wert funktioniert: if (Array.isArray(tags)) für das Array oder typeof tags === "string" für den Text.

undefined entfernt, null vergessen

function initial(name: string | null | undefined): string {
  if (name !== undefined) {
    return name.charAt(0);
  }
  return "?";
}

console.log(initial("Ada"));

Was tsc oder Node.js ausgibt

main.ts(3,12): error TS18047: 'name' is possibly 'null'.

Warum, und die Lösung

!== undefined entfernt nur undefined; im if ist null weiterhin möglich. Nutzen Sie die lockere Prüfung name != null, die null und undefined zusammen entfernt, oder name !== null && name !== undefined. Eine Truthiness-Prüfung, if (name), würde auch kompilieren, schickt aber auch den leeren String zu "?"; das ist nur in Ordnung, wenn Sie genau das wollen.

Eine optionale Methode nach einer in-Prüfung aufrufen

type Fish = { swim: () => void };
type Human = { name: string; swim?: () => void };

function dive(animal: Fish | Human) {
  if ("swim" in animal) {
    animal.swim();
  }
}

dive({ swim: () => console.log("splash") });

Was tsc oder Node.js ausgibt

main.ts(6,5): error TS2722: Cannot invoke an object which is possibly 'undefined'.

Warum, und die Lösung

Human deklariert swim als optional, deshalb bleibt Human im wahren Zweig der in-Prüfung, und dort kann swim undefined sein. Die in-Prüfung sagt Ihnen, dass die Eigenschaft deklariert ist, nicht, dass sie eine Funktion enthält. Prüfen Sie den Wert selbst: if (animal.swim) { animal.swim(); }, oder rufen Sie sie mit animal.swim?.() auf.

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

typeof: acht Antworten, und was "object" noch durchlässt

typeof liefert einen von acht Strings: "string", "number", "bigint", "boolean", "symbol", "undefined", "object" und "function". Der Vergleich mit einem davon ist ein Type Guard: in if (typeof v === "number") ist v eine Zahl, der else-Zweig bekommt die übrigen Mitglieder. typeof liefert nie "null" oder "array": null und Arrays ergeben beide "object". Für v: string | string[] | Date | null lässt typeof v === "object" also string[] | Date | null übrig. Ergänzen Sie v !== null, um null zu entfernen, und wählen Sie Arrays mit Array.isArray(v). typeof v === "function" wählt die Funktionsmitglieder. Ein Tippfehler wie "strng" ist error TS2367: typeof kann ihn nie liefern.

Truthiness: ein if verwirft mehr als null

Ein if nimmt jeden Wert an, nicht nur Booleans. Es behandelt false, 0, NaN, "", 0n, null und undefined als falsch und alles andere als wahr, auch "0", " " und []. TypeScript grenzt damit ein: in if (count) ist count: number | undefined eine Zahl. Der andere Zweig behält number | undefined, denn die Zahl kann 0 sein. Das ist die Falle: if (!count) return "unknown" antwortet auch für 0 mit "unknown", und tsc schweigt, weil der Code typkorrekt ist; nur die Logik stimmt nicht. Wenn 0 oder "" echte Werte sind, prüfen Sie, was Sie meinen: count == null, count !== undefined oder count ?? 50 statt count || 50. ?? greift nur bei null und undefined.

Gleichheit und in

=== und !== gegen einen Wert grenzen ebenfalls ein: nach if (v === null) return ist null weg. === undefined entfernt nur undefined; das lockere == null und != null entfernen null und undefined zusammen, der eine übliche Einsatz von ==. Ein switch grenzt jeden case genauso ein. "network" in device prüft, ob das Objekt diese Eigenschaft hat: Der wahre Zweig behält die Mitglieder, die sie deklarieren, ob Pflicht oder optional, der falsche Zweig die, bei denen sie optional ist oder fehlt. Eine optionale Eigenschaft steht also auf beiden Seiten und kann im wahren Zweig noch undefined sein. Rechts von in muss ein Objekt stehen, sonst meldet tsc error TS2322.

Quellen

Zuletzt geprüft am 4. Oktober 2026