Categories
funzioni linguaggio javascript namespace

Funzioni e closure

In JavaScript _non_ esistono namespace, quindi potrebbe sorgere il problema della sovrapposizione dei nomi delle variabili (shadowing) e/o della loro visibilità.

Una possibilie soluzione consiste nel proteggere queste variabili evitando di usare il namespace globale e, usare, invece, una funzione come contenitore che possa proteggere la visibilità di queste entità.

In questo caso, in JavaScript, si potrebbe osservare dentro una funzione (usata come namespace) la definizione delle variabili locali e di altre funzioni annidate che vengono usate per restituire le precedenti variabili o altre funzioni.

First class object

Per proteggere le variabili locali in JavaScript è possibile usare anche le classi, come zucchero sintattico, ma si devono comunque studiare le closure perchè è importante saperle riconocere quando le si incontra.

Anche i componenti di React Native hanno la caratteristica di poter essere creati sia usando le classi, sia usando le funzioni. Per approfondimento si legga questo articolo:

In JavaScript tutte le funzioni possono essere considerate closure perchè tutte le funzioni sono oggetti di prima classe (first class object). Questo significa che le funzioni sono oggetti completi, che possono essere assegnati alle variabili e restituiti da altre funzioni.

Bisogna immaginare gli oggetti di tipo funzione composti da due parti:

  • il vero e prorprio codice sorgente della funzione
  • un riferimento all’ambiente della sua definizione, referencing environment o lexical environment, cioè una tabella con i nomi e i valori delle variabili che si possono trovare definite al suo esterno, in un’eventuale funzione genitore (parent function).

La gerarchia di funzioni annidate tra loro può essere espressa mediante alcuni termini genealogici:

  • ancestor function
  • parent function
  • function
le funzioni annidate si possono paragonare ad una scatola dentro un altra scatola
le funzioni annidate si possono paragonare ad una scatola dentro un altra scatola

In questo caso, la funzione genitore non ha accesso al contenuto del figlio, mentre il figlio ha accesso alle variabili contenute nella funzione genitore.

Per capire meglio, può essere utile analizzare alcuni termini che possono avere un siginificato diverso in diversi linguaggi di programmazione:

  • lexical environment
  • binding
  • lifetime
  • closure

Binding

Il binding di una variabile è l’associazione (temporanea) tra il suo nome simbolico e il suo valore nella memoria, mentre lo scope di una variabile è il luogo nel codice sorgente dove questo binding funziona.

Il lexical scope viene creato al momento della definizione della funzione, ed è memorizzato all’interno dell’oggetto funzione (insieme al codice sorgente della funzione).

Questa capacità della funzione figlio di catturare le variabili locali dell’ambiente esterno (genitore) in cui sono state definite permette di ottenere un deep binding.

Lifetime

La durata della vita delle variabili dipende da dove si riserva la memoria. Nello stack la durata delle variabili locali sarebbe limitata alla durata dell’esecuzione della funzione. Nell’heap si possono usare reference con allocazione dinamica e una durata della vita potenzialmente superiore.

L’implementazione del JavaScript deve consentire una durata maggiore per poter implementare le closure.

Closure

La parola closure deriva dall’inglese closed expression, che indica un’espressione sintattica (una definizione di funzione) che viene collegata (bound), oppure tenuta vicino (close by) all’ambiente in cui essa è definita (referencing environment, oppure, lexical environment). Questo ambiente risulta essere composto dalle variabili locali della funzione parent.

Vediamo un piccolo esempio di codice dove si crea una funzione genitore inizializza(), che viene eseguita solo la prima volta per memorizzare la variabile stato di un oggetto iniziale e per proteggerlo da accessi non autorizzati.

La funzione inizializza() permette di fare contemporaneamente anche un’altra cosa: restituisce la funzione incrementa() e la mette a disposizione per agire eventualmente sulle variabili locali di inizializza().

let inizializza = function (stato) => {
    const incrementa = function (n) => {
        const oggetto = {
                         stato,
			 n
        };
        return oggetto;
    }
    return incrementa;
};

/* prima esecuzione del genitore inizializza() 
   memorizza lo stato in modo protetto
   e contemporaneamente restituisce la definizione
   della funzione figlio incrementa() che realizza 
   una closure con la variabile stato
*/

let primo = inizializza("acceso"); // -> function incrementa()

// esecuzione della funzione figlio 

const secondo = primo(99); // -> {"stato": "acceso", "n": 99}

Nella riga 19 non è possibile invocare prima la funzione incrementa(), perchè essa viene definita solo dentro la funzione inizializza().

Nella riga 23 si nota che i valori delle variabili sono gli stessi della prima esecuzione della funzione genitore, inizializza(), ovvero quelli memorizzati durante la definizione della funzione figlio, incrementa(), mentre non dipendono dal momento dell’invocazione successiva della funzione figlio. Infatti, quando si invoca primo(99) la variabile stato dovrebbe essere stata distrutta. Al contrario, lo stato vale "acceso".

La closure è un oggetto di tipo funzione che riesce a mantenere l’ambiente che esisteva quando il suo ambiente esterno (genitore) era ancora in esecuzione. Quando si invoca la funzione closure (funzione figlio) si usano dei reference alle variabili locali del genitore. Questo permette di recuperare il valore che tali variabili avevano nell’ambiente in cui erano state definite, nonostante questo ambiente non esista più.

Tale comportamento è detto lexical binding

Se la definizione e l’invocazione della funzione figlio avvengono nello stesso momento e nello stesso luogo, allora la closure non è interessante e non produce effetti rilevanti.

Vedere anche l’articolo…

Definire le funzioni

Leave a Reply