Základní vzory pro vytváření jmenných prostorů v JavaScriptu

Jmenné prostory v JavaScriptu patří jednoznačně k pokročilejším technikám. K jejich pochopení je třeba znát princip fungování closures a objektů v JS. Při práci na středních a větších projektech je bezpodmínečně nutné tyto techniky znát a umět používat. Addy Osmani v článku ukazuje osvědčené postupy.

Seriál: Javascriptaření (9 dílů)

  1. Javascriptaření: hrajte si s funkcemi! 31.1.2011
  2. ECMAScript Strict mode ve Firefoxu 4 8.2.2011
  3. Javascriptaření: nejen jQuery živ je JavaScriptař 8.3.2011
  4. Javascriptaření: fyzika, grafika a společenská konverzace 23.3.2011
  5. JavaScriptaření: drátujeme, překládáme, spojujeme 31.3.2011
  6. Javascriptaření: ukažte mi, označte mě, opravte mě 13.4.2011
  7. Kontrola JavaScriptu s JSLint a JSHint 14.7.2011
  8. Základní vzory pro vytváření jmenných prostorů v JavaScriptu 10.10.2011
  9. Javascriptaření: překladače, pakovače 9.11.2011

Autorem článku je Addy Osmani; originál vyšel na jeho blogu pod názvem Essential JavaScript Namespacing Patterns. Překlad vychází s autorovým souhlasem. Autor pracuje jako vývojář v AOL, podílí se na vývoji jQuery a napsal knihu „Základní návrhové vzory v JavaScriptu“.

V tomto článku si probereme středně pokročilé a pokročilé vzory a postupy pro tvoření jmenných prostorů v JavaScriptu. Začneme rovnou popisem těchto metod, protože předpokládám, že většina čtenářů zkušenosti v této oblasti má. Pokud jste v oblasti jmenných prostorů v JS nováčky a chcete se dozvědět něco víc o základech, můžete si přečíst nejprve pasáž ‚základy jmenných prostorů‘.

Jak používat jmenné prostory?

V mnohých programovacích jazycích slouží jmenné prostory k tomu, aby kód nekolidoval s jinými objekty a proměnnými v globálním jmenném prostoru. Jsou rovněž užitečné při organizaci funkčních bloků v aplikaci do snadno udržovatelných skupin, které jsou jednoznačně identifikovatelné. 

V JavaScriptu je pokročilé využívání jmenných prostorů nezbytné a důležité pro zabezpečení kódu před „rozbitím“, například v případě, že některý jiný skript na stránce použije stejnou proměnnou nebo stejné jméno metody jako vy. S rostoucím počtem různých skriptů vkládaných do stránek se tento problém stává běžnějším a všichni se s ním dříve či později setkáme. Jako slušní obyvatelé globálního jmenného prostoru musíme dělat vše pro to, abychom zároveň nerozbíjeli skripty jiným vývojářům.

JavaScript nemá zabudovanou podporu pro jmenné prostory jako jiné jazyky, ale má objekty a uzávěry, které mohou být použity k dosažení stejného efektu.

Pokročilé vzory pro vytváření jmenných prostorů

V této části si projdeme některé pokročilé vzory a pomocné techniky, které se mi osvědčily při práci na velkých projektech. Měl bych poznamenat, že vás nenutím do žádného z dále popsaných postupů, pouze ukazuji způsoby, jaké se mi v praxi osvědčily.

Automatizace vnořených jmenných prostorů (nested NS)

Pravděpodobně tušíte, že vnořenými jmennými prostory vytváříme organizovanou hierarchii v aplikaci. Jako příklad takového jmenného prostoru může sloužit třeba: application.u­tilities.drawin­g.canvas.2d. V JavaScriptu vytvoříme ekvivalent takového jmenného prostoru pomocí objektového literálu:

var application = {
            utilities:{
                    drawing:{
                            canvas:{
                                    2d:{
                                            /*...*/
                                    }
                            }
                    }
            }
};

Pane jo, to je hnusné!

Jeden z častých problémů při použití tohoto vzoru je, že každá přidaná úroveň zanoření představuje nový objekt, který musíte zapsat a správně uzávorkovat. To může v okamžiku, kdy se vaše aplikace stane složitější a začnete potřebovat víc úrovní, představovat netriviální práci.

Jak lze tento problém vyřešit líp? V knize JavaScript Patterns ukázal Stoyan Stefanov velmi chytrý postup pro automatickou definici zanořených jmenných prostorů do existující globální proměnné pomocí funkce. Ta má jeden parametr typu řetězec, tento parametr rozparsuje a automaticky rozšíří jmenný prostor o potřebné objekty.

Postup, který doporučuje, vidíte níže. Já jsem funkci upravil na generickou, aby bylo možné postup použít pro různé jmenné prostory:

// top-level namespace je vytvořen jako objektový literál
var myApp = myApp || {};

// funkce pro parsování názvu jmenného prostoru a
// automatické vygenerování vložených objektů
function extend( ns, ns_string ) {
    var parts = ns_string.split('.'),
        parent = ns,
        pl, i;
    if (parts[0] == "myApp") {
        parts = parts.slice(1);
    }
    pl = parts.length;
    for (i = 0; i < pl; i++) {
        //create a property if it doesnt exist
        if (typeof parent[parts[i]] == 'undefined') {
            parent[parts[i]] = {};
        }
        parent = parent[parts[i]];
    }
    return parent;
}

// ukázka použití:
// rozšíříme myApp o hluboce zanořený jmenný prostor
var mod = extend(myApp, 'myApp.modules.module2');

// výsledkem by měl být správně zanořený objekt
console.log(mod);

// pomocný test pro ověření, zda je možné instanci mod
// použít mimo prostoru myApp pro přístup
// k definovanému jmennému prostoru
console.log(mod == myApp.modules.module2); //true

// další ukázka snadného vnořování jmenných prostorů
// pomocí funkce extend

extend(myApp, 'moduleA.moduleB.moduleC.moduleD');
extend(myApp, 'longer.version.looks.like.this');
console.log(myApp);

Web inspector vypíše:

Všimněte si, že tam, kde bylo předtím potřeba zapisovat explicitně různé zanořené objekty, lze nyní jednoduše napsat jeden řádek kódu. Funguje to výtečně, když potřebujete definovat pouze samotný namespace, ale v případě, kdy potřebujete definovat i funkce a vlastnosti už v době deklarace jmenného prostoru, není tohle řešení dostatečně pružné. Přesto je opravdu mocné a podobný postup užívám běžně v některých svých projektech.

Deklarace závislostí

V této sekci se podíváme na drobné rozšíření vzoru zanořených jmenných prostorů, které můžete vidět v některých aplikacích. Víme, že lokální reference na objekt může výrazně snížit čas potřebný k přístupu k těmto objektům. Pojďme to použít na případ jmenných prostorů a podívejme se, jak to vypadá v praxi:

// běžný způsob přístupu ke vnořeným jmenným prostorům
myApp.utilities.math.fibonacci(25);
myApp.utilities.math.sin(56);
myApp.utilities.drawing.plot(98,50,60);

// s lokálním odkazem
Var utils = myApp.utilities,
maths = utils.math,
drawing = utils.drawing;

// usnadnění přístupu ke jmennému prostoru
maths.fibonacci(25);
maths.sin(56);
drawing.plot(98, 50,60);

// výše uvedený postup je pozorovatelně rychlejší při srovnávání
// stovek či tisíců přístupů k vnořenému namespace vs
// přístupy k lokální referenci na namespace.

Práce s lokální proměnnou je skoro vždy rychlejší než odkazování na globální proměnnou nejvyšší úrovně (např. myApp). Je to nejen rychlejší, ale i vhodnější, než opakovat neustále zanořený jmenný prostor na každém řádku a u složitějších aplikací to může zlepšit čitelnost kódu.

Stoyan doporučuje deklarovat jmenné prostory, vyžadované funkcí nebo modulem, na začátku oblasti viditelnosti (scope) dané funkce/modulu (pomocí vzoru „jedna proměnná“) a volat je pomocí tohoto vzoru. Jednou z výhod tohoto postupu je zmenšení nutnosti hledat a řešit závislosti, takže snadněji vytvoříte rozšiřitelnou architekturu, kde si můžete dynamicky nahrávat moduly do jmenných prostorů, když je potřebujete.

Dle mého názoru funguje tento vzor výborně, když pracujete na úrovni modulů, kde můžete do jednoho prostoru soustředit skupinu metod. Vytváření jmenných prostorů na úrovni jednotlivých funkcí, obzvlášť v případech, kdy je patrné významné překrývání mezi prostory, je něco, čemu byste se měli vyhnout všude, kde to je možné. Místo toho definujte prostor o úroveň výš a přistupujte k nim pomocí jedné reference.

Hluboké rozšíření objektů

Alternativním postupem k automatickému vytváření jmenných prostorů je hluboké rozšíření objektů (deep object extension – analogicky k hluboké kopii). Jmenné prostory, definované pomocí objektového literálu, mohou být snadno rozšířeny (nebo sloučeny) s jinými objekty (nebo jmennými prostory) tak, že vlastnosti a funkce z obou jmenných prostorů budou dostupné pod jedním sloučeným jmenným prostorem.

Pomocí moderních JS frameworků (viz např. jQuery funkci $.extend) je tento postup snadný, ale pokud budete řešit rozšiřování objektů (jmenných prostorů) v čistém JS, pomůže vám následující rutina.

// extend.js
// written by andrew dupont, optimized by addy osmani

function extend(destination, source) {
    var toString = Object.prototype.toString,
        objTest = toString.call({});

    for (var property in source) {
        if (source[property] && objTest == toString.call(source[property])) {
            destination[property] = destination[property] || {};
            extend(destination[property], source[property]);
        } else {
            destination[property] = source[property];
        }
    }
    return destination;
};

console.group("objExtend namespacing tests");

// definujeme hlavní jmenný prostor
var myNS = myNS || {};

// 1. rozšíříme jej o objekt "utils"
extend(myNS, {
        utils:{
        }
});
console.log('test 1', myNS);

//myNS.utils je teď dostupný

// 2. rozšíříme jej o zanořený namespace (namespace.hello.world.wave)
extend(myNS, {
                hello:{
                        world:{
                                wave:{
                                    test: function(){
                                        /*...*/
                                    }
                                }
                        }
                }
});

// ověříme, zda přímé přiřazení funguje jak má
myNS.hello.test1 = 'this is a test';
myNS.hello.world.test2 = 'this is another test';
console.log('test 2', myNS);

// 3. co když myNS už obsahuje stejně pojmenovanou součást
// (např. 'library')? Chceme si být jisti, že při rozšiřování
// nebude přepsáno nic existujícího
myNS.library = {
        foo:function(){}
};
extend(myNS, {
        library:{
                bar:function(){
                    /*...*/
                }
        }
});

// ověříme, zda je rozšíření bezpečné (jak očekáváme)
// myNS by nyní měl obsahovat také library.foo, library.bar
console.log('test 3', myNS);

// 4. co když potřebujeme jednoduchý přístup k určitému prostoru
// a nechceme jeho jméno psát stále dokola?
var shorterAccess1 = myNS.hello.world;
shorterAccess1.test3 = "hello again";
console.log('test 4', myNS);

// úspěch, myApp.hello.world.test3 je teď 'hello again'
console.groupEnd();

Pokud v aplikaci používáte jQuery, můžete využít naprosto stejné možnosti rozšíření pomocí $.extend – viz příklad:

// top-level namespace
var myApp = myApp || {};

// Přímo přiřadíme zanořený NS
myApp.library = {
    foo:function(){ /*..*/}
};

// hluboké rozšíření tohoto NS o jiný
// pro zajímavost řekněme, že o NS se stejným jménem
// ale s odlišnými funkcemi
// syntax: $.extend(deep, target, object1, object2)
$.extend(true, myApp, {
    library:{
        bar:function(){
            /*..*/
        }
    }
});
console.log('test', myApp);

// myApp nyní obsahuje jak library.foo(), tak library.bar()
// nic nebylo přepsáno, jak jsme doufali.

Pro úplnost se podívejte na ekvivalentní zápis pomocí jQuery $.extend pro ostatní příklady z této sekce.

Základy jmenných prostorů

Jmenné prostory naleznete téměř ve všech seriózních JS aplikacích. Pokud nepracujete jen s útržky kódu, je důležité, abyste se ujistili, že správně implementujete jmenné prostory – ne proto, abyste je měli, ale hlavně proto, aby váš kód fungoval a nenechal se cizím kódem rozbít. Běžné vzory, které si ukážeme, jsou:

  1. Jediná globální proměnná
  2. Zápis objektovým literálem
  3. Zanořené jmenné prostory (Nested namespacing)
  4. Bezprostředně vyvolané funkční výrazy (Immediately-invoked Function Expressions – IIFE)
  5. Vsouvání jmenných prostorů (Namespace injection)

1. Jediná globální proměnná

Populární vzor pro vytvoření namespace v JavaScriptu je zvolení jedné globální proměnné jako primárního referenčního objektu. Kostra takové implementace, kde vrátíme objekt s funkcemi a vlastnostmi, může vypadat takto:

var myApplication =  (function(){
        function(){
            /*...*/
        },
        return{
            /*...*/
        }
})();

Tento postup bude fungovat v určitých situacích, ale velká obtíž spočívá v nutnosti spolehnout se na to, že nikdo jiný nepoužije globální proměnnou se stejným názvem.

Možné řešení tohoto problému nastínil Peter Michaux, totiž použití prefixovaných jmenných prostorů. V podstatě to je jednoduchá myšlenka – vyberete si jedinečný prefix pro svůj jmenný prostor (v našem příkladu to bude „myApplication_“) a pak definujeme vlastnosti, metody, proměnné a další objekty s tímto prefixem:

var myApplication_propertyA = {};
var myApplication_propertyB = {};
funcion myApplication_myMethod(){ /*..*/ }

Z hlediska snížení rizika konfliktu proměnných jde o efektivní postup, ale nezapomeňme, že jedinečně pojmenovaný objekt bude mít stejný efekt. Navíc je zde problém s tím, že výsledkem může být velké množství globálních proměnných ve chvíli, kdy vaše aplikace naroste. Navíc jste hodně závislí na tom, že stejný prefix nepoužije nikdo jiný („myApplication“ tedy nebude asi nejvhodnější… pozn.aut.) Buďte tedy opatrní, pokud si tuto metodu vyberete.

Podívejte se na další Peterovy poznámky k tomuto vzoru.

2. Zápis objektovým literálem

Na zápis objektovým literálem můžeme hledět jako na objekt, složený z párů klíč:hodnota, které jsou oddělené čárkami.

var myApplication = {
    getInfo:function(){ /**/ },

    // můžeme rozšířit náš objektový literál a připravit
    // další vložené objekty, které mohou obsahovat
    // opět cokoli:
    models : {},
    views : {
        pages : {}
    },
    collections : {}
};

Můžeme si rovněž zvolit cestu přímého přidávání vlastností do jmenného prostoru:

myApplication.foo = function(){
    return "bar";
}

myApplication.utils = {
    toString:function(){
        /*..*/
    },
    export: function(){
        /*..*/
    }
}

Objektové literály mají výhodu v tom, že neznečišťují globální datový prostor, ale napomáhají logickému organizování kódu a parametrů. To je užitečné ve chvíli, kdy chcete vytvořit snadno čitelnou strukturu, která může být rozšířena o další vnořené úrovně. Na rozdíl od jediné globální proměnné jsou deklarace objektových literálů často spojeny s testem existence proměnné se stejným jménem, takže pravděpodobnost kolize klesá.

Kód v dalším příkladu ukazuje různé způsoby, jakými můžeme zjistit, zda proměnná (jmenný prostor) už existuje před tím, než je definována. Běžně uvidíte možnost 1, ačkoli možnosti 3 a 5 jsou o něco důkladnější a možnost 4 je považována za „best practise“.

// Nekontroluje existenci 'myApplication' v globálním
// jmenném prostoru. To je špatný postup, při kterém
// snadno poškodíme existující proměnnou / NS stejného jména
var myApplication = {};

/*
Následující možnosti kontrolují existenci proměnné či namespace.
Pokud je definována, použijeme ji, jinak vytvoříme nový
objektový literál myApplication.
Možnost 1: var myApplication = myApplication || {};
Možnost 2  if(!MyApplication) MyApplication = {};
Možnost 3: var myApplication = myApplication = myApplication || {}
Možnost 4: myApplication || (myApplication = {});
Možnost 5: var myApplication = myApplication === undefined ? {} : myApplication;
*/

Existuje samozřejmě spousta variant jak a kde použít objektové literály k organizaci a strukturování kódu. U menších aplikací, kde chcete vystavit vnořené API konkrétního uzavřeného modulu, lze použít následující techniku, kdy vracíme rozhraní, které mohou využít další vývojáři. Je to obdoba vzoru pro moduly, kde jádro vzoru tvoří IIFE (viz další text) a vrácené rozhraní je objektový literál:

var namespace = (function () {

    // definováno v lokálním scope
    var privateMethod1 = function () { /* ... */ }
    var privateMethod2 = function () { /* ... */ }
    var privateProperty1 = 'foobar';
    return {

        // vrácený objekt může být zanořen
        // do více úrovní, ačkoli, jak tu už zaznělo,
        // je tento způsob vhodnější pro malé aplikace
        // (alespoň podle mého)

        publicMethod1: privateMethod1,

        //zanořený jmenný prostor s veřejnou vlastností
        properties:{
            publicProperty1: privateProperty1
        },

        //testujeme další jmenný prostor
        utils:{
            publicMethod2: privateMethod2
        }
        ...
    }
})();

Výhoda objektových literálů je, že mají velmi jednoduchou a elegantní syntax „klíč:hodnota“. Díky ní jsme schopni snadno zapouzdřit jakoukoli logiku či funkcionalitu ve své aplikaci způsobem, který je jasně oddělí od zbytku a zároveň nabídne solidní základ pro další rozšiřování.

Nevýhodou může být, že tento zápis má tendence časem narůst do obrovských nepřehledných konstrukcí. V takovém případě stojí za zvážení vnořování jmenných prostorů (viz další text), které používá rovněž objektové literály.

Tento vzor můžeme použít ve spoustě dalších situací. Kromě jmenných prostorů jím můžeme oddělit konfiguraci aplikace do jedné oblasti, v níž může být snadno modifikována, aniž by bylo potřeba hledat konstanty, rozmístěné po celém kódu. Pro takový účel jsou objektové literály jako stvořené. Příklad využití literálů pro uložení konfigurace:

var myConfig = {
    language: 'english',
    defaults: {
        enableGeolocation: true,
        enableSharing: false,
        maxPhotos: 20
    },
    theme: {
        skin: 'a',
        toolbars: {
            index: 'ui-navigation-toolbar',
            pages: 'ui-custom-toolbar'
        }
    }
}

Všimněte si, že mezi objektovým literálem a standardním zápisem v JSON jsou jen mizivé syntaktické rozdíly. Pokud se z libovolného důvodu rozhodnete ukládat konfiguraci v JSON (např. pro snazší ukládání na server), můžete. Více se o objektových literálech dozvíte v článku Rebeccy Murphey.

3. Vnořené jmenné prostory

Rozšířením předchozího vzoru jsou vnořené jmenné prostory. Jde o další běžný vzor, který snižuje riziko kolize jmen, protože i když jmenný prostor již existuje, je málo pravděpodobné, že bude obsahovat stejné potomky.

Připadá vám to povědomé?

YAHOO.util.Dom.getElementsByClassName('test');

YUI framework od Yahoo používá vnořené jmenné prostory běžně; v AOL je používéme ve spoustě velkých aplikací. Ukázková implementace vnořeného jmenného prostoru může vypadat například takto:

var myApp =  myApp || {};

// při definování potomků uděláme stejný test
myApp.routers = myApp.routers || {};
myApp.model = myApp.model || {};
myApp.model.special = myApp.model.special || {};

// vnořené jmenné prostory mohou být tak komplexní, jak je třeba:
// myApp.utilities.charting.html5.plotGraph(/*..*/);
// myApp.modules.financePlanner.getSummary();
// myApp.services.social.facebook.realtimeStream.getLatest();

Stejně tak můžete vytvářet nové vnořené prostory pomocí indexovaných vlastností, např.:

myApp["routers"] = myApp["routers"] || {};
myApp["models"] = myApp["models"] || {};
myApp["controllers"] = myApp["controllers"] || {};

Obě možnosti jsou čitelné, organizované a nabízejí poměrně bezpečnou cestu, jak v aplikaci používat jmenné prostory podobným stylem, jakým se používají v jiných jazycích. Jediný opravdový problém je ten, že JS engine musí nejprve najít objekt myApp a pak se prokousávat strukturou dovnitř, dokud nenajde funkci, kterou chcete použít.

To představuje větší množství práce při hledání, i když vývojáři jako Juriy Zaytsev (Jurij Zajcev) prováděli testy výkonu v moderních JS enginech a zjistili, že rozdíly v rychlosti mezi jednoduchým jmenným prostorem a zanořeným jsou téměř neznatelné.

4. Immediately-invoked Function Expressions (IIFE)s

Vzor IIFE (bezprostředně vykonaný funkční výraz) je v podstatě nepojmenovaná funkce, která je vyvolána hned poté, co je definována. Protože jsou v JavaScriptu funkce a proměnné, definované v takovém kontextu, viditelné pouze zevnitř, můžeme tento postup použít jako spolehlivý způsob pro uchování soukromí.

To je jeden z mnoha důvodů, proč jsou IIFE populární jako postup pro zapouzdření aplikační logiky a ochranu před globálním jmenným prostorem. Pravděpodobně tento vzor znáte pod jménem self-executing (nebo self-invoked) anonymous function, ale osobně dávám přednost pojmenování, které zvolil Ben Alman, protože myslím, že je popisnější a přesnější.

Nejjednodušší verze IIFE může vypadat třeba takto:

// (anonymní) bezprostředně vyvolaný funkční výraz
(function(){ /*...*/})();

// pojmenovaný bezprostředně vyvolaný funkční výraz
(function foobar(){ /*..*/}());

// "self-executing function" by vypadala spíš takto, což je velký rozdíl
function foobar(){ foobar(); }

Lehce rozšířená verze prvního příkladu může vypadat třeba takto:

var namespace = namespace || {};

// zde je objekt "namespace" předán jako parametr funkce
// k němuž dodáme patřičné metody a vlastnosti
(function( o ){
    o.foo = "foo";
    o.bar = function(){
        return "bar";
    };
})(namespace);

console.log(namespace);

I když tento příklad budeme rozšiřovat o další postupy, jako jsou public/private proměnné a funkce, zůstane stále čitelný. Pojďme si ukázat větší kód:

// namespace (jméno našeho prostoru) a undefined jsou zde předávány
// proto, abychom se ujistili, že 1. prostor bude modifikován lokálně
// a není přepisován zvnějšku
// 2. aby hodnota "undefined" byla v našem kontextu opravdu nedefinovaná.
// Důvodem jsou problémy s undefined, který ve verzích před ES5 bylo možné
// změnit.
(function ( namespace, undefined ) {

    // soukromé vlastnosti
    var foo = "foo",
        bar = "bar";

    // veřejné metody a vlastnosti
    namespace.foobar = "foobar";
    namespace.sayHello = function () {
        speak("hello world");
    };

    // soukromá metoda
    function speak(msg) {
        console.log("You said: " + msg);
    };

    // zkontrolujeme, jestli "namespace" existuje v globálním objektu window
    // pokud ne, přiřadíme do window.namespace
    // prázdný objekt
}(window.namespace = window.namespace || {});

// otestujeme veřejné metody a vlastnosti
console.log(namespace.foobar); // foobar
namescpace.sayHello(); // hello world

// přidáme nové
namespace.foobar2 = "foobar";
console.log(namespace.foobar2);

Schopnost rozšiřování je samozřejmě klíčová pro jakýkoli škálovatelný vzor jmenných prostorů; s IIFE toho lze dosáhnout poměrně snadno. V následujícím příkladu předáme náš existující jmenný prostor ‚namespace‘ jako argument anonymní funkci, která jej rozšíří („dekoruje“) o další funkce:

// pojďme přidat nové funkce do jmenného prostoru
(function( namespace, undefined ){
    // veřejná metoda
    namespace.sayGoodbye = function(){
        console.log(namespace.foo);
        console.log(namespace.bar);
        speak('goodbye');
    }
}( window.namespace = window.namespace || {});

namespace.sayGoodbye(); //goodbye

Pro tuto chvíli je to o IIFE vše. Zájemcům o další informace o tomto vzoru doporučím článek o IIFE od Bena Almada a článek Elijaha Manora o namespace patterns z C#.

5. Namespace injection

Namespace injection („vstřikování“ jmenných prostorů, analogicky k dependency injection) je další variantou vzoru IIFE, při níž „vstřikujeme“ metody a vlastnosti do určitého jmenného prostoru pomocí funkčního wrapperu, kde používáme this jako namespace proxy. Výhodou tohoto postupu je možnost snadné aplikace funkcí na více objektů nebo jmennýc prostorů, což se může hodit napříkald v případě, že přidáváme sadu metod, na nichž chceme stavět (například gettery a settery).

Nevýhodou tohoto přístupu je, že existují snazší nebo vhodnější způsoby, jak dosáhnout stejného výsledku (například slučováním jmenných prostorů nebo jejich zanořováním), které jsme si už probrali.

Ukažme si tento vzor v akci, kdy jej použijeme k rozšíření funkce dvou jmenných prostorů, jednoho definovaného (utils) a druhého, který dynamicky vytvoříme jako součást prostoru utils (nový prostor tools).

var myApp = myApp || {};
myApp.utils =  {};

(function() {
    var val = 5;
    this.getValue = function() {
        return val;
    };
    this.setValue = function(newVal) {
        val = newVal;
    }
    // přidáme také nový jmenný prostor
    this.tools = {};
}).apply(myApp.utils);

// Přidáme novou funkci do prostoru tools
// který jsme definovali výše
(function(){
    this.diagnose = function(){
        return 'diagnosis';
    }
}).apply(myApp.utils.tools);

// všimněte si, že stejný postup může být použitý
// i u obyčejného IIFE, kdy předáme kontext jako parametr
// a budeme modifikovat tento kontext namísto implicitního
// 'this'

// testy
console.log(myApp); //the now populated namespace
console.log(myApp.utils.getValue()); // test get
myApp.utils.setValue(25); // test set
console.log(myApp.utils.getValue());
console.log(myApp.utils.tools.diagnose());

Angus Croll před časem navrhoval myšlenku využití funkce call k přirozenému oddělení kontextu a argumentů. Tento postup může sloužit i pro vytvoření funkce, která rozšiřuje libovolné moduly nebo jmenné prostory – pro úplnost si ji ukážeme:

// definujeme prostory, využijeme je později
var ns = ns || {}, ns2 = ns2 || {};

// funkce, která vytváří potřebné proměnné a metody v cizím NS
var creator = function(val){
    var val = val || 0;
    this.next = function(){
        return val++
    };
    this.reset = function(){
        val = 0;
    }
}

creator.call(ns);
// nyní existuje ns.next a ns.reset

creator.call(ns2, 5000);
// ns2 obsahuje stejné metody
// ale hodnota val je zde 5000

Tento vzor je, jak už bylo řečeno, vhodný v případech, kdy přiřazujeme podobnou sadu funkcí do více modulů či jmenných prostorů, ale já bych doporučil využívat jej tam, kde explicitní deklarace funkcí uvnitř modulu/closure s přímým přístupem nedává smysl.

Závěr

Z výše uvedeného seznamu návrhových vzorů pro jmenné prostory používám nejčastěji vnořené jmenné prostory spolu se zápisem objektovým literálem.

IIFE a jediná globální proměnná mohou fungovat dobře pro malé a střední aplikace, protože větší aplikace budou nejspíš vyžadovat strukturované jmenné prostory, kde je potřeba zvolit řešení, které je snadno čitelné a škálovatelné. V úvodu zmiňované metody takové jsou.

Rovněž doporučuji vyzkoušet si zmiňované pokročilejší metody rozšiřování jmenných prostorů, protože z vlastní zkušenosti vím, že dokáží ušetřit spoustu času při dalším vývoji.

Pozn. red.: Před časem napsal Daniel Steigerwald miniseriál o OOP v JS, který ukazuje návrhové vzory z podobné oblasti – pro řešení „tříd“ a „zapouzdření“.

Začal programovat v roce 1984 s programovatelnou kalkulačkou. Pokračoval k BASICu, assembleru Z80, Forthu, Pascalu, Céčku, dalším assemblerům, před časem v PHP a teď by rád neprogramoval a radši se věnoval starým počítačům.

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

Komentáře: 29

Přehled komentářů

Maritn Hruška to je ale nebývale nudný článek
Alex Šťastný Re: to je ale nebývale nudný článek
Maritn Hruška Re: to je ale nebývale nudný článek
blizz Re: to je ale nebývale nudný článek
srigi NS a CoffeeScript
bauglir Re: NS a CoffeeScript
foo 2011
Martin Malý Re: 2011
ondra.novacisko.cz Re: 2011
BostX Re: 2011
asdasd Re: 2011
Nox Re: 2011
asdasd Re: 2011
Nox Re: 2011
asdasd Re: 2011
Nox Re: 2011
Nox Re: 2011
Pilgrim Jsem si přečetl něco nového
asdasd Re: Základní vzory pro vytváření jmenných prostorů v JavaScriptu
Lukáš Rychtecký V čem je rozdíl?
asdasd Re: V čem je rozdíl?
Nox Re: V čem je rozdíl?
asdasd Re: V čem je rozdíl?
langpa Re: V čem je rozdíl?
asdasd Re: V čem je rozdíl?
doominick OOP v Javascriptu vs namespaces
langpa Re: OOP v Javascriptu vs namespaces
langpa Moje řešení
Jan Prachař Re: Moje řešení
Zdroj: https://www.zdrojak.cz/?p=3556