Co byste měli vědět o JavaScriptu [4]: deklarace funkce a funkce jako výraz

Vítejte u dalšího dílu mého “seriálu o méně známých zákoutích JavaScriptu”:http://zapisnik.pepiino.cz/co-byste-meli-vedet-o-javascriptu-0-uvod/. Pravidelný čtenář si zajisté všiml, že první tři díly měly něco společného. Všechny se nějak dotýkaly platnosti proměnných v JavaScriptu. Dnešní článek nebude výjimkou a bude trochu delší i náročnější, takže doporučuji lehce si osvěžit paměť prolétnutím předcházejících témat.

V prvním díle jsem psal o “deklaraci lokálních proměnných, klíčovém slovu `var` a globálním ‘scope'”:http://zapisnik.pepiino.cz/co-byste-meli-vedet-o-javascriptu-1-promenna-a-globalni-obor-platnosti/, navázal jsem krátkou zmínkou o tom, “že v JavaScriptu jsou lokální proměnné platné vždy v rámci celé funkce”:http://zapisnik.pepiino.cz/co-byste-meli-vedet-o-javascriptu-2-javascript-nema-block-level-scope/ a nakonec jsem se minulý týden rozepsal o méně známé vlastnosti: “variable hoisting”:http://zapisnik.pepiino.cz/co-byste-meli-vedet-o-javascriptu-3-variable-hoisting/.

Funkce jako hodnota
******************
Asi jednou z nejhezčích vlastností JavaScriptu je, že se funkce chovají naprosto stejně jako jakýkoliv jiný referenční datový typ. Můžete je ukládat do proměnných, předávat jako parametr jiným funkcím a dokonce i vracet jako návratovou hodnotu. (V angličtině se používá termín *first class object*, který ale nemá s objekty ani třídami z OOP nic společného).

Předpokládám, že každý čtenář dokáže bezpochyby funkci deklarovat:
/—html

//třeba takto
function ahoj() {
   //....
}

//nebo takto
var ahoj = function() {
   //...
}


//i takto
var ahoj = function ahoj() {
   //...
}

//všechny tři varianty voláme stejně, oslovujeme dle nálady
ahoj();

\—

Ve všech třech příkladech vznikne v aktuálním kontextu (oboru platnosti) proměnná s názvem `ahoj` odkazující na naši novou funkci.

Deklarace versus výraz
**********************
Abychom neměli život příliš jednoduchý, je mezi prvním výše uvedeným příkladem a jeho následovníky podstatný rozdíl. Z hlediska syntaxe JavaScriptu existují dvě konstrukce, které lze použít k vytvoření funkce: *function declaration* (deklarace) a *function expression* (výraz).

Zatímco deklarace má pouze jednu formu:
/—html

//deklarace
function ahoj(parametr, dalsiparametr) {
   //....
}

\—
Výraz může nabývat rozličných tvarů, chutí i vůní:
/—html

//anonymní funkce přiřazená do proměnné
var ahoj = function(parametr, dalsiparametr) {
   //....
}
//pojmenovaná funkce přiřazená do proměnné
var ahoj = function nazdar(parametr, dalsiparametr) {
   //....
}

\—
Jak interpret JavaScriptu (nebo třeba programátor) pozná, zda jde v konkrétním případě o deklaraci či výraz? Je potřeba mít na paměti, že název funkce je u deklarace povinný a u výrazu volitelný, takže jeho nepřítomnost nelze považovat za rozhodující. Rozhodující pravidlo by mohlo být formulováno třeba takto: je-li v daném kontextu očekávána hodnota (přiřazení do proměnné, návratová hodnota, ale i další) bude konstrukce zpracována jako výraz, jinak jako deklarace. Těžko představitelné? Lze použít i lehce zjednodušující pomůcku: začíná-li příkaz (*statement*) klíčovým slovem function jde o deklaraci, jinak o výraz.

Srovnejte:
/—html

function ahoj() {...} //vyhodnocena jako deklarace

(function ahoj() {...}) //vyhodnocena jako výraz

function() {...} //je vyhodnocována jako deklarace, pak je ale očekáváno povinné jméno => dojde k syntaktické chybě

return function() {...} //zde již je vyhodnocována jako výraz 

new function() {...}; // operátor new aplikovaný na anonymní funkci, která je opět vyhodnocena jako výraz. Funkce poslouží jako konstruktor.

\—-

Deklarace funkce se vznášejí také, ale jinak
********************************************
“Podobně jako u deklarací proměnných”:http://zapisnik.pepiino.cz/co-byste-meli-vedet-o-javascriptu-3-variable-hoisting/ dochází k “vznášení” identifikátorů funkcí. Pokud stvoříte funkci pomocí výrazu, aplikuje se *variable hoisting* podle totožného principu jako u ostatních proměnných (což nás nijak nepřekvapí). Použijete-li ale deklaraci, probublá na začátek nadřazeného kontextu nejen identifikátor, ale i tělo (nebo chcete-li hodnota) funkce.

Takže následující ukázka:
/—html

function tulipan() {
    //... kód
    var ahoj = function(parametr, dalsiparametr) {
        console.log("ahoj");
    }    
    //... kód
    function nazdar() {
        console.log("nazdar");
    }
    
}

\—
Je interpretována nějak takto:
/—html

function tulipan() {
    var ahoj, nazdar = function nazdar() {
        console.log("nazdar");
    };

    //... kód
    ahoj = function(parametr, dalsiparametr) {
        console.log("ahoj");
    }
    //... kód
}

\—
Můžete si “zkusit následující příklad”:http://jsfiddle.net/pepiino/UDfqT/:
/—html

function tulipan() {
    console.log(typeof ahoj); //=> undefined
    console.log(typeof nazdar); //=> function;
    //ahoj(); //odkomentujte a dojde k chybě
    nazdar(); //=> "nazdar"

    var ahoj = function(parametr, dalsiparametr) {
        console.log("ahoj");
    }    
    console.log(typeof ahoj); //=> "function"
    ahoj(); //=> "ahoj"

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

tulipan();

\—

Trable s bloky
**************
V souvislosti s deklaracemi funkcí musím zmínit jednu z nekompatibilit mezi současnými interprety JavaScriptu. Z hlediska specifikace totiž nesmí být deklarace funkce zanořena v blocích podmínek, cyklů a podobně. Všechny implementace JavaScriptu ale takovou konstrukci akceptují a chovají se různě.

“Vyzkoušejte si následující ukázku”:http://jsfiddle.net/pepiino/gkrWp/ v různých prohlížečích (se zapnutou konzolí) a uvidíte, že některé vypíšou “nazdar”, jiné “ahoj”.
/—html

function tulipan() {

   if (true) {
       function ahoj() {
           console.log("ahoj");
       }
   } else {
       function ahoj() {
           console.log("nazdar");
       }
   }

  
   ahoj();
   //Chrome a IE vypíšou 'nazdar'
   //Firefox vypíše 'ahoj'
}

tulipan();

\—

Používat deklarace?
******************
Nejsilnější výhodou deklarací je možnost volat funkci dříve, než je v kódu uvedena (u výrazu sice proměnná díky *hoistingu* existuje, ale má hodnotu `undefined`). Jako další argument pro používání deklarací se často uvádí lepší ladění v debuggerech – díky tomu, že funkce jsou pojmenované. Pozorný čtenář si ale v příkladech zajisté všiml pojmenované funkce i ve výrazech.

/—html

//pojmenovaná funkce přirazená do proměnné
var ahoj = function nazdar(parametr, dalsiparametr) {
   //....
}

\—html
Tato konstrukce problém řeší, leč bohužel nefuguje úplně korektně v Internet Exploreru (pro podrobnější popis viz na konci odkázaný článek).

Při vší skromnosti si myslím, že deklarace funkce je v JavaScriptu omylem. Respektive záměrně, ale jen aby usnadnila tvorbu kódu programátorům přicházejícím od jiných jazyků, kde jsou na podobnou konstrukci zvyklí. Poměrně se vymyká základní myšlence práce s funkcemi v JavaScriptu a působí víc zmatení, než užitku.

Nestačí-li vám můj stručný popis problematiky, doporučuji zevrubné a asi nejlepší zpracování tématu od Juriye ‘kangax’ Zaytseva: “Named function expressions demystified”:http://kangax.github.com/nfe/.