Fondamentali namespacing

Indice

I namespaces, o spazi dei nomi, si trovano in quasi tutte le applicazioni serie scritte in JavaScript. Sempre che non si stia lavorando con un frammento di codice, è imperativo che voi fate del vostro meglio per assicurare che si sta implementando il namespacing correttamente perché non è solo semplice da pick-up, ma sarà anche utile per evitare di sovrascrivere il proprio codice da terze parti. I modelli che esamineremo in questa sezione sono:

1. Variabili globali singole
Il modello popolare per i namespacing in JavaScript consiste nel creare una singola variabile globale come l’oggetto principale di riferimento. Un’implementazione dello schema di quest’approccio, dove ritorna un oggetto con funzioni e proprietà ha una struttura come di seguito si mostra:

Copia codice

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

Anche se questo funziona per determinate situazioni, la sfida più grande, con il modello di variabile globale singola, è di garantire che nessun altro codice possa usare lo stesso nome di variabile globale come quella che avete scritto per la pagina.
Una soluzione a questo problema, come menzioato da Peter Michaux, è quello di utilizzare il prefisso per il namespacing. È un concetto semplice, ma l’idea è di selezionare un unico prefisso per lo spazio dei nomi che si desidera utilizzare (come in questo esempio, “myApplication_”) e quindi definire i metodi, le variabili o altri oggetti dopo il prefisso, come segue:

Copia codice

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

Questo è efficace dal punto di vista se si cerca di abbassare le probabilità che una particolare variabile esista nell’ambito globale, ma ricordate che un oggetto con un nome univoco può avere lo stesso effetto. A parte questo, il più grande problema con il modello è che possono diventare molti oggetti globali, una volta che l’applicazione si avvia a crescere. C’è anche una bella pesante dipendenza se il tuo prefisso non è utilizzato da altri sviluppatori nel namespace globale, quindi, state attenti se si sceglie di utilizzare questo modello.
Per maggiori informazioni su Peter circa il modello di variabile globale singola, leggete il suo ottimo post da qui (in inglese).

2. Notazione dell'oggetto letterale
La notazione dell’oggetto letterale può essere pensato come un oggetto che contiene una collezione di coppie chiave:valore con i due punti che separano ogni coppia di chiavi e valori. La sintassi da utilizzare richiede una virgola dopo ogni chiave:valore con l’eccezione dell’ultima voce nell’oggetto, simile ad un normale array.

Copia codice

var myApplication = {
   getInfo:function(){ /**/ },
   // possiamo anche popolare il nostro oggetto
   // letterale per supportare ulteriori oggetti
   // letterale contenenti qualcosa di veramente:
   models : {},
   views : {
      pages : {}
   },
   collections : {}
};

Si può anche scegliere di aggiungere direttamente le proprietà al namespace:

Copia codice

myApplication.foo = function(){
   return "bar";
}
myApplication.utils = {
   toString:function(){
       /*..*/
   },
   export: function(){
       /*..*/
   }
}

Gli oggetti letterali hanno il vantaggio di non inquinare il namespace globale e aiutano a organizzare il codice e a parametrizzarli logicamente. Sono vantaggiosi se si desidera creare strutture facilmente leggibili e può essere ampliato per supportare la sua nidificazione in profondità. A differenza delle variabili globali singole, con gli oggetti letterali spesso si fanno delle prove per controllare l’esistenza di una variabile con lo stesso nome e così le probabilità di collisioni che accadrebbero, si ridurrebbero notevolmente.
Il codice in cima dell’esempio successivo mostra i diversi modi con cui è possibile controllare se una variabile (oggetto namespace) esiste già prima della sua definizione. Potrai vedere comunemente a degli sviluppatori che utilizzano l’opzione 1; tuttavia le opzioni 3 e 5 possono essere considerate più approfondite e l’opzione 4 è considerata una buona best-practice o la migliore prassi.

Copia codice

// Questo non controlla l'esistenza di 'myApplication'
// nello spazio dei nomi globale. Cattiva pratica
// di come si può  facilmente sovrascrivere una
// variabile/namespace esistente con lo stesso nome
var myApplication = {};
/*
Le seguenti opzioni *controllano* se la
variabile/namespace esiste. Se è  già definito,
si usa tale istanza, altrimenti si assegna un
nuovo oggetto letterale per MyApplication.
opzione 1: var myApplication = myApplication || {};
opzione 2  if(!MyApplication) MyApplication = {};
opzione 3: var myApplication = myApplication = myApplication || {}
opzione 4: myApplication || (myApplication = {});
opzione 5: var myApplication = myApplication === undefined ? {} : myApplication;
*/

Vi è naturalmente una grande quantità di varietà di come e, dove gli oggetti letterali sono utilizzati per l’organizzazione e la strutturazione del codice. Per le applicazioni più piccole dove si desidera esporre un’API nidificata per un particolare modulo (self-enclosed) chiuso in se stesso, si può solo provare a usare il seguente modello che ritorna un’interfaccia da utilizzare d’altri sviluppatori. È una variante del modello di modulo in cui la struttura di base del modello è uno IIFE (acronimo di Immediately Invoked Function Expressions, cioè Funzione Espressione Immediatamente Invocate), tuttavia l’interfaccia restituisce un oggetto letterale:

Copia codice

var namespace = (function () {
   // definito nell'ambito locale
   var privateMethod1 = function () { /* ... */ }
   var privateMethod2 = function () { /* ... */ }
   var privateProperty1 = 'foobar';
   return {
      // l'oggetto letterale tornato qui può  avere
      // qualunque profondità nidificata che si desider1,
      // tuttavia, come scritto prima, questo modo di fare
      // lavora meglio per applicazioni più  piccole, con
      // limitata portata nelle sue applicazioni
      publicMethod1: privateMethod1,
      // spazio dei nomi nidificato con proprietà pubblica
      properties:{
          publicProperty1: privateProperty1
      },
      // un altro spazio dei nomi testato
      utils:{
          publicMethod2: privateMethod2
      }
      ...
   }
})();

Il vantaggio degli oggetti letterali è che ci offrono una sintassi molto elegante per lavorare con le coppie chiave/valore, e anche quella in cui siamo in grado di incapsulare facilmente qualsiasi logica distinta o funzionalità per la nostra applicazione in modo da separarsi nettamente dagli altri, e di fornire una solida base per estendere il codice.
Un aspetto negativo tuttavia è possibile, e cioè che gli oggetti letterali hanno il potenziale di crescere lungo i costrutti sintattici. Scegliere di sfruttare il modello di spazio dei nomi nidificato, che utilizza anche lo stesso modello come sua base.

Questo modello ha altre applicazioni utili. Oltre al namespacing, è di beneficio spesso per disaccoppiare la configurazione predefinita della vostra applicazione in un unico spazio che può essere facilmente modificato senza il bisogno di cercarlo lungo tutto il codice solo per modificarlo – gli oggetti letterali lavorano alla grande a questo scopo.
Ecco un esempio di un ipotetico oggetto letterale per la configurazione:

Copia codice

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

Si noti che ci sono solo piccole differenze sintattiche tra il modello di oggetto letterale e lo standard JSON per i set di dati. Se per qualsiasi motivo si desidera utilizzare JSON per memorizzare le configurazioni, ad esempio per la semplice archiviazione quando s’inviano dei back-end, sentitevi liberi di farlo.

3. Namespacing nidificati
Un’estensione del modello di oggetto letterale è annidata con il namespacing. È un altro comune modello che offre un più basso rischio di collisione poiché anche se esiste già uno spazio dei nomi, è improbabile che gli stessi figli annidati lo facciano.
Questo vi sembra familiare?

Copia codice

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

Il framework di Yahoo YUI utilizza il modello di oggetto nidificato con il namespacing, e con AOL usiamo anche questo modello in molte altre delle nostre principali applicazioni. Un esempio d’implementazione di namespacing nidificato può apparire come segue:

Copia codice

var myApp =  myApp || {};
// esegue un controllo analogo al momento
// di definire i bambini nidificati
myApp.routers = myApp.routers || {};
myApp.model = myApp.model || {};
myApp.model.special = myApp.model.special || {};
// gli spazi dei nomi nidificati possono essere
// così  complessi come i seguenti:
myApp.utilities.charting.html5.plotGraph(/*..*/);
myApp.modules.financePlanner.getSummary();
myApp.services.social.facebook.realtimeStream.getLatest();

Si può anche scegliere di dichiarare nuovi spazi dei nomi o proprietà nidificati come proprietà indicizzate come segue:

Copia codice

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

Entrambe le scelte sono leggibili, organizzate e offrono in un modo relativamente sicuro una via per il namespacing della vostra applicazione, in un modo simile a quello che può essere usato in altri linguaggi. L’unica avvertenza è che richiede che il motore JavaScript del browser localizzi prima l’oggetto myApp e poi scavare fino a quando arrivi alla funzione che in realtà si desidera utilizzare.
Questo può significare una maggiore quantità di lavoro da eseguire nelle ricerche, tuttavia sviluppatori come Juriy Zaytsev hanno già provato e trovato che le differenze di prestazioni tra il singolo oggetto namespacing e l’approccio ‘annidato’ sono abbastanza trascurabili.

4. Funzione Espressioni Immediatamente Invocate (IIFE)
Uno IIFE è effettivamente una funzione senza nome che viene invocata immediatamente dopo che è stata definita. In JavaScript, perché entrambe le variabili e le funzioni esplicitamente definite all’interno di questo contesto possono accedere solo tra di loro all’interno di esso, l’invocazione della funzione fornisce un facile mezzo per raggiungere la privacy.
Questo è uno dei tanti motivi per cui lo IIFE è un popolare approccio per incapsulare la logica dell’applicazione per proteggerlo dallo spazio dei nomi globale. Probabilmente hai già incontrato questo modello, prima sotto il nome di una funzione anonima auto eseguibile (self-executing o self-invoked), ma io personalmente preferisco la convezione di denominazione di Ben Alman per questo modello particolare per quanto credo che sia più descrittivo e più accurato.
Una semplice versione di uno IIFE potrebbe essere la seguente:

Copia codice

// un'espressione di una funzione (anonima)
// immediatamente invocata
(function(){
   /*...*/
})();
// un'espressione di una funzione con nome
// immediatamente invocata
(function foobar(){
   /*..*/
}());
// questo è  tecnicamente una funzione
// self-executing, che è  molto diversa
function foobar(){
   foobar();
}

Mentre una versione leggermente più estesa del primo esempio potrebbe essere simile a questo:

Copia codice

var namespace = namespace || {};
// ecco un oggetto namespace viene passato come
// parametro alla funzione, dove si assegnano i
// metodi pubblici e le proprietà le sue proprietà
(function( o ){
   o.foo = "foo";
   o.bar = function(){
      return "bar";
   };
})(namespace);
console.log(namespace);

Mentre leggibile, questo esempio potrebbe essere notevolmente ampliato per rispondere alle preoccupazioni di sviluppo comune, come i livelli di privacy definiti (funzioni pubbliche/private e variabili), nonché l’estensione conveniente dello spazio dei nomi. Andiamo ancora un po’ tramite il codice:

Copia codice

// namespace (il nostro spazio dei nomi) e undefined sono
// passati qui per garantire
// 1. lo spazio dei nomi possono essere modificati
//    a livello locale e non è  sovrascritto al di
//    fuori del contesto della nostra funzione
// 2. il valore undefined è  garantito veramente come
//    indefinito. Questo per evitare problemi con
//    indefiniti  che possono essere mutevole pre-ES5.
(function ( namespace, undefined ) {
   // proprietà private
   var foo = "foo",
      bar = "bar";
   // metodi e proprietà publiche
   namespace.foobar = "foobar";
   namespace.sayHello = function () {
      speak("Ciao Mondo");
   };
   // metodo privato
   function speak(msg) {
      console.log("Hai detto: " + msg);
   };
   // verifica per valutare se 'namespace'
   // esiste nel namespace globale - se non, assegna
   // un oggetto letterale a window.namespace
}(window.namespace = window.namespace || {});
// possiamo testare le nostre proprietà e
// metodi come pubbliche
console.log(namespace.foobar); // foobar
namescpace.sayHello(); // Ciao Mondo
// assegna una nuova proprietà
namespace.foobar2 = "foobar";
console.log(namespace.foobar2);

L’estensibilità è ovviamente fondamentale per scalare qualsiasi modello namespacing e lo IIFE può essere usato per raggiungere quest’obiettivo abbastanza facilmente. Nell’esempio sottostante, il nostro ‘namespace’ è ancora una volta passato come argomento alla nostra funzione anonima ed è poi estesa (o decorata) con altre funzionalità:

Copia codice

// cerchiamo di ampliare lo spazio dei nomi
// con nuove funzionalità
(function( namespace, undefined ){
   // metodo pubblico
   namespace.sayGoodbye = function(){
      console.log(namespace.foo);
      console.log(namespace.bar);
      speak('Ciao');
   }
}( window.namespace = window.namespace || {});
// suo uso
namespace.sayGoodbye(); //Ciao

Questo è tutto per lo IIFE. Se volete saperne di più su questo modello, vi consiglio di leggere in inglese il post sullo IIFE e quello sui modelli dello spazio dei nomi in C#.

5. Iniezione negli spazi dei nomi
L’iniezione nello spazio dei nomi è un’altra variazione di IIFE dove ci si ‘inietta’ metodi e proprietà di un particolare spazio dei nomi all’interno di una funzione avvolgente (wrapper) usando this come uno spazio dei nomi proxy. Il vantaggio di questo modello è di offrire una facile applicazione di comportamenti funzionali a più oggetti o spazi dei nomi e può tornare utile quando si applica un insieme di metodi di base da essere costruiti in un secondo momento (es. getter e setter).
Gli svantaggi di questo modello sono che ci possono essere approcci più facil1 o più ottimali per raggiungere questo obiettivo (es. oggetti di estensioni/fusioni in profondità), già coperti in precedenza in questo articolo.
Qui sotto possiamo vedere un esempio di questo modello in azione, in cui lo usiamo per popolare il comportamento di due spazi dei nomi: il primo, definito inizialmente come (utils) e un altro da creare dinamicamente come una parte della funzionalità di utils (con un nuovo spazio dei nomi chiamato tools).

Copia codice

var myApp = myApp || {};
myApp.utils =  {};
(function() {
   var val = 5;
   this.getValue = function() {
      return val;
   };
   this.setValue = function(newVal) {
      val = newVal;
   }
   // introduce anche una nuova sotto-namespace
   this.tools = {};
}).apply(myApp.utils);
// inietta un nuovo comportamento nel namespace
// tools che abbiamo definito attraverso il
// modulo utilities
(function(){
   this.diagnose = function(){
      return 'diagnosis';
   }
}).apply(myApp.utils.tools);
// notare questo stesso approccio per l'estensione
// potrebbe essere applicato ad un regolare IIFE, con
// passare nel contesto l'argomento e modificando
// non solo il contesto 'this'
// prova
console.log(myApp); // lo spazio dei nomi ora popolato
console.log(myApp.utils.getValue()); // prova di get
myApp.utils.setValue(25); // prova di set
console.log(myApp.utils.getValue());
console.log(myApp.utils.tools.diagnose());

Angus Croll ha già suggerito l’idea di utilizzare la chiamata all’API per fornire una separazione naturale tra contesti e argomenti. Questo modello può fare molto di più di un creatore di modulo, ma come moduli offrono ancora una soluzione di incapsulamento, che l’ho brevemente descritto per accuratezza:

Copia codice

// definisce dei namespace da usare dopo
var ns = ns || {}, ns2 = ns2 || {};
// il creatore di module/namespace
var creator = function(val){
   var val = val || 0;
   this.next = function(){
      return val++
   };
   this.reset = function(){
      val = 0;
   }
}
creator.call(ns);
// ns.next, ns.reset adesso esistono
creator.call(ns2, 5000);
// ns2 contiene lo stesso metodo
// ma ha sovrascritto il valore di val
// a 5000

Come accennato, questo tipo di modello è utile per l’assegnazione di una base simile a un insieme di funzionalità a più moduli o spazi dei nomi, ma posso solo suggerire che usarlo in dichiarazioni esplicite la loro funzionalità all’interno di un oggetto/chiusura per l’accesso diretto non ha senso.

Conclusione
Rivedendo i modelli di spazio dei nomi, la scelta che avrei personalmente usato per la maggior parte delle mie applicazioni più grandi è l’oggetto namespacing annidato e il modello di oggetto letterale.

Lo IIFE e le variabili globali singole possono funzionare bene nell’ambito di piccole applicazioni a medio raggio, invece, per i codici più grandi e che richiedono sia spazi dei nomi e sub-namespace in profondità, è la soluzione sintetica che promuove la leggibilità e la scalabilità.

Vorrei anche consigliare a provare alcuni dei metodi suggeriti di utilità avanzati nell’estensione dello spazio dei nomi per verificare come in realtà consentono di risparmiare tempo nel lungo periodo.