35 closure *

Come abbiamo visto nella lezione precedente, una funzione guadagna l’accesso, oltre che alle variabili ‘sue’ (interne), anche alle variabili  della funzione esterna che eventualmente la contiene, nonché alle variabili globali:

let var_esterna = 'esterna';
let funzione = undefined;

function f_esterna() {
    let var_locale = 'locale';

    function f_interna() {

        writeln(var_esterna);
        writeln(var_locale);
    }
funzione = f_interna;
}

La funzione e lo scope alle quali essa ha accesso viene chiamato closure. Tutto il precedente codice realizza una closure, infatti f_interna() ha pieno accesso a var_locale, dichiarata dalla funzione f_esterna() e anche a var_esterna e funzione (globali).

Una parte da notare è che f_esterna() ha nella sua closure l’accesso alle variabili var_esterna e funzione, in particolare assegna alla variabile esterna funzione una sua funzione interna f_interna() :

funzione = f_interna;

Richiamando globalmente f_esterna e funzione() porterebbe a far pensare, come avviene in molti altri linguaggi, che  f_interna sia undefined terminata l’esecuzione di f_esterna

let var_esterna = 'esterna';
let funzione = undefined;

function f_esterna() {
    let var_locale = 'locale';

    function f_interna() {
        writeln(var_esterna);
        writeln(var_locale);
    }

    funzione = f_interna;
}

f_esterna();
funzione();

Oltretutto la f_interna contenuta in f_esterna stampa la variabile globale var_esterna e anche la va_locale dovrebbe essere non più accessibile

function f_interna() {
    writeln(var_esterna);
    writeln(var_locale);
}

L’esecuzione del codice smentisce e sembra proprio che si possa avere accesso a tutto.

Una delle caratteristiche maggiori delle closure è proprio questa, l’avere a disposizione le risorse facenti parte della closure stessa.

Infatti la definizione della funzione esiste, a patto di memorizzarne il riferimento da qualche parte:

funzione = f_interna;

Inoltre evidenziamo che le variabili della closure f_interna(), come var_locale definita da f_esterna(), sono ancora utilizzabili e non smettono di esistere, infatti quando chiamo

funzione();

si va a richiamare la f_interna passatagli che a sua volta richiama la var_locale stampandone il valore.

Aggiungiamo il parametro a f_interna e aggiungiamo una variabile globale in fondo:

let var_esterna = 'esterna';
let funzione = undefined;

function f_esterna() {
    let var_locale = 'locale';

    function f_interna(x) {
        writeln(x);
        writeln(var_esterna);
        writeln(var_locale);
    }

    funzione = f_interna;
}

let ci_sono_anche_io = "oooh!";
f_esterna();
funzione("io sono il parametro");

Ovviamente anche i parametri come x fanno parte della closure di f_interna, ma non solo, anche la variabile ci_sono_anche_io, seppur definita dopo, fa parte del contesto globale della closure di f_interna.

Aggiungendola alla stampa in f_interna vedremo che il suo valore rimarrà intatto

let var_esterna = 'esterna';
let funzione = undefined;

function f_esterna() {
    let var_locale = 'locale';

    function f_interna(x) {
        writeln(x);
        writeln(ci_sono_anche_io);
        writeln(var_esterna);
        writeln(var_locale);
    }

    funzione = f_interna;
}

let ci_sono_anche_io = "oooh!";
f_esterna();
funzione("io sono il parametro");

Anche se la dichiarazione della variabile è stato posizionato dopo la chiusura delle funzioni, comunque viene dichiarato prima della loro esecuzione, se l’avessimo messo in fondo genererebbe errore.

Caso pratico

vediamo l’utilizzo della funzione built-in di js setInterval() , che permette di eseguire una certa funzione ogni tot di millisecondi

setInterval( messaggio, 1000);

function messaggio() {
    writeln('Spegnimento sistema entro pochi secondi...');
}

Ogni secondo (1000 millisecondi) viene eseguita la funzione messaggio().

Se miglioriamo il codice, passando il messaggio come parametro della funzione, il che del tutto normale, al fine di poter far cambiare il messaggio ad un eventuale utilizzatore:

setInterval( messaggio('Spegnimento sistema entro pochi secondi...'), 1000);

function messaggio(ilMessaggio) {
    writeln(ilMessaggio);
}

Ci potremmo aspettare che ogni secondo venga stampato, invece non è così.

seInterval() come primo parametro si aspetta l’indirizzo di una funzione (il nome), non un’esecuzione di funzione.

Utilizzando la closure di messaggio(), dichiariamo una funzione chiamaMessaggio() e passiamo il suo parametro a messaggio()

setInterval( chiamaMessaggio("Spegnimento sistema entro pochi secondi..."), 1000);

function chiamaMessaggio(ilMessaggio) {

    function messaggio() {
        writeln(ilMessaggio);
    }
    return messaggio();
}

Vediamo un modo migliore per raggruppare il codice in una forma più elegante con il setInterval incapsulato nella funzione stessa:

function chiamaMessaggio2(ilMessaggio) {
    setInterval(
        function () {
            writeln(ilMessaggio);
        },
        1000 )
}

chiamaMessaggio2("ciao");

in questo caso viene chiamato internamente il setInterval() alla funzione anonima che nello scope della sua closure otterrà il parametro ilMessaggio ricevuto da chiamaMessaggio()