Article original : JavaScript Closures Explained by Mailing a Package
Par Prashant Ram
Les closures en JavaScript sont l'un de ces concepts que beaucoup ont du mal à comprendre. Dans l'article suivant, je vais expliquer en termes clairs ce qu'est une closure, et je vais illustrer cela avec des exemples de code simples.
Qu'est-ce qu'une closure ?
Une closure est une fonctionnalité en JavaScript où une fonction interne a accès aux variables de la fonction externe (enclosante) — une chaîne de portée.
La closure a trois chaînes de portée :
- elle a accès à sa propre portée — les variables définies entre ses accolades
- elle a accès aux variables de la fonction externe
- elle a accès aux variables globales
Pour les non-initiés, cette définition peut sembler être un simple jargon !
Mais qu'est-ce qu'une closure, vraiment ?
Une closure simple
Examinons un exemple simple de closure en JavaScript :
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
Ici, nous avons deux fonctions :
- une fonction externe
outerqui a une variableb, et retourne la fonctioninner - une fonction interne
innerqui a sa propre variable appeléea, et accède à une variablebdeouter, dans son corps de fonction
La portée de la variable b est limitée à la fonction outer, et la portée de la variable a est limitée à la fonction inner.
Invocons maintenant la fonction outer(), et stockons le résultat de la fonction outer() dans une variable X. Invocons ensuite la fonction outer() une deuxième fois et stockons-la dans la variable Y.
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a + b);
}
return inner;
}
var X = outer(); // outer() invoquée la première fois
var Y = outer(); // outer() invoquée la deuxième fois
Examinons étape par étape ce qui se passe lorsque la fonction outer() est invoquée pour la première fois :
- La variable
best créée, sa portée est limitée à la fonctionouter(), et sa valeur est définie à10. - La ligne suivante est une déclaration de fonction, donc rien à exécuter.
- Sur la dernière ligne,
return innercherche une variable appeléeinner, trouve que cette variableinnerest en fait une fonction, et retourne donc tout le corps de la fonctioninner. [Notez que l'instructionreturnn'exécute pas la fonction interne — une fonction n'est exécutée que lorsqu'elle est suivie de()— mais plutôt l'instructionreturnretourne tout le corps de la fonction.] - Le contenu retourné par l'instruction
returnest stocké dansX. Ainsi,Xstockera ce qui suit :function inner() {var a=20;console.log(a+b);} - La fonction
outer()termine son exécution, et toutes les variables dans la portée deouter()n'existent plus.
Cette dernière partie est importante à comprendre. Une fois qu'une fonction a terminé son exécution, toutes les variables qui étaient définies dans la portée de la fonction cessent d'exister.
La durée de vie d'une variable définie à l'intérieur d'une fonction est la durée de vie de l'exécution de la fonction.
Cela signifie que dans console.log(a+b), la variable b n'existe que pendant l'exécution de la fonction outer(). Une fois que la fonction outer a terminé son exécution, la variable b n'existe plus.
Lorsque la fonction est exécutée une deuxième fois, les variables de la fonction sont créées à nouveau, et vivent seulement jusqu'à ce que la fonction termine son exécution.
Ainsi, lorsque outer() est invoquée la deuxième fois :
- Une nouvelle variable
best créée, sa portée est limitée à la fonctionouter(), et sa valeur est définie à10. - La ligne suivante est une déclaration de fonction, donc rien à exécuter.
return innerretourne tout le corps de la fonctioninner.- Le contenu retourné par l'instruction
returnest stocké dansY. - La fonction
outer()termine son exécution, et toutes les variables dans la portée deouter()n'existent plus.
Le point important ici est que lorsque la fonction outer() est invoquée la deuxième fois, la variable b est créée à nouveau. De plus, lorsque la fonction outer() termine son exécution la deuxième fois, cette nouvelle variable b cesse à nouveau d'exister.
C'est le point le plus important à réaliser. Les variables à l'intérieur des fonctions n'existent que lorsque la fonction est en cours d'exécution, et cessent d'exister une fois que la fonction a terminé son exécution.
Revenons maintenant à notre exemple de code et examinons X et Y. Puisque la fonction outer() retourne une fonction lors de son exécution, les variables X et Y sont des fonctions.
Cela peut être facilement vérifié en ajoutant ce qui suit au code JavaScript :
console.log(typeof(X)); // X est de type fonction
console.log(typeof(Y)); // Y est de type fonction
Puisque les variables X et Y sont des fonctions, nous pouvons les exécuter. En JavaScript, une fonction peut être exécutée en ajoutant () après le nom de la fonction, comme X() et Y().
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
var X = outer();
var Y = outer();
// fin des exécutions de la fonction outer()
X(); // X() invoquée la première fois
X(); // X() invoquée la deuxième fois
X(); // X() invoquée la troisième fois
Y(); // Y() invoquée la première fois
Lorsque nous exécutons X() et Y(), nous exécutons essentiellement la fonction inner.
Examinons étape par étape ce qui se passe lorsque X() est exécutée la première fois :
- La variable
aest créée, et sa valeur est définie à20. - JavaScript essaie maintenant d'exécuter
a + b. C'est là que les choses deviennent intéressantes. JavaScript sait queaexiste puisque c'est lui qui vient de la créer. Cependant, la variablebn'existe plus. Puisquebfait partie de la fonction externe,bn'existerait que pendant l'exécution de la fonctionouter(). Puisque la fonctionouter()a terminé son exécution bien avant que nous n'invoquionsX(), toutes les variables dans la portée de la fonctionoutercessent d'exister, et donc la variablebn'existe plus.
Comment JavaScript gère-t-il cela ?
Closures
La fonction inner peut accéder aux variables de la fonction englobante grâce aux closures en JavaScript. En d'autres termes, la fonction inner préserve la chaîne de portée de la fonction englobante au moment où la fonction englobante a été exécutée, et peut ainsi accéder aux variables de la fonction englobante.
Dans notre exemple, la fonction inner avait préservé la valeur de b=10 lorsque la fonction outer() a été exécutée, et a continué à la préserver (closure).
Elle se réfère maintenant à sa chaîne de portée et remarque qu'elle a bien la valeur de la variable b dans sa chaîne de portée, puisqu'elle avait enfermé la valeur de b dans une closure au moment où la fonction outer avait été exécutée.
Ainsi, JavaScript connaît a=20 et b=10, et peut calculer a+b.
Vous pouvez vérifier cela en ajoutant la ligne de code suivante à l'exemple ci-dessus :
function outer() {
var b = 10;
function inner() {
var a = 20;
console.log(a + b);
}
return inner;
}
var X = outer();
console.dir(X); // utilisez console.dir() au lieu de console.log()
Ouvrez l'élément Inspect dans Google Chrome et allez dans la Console. Vous pouvez développer l'élément pour voir réellement l'élément Closure (affiché dans l'avant-dernière ligne ci-dessous). Remarquez que la valeur de b=10 est préservée dans la Closure même après que la fonction outer() ait terminé son exécution.
La variable b=10 est préservée dans la Closure, Closures en JavaScript
Revenons maintenant à la définition des closures que nous avons vue au début et voyons si elle a maintenant plus de sens.
Ainsi, la fonction interne a trois chaînes de portée :
- accès à sa propre portée — variable
a - accès aux variables de la fonction
outer— variableb, qu'elle a enfermée - accès à toute variable globale qui peut être définie
Closures en action
Pour bien comprendre les closures, enrichissons l'exemple en ajoutant trois lignes de code :
function outer() {
var b = 10;
var c = 100;
function inner() {
var a = 20;
console.log("a= " + a + " b= " + b);
a++;
b++;
}
return inner;
}
var X = outer(); // outer() invoquée la première fois
var Y = outer(); // outer() invoquée la deuxième fois
// fin des exécutions de la fonction outer()
X(); // X() invoquée la première fois
X(); // X() invoquée la deuxième fois
X(); // X() invoquée la troisième fois
Y(); // Y() invoquée la première fois
Lorsque vous exécutez ce code, vous verrez la sortie suivante dans la console.log :
a=20 b=10
a=20 b=11
a=20 b=12
a=20 b=10
Examinons ce code étape par étape pour voir exactement ce qui se passe et pour voir les closures en action !
var X = outer(); // outer() invoquée la première fois
La fonction outer() est invoquée la première fois. Les étapes suivantes ont lieu :
- La variable
best créée, et est définie à10La variablecest créée, et définie à100Appelons celab(premiere_fois)etc(premiere_fois)pour notre propre référence. - La fonction
innerest retournée et assignée àXÀ ce stade, la variablebest enfermée dans la chaîne de portée de la fonctioninneren tant que closure avecb=10, puisqueinnerutilise la variableb. - La fonction
outertermine son exécution, et toutes ses variables cessent d'exister. La variablecn'existe plus, bien que la variablebexiste en tant que closure dansinner.
var Y= outer(); // outer() invoquée la deuxième fois
- La variable
best créée à nouveau et est définie à10La variablecest créée à nouveau. Notez que même siouter()a été exécutée une fois auparavant, les variablesbetcont cessé d'exister une fois que la fonction a terminé son exécution, elles sont créées en tant que nouvelles variables. Appelons celles-cib(deuxieme_fois)etc(deuxieme_fois)pour notre propre référence. - La fonction
innerest retournée et assignée àYÀ ce stade, la variablebest enfermée dans la chaîne de portée de la fonctioninneren tant que closure avecb(deuxieme_fois)=10, puisqueinnerutilise la variableb. - La fonction
outertermine son exécution, et toutes ses variables cessent d'exister. La variablec(deuxieme_fois)n'existe plus, bien que la variableb(deuxieme_fois)existe en tant que closure dansinner.
Voyons maintenant ce qui se passe lorsque les lignes de code suivantes sont exécutées :
X(); // X() invoquée la première fois
X(); // X() invoquée la deuxième fois
X(); // X() invoquée la troisième fois
Y(); // Y() invoquée la première fois
Lorsque X() est invoquée la première fois,
- la variable
aest créée, et définie à20 - la valeur de
a=20, la valeur debest celle de la closure.b(premiere_fois), doncb=10 - les variables
aetbsont incrémentées de1 X()termine son exécution et toutes ses variables internes — la variablea— cessent d'exister. Cependant,b(premiere_fois)a été préservée en tant que closure, doncb(premiere_fois)continue d'exister.
Lorsque X() est invoquée la deuxième fois,
- la variable
aest créée à nouveau, et définie à20Toute valeur précédente de la variablean'existe plus, puisqu'elle a cessé d'exister lorsqueX()a terminé son exécution la première fois. - la valeur de
a=20la valeur debest prise à partir de la valeur de la closure —b(premiere_fois)Notez également que nous avions incrémenté la valeur debde1à partir de l'exécution précédente, doncb=11 - les variables
aetbsont incrémentées de1à nouveau X()termine son exécution et toutes ses variables internes — la variablea— cessent d'exister Cependant,b(premiere_fois)est préservée puisque la closure continue d'exister.
Lorsque X() est invoquée la troisième fois,
- la variable
aest créée à nouveau, et définie à20Toute valeur précédente de la variablean'existe plus, puisqu'elle a cessé d'exister lorsqueX()a terminé son exécution la première fois. - la valeur de
a=20, la valeur debest celle de la closure —b(premiere_fois)Notez également que nous avions incrémenté la valeur debde1dans l'exécution précédente, doncb=12 - les variables
aetbsont incrémentées de1à nouveau X()termine son exécution, et toutes ses variables internes — la variablea— cessent d'exister Cependant,b(premiere_fois)est préservée puisque la closure continue d'exister
Lorsque Y() est invoquée la première fois,
- la variable
aest créée à nouveau, et définie à20 - la valeur de
a=20, la valeur debest celle de la closure —b(deuxieme_fois), doncb=10 - les variables
aetbsont incrémentées de1 Y()termine son exécution, et toutes ses variables internes — la variablea— cessent d'exister Cependant,b(deuxieme_fois)a été préservée en tant que closure, doncb(deuxieme_fois)continue d'exister.
Remarques finales
Les closures sont l'un de ces concepts subtils en JavaScript qui sont difficiles à saisir au début. Mais une fois que vous les comprenez, vous réalisez que les choses n'auraient pas pu être autrement.
Espérons que ces explications étape par étape vous ont vraiment aidé à comprendre le concept des closures en JavaScript !
Autres articles :