Jak již vÃme, funkce v JavaScriptu je hodnota.
Každá hodnota v JavaScriptu má svůj typ. Jakého typu je funkce?
V JavaScriptu jsou funkce objekty.
Dobrý způsob, jak si pÅedstavit funkce, je pÅedstavit si je jako âakÄnà objektyâ, které lze volat. Můžeme je nejenom volat, ale i zacházet s nimi jako s objekty: pÅidávat a ubÃrat vlastnosti, pÅedávat je odkazem a tak dále.
Vlastnost ânameâ
FunkÄnà objekty obsahujà nÄkteré užiteÄné vlastnosti.
NapÅÃklad název funkce je dostupný ve vlastnosti name:
function ÅekniAhoj() {
alert("Ahoj");
}
alert(ÅekniAhoj.name); // ÅekniAhoj
Zábavné je, že logika pÅiÅazenà názvu je chytrá a pÅiÅadà korektnà název i funkci, která je vytvoÅena bez názvu a pak okamžitÄ pÅiÅazena:
let ÅekniAhoj = function() {
alert("Ahoj");
};
alert(ÅekniAhoj.name); // ÅekniAhoj (je tady název!)
Funguje to i tehdy, je-li pÅiÅazena jako standardnà hodnota:
function f(ÅekniAhoj = function() {}) {
alert(ÅekniAhoj.name); // ÅekniAhoj (funguje!)
}
f();
Ve specifikaci se tato vlastnost nazývá âkontextuálnà názevâ (âcontextual nameâ). Jestliže funkce neobsahuje vlastnà název, je pÅi pÅiÅazenà detekován z kontextu.
I metody objektů majà názvy:
let uživatel = {
ÅekniAhoj() {
// ...
},
ÅekniNashle: function() {
// ...
}
}
alert(uživatel.ÅekniAhoj.name); // ÅekniAhoj
alert(uživatel.ÅekniNashle.name); // ÅekniNashle
Nenà v tom ovÅ¡em nic magického. Existujà pÅÃpady, kdy nenà způsob, jak zjistit skuteÄný název. V takovém pÅÃpadÄ je vlastnost name prázdná, napÅÃklad zde:
// funkce vytvoÅená uvnitÅ pole
let pole = [function() {}];
alert( pole[0].name ); // <prázdný ÅetÄzec>
// motor nemá jak zjistit správný název, takže tady žádný nenÃ
V praxi vÅ¡ak vÄtÅ¡ina funkcà název má.
Vlastnost âlengthâ
Dalšà vestavÄná vlastnost je length, která vracà poÄet parametrů funkce, napÅÃklad:
function f1(a) {}
function f2(a, b) {}
function mnoho(a, b, ...dalÅ¡Ã) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(mnoho.length); // 2
Zde vidÃme, že zbytkové parametry se nepoÄÃtajÃ.
Vlastnost length se nÄkdy použÃvá pro introspekci ve funkcÃch, které operujà s jinými funkcemi.
NapÅÃklad v nÞe uvedeném kódu funkce zeptejSe pÅijÃmá parametr otázka, kterou položÃ, a libovolný poÄet funkÄnÃch handlerů v parametru handlery, které zavolá.
Jakmile uživatel poskytne odpovÄÄ, funkce zavolá handlery. Můžeme pÅedávat dva druhy handlerů:
- Funkci bez argumentů, která se volá jedinÄ tehdy, když uživatel zadá kladnou odpovÄÄ.
- Funkci s argumenty, která se volá v každém pÅÃpadÄ a vrátà nÄjakou odpovÄÄ.
Abychom zavolali handler správnÄ, prozkoumáme vlastnost handler.length.
MyÅ¡lenkou je, že máme jednoduchou syntaxi handleru bez argumentů pro kladné pÅÃpady (nejÄastÄjšà varianta), ale jsme schopni podporovat i univerzálnà handlery:
function zeptejSe(otázka, ...handlery) {
let jeAno = confirm(otázka);
for(let handler of handlery) {
if (handler.length == 0) {
if (jeAno) handler();
} else {
handler(jeAno);
}
}
}
// pÅi kladné odpovÄdi se volajà oba handlery
// pÅi záporné odpovÄdi se volá jen druhý
zeptejSe("Otázka?", () => alert('Åekl jste ano'), výsledek => alert(výsledek));
Toto je zvláštnà pÅÃpad tzv. polymorfismu â odliÅ¡ného zacházenà s argumenty v závislosti na jejich typu nebo v naÅ¡em pÅÃpadÄ v závislosti na jejich poÄtu v length. Tato myÅ¡lenka je Äasto využÃvána v knihovnách JavaScriptu.
Vlastnà vlastnosti
Můžeme si také pÅidávat svoje vlastnà vlastnosti.
Zde pÅidáme vlastnost ÄÃtaÄ, která poÄÃtá celkový poÄet volánÃ:
function ÅekniAhoj() {
alert("Ahoj");
// spoÄÃtáme, kolikrát jsme tuto funkci volali
ÅekniAhoj.ÄÃtaÄ++;
}
ÅekniAhoj.ÄÃtaÄ = 0; // poÄáteÄnà hodnota
ÅekniAhoj(); // Ahoj
ÅekniAhoj(); // Ahoj
alert( `Voláno ${ÅekniAhoj.ÄÃtaÄ}krát` ); // Voláno 2krát
Vlastnost pÅiÅazená funkci, napÅ. ÅekniAhoj.ÄÃtaÄ = 0, nedefinuje uvnitÅ funkce lokálnà promÄnnou ÄÃtaÄ. Jinými slovy, vlastnost ÄÃtaÄ a promÄnná let ÄÃtaÄ jsou dvÄ různé vÄci.
Můžeme s funkcà zacházet jako s objektem, ukládat do nà vlastnosti, ale to nemá žádný vliv na jejà provádÄnÃ. PromÄnné nejsou vlastnosti funkce a naopak. Jsou to dva paralelnà svÄty.
Vlastnosti funkce nÄkdy mohou nahradit uzávÄry. NapÅÃklad můžeme pÅepsat pÅÃklad funkce ÄÃtaÄe z kapitoly Oblast platnosti promÄnné, uzávÄr tak, že použijeme vlastnost funkce:
function vytvoÅÄÃtaÄ() {
// namÃsto:
// let poÄet = 0
function ÄÃtaÄ() {
return ÄÃtaÄ.poÄet++;
};
ÄÃtaÄ.poÄet = 0;
return ÄÃtaÄ;
}
let ÄÃtaÄ = vytvoÅÄÃtaÄ();
alert( ÄÃtaÄ() ); // 0
alert( ÄÃtaÄ() ); // 1
Nynà je poÄet uložen pÅÃmo ve funkci, ne v jejÃm vnÄjÅ¡Ãm lexikálnÃm prostÅedÃ.
Je to lepšà nebo horÅ¡Ã, než použÃt uzávÄr?
HlavnÃm rozdÃlem je, že jestliže hodnota poÄet pÅebývá ve vnÄjšà promÄnné, externà kód nenà schopen k nà pÅistupovat. Mohou ji modifikovat jedinÄ vnoÅené funkce. Ale jestliže je vázána na funkci, pak je nÄco takového možné:
function vytvoÅÄÃtaÄ() {
function ÄÃtaÄ() {
return ÄÃtaÄ.poÄet++;
};
ÄÃtaÄ.poÄet = 0;
return ÄÃtaÄ;
}
let ÄÃtaÄ = vytvoÅÄÃtaÄ();
ÄÃtaÄ.poÄet = 10;
alert( ÄÃtaÄ() ); // 10
Volba implementace tedy závisà na naÅ¡ich potÅebách.
Pojmenovaný funkÄnà výraz
Pojmenovaný funkÄnà výraz (âNamed Function Expressionâ), zkrácenÄ NFE, je termÃn oznaÄujÃcà funkÄnà výraz, který má nÄjaký název.
VezmÄme si napÅÃklad obyÄejný funkÄnà výraz:
let ÅekniAhoj = function(kdo) {
alert(`Ahoj, ${kdo}`);
};
A pÅidejme mu název:
let ÅekniAhoj = function funkce(kdo) {
alert(`Ahoj, ${kdo}`);
};
Dosáhli jsme tÃm nÄÄeho? Jaký je smysl pÅidaného názvu âfunkceâ?
Nejprve si vÅ¡imnÄme, že stále máme funkÄnà výraz. PÅidánà názvu âfunkceâ za function z nÄj neuÄinilo deklaraci funkce, protože funkce je stále vytvoÅena jako souÄást pÅiÅazovacÃho výrazu.
PÅidánà takového názvu rovnÄž nic nerozbilo.
Funkce je stále dostupná jako ÅekniAhoj():
let ÅekniAhoj = function funkce(kdo) {
alert(`Ahoj, ${kdo}`);
};
ÅekniAhoj("Jan"); // Ahoj, Jan
Název funkce má dvÄ speciálnà vÄci, které jsou důvodem pro jeho použitÃ:
- Název umožÅuje funkci odkazovat se uvnitÅ sebe na sebe sama.
- Název nenà viditelný zvnÄjÅ¡ku funkce.
NapÅÃklad následujÃcà funkce ÅekniAhoj volá sama sebe s parametrem "Host", nenÃ-li pÅedáno kdo:
let ÅekniAhoj = function funkce(kdo) {
if (kdo) {
alert(`Ahoj, ${kdo}`);
} else {
funkce("Host"); // použijeme âfunkceâ k volánà sebe sama
}
};
ÅekniAhoj(); // Ahoj, Host
// Ale tohle nebude fungovat:
funkce(); // Chyba, funkce nenà definována (nenà viditelná zvnÄjÅ¡ku funkce)
ProÄ použÃváme funkce? Možná by pro vnoÅené volánà staÄilo použÃt ÅekniAhoj?
Ve skuteÄnosti ve vÄtÅ¡inÄ pÅÃpadů ano:
let ÅekniAhoj = function(kdo) {
if (kdo) {
alert(`Ahoj, ${kdo}`);
} else {
ÅekniAhoj("Host");
}
};
Problém s tÃmto kódem je, že ÅekniAhoj se může ve vnÄjÅ¡Ãm kódu zmÄnit. Jestliže funkce bude pÅiÅazena do jiné promÄnné, tento kód zaÄne způsobovat chyby:
let ÅekniAhoj = function(kdo) {
if (kdo) {
alert(`Ahoj, ${kdo}`);
} else {
ÅekniAhoj("Host"); // Chyba: ÅekniAhoj nenà funkce
}
};
let vÃtej = ÅekniAhoj;
ÅekniAhoj = null;
vÃtej(); // Chyba, vnoÅené volánà ÅekniAhoj už nefunguje!
To se stane proto, že funkce pÅebÃrá ÅekniAhoj ze svého vnÄjÅ¡Ãho lexikálnÃho prostÅedÃ. Neexistuje lokálnà ÅekniAhoj, takže se použije vnÄjšà promÄnná. A v okamžiku volánà je vnÄjšà ÅekniAhoj rovno null.
Nepovinný název, který můžeme vložit do funkÄnÃho výrazu, je urÄen právÄ k ÅeÅ¡enà problémů tohoto druhu.
Použijme jej k opravÄ naÅ¡eho kódu:
let ÅekniAhoj = function funkce(kdo) {
if (kdo) {
alert(`Ahoj, ${kdo}`);
} else {
funkce("Host"); // Nynà je vÅ¡e v poÅádku
}
};
let vÃtej = ÅekniAhoj;
ÅekniAhoj = null;
vÃtej(); // Ahoj, Host (vnoÅené volánà funguje)
Nynà to funguje, protože název âfunkceâ je funkÄnÄ lokálnÃ. NepÅebÃrá se zvnÄjÅ¡ku (a nenà tam viditelný). Specifikace zaruÄuje, že se bude vždy odkazovat na aktuálnà funkci.
VnÄjšà kód stále má svou promÄnnou ÅekniAhoj nebo vÃtej. A funkce je âinternà funkÄnà názevâ, způsob, jakým tato funkce může spolehlivÄ volat sama sebe.
Popsaná vlastnost âinternà názevâ je k dispozici jen pro funkÄnà výrazy, ne pro deklarace funkcÃ. V deklaracÃch funkcà neexistuje žádná syntaxe, jak pÅidat âinternÃâ název.
NÄkdy, když potÅebujeme spolehlivý internà název, je to důvod, proÄ pÅepsat deklaraci funkce do formy pojmenovaného funkÄnÃho výrazu.
ShrnutÃ
Funkce jsou objekty.
Zde jsme probrali jejich vlastnosti:
nameâ název funkce. Obvykle se pÅebÃrá z definice funkce, ale pokud tam nenÃ, JavaScript se pokusà odhadnout jej z kontextu (tj. z pÅiÅazenÃ).lengthâ poÄet argumentů v definici funkce. Zbytkové parametry se nepoÄÃtajÃ.
Je-li funkce deklarována jako funkÄnà výraz (ne v hlavnÃm bÄhu kódu) a ten obsahuje název, nazývá se pojmenovaný funkÄnà výraz. Název lze použÃvat uvnitÅ funkce, aby se na ni odkazoval, pro rekurzÃvnà volánà a podobnÄ.
Funkce si také může uchovávat pÅidané vlastnosti. Tuto vlastnost zhusta využÃvá mnoho dobÅe známých JavaScriptových knihoven.
VytváÅejà âhlavnÃâ funkci a pÅidávajà k nà mnoho dalÅ¡Ãch âpomocnýchâ funkcÃ. NapÅÃklad knihovna jQuery vytváÅà funkci jménem $. Knihovna lodash vytváÅà funkci jménem _ a pak do nà pÅidává _.clone, _.keyBy a jiné vlastnosti (pokud se o nich chcete dozvÄdÄt vÃc, nahlédnÄte do dokumentace). Ve skuteÄnosti to dÄlajà proto, aby snÞily zamoÅenà globálnÃho prostoru, takže jedna knihovna vytváÅà pouze jednu globálnà promÄnnou. TÃm se snižuje pravdÄpodobnost konfliktů názvů.
Funkce tedy může sama o sobÄ odvádÄt užiteÄnou práci a může také obsahovat hromadu jiných funkcionalit ve svých vlastnostech.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)