Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 33 di 112
  • livello avanzato
Indice lezioni

Clousure e scope

Comprendere il concetto di Closure in JavaScript a partire dallo scope delle variabili, dalla distinzione tra scope globale e locale e dal concetto di scope chain
Comprendere il concetto di Closure in JavaScript a partire dallo scope delle variabili, dalla distinzione tra scope globale e locale e dal concetto di scope chain
Link copiato negli appunti

Tra i concetti fondamentali sulle funzioni abbiamo esaminato l'ambito di visibilità o scope delle variabili, distinguendo:

  • scope globale, accessibilità estesa all'intero script;
  • scope locale, accessibilità ristretta al solo codice di una funzione o di un blocco di codice.

Alla luce degli aspetti delle funzioni che abbiamo visto in questa sezione, la suddivisione tra scope locale e globale appare un po' troppo semplicistica. Infatti, nella determinazione degli scope e quindi del contesto di esecuzione intervengono altri fattori dipendenti dalla possibilità di definire funzioni all'interno di funzioni, di associare funzioni a variabili e di passarle o ottenerle da altre funzioni.

Vediamo di analizzare queste problematiche con qualche esempio. Consideriamo il seguente codice:

var saluto = "Buongiorno";
function saluta(persona) {
	var nomeCognome = persona.nome + " " + persona.cognome;
	function visualizzaSaluto() {
		console.log(saluto + " " + nomeCognome);
	}
	visualizzaSaluto();
}
saluta({nome: "Mario", cognome: "Rossi"});

Abbiamo definito una funzione saluta() che prende un oggetto persona e visualizza una stringa con un saluto rivolto al nome e cognome della persona. La visualizzazione della stringa è affidata ad una funzione definita all'interno del corpo della funzione saluta().

Nella definizione del codice abbiamo sfruttato alcune caratteristiche dello scope delle variabili che già conoscevamo, come ad esempio il fatto che all'interno di una funzione è possibile fare riferimento ad una variabile globale. Nel nostro caso la funzione visualizzaSaluto() accede alla variabile saluto definita globalmente.

Scope Chain

Un aspetto nuovo evidenziato dal codice è il fatto che la funzione visualizzaSaluto(), definita all'interno della funzione saluta(), possa accedere ad una variabile locale di quest'ultima, nello specifico nomeCognome.

Non si ha più, quindi, una semplice distinzione tra scope locale e globale, ma è possibile avere una vera e propria gerarchia di scope o scope chain.

Una funzione, infatti, può accedere allo scope locale, allo scope globale ed allo scope accessibile dalla funzione in cui è stata definita (funzione esterna), il quale può essere a sua volta il risultato della combinazione del proprio scope locale con lo scope della sua funzione esterna e così via.

La cosa ancora più interessante è il fatto che in JavaScript l'accesso allo scope della sua funzione esterna è consentito anche dopo che questa ha terminato la sua esecuzione.

Consideriamo il seguente esempio derivato dal codice precedente:

var saluto = "Buongiorno";
var visualizzaSaluto; 
function saluta(persona) {
	var nomeCognome = persona.nome + " " + persona.cognome; 
	return function() {
		console.log(saluto + " " + nomeCognome);
	};
}
visualizzaSaluto = saluta({nome: "Mario", cognome: "Rossi"});
visualizzaSaluto();

In questo caso la funzione saluta() non visualizza direttamente la stringa ma restituisce una funzione che assolve questo compito. Pertanto, quando la funzione restituita viene invocata, la funzione saluta() (la sua funzione esterna) ha terminato la sua esecuzione e quindi il suo contesto di esecuzione non esiste più. Nonostante ciò è ancora possibile accedere alla variabile nomeCognome presente nel suo scope locale.

Definizione di Closure

Il principio di base su cui si fonda questo meccanismo stabilisce che ogni variabile che era accessibile quando una funzione è stata definita rimane "racchiusa" nello scope accessibile dalla funzione. Questo meccanismo è detto closure.

La closure di una funzione ha un enorme potere espressivo e può essere sfruttata in maniera creativa per definire pattern di programmazione evoluta, come vedremo più avanti nella guida.

Tuttavia in certe situazioni può rappresentare una trappola insidiosa la cui identificazione richiede un'attenta analisi.

Consideriamo ad esempio il seguente codice che rappresenta un'ulteriore evoluzione dell'esempio precedente:

var saluto = "Buongiorno";
var visualizzaSaluti;
function saluta(persone) {
	var nomeCognome;
	var saluti = [];
	for (var i in persone) {
		nomeCognome = persone[i].nome + " " + persone[i].cognome;
		saluti.push(function() {
			console.log(saluto + " " + nomeCognome);
		});
	}
	return saluti;
}
visualizzaSaluti = saluta([{nome: "Mario", cognome: "Rossi"},
                           {nome: "Marco", cognome: "Neri"}]);
for (var i in visualizzaSaluti) {
	visualizzaSaluti[i]();
}

In questo caso, in corrispondenza di un array di persone, intendiamo generare un array di funzioni che visualizzano i rispettivi saluti.

Nel caso specifico ci aspetteremmo che vengano visualizzate due stringhe personalizzate per i due oggetti presenti nell'array. Invece il risultato che otteniamo è la medesima stringa ripetuta due volte per la stessa persona: l'ultimo elemento dell'array.

Il perchè di questo comportamento inatteso è legato alla closure delle funzioni definite all'interno del ciclo. Ciascuna delle due funzioni fa riferimento alla variabile locale nomeCognome della funzione saluta() ed il valore di questa variabile nel momento in cui viene restituito l'array di funzioni è proprio la concatenazione del nome e cognome dell'ultimo elemento dell'array. Questo spiega il perchè della visualizzazione della stessa stringa.

Per aggirare l'ostacolo possiamo fare ricorso ad una immediately-invoked function expression che ci consente di fissare il valore corrente della variabile:

var saluto = "Buongiorno";
var visualizzaSaluti;
function saluta(persone) {
	var nomeCognome;
	var saluti = [];
	for (var i in persone) {
		nomeCognome = persone[i].nome + " " + persone[i].cognome;
		// immediately-invoked function expression
		saluti.push((function(nc) {
						return function() {
							console.log(saluto + " " + nc);
						};
					})(nomeCognome));
	}
	return saluti;
}
visualizzaSaluti = saluta([{nome: "Mario", cognome: "Rossi"},
                           {nome: "Marco", cognome: "Neri"}]);
for (var i in visualizzaSaluti) { visualizzaSaluti[i](); }

Il codice evidenzia come, al posto di aggiungere nell'array direttamente la funzione incaricata di visualizzare il saluto, definiamo una funzione che restituisce la funzione desiderata dopo averla invocata passando la variabile nomeCognome come parametro. In questo modo fissiamo il valore della variabile eliminando di fatto il riferimento diretto alla variabile nomeCognome.


Ti consigliamo anche