En un mot Javascript Closures permettent une fonction d' accéder à une variable qui est déclarée dans une fonction-parent lexical .
Voyons une explication plus détaillée. Pour comprendre les fermetures, il est important de comprendre comment JavaScript étend les variables.
Portées
En JavaScript, les étendues sont définies avec des fonctions. Chaque fonction définit une nouvelle portée.
Prenons l'exemple suivant;
function f()
{//begin of scope f
var foo='hello'; //foo is declared in scope f
for(var i=0;i<2;i++){//i is declared in scope f
//the for loop is not a function, therefore we are still in scope f
var bar = 'Am I accessible?';//bar is declared in scope f
console.log(foo);
}
console.log(i);
console.log(bar);
}//end of scope f
appeler f imprime
hello
hello
2
Am I Accessible?
Considérons maintenant le cas où nous avons une fonction g
définie dans une autre fonction f
.
function f()
{//begin of scope f
function g()
{//being of scope g
/*...*/
}//end of scope g
/*...*/
}//end of scope f
Nous appellerons f
le parent lexical de g
. Comme expliqué précédemment, nous avons maintenant 2 portées; la portée f
et la portée g
.
Mais une portée est "dans" l'autre portée, donc la portée de la fonction enfant fait-elle partie de la portée de la fonction parent? Que se passe-t-il avec les variables déclarées dans la portée de la fonction parent; pourrai-je y accéder depuis le champ de la fonction enfant? C'est exactement là que les fermetures interviennent.
Fermetures
En JavaScript, la fonction g
peut non seulement accéder à toutes les variables déclarées dans la portée, g
mais également accéder à toutes les variables déclarées dans la portée de la fonction parent f
.
Envisagez de suivre;
function f()//lexical parent function
{//begin of scope f
var foo='hello'; //foo declared in scope f
function g()
{//being of scope g
var bar='bla'; //bar declared in scope g
console.log(foo);
}//end of scope g
g();
console.log(bar);
}//end of scope f
appeler f imprime
hello
undefined
Regardons la ligne console.log(foo);
. À ce stade, nous sommes dans la portée g
et nous essayons d'accéder à la variable foo
déclarée dans la portée f
. Mais comme indiqué précédemment, nous pouvons accéder à n'importe quelle variable déclarée dans une fonction parent lexicale, ce qui est le cas ici; g
est le parent lexical de f
. Par conséquent, hello
est imprimé.
Regardons maintenant la ligne console.log(bar);
. À ce stade, nous sommes dans la portée f
et nous essayons d'accéder à la variable bar
déclarée dans la portée g
. bar
n'est pas déclaré dans la portée actuelle et la fonction g
n'est pas le parent de f
, bar
n'est donc pas définie
En fait, nous pouvons également accéder aux variables déclarées dans le cadre d'une fonction lexicale de «grand parent». Par conséquent, s'il y avait une fonction h
définie dans la fonctiong
function f()
{//begin of scope f
function g()
{//being of scope g
function h()
{//being of scope h
/*...*/
}//end of scope h
/*...*/
}//end of scope g
/*...*/
}//end of scope f
alors h
serait en mesure d'accéder à toutes les variables déclarées dans le cadre de la fonction h
, g
et f
. Cela se fait avec des fermetures . Dans les fermetures JavaScript, nous pouvons accéder à toute variable déclarée dans la fonction parent lexicale, dans la fonction grand parent lexical, dans la fonction grand-grand parent lexical, etc. Cela peut être vu comme une chaîne de portée ; scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
jusqu'à la dernière fonction parent qui n'a pas de parent lexical.
L'objet fenêtre
En fait, la chaîne ne s'arrête pas à la dernière fonction parent. Il y a une autre portée spéciale; la portée mondiale . Chaque variable non déclarée dans une fonction est considérée comme déclarée dans la portée globale. La portée mondiale a deux spécialités;
- chaque variable déclarée dans la portée globale est accessible partout
- les variables déclarées dans la portée globale correspondent aux propriétés de l'
window
objet.
Par conséquent, il existe exactement deux façons de déclarer une variable foo
dans la portée globale; soit en ne le déclarant pas dans une fonction, soit en définissant la propriété foo
de l'objet window.
Les deux tentatives utilisent des fermetures
Maintenant que vous avez lu une explication plus détaillée, il peut maintenant être évident que les deux solutions utilisent des fermetures. Mais pour être sûr, faisons une preuve.
Créons un nouveau langage de programmation; JavaScript sans fermeture. Comme son nom l'indique, JavaScript-No-Closure est identique à JavaScript, sauf qu'il ne prend pas en charge les fermetures.
En d'autres termes;
var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello
Bon, voyons ce qui se passe avec la première solution avec JavaScript-No-Closure;
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}, 1000)
})();
}
par conséquent, cela s'imprimera undefined
10 fois en JavaScript sans fermeture.
Par conséquent, la première solution utilise la fermeture.
Regardons la deuxième solution;
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
par conséquent, cela s'imprimera undefined
10 fois en JavaScript sans fermeture.
Les deux solutions utilisent des fermetures.
Modifier: il est supposé que ces 3 extraits de code ne sont pas définis dans la portée globale. Sinon, les variables foo
et i
seraient liées à l' window
objet et donc accessibles via l' window
objet en JavaScript et JavaScript-No-Closure.