Hopp til innholdet

cjohansen.no

Navnerom

Fordi all JavaScript i utgangspunktet kjører i det globale skopet er det skremmende stor sjanse for kollisjoner mellom script (særlig om de er fra forskjellige kilder). Ved å bruke objekter som navnerom kan vi minimere scriptenes globale fotspor samtidig som vi kan beholde et rikt spekter av funksjonalitet.

Navnerom i JavaScript

JavaScript tilbyr ingen formelle navnerom eller pakkeløsninger for koden din. JavaScript tilbyr et globalt nivå og lar det være opp til deg hvordan du vil skrive koden din. Sånt blir det fort søl i det globalenavnerommet av, men vi kan løse det ganske enkelt.

Et "navnerom" er bare en eller annen måte å bygge koden i hierarkier fremfor å legge alt side-ved-side på samme nivå. I JavaScript gjøres dette med objekter:

var cj = {};

cj.Element = {
    // Element-relatert funksjonalitet
};

cj.Element.Event = {
    // Event-spesifikk element-funksjonalitet
};

cj.Tools = {};
cj.Tools.Remote = {};

En fordel med koden over er at den kun bidrar med ett eneste objekt i det globale navnerommet: cj.

I Validatious finnes det en haug med objekter; Form, Validator, Fieldset og mye annet. Sjansen for at jeg er alene om klasser ved disse generiske navnene er små, og derfor er all kode "namespacet" i v2-modulen, så det heller heter v2.Form osv. Sjansene for kollisjon med andres kode er dermed langt mindre.

namespace()

Et navnerom er bare objekter i objekter. Noen ganger er objektene i de forskjellige nivåene nyttige objekter med "egen" funksjonalitet, og noen ganger er de bare tomme skall.

Når du jobber med større prosjekter og deler funksjonalitet over flere filer kan det fort bli uklart hvilke navnerom som er tilgjengelige hvor. Og noen ganger skal du lage navnerom som nøster mer enn ett nivå lengre enn det du allerede har. I begge disse tilfellene kan det være nyttig å ha en funksjon som tar en streng og garanterer at navnerommet den definerer eksisterer - enten ved å bekrefte at det gjør det, eller ved å lage tomme objekter der de mangler.

La oss spesifisere funksjonalitet med noen YUI Test-tester:

testExistingNameSpace: function() {
    var assert = YAHOO.util.Assert;

    // Make sure namespace "cj" is global
    window.cj = {};
    var namespace = "cj".namespace();
    assert.isNotNull(namespace);
    assert.areEqual(cj, namespace);

    window.cj.Tools = { BrowserTools: {} };
    namespace = "cj.Tools.BrowserTools".namespace();
    assert.isNotNull(namespace);
    assert.areEqual(cj.Tools.BrowserTools, namespace);
},

testNewNamespace: function() {
    var assert = YAHOO.util.Assert;

    assert.isUndefined(window.myns);
    var namespace = "myns.deeply.nested.namespace".namespace();
    assert.isNotNull(myns);
    assert.isNotNull(myns.deeply.nested.namespace);
},

testSeparator: function() {
    var assert = YAHOO.util.Assert;

    assert.isUndefined(window.separatorNS);
    var namespace = "separatorNS-nest-some".namespace("-");
    assert.isNotNull(namespace);
},

testPreserveExisting: function() {
    var assert = YAHOO.util.Assert;
    window.cj = { Tools: { BrowserTools: { version: "1.0" } } };
    var namespace = "cj.Tools".namespace();

    assert.isNotNull(cj.Tools.BrowserTools);
    assert.areEqual(cj.Tools, namespace);
    assert.areEqual("1.0", namespace.BrowserTools.version);
},

testAssignToNewNamespace: function() {
    var assert = YAHOO.util.Assert;
    "cj.Namespace.Test".namespace().version = "2.0";
    assert.isNotUndefined(cj);
    assert.isNotUndefined(cj.Namespace);
    assert.areEqual("2.0", cj.Namespace.Test.version);
}

Testene feiler, som de skal når vi ikke har koda noe enda. Fra testene vet vi at koden skal:

Og med det hacker vi i vei:

String.prototype.namespace = function(separator) {
    // Optional parameter
    separator = separator || ".";

    // Initial scope
    var scope = window;

    // Individual pieces
    var objects = this.split(separator);

    for (var i = 0, object; (object = objects[i]); i++) {
        // Note: object["property"] === object.property
        if (typeof scope[object] === "undefined") {
            scope[object] = {};
        }

        scope = scope[object];
    }

    return scope;
};

Testene gir oss den gode følelsen når de blir grønne. Merk at implementasjonen er en minimumsløsning i forhold til kravene (testene). Heldigvis gir den allikevel grei håndtering av forskjellige strenger, noe denne testen gir et hint om:

testBogusInput: function() {
    var assert = YAHOO.util.Assert;

    var namespace = "".namespace();
    assert.areEqual(window, namespace, "Implicit namespace should be global namespace")

    namespace = ".".namespace();
    assert.areEqual(window, namespace, "Bogus namespace should yield global namespace")
}

Denne passerer.

YUI har forøvrig en lignende metode, men som namespacer ting inn i YAHOO-objektet.

Vel møtt i morgen for en gjennomgang av objektorientering med closures.

Muligens relatert

2006 - 2010 Christian Johansen Creative Commons Lisens