Funkcje są wartościami
W JavaScript funkcja jest wartością jak każda inna. Możesz zapisać ją w zmiennej, włożyć do tablicy, przekazać innej funkcji albo z niej zwrócić. Już sam ten fakt otwiera cały styl programowania:
Gdy oswoisz się z traktowaniem funkcji jak wartości, funkcje wyższego rzędu przestają być tajemnicze. Funkcja wyższego rzędu to po prostu funkcja, która przyjmuje funkcję jako argument, zwraca funkcję albo robi obie te rzeczy. To cała definicja.
Funkcja jako argument
Najczęstsza forma: funkcja, która przyjmuje callback i wywołuje go za ciebie. Na pewno już takich używasz, nawet o tym nie myśląc.
forEach jest funkcją wyższego rzędu: bierze twoją funkcję i wywołuje ją raz dla każdego elementu. setTimeout też: bierze twoją funkcję i wywołuje ją po opóźnieniu. Ty skupiasz się na tym, co zrobić, a one zajmują się tym, kiedy i ile razy.
Własną funkcję tego typu piszesz tak samo. Oto mała funkcja, która uruchamia callback tylko wtedy, gdy warunek jest spełniony:
action to parametr, który akurat przechowuje funkcję. Wywołanie action() uruchamia to, co zostało przekazane.
Trzy, których naprawdę będziesz używać: map, filter, reduce
Tablice mają metody wyższego rzędu, które zastępują większość pętli for, jakie inaczej byś napisał. Naucz się tych trzech, a ogromna część codziennego kodu stanie się krótsza i czytelniejsza.
map: przekształć każdy element
map wywołuje twoją funkcję raz dla każdego elementu i zbiera zwrócone wartości do nowej tablicy. Ta sama długość, przekształcona zawartość. Oryginalna tablica nie jest modyfikowana.
filter: zostaw pasujące
filter zostawia elementy, dla których callback zwraca wartość truthy, a resztę odrzuca. Wynikiem jest nowa tablica, być może krótsza.
reduce: zwiń tablicę do jednej wartości
reduce jest najbardziej uniwersalna. Callback dostaje bieżący akumulator i aktualny element, a to, co zwrócisz, staje się kolejnym akumulatorem. Drugi argument (tutaj 0) to wartość początkowa.
Możesz je łączyć w łańcuch. Właśnie tu ten styl się opłaca:
Czytaj od góry do dołu: zostaw opłacone zamówienia, wyciągnij cenę, zsumuj. Bez pętli, bez zmiennego licznika, bez martwienia się o błąd o jeden.
Zwracanie funkcji z funkcji
Druga połowa funkcji wyższego rzędu. Funkcja, która buduje i zwraca inną funkcję:
multiplyBy(2) uruchamia się raz i oddaje zupełnie nową funkcję. Ta nowa funkcja wciąż pamięta factor. To domknięcie (closure), które ma później własną stronę. Na razie najważniejsze: wywołanie multiplyBy z różnymi argumentami daje różne wyspecjalizowane funkcje zbudowane z tego samego szablonu.
Ten wzorzec pojawia się wszędzie:
Jedna definicja, dwie funkcje wielokrotnego użytku. Lepsze niż ręczne pisanie warn i info i pilnowanie, żeby były spójne.
Funkcje nazwane kontra callbacki inline
Możesz przekazać funkcję strzałkową inline albo funkcję po nazwie. Oba sposoby działają, wybierz ten, który czyta się lepiej:
Gdy przekazujesz isEven (bez nawiasów), oddajesz samą funkcję. Dodanie () wywołałoby ją od razu i przekazało wynik. To częsty błąd początkujących:
nums.filter(isEven); // correct: passes the function
nums.filter(isEven()); // wrong: calls isEven with no args, passes the result
Jeśli callback zaczyna się rozrastać ponad kilka linii, wydziel go i nadaj mu nazwę. Otaczający kod zwykle czyta się wtedy lepiej.
Przykład w praktyce
Funkcje wyższego rzędu błyszczą, gdy składasz małe elementy. Załóżmy, że masz listę produktów i chcesz nazwy tanich, dostępnych produktów, zapisane wielkimi literami:
Każda funkcja pomocnicza ma jedno zadanie. Każda metoda tablicowa wykonuje jedno przekształcenie. Taki potok czyta się jak specyfikację tego, czego chcesz, a nie opis tego, jak zrobić pętlę.
Kiedy po nie nie sięgać
Metody wyższego rzędu są świetne, ale nie zastępują pętli w każdej sytuacji:
- Jeśli musisz przerwać w połowie,
forlubfor...ofzbreakjest czytelniejsze niż próby wyskoczenia zforEach. - Jeśli w callbacku robisz coś asynchronicznego,
mapiforEachnie poczekają na wynik. Użyjfor...ofzawaitalboPromise.allzmap. - Jeśli callback modyfikuje współdzielony stan, oddalasz się od mocnych stron tego stylu. Sięgnij po zwykłą pętlę albo przepisz kod tak, by zwracał nowe wartości.
Użyte tam, gdzie pasują, map, filter i reduce wycinają większość pętlowego boilerplate'u z codziennego kodu. Użyte wszędzie, zaczynają szkodzić czytelności. Wybierz narzędzie, które najlepiej pokazuje intencję.
Dalej: obiekty
Funkcje to nie jedyne wartości, na których warto budować. Obiekty to w JavaScript podstawowe narzędzie do grupowania powiązanych danych i zachowań. To właśnie nimi były wypełnione tablice, które przed chwilą filtrowaliśmy i mapowaliśmy. O nich jest następna strona.
Najczęściej zadawane pytania
Czym jest funkcja wyższego rzędu w JavaScript?
Funkcja wyższego rzędu to funkcja, która robi co najmniej jedną z dwóch rzeczy: przyjmuje inną funkcję jako argument albo zwraca funkcję jako wynik. Array.prototype.map, setTimeout i addEventListener to funkcje wyższego rzędu: przyjmują callback i wywołują go za ciebie.
Czym różnią się map, filter i reduce?
map przekształca każdy element i zwraca nową tablicę o tej samej długości. filter zostawia elementy, dla których callback zwraca wartość truthy, i zwraca (być może krótszą) tablicę. reduce zwija tablicę do jednej wartości, łącząc elementy jeden po drugim. Wszystkie trzy to funkcje wyższego rzędu przyjmujące callback.
Po co zwracać funkcję z innej funkcji?
Żeby budować małe, konfigurowalne funkcje pomocnicze bez powtarzania logiki. Funkcja taka jak multiplyBy(n) zwraca nową funkcję mnożącą przez n, więc multiplyBy(2) i multiplyBy(10) dają dwie wyspecjalizowane funkcje z jednej definicji. Ten wzorzec korzysta z domknięć i jest częsty w obsłudze zdarzeń, middleware i bibliotekach narzędziowych.