Zum Inhalt springen
aviral gupta

// B4.4 · ca. 30 Min. · Einstieg

void, unknown, never und this

Nach dieser Lektion typisieren Sie, was eine Funktion zurückgibt, wenn nichts Brauchbares zurückkommt, nehmen beliebige Objekte oder Werte sicher an und deklarieren, was this ist.

Lektion 4 von 5 in B4 Mehr zu Funktionen

Danach können Sie

  • void als Rückgabetyp verwenden und erklären, warum ein void-Funktionstyp Funktionen annimmt, die einen Wert zurückgeben
  • In einer Signatur object, unknown, never oder einen Funktionstyp statt Object, Function und any wählen
  • Den Typ von this in einer Funktion deklarieren und dort, wo this zählt, einen Funktionsausdruck verwenden
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus JavaScript: Keine der beiden Funktionen gibt einen Wert zurück. Was gibt das aus?

    function noop() {
      return;
    }
    
    const values = [noop(), [1, 2].forEach((n) => n * 2)];
    console.log(values);
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. Beide Funktionen geben true zurück, wo void steht. Welche Zeile lehnt tsc ab?

    type VoidFunc = () => void;
    
    const f1: VoidFunc = () => true;
    
    function f2(): void {
      return true;
    }
  3. Üben · Aufgabe 3 von 7

    Ein Listener loggt, der andere pusht, und push gibt eine Zahl zurück. Ergänzen Sie den Rückgabetyp von Listener, sodass beide passen und das Programm got save, dann 1 ausgibt.

    type Listener = (event: string) => ____;
    
    const seen: string[] = [];
    const listeners: Listener[] = [(e) => console.log("got " + e), (e) => seen.push(e)];
    listeners.forEach((listener) => listener("save"));
    console.log(seen.length);
    type Listener = (event: string) => ;
  4. Üben · Aufgabe 4 von 7

    keys nimmt ein object, keys2 ein Object. Welche Zeile lehnt tsc ab?

    function keys(o: object): string[] {
      return Object.keys(o);
    }
    function keys2(o: Object): string[] {
      return Object.keys(o);
    }
    
    console.log(keys({ a: 1 }), keys([5]), keys(() => 1));
    console.log(keys2(42));
    console.log(keys(42));
  5. Üben · Aufgabe 5 von 7

    increment deklariert, was this sein muss. Welche Zeile lehnt tsc ab?

    const counter = {
      count: 0,
      increment(this: { count: number }) {
        this.count++;
        return this.count;
      },
    };
    console.log(counter.increment());
    const inc = counter.increment;
    inc();
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. greet wird von user gelöst und allein aufgerufen. Was passiert, wenn Sie npx tsc und dann node main.ts ausführen?

    const user = {
      name: "Ada",
      greet() {
        return "Hi, " + this.name;
      },
    };
    const greet = user.greet;
    console.log(user.greet());
    console.log(greet());
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Schreiben Sie einen Typ LineCallback für eine Funktion, die eine Zeile und ihren Index nimmt und void zurückgibt, und eachLine(text, callback), das ihn für jede Zeile des Textes aufruft. Übergeben Sie einen Callback, der jede Zeile in ein Array pusht: push gibt eine Zahl zurück, und das muss akzeptiert werden. Schreiben Sie readJson(text) mit dem Rückgabetyp unknown und prüfen Sie das Ergebnis, bevor Sie es verwenden. Zuletzt ein Objekt counter mit einer Methode count(this: { lines: number }, line: string), die nicht leere Zeilen zählt. 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

Admins, Einstellungen und Schlüssel

Das DB aus dem Handbook als Interface: filterUsers ruft seinen Callback mit einem User als this auf, und der Callback ist ein Funktionsausdruck, der this: User deklariert. forEach nimmt einen void-Callback, und die Zahl von push wird ignoriert. safeParse gibt unknown zurück, also prüft das Programm den Wert, bevor es theme liest. countKeys nimmt ein object. Führen Sie npx tsc aus, dann node main.ts. Ersetzen Sie danach den Callback durch () => this.admin und führen Sie tsc erneut aus.

main.ts

interface User {
  name: string;
  admin: boolean;
}

// filter is called with a User as this: declare it, as the Handbook's DB does.
interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const users: User[] = [
  { name: "Ada", admin: true },
  { name: "Grace", admin: false },
  { name: "Linus", admin: true },
];

const db: DB = {
  filterUsers(filter) {
    return users.filter((user) => filter.call(user));
  },
};

// A function expression, not an arrow: an arrow would not get this from the call.
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

// A void callback may return something: push returns a number, which forEach ignores.
const names: string[] = [];
admins.forEach((user) => names.push(user.name));
console.log("admins: " + names.join(", "));

// unknown: the caller must check the value before using it.
function safeParse(s: string): unknown {
  return JSON.parse(s);
}
const settings = safeParse('{"theme": "dark"}');
if (typeof settings === "object" && settings !== null && "theme" in settings) {
  console.log("theme: " + String(settings.theme));
}

// object accepts arrays and functions too, but no primitives.
function countKeys(value: object): number {
  return Object.keys(value).length;
}
console.log(countKeys({ a: 1, b: 2 }), countKeys(["x"]), countKeys(() => 0));

Ausführen mit

npx tsc
node main.ts

Ausgabe

admins: Ada, Linus
theme: dark
2 1 0
  • filter.call(user) ruft den Callback mit user als this auf, wie es der Typ (this: User) => boolean verspricht.
  • Mit () => this.admin meldet tsc error TS2532: Object is possibly 'undefined': Eine Arrow-Funktion nimmt this von außen, und außerhalb jeder Funktion in main.ts gibt es keines.
  • Ohne die Prüfungen wäre settings.theme error TS18046: 'settings' is of type 'unknown'.
  • Ein Array hat einen Schlüssel pro Element, und ein Funktionswert hat keine eigenen aufzählbaren Schlüssel, daher 1 und 0.
Ä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

Ein Event-Hub mit void-Listenern

Ein Listener bekommt eine Nachricht. Die beiden Listener unten pushen in history, und push gibt eine Zahl zurück, also meldet tsc für jeden von ihnen error TS2322: Type 'number' is not assignable to type 'undefined'. Ändern Sie Listener so, dass jeder Listener passt, egal was er zurückgibt. Geben Sie on den Rückgabetyp void. Lassen Sie dann emit zurückgeben, wie viele Listener es aufgerufen hat, mit dem Rückgabetyp number.

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

    Welchen Rückgabetyp verwendet das Handbook für einen Callback, dessen Ergebnis ignoriert wird?

  2. Hinweis 2

    export type Listener = (message: string) => void; dann wird die Zahl von push einfach ignoriert.

  3. Hinweis 3

    In emit nach dem forEach: return listeners.length; und schreiben Sie : number hinter die Parameterliste.

Eine Lösung zeigen

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

export type Listener = (message: string) => void;

const listeners: Listener[] = [];
export const history: string[] = [];

export function on(listener: Listener): void {
  listeners.push(listener);
}

export function emit(message: string): number {
  listeners.forEach((listener) => listener(message));
  return listeners.length;
}

on((message) => history.push(message));
on((message) => history.push(message.toUpperCase()));
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 Listener = (message: string) => undefined;

const listeners: Listener[] = [];
export const history: string[] = [];

export function on(listener: Listener) {
  listeners.push(listener);
}

export function emit(message: string) {
  listeners.forEach((listener) => listener(message));
}

on((message) => history.push(message));
on((message) => history.push(message.toUpperCase()));

main.test.ts

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {on, emit, history} from './main.ts';

test('emit ruft beide Listener auf und gibt 2 zurück', () => {
  const called = emit('hi');
  assert.equal(called, 2, `emit('hi') gab ${called} zurück`);
});

test('die Listener haben die Nachricht in history geschrieben', () => {
  assert.deepEqual(history.slice(0, 2), ['hi', 'HI'], `history beginnt mit ${JSON.stringify(history.slice(0, 2))}`);
});

test('ein später hinzugefügter Listener wird auch aufgerufen', () => {
  const got = [];
  on((message) => got.push(message));
  const called = emit('ok');
  assert.equal(called, 3, `nach einem dritten Listener gab emit ${called} zurück`);
  assert.deepEqual(got, ['ok'], `der neue Listener bekam ${JSON.stringify(got)}`);
});

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

Ein Zähler, der sein this behält

clickTwice löst increment vom Zähler und ruft es allein auf, also ist this undefined, und die Tests brechen mit einem TypeError ab. tsc bemerkt es nicht, weil increment this nicht deklariert. Deklarieren Sie es im Interface Counter: increment(this: Counter): number. Führen Sie die Tests aus: tsc meldet jetzt error TS2684 in clickTwice. Korrigieren Sie clickTwice so, dass es increment zweimal als Methode von counter aufruft und das zweite Ergebnis zurückgibt.

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

    this ist der erste Parameter in der Signatur, und er ist kein echter Parameter: increment(this: Counter): number;.

  2. Hinweis 2

    Das Objektliteral in makeCounter braucht keine Änderung: Es bekommt sein this vom Rückgabetyp Counter.

  3. Hinweis 3

    Rufen Sie die Methode mit ihrem Objekt auf: counter.increment(); dann return counter.increment();.

Eine Lösung zeigen

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

export interface Counter {
  count: number;
  step: number;
  increment(this: Counter): number;
}

export function makeCounter(step: number): Counter {
  return {
    count: 0,
    step,
    increment() {
      this.count += this.step;
      return this.count;
    },
  };
}

export function clickTwice(counter: Counter): number {
  counter.increment();
  return counter.increment();
}
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 interface Counter {
  count: number;
  step: number;
  increment(): number;
}

export function makeCounter(step: number): Counter {
  return {
    count: 0,
    step,
    increment() {
      this.count += this.step;
      return this.count;
    },
  };
}

export function clickTwice(counter: Counter): number {
  const click = counter.increment;
  click();
  return click();
}

main.test.ts

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

test('increment addiert die Schrittweite', () => {
  const counter = makeCounter(2);
  const result = counter.increment();
  assert.equal(result, 2, `makeCounter(2).increment() gab ${result} zurück`);
});

test('clickTwice gibt den Stand nach zwei Klicks zurück', () => {
  const result = clickTwice(makeCounter(5));
  assert.equal(result, 10, `clickTwice(makeCounter(5)) gab ${result} zurück`);
});

test('clickTwice ändert den übergebenen Zähler', () => {
  const counter = makeCounter(3);
  clickTwice(counter);
  assert.equal(counter.count, 6, `nach clickTwice ist count ${counter.count}`);
});

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

Einen Wert aus einer als void deklarierten Funktion zurückgeben

function save(name: string): void {
  console.log("saved " + name);
  return name.length;
}

Was tsc oder Node.js ausgibt

main.ts(3,3): error TS2322: Type 'number' is not assignable to type 'void'.

Warum, und die Lösung

Eine Funktion, deren eigener Rückgabetyp void ist, darf keinen Wert zurückgeben; das darf nur eine Funktion in einem void-Funktionstyp. Wenn Aufrufer die Länge brauchen, sagen Sie es: function save(name: string): number. Wenn nicht, löschen Sie die return-Anweisung.

Das Ergebnis eines void-Callbacks verwenden

function runLater(f: () => void) {
  return f();
}
const r = runLater(() => 42);
console.log(r.toFixed());

Was tsc oder Node.js ausgibt

main.ts(5,15): error TS2339: Property 'toFixed' does not exist on type 'void'.

Warum, und die Lösung

f hat den Typ () => void, also gilt alles, was es zurückgibt, als void, auch wenn diese Arrow-Funktion 42 zurückgibt. Genau das bedeutet void: Der Aufrufer verspricht, das Ergebnis nicht zu verwenden. Soll runLater das Ergebnis weitergeben, typisieren Sie den Callback damit: f: () => number, und geben Sie runLater den Rückgabetyp number.

Eine Arrow-Funktion, wo this deklariert ist

interface User {
  name: string;
  admin: boolean;
}

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const users: User[] = [
  { name: "Ada", admin: true },
  { name: "Grace", admin: false },
];

const db: DB = {
  filterUsers(filter) {
    return users.filter((user) => filter.call(user));
  },
};

const admins = db.filterUsers(() => this.admin);

Was tsc oder Node.js ausgibt

main.ts(21,37): error TS2532: Object is possibly 'undefined'.

Warum, und die Lösung

Eine Arrow-Funktion hat kein eigenes this; sie verwendet das this ihrer Umgebung, und außerhalb jeder Funktion in main.ts gibt es keines. filterUsers setzt this für seinen Callback nur, wenn er ein Funktionsausdruck ist. Das Handbook: „you need to use function and not arrow functions“. Schreiben Sie db.filterUsers(function (this: User) { return this.admin; }).

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

void: Es kommt nichts Brauchbares zurück

Das Handbook: „void represents the return value of functions which don't return a value.“ void wird für eine Funktion ohne return-Anweisungen inferiert. Eine mit : void deklarierte Funktion darf nichts zurückgeben: return name.length darin ist error TS2322: Type 'number' is not assignable to type 'void'. Ein Funktionstyp ist großzügiger. () => void nimmt als Callback-Typ auch () => true an; der Wert wird ignoriert, und der Aufruf ergibt void, also ist if (f1()) error TS1345: An expression of type 'void' cannot be tested for truthiness. Deshalb kompiliert src.forEach((el) => dst.push(el)): push gibt eine Zahl zurück, und forEach erwartet void. Und void ist nicht undefined: const x: undefined = log("hi") ist error TS2322.

object, unknown und never statt Object, Function und any

object ist jeder Wert, der kein Primitive ist: Objekte, Arrays und Funktionen. function keys(o: object) lehnt keys(42) mit error TS2345 ab. Object mit großem O nimmt auch 42 an; das Handbook sagt: „object is not Object. Always use object!“ Der Typ Function nimmt jede Funktion an, aber ein Aufruf darüber gibt any zurück, also kompiliert f(1, 2, 3).toUpperCase() und scheitert zur Laufzeit; () => void ist sicherer. unknown aus B2.1 wirkt in beide Richtungen: Ein Parameter vom Typ unknown nimmt jeden Wert an, und ein Rückgabetyp unknown, wie bei safeParse(s: string): unknown, zwingt Aufrufer, den Wert erst zu prüfen. never aus B3.5 ist für eine Funktion, die nie zurückkehrt, wie fail(msg): never.

this deklarieren

In einem Objektliteral inferiert TypeScript this für eine Methode: In user.becomeAdmin ist this user. Aber eine Methode, die man vom Objekt löst, verliert es: const greet = user.greet; greet() kompiliert und bricht mit TypeError: Cannot read properties of undefined (reading 'name') ab. JavaScript verbietet einen Parameter namens this, also nutzt TypeScript diese Stelle für den Typ von this: increment(this: { count: number }). Aufrufer übergeben dafür nichts. Jetzt ist const inc = counter.increment; inc() error TS2684: The 'this' context of type 'void' is not assignable to method's 'this' of type '{ count: number; }'. Auch Callback-APIs deklarieren es: filterUsers(filter: (this: User) => boolean). Übergeben Sie dort einen Funktionsausdruck: Eine Arrow-Funktion hat kein eigenes this.

Quellen

Zuletzt geprüft am 4. Oktober 2026