Funkcje

Gdy mąż słyszy od żony idź do sklepu, kup ziemniaki, wówczas zaczyna wykonywać pewien zbiór czynności. Za każdym razem, gdy usłyszy idź do sklepu, kup ziemniaki, wykona ten sam zbiór czynności (do czasu...). W javascript takie czynności objęte klamrami to właśnie funkcje.

Funkcja jest wywoływana przez inną część skryptu (w realnym życiu przez żonę), a w momencie jej wywołania zostaje wykonywany kod w niej zawarty. Ogólna deklaracja funkcji ma następującą postać:


function nazwaFunkcji() {
    ...kod funkcji
}

nazwaFunkcji(); //wywołujemy funkcję

//lub

var nazwaFunkcji = function() {
    ...kod funkcji anonimowej
}

nazwaFunkcji(); //wywołujemy funkcję

Powyższy przykład wcale nie jest taki błahy. O różnicach w powyższych deklaracjach możesz przeczytać tutaj. Póki co nie zawracaj sobie tym głowy.


function writeSomething() {
    console.log("To jest tekst zawarty w funkcji.");
}

writeSomething(); //odpalamy powyższą funkcję

Funkcję możemy też tworzyć za pomocą konstruktora Function, do którego jako kolejne atrybuty podajemy atrybuty , a w ostatnim przekazujemy jako string kod funkcji.


new Function ([arg1[, arg2[, ...argN]],] functionBody)        

Stosowanie konstruktora jest jednak mało wygodne, dlatego jest to odradzana metoda.

W javascript

Parametry (argumenty) funkcji

Częstokroć konieczne będzie przekazanie do funkcji określonych danych, które następnie zostaną przez nią przetworzone.
Parametry przekazuje się wypisując je między nawiasami występującymi po nazwie funkcji:

        
function calculate(number1, number2) {
    console.log(number1 + ' + ' + number2 + ' = ' + (number1 + number2))
}

calculate(3, 4); //wypisze "3 + 4 = 7"
calculate(7, 7); //wypisze "7 + 7 = 14"
calculate(100, 100); //wypisze "100 + 100 = 200"

Javascript nie wymaga od nas, abyśmy przekazywali do funkcji odpowiednią ilość argumentów.
Jeżeli nie zakładamy konkretnej liczby argumentów dla funkcji, wtedy możemy skorzystać z tablicy arguments, dzięki której możliwe jest pobieranie poszczególnych parametrów:


//wypisz ilość przekazanych argumentów
function calculate() {
    var argLength = arguments.length;

    if (argLength == 0) {
        console.warn('Błąd: Nie podałeś żadnych liczb');
    } else if (argLength == 1) {
        console.warn('Podałeś tylko jedną liczbę:' + arguments[0]);
    } else {
        var result = 0;
        for (i in arguments) {
            result += arguments[i];
        }
        console.log(result);
    }
}

calculate(); //Wypisze "Błąd: Nie podałeś żadnych liczb"
calculate(1); //Wypisze "Podałeś tylko jedną liczbę: 1"
calculate(4,5,6); //Wypisze 15

Instrukcja return

Każda funkcja po zakończeniu działania zwraca jakąś wartość. Może to być jakiś tekst, liczba czy wartość logiczna (prawda / fałsz).
Dzięki zastosowaniu instrukcji return możemy nakazać funkcji zwracanie określonej wartości. Instrukcja ta równocześnie przerywa dalsze działanie funkcji:

    
function calculate(number1, number2) {
    var result = number1 + number2;
    return result;
}

console.log( calculate(10,4) ); //wypisze 14
    
function isEven(number) {
    if (number % 2 == 0) {
        return true;
    } else {
        return false;
    }        
}

var x = 1;
if ( isEven(x) ) {
    console.log('Liczba' + x + ' nie jest parzysta');
} else {
    console.log('Liczba' + x + ' jest parzysta');
}

Zauważyłeś jak w powyższym przykładzie zastosowaliśmy funkcję wraz z instrukcją "if"?
Jeżeli daną funkcję zastosujemy w instrukcji warunkowej if, wtedy zwracana wartość przez tą funkcję zostanie automatycznie przekonwertowana do typu logicznego (true/false). Wszystkie "pełne" wartości zamienią się na true, a puste (0, pusty string itp) zamienią się na false:

    
function checkText(tekst) {
    return tekst;
}

if ( checkText('testujemy') ) {
    console.log('Funkcja zwróciła true'); //to zostanie wypisane
}
if ( checkText('') ) {
    console.log('Funkcja zwróciła false'); //to nie zostanie wypisane
}

Instrukcja return może też zwracać kilka wartości, które grupujemy w tablicę...


function returnArray() {
    return [1, 2, 3] //zwracamy tablicę, którą poznasz niebawem
}

var result = returnArray();

console.log(result[0] + '|' + result[1] + '|' + result[2]); //wypisze 1|2|3

...lub obiekt:


function returnObject() {
    return {
        first: 1,
        second: 2,
        third: 3
    }
}

var result = returnObject();

console.log(result.first + '|' + result.second + '|' + result.third); //wypisze 1|2|3

Czekaj, czekaj. Nie tak prędko!
Do tego wszystkiego jeszcze dojdziemy.
Na chwilę obecną najważniejsze jest byś zapamiętał, że funkcja to zamknięty fragment kodu, który możesz wywołać z "każdego miejsca". Ten fragment kodu może zwracać jakiś wynik, który bardzo łatwo można wykorzystywać przy instrukcjach sprawdzających, równaniach itp.

Różnice w deklaracji funkcji

Na samym początku podałem dwie metody deklaracji funkcji. Pierwsza z nich to tak zwana deklaracja funkcji. Deklaracja taka wymaga podania nazwy funkcji:


//deklaracja funkcji        
function myFunction() {
    ...kod funkcji
}

Drugi sposób zwie się wyrażeniem. Anonimową funkcję (czyli taką, która nie ma nazwy) od razu podstawiamy pod zmienną:


//function jako wyrażenie (expresion)
var myFunction = function() {
    ...kod funkcji anonimowej
}

Różnią się one nie tylko sposobem zapisu, ale także tym, jak taki kod jest interpretowany przez przeglądarkę. Funkcja za pomocą deklaracji jest od razu dostępna dla całego skryptu. Funkcja stworzona za pomocą wyrażenia jest dostępna dopiero po całkowitym przetworzeniu skryptu.


//Tutaj jest ok
myFunction();

function myFunction() {
    console.log('...');
}

//Błąd
myFunction();

var myFunction = function() {
    console.log('...');
}

Istnieje też nieco inne wykorzystanie anonimowych funkcji. Bardzo często stosuje się je w przypadku pracy z zdarzeniami:


document.querySelector('.element').addEventListener('click', function() {
    ...
});

Nie jest to może idealnie zalecana technika pracy z eventami, ale w praktyce często się ją wykorzystuje ze względu na wygodę i lenistwo programistów :)

Zasięg zmiennych

W przypadku funkcji dochodzi dodatkowe pojęcie - zasięgu zmiennych. Do tej pory posługiwaliśmy się zmiennymi globalnymi, które były dostępne dla całego skryptu, wszystkich funkcji i w ogóle całego świata. Rozważmy prosty przykład:


    var x = 10;

    function sum(y) {
        x = x + y;
        return x;
    }
    
    console.log( sum(10) ); //wypisze 20
    console.log( x ); //wypisze 20

Funkcja sum() nie tylko dokonuje wyliczenia sumy liczby, ale także modyfikuje naszą zmienną globalną x.
Pisząc kod funkcji powinniśmy się starać, by była ona zamkniętym bytem. Podajemy do niej jakieś parametry, funkcja to przetwarza za pomocą swojego kodu i zmiennych, po czym kończy swoje działanie zwracając jakiś wynik. Stosując zmienne globalne nie jesteśmy w stanie tego uzyskać, bo każdorazowo zmieniamy zmienne spoza funkcji.

W Javascript możemy także korzystać także ze zmiennych lokalnych, które są dostępne tylko we wnętrzu danej funkcji. Aby zadeklarować taką zmienną wewnątrz funkcji używamy słowa var:


    var x = 1;

    function showX() {
        var x = 2; //wewnętrzna zmienna x dostępna tylko w tej funkcji
        console.log(x); //wypisze 2
    }

    showX();
    console.log(x); //wypisze 1 - jesteśmy poza funkcją

Dzięki stosowaniu zmiennych lokalnych nie tylko unikamy zmian w zmiennych globalnych, ale także unikamy problemu z nazewnictwem zmiennych (co pokazuje powyższy przykład). Wyobraź sobie, że twój skrypt używał by powiedzmy 10000 zmiennych i tyle samo funkcji. Za każdym razem musiał byś się zastanawiać jaką nową nazwę wymyślać, by nie nadpisać poprzednich zmiennych. Przy zmiennych lokalnych ten problem nie istnieje.

Tak naprawdę nie tylko zmienne mogą być lokalne. To samo tyczy się funkcji, które mogą być zagnieżdżone w innych funkcjach:


    function twoSquares(x) {
        function square(y) {
            return y * y
        }

        return square(x) + square(x);
    }

    console.log( twoSquares(3) ); //wypisze 18

    console.log(square(5)); //błąd - funkcja kwadrat jest lokalna

Funkcje w funkcjach jeszcze będą się przewijać w tym kursie, bo stanowią istotną rolę w tak zwanych domknięciach (closures)

Samo wywołująca się funkcja

W JS istnieje pewien wzorzec anonimowej funkcji, która sama się wywołuje. Zanim do niego przejdziemy, powtórzmy to, co powyżej poznaliśmy.

Zadeklarujmy prostą funkcję:


    var foo = function() {...}

Żeby teraz wywołać powyższą funkcję, musimy podać jej nazwę, za którą wstawimy parę nawiasów czyli foo().

Częstokroć jednak wcale nie będziemy potrzebować nazwy funkcji, bo wewnętrzny kod chcielibyśmy wykonać tylko jeden raz. Czyli po definicji funkcji chcielibyśmy od razu ją wywołać:


    function() {...}(); //zwróci błąd

Powyższy kod zwróci błąd. Gdy parser napotka powyższy zapis, potraktuje go jako deklarację (spójrz tutaj). Deklaracja funkcji wymaga podania nazwy, dlatego parser wyrzuci błąd. Aby to naprawić wystarczy powyższy zapis funkcji objąć nawiasami:


    (function() {...})();

I tak właśnie powstał nasz wzorzec samo wywołującej się anonimowej funkcji:


(function() {
    console.log('Jakiś tekst'); //wywoła się od razu
})();

//jest praktycznie równoznaczne z
function foo() {
    console.log('Jakiś tekst');
}
foo();

Z powodu brakującej nazwy powyższą deklarację zamieniliśmy na wzorzec za pomocą dodatkowych nawiasów. Co by się stało, gdyby zamiast zamieniać deklarację, chcielibyśmy od razu wywołać wyrażenie:


    var foo = function() {...}();

Nie stało by się nic. Jest to całkowicie poprawny zapis funkcji, która sama się wywoła.

Tak samo jak w przypadku wywoływania zwykłych funkcji, między nawiasami możemy przekazywać parametry:


(function(win, v) {
    console.log(w.innerHeight);
})(window, ourVariable);

Tak samo też jak w klasycznych funkcjach, wnętrze tej funkcji sprawia, że zmienne i metody tam deklarowane stają się lokalnymi. Sprawia to, że wzorzec samo wywołującej się funkcji jest bardzo często wykorzystywany do zamykania całych skryptów przed zewnętrznym środowiskiem, co zabezpiecza je przed złymi ludźmi.


(function() {
    var x = 10;

    var printNumber = function() {
        console.log(x);
    }

    console.log(x);

    printNumber();
})();

console.log(x); //wyrzuci błąd, bo nie mamy tutaj dostępu do kodu z środka
printNumber(); //także zwróci błąd

Do tego tematu powrócimy gdy będziemy robić moduły.