Lekcja 6: Funkcje

Funkcje to bloki kodu, które można zdefiniować raz i wywoływać wielokrotnie w różnych miejscach programu. Pozwalają na organizację kodu, jego reużywalność i tworzenie bardziej złożonych aplikacji.

Definiowanie funkcji

Istnieje kilka sposobów definiowania funkcji w JavaScript:

Deklaracja funkcji (Function Declaration)

Najbardziej klasyczny sposób. Funkcje zadeklarowane w ten sposób są poddawane hoistingowi, co oznacza, że można je wywołać przed ich definicją w kodzie.

function nazwaFunkcji(parametr1, parametr2) {
    // Ciało funkcji - kod do wykonania
    console.log("Parametr 1:", parametr1);
    console.log("Parametr 2:", parametr2);
    // Opcjonalnie: zwracanie wartości
    return parametr1 + parametr2;
}

// Wywołanie funkcji
let wynik = nazwaFunkcji(5, 10);
console.log("Wynik funkcji:", wynik); // Wynik funkcji: 15

Wyrażenie funkcyjne (Function Expression)

Funkcja jest przypisywana do zmiennej. Takie funkcje nie są hoistowane - nie można ich wywołać przed definicją.

const mojaFunkcja = function(parametr) {
    console.log("Wywołano wyrażenie funkcyjne z parametrem:", parametr);
    return parametr * 2;
};

// Wywołanie funkcji
let rezultat = mojaFunkcja(7);
console.log("Rezultat:", rezultat); // Rezultat: 14

Wyrażenia funkcyjne mogą być anonimowe (jak powyżej) lub nazwane (co jest przydatne przy debugowaniu).

Funkcje strzałkowe (Arrow Functions - ES6)

Bardziej zwięzły sposób zapisu funkcji, wprowadzony w ES6. Mają kilka istotnych różnic w zachowaniu (np. dotyczących słowa kluczowego this), które omówimy później.

// Najprostsza forma
const powitaj = () => {
    console.log("Witaj!");
};

powitaj(); // Witaj!

// Z jednym parametrem (nawiasy opcjonalne)
const kwadrat = x => {
    return x * x;
};
// Lub jeszcze krócej (niejawny return)
const kwadratKrotko = x => x * x;

console.log(kwadrat(4)); // 16
console.log(kwadratKrotko(5)); // 25

// Z wieloma parametrami
const suma = (a, b) => {
    return a + b;
};
// Lub krócej
const sumaKrotko = (a, b) => a + b;

console.log(suma(3, 8)); // 11
console.log(sumaKrotko(10, 2)); // 12

Parametry i argumenty

Parametry domyślne (ES6)

Można zdefiniować domyślne wartości dla parametrów, które zostaną użyte, jeśli argument nie zostanie przekazany.

function przywitaj(imie = "Gościu") {
    console.log(`Witaj, ${imie}!`);
}

przywitaj("Anna"); // Witaj, Anna!
przywitaj();      // Witaj, Gościu!

Parametry resztowe (Rest Parameters - ES6)

Pozwalają na zebranie dowolnej liczby pozostałych argumentów w tablicę.

function sumujWszystko(...liczby) {
    let suma = 0;
    for (const liczba of liczby) {
        suma += liczba;
    }
    return suma;
}

console.log(sumujWszystko(1, 2, 3));       // 6
console.log(sumujWszystko(10, 20, 30, 40)); // 100
console.log(sumujWszystko(5));           // 5
console.log(sumujWszystko());          // 0

Zwracanie wartości (`return`)

Instrukcja return kończy wykonanie funkcji i opcjonalnie zwraca wartość do miejsca wywołania. Jeśli funkcja nie ma instrukcji return lub ma return bez wartości, domyślnie zwraca undefined.

function czyPelnoletni(wiek) {
    if (wiek >= 18) {
        return true;
    } else {
        return false;
    }
    // Lub krócej: return wiek >= 18;
}

let status = czyPelnoletni(25);
console.log("Czy pełnoletni?", status); // Czy pełnoletni? true

function nicNieZwraca() {
    console.log("Ta funkcja nic nie zwraca jawnie.");
}

let wynikPusty = nicNieZwraca();
console.log("Wynik pustej funkcji:", wynikPusty); // Wynik pustej funkcji: undefined

Zakres zmiennych (Scope)

Określa, gdzie w kodzie zmienne są dostępne.

let globalnaZmienna = "Jestem globalna";

function mojaFunkcja() {
    let lokalnaZmienna = "Jestem lokalna";
    console.log(globalnaZmienna); // Dostęp do zmiennej globalnej
    console.log(lokalnaZmienna);  // Dostęp do zmiennej lokalnej

    if (true) {
        let blokowaZmienna = "Jestem blokowa";
        console.log(blokowaZmienna);
    }
    // console.log(blokowaZmienna); // Błąd: blokowaZmienna is not defined
}

mojaFunkcja();
// console.log(lokalnaZmienna); // Błąd: lokalnaZmienna is not defined

Zadanie praktyczne

Napisz funkcję o nazwie obliczPoleProstokata, która przyjmuje dwa parametry (długości boków a i b) i zwraca pole tego prostokąta. Wywołaj funkcję z przykładowymi wartościami i wyświetl wynik w konsoli.

Pokaż rozwiązanie
// Deklaracja funkcji
function obliczPoleProstokata(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number' || a <= 0 || b <= 0) {
        return "Nieprawidłowe wymiary"; // Obsługa błędnych danych
    }
    return a * b;
}

// Wywołanie funkcji
let bok1 = 7;
let bok2 = 4;
let pole = obliczPoleProstokata(bok1, bok2);

console.log(`Pole prostokąta o bokach ${bok1} i ${bok2} wynosi: ${pole}`); // Wynik: 28

// Test z błędnymi danymi
console.log(obliczPoleProstokata(5, -2)); // Wynik: Nieprawidłowe wymiary
console.log(obliczPoleProstokata(10, "abc")); // Wynik: Nieprawidłowe wymiary

Zadanie do samodzielnego wykonania

Napisz funkcję strzałkową o nazwie znajdzMax, która przyjmuje dowolną liczbę argumentów (użyj parametrów resztowych) i zwraca największą z przekazanych liczb. Przetestuj funkcję, przekazując jej różne zestawy liczb.

FAQ - Funkcje

Jaka jest główna różnica między deklaracją funkcji a wyrażeniem funkcyjnym?

Główna różnica to hoisting. Deklaracje funkcji są "podnoszone" na górę swojego zakresu, więc można je wywołać przed ich definicją w kodzie. Wyrażenia funkcyjne nie są hoistowane i muszą być zdefiniowane przed pierwszym użyciem.

Kiedy używać funkcji strzałkowych?

Funkcje strzałkowe są świetne dla krótkich, anonimowych funkcji (np. jako callbacki). Ich zwięzła składnia jest bardzo wygodna. Mają też inne zachowanie this, co jest kluczowe w metodach obiektów i obsłudze zdarzeń (omówimy to później).

Czy funkcja może wywoływać samą siebie?

Tak, nazywa się to rekurencją (lub rekursją). Funkcja rekurencyjna wywołuje samą siebie, aby rozwiązać mniejszą wersję tego samego problemu. Ważne jest, aby miała warunek bazowy (kończący), który zapobiega nieskończonemu wywoływaniu.

Co to jest funkcja zwrotna (callback)?

Funkcja zwrotna (callback) to funkcja przekazana jako argument do innej funkcji, która ma zostać wywołana później, zazwyczaj po zakończeniu jakiejś operacji (np. asynchronicznej). Są one powszechnie używane w JavaScript, np. w obsłudze zdarzeń czy operacjach asynchronicznych.

Czy kolejność parametrów ma znaczenie?

Tak, kolejność argumentów przekazywanych podczas wywołania funkcji musi odpowiadać kolejności parametrów w jej definicji. Jeśli przekażesz mniej argumentów niż parametrów, pozostałe parametry otrzymają wartość undefined (chyba że mają wartości domyślne).

Co to jest IIFE (Immediately Invoked Function Expression)?

IIFE to wyrażenie funkcyjne, które jest definiowane i wywoływane natychmiast. Służy do tworzenia prywatnego zakresu dla zmiennych, aby uniknąć zanieczyszczania zakresu globalnego. Przykład: (function() { /* kod */ })();.