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.


