Da un punto di vista puramente semantico, una closure può essere definita come una espressione o una funzione che può accedere a particolari variabili presenti in un ambiente ben delimitato che, appunto, "racchiude" la funzione.
È un concetto molto discusso soprattutto recentemente visto l'enorme successo di JavaScript, linguaggio che interpreta perfettamente questa definizione. Anche in altri linguaggi è presente questa notazione, ma le maggiori potenzialità di questa tecnica emergono proprio nel linguaggio di scripting principe nell'epoca di AJAX e delle richieste asincrone effettuate da pagine web.
Il concetto di scope chain
Ritornando alle lezioni sullo scope, possiamo riaffermare che qualsiasi espressione JavaScript (sia essa una funzione globale o un particolare metodo di un oggetto) viene eseguita in uno scope che rappresenta l'ambiente all'interno della quale essa risiede. Questa affermazione non è del tutto precisa. Infatti le espressioni non presentano un unico scope, bensì un insieme gerarchico di ambienti di esecuzione, identificato come scope chain, catena di scope.
Ovviamente questa catena può essere composta da un unico scope e quindi la precedente frase risulta oggettivamente vera, ma non è sempre cosi: ci possono essere espressioni con scope chain di differenti ampiezze. Giusto per fare un primo esempio:
<script type="text/javascript">
var global = "ciao";
function func() {
var local1 = "come";
var inner = function() {
var local2 = "va?";
alert(global + " " + local1 + " " + local2);
}
inner.call();
}
window.onload = function() {
document.getElementsByTagName("button")[0].onclick = func;
}
</script>
<button>Bottone 1</button>
In questo piccolo e banale esempio abbiamo una variabile globale, una variabile locale per la funzione func()
e una variabile locale per la function inner()
. L'esecuzione di inner
(tramite il metodo call()
) fa sì che essa venga eseguita all'interno dello scope rappresentato dalla funzione func()
che a sua volta viene eseguita all'interno dello scope globale (che ricordo è l'oggetto window
): viene a costruirsi una vera e propria catena.
La funzione inner
quindi ha a disposizione sia variabili locali a se stessa (local2
) sia variabili prese in prestito da qualsiasi scope facente parte alla catena (local1
e global
). Questo ovviamente perché non ci sono conflitti di nomi; nel momento in cui due variabili avessero lo stesso nome, avrebbe priorità quella appartenente allo scope più "vicino" alla funzione eseguita:
<script type="text/javascript">
var name = "globale";
function func() {
var name = "locale di func()";
var inner = function() {
var name = "locale di inner()";
alert(name);
}
inner.call();
}
window.onload = function() {
document.getElementsByTagName("button")[0].onclick = func;
}
</script>
<button>Bottone 1</button>
In questo caso leggeremmo "locale di inner()
", in quanto essa è stata definita ad un livello il più vicino possibile alla funzione (praticamente proprio al suo interno).