JavaScript: il module pattern

JavaScript: il module pattern

Il module pattern è un design pattern JavaScript ampiamente usato. Generalmente è molto noto, ma vi sono alcuni usi avanzati che non sono ancora stati discussi.

I fondamenti

Alla base di questo design pattern vi è la caratteristica di JavaScript chiamata closure anonima. Creiamo una funzione anonima e la eseguiamo immediatamente. Tutto il codice che viene eseguito all'interno della closure riceve un suo namespace che gli fornisce uno stato privato per tutto il ciclo di vita della nostra applicazione.


(function() {
	//...
}());

Notate le parentesi tonde intorno alla funzione anonima: JavaScript lo richiede perché le asserzioni che iniziano con il token function sono sempre considerate dichiarazioni di funzione.

Includere le parentesi tonde crea un'espressione di funzione. JavaScript ha una caratteristica nota come scope globale implicito. Quando viene usata una variabile o un nome, l'interprete risale la catena dello scope in cerca dell'asserzione varper quel nome o variabile.

Se non lo trova, allora quella variabile viene considerata globale. Se viene usata in un'assegnazione, la variabile globale viene creata se già non esiste. Ciò significa che usare o creare variabili globali in una closure anonima è molto semplice, ma purtroppo rende il nostro codice difficile da gestire.

Per fortuna la nostra funzione anonima fornisce una valida alternativa: passando variabili globali come parametri alla nostra funzione non facciamo altro che importarle nel nostro codice, il che è più lineare e veloce delle variabili globali implicite. Esempio:


(function($) {
  //...
}(jQuery));

Ora abbiamo accesso alla variabile globale jQuery (come $) nel nostro codice.

Il pattern module, in sostanza, si concretizza nel dichiarare variabili globali senza usarle tramite un'esportazione. In pratica si tratta di utilizzare il valore di ritorno della nostra funzione anonima. Esempio:


var Module = (function () {
	var my = {},
	privateProperty = 1;
	
	function privateMethod() { //... }
	
	my.moduleProperty = 1;
	
	my.moduleMethod = function() {
		//...
	};
	return my;
}());

Abbiamo dichiarato un modulo globale con due proprietà pubbliche. Il modulo preserva il suo stato privato interno usando la closure della funzione anonima. In più possiamo importare variabili globali utilizzando il pattern illustrato sopra.

Usi avanzati

Un limite del pattern module è che l'intero modulo deve essere contenuto in un unico file. Tuttavia esiste una soluzione a questo problema: importiamo il modulo, aggiungiamo le proprietà e quindi lo esportiamo:


var Module = (function (my) {
	my.anotherMethod = function() { //...};
	return my;
}(Module));

Dopo che il codice è stato eseguito, il nostro modulo avrà acquisito un nuovo metodo pubblico. Il nuovo file manterrà a sua volta il suo stato interno privato e le sue importazioni.

Possiamo inoltre creare dei moduli flessibili che possono caricarsi in qualsiasi ordine. Ogni file dovrebbe avere la seguente struttura:


var Module = (function (my) { 
	//...
	return my;
}(Module || {}));

Se vogliano invece preservare l'ordine di caricamento possiamo utilizzare quest'altra soluzione:


var Module = (function (my) {
	var oldModuleMethod = my.moduleMethod;
	my.moduleMethod = function () {
		// ...
	};
	return my;
}(Module));

Un grande limite della separazione di un modulo in diversi file è che ciascun file mantiene il suo stato privato e non ha accesso allo stato interno degli altri file. Ecco come risolvere il problema:


var Module = (function (my) {
	var _private = my._private = my._private || {},
	_seal = my._seal = my._seal || function () {
		delete my._private;
		delete my._seal;
		delete my._unseal;
	},
	
	_unseal = my._unseal = my._unseal || function () {
		my._private = _private;
		my._seal = _seal;
		my._unseal = _unseal;
	};
	// ...
	return my;
}(Module || {}));

Ogni file può impostare le sue proprietà sulla sua variabile locale _private. Una volta che il modulo è caricato, l'applicazione dovrebbe eseguire il metodo _seal() che impedirà l'accesso dall'esterno alle proprietà private. Al contrario il metodo _unseal()permette agli altri moduli l'accesso alle proprietà private.

Infine, possiamo creare dei sottomoduli:


Module.sub = (function () { 

	var my = {};
	// ...
	return my;
}());

Ovviamente i sottomoduli hanno tutte le caratteristiche dei moduli normali in quanto sono basati sullo stesso design pattern.

Torna su