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. 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‘, 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 a nakonec jsem se minulý týden rozepsal o méně známé vlastnosti: 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:

//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:

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

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

//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:

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 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:

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:

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:

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 v různých prohlížečích (se zapnutou konzolí) a uvidíte, že některé vypíšou „nazdar“, jiné „ahoj“.

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.

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

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.