Funkcje w JavaScript
od podstaw do wzorców produkcyjnych
Zrozum deklaracje, funkcje strzałkowe, zakres, domknięcia, this, callbacki i kod asynchroniczny. Bez skrótów myślowych — z przykładami, które można wykorzystać w realnym projekcie.
- 8
- kluczowych tematów
- 12+
- praktycznych przykładów
- 18 min
- szacowany czas
Najważniejsza idea
Funkcja to nazwana lub anonimowa porcja zachowania. Przyjmuje dane wejściowe, wykonuje określoną pracę i opcjonalnie zwraca wynik. Dobrze zaprojektowana funkcja ma jeden czytelny cel, przewidywalny kontrakt i nazwę, która opisuje intencję.
Fundament
Czym jest funkcja w JavaScript?
W JavaScript funkcje są wartościami pierwszej klasy. Możesz przypisać je do zmiennej, przekazać jako argument, zwrócić z innej funkcji albo przechować w obiekcie. Dzięki temu język naturalnie wspiera kompozycję, callbacki i programowanie funkcyjne.
Każde wywołanie tworzy własny kontekst wykonania. Parametry otrzymują wartości argumentów, deklaracje lokalne trafiają do zakresu funkcji, a instrukcja return kończy wykonanie i przekazuje wynik do miejsca wywołania.
1function calculateGrossPrice(netPrice, taxRate = 0.23) {2 if (!Number.isFinite(netPrice) || netPrice < 0) {3 throw new TypeError(4 'netPrice musi być liczbą większą lub równą 0'5 );6 }7 8 return Number((netPrice * (1 + taxRate)).toFixed(2));9}10 11const grossPrice = calculateGrossPrice(100);12console.log(grossPrice); // 123
Walidacja znajduje się przy granicy funkcji, a zwracany wynik ma stabilny typ number.
Można je wywołać przed miejscem deklaracji w kodzie. Nie oznacza to jednak, że warto budować na tym nieczytelną kolejność pliku — funkcje publiczne nadal powinny być łatwe do odnalezienia.
Składnia i semantyka
Deklaracja, wyrażenie i funkcja strzałkowa
Trzy popularne formy mogą wyglądać podobnie, ale różnią się zachowaniem. Wybieraj je świadomie — przede wszystkim ze względu na hoisting, nazwę w stack trace oraz sposób wiązania this.
Deklaracja funkcji
function loadData() {}
Najlepsza dla głównych, nazwanych operacji modułu. Czytelna w stack trace i dostępna przed deklaracją.
Wyrażenie funkcyjne
const loadData = function () {};
Funkcja jest wartością przypisaną do zmiennej. Dobrze współpracuje z warunkową konfiguracją i fabrykami.
Funkcja strzałkowa
const loadData = () => {};
Zwięzła i z leksykalnym this. Świetna dla transformacji danych; zwykle zła jako metoda obiektu.
1const users = [2 { name: ' Anna ', active: true },3 { name: 'Marek', active: false },4 { name: 'Łukasz', active: true }5];6 7const activeNames = users8 .filter(({ active }) => active)9 .map(({ name }) => name.trim())10 .sort((a, b) => a.localeCompare(b, 'pl'));11 12console.log(activeNames); // ['Anna', 'Łukasz']
Funkcje strzałkowe dobrze sprawdzają się jako małe callbacki, gdy kontekst this nie jest potrzebny.
Projektowanie API
Parametry, wartości domyślne i destrukturyzacja
Im więcej pozycyjnych argumentów ma funkcja, tym trudniej ją poprawnie wywołać. Przy trzech lub większej liczbie opcji zwykle lepiej przyjąć pojedynczy obiekt konfiguracyjny. Nazwy pól dokumentują wywołanie i pozwalają dodawać kolejne opcje bez łamania istniejącego kodu.
1function createInvoice({2 customer,3 items = [],4 currency = 'PLN',5 issuedAt = new Date()6}) {7 if (!customer?.name) {8 throw new TypeError('Brak nazwy klienta');9 }10 11 const total = items.reduce(12 (sum, { price, quantity = 1 }) => sum + price * quantity,13 014 );15 16 return {17 customer: customer.name,18 currency,19 issuedAt: issuedAt.toISOString(),20 total: Number(total.toFixed(2))21 };22}
Destrukturyzowany obiekt wejściowy tworzy czytelny kontrakt i ułatwia rozbudowę funkcji.
Waliduj na wejściu
Błąd zgłoszony blisko źródła jest prostszy do zdiagnozowania niż niepoprawny wynik kilka wywołań dalej.
Nie mutuj argumentów
Twórz nowe tablice i obiekty, chyba że kontrakt jawnie mówi o mutacji i przynosi ona mierzalną korzyść.
Stabilizuj typ wyniku
Funkcja zwracająca raz obiekt, raz false, a raz null przerzuca złożoność na każdego klienta.
Kompozycja
Callbacki i funkcje wyższego rzędu
Funkcja wyższego rzędu przyjmuje inną funkcję albo ją zwraca. Ten mechanizm stoi za metodami map, filter, obsługą zdarzeń, middleware i wieloma bibliotekami reaktywnymi.
Najlepsza abstrakcja ukrywa mechanizm, ale pozostawia intencję widoczną w miejscu użycia.
1const pipe = (...functions) => input =>2 functions.reduce((value, fn) => fn(value), input);3 4const sanitizeUsername = pipe(5 value => value.trim(),6 value => value.toLowerCase(),7 value => value.replace(/\s+/g, '-'),8 value => value.replace(/[^a-z0-9-]/g, '')9);10 11console.log(sanitizeUsername(' Jan Kowalski! '));12// jan-kowalski
pipe łączy małe transformacje w jeden czytelny przepływ od lewej do prawej.
Jeżeli funkcja wyższego rzędu jest używana tylko raz i utrudnia czytanie, zwykły kod sekwencyjny może być lepszym rozwiązaniem.
Pamięć zakresu
Domknięcia — funkcja pamięta swoje otoczenie
Domknięcie powstaje, gdy funkcja zachowuje dostęp do zakresu leksykalnego, w którym została utworzona. Nawet po zakończeniu funkcji zewnętrznej potrzebne zmienne pozostają osiągalne dla funkcji wewnętrznej.
1function createCounter(initialValue = 0) {2 let value = initialValue;3 4 return Object.freeze({5 increment(step = 1) {6 value += step;7 return value;8 },9 decrement(step = 1) {10 value -= step;11 return value;12 },13 current() {14 return value;15 }16 });17}18 19const counter = createCounter(10);20counter.increment(2); // 1221counter.current(); // 12
value jest prywatnym stanem. Nie można zmienić go bez użycia metod zwróconego obiektu.
Gdzie domknięcia są naprawdę przydatne?
Fabryki funkcji i konfiguracja zachowania.
Prywatny stan bez klasy i bez zmiennej globalnej.
Memoizacja, debounce, throttle i cache.
Obsługa zdarzeń pamiętająca kontekst.
Kontekst wywołania
Jak działa this i dlaczego strzałki są inne
W zwykłej funkcji wartość this zależy od sposobu wywołania, a nie od miejsca deklaracji. Metoda wywołana przez object.method() otrzyma obiekt po lewej stronie kropki. Po odłączeniu metody kontekst znika, chyba że użyjesz bind, call albo apply.
1const cart = {2 items: [],3 4 add(product) {5 this.items.push(product);6 },7 8 total() {9 return this.items.reduce(10 (sum, item) => sum + item.price * item.quantity,11 012 );13 }14};15 16cart.add({ price: 49.99, quantity: 2 });17 18const detachedTotal = cart.total.bind(cart);19console.log(detachedTotal()); // 99.98
Metody obiektu zapisano skróconą składnią. bind tworzy nową funkcję ze stałym kontekstem cart.
Gdy funkcja ma korzystać z właściwości obiektu przez this.
Strzałka nie tworzy własnego this; przejmuje je z otaczającego zakresu.
Praca asynchroniczna
Funkcje async, Promise i obsługa błędów
Funkcja oznaczona jako async zawsze zwraca obietnicę. await wstrzymuje tylko jej własny przepływ, nie blokuje całego wątku JavaScript. Błędy z odrzuconych obietnic można obsłużyć przez try...catch albo przez końcowe .catch().
1async function fetchUser(userId, { signal } = {}) {2 const safeId = encodeURIComponent(userId);3 const response = await fetch(`/api/users/${safeId}`, { signal });4 5 if (!response.ok) {6 throw new Error(7 `Nie udało się pobrać użytkownika: ${response.status}`8 );9 }10 11 return response.json();12}13 14const controller = new AbortController();15 16fetchUser('42', { signal: controller.signal })17 .then(user => console.log(user))18 .catch(error => {19 if (error.name !== 'AbortError') console.error(error);20 });
AbortSignal pozwala anulować żądanie, gdy komponent znika albo użytkownik rozpoczyna nowszą operację.
Funkcja infrastrukturalna powinna dodać kontekst techniczny i rzucić błąd dalej. Warstwa interfejsu decyduje, jaki komunikat zobaczy użytkownik.
Kod produkcyjny
Wzorce, testowalność i wydajność
Dobra funkcja jest mała nie dlatego, że ma określoną liczbę linii, ale dlatego, że reprezentuje jeden poziom abstrakcji. Łączy minimum zależności, jawnie komunikuje efekty uboczne i daje się przetestować bez skomplikowanego środowiska.
1function memoize(2 fn,3 keyResolver = (...args) => JSON.stringify(args)4) {5 const cache = new Map();6 7 return function memoized(...args) {8 const key = keyResolver(...args);9 10 if (cache.has(key)) {11 return cache.get(key);12 }13 14 const result = fn.apply(this, args);15 cache.set(key, result);16 return result;17 };18}19 20const fibonacci = memoize(function calculate(n) {21 if (n < 2) return n;22 return fibonacci(n - 1) + fibonacci(n - 2);23});
Memoizacja ma sens dla kosztownych, deterministycznych obliczeń. Cache bez limitu nie jest darmowy — zużywa pamięć.
Checklist seniora
Zanim zatwierdzisz funkcję w code review
- ✓Nazwa opisuje intencję, a nie implementację.
- ✓Argumenty i typ wyniku tworzą spójny kontrakt.
- ✓Efekty uboczne są jawne i ograniczone.
- ✓Błędy mają użyteczny kontekst.
- ✓Nie ma ukrytej zależności od globalnego stanu.
- ✓Test obejmuje przypadek typowy, graniczny i błędny.
thisasync/awaitDodaj anulowanie i błędyNajczęstsze pytania
Funkcje JavaScript bez niejasności
Nie wybieraj składni na podstawie domniemanej mikrooptymalizacji. Różnice zależą od silnika i kontekstu, a zwykle są nieistotne wobec czytelności oraz poprawnej semantyki this.
Nie ma uniwersalnego limitu linii. Problem zaczyna się wtedy, gdy funkcja miesza różne poziomy abstrakcji, ma kilka niezależnych powodów do zmiany albo wymaga komentarzy wyjaśniających kolejne fazy.
Nie. Aplikacja musi komunikować się z siecią, zapisywać dane i aktualizować interfejs. Ważne, aby efekty uboczne były skupione w jawnych granicach, a logika obliczeniowa pozostała możliwie czysta.
Parametr to nazwa w definicji funkcji, np. name. Argument to konkretna wartość przekazana podczas wywołania, np. 'Anna'.
Czas utrwalić wiedzę
Napisz jedną funkcję mniej.
Napisz jedną funkcję lepiej.
Weź fragment z własnego projektu, nazwij jego kontrakt, oddziel obliczenia od efektów ubocznych i dodaj trzy testy: typowy, graniczny oraz błędny.
Wróć na początek ↑O materiale
Przewodnik projektowy dla nowoczesnego JavaScript
Treść łączy podstawy języka z decyzjami, które pojawiają się podczas projektowania modułów, integracji API i code review.

