Funkcje to fundament JavaScriptu – pozwalają pisać kod wielokrotnego użytku, modularny i łatwy w utrzymaniu. W praktyce korzystamy z trzech głównych sposobów ich definiowania: deklaracji funkcji, wyrażeń funkcyjnych oraz funkcji strzałkowych. Kluczowe różnice dotyczą hoistingu, wiązania this i dostępu do arguments, co bezpośrednio wpływa na czytelność, wydajność i przewidywalność kodu.
Trzy podstawowe mechanizmy definiowania funkcji w JavaScript to:
- Deklaracje funkcji – tradycyjna forma, w pełni hoistowana;
- Wyrażenia funkcyjne – funkcje jako wartości przypisywane do zmiennych;
- Funkcje strzałkowe – zwięzła składnia z leksykalnym
this.
Deklaracje funkcji – tradycyjne podejście do definiowania
Deklaracje tworzą nazwane funkcje przy użyciu słowa kluczowego function. Cała definicja jest hoistowana, więc funkcję można wywołać, zanim pojawi się w kodzie jej deklaracja.
Przykład minimalnej deklaracji i hoistingu:
console.log(calcRectArea(3, 4)); // 12
function calcRectArea(width, height) {
return width * height;
}
Deklaracje funkcji mają własne wiązanie this oraz dostęp do arguments, co sprzyja klasycznym wzorcom obiektowym i rekurencji.
Kiedy deklaracje sprawdzają się najlepiej:
- Funkcje ogólnego przeznaczenia – używane szeroko w module lub pliku;
- Rekurencja – łatwy dostęp do nazwy funkcji w całym zakresie;
- Organizacja kodu – chcesz móc wywoływać funkcje „w przód” dzięki hoistingowi.
Zakres i ponowna deklaracja
Na najwyższym poziomie skryptu deklaracje stają się właściwościami globalThis. W modułach oraz w blokach (w trybie ścisłym) obowiązują zasady podobne do let – nie można ich swobodnie redeklarować. Aby uniknąć niejednoznaczności, preferuj deklaracje w pełnym zakresie funkcji/modułu lub używaj wyrażeń w blokach.
Wyrażenia funkcyjne – elastyczne funkcje jako wartości
Wyrażenia tworzą funkcje „w locie” i przypisują je do zmiennych. Nie są hoistowane jak deklaracje – funkcja istnieje dopiero od momentu wykonania danej linii.
Przykład anonimowego wyrażenia:
const multiply = function (a, b) { return a * b; };
multiply(4, 3); // 12
Przykład nazwanego wyrażenia (nazwa dostępna tylko wewnątrz funkcji – ułatwia rekurencję):
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};
Przy const/let zmienna z funkcją jest w TDZ do czasu inicjalizacji; przy var odczyt przed przypisaniem zwróci undefined, a wywołanie spowoduje TypeError.
Kiedy wyrażenia są najlepszym wyborem:
- Funkcje przekazywane jako argumenty – callbacki i funkcje wyższego rzędu;
- Tworzenie funkcji warunkowo – zależnie od stanu w czasie wykonania;
- IIFE – natychmiastowe wywołanie w celu izolacji zakresu.
Funkcje strzałkowe – zwięzła składnia i leksykalne this
Funkcje strzałkowe (=>) to uproszczona forma wyrażeń funkcyjnych. Nie mają własnego this ani arguments; dziedziczą je leksykalnie. Nie występują jako deklaracje – zawsze są wyrażeniami.
Przykłady składni:
const add = (a, b) => a + b; // zwrot wyrażenia bez klamer
const double = x => x * 2; // jeden parametr, bez nawiasów
const getObj = () => ({ ok: true }); // zwracanie literału obiektu
const sum = (...args) => args.reduce((a, b) => a + b, 0);
Różnice w this – zachowanie leksykalne w praktyce:
function Counter() {
this.count = 0;
setInterval(() => { this.count++; }, 1000); // this wskazuje instancję
}
Funkcji strzałkowych nie można używać z new i nie mogą być generatorami.
Co wyróżnia funkcje strzałkowe:
- Leksykalne
this– brak konieczności.bind()w callbackach; - Brak własnego
arguments– preferuj parametry rest...args; - Zwięzła składnia – idealne krótkie callbacki i transformacje;
- Ograniczenia – brak
newiyield, nie nadają się na metody wymagające dynamicznegothis.
Porównanie – kluczowe różnice i kryteria wyboru
Poniższa tabela podsumowuje najważniejsze aspekty trzech typów funkcji:
| Typ | Hoisting | this |
arguments |
new |
Najlepsze zastosowanie |
|---|---|---|---|---|---|
| Deklaracja funkcji | Tak (pełna definicja) | Dynamiczne | Własny obiekt | Tak | API ogólne, rekurencja, wywołania „w przód” |
| Wyrażenie funkcyjne | Nie (tylko nazwa przy var) |
Dynamiczne | Własny obiekt | Tak | Callbacki, funkcje wyższego rzędu, IIFE |
| Funkcja strzałkowa | Nie | Leksykalne | Brak (dziedziczy) | Nie | Krótkie callbacki, transformacje, zachowanie kontekstu |
Zaawansowane koncepcje i wzorce
Kluczowe wzorce funkcyjne, które warto stosować w praktyce:
- Funkcje wyższego rzędu – przyjmują/zwracają funkcje, wspierają kompozycję;
- Domknięcia – funkcje „pamiętają” leksykalny zakres, tworząc prywatny stan;
- IIFE – natychmiastowe wywołanie dla izolacji i inicjalizacji;
- Rekursja (w tym ogonowa) – rozwiązywanie problemów przez podproblemy;
- Aplikacja częściowa i currying – budowanie wyspecjalizowanych funkcji z ogólnych.
Przykład IIFE i domknięcia z prywatnym stanem:
const counter = (function () {
let value = 0;
return {
inc: () => ++value,
get: () => value
};
})();
counter.inc(); // 1
counter.get(); // 1
Praktyczne zastosowania i scenariusze
Obsługa zdarzeń i manipulacja DOM
Tradycyjne funkcje jako handlery mają this wskazujące na element wywołujący zdarzenie, co ułatwia bezpośrednie modyfikacje:
button.addEventListener('click', function () {
this.classList.add('active');
});
Funkcje strzałkowe nie tworzą własnego this, więc w handlerach zależnych od this zwykle nie są właściwym wyborem.
Programowanie asynchroniczne i wywołania zwrotne
Gdy trzeba zachować kontekst instancji w callbacku, funkcje strzałkowe są wygodne:
class Loader {
constructor() { this.done = false; }
init() {
fetch('/api').then(() => { this.done = true; });
}
}
Przetwarzanie tablic i transformacja danych
Funkcje strzałkowe często zapewniają najczytelniejsze transformacje:
const users = [{active:true},{active:false}];
const actives = users.filter(u => u.active).map(u => ({ ...u, ts: Date.now() }));
Dobre praktyki
Dopasuj rodzaj funkcji do potrzeb kodu zgodnie z poniższymi wskazówkami:
- Deklaracje – stosuj do publicznych API modułu, funkcji często wywoływanych i rekurencyjnych;
- Wyrażenia (const/let) – używaj, gdy chcesz kontrolować kolejność inicjalizacji i unikać odwołań „w przód”;
- Funkcje strzałkowe – wybieraj do zwięzłych callbacków oraz tam, gdzie potrzebne jest leksykalne
this; - Metody obiektów/klas – preferuj tradycyjne funkcje, jeśli metoda korzysta z dynamicznego
this; - Argumenty zmienne – zamiast
argumentsużywaj...args(czytelniej i typowo szybciej).