Zum Inhalt springen
aviral gupta

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

Funktionstyp-Ausdrücke und Aufrufsignaturen

Nach dieser Lektion schreiben und benennen Sie den Typ einer Funktion, erkennen, welche Funktionen dazu passen, und typisieren eine Funktion mit Properties oder einen Konstruktor.

Lektion 1 von 5 in B4 Mehr zu Funktionen

Anfang des Moduls

Danach können Sie

  • Einen Funktionstyp-Ausdruck mit benannten Parametern schreiben und mit einem Typalias benennen
  • Kontextuelle Typisierung nutzen und erkennen, welche Funktionen zu einem Funktionstyp passen
  • Ein aufrufbares Objekt mit einer Aufrufsignatur und einen Konstruktor mit einer Konstruktsignatur beschreiben
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus JavaScript: Funktionen sind Werte, die Sie anderen Funktionen übergeben können. Was gibt das aus?

    function twice(fn, x) {
      return fn(fn(x));
    }
    
    const addThree = (n) => n + 3;
    console.log(twice(addThree, 1));
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. Handler soll eine Funktion sein, die einen String nimmt. Was meldet tsc?

    type Handler = (string) => void;
    
    const shout: Handler = (text) => console.log(text.toUpperCase());
  3. Üben · Aufgabe 3 von 7

    Setzen Sie ein, was zwischen Parameterliste und Rückgabetyp steht, damit Formatter eine Funktion von number nach string ist und das Programm 4.50 EUR ausgibt.

    type Formatter = (value: number) ____ string;
    
    const euro: Formatter = (value) => value.toFixed(2) + " EUR";
    console.log(euro(4.5));
    type Formatter = (value: number) string;
  4. Üben · Aufgabe 4 von 7

    Vier Funktionen werden als Logger gespeichert. Welche Zeile lehnt tsc ab?

    type Logger = (message: string, level: number) => void;
    
    const a: Logger = (message, level) => console.log(level, message);
    const b: Logger = (message) => console.log(message);
    const c: Logger = () => console.log("log");
    const d: Logger = (message: string, level: number, time: number) => console.log(time);
  5. Üben · Aufgabe 5 von 7

    Ordnen Sie jedem Typ zu, was er beschreibt.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Drei verschiedene Funktionen landen in einem Check[]: Ihre Parameter heißen anders oder fehlen ganz. Nimmt tsc das an, und was gibt node main.ts aus?

    type Check = (value: number) => boolean;
    
    function isBig(amount: number): boolean {
      return amount > 100;
    }
    
    const checks: Check[] = [isBig, (n) => n % 2 === 0, () => true];
    console.log(checks.map((check) => check(150)).join(" "));
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Ein Formular prüft jedes Feld mit Regeln. Schreiben Sie einen Typ Rule mit einer Aufrufsignatur (sie nimmt den Wert als String und gibt einen boolean zurück) und einer Property message. Schreiben Sie zwei Regeln als Funktionsdeklarationen, required und short (höchstens 10 Zeichen), und geben Sie jeder eine message. Benennen Sie dann einen Funktionstyp Report für (field, problems) und schreiben Sie check(field, value, rules, report), das die messages der verletzten Regeln sammelt. Übergeben Sie als Report eine Pfeilfunktion, ohne ihre Parameter zu annotieren. 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 Bezahlvorgang mit typisierten Schritten

Ein Preis in Cent durchläuft eine Liste von Schritten, jeder ein PriceStep. Der Rabatt ist zugleich ein Discount: eine Funktion mit einem label, beschrieben durch eine Aufrufsignatur. addShipping und log bekommen ihre Parametertypen von dem Typ, in dem sie gespeichert werden, und log hat weniger Parameter als Logger, was erlaubt ist. Führen Sie npx tsc aus, dann node main.ts. Löschen Sie danach die Zeile tenPercentOff.label = "10% off" und führen Sie tsc erneut aus.

main.ts

// A checkout: a price in cents goes through a list of steps.
type PriceStep = (cents: number) => number;

// A discount is a step with a label: a call signature plus a property.
type Discount = {
  label: string;
  (cents: number): number;
};

function tenPercentOff(cents: number): number {
  return Math.round(cents * 0.9);
}
tenPercentOff.label = "10% off";

const addShipping: PriceStep = (cents) => cents + 490; // cents is a number, from PriceStep

type Logger = (message: string, step: number) => void;
const log: Logger = (message) => console.log("  " + message); // fewer parameters fit

function euro(cents: number): string {
  return (cents / 100).toFixed(2) + " EUR";
}

function checkout(cents: number, steps: PriceStep[], logger: Logger): number {
  let total = cents;
  steps.forEach((step, i) => {
    total = step(total);
    logger("after step " + (i + 1) + ": " + euro(total), i + 1);
  });
  return total;
}

function describe(d: Discount, cents: number): string {
  return d.label + " on " + euro(cents) + " saves " + euro(cents - d(cents));
}

console.log(describe(tenPercentOff, 4999));
console.log("checkout:");
const total = checkout(4999, [tenPercentOff, addShipping], log);
console.log("total: " + euro(total));

Ausführen mit

npx tsc
node main.ts

Ausgabe

10% off on 49.99 EUR saves 5.00 EUR
checkout:
  after step 1: 44.99 EUR
  after step 2: 49.89 EUR
total: 49.89 EUR
  • Ohne die label-Zeile meldet tsc den Aufruf describe(tenPercentOff, 4999): tenPercentOff ist dann kein Discount mehr, wohl aber noch ein PriceStep.
  • tenPercentOff ist sowohl ein Discount als auch ein PriceStep: Im Array steps zählt nur seine Aufrufsignatur.
  • checkout ruft logger mit zwei Argumenten auf; log verwendet nur das erste, und JavaScript ignoriert das zweite.
  • forEach ist genauso typisiert: Sein Callback (step, i) bekommt seine Typen vom Array, wie in B2.2.
Ä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

Den Funktionstyp benennen

countWhere zählt die Elemente, für die test true zurückgibt, und isEven ist ein solcher Test. tsc meldet error TS7006 für test und für n. Schreiben Sie einen exportierten Typalias NumberTest für eine Funktion, die eine Zahl nimmt und einen boolean zurückgibt, und verwenden Sie ihn für den Parameter test und für isEven, damit n seinen Typ von NumberTest bekommt. Geben Sie countWhere den Rückgabetyp number. Führen Sie die Tests aus: Der erste ist die Typprüfung.

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

    Ein Funktionstyp wird wie eine Pfeilfunktion geschrieben: (Parameter: Typ) => Rückgabetyp.

  2. Hinweis 2

    export type NumberTest = (value: number) => boolean; dann test: NumberTest.

  3. Hinweis 3

    Bei isEven annotieren Sie die const, nicht die Pfeilfunktion: export const isEven: NumberTest = (n) => n % 2 === 0;

Eine Lösung zeigen

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

export type NumberTest = (value: number) => boolean;

export function countWhere(items: number[], test: NumberTest): number {
  let count = 0;
  for (const item of items) {
    if (test(item)) count++;
  }
  return count;
}

export const isEven: NumberTest = (n) => n % 2 === 0;
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 countWhere(items: number[], test) {
  let count = 0;
  for (const item of items) {
    if (test(item)) count++;
  }
  return count;
}

export const isEven = (n) => n % 2 === 0;

main.test.ts

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

test('isEven unterscheidet gerade von ungeraden Zahlen', () => {
  assert.equal(isEven(4), true, 'isEven(4) sollte true sein');
  assert.equal(isEven(7), false, 'isEven(7) sollte false sein');
});

test('countWhere zählt die geraden Zahlen in [1, 2, 3, 4]', () => {
  assert.equal(countWhere([1, 2, 3, 4], isEven), 2, 'countWhere([1, 2, 3, 4], isEven) sollte 2 sein');
});

test('countWhere funktioniert mit einer Pfeilfunktion als Test', () => {
  assert.equal(countWhere([5, 12, 20], (n) => n > 10), 2, 'countWhere([5, 12, 20], (n) => n > 10) sollte 2 sein');
  assert.equal(countWhere([], isEven), 0, 'countWhere einer leeren Liste 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

Übung 2 von 2

Ein Zähler mit Summe

Ein Tally ist eine Funktion, die einen Betrag addiert und die neue Summe zurückgibt, und sie hat außerdem eine Property total. makeTally hält die Summe stattdessen in einer lokalen Variablen, deshalb meldet tsc error TS2741: Property 'total' is missing in type '(amount: number) => number' but required in type 'Tally'. Lassen Sie den Typ Tally, wie er ist. Ändern Sie makeTally so, dass die zurückgegebene Funktion eine Property total hat, die bei 0 beginnt und mit jedem Aufruf wächst.

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

    Eine Funktion kann in JavaScript Properties haben. TypeScript erlaubt, einer const-Funktion direkt nach ihrer Deklaration eine hinzuzufügen.

  2. Hinweis 2

    Schreiben Sie add.total = 0; nach der Pfeilfunktion und vor return add;.

  3. Hinweis 3

    Verwenden Sie in add statt der lokalen Variablen add.total: add.total += amount; return add.total;

Eine Lösung zeigen

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

export type Tally = {
  total: number;
  (amount: number): number;
};

export function makeTally(): Tally {
  const add = (amount: number): number => {
    add.total += amount;
    return add.total;
  };
  add.total = 0;
  return add;
}
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 Tally = {
  total: number;
  (amount: number): number;
};

export function makeTally(): Tally {
  let total = 0;
  const add = (amount: number): number => {
    total += amount;
    return total;
  };
  return add;
}

main.test.ts

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

test('ein neuer Zähler beginnt bei 0', () => {
  const tally = makeTally();
  assert.equal(tally.total, 0, `total eines neuen Zählers ist ${tally.total}`);
});

test('ein Aufruf addiert zur Summe', () => {
  const tally = makeTally();
  tally(5);
  const result = tally(3);
  assert.equal(result, 8, `tally(5) und dann tally(3) gab ${result} zurück`);
  assert.equal(tally.total, 8, `nach tally(5) und tally(3) ist total ${tally.total}`);
});

test('zwei Zähler zählen getrennt', () => {
  const a = makeTally();
  const b = makeTally();
  a(10);
  assert.equal(b.total, 0, `nach a(10) ist total von b ${b.total}`);
});

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

Ein Parametertyp ohne Parameternamen

type Handler = (string) => void;

const shout: Handler = (text) => console.log(text.toUpperCase());
shout("hello");

Was tsc oder Node.js ausgibt

main.ts(1,17): error TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?

Warum, und die Lösung

In einem Funktionstyp braucht jeder Parameter einen Namen, also wird string als Name eines Parameters vom Typ any gelesen. Schreiben Sie type Handler = (text: string) => void. Der Name dient nur dem Lesen: Eine Funktion, deren Parameter anders heißt, passt trotzdem.

Ein Pfeil in einem Objekttyp

type Discount = {
  label: string;
  (cents: number) => number;
};

function tenOff(cents: number): number {
  return Math.round(cents * 0.9);
}
tenOff.label = "10% off";

const d: Discount = tenOff;
console.log(d.label, d(2000));

Was tsc oder Node.js ausgibt

main.ts(3,19): error TS1005: ':' expected.

Warum, und die Lösung

In einem Objekttyp setzt eine Aufrufsignatur einen Doppelpunkt zwischen Parameterliste und Rückgabetyp: (cents: number): number;. Der Pfeil gehört zu einem eigenständigen Funktionstyp-Ausdruck wie type PriceStep = (cents: number) => number.

Ein Callback, der ein Argument braucht, das er nie bekommt

type OnSave = (name: string) => void;

const onSave: OnSave = (name: string, when: number) => console.log(name, when);
onSave("report.pdf");

Was tsc oder Node.js ausgibt

main.ts(3,7): error TS2322: Type '(name: string, when: number) => void' is not assignable to type 'OnSave'.

Warum, und die Lösung

Wer ein OnSave aufruft, übergibt ein Argument, also wäre when immer undefined; die zweite Zeile der Meldung sagt Target signature provides too few arguments. Lassen Sie den Parameter weg, oder, wenn der Aufrufer wirklich eine Zeit zu übergeben hat, nehmen Sie ihn in den Typ auf: type OnSave = (name: string, when: number) => void.

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

Ein Funktionstyp sieht aus wie eine Pfeilfunktion

Funktionen sind Werte: Sie speichern sie, übergeben sie und legen sie in Arrays ab, also brauchen sie auch Typen. Ein Funktionstyp-Ausdruck wird wie eine Pfeilfunktion geschrieben: (a: string) => void ist eine Funktion, die einen String nimmt und nichts Brauchbares zurückgibt; (value: number) => string nimmt eine Zahl und gibt einen String zurück. Benennen Sie ihn mit einem Typalias, type Formatter = (value: number) => string, und verwenden Sie den Namen für Parameter, Variablen und Arrays: steps: PriceStep[]. Jeder Parameter braucht einen Namen. (string) => void deklariert einen Parameter namens string ohne Typ, und strict meldet error TS7051: Parameter has a name but no type. Did you mean 'arg0: string'? Welchen Namen Sie wählen, spielt keine Rolle.

Kontextuelle Typisierung, und welche Funktionen passen

Eine Funktion, die dort steht, wo ein Funktionstyp erwartet wird, übernimmt ihre Parametertypen von diesem Typ. In const euro: Formatter = (value) => value.toFixed(2) ist value ohne Annotation eine Zahl, wie bei Callbacks in B2.2, und der Rumpf wird geprüft: value * 100 zurückzugeben ist error TS2322: Type 'number' is not assignable to type 'string'. Eine Funktion mit weniger Parametern passt: (message) => … ist ein gültiges (message: string, level: number) => void, denn JavaScript ignoriert überzählige Argumente. Eine Funktion, die einen Parameter braucht, den der Typ nie übergibt, passt nicht: Target signature provides too few arguments. Aufrufer müssen übergeben, was der Typ sagt: zwei Argumente für einen Parameter sind error TS2554: Expected 1 arguments, but got 2.

Aufrufsignaturen und Konstruktsignaturen

In JavaScript kann eine Funktion auch Properties haben. Ein Funktionstyp-Ausdruck kann sie nicht beschreiben, eine Aufrufsignatur in einem Objekttyp schon: type Discount = { label: string; (cents: number): number }. Im Objekttyp folgt der Rückgabetyp nach einem Doppelpunkt; (cents: number) => number ist dort error TS1005: ':' expected. TypeScript erlaubt, einer Funktion nach ihrer Deklaration Properties hinzuzufügen: tenOff.label = "10% off" macht tenOff zu einem Discount, und ohne diese Zeile meldet tsc error TS2741: Property 'label' is missing. Mit new davor ist es eine Konstruktsignatur: { new (text: string): Date } nimmt Date selbst an. Sie ohne new aufzurufen ist error TS2348: … Did you mean to include 'new'?

Quellen

Zuletzt geprüft am 4. Oktober 2026