I moduli sono elementi essenziali dell'architettura di un'applicazione robusta e facilmente gestibile. Essi consentono di organizzare le parti di un'applicazione in unità separate ma integrabili grazie ai meccanismi di esportazione e importazione, cioè rispettivamente della possibilità di rendere pubblicamente accessibile del codice e di accedere a codice esportato da altri moduli.
Prima della loro introduzione con ECMASCript 2015, JavaScript non aveva un meccanismo nativo per la definizione dei moduli e molto spesso il codice veniva scritto senza preoccuparsi di organizzarlo in modo da evitare, ad esempio, conflitti di nomi di variabili o funzioni. Anche se le nuove specifiche prevedono questo importante funzionalità, non possiamo ignorare la quantità di codice JavaScript esistente che implementa i moduli ricorrendo al Module Pattern.
Vediamo quindi come implementare il Module Pattern in JavaScript in maniera efficace.
Module pattern con closure e IIFE
Come abbiamo già avuto modo di vedere parlando di programmazione orientata agli oggetti, JavaScript non ha un meccanismo nativo di incapsulamento e information hiding, ma abbiamo visto come emularlo sfruttando la closure.
Anche nell'implementazione del Module Pattern il concetto di closure ci viene in aiuto. Consideriamo il seguente codice:
var modulo = (function() {
function metodoPrivato() {
//...
}
return {
metodoPubblico: function() {
metodoPrivato();
}
}
})();
In esso assegniamo alla variabile modulo
, che rappresenterà evidentemente il nostro modulo, il risultato di un'espressione IIFE (immediately-invoked function expression
), il cui effetto è la restituzione di un oggetto con il metodo metodoPubblico()
. Nel nostro esempio, questo metodo utilizza il metodo privato implementato internamente alla funzione anonima ed inaccessibile dall'esterno per effetto della closure
.
Abbiamo di fatto implementato un modulo che esporta il metodo metodoPubblico()
e che potrà essere utilizzato come nel seguente esempio:
modulo.metodoPubblico();
Importare moduli esterni
Un modulo deve prevedere anche la possibilità di importare funzionalità da altri moduli. Si potrebbe essere tentati di utilizzare all'interno del nostro modulo direttamente variabili o funzioni globali, dal momento che JavaScript consente di farlo.
Tuttavia questa non è una buona pratica dal momento che non risulterebbe chiara la dipendenza tra le funzionalità dell'applicazione e si perderebbe il vantaggio vero dei moduli, cioè la costruzione di un'applicazione come la combinazione organizzata di unità di codice.
L'importazione di uno o più moduli può essere implementata semplicemente prevedendo dei parametri nella nostra espressione IIFE:
var altroModulo = (function() {
//...
})();
var modulo = (function(moduloEsterno) {
function metodoPrivato() {
moduloEsterno.metodo();
//...
}
return {
metodoPubblico: function() {
metodoPrivato();
}
}
})(altroModulo);
In questo modo definiamo chiaramente la dipendenza del modulo modulo
dal modulo altroModulo
ed evitiamo di creare dipendenze implicite difficili da individuare in applicazioni complesse.