JavaScript closures

Nella letteratura su JavaScript mi sono imbattuto varie volte nelle “closures” soprattutto in riferimento al concetto di “Scope” di una variabile e di “Context“. Ma non avevo mai approfondito più di tanto l’argomento. Poi mi sono imbattuto in un  articolo –  javascript-closure – dove si dice :

 “ritengo che esistano due tipologie di programmatori JavaScript: chi sa come usare le closure e chi no”

La cosa mi ha incuriosito, forse le ho sempre usate senza rendermene conto forse mi sono perso qualche cosa delle potenzialità di JavaScript. Nel dubbio mi sono proposto di approfondire la questione, nell’unico modo che ritengo valido per ben comprendere i concetti in informatica: scrivere esempi esplicativi. Fortunatamente al termine del mio approfondimento mi sono reso conto che le ho sempre usate senza problemi anche se probabilmente non avevo ben chiaro il loro aspetto teorico.
Quest’uso di una risorsa senza aver chiara la sua natura è qualcosa che ha a che fare con la natura di JavaScript: un linguaggio molto flessibile e libero che si presta a diversi stili di utilizzo.
Provenendo da Java ho applicato pattern tipici di questo linguaggio e in questo modo ho utilizzato in modo, per me naturale, queste misteriose “closures”.
In questo articolo cercherò di spiegare come.
Nei vari articoli sulle “closures” che ho consultato si parte sempre dalla distinzione tra “Context” e “Scope”.
Semplificando al massimo con “Scope” ci si riferisce all’ambito di validità di una variabile che può essere “locale” – definita all’interno della funzione – o globale – definita al suo esterno – . JavaScript non è molto raffinato nella gestione dei diversi ambiti di validità delle variabili. Non esistono per esempio le variabili locali a livello di blocco per non parlare di quelle di classe ( le classi semplicemente non esistono in JavaScript) . Semplificando molto si può dire che le variabili locali di una funzione vivono all’interno di ogni singola istanza della funzione stessa.
Con il “Context”  le cose si complicano; per poterne dare un esempio bisogna considerare una funzione nidificata in un altra: tutte le variabile definite all’interno della funzione contenitore e esterne alla funzione nidificata appartengono al “Context”.
Nell’esempio es1. ‘n‘ è una variabile di “Context”.

es1.

var Add = function() {
    var n = 0;
    return function() {
        n+=1;
        return n;
    };
};

var add_es1=Add();
add_es1();
add_es1();
add_es1();
add_es1();
var x=add_es1();

var add_es2=Add();
x=add_es2();
x=add_es2();

Permette di implementare un contatore senza far riferimento ad una variabile globale in quanto la  vita di ‘n‘  sopravvive per così dire alle diverse chiamate della funzione, questo è l’esempio più banale e più diffuso sull’uso delle “closures“, nel caso specifico individuano l’ambito di validità della variabile ‘n‘.
L’esempio es2. è solo un caso un poco più complesso.

es2.

var Add = function() {
    var n = 0;
    return {
        sum: function(x) {
            n += x;
        },
        get: function() {
            return n;
        }
    };
};

var add1 = Add();
add1.sum(10);
add1.sum(15);
add1.sum(50);
var x = add1.get();

var add2 = Add();
add2.sum(100);
add2.sum(1);
var x = add2.get();

Notate che ad ogni nuova istanza ( var add= Add(n); ) si crea un nuovo “Context” completamente indipendente.
Chi viene da Java può considerare la variabile ‘n‘ come una variabile d’istanza.
Credo che la difficoltà a comprendere il “Context” in JavaScript dipenda dal fatto che è un linguaggio ad oggetti senza una definizione esplicita di classe. Ma forse questo è solo un codizionamento che deriva dalle mie esperienze.
L’esempio es3. ,che usa una notazione di moda in JavaScript, serve a confondere un poco le acque e quindi a provocare lo sforzo per meglio comprendere. Sembra funzionare perfettamente, ma se eseguite l’esercizio noterete che è possibile avere una sola istanza, in altri termini si comporta come un “singleton”.

es3.

var Add = (function() {
    var n = 0;
    return  function(x) {
        n += x;
        return n;
    };
})();

var add_es1=Add;
add_es1(10);
add_es1(20);
add_es1(30);
add_es1(100);
var x=add_es1(1);

var add_es2=Add;
add_es2(100);
x=add_es2(100);

Gli esempi es4. e es5. son quasi eguali, l’unica differenza è nella definizione della variabile ‘num‘. Nel primo è definita come variabile di “Context” , quindi utilizzando le “closures“, nel secondo è definita all’interno dell’oggetto ‘op‘. Si potrebbe definire in termini intuitivi come locale rispetto all’oggetto ‘op’ e “contestuale” rispetto ai metodi  – funzioni –  definite in ‘op‘. Notate che in questo caso si deve utilizzare la variabile ‘num‘ utilizzando la parola chiave ‘this‘.

es4:

var Operator = function() {
    var num = 0;
    var op = {
        set: function(n) {
            num = n;
            return this;
        },
        get: function() {
            return num;
        },
        add: function(n) {
            num = num + n;
            return this;
        },
        mult: function(n) {
            num = num * n;
            return this;
        },
        sub: function(n) {
            num = num - n;
            return this;
        },
        div: function(n) {
            num = num / n;
            return this;
        }
    };
    return op;
};

var op_es1 = Operator();
op_es1.set(1);
op_es1.add(10).mult(20);
var x = op_es1.get();

var op_es2 = Operator();
op_es2.set(1000);
op_es2.sub(300);
op_es2.div(10);
x = op_es2.get();

es5.

var Operator = function() {
    var op = {
        num:0,
        set: function(n) {
            this.num = n;
            return this;
        },
        get: function() {
            return this.num;
        },
        add: function(n) {
            this.num = this.num + n;
            return this;
        },
        mult: function(n) {
            this.num = this.num * n;
            return this;
        },
        sub: function(n) {
           this.num = this.num - n;
            return this;
        },
        div: function(n) {
            this.num = this.num / n;
            return this;
        }
    };
    return op;
};

var op_es1 = Operator();
op_es1.set(1);
op_es1.add(10).mult(20);
var x = op_es1.get();

var op_es2 = Operator();
op_es2.set(1000);
op_es2.sub(300);
op_es2.div(10);
x = op_es2.get();

La scelta fra le due soluzioni in questo caso è una questione di gusti, ma si possono presentare delle soluzioni nelle quali utilizzare una tecnica o l’altra permette di avere codice più leggibile. Concludendo si può dire che, al di là della diversa notazione, le “closures” permettono di definire delle variabile che possono considerarsi come le variabili d’ istanza di object in Java ed altri linguaggi ad oggetti (python, c++, ruby, ..).
Questa non è certo la spiegazione più rigorosa delle “closures” ma in un linguaggio “libero” come JavaScript forse la cosa più saggia è costruirsi degli schemi  di organizzazione del codice sulla base delle proprie esperienze con linguaggi più “disciplinati”.

Per provare gli esempi (uaclosure_es.tar.gz) è disponibile un semplice server.py; è necessario solo con IE (restituisce errore 200 quando la response alle request AJAX è un file locale) per tutti gli altri è sufficiente caricare il file index.html.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *