Přejít k navigační liště

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

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

Články JavaScript, Různé

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.

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í“.

Komentáře

Subscribe
Upozornit na
guest
29 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Maritn Hruška

To je ale nudný článek, ani jsem ho nedočetl do konce. Nebyla by radši nějaká vtipná glosa? Určitě by se četla líp!

A její překlad by netrval celý den, jako tento „kvalitní obsah“ ;)

Alex Šťastný

TO MARTIN HRUŠKA >> hoď sem nějaký odkaz na své články, ať se můžeme pobavit …

Jinak moc zajímavý článek. Pro mě je přínosem. Dík autorovi :)

Maritn Hruška

http://twitter.com/#!/adent/status/12274361­1979870208

blizz

Maritn Hruška náhodou je to výborný článok! ak tu hľadáš beletriu, horoskopy alebo erotické poviedky tak si na nesprávnej adrese. toto je web o programovaní!

Srigi

Tento clanok mi prisiel vhod, lebo sa prave pokusam v CfS pisat skalovatelne (co sa kodu tyka) aplikacie. Kedze som si z Nette a PHP 5.3 zvykol na oddelovanie funkcionality do samostatnych namaspace (modulov), hladam podobne robustne riesenie aj v JS. Vsimol som si, ze CfS pouziva poslednu menovanu metodu na „schovanie“ kodu pred globalnym svetom:

(function() {
    // bla bla
}).call(this);

Ak si kceme predat nejaky objekt mezdi CfS subormi treba pouzit globalny objekt window, ktory je v samotnom CfS kode dostupny (vdaka horeuvedenemu) ako this. Bolo by fajn CfS kompiler donutit aby do .call() kompiloval vlastny NS.

bauglir

nevím, jestli přesně toto máte na mysly, ale zkuste toto

(function(utils) {
// this == window
// utils == myNamespace.myUtils
}).call(this, myNamespace.my­Utils);

foo

Jsou to 4 roky jsem na projektu psal cisty javascript. Take jsme pouzivali vnorene jmenne prostory a objektovy literal a kdybych se nepodival zblizka na jednotlive vzory, tak bych ani nevedel, ze se tomu tak rika.
Co me ale zarazi, ze se v roce 2011 javascript stale mota okolo naprostych zakladu a predstava, ze by se na zacatku vyvoje mela resit otazka „budeme objekty vytvaret takhle nebo onak?“ mi prijde uplne absurdni.

ondra.novacisko.cz

Ono napsat o jmen7ch prostorech v C++ článek by bylo moc jednoduché.

namespace Jmeno {

}

Jo, javascript je holt veda

BostX

Tiez mi to tak pripada :)

asdasd

V JS ti nikdo nebrání jednoduše napsat:

jmeno = {

};

jmeno.b = {

};

Akorát je to prostě mimořádně ohebný jazyk, takže svádí vymýšlet ještě kulatější kola, která ve většině případů nejsou potřeba.

Nox

Jestli to chápu dobře, tak to lidi dělají aby měli robustní řešení, když v JS jde řada věcí dynamicky měnit, dokonce jde do undefined přiřadit hodnota což může rozbít skript atd. (kdyžtak mě prosím opravte)

asdasd

„Jestli to chápu dobře, tak to lidi dělají aby měli robustní řešení, když v JS jde řada věcí dynamicky měnit“

Někdy se hodí si JS trochu přiohnout, ale nic by se nemělo přehánět. Kolikrát z toho vznikne v podstatě úplně nový jazyk, ve kterém se vyzná jenom autor :-).

„dokonce jde do undefined přiřadit hodnota což může rozbít skript atd“

Místo „do undefined přiřadit hodnotu“ bych spíš řekl možnost vytvářet proměnné za běhu. Dynamičnost má jako všechno svoje výhody i nevýhody. Buď vím, kam můžu/nemůžu hrabat a řídím se podle toho nebo si můžu uměle vytvořit prvky jako privátní členy, atd. Každému vyhovuje něco jiného. Na věci, které se dají ohlídat, se musí myslet a když je to nutné, tak kontrolovat to, co by normálně kontroloval kompiler.

Nox

Jenže náš skript nemusí být jediný na stránce a bylo by hezké kdyby uměl fungovat i přesto, že tam jsou nějaké blbě napsané skripty.

Nevím jestli proti tomuto jde mít 100% obranu (chápu že je to chyba především toho blbého skriptu), když je JS tak dynamické…ale asi lepší jak drátem do voka

asdasd

„Nevím jestli proti tomuto jde mít 100% obranu „

100% obrana není. Stačí, když nějakej mamlas napíše Math = 1; nebo Array = undefined; a můžeš mít skript napsanej sebelíp, stejně to půjde do kopru.

„Jenže náš skript nemusí být jediný na stránce a bylo by hezké kdyby uměl fungovat i přesto, že tam jsou nějaké blbě napsané skripty.“

Tak nepoužívej blbě napsaný skripty :-).

Nox

:P Myslel jsem když někdo, kdo využije námi napsaný skript, použije zároveň i jiný, který bude nedobře napsaný… bylo by vhodný, kdyby to náš skript ustál

Nox

Taky možná proto že začátečníci (i když toto už nejsou ty nejtriviálnější základy viz jakpsatweb) byli, jsou a budou

Přijde mi to jako další z miliardy příspěvků „děláte to blbě, já to umím líp“ = pokud to máte vyřešené, proč se nepodělíte aspoň naznačením s ostatníma? (tím spíš pokud přepokládáte že všechny články na JS mají být na vaší nebo vyšší úrovni)

Pilgrim

.. ba ne kecám :-)
Jsem viděl titulek clanku, tak si rikam, ze se naucim neco noveho, ale po precteni clanku jsem dosel k zaveru, ze „jmenne prostory“ pouzivam den co den. V podstate slozitejsi skripty bez toho ani nedelam… Akorat mi prijde clanek zbytecne dlouhy, ona to zase neni zadna veda tohle, jsou daleko slozitejsi veci.

asdasd

Pro rozdělení do modulů a prostorů mi ve většině případů jako nejpoužitelnější připadá obyčejný objektový literál. Leda že by někdo chtěl stejně jako v článku zanořovat pět a více jmenných prostorů do sebe, což mi přijde jako pěkná pakárna. Podobné postupy používám spíš pro možnost jednoduché integrace funkcí z určitého prostoru s nějakým frameworkem, atd.

Lukáš Rychtecký

Díky za článek:) Jaký je rozdíl mezi: var myApplication = myApplication = myApplication || {} a var myApplication = myApplication || {}; ? Píšete, že ten první případ je důkladnější.

asdasd

Podle mě je to úplně jedno. Pokud ne, tak by mě taky zajímal důvod.

Nox

Není to tím že u 1. se v případě == false přiřadí {} i do externí myApplication a ne jen do lokální?

asdasd

Měl jsem za to, že to namespace se deklaruje v globálním prostoru. Jinak aby fungovalo to vytvoření/přiřazení do globální proměnné se stejným názvem, tak by to podle mě měo vypadat takhle:

var myApplication = window.myAppli­cation = window.myAppli­cation || {};

Pavel Lang

Což je v každém prohlížeči ekvivalentní prostému zápisu
window.myAppli­cation = window.myAppli­cation || {};
a pokud nebude window existovat (třeba ve WebWorkeru) tak je zase jiný problém.

Osobně mám pouze jednu globální proměnnou a jednu globální funkci a ta se mi o vše postará: https://github.com/langpavel/FastJS/blob/master/src/boot.js

asdasd

Až na to, že jsme se bavili o zápisu uvnitř funkce, kde to ekvivaletní nebude…

doominick

Většina zde popisovaných věcí IIFE je taky ve zdejším starším článku http://zdrojak.root.cz/clanky/oop-v-javascriptu-i jako příklady jak to nedělat. To jste mi teď nasadili pěkného brouka do hlavy.

Pavel Lang

Držel bych se akorát zásady nezahnojit globální prostor, na provedení zase moc nezáleží, přívětivost samozřejmě potěší.

Pavel Lang

Podělím se o svoje řešení, snad mě nikdo neukamenuje :-)

"use strict";

if(typeof FastJS === 'undefined')
    FastJS = {};    // vytvoř hlavní namespace, pokud neexistuje

if(typeof getFastJS === 'undefined') {
    // vytvoř funkci vracející namespace, pokud není poskytnuta vlastní
    var getFastJS = (function(){
        var FJS = FastJS;
        FJS.E = function() { /* void */ };
        FJS.T = function() { return true; };
        FJS.F = function() { return false; };
        FJS.K = function(x) { return x; };
        if(typeof FJS.debug === 'undefined')
        FJS.debug = FJS.E;

        return function() {
            if(arguments.length === 0)
                return FJS;
            var nn, i;
            var l=arguments.length;
            var ns = FJS;
            for(i=0; i<l; i++)
            {
                nn = arguments[i];
                ns = (typeof ns[nn] === 'undefined')
                    ? (ns[nn] = {})
                    : ns[nn];
            }
            return ns;
        };
    })();
}

// ulož aktuální kontext jako globální namespace (většinou window)
getFastJS()['GLOBAL'] = this;

Zajímají mě vaše názory. Zdroj: https://github.com/langpavel/FastJS/

Jan Prachař

Proč je tam ta druhá globální proměnná, když můžu rovnou udělat FastJS['GLOBAL']? A taky nechápu smysl té anonymní funkce, když se v ní přístupuje k předem definované globální proměnné.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.