Funkcje

Co to są funkcje

Funkcje to zbiór zgrupowanych instrukcji, które możemy odpalać poprzez podanie ich nazwy. Każda taka funkcja po wywołaniu wykonuje swój wewnętrzny kod, a następnie może zwrócić nam jakąś wartość.

Ogólna deklaracja funkcji ma postać:


function nazwaFunkcji(nr) {
    const result = nr * nr; //możemy też po prostu zwrócić nr * nr
    return result;
}

//Po stworzeniu funkcji wystarczy ją wywołać poprzez podanie jej nazwy:
nazwaFunkcji(2); //4
nazwaFunkcji(3); //9
nazwaFunkcji(5); //25

Funkcję można traktować jak swego rodzaju podprogramy (autor lubi nazywać je klockami, ale, że niektórym dziwnie to się kojarzy...), które w każdej chwili możemy odpalić by wykonać dany kod. Takie funkcje możesz spokojnie trzymać w oddzielnym pliku, który potem będziesz dołączać do wybranych stron. Na tej właśnie zasadzie używa się znanych bibliotek - np. jQuery, Lodash czy podobnych, które są niczym innym jak zbiorem funkcji, które ktoś dla nas napisał.

W tym kursie już nie raz używałeś funkcji:


alert("Ala ma kota");

Math.random();
Math.max(1,2,3);

"ala ma kota".toUpperCase();
"kot i pies".substr(1);

[1,2,3].push(4);
[1,2,3].join("-");

Widzisz te nawiasy na końcu każdej linii? Poprzedza je nazwa danej funkcji. Autorzy JavaScript przygotowali dla nas zestaw funkcji. Miło z ich strony...

Parametry funkcji

Dla każdej funkcji możemy utworzyć parametry. Dzięki nim przy odpalaniu będziemy mogli podawać dla danej funkcji jakieś wartości:


function sum(a, b) {
    return a + b;
}

console.log( sum(2, 3) ); //5
console.log( sum(4, 3) ); //7

function lineText(name, pet) {
    console.log(name + " ma " + pet);
}

lineText("Ola", "kota"); //Ola ma kota
lineText("Ala", "psa"); //Ala ma psa

Jeżeli nasza funkcja wymaga pewnych wartości, a my ich nie podamy, zostaną użyte dla nich wartości undefined:


function writeText(name, age) {
    console.log(`${name} ma kota, który ma ${age} lat`);
}

writeText("Ala", 5); //"Ala ma kota, który ma 5 lat"
writeText("Marysia"); //"Marysia ma kota, który ma undefined lat"
writeText(); //"undefined ma kota, który ma undefined lat"

Wartości do funkcji musimy przekazywać w takiej kolejności, jakiej wymaga tego dana funkcja. Możemy to jednak ominąć za pomocą tak zwanej destrukturyzacji.

Domyślne wartości

Jeżeli funkcja wymaga podania wartości przy jej wywołaniu, a my ich nie podamy, w jej wnętrzu będą one wynosić undefined:


function printText(txt) {
    console.log("Twój tekst to " + txt);
}

printText("kot"); //"Twój tekst to kot"
printText(); //"Twój tekst to undefined"

Dla parametrów możemy też ustawiać domyślne wartości. Wystarczy po nazwie parametru ustawić mu domyślną wartość:


function print(name = "Michał", status = "najlepszy") {
    console.log(name + " jest " + status);
}

print(); //"Michał jest najlepszy"
print("Karol"); //"Karol jest najlepszy"
print("Paweł", "wysoki"); //"Paweł jest wysoki"
print(undefined, "wysoki"); //"Michał jest wysoki" - undefined jest traktowane jak niepodanie wartości

W poprzednich wersjach JavaScript domyślne atrybuty nie istniały, ale i na to były sposoby.

Wystarczy wewnątrz funkcji przeprowadzić test, czy dana zmienna ma wartość:


    function printText(txt) {
        if (typeof txt === "undefined") {
            txt = "brak tekstu";
        }
        console.log(txt);
    }

    //lub

    function printText(txt) {
        txt = (typeof txt === "undefined")? "brak tekstu" : txt;
        console.log(txt);
    }

    printText(); //"brak tekstu"
    

Istnieje też krótsza metoda.


    function printText(txt) {
        txt = txt || "brak tekstu";

        console.log(txt);
    }

    printText(); //"brak tekstu"
    

Trzeba mieć tutaj tylko na uwadze, że jeżeli pod txt podamy pusty ciąg znaków, lub dowolną wartość falsy, wtedy zostanie użyty domyślny "lorem". Nie zawsze jest to dobre rozwiązanie, dlatego polecam zostać przy dłuższym zapisie.


    function printText(txt) {
        txt = txt || "brak tekstu";

        console.log(txt);
    }

    printText(""); //"brak tekstu", a powinno być ""
    
Problem ten ma być rozwiązany w niedalekiej przyszłości. Jedną z kolejnych zmian ma być wprowadzenie operatora ??, który będzie podobny do powyższego, ale będzie pomijał wartości falsy (1, 2).

arguments i rest

JavaScript nie wymaga od nas, abyśmy przekazywali do funkcji wymaganą przez daną funkcję ilość wartości.

Jeżeli nie zakładamy konkretnej liczby parametrów dla funkcji, możemy skorzystać z właściwości arguments, która zawiera w sobie wszystkie przekazane wartości:


function sum() {
    console.log(arguments);
}

sum(); //[] Arguments 
sum(1, 2, 3, 4); //[1, 2, 3, 4] Arguments 
sum("ala", "ma", "kota"); //["ala", "ma", "kota"] Arguments 

Obiekt arguments jest tablico podobny, ale tak naprawdę nie jest tablicą.
Oznacza to, że nie możemy na nim wykonywać metod przeznaczonych dla tablic np. reduce, map i podobnych:


function sumNumbers() {
    const sum = arguments.reduce(function(a, b) { //błąd, reduce jest dla tablic
        return a + b;
    });

    return sum;
}

sumNumbers(1, 2, 3, 4);

W dzisiejszych czasach zamiast operować na obiekcie arguments, zalecane jest używanie rest operator, który zbiera przekazane argumenty w postaci klasycznej tablicy.


function superSum(...r) {
    console.log(r); //[1, 2, 3, 4]
}

superSum(1, 2, 3, 4);

function superSum(...params) {
    console.log(params); //[1, 2, 3, 4]

    const sum = params.reduce(function(a, b) { //można krócej ale na to przyjdzie jeszcze czas
        return a + b;
    });

    return sum;
}

superSum(1, 2, 3, 4);

Dokładnie na ten temat pomówimy sobie w rozdziale o rest parameter.

Instrukcja return

Każda funkcja zwraca jakąś wartość. Domyślnie jest nią undefined. Aby zwrócić naszą wartość, posłużymy się instrukcją return:


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

calculate(10, 4) //wypisze 14

Dzięki temu, że nasza funkcja zwraca jakąś wartość, możemy ją użyć do innych celów niż tylko wypisywanie tekstów w debuggerze:


function randomBetween(min = 0, max = 10) {
    return Math.floor(Math.random()*(max-min+1)+min);
}


//wstawiam wynik do body
document.body.innerText = randomBetween(1, 100);

//wykorzystuję funkcję do powtarzania tekstu
console.log( "kot".repeat(randomBetween(1, 6)) );

//dodaję 2 losowe liczby
console.log( randomBetween(1, 6) + randomBetween(1, 10) );

//generuję tablicę z liczbami 1-100
const tab = [];
for (let i=0; i<10; i++) {
    tab.push(randomBetween(1, 100));
}

if (randomBetween(1, 10)) { //w miejscu gdzie używamy funkcji pojawia się wynik
    ...
}

Instrukcja return nie tylko zwraca wartość, ale i przerywa dalsze działanie danej funkcji.


function sum(a, b) {
    return a + b;

    console.log(a + b); //nigdy nie zostanie wykonane, bo wcześniej return przerwie działanie funkcji
    console.log("Test");
}

W wielu edytorach kod leżący za return będzie miał przytłumione kolory, co symbolizuje, że taki kod nigdy sie nie wykona:

kod po instrukcji return

Instrukcji return może być wiele dla jednej funkcji. Zawsze jednak wykonana zostanie tylko jedna:


function getStatus(number) {
    if (number < 20) {
        return "bad"
    }

    if (number < 30) {
        return "medium"
    }

    return "good"
}

console.log(getStatus(10));
console.log(getStatus(25));

function fixName(name) {
    return name.charAt(0).toUpperCase() + name.slice(1);
}

const result = fixName("piotr") + " " + fixName("kowalski");
console.log(result); //Piotr Kowalski

Instrukcja return może zwracać dowolną wartość. Może to być tablica:


function returnArray(size) {
    return new Array(size).fill(0).map((el, key) => key);
}

const result = returnArray(10); //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

console.log(result[0]); //0

...lub obiekt:


function returnObject() {
    return {
        first: "ala",
        second: "bala",
        third: "cala"
    }
}

console.log(returnObject().first); //"ala"

lub inne funkcje, o czym przekonasz się nieco później.

Wyrażenie funkcyjne

Do tej pory poznaliśmy jeden sposób tworzenia funkcji:


function printText() {
    ...
}

printText();

Jest to tak zwana deklaracja funkcji. Drugi sposób zwie się wyrażeniem funkcyjnym i jest niczym innym jak podstawieniem funkcji pod zmienną:


const printText = function() {
    ...
}

printText();

Wyrażenie i definicja różnią się od siebie nie tylko sposobem zapisu, ale także tym, jak taki kod jest interpretowany przez przeglądarkę.

Funkcja stworzona za pomocą deklaracji jest od razu dostępna dla całego skryptu. Wynika to z działania mechanizmu hoistingu (znany ze zmiennych), który przenosi taką deklarację na początek danego zakresu kodu (skryptu lub funkcji).
Dzięki temu możemy odwoływać się do funkcji, która jest zadeklarowana poniżej:


myFunction(); //Tutaj jest ok

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

W przypadku wyrażeń funkcyjnych takie odwołanie rzuci nam błędem:


myFunction(); //Błąd

const myFunction = function() {
    console.log("...");
}

Wynika to z faktu, że powyżej podstawiliśmy funkcję pod zmienną, a przecież do takich nie możemy się odwoływać przed ich utworzeniem.

Istnieje jeszcze jedna różnica między tymi zapisami. Przy stosowaniu deklaracji, dana funkcja zapisywana jest jako klucz obiektu Window (to samo ma miejsce, gdy tworzymy globalną zmienną za pomocą zmiennej var). W przypadku wyrażenia poprzedzonego słowem const/let nie ma to miejsca.


function testX() {
    console.log("x");
}

const textY = function() {
    console.log("y");
}

window.testX(); //"x"
window.testY(); //błąd
function declaration

Powyższe różnice nie oznaczają jednak, że deklaracji nigdy nie powinieneś używać. Używaj - a jakże. W większości przypadków takie detale nie mają totalnie znaczenia.

Funkcja anonimowa

Funkcja anonimowa to taka funkcja, która nie ma swojej nazwy. Funkcje takie wykorzystywane są jako funkcje zwrotne, które przekazujemy do innych funkcji.


document.addEventListener("click", function() {
    console.log("klik");
});

[1,2,3].forEach(function(el) {
    console.log(el);
});

[1,2,3].sort(function(a, b) {
    return a - b;
});

W dzisiejszych czasach powyższe zapisy możemy skrócić za pomocą tak zwanej funkcji strzałkowej. W kolejnym rozdziale zajmiemy się właśnie nią.

Trening czyni mistrza

Jeżeli chcesz sobie potrenować zdobytą wiedzę, zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.