Funkce v JavaScriptu
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu
Reklama

Funkce v JavaScriptuFunkce v JavaScriptu

 

Funkce v JavaScriptu

Google       Google       2. 8. 2008       31 009×

JavaScript – mnohými milovaný, mnohými nenáviděný, mnohými nepochopený jazyk, mnohými všechno dohromady. Mnoho nejasností pramení z jeho práce s funkcemi a jejich volání. A proto si v dnešním článku ukážeme, jak to vlastně je.

Reklama
Reklama

Tento článek se nebude zabývat naprostými základy, jakými jsou např. definování proměnných, výrazy, operátory, řídící struktury apod. K těmto účelům jsou zde jiné zdroje [Kurz JavaScriptu, Seriál JavaScript].

Ač to možná někteří neví anebo to opomíjejí, funkce jsou v JavaScriptu tzv. „first-class“ hodnoty. Neboli se s nimi může nakládat jako s jakýmikoli jinými hodnotami – dají se ukládat do proměnných, předávat jako parametry funkcí a vracet z funkcí. Je to díky tomu, že funkce jsou vlastně objekty.

Javascriptové objekty jsou kapitola sama pro sebe, takže jenom stručně. JavaScript nepodporuje „klasickou“ dědičnost známou z dnes nejrozšířenějších jazyků podporujících objektově orientované programování – třídní dědičnost. JavaScript je založen tzv. dědičnosti prototypové, neboli beztřídní. To znamená, že se v něm objekty neřadí k žádným třídám, které by určovaly jejich chování, ale objekty jsou samostatné entity, kdy dva mohou třeba vykazovat naprosto stejné chování, ale není nijak určeno, že by měly.

Klíčem ke sdílení vlastností mezi objekty jsou tzv. prototypy. Může se k nim přistupovat pomocí vlastnosti prototype každého objektu (tedy i funkcí).

Nyní se již vrhněme k tomu, jak definovat funkci.

// 1. vytvořením funkčního objektu
var add = new Function("a", "b", "return a + b;");

// 2. funkčním literálem
var add = function (a, b) {
    return a + b;
};

// 3. funkčním literálem se syntaktickým cukrem
function add(a, b) {
    return a + b;
}

Všechny tři zápisy jsou ekvivalentní. První způsob (pomocí vytvoření nového funkčního objektu) není moc obvyklý, proto ho berme jen jako zajímavost a dál se jím již nezabývejme. Druhý mezi některými také nemusí být příliš obvyklý, ale je velice názorný v tom, jak zvýrazňuje hlavní vlastnost javascriptových funkcí – jejich hodnost jako „first-class“ objektů. Třetí je nejspíš nejobvyklejší, protože syntaktický cukr je přeci jen hezká věc, a navíc jelikož pro mnoho lidí není JavaScript jejich „hlavní“ jazyk, ale většinou jen spíše pomůcka, jak zinteraktivnit jejich aplikace, tento zápis jim připomíná syntaxi jejich jazyka. Srovnejme třetí příklad definice kupř. s PHP:

function add($a, $b) {
    return $a + $b;
}

Po zbytek článku bude používána druhá metoda definice funkcí, aby byla zvýrazněna jejich „objektovost“ :o). Hlavní využití třetího způsobu bych ponechal pro rekurzivní funkce. Jelikož pak můžeme například dělat takovéhle kusy:

var return_factorial = function () {
    return function fac(n) {
        if (n < 2) {
            return 1;
        } else {
            return n * fac(n - 1);
        }
    };
};

var my_factorial = return_factorial();

alert("5! = " + my_factorial(5));

Praktické využití tohoto příkladu je nulové. Takže pokud vás napadne nějaké lepší, budu rád, pokud se podělíte v diskusi pod článkem s nějakým lepším.

Volání funkcí

Právě u volání funkcí někdy bývají nejasnosti, hlavně co se týče toho, k jaké hodnotě se bude vázat funkční proměnná this. Dokonce existují čtyři způsoby volání funkcí. Pojďme se tedy na ně podívat.

Funkční volání

Prvním a asi nejjednodušším voláním je „funkční volání“ (funcion invocation pattern). Je to totéž, co jsme doposud ve článku mohli vidět. Při funkčním volání je v this uložen tzv. „globální objekt“. Snad vždy se jedná o objekt window. (Pokud byste věděli více, určitě se vyjádřete pod článkem.) Pro příklady funkčního volání se podívejte na ukázky kódu výše. Dá se říci, že při funkčním volání je nám this všeho všudy k ničemu.

Volání s operátorem new

Operátor new slouží, podobně jako v jiných jazycích, k vytváření objektů. Při přidání new před volání funkce se tak funkce stává tzv. „konstruktorem“, tedy metodou objektu, která inicializuje jeho stav. Jelikož v JavaScriptu neexistují třídy, všechno je řešeno pomocí prototypů. Takže při volání konstruktoru se vytvoří nový prázdný objekt, kterému se do prototypu zkopíruje prototyp volané funkce, a pak se již vykonává samotný konstruktor, kdy hodnotou proměnné this je ten nově vytvořený objekt.

var Cat = function (name) {
    this.name = name;
};

var my_cat = new Cat("lucy");

alert(my_cat);
alert(my_cat.name); // lucy

Velký problém nastává tehdy, když je volána „konstruktorová“ metoda ne jako konstruktor – metoda stavěná na to, aby byla volána s operátorem new je volána bez tohoto operátoru. To se pak veškerá inicializace provede do globálního objektu a jelikož konstruktory jen málokdy vrací nějaké hodnoty (při volání s operátorem new je taková hodnota, pokud se nejedná o objekt, stejně zahozena a vrácen je nový objekt), dostane se nám do proměnné, která měla obsahovat inicializovaný objekt, hodnota undefined.

Volání jako metody objektu

Třetím případem volání může být takové, pokud máme nějaký objekt a pomocí tečkové notace (.) přistupujeme k jeho vlastnosti (objekt.vlastnost) a voláme ji jako metodu (objekt.vlastnost()). Je nejspíš logické, co se pak bude skrývat pod this – ano, objekt, nad kterým je metoda volána (this === objekt). Pro pochopení bude asi nejlepší rozšířit příklad s naší kočkou.

Cat.prototype.sayMeow = function () {
    alert(this.name + " says meow!");
};

var my_cat = new Cat("lucy");
my_cat.sayMeow(); // lucy says meow!

Volání pomocí call() a apply()

Jak již bylo řečeno, funkce jsou vlastně objekty, takže na nich jdou také volat metody. Jedněmi z nich, zděděné z funkčního prototypu, jsou metody call() a apply(). Obě jsou si prakticky stejné – slouží k vyvolání funkce. První parametr obou funkcí je objekt, který bude přiřazen do this. Jediný rozdíl mezi oběma funkcemi je v tom, že při volání call() jsou parametry pro volanou funkci všechny parametry call() krom prvního (první parametr je this), zatímco apply() přijímá parametry v poli jako druhý argument.

add.call(null, 2, 3); // 5

add.apply(null, [2, 3]); // 5

Argumenty

Všechny argumenty funkcí jsou v JavaScriptu nepovinné a nelze nijak zajistit, aby povinné byly. Na jednu stranu to může být dobré, na druhou zase moc ne. Parametry deklarované při definici funkce jsou jen formální a slouží hlavně k tomu, abychom se na ně ve funkci mohli odkazovat jménem.

Jinak je v každé funkci totiž přítomen objekt arguments, který obsahuje pod číselnými indexy všechny argumenty funkci předané (první argument je na indexu 0). Můžeme říct, že je to něco jako pole, ale pole samotné (objekt Array) to není (což je mimochodem veliká designová chyba jazyka, ale nadělat s tím moc nemůžeme). Nad arguments i přesto, že obsahuje vlastnost length jako pole, nemůžeme volat metody pole.

var sum = function () {
    var i, sum = 0;
    for (i = 0; i < arguments.length; ++i) {
        sum += arguments[i];
    }
    return sum;
};

sum(1, 2, 3, 4, 5); // 15

Platnost proměnných a closures (uzávěry)

JavaScript na rozdíl od jiných jazyků nemá příliš propracovaný systém bloků, ale jak se říká, v jednoduchosti je síla. Jelikož zde nejsou žádné jmenné prostory, žádné třídy ani nic podobného, základní jednotkou oboru platnosti proměnných je funkce. (Kromě funkce je zde samozřejmě také, jak bývá zvykem, globální prostor jmen.)

Jelikož se mohou funkce zanořovat – lze definovat funkci uvnitř těla jiné funkce –, každá taková zanořená funkce má dostupné všechny proměnné, které jsou dostupné z funkce nadřazené.

var d = 8;
var foo = function () {
    var a = 1, b = 2;

    // a = 1, b = 2, d = 8

    d = 10;

    // a = 1, b = 2, d = 10

    var bar = function () {
        var b = 1, c = 8

        // a = 1, b = 1, c = 8, d = 10

        a = 5;
        b = 3;
        d = 20;

        // a = 5, b = 3, c = 8, d = 20
    };

    bar();

    alert("a = " + a); // a = 5
    alert("b = " + b); // b = 2
                       // c neexistuje
};

foo();

alert("d = " + d); // d = 20

S vědomím tohoto se dají vyrábět tzv. closures, což jsou funkce, které v sobě obsahují kontext z jiné funkce, ve které byly definované, i když vykonávání jejich „mateřské“ funkce už skončilo. Zní to složitě, takže lepší bude nějaký příklad. Řekněme, že potřebujeme objekt reprezentující osobu. Jelikož lidé mohou růst, bude zde samozřejmě možnost postupně časem měnit výšku takové osoby. První řešení, jaké by nás mohlo napadnout, by mohlo vypadat takto:

var person = function (meters) {
    this.height = meters;
};

Osobu vytvoříme voláním funkce person() s operátorem new (aby se vytvořil nový objekt, který bude přiřazen do this). K výšce můžeme po vytvoření přistupovat pomocí vlastnosti height. Je to velice jednoduché, ale je zde problém, že výšku nemůžeme nijak regulovat. Takže se nám zde pak klidně může vyskytnout osoba měřící místo pár metrů i pár stovek (kilo)metrů, ba dokonce i nějaká, která by „rostla do země“ (výška by byla záporná), či persona nulového vzrůstu (i takový Kulihrášek musel mít přinejmenším pár milimetrů). Dalším řešením by mohlo být zavedení tzv. „getterů“ a „setterů“, takže by stačilo přidat:

person.prototype.getHeight = function () {
    return this.height;
};

person.prototype.setHeight = function (meters) {
    if (meters > 0 && meters < 5) {
        this.height = meters;
        return true;
    }
    return false;
};

Mohlo by se zdát, že problém je vyřešen, ale jelikož JavaScript nezná žádnou viditelnost vlastností objektů, pořád můžeme měnit výšku i jinak než přes její setter – stačí normálně změnit vlastnost height. Řešením jsou closures.

var person = function (meters) {

    this.getHeight = function () {
        return meters;
    };

    this.setHeight = function (new_meters) {
        if (new_meters > 0 && new_meters < 5) {
            meters = new_meters;
            return true;
        }
        return false;
    };
};

Nyní se již nemůžeme k výšce dostat jinak než pomocí getteru a setteru.

Na závěr

Funkce jsou až na pár malých nedostatků nejsilnější zbraní JavaScriptu, který je hlavně díky nim nazýván „Lispem v kabátě Céčka“. Samozřejmě jako se všemi mocnými zbraněmi je potřeba umět s nimi zacházet správně. Doufám, že vám tento článek poskytl lepší představu, jak tak činit.

×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

Hlasování bylo ukončeno    
0 hlasů
Google
(fotka) Jakub KulhanAutor momentálně studuje na osmiletém gymnáziu v Kralupech nad Vltavou. Programování se věnuje od 11 let, kdy ho poprvé uchvátila možnost "mít vlastní stránky". Nakrátko poté objevil PHP a už se to s ním "vezlo". Webové aplikace zůstaly jeho hlavní doménou, ale ve svém volném čase probádává nejrůznější zákoutí světa programování, programovacích jazyků a všeho kolem nich.
Web    

Nové články

Obrázek ke článku Hackerský kongres přiveze v září do Prahy špičky světové kryptoanarchie

Hackerský kongres přiveze v září do Prahy špičky světové kryptoanarchie

Hackerský kongres HCPP16 pořádá od 30. září do 2. října nezisková organizace Paralelní Polis již potřetí, a to ve stejnojmenném bitcoinovém prostoru v pražských Holešovicích. Letos přiveze na třídenní konferenci přes 40 většinou zahraničních speakerů – lídrů z oblastí technologií, decentralizované ekonomiky, politických umění a aktivismu. Náměty jejich přednášek budou také hacking, kryptoměny, věda, svoboda nebo kryptoanarchie.

Reklama
Reklama
Obrázek ke článku ICT PRO školení zaměřené nejenom na ICT

ICT PRO školení zaměřené nejenom na ICT

Dovolte, abychom se představili. Jsme zaměstnanci společnosti ICT Pro, profesionálové v oblasti poskytování komplexních ICT služeb. Neboli služeb spojených s informačními a komunikačními technologiemi, které dnes - ve 21. století - tvoří  nedílnou součást běžného provozu všech moderních firem.

loadingtransparent (function() { var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; po.src = 'https://apis.google.com/js/plusone.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); })();
Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032016 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý