Cos'è il namespacing?

Indice

In molti linguaggi di programmazione, il namespacing è una tecnica impiegata per evitare collisioni con altri oggetti o variabili nello spazio dei nomi globale. Sono anche molto utili per aiutare organizzare i blocchi delle funzionalità dell’applicazione in gruppi facilmente gestibili che possono essere identificati in modo univoco.
In JavaScript, il namespacing a livello aziendale è fondamentale perché è importante per evitare un blocco in caso che un altro script nella pagina utilizzi lo stesso nome variabile o metodo. Con il numero di terze parti che ultimamente si aggiungono nelle pagine, questo può essere un problema comune a tutti noi e che abbiamo bisogno di affrontare a un certo punto della nostra carriera. Come ‘cittadini’ ben educati sullo spazio dei nomi globale, sono anche indispensabili fare del nostro meglio per impedire che somiglianze con script di altri sviluppatori non si eseguano a causa degli stessi problemi.

Mentre JavaScript in realtà non è dotato di supporto per i namespace, come altre lingue, ha oggetti e chiusure che possono essere utilizzati per ottenere un effetto simile.
Modelli avanzati per il namespacing
In questa sezione, si descriveranno alcuni modelli e le tecniche avanzate di utilità che aiutano quando si lavora su progetti di grandi dimensioni e che richiedono un nuovo approccio su come elaborate il namespacing dell’applicazione. Si dovrebbe specificare che qui non si sostiene una qualsiasi di queste come *il* modo di fare le cose, ma solo *un* modo di fare che si trova in pratica lavorando.

Automatizzare il namespacing nidificato
Come probabilmente siete a conoscenza, uno spazio dei nomi nidificato fornisce, una gerarchia organizzata di strutture in un’applicazione. Un esempio di uno spazio dei nomi potrebbe essere la seguente: application.utilities.drawing.canvas.2d.
L’equivalente in JavaScript di questa definizione, utilizzando il modello oggetto letterale potrebbe essere:
Copia codice

var application = {
   utilities:{
      drawing:{
         canvas:{
            2d:{
               /*...*/
            }
         }
      }
   }
};
Wow, è brutto.
Una delle sfide evidenti con questo modello è che ogni profondità supplementare che si desidera creare richiede ancora un altro oggetto da definire come figlio di qualche genitore nel vostro livello principale dello spazio dei nomi. Questo può diventare particolarmente laborioso quando molti strati di profondità sono richiesti dall’applicazione, non appena aumenti in complessità.
Come può essere risolto questo problema? In JavaScript Patterns, Stoyan Stefanov presenta (in inglese) un approccio molto intelligente, per definire automaticamente spazi dei nomi nidificati in una variabile globale esistente utilizzando un metodo pratico che prende una singola stringa come argomento per un nodo, l’analizza e l’inserisce automaticamente nel namespace di base con gli oggetti richiesti.
Il metodo che si propone di utilizzare è il seguente, che ho aggiornato perché sia una funzione generica e facilitarne il riutilizzo, con più spazi dei nomi:
Copia codice

// livello principale dello spazio dei nomi
// viene assegnato un oggetto letterale
var myApp = myApp || {};
// una funzione di aiuto per l'analisi della stringa
// per il namespace e la generazione automatica
// di spazi dei nomi nidificati
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++) {
        // creare una proprietà se non esiste
        if (typeof parent[parts[i]] == 'undefined') {
            parent[parts[i]] = {};
        }
        parent = parent[parts[i]];
    }
    return parent;
}
// esempio d'uso:
// estende myApp con uno spazio dei nomi
// nidificato in profondità
var mod = extend(myApp, 'myApp.modules.module2');
// uscita dell'oggetto corretto
// nidificato in profondità
console.log(mod);
// prova minore per verificare se l'istanza di mod può
// essere utilizzata anche al di fuori del namesapce
// myApp come un clone che include le estensioni
console.log(mod == myApp.modules.module2); //true
// ulteriore dimostrazione di facile assegnazione
// dello spazio dei nomi nidificato utilizzando extend
extend(myApp, 'moduleA.moduleB.moduleC.moduleD');
extend(myApp, 'longer.version.looks.like.this');
console.log(myApp);
Si noti come in precedenza si sarebbe dovuto dichiarare esplicitamente i diversi nodi per il loro spazio dei nomi come oggetti, questo adesso può essere facilmente realizzato utilizzando una unica e più pulita linea di codice. Questo funziona veramente bene solo quando si definisce puramente il namespace, ma può sembrare un po’ ‘meno flessibile’ quando si desidera definire entrambi: cioè , quando si dichiarano le funzioni e le proprietà allo stesso tempo nello spazio dei nomi. Indipendentemente da ciò , è ancora molto potente e un simile approccio lo utilizzo regolarmente in alcuni dei miei progetti.
Dipendenza della dichiarazione del modello
In questa sezione andremo a dare un’occhiata a una minore ampliazione del modello per il namespacing nidificato che si possono trovare in alcune applicazioni. Sappiamo tutti che i riferimenti locali per gli oggetti possono ridurre, in generale, i tempi di ricerca, ma cerchiamo di applicare questo namespacing per vedere come potrebbe apparire in pratica:
Copia codice

// approccio comune per l'accesso
// a spazi dei nomi nidificati
myApp.utilities.math.fibonacci(25);
myApp.utilities.math.sin(56);
myApp.utilities.drawing.plot(98,50,60);
// con riferimenti local/cached
var utils = myApp.utilities,
maths = utils.math,
drawing = utils.drawing;
// accesso semplice allo spazio dei nomi
maths.fibonacci(25);
maths.sin(56);
drawing.plot(98, 50,60);
// notare che quanto sopra è  particolarmente performante
// rispetto alle centinaia o migliaia di chiamate al
// namespace annidato contro un riferimento locale ad esso
Lavorare con una variabile locale è di solito più veloce che lavorare con un’altra a livello globale. È anche più conveniente e più performante che accedere alle proprietà nidificate/sub dello spazio dei nomi su ogni riga successiva e può migliorare la leggibilità in applicazioni più complesse.
Stoyan raccomanda di dichiarare il namespace richiesto da una funzione o da un modulo in cima alla portata della funzione (usando un modello per la singola variabile) e chiama a questo un modello dichiarazione di dipendenza. Uno se i vantaggi che questo offre è una diminuzione delle dipendenze locali e risolverli, avendo un’architettura estensibile che consente di caricare dinamicamente i moduli nel proprio spazio dei nomi quando è richiesto.
A mio parere questo modello funziona meglio quando si lavora a un livello modulare, localizzando un namespace per essere utilizzato da un gruppo di metodi. La localizzazione di spazi di nomi per ogni singolo livello, soprattutto se c’è una rilevante sovrapposizione tra dipendenze dello spazio dei nomi, sarebbe una cosa che mi sento raccomandare di evitare, ove possibile. Invece, più in alto si definiscono e tutti avranno gli accessi allo stesso riferimento.
Estensione dell'oggetto in profondità
Un approccio alternativo al namespacing automatico è l’estensione profonda dell’oggetto. Spazi dei nomi definiti usando la notazione letterale dell’oggetto, possono essere facilmente estesi (o fusi) con altri oggetti (o spazi dei nomi) in modo tale che le proprietà e le funzioni di entrambi i namespaces possono essere accessibili sotto lo stesso namespace post-fusione.
Questo è qualcosa che è stato abbastanza facile da realizzare con i moderni framework in JavaScript (ad esempio vedere in jQuery il metodo $.extend), tuttavia, se stai cercando di estendere l’oggetto (namespace) utilizzando vaniglia JS, la routine che segue può essere d’aiuto.
Copia codice

// extend.js
// scritto da andrew dupont,
// ottimizzato da addy osmani,
// tradotto da sergio parilli
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");
// definire un namespace al top-level
var myNS = myNS || {};
// 1. estendere lo spazio dei nomi
//    con un oggetto 'utils'
extend(myNS, {
   utils:{
   }
});
console.log('prova 1', myNS);
//myNS.utils ora esiste
// 2. estendere con più  profondità
//    (namespace.hello.world.wave)
extend(myNS, {
   hello:{
      world:{
         wave:{
            test: function(){
               /*...*/
            }
         }
      }
   }
});
// prova diretta di assegnazione
// funziona come previsto
myNS.hello.test1 = 'questa è  una prova';
myNS.hello.world.test2 = 'questa è  un altra prova';
console.log('prova 2', myNS);
// 3. cosa succederebbe se myNS contiene già lo spazio
//    dei nomi se viene aggiunto (es. 'library')?
//    vogliamo garantire l'assenza del namespace che
//    sarà sovrascritto durante l'estensione
myNS.library = {
   foo:function(){}
};
extend(myNS, {
   library:{
      bar:function(){
         /*...*/
      }
   }
});
// confermato che extend funziona in modo sicuro
// (come previsto) e myNS ora contiene anche
// library.foo, library.bar
console.log('prova 3', myNS);
// 4. come dovrebbe essere scritto se volessimo
//    avere un più  facile accesso a uno spazio
//    dei nomi specifico senza dover digitare
//    l'intero spazio dei nomi di volta in volta?.
var shorterAccess1 = myNS.hello.world;
shorterAccess1.test3 = "Ciao ancora";
console.log('test 4', myNS);
// successo, myApp.hello.world.test3
// contiene adesso 'Ciao ancora'
console.groupEnd();
Se vi capita di usare jQuery nella propria applicazione, è possibile ottenere lo stesso identico oggetto namespace utilizzando $.extend come si vede qui sotto:
Copia codice

// livello principale del namespace
var myApp = myApp || {};
// assegna direttamente uno spazio dei nomi nidificato
myApp.library = {
   foo:function(){ /*..*/}
};
// extend/merge in profondità questo spazio dei nomi
// con un altro per fare cose interessanti, diciamo
// che è  uno spazio dei nomi con lo stesso nome
// ma con una firma diversa nella funzione:
// $.extend(deep, target, object1, object2)
$.extend(true, myApp, {
   library:{
      bar:function(){
         /*..*/
      }
   }
});
console.log('prova', myApp);
// myApp ora contiene sia il metodo library.foo()
// che library.bar() e nulla è  stato sovrascritto,
// che è  quello che attendiamo.