Zum Inhalt springen
aviral gupta

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

Optionale, Default- und Rest-Parameter

Nach dieser Lektion schreiben Sie Funktionen mit optionalen, Default- und beliebig vielen Argumenten, spreaden Arrays in Aufrufe und destrukturieren einen typisierten Parameter.

Lektion 2 von 5 in B4 Mehr zu Funktionen

Danach können Sie

  • Einen Parameter mit ? oder einem Default optional machen und undefined im Rumpf behandeln
  • Argumente mit einem Rest-Parameter sammeln und Arrays und Tupel in Aufrufe spreaden
  • Einen destrukturierten Parameter annotieren und Callbacks ohne unnötige optionale Parameter typisieren
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus JavaScript: name hat einen Default-Wert. Was gibt das aus?

    function greet(name = "guest") {
      return "Hello, " + name;
    }
    
    console.log(greet(), greet(undefined), greet(null));
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. name ist optional, und greet() lässt es weg. Was meldet tsc?

    function greet(name?: string): string {
      return "Hello, " + name.toUpperCase();
    }
    
    console.log(greet());
  3. Üben · Aufgabe 3 von 7

    Setzen Sie ein, was values zu einem Rest-Parameter macht, damit sum beliebig viele Zahlen nimmt und das Programm 9.5 ausgibt.

    function sum(____values: number[]): number {
      let total = 0;
      for (const v of values) total += v;
      return total;
    }
    
    console.log(sum(3, 4.5, 2));
    function sum(values: number[]): number {
  4. Üben · Aufgabe 4 von 7

    Welchen Typ hat width in der Funktion?

    function pad(text: string, width?: number): string {
      // what is width here?
      return text.padStart(width ?? 8);
    }
  5. Üben · Aufgabe 5 von 7

    point nimmt genau zwei Zahlen. Welche Zeile lehnt tsc ab?

    function point(x: number, y: number): string {
      return "(" + x + ", " + y + ")";
    }
    
    const a = [3, 4];
    const b = [3, 4] as const;
    console.log(point(...a));
    console.log(point(...b));
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. tsc nimmt diese Datei an. Welche Argumente ersetzt der Default, und was gibt node main.ts aus?

    function repeat(text: string, times = 2): string {
      return text.repeat(times);
    }
    
    const counts = [undefined, 0, 3];
    console.log(counts.map((n) => "[" + repeat("ab", n) + "]").join(" "));
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Eine Order hat eine id, eine Liste von Artikelpreisen und eine optionale note. Schreiben Sie total(...prices), das beliebig viele Preise addiert. Schreiben Sie dann describe, das { id, items, note } aus einer Order in seiner Parameterliste destrukturiert und eine currency mit dem Default "EUR" nimmt. Es gibt Text wie #1: 9.75 EUR zurück und hängt die note in Klammern an, wenn es eine gibt. Verwenden Sie darin total(...items). 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 Café-Kassenbon

euro hat eine Default-Währung, line destrukturiert ein Item, dessen quantity optional ist, printReceipt und sum nehmen beliebig viele Argumente, und thanks hat einen optionalen Namen. Die letzten beiden Zeilen spreaden ein mit as const erzeugtes Tupel in eine Funktion mit zwei festen Parametern. Führen Sie npx tsc aus, dann node main.ts. Entfernen Sie danach as const und führen Sie tsc erneut aus.

main.ts

// A receipt: optional, default and rest parameters, and a destructured parameter.
type Item = { name: string; price: number; quantity?: number };

function euro(cents: number, symbol = "EUR"): string {
  return (cents / 100).toFixed(2) + " " + symbol; // symbol is a string, from its default
}

function line({ name, price, quantity }: Item): string {
  const count = quantity ?? 1; // quantity is number | undefined here
  return (count + " x " + name).padEnd(16) + euro(price * count);
}

function sum(...amounts: number[]): number {
  let total = 0;
  for (const amount of amounts) total += amount;
  return total;
}

function printReceipt(title: string, ...items: Item[]): void {
  console.log(title);
  for (const item of items) console.log("  " + line(item));
  const cents = items.map((item) => item.price * (item.quantity ?? 1));
  console.log("  " + "total".padEnd(16) + euro(sum(...cents)));
}

function thanks(name?: string): string {
  return name === undefined ? "Thank you!" : "Thank you, " + name + "!";
}

function clock(hours: number, minutes: number): string {
  return String(hours).padStart(2, "0") + ":" + String(minutes).padStart(2, "0");
}

const order: Item[] = [
  { name: "coffee", price: 320, quantity: 2 },
  { name: "croissant", price: 245 },
];
printReceipt("Cafe Mitte", ...order, { name: "water", price: 150 });
console.log(thanks());
console.log(thanks("Ada"));

const opens = [7, 30] as const; // a tuple of exactly two numbers
console.log("Open daily from " + clock(...opens));

Ausführen mit

npx tsc
node main.ts

Ausgabe

Cafe Mitte
  2 x coffee      6.40 EUR
  1 x croissant   2.45 EUR
  1 x water       1.50 EUR
  total           10.35 EUR
Thank you!
Thank you, Ada!
Open daily from 07:30
  • symbol hat keine Annotation: Sein Default "EUR" macht es zu einem String, und jeder Aufruf hier lässt es weg.
  • quantity ist in Item optional, also ist es nach dem Destrukturieren number | undefined, und ?? 1 ergibt die Anzahl.
  • ...order spreadet ein Array in den Rest-Parameter items, und im selben Aufruf darf ein weiteres Item folgen.
  • Ohne as const ist opens number[], und clock(...opens) ist error TS2556.
Ä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

Defaults für eine Preisformatierung

formatPrice(1250) soll 12.50 EUR ergeben, aber alle drei Parameter sind Pflicht, deshalb meldet tsc error TS2554: Expected 3 arguments, but got 1. Geben Sie currency den Default "EUR" und decimals den Default 2, ohne Annotationen. Fügen Sie dann einen vierten, optionalen Parameter label hinzu: Wenn er angegeben ist, beginnt das Ergebnis mit dem Label und einem Doppelpunkt, wie in Total: 12.50 EUR.

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 Default steht mit = nach dem Parameternamen, und TypeScript inferiert den Typ daraus: currency = "EUR".

  2. Hinweis 2

    Ein optionaler Parameter hat ein ? nach dem Namen und muss nach den Pflichtparametern stehen: label?: string.

  3. Hinweis 3

    Im Rumpf kann label undefined sein: return label === undefined ? text : label + ": " + text;

Eine Lösung zeigen

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

export function formatPrice(cents: number, currency = "EUR", decimals = 2, label?: string): string {
  const text = (cents / 100).toFixed(decimals) + " " + currency;
  return label === undefined ? text : label + ": " + text;
}

console.log(formatPrice(1250));
console.log(formatPrice(1999, "USD", 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 formatPrice(cents: number, currency: string, decimals: number): string {
  return (cents / 100).toFixed(decimals) + " " + currency;
}

console.log(formatPrice(1250));
console.log(formatPrice(1999, "USD", 0));

main.test.ts

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

test('formatPrice(1250) verwendet EUR und zwei Nachkommastellen', () => {
  assert.equal(formatPrice(1250), '12.50 EUR', `formatPrice(1250) ergab ${JSON.stringify(formatPrice(1250))}`);
});

test('eine angegebene Währung und Stellenzahl werden verwendet', () => {
  assert.equal(formatPrice(1999, 'USD', 0), '20 USD', `formatPrice(1999, 'USD', 0) ergab ${JSON.stringify(formatPrice(1999, 'USD', 0))}`);
});

test('undefined als Währung ergibt den Default', () => {
  assert.equal(formatPrice(1250, undefined, 1), '12.5 EUR', `formatPrice(1250, undefined, 1) ergab ${JSON.stringify(formatPrice(1250, undefined, 1))}`);
});

test('ein Label steht vorne, mit Doppelpunkt', () => {
  assert.equal(formatPrice(1250, 'EUR', 2, 'Total'), 'Total: 12.50 EUR', `formatPrice(1250, 'EUR', 2, 'Total') ergab ${JSON.stringify(formatPrice(1250, 'EUR', 2, '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

Übung 2 von 2

Beliebig viele Runden

total soll die Punkte beliebig vieler Runden addieren, einzeln übergeben: total(a, b), total() oder total(...list). describe hat keinen Parametertyp, deshalb meldet tsc error TS7006. Machen Sie rounds zu einem Rest-Parameter, und ändern Sie describe so, dass es player und points aus einer Round in seiner Parameterliste destrukturiert, mit der Annotation nach dem Muster und dem Rückgabetyp string.

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 Rest-Parameter hat drei Punkte vor dem Namen und einen Array-Typ: ...rounds: Round[].

  2. Hinweis 2

    Der Rumpf von total kann bleiben, wie er ist: rounds ist weiterhin ein Array, leer, wenn keine Runden übergeben werden.

  3. Hinweis 3

    export function describe({ player, points }: Round): string { return player + ": " + points; }

Eine Lösung zeigen

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

export type Round = { player: string; points: number };

export function total(...rounds: Round[]): number {
  let sum = 0;
  for (const round of rounds) sum += round.points;
  return sum;
}

export function describe({ player, points }: Round): string {
  return player + ": " + points;
}
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 Round = { player: string; points: number };

export function total(rounds: Round[]): number {
  let sum = 0;
  for (const round of rounds) sum += round.points;
  return sum;
}

export function describe(round) {
  return round.player + ": " + round.points;
}

main.test.ts

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

const a = {player: 'Ada', points: 7};
const b = {player: 'Ada', points: 5};

test('total addiert einzeln übergebene Runden', () => {
  assert.equal(total(a, b), 12, 'total(a, b) sollte 12 sein');
});

test('total() ohne Runden ist 0', () => {
  assert.equal(total(), 0, 'total() sollte 0 sein');
});

test('ein Array lässt sich in total spreaden', () => {
  const list = [a, b, {player: 'Grace', points: 9}];
  assert.equal(total(...list), 21, 'total(...list) sollte 21 sein');
});

test('describe nennt Spieler und Punkte', () => {
  assert.equal(describe(a), 'Ada: 7', `describe(a) ergab ${JSON.stringify(describe(a))}`);
});

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 optionaler Parameter vor einem Pflichtparameter

function label(prefix?: string, name: string): string {
  return (prefix ?? "") + name;
}

console.log(label(undefined, "Ada"));

Was tsc oder Node.js ausgibt

main.ts(1,33): error TS1016: A required parameter cannot follow an optional parameter.

Warum, und die Lösung

Argumente werden nach Position zugeordnet, also kann ein Aufrufer nicht das erste weglassen und das zweite übergeben. Stellen Sie die Pflichtparameter an den Anfang: label(name: string, prefix?: string). Muss prefix vorne bleiben, geben Sie ihm stattdessen einen Default (prefix = ""), und Aufrufer übergeben undefined, um ihn zu nutzen.

Typen in einem Destrukturierungsmuster

function area({ width: number, height: number }) {
  return width * height;
}

console.log(area({ width: 3, height: 4 }));

Was tsc oder Node.js ausgibt

main.ts(1,24): error TS2300: Duplicate identifier 'number'.

Warum, und die Lösung

In einem Destrukturierungsmuster bedeutet width: number "nimm width und nenne die Variable number", also gibt es zwei Variablen namens number und keine namens width. Die Annotation steht nach dem ganzen Muster: function area({ width, height }: { width: number; height: number }).

Ein optionaler Index in einem Callback-Typ

function myForEach(arr: number[], callback: (arg: number, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
}

myForEach([1, 2, 3], (a, i) => {
  console.log(i.toFixed());
});

Was tsc oder Node.js ausgibt

main.ts(8,15): error TS18048: 'i' is possibly 'undefined'.

Warum, und die Lösung

index?: number sagt, dass myForEach den Callback auch ohne Index aufrufen könnte, also muss jeder Callback mit undefined rechnen, obwohl die Schleife immer einen übergibt. Folgen Sie der Regel des Handbooks und schreiben Sie index: number. Callbacks, die ihn nicht brauchen, wie (a) => console.log(a), passen trotzdem.

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

Optionale Parameter und Defaults

Ein ? nach dem Parameternamen macht ihn optional: Mit function greet(name?: string) ist greet() in Ordnung, und greet(undefined) ebenso. Im Rumpf ist name string | undefined, denn ein fehlendes Argument ist undefined, also ist name.toUpperCase() error TS18048: 'name' is possibly 'undefined'. Prüfen Sie zuerst, oder schreiben Sie name ?? "guest". Ein Default erledigt beides auf einmal: In function repeat(text: string, times = 2) braucht times keine Annotation, ist im Rumpf eine Zahl, und Aufrufer sehen times?: number. Der Default ersetzt nur undefined: repeat("ab", 0) wiederholt nullmal. Optionale Parameter stehen nach den Pflichtparametern, sonst meldet tsc error TS1016. Zu viele Argumente bleiben Fehler: error TS2554: Expected 1-2 arguments, but got 3.

Rest-Parameter und gespreadete Argumente

Ein Rest-Parameter sammelt die übrigen Argumente in einem Array: function multiply(n: number, ...m: number[]). multiply(10, 1, 2) ergibt m = [1, 2], multiply(10) ein leeres Array. Er muss am Ende stehen (error TS1014) und einen Array- oder Tupeltyp haben: ...m: number ist error TS2370. Jedes Argument wird gegen den Elementtyp geprüft. In die andere Richtung spreadet ... in einem Aufruf ein Array: sum(...prices) übergibt jeden Preis an den Rest-Parameter. In feste Parameter wie bei Math.atan2(y, x) zu spreaden, braucht eine bekannte Länge. const args = [8, 5] ist number[], beliebig lang, also ist Math.atan2(...args) error TS2556. [8, 5] as const ist ein Tupel aus genau zwei Zahlen und passt.

Destrukturierte Parameter und optionale Parameter in Callbacks

Ein destrukturierter Parameter bekommt seine Annotation nach dem ganzen Muster: function sum({ a, b, c }: { a: number; b: number; c: number }), oder mit einem benannten Typ, ({ a, b, c }: ABC). Im Muster benennt ein Doppelpunkt um: { width: number, height: number } erzeugt zwei Variablen namens number, was tsc als error TS2300: Duplicate identifier 'number' meldet. Für Callbacks gibt das Handbook eine Regel: Schreiben Sie in einem Callback-Typ nie einen optionalen Parameter, außer Sie rufen die Funktion auch ohne dieses Argument auf. Mit callback: (arg: number, index?: number) => void ist (a, i) => i.toFixed() beim Aufrufer error TS18048: 'i' is possibly 'undefined'. Schreiben Sie index: number; Callbacks, die ihn ignorieren, passen trotzdem (B4.1).

Quellen

Zuletzt geprüft am 4. Oktober 2026