Cześć, w dzisiejszym wpisie postaram się omówić jeden z ciekawszych elementów języka Javascript. Będą to mianowicie funkcje. Funkcje stanowią blok kodu, którego zadaniem jest realizowanie ściśle określonego zadania np.: obliczenie pola powierzchni sześcianu lub wyświetlenie komunikatu dla użytkownika.

Funkcje powinny realizować jedno zadania, ale za to dobrze, gdyż to ułatwia jej testowanie. Ponadto kod funkcji możemy używać wielokrotnie, dzięki czemu nie trzeba tworzyć tej samej funkcjonalności na nowo

Tworzenie funkcji

Stworzyć funkcję można na dwa sposoby(istnieje jeszcze trzeci sposób, jednak jest on dostępny w nowszej wersji Javascript – ES6).

// 1 sposób, function declaration or definition
function poleKwadratu(x) {
    return x * x;
}
// 2 sposób, function expression
var poleKwadratu = function(x) {
    return x * x;
}

W obu przypadkach tworzymy funkcję poleKwadratu(x), która przyjmuje jeden argument, a następnie zwraca(ang. return) wartość pola kwadratu.

Wywołanie funkcji jest bardzo proste. Spójrzmy.

//wywołanie funkcji z parametrem 5
console.log(poleKwadratu(5)); // => 25

Dwa wcześniej wspomniane sposoby tworzenia funkcji różnią się odrobinę. Przeanalizujmy to na następującym przykładzie.

console.log(sayHello('Emil')); // => Hi, Emil
console.log(sayHello2('Emil')); // => TypeError: sayHello2 is not a function


function sayHello(name) {
    return 'Hi, ' + name;
}

var sayHello2 = function(name) {
    return 'Hello, ' + name;
}

Jak widzimy, mamy dwie funkcje, których zadaniem jest powitanie użytkownika. Jednak nie możemy wywołać drugiej funkcji. Dzieje się tak za sprawą windowania(ang. hoisting), które sprawia, że funkcje stworzone za pomocą function declaration mogą być wywoływane wcześniej, niż były zadeklarowane. Natomiast funkcje stworzone za pomocą function expression, mogą być tylko użyte po tym, jak zostały zadeklarowane. Wcześniejszy kod przez Javascript może być interpretowany w poniższy sposób.

function sayHello(name) {
    return 'Hi, ' + name;
}

var sayHello2; // ma wartosć undefined

console.log(sayHello('Emil'));  // => Hi, Emil
console.log(sayHello2('Emil')); // =>Uncaught TypeError: sayHello2 is not a function

// przypisywana jest funkcja anonimowa
sayHello2 = function(name) {
    return 'Hello, ' + name;
}

Argumenty przekazywane do funkcji

Do funkcji można przekazywać tyle argumentów, ile się chce. Jeśli wywołamy funkcję(która przyjmuje argument) bez przekazania jej parametru, to wartość parametru wewnątrz funkcji będzie wynosić undefined. Natomiast jeśli przekażemy większa liczbę argumentów niż tego wymaga funkcja, będziemy mieli do nich dostęp za pomocą tablicopodobnego obiektu arguments. 

function notEnough(a,b) {
    console.log(a,b);
}

notEnough('pierwszy'); // => pierwszy undefined
function toMuch(a,b) {
    // arguments przechowuje wszystkie argumenty przekazane do funkcji oraz ich liczbę
    console.log('Liczba argumentów: ' + arguments.length);
    for(var i in arguments) 
        console.log(arguments[i]);
}

toMuch(12,43,6,46,73,24,64);    // Liczba argumentów: 7
                                // => 12 43 6 46 73 24 64

Nowa wersja Javascript(ES6) wprowadza domyślną wartość dla parametrów. Jednak wcześniej JS nie oferował takiej funkcji, dlatego trzeba było sobie radzić inaczej.

function test(x) {
    x = typeof x !== 'undefined' ? x : 'jakaś domyślna wartość';
    console.log(x);
}

test(5); // => 5
test(); // => jakaś domyślna wartość

 

Zakres funkcji

Funkcje w Javascript mogą tworzyć nowy zasięg zmiennych, co oznacza, że zmienne zadeklarowane wewnątrz funkcji nie będą dostępne na zewnątrz. Ponado funkcje mają dostęp do zmiennych, które zostały stworzone w zakresie zewnętrznym np.: globalnym.

var x = 10;
var y = 20;

function test() {
    // zmienna globalna x jest przesłaniana przez lokalną zmienną x
    var x = 5;  
    console.log(x, y); 
    // zmieniana jest wartość globalnej zmiennej y
    y = 30;
}

test(); // => 5 20
console.log(x,y); // 10 30

Zagnieżdżone funkcje

W Javascript istnieje możliwość definiowania funkcji wewnątrz innych. Jest to właśnie zagnieżdżanie funkcji. Funkcja wewnętrzna ma dostęp do zmiennych zadeklarowanych w funkcji zewnętrznej. Natomiast zmienne zadeklarowane w funkcji wewnętrznej pozostają niedostępne dla funkcji zewnętrznej.

function sayHello(name, age) {
    // wewnętrzna funkcja sprawdzająca pełnoletność
    function isAdult() {
        return age >= 18 ? true : false;
    }
    
    // za pomocą wewnętrznej funkcji sprawdzamy czy zostało skończone 18 lat
    if(isAdult())
        console.log('Hi, ' + name + ".You're an adult.");
    else
        console.log('Hi, ' + name + ".You're a child.");
}


sayHello('Emil',18);    // => Hi, Emil.You're an adult.
sayHello('Steve', 12);  // => Hi, Steve.You're a child.

Rekurencja

W sytuacji, gdy funkcja wywołuje samą siebie, mamy do czynienia z rekurencją. Rekurencja potrafi czasami zastąpić działanie pętli, przez co kod może być bardziej czytelny. Jednak korzystanie z rekurencji kosztuje więcej zasobów, gdyż wyniki wywołań są przechowywane w pamięci komputera. Bardzo dobrze zasada działania rekurencji została wyjaśniona na kanale pana Mirosława Zelenta(bardzo gorąco polecam zapoznać się z tym kanałem).

// działanie rekurencji na przykładzie obliczenia silni

// wersja z rekurencją
function silnia(num) {
    if(num === 1)
        return num;
    // tutaj funkcja wywołuje samą siebie
    return num * silnia(--num);
}
console.log(silnia(5)) // => 120

// wersja z pętlą
function silnia2(num) {
    var wynik = 1;
    for(var i = 2; i <= num; i++)
        wynik *= i;
    return wynik;
}

console.log(silnia2(5));  // => 120

Domknięcia

Domknięcia(z ang. Closures) są jednym z ciekawszych możliwości, jakie oferuje Javascript. Domknięcia są związane z zagnieżdżonymi funkcjami. Pozwalają one zachować dostęp do zmiennych, nawet po wykonaniu funkcji. Lepiej to zostanie wyjaśnione na przykładzie.

function makeAdder(a) {
    function add(b) {
        return a + b;
    }
    // funkcja add będzie pamiętać wartość zmiennej a
    return add;
}

var add2 = makeAdder(2);
console.log(add2(5)); // => 7
console.log(add2(10)); // => 12

var add5 = makeAdder(5);
console.log(add5(6)); // => 11
console.log(add5(1)); // => 6

Widzimy, że mamy funkcję makeAdder, która zwraca kolejną funkcję add. Funkcja add zachowa dostęp do zmiennej a, dzięki czemu wynik działania funkcji add2 i add5 będzie prawidłowy.

Podsumowanie

To już koniec. Myślę, że wpis się podobał. W następnym wpisie rozwinę wątek funkcji związany z tworzeniem nowych obiektów za pomocą konstruktorów.

Zapraszam do komentowania i zadawania pytań oraz sugestii jak można ulepszyć wpisy.