Zlepšite svoje jQuery – OOP, menné priestory, pub-sub

Denne využívam JS framework jQuery k svojej práci. Napriek nepopierateľným výhodám, ktoré tento framework do života webdevelopera prináša, nepredstavuje klasická štruktúra jQuery kódu ideálny spôsob písania väčšieho projektu. V tomto screencaste vám chcem predstaviť odlišnú štruktúru JS aplikácie, ktorá vám umožní „vyrásť“.

K natočeniu screencastu ma viedlo objavenie jednoduchého riešenia, ktoré predstavuje kompromis medzi používaním nových a zaužívaných postupov. Na jednej strane využívanie OOP, menných priestorov a event programing. Z druhého konca potom zužitkovanie znalosti jQuery a eliminovanie potreby učiť sa nejakú inú knižnicu. Celé to dáva dohromady minimálny krok, ktorý môžu frontend developeri podstúpiť, aby nestratili kontakt s modernými riešeniami a zároveň  nemuseli podstupovať dlhé školenia.

live demo zdrojové kódy

Klasické písanie s jQuery

Keď dnes počujeme niekoho povedať „na stránke je použité jQuery“, nemyslí tým nič iné ako napráskanie celej JS aplikácie do jednej $(document).ready(function(){ /* code_here*/ }); Ukážka z live dema použitého v screencaste:

$(function(){

        var uniqId = function() {
                return Math.random() * Math.pow(10, 17) + Math.random() * Math.pow(10, 17) + Math.random() * Math.pow(10, 17);
        };

        var $searchForm = $('#searchForm');
        var $searchResults = $('#searchResults');
        var $searchLogger = $('#searchLogger');

        $searchForm.submit(function(ev){
                ev.preventDefault();

                var rqid = uniqId();
                var q = $searchForm.find('[name="q"]').val();

                $searchLogger.append('<li class="loading" data-rqid="'+ rqid +'"><a href="#">' + q + '</a></li>');
                $.ajax({
                        url             : 'http://query.yahooapis.com/v1/public/yql',
                        type    : 'POST',
                        dataType: 'json',
                        data    : {
                                callback        : '',
                                format          : 'json',
                                env                     : 'store://datatables.org/alltableswithkeys',
                                q                       : 'SELECT title, href FROM digg.search.search WHERE query="' + q + '" LIMIT 5'
                        },

                        beforeSend: function($xhr, settings) {
                                $xhr.rqid = rqid;
                        },

                        success: function(data, state, $xhr) {
                                var html = '';

                                $searchLogger.find('[data-rqid="' + $xhr.rqid + '"]')
                                 .removeClass('loading')
                                 .removeClass('error')
                                 .addClass('ok');

                                $.each(data.query.results.stories, function(idx, obj){
                                        html += '<li><a href="' + obj.href + '">' + obj.title + '</a></li>';
                                });

                                $searchResults.html(html);
                        },
                        error: function(xhr){
                                $searchLogger.find('[data-rqid="' + $xhr.rqid + '"')
                                 .removeClass('loading')
                                 .removeClass('ok')
                                 .addClass('error');
                        }
                });
        });

});

Aj keď kód napíšete najčistejšie ako sa len dá (o čo som sa snažil v ukážke), dostanete v podstate procedurálny kód bez štruktúry. Čerešničkou na torte je, že kód je mix synchrónnych a asynchrónnych blokov.

Nezriedka sa stane, že vám projekt rastie pod rukami (požiadavkami od zákazníka). Keď skúsite takúto aplikáciu škálovať, rýchlo stratíte podporu zo strany jQuery. Chtiac-nechtiac musíte podstúpiť buď návrat k raw Javascriptu, alebo siahnuť po sofistikovanej knižnici (Dojo, Google Closure), čo stojí čas/peniaze. Medzi základne ingrediencie pre zlepšenie vášho Javascriptu patria:

Menné priestory

O výhodách používania menných priestorov by vám mohli rozprávať členovia Nette Framework komunity, ktorí switchovali v minulom roku a pol. Nenájdete jedného, ktorý by tvrdil, že to bol chybný krok, alebo že sa chce vrátiť späť. V rovnakom období začala potreba používať menné
priestory aj na obyčajné webštúdiá/fre­elancerov.

Základné informácie o menných priestoroch v Javascripte zhrnul Martin Malý tu na Zdrojáku, v článku Základní vzory pro vytváření jmenných prostorů v JavaScriptu. Menné priestory si automaticky vynútia nový spôsob organizácie kódu. Už si nevystačíme s jediným súborom. Ideálne je začať používať štandard PSR-0, ktorý definuje umiestnenie súboru s kódom podľa schémy <ApplicationName>/(<Namespace>)*/<Class Name>.

web_root/js/

Lorem/
  Form/
    Search.js

  SearchLogger.js
  SearchResults.js

Okrem toho budeme potrebovať nejaký súbor pre základnú inicializáciu aplikácie. Najdôležitejšou sekciou je kód s definíciou globálneho namespace kontainera.

<script scr="path/to/jquery.js"></script>
<script scr="path/to/jquery.pubsub.js"></script>

<script scr="path/to/Lorem/bootstrap.js"></script>

web_root/js/Lo­rem/bootstrap­.js

var Lorem = Lorem || {}; // definujeme základný namespace kontajner

Lorem.uniqId = function() { // pridáme do namespace statickú utility metódu
    return Math.random() * Math.pow(10, 17) + Math.random() * Math.pow(10, 17) + Math.random() * Math.pow(10, 17);
}

Potom načítame súbory s definíciami tried našej aplikácie (viď. ďalší krok OOP v Javascripte).

<script scr="path/to/jquery.js"></script>
<script scr="path/to/jquery.pubsub.js"></script>

<script scr="path/to/Lorem/bootstrap.js"></script>
<script scr="path/to/Lorem/Form/Search.js"></script>
<script scr="path/to/Lorem/SearchLogger.js"></script>
<script scr="path/to/Lorem/SearchResults.js"></script>
<script scr="path/to/Lorem/app.js"></script>

Samotné spustenie aplikácie príde nakoniec v samostatnom súbore. Môžete využiť $(document).ready(function) aby sa aplikácia nespustila pred skonštruovaním DOM. Aplikáciu obalíme vlastným scope, do ktorého vstriekneme namespace.

web_root/js/Lo­rem/app.js

;jQuery(document).ready(function() {

    ;(function($, undefined){

        // this === Lorem

        // vas kod

    }).call(Lorem, jQuery);

});

OOP v Javascripte

jQuery nás učí aplikáciu abstrahovať do jQuery kolekcií – objektov získaných cez CSS3 selektory. Takýto prístup nie je z dlhodobého hľadiska vhodný. Nie je možné jednoducho encapsulovať do takéhoto objektu nejaké metódy a výsledok znovuvyužiť v inom (alebo dokonca to istom) projekte.

Na server-side však väčšina z vás ovládla class-based OOP. Javascript žiadne triedy neponúka. Razí si cestu takzvaným prototypovým OOP. Túto tému na Zdrojáku výborne spracoval Daniel Steigerwald, v druhom diely jeho seriálu ukazuje rovnaký spôsob emulácie tried, aký používa CoffeeScript. Rozhodne si ho prečítajte, než budete pokračovať.

Najjednoduchším spôsobom, ako na prototypové OOP naskočiť, je na stránke identifikovať jednotlivé funkčné entity a tie abstrahovať ako Javascript objekty. Nebudeme ich však definovať ako objektové literály, ale necháme si vytvoriť živé inštancie pomocou takzvanej konštrukčnej funkcie. Jej definíciu umiestnime na požadované miesto v namespace.

web_root/js/Lo­rem/Form/Sear­ch.js

Lorem.Form.Search = function($node) {
    this.node = $node;
};

web_root/js/Lo­rem/app.js

var searchForm = new Lorem.Form.Search($('#searchForm'));

Každé volanie konštrukčnej funkcie vráti nový objekt, ktorý odpovedá jej vnútornému scope. Vrátenie tohto objektu netreba explicitne definovať.

console.log(searchForm);             // { node=[form#searchForm] }
console.log(searchForm.constructor); // function() - Lorem.Form.Search

Nezabúdajte, že aj funkcie sú v Javascripte objekt. Každá funkcia, definovaná ľubovoľným spôsobom, obsahuje property prototype čo je tiež objekt.

console.log(Lorem.Form.Search.prototype);  // Object { }

Do tohoto prázdneho objektu je možné priradiť ľubovoľné properties (najčastejšie metódy). Na tie potom vedie skrytý link z inštancie, ktorú konštrukčná funkcia vytvorí.

Lorem.Form.Search.prototype.validate = function() {};
Lorem.Form.Search.prototype.submit = fucntion() {};

console.log(Lorem.Form.Search.prototype.validate === searchForm.validate); // true

Inštancia neobsahuje vlastnú kópiu metódy, ale skrytý link vedie do prototype konštrukčnej funkcie. Metóda je tak v pamäti iba 1×.

Volanie metód skrýva malý zádrheľ – metódy veľmi ochotne preberú scope volajúceho kódu. V praxi to znamená, že ak v metóde odkazujete pomocou this, nie vždy ukazuje na nášu inštanciu. Našťastie zjednať nápravu je jedoduché pomocou metódy bind, ktorá vráti funkciu so správne nastaveným scope. V odkazovanom článku nájdete aj tzv. polyfil, ktorý vám bind sprístupní, aj keď ju váš JS engine nepodporuje.

web_root/js/Lo­rem/Form/Sear­ch.js

Lorem.Form.Search = function($node) {
    this.node = $node;
    this.node.submit(this.validate.bind(this));
};

Pub-Sub

Asynchrónne vykonávanie kódu je takmer základným paradigmom v Javascripte (obzvlášť v browseri). Kedykoľvek klepnete na element, stlačíte klávesu alebo zascrollujete stránkou, JS engine na pozadí oznamuje tieto udalosti. V javascripte si môžete napísať tzv. event-handler – funkciu, ktorá sa zavesí na udalosť (event) a je spustená v potom, čo udalosť nastane. Funkcia pri spustení dostane od prostredia tzv. event-objekt, ktorý event popisuje.

Málokto sa však stretol s tým, že je možné vyvolávať vlastné udalosti, ktoré na rozdiel od tých natívnych (dalo by sa povedať hardwarových) sú plne vo vašich rukách. Dobre zvládnutá knižnica vám umožní implementovať vzor observer a na jeden event navesiť ľubovoľný počet poslucháčov (subscriberov). Vaša aplikácia získa nový rozmer – publisher eventu môže filtrovať natívne eventy, predať subscriberom dodatočné parametre a pod.

jQuery samotné umožňuje techniku pub-sub implementovať, jej tragédiou je však pevné zviazanie s DOM elementom, čo neskutočne degraduje výkon. Pre bežné prípady je vhodnejší jednoduchý jQuery plugin.

$.publish('topic', [param1, param1]);

// na inom mieste
var handle = $.subscribe('topic', function topicHandler(params) {

});

// odhlasenie z /odposluchu/ udalosti
$.unsubscribe([handle]);

V aplikácii je už potom hračkou navesiť na eventy metódy vašich objektov:

web_root/js/Lo­rem/SearchLog­ger.js

Lorem.SearchLogger = function($node) {
    this.node = $node;

    $.subscribe('Form.Search.submit', this.handleSubmit.bind(this));
    $.subscribe('Form.Search.results', this.handleOk.bind(this));
    $.subscribe('Form.Search.error', this.handleError.bind(this));
};

Malé triky

Snažte sa zo svojích aplikácií odstrániť a ďalej nezavádzať tight coupling – pevné závislosti na kód mimo aktuálny modul. Závislosti vždy predávajte zvonka (cez konštruktor, alebo setter metódy), alebo sa spoliehajte na dohodnutú statickú metódu vo vašom namespace. Nikdy však priamo nevolajte metódy 3rd-party knižnice. Tú môže ktokoľvek vymeniť. Práve vtedy oceníte, že nebudete musieť prechádzať polovicu projektu a opravovať volania API.

V predchádzajúcom príklade sme takúto tesnú závislosť vytvorili – metódu subscribe() voláme priamo z knižnice. Správnejšie bude, keď na to v namespace vyhradíme nejakú utility metódu

web_root/js/Lo­rem/bootstrap­.js

Lorem.publish = function(topic, args) {
        $.publish(topic, args);
};
Lorem.subscribe = function(topic, cb) {
        $.subscribe(topic, cb);
};

web_root/js/Lo­rem/SearchLog­ger.js

Lorem.SearchLogger = function($node) {
    this.node = $node;

    Lorem.subscribe('Form.Search.submit', this.handleSubmit.bind(this));
    Lorem.subscribe('Form.Search.results', this.handleOk.bind(this));
    Lorem.subscribe('Form.Search.error', this.handleError.bind(this));
};

Podobnú logiku uplatňujeme aj v AJAX komunikácii. Ak sa už cítite sebaistejší s prototypovým OOP, napíšte si nejaký AJAX adaptér, ktorý predáte všetkým potomkom nejakého BaseModule. Nikdy však nevystrkujte ruky do globálneho scope zo svojích objektov!

Zhrnutie a zdroje

V screencaste (tutoriáli) som vám predviedol drobné kroky, ktoré môžete začať robiť ihneď a nevzdať sa pritom sebaistoty, ktorú vám poskytujú znalosti jQuery. Nastúpite, myslím si, na správnu cestu k spravovateľnejším Javascript aplikáciám, nad ktorými nestrávite desiatky minút snahou pochopiť polroka starý kód.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 22

Přehled komentářů

blizz Re: Zlepšite svoje jQuery – OOP, menné priestory, pub-sub
blizz Re: Zlepšite svoje jQuery – OOP, menné priestory, pub-sub
Petrrr Re: Zlepšite svoje jQuery – OOP, menné priestory, pub-sub
blizz Re: Zlepšite svoje jQuery – OOP, menné priestory, pub-sub
Oldis Re: Zlepšite svoje jQuery – OOP, menné priestory, pub-sub
srigi Re: Zlepšite svoje jQuery – OOP, menné priestory, pub-sub
Alois.Janicek 480p ?
srigi Re: 480p ?
Čelo Re: 480p ?
Martin Malý Re: 480p ?
Martin Malý Re: 480p ?
František Kučera Re: 480p ?
Pilgrim Není jQuery jako jQuery
maryo Re: Zlepšite svoje jQuery – OOP, menné priestory, pub-sub
srigi Re: Zlepšite svoje jQuery – OOP, menné priestory, pub-sub
Jadro007 Povedené
Daniel Steigerwald PubSub v jQuery už je
Daniel Steigerwald doplnění
srigi Re: doplnění
maryo Re: Zlepšite svoje jQuery – OOP, menné priestory, pub-sub
juzna.cz Spatne retezeni funkci
Eda Pokračování?
Zdroj: https://www.zdrojak.cz/?p=3603