Ninjatriks i Javascript, del 1

Jeg drister meg frempå med en “del 1″, selv om jeg ikke vet om det blir noen del 2, 3 etc..

Uansett: Jeg skriver jo endel Javascript, og jeg ser endel javascript-kode rundt om kring. En ting som irriterer meg er hvordan folk roter til det globale namespacet, og dermed tenkte jeg å dele noen triks for å holde styr på namespaces etc i Javascript.

Detter er på ingen måte noen fasit, men det er noe som jeg synes fungerer godt selv. Har du inspill eller korreksjoner lærer jeg gjerne av deg!

Hold det globale namespacet ryddig!

Alle funksjoner og variabler du definerer i Javascript legger seg i det “globale namespacet”. Dvs, du kan få trøbbel med at du overskriver variabler eller funksjoner som finnes fra før (hvis disse ikke har vært ryddige). Men, frykt ikke, løsningen er rimelig enkel: namespacing! Hvordan? Vel, Javascript har ikke noe namespace-konsept, men det har objekter (eller, object literals, dvs {}). Disse kan vi bruke som namespace. Si at jeg vil ha et namespace Atlefren. Da kan jeg lage det som følger:


var Atlefren = {};

Da kan jeg, lage funksjonene mine som dette:


Atlefren.myFunc = function () {
    return "foo";
};

Problemet med denne løsningen er ser du hvis du i fil a.js skriver følgende:


var Atlefren = {};
Atlefren.myFunc = function () {
    return "foo";
};

og i fil b.js:


var Atlefren = {};
Atlefren.myOtherFunc = function () {
    return "bar";
};

Hvis du inkluderer a.js og så b.js vil myFunc være “undefined”, fordi “namespacet” Atlefren settes til et tomt objekt i toppen av b.js. Et triks for å omgå dette er følgende:


var Atlefren = window.Atlefren || {};

Her skjer følgende: “namespacet” Atlefren settes lik namespacet Atlefren fra “window” (som er det globale namespacet), og hvis ikke dette finnes lages det et nytt, tomt objekt.

Dermed vil både Atlefren.myFunc og Atlefren.myOtherFunc eksistere etter at a.js og b.js er lastet, og rekkefølgen du laster dem i har heller ikke noe å si.

Selveksekverende funksjoner
Men, vi er ikke helt i mål. Hva med diverse småfunksjoner du ikke vil eksponere ut? ta dette eksempelet:


var Atlefren = window.Atlefren || {};

function someHelpingFunction(string) {
    return string + "!";
}

Atlefren.myFunc = function () {
    return someHelpingFunction("foo");
};

Nå vil Atlefren.myFunc være pent og pyntelig inne i namespacet, mens someHelpingFunction vil ligge i det globale namespacet. Vi kunne såklart lagt someHelpingFunction i Atlefren-namespacet også, men det blir upent. For det første vil det da bli masse, unødvendige funksjoner i Atlefren-namespacet, for det andre må du bruke notasjonen


namespace.funksjonsnavn = function () {};

hver gang du skal lage en ny funksjon. Det er mye skriving. Heldigvis finnes det en løsning, og den inkluderer anonyme funksjoner. Såkalte selv-eksekverende funksjoner. Teorien er sm følger:

Du kan definere en anonym funksjon som følger:


function () {};

Vi vet også at denne kan assignes til en variabel og kjøres:


var func = function () {};
func();

Dette kan også gjøres i en go:


(function () {/*do someting*/}());

Her lager vi en funksjon, og kjører den umiddelbart (det siste () er kallet til funksjonen). Det er “god skikk” å legge et ekstra par parenteser rundt en slik funksjon, for å signalisere at dette er en “selveksekverende funksjon) Denne funksjonen blir kjørt en gang og så har du ikke tilgang til den mer. Det fine her er at funksjoner at et eget scope, variabler og funksjoner definert inne i en funkjsjon legges IKKE i det globale namespacet. Dermed kan vi gjøre følgende:


var Atlefren = window.Atlefren || {}; //definer namespacet
(function () {
    //denne funksjonen finnes kun i scopet til den selv-eksekverende funksjonen
    function someHelpingFunction(string) {
        return string + "!";
    }

    // denne legges på Atlefren-namespacet som før
    Atlefren.myFunc = function () {
        return someHelpingFunction("foo");
    };
}());

Ett lite problem gjenstår: jeg må fortsatt skrive hele namespacet mitt for hver funksjon jeg skal eksponere. Dette kan bli slitsomt hvis jeg har et langt, nøstet namespace (eks org.atlefren.demo.stuff, for Java-fanatikerene), eller om jeg skal endre navnet på namespacet mitt. Heldigvis er jo den selveksekverende funksjonen nettopp en funksjon, og kan dermed ta argumenter. Vi kan dermed sende inn namespacet som et argument og bruke dette (jeg har lagt meg på å kalle parameteret ns, så jeg alltid vet hva jeg jobber med:


var Atlefren = window.Atlefren || {}; //definer namespacet
(function (ns) {
    "use strict";

    //denne funksjonen finnes kun i scopet til den selv-eksekverende funksjonen
    function someHelpingFunction(string) {
        return string + "!";
    }

    // denne legges på Atlefren-namespacet som før
    ns.myFunc = function () {
        return someHelpingFunction("foo");
    };
}(Atlefren));

Sånn, nå har vi et ryddig public namespace, ikke så mye clutter og fint strukturert kode. Hvis du er eksta nøye kan du legge på en “use strict” øverst i den selv-eksekverende funksjonen, for å fortelle javascript-parseren at du skriver “skikkelig” javascript (som jeg har gjort).

2 thoughts on “Ninjatriks i Javascript, del 1

  1. Torbjørn

    Likte virkelig denne :)

    Eneste jeg tenker på er:

    var Atlefren = window.Atlefren || {}; //definer namespacet

    Er det noen måte å skrive dette på slik at den ikke feiler når window ikke eksisterer, slik som i en web worker?

  2. Atle Post author

    Takk!

    Det du kan gjøre er følgende:


    var Atlefren = this.Atlefren || {}; //definer namespacet

    this vil i denne konteksten peke på det “øverste” namespacet. Og, som du sier, window eksisterer jo kun i nettleseren, så det er nok mer robust å bruke “this” der,

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>