Néhány JavaScript best practice egy egyszerű időzítőn keresztül

By | 2012. április 4.

JavaScript esetében is fontos szerepe van az időzítésnek, gyakran kell bizonyos időközönként elvégezni tevékenységeket, vagy bizonyos időkésleltetéssel dolgozni (tipikus eset animációk készítése). Ehhez két függvény áll rendelkezésre, a setTimeout és a setInterval, ezek azonban csak alapvető funkciót biztosítanak, ráadásul egyáltalán nem objektum-orientált megközelítésűek. Emiatt készítettem egy nagyon egyszerű Timer objektumot, amin keresztül ráadásul be tudok mutatni nektek néhány JavaScript best practice-t. 

Legelső feladat a namespace létrehozása, hiszen nem szeretnénk fölöslegesen terhelni a globális  névteret, ezt Javascript esetében a következőképp tehetjük meg:

var Webteam = Webteam || {};
Webteam.SimpleTimer = Webteam.SimpleTimer || {};

A névteret gyakran úgy képzik, hogy az első tag a cég neve, a második pedig a projekt neve. Mi közös megegyezésre általában a Webteam névteret használjuk a projektjeinkben.

Ezek után következik az objektum létrehozására, erre JavaScriptben számos lehetőség adott, most a példa kedvéért az egyik legkülönlegesebb módszert mutatom be:

A kód becsapós lehet, hiszen első ránézésre egy Timer függvény létrehozásának tűnhet, viszont ha jobban megnézitek, akkor ez egy névtelen függvény, amit a végén egyből meg is hívok, és a visszatérési értéke lesz maga a Timer objektum.

Ennek az az értelme, hogy privát tagváltozókat tudtam létrehozni. Ez annak köszönhető, hogy a JavaScriptben ha egy függvényen belül hozol létre egy belső függvényt, akkor a belső függvényben elérhetők a külső függvény lokális változói, még a függvény lefutása után is. A fenti példában például a start metódusban el tudom érni a külső függvény enabled lokális  változóját is, még azután is, hogy a külső függvény régesrég lefutott. Ezáltal privát tagváltozókat hoztam létre.

A fent leírt mechanizmust hívják a JavaScriptben Closure-nek, sokan ezt tartják a nyelv legnagyobb erejének.

Tehát a recept a következő: a privát tagokat a külső függvény lokális változójaként hozom létre, amelyeket viszont publikussá szeretnék tenni, azokat a visszaadásra kerülő objektumba helyezem.

Ezek után viszont már világos az objektum működése, a start metódus időkésleltetéssel meghívja az update metódust, ami előbb meghívja az ontick metódust, majd saját magát időkésleltetéssel egészen addig, amíg le nem állítjuk a stop metódussal. Jól látható, hogy az időközönként lefuttatandó kódot az ontick metódusban kell implementálnia az objektum felhasználójának.

Az, hogy milyen időközönként járjon le az időzítő az interval private változó segítségével állíthatjuk. Kívülről ez a változó az Interval metóduson keresztül érhető el, ami egyben setter és getter is. Abban az esetben, ha paraméter nélkül hívjuk meg, visszaadja az interval aktuális értéket, ha paraméterrel, akkor egy ellenőrzés után kerül csak beállításra az érték. Ez szintén egy igen gyakran használt megoldás.

Ahogyan azt az objektum-orientált szemléletünkkel el is várjuk, a Timer objektum meg tudja magát védeni, hiszen az intervalnak nem lehet butaságot beállítani, valamint a kulcs működést biztosító update metódust sem lehet kívülről elérni, melynek közvetlen meghívása tönkretenné az egész Timer működését.

A fent bemutatott példának több haszna is volt reményeim szerint, egyrészt kaptatok egy nagyon egyszerű, mégis hatékony időzítő objektumot, másrészt megtanultátok a closure-ok jelentőségét és felhasználási módját a JavaScript nyelvben.

  • jtakacs

    Webteam.SimpleTimer.Timer = (function (undefined) {

    })();
    Így a closure-on belül lesz egy privát változód, aminek az értéke garantáltan undefined, és egy előzetesen betöltött 3rd party script esetleg poénkodik azzal, hogy undefined=true; akkor is működni fog a kódod. Egyébként a jQuery kódja is ugyanezt a trükköt használja.
    Ezen kívül még az alert() használatába tudok belekötni, ugyanis ott amíg le nem okézod, addíg megáll az összes timer. (Lehet, hogy néhány ultramodern böngészőben már nem, de a régebbiekben biztosan.)
    Helyette inkább használj console.log-ot, persze ezt érdemes betenni saját logger függvénybe, és körülvenni if-ekkel, mert console.log sincs minden böngészőben.

    Ettől eltekintve, ez egy nagyon jó demonstrációs példakód.

    Még egyet találtam közben: if(enabled === false)
    Ezt miért ellenőrzöd ilyen szigorúan? Nagyon sok más false-ra konvertálódó érték is lehet, pl null, undefined, 0, amik szerintem ugyanolyan jól kifejezik azt, hogy le szeretnéd állítani a timert.

  • mad

    Jó ez a module pattern, csak így nem lehet példányosítani több időzítőt.
    Persze lehet workaroundolni, pl így: http://jsfiddle.net/gpHe5/
    Csak ez meg memória szempontból pazarol (annyi start/stop/stb függvényt csinál, ahány timer van), amin mondjuk örökléssel lehetne segíteni. A kettőt kombinálni viszont nekem még nem sikerült (tehát prototípus minta + modul minta ötvözése)

  • Szél Péter

    jtakacs: Köszi, ez nagyon jó trükk, nem gondoltam még soha arra, hogy valaki más értéket adna az undefined objektumnak (most gyorsan kipróbáltam pár böngészőben, és egyik sem engedte meg, de simán el tudom képzelni, hogy a szabvány megengedi 🙂 )
    Az alert pedig csak egy placeholder, saját kód jön oda, valami értelmes 🙂
    Az == operátort messziről elkerülöm, itt van néhány példa, miért:

    ” == ‘0’ // false
    0 == ” // true
    0 == ‘0’ // true

    Tranzitivitás nuku?

    false == ‘false’ // false
    false == ‘0’ // true

    false == undefined // false
    false == null // false
    null == undefined // true

    Az utolsó a legveszélyesebb. És még egy vicces:

    ‘ \t\r\n ‘ == 0 // true

    mad: köszi a kiegészítést! 🙂 Igen, így is létre lehet hozni objektumokat, van előnye, hátránya. Én kifejezetten az adott trükköt szerettem volna bemutatni, azért választottam a fenti megoldást.