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.
application.utilities.drawing.canvas.2d
.L’equivalente in JavaScript di questa definizione, utilizzando il modello oggetto letterale potrebbe essere:
var application = {
utilities:{
drawing:{
canvas:{
2d:{
/*...*/
}
}
}
}
};
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:
// 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);
// 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
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.
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.
// 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();
$.extend
come si vede qui sotto:
// 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.
Ancora nessun commento