jQuery a další frameworky často používají speciální techniku pro vytváření JavaScriptových funkcí, kdy, místo aby uváděli zdrojový kód pro každou funkci, nadefinují jména funkcí a obecný kód, který funguje pro všechny funkce.
Podívejme se podrobněji na výhody a nevýhody tohoto přístupu a postupy, jak ho použít k vlastnímu prospěchu.
Plusy…
Hlavní důvod, proč se tato technika používá, je úspora kódu a tedy velikosti dat, které je potřeba přenést do prohlížeče uživatele.
V angličtině se tato technika nazývá DRY, tedy „Don’t Repeat Yourself“ (česky „neopakuj se“). Opak se nazývá WET, což může znamenat „We Enjoy Typing“ (česky „rádi píšeme“); ve skutečnosti je to ale jen hříčka se slovy dry (suchý) a wet (mokrý).
Porovnejte tyhle dva kódy:
this.getName = function() { return this.name; } this.getId = function() { return this.id; } this.getStreet = function() { return this.street; } this.getCity = function() { return this.city; } this.getCountry = function() { return this.country; } this.getState = function() { return this.state; }
$.each(['name', 'id', 'street', 'city', 'country', 'state']), function(i, prop) { obj['get'+prop.cap()] = function() { return this[prop]; } } });
Na první pohled je vidět, že druhý kód je kratší (zhruba poloviční) – a to definuje pouze 6 getterů. Pro větší objekty s desítkami getterů a setterů by byl rozdíl mnohem větší.
Navíc pro zkušené JS programátory je druhý kód i přehlednější, protože snadněji zjistí, které vlastnoti lze getterem získat a jak takový getter (obecně) funguje.
Poznámka: funkce cap()
slouží pro zvětšení prvního písmene:
String.prototype.cap = function() { return this.charAt(0).toUpperCase() + this.slice(1); };
… a Mínusy
Naopak nevýhoda je pro vývojáře, kteří potřebují takovou funkci debuggovat nebo s ní pracovat. Pokud např. takovou funkci zobrazíte pomocí toString()
, z výsledku nezjistíte, co vlastně dělá:
alert(this.getName.toString()); //zobrazí: function() { return this[prop]; }
Z kódu funkce pouze zjistíte, že vrací nějakou propertu objektu, ale kterou, to už se nedozvíte. Pamatujte na to, že tohle je hodně jednoduchý příklad, ve kterém se jméno property dá odhadnout z jména funkce. Ve skutečném případě by ale kód byl mnohem složitější, a odhadnout, co vlastně dělá „mimo“ zobrazený kód, by bylo mnohem složitější.
V takovém případě vývojáři pak nezbude nic jiného, než stáhnout a otevřít vývojářskou verzi JS souboru a prostudovat celý kód.
Kdy generování použít
Z příkladu výše je použití vidět – hodí se tam, kde máte několik funkcí, které mají více či méně podobný kód a liší se pouze v částech, které lze uložit do proměnné nebo zpracovat v oddělené funkci.
Zkusme si pro příklad napsat 3 funkce, které budou zjišťovat, jestli je trojúhelník pravoúhlý, ostrý nebo tupý. Základem všech tří bude ta samá Pythagorova věta, a pouze místo rovnítka budeme dosazovat operátory větší nebo menší.
- pravoúhlý: c2 = a2 + b2
- ostrý: c2 < a2 + b2
- tupý: c2 > a2 + b2
Funkce se tedy budou shodovat v tom, že musí spočítat hodnoty (c2) a (a2 + b2) a lišit se budou pouze ve znamínku porovnání („=“, „<“ a „>“).
Asi je na první pohled zřejmé, že psát tři funkce, které se budou lišit jen jedním znakem, je zbytečné. Proto použijeme generování:
(function(window) { //------------------------- //příprava namespace: window.trojuhelnik = {}; //generování funkcí: var funkce = { 'jeOstry': function(x,y) { return x < y; }, 'jeTupy': function(x,y) { return x > y; }, 'jePravouhly': function(x,y) { return x == y; } }, operace, operator; for (operace IN funkce) { operator = funkce[operace]; (function(operace, operator) { trojuhelnik[operace] = function(a, b, c) { var a2 = a * a, b2 = b * b, c2 = c * c; return operator(c2, a2 + b2); }; }(operace, operator)); } //------------------------- }(window));
Tento kód můžete vložit do libovolné stránky (nevyžaduje jQuery ani jiné knihovny) a zkusit použít:
trojuhelnik.jePravouhly(3,4,5); // = true trojuhelnik.jeOstry(3,4,5); //false trojuhelnik.jeTupy(2,4,6); //true
Poznámka: funkce samozřejmě nekontrolují, jestli zadané rozměry mohou tvořit trojúhelník (např. „1,1,1000“) nebo zda hodnoty a, b, c nejsou zpřeházeny (např. „5,4,3“ vyhodnotí jako ostrý, i když je pravoúhlý). Nicméně kontrolní kód by byl samozřejmě pro všechny tři funkce stejný, proto by bylo možno ho napsat jen jednou do sdílené části kódu.
Kód se skládá ze dvou částí: v první definujeme funkce s operacemi, které jsou specifické pro jednotlivé výsledné funkce; v druhém kroku pak nadefinujeme kód, který je společný a jako svoji proměnlivou část volá specifické funkce.
Aby méně zkušení programátoři pochopili, proč je v kódu tolikrát klíčové slovo function
, rozepíšu jednotlivé případy:
Výsledná funkce se definuje pomocí function(a,b,c)
. Je obalena funkcí function(operace, operator)
proto, že musíme vytvořit uzávěru (Closure), která každé operaci přiřadí její specifický operátor (tomuto přístupu se také říká Function Factory). Bez tohoto obalu by všechny tři funkce pracovali s posledním operátorem, který zůstal uložen v proměnné operator
po skončení FOR
cyklu. Celý kód je pak obalen ve funkci function(window)
, aby se zajistilo, že uvnitř vytvořené proměnné (funkce
, operace
, operator
) nebudou přístupné ostatním částem kódu a po vygenerování požadovaných funkcí se smažou (vlastnost moderních prohlížečů, které optimalizují použití uzávěr). Funkce function(x,y)
jsou pak pomocné funkce pro operátory.
Výjimky v generování funkce
Předchozí příklad s trojúhelníkem používá pro proměnlivé části kódu externí funkce. Někdy se ale může více hodit zahrnout proměnnou část přímo do sdíleného kódu a připojit k němu podmínku.
Pro příklad si vezměme funkci, která bude registrovat uživatele přes facebook, twitter nebo přímým zadáním emailu. U přímo zadaného emailu budeme muset odeslat aktivační email pro ověření správnosti, zatímco u Facebooku a Twitteru si můžeme být jisti, že email je správný.
$.each(['Email', 'Facebook', 'Twitter'], function(i, service) { window['registerWith' + service] = function(email) { if (service === 'Email') { $.post('/email/activate', {email: email}); } $.post('/register/' + service, {email: email}); }; } );
Každá z funkcí registerWithEmail()
, registerWithFacebook()
a registerWithTwitter()
odešle zadaný email na server na adresu „/register/Email
„, „/register/Facebook
“ nebo „/register/Twitter
„. Funkce registerWithEmail()
ale ještě navíc pošle email na URL „/email/activate
„, která zajistí odeslání potvrzovacího emailu.
V tomto příkladu je vidět ještě jedna výhoda použití generovaných funkcí – pokud budeme chtít přidat např. registraci přes Google+, stačí do seznamu servisů v prvním řádku připsat „Google“ a automaticky se nám vytvoří nová funkce registerWithGoogle()
, která bude odesílat email na URL „/register/Google
„.