Parametry opcjonalne, domyślne i rest

Nie każdy gracz ma pseudonim, nie każde zamówienie ma rabat, a lista punktów może być dowolnie długa. Naucz funkcję obsługiwać takie sytuacje bez zgadywania.

CEL LEKCJI

Po tej lekcji potrafisz

  • Dobierzesz parametr opcjonalny lub wartość domyślną.
  • Zbierzesz argumenty parametrem rest i rozwiniesz tablicę przez spread.
  • Odczytasz proste przeciążenie funkcji.
WPROWADZENIE

Jedna funkcja, różne zamówienia

Kolega chce kupić jeden bilet, ktoś inny pięć. Większość zamówień nie ma rabatu. Nie potrzebujesz osobnej funkcji dla każdej sytuacji — opisz, co musi zostać podane, a co program może uzupełnić.

POZNAJ SKŁADNIĘ

Małe kroki, konkretne efekty

Domyślna wartość jest gotowym zastępstwem

Parametr z wartością domyślną może zostać pominięty. Zastępstwo działa też dla jawnego undefined, czyli braku wartości. Nie zastępuje zera, pustego tekstu ani null.

src/index.ts — osobny przykład
function bilety(ile: number, cena = 20): number {
  return ile * cena;
}
console.log(bilety(2));
console.log(bilety(2, 0));
console.log(bilety(2, undefined));
wynik w konsoli
40
0
40

Zero jest prawidłową ceną, więc darmowe bilety pozostają darmowe. Parametry wymagane najczytelniej umieszczać przed opcjonalnymi.

Pytajnik oznacza możliwość braku

Przy parametrze opcjonalnym musisz sam zdecydować, jak obsłużyć brak. W środku funkcji pseudonim ma typ string | undefined: tekst albo brak. Pionową kreskę omówimy szerzej w lekcji 15.

src/index.ts — osobny przykład
function powitaj(imie: string, pseudonim?: string): string {
  if (pseudonim === undefined) { return "Cześć, " + imie; }
  return "Cześć, " + pseudonim;
}
console.log(powitaj("Ala"));
console.log(powitaj("Ala", "Pixel"));
wynik w konsoli
Cześć, Ala
Cześć, Pixel

Nie wywołuj metod tekstu przed sprawdzeniem undefined. Zapis ? nie dostarcza automatycznie domyślnego pseudonimu.

Rest zbiera, spread rozpakowuje

Trzy kropki w deklaracji zbierają pozostałe argumenty do tablicy. Przy wywołaniu rozwijają elementy tablicy na osobne argumenty. Parametr rest jest ostatni i w tej funkcji ma typ number[].

src/index.ts — osobny przykład
function suma(...punkty: number[]): number {
  let wynik = 0;
  for (const p of punkty) { wynik += p; }
  return wynik;
}
const runda = [2, 3, 5];
console.log(suma(), suma(10, 20), suma(...runda));
wynik w konsoli
0 30 10

Puste wywołanie tworzy pustą tablicę, więc wynik wynosi 0. To inny zapis wejścia niż pojedynczy parametr punkty: number[].

PRZYKŁAD Z OMÓWIENIEM

Koszt wspólnego zamówienia pizzy

Ceny liczymy w całych złotych. Dostawa domyślnie kosztuje 8 zł; każda kolejna cena pizzy trafia do rest. Przyjmujemy nieujemne ceny.

src/index.ts — osobny przykład
function zamowienie(dostawa = 8, ...ceny: number[]): number {
  let razem = dostawa;
  for (const cena of ceny) { razem += cena; }
  return razem;
}
const pizze = [30, 35, 25];
console.log("Z dostawą: " + zamowienie(undefined, ...pizze));
console.log("Odbiór własny: " + zamowienie(0, ...pizze));
wynik w konsoli
Z dostawą: 98
Odbiór własny: 90

Jak program dochodzi do wyniku

  1. undefined uruchamia domyślną dostawę 8 zł.
  2. Spread rozwija pizze na trzy argumenty. Rest w funkcji zbiera je do lokalnej tablicy ceny.
  3. Pętla dodaje 30 + 35 + 25 do opłaty za dostawę.
  4. Zero w drugim wywołaniu świadomie wyłącza opłatę.

Zanim przejdziesz dalej

Ile zwróci zamowienie()? Czy to znaczy, że sklep powinien przyjąć takie zamówienie?

Sprawdź swoje rozumowanie

Zwróci 8. Funkcja zgodnie z kodem nalicza dostawę także bez pizzy. Regułę zakazującą pustych zamówień trzeba dopisać — typy nie ustalają zasad sklepu.

EKSPERYMENT

Zmień kod i zobacz, co się stanie

Przeciążenia — różne umowy, jedno ciało

To rozszerzenie dla chętnych. Sygnatury przeciążenia opisują dozwolone wywołania. Niżej zapisujemy jedną implementację obsługującą wszystkie przypadki. typeof sprawdza rodzaj wartości podczas działania.

src/index.ts — osobny przykład
function etykieta(wartosc: number): string;
function etykieta(wartosc: string): string;
function etykieta(wartosc: number | string): string {
  if (typeof wartosc === "number") { return "#" + wartosc; }
  return wartosc.toUpperCase();
}
console.log(etykieta(7), etykieta("vip"));
wynik w konsoli
#7 VIP

W tym prostym przypadku jeden parametr number | string byłby wystarczający. Przeciążenia przydają się bardziej, gdy rodzaj wejścia wpływa na typ wyniku. Nie twórz kilku funkcji o tej samej nazwie z osobnymi ciałami.

CZĘSTE BŁĘDY

Gdy wynik zaskakuje

Pomyłka Co się dzieje i jak to poprawić
Rabat 0 zastąpiony ceną domyślną Parametr domyślny nie zastępuje zera. Jeśli tak działa Twój program, sprawdź użycie operatora ||.
Rest przed innym parametrem Rest musi być ostatni: inaczej nie wiadomo, które argumenty miałby zebrać.
Parametr z ? użyty jak pewny tekst Sprawdź undefined albo wybierz wartość domyślną.

undefined na zapowiedź

Parametr opcjonalny, którego nie podano, ma wartość undefined. Na razie traktuj ją jak „brak wartości”. Czym różni się od null, jak ją bezpiecznie obsłużyć i co robią operatory ?. oraz ??lekcja 16.

ZADANIA

Teraz Twoja kolej

Rozwiązanie jest jedną z możliwości. Zanim je odkryjesz, uruchom własną wersję dla podanych danych i przynajmniej jednego przypadku brzegowego.

ZAD 1Powitanie na serwerze★☆☆

Funkcja powitaj ma przyjmować nazwę, domyślnie „Gościu”. Wywołaj ją bez argumentu i z „Pixel”.

Mała podpowiedź

Wartość domyślna pozwala wywnioskować string.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
function powitaj(nazwa = "Gościu"): string { return "Hej, " + nazwa; }
console.log(powitaj());
console.log(powitaj("Pixel"));
wynik w konsoli
Hej, Gościu
Hej, Pixel

Pominięty argument jest zastąpiony tekstem, który zapisano w deklaracji.

ZAD 2Podpis drużyny★★☆

Zwróć nazwę drużyny. Jeżeli podano miasto, dodaj je w nawiasie. Brak miasta nie może dawać tekstu „undefined”.

Mała podpowiedź

Porównaj opcjonalny parametr z undefined.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
function podpis(nazwa: string, miasto?: string): string {
  if (miasto === undefined) { return nazwa; }
  return nazwa + " (" + miasto + ")";
}
console.log(podpis("Wilki"));
console.log(podpis("Wilki", "Gdańsk"));
wynik w konsoli
Wilki
Wilki (Gdańsk)

Obsługujemy brak osobno. Pusty napis byłby nadal podanym miastem — dodatkowa walidacja to osobna reguła.

ZAD 3Licznik medali★★☆

Zsumuj dowolną liczbę medali parametrem rest. Sprawdź brak argumentów i tablicę [1, 3, 2] rozwiniętą przy wywołaniu.

Mała podpowiedź

Zacznij sumę od zera.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
function medale(...liczby: number[]): number {
  let suma = 0;
  for (const n of liczby) { suma += n; }
  return suma;
}
console.log(medale(), medale(...[1, 3, 2]));
wynik w konsoli
0 6

Spread i rest mogą wystąpić po dwóch stronach tego samego wywołania.

ZAD 4Rabat grupowy★★★

Przyjmij domyślny rabat 10% i dowolnie wiele cen. Zwróć sumę po rabacie. Dla 100 i 50 wynik to 135, z rabatem 0 — 150. Zakładamy rabat 0–100 i nieujemne ceny.

Mała podpowiedź

Sumuj ceny, a potem pomnóż przez 1 minus rabat / 100.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
function poRabacie(rabat = 10, ...ceny: number[]): number {
  let suma = 0;
  for (const cena of ceny) { suma += cena; }
  return suma * (1 - rabat / 100);
}
console.log(poRabacie(undefined, 100, 50));
console.log(poRabacie(0, 100, 50));
console.log(poRabacie());
wynik w konsoli
135
150
0

Rabat jest pierwszym argumentem, dlatego przy jego pominięciu używamy undefined. W większej aplikacji wygodniejszy może być obiekt opcji.

PODSUMOWANIE

Co zabierasz do następnej lekcji

  • Domyślna wartość reaguje na undefined, a nie na każdą wartość fałszywą.
  • Parametr opcjonalny może nie istnieć — trzeba to obsłużyć.
  • Rest tworzy tablicę argumentów; spread rozwija tablicę przy wywołaniu.