Un de mes amis et moi discutons actuellement de ce qui est une fermeture dans JS et ce qui ne l'est pas. Nous voulons juste nous assurer de bien le comprendre correctement.
Prenons cet exemple. Nous avons une boucle de comptage et voulons imprimer la variable de compteur sur la console différée. Par conséquent, nous utilisons setTimeout
et fermetures pour capturer la valeur de la variable compteur pour nous assurer qu'elle n'imprimera pas N fois la valeur N.
La mauvaise solution sans fermetures ou quoi que ce soit proche des fermetures serait:
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
qui imprimera bien sûr 10 fois la valeur de i
after the loop, soit 10.
Donc sa tentative était:
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2);
}, 1000)
})();
}
impression de 0 à 9 comme prévu.
Je lui ai dit qu'il n'utilisait pas de fermeture pour capturer i
, mais il insiste sur le fait. J'ai prouvé qu'il n'utilise pas de fermetures en plaçant le corps de la boucle for dans un autre setTimeout
(en passant sa fonction anonyme à setTimeout
), en imprimant 10 fois 10 à nouveau. La même chose s'applique si je stocke sa fonction dans un var
et l'exécute après la boucle, en imprimant également 10 fois 10. Donc mon argument est qu'il ne capture pas vraiment la valeur dei
, ce qui rend sa version pas une fermeture.
Ma tentative était:
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2);
}
})(i), 1000);
}
Donc je capture i
(nommé i2
dans la fermeture), mais maintenant je retourne une autre fonction et je la passe. Dans mon cas, la fonction passée à setTimeout capture vraiment i
.
Maintenant, qui utilise les fermetures et qui n'en utilise pas?
Notez que les deux solutions impriment de 0 à 9 sur la console différée, donc elles résolvent le problème d'origine, mais nous voulons comprendre laquelle de ces deux solutions utilise des fermetures pour y parvenir.
la source
Réponses:
Note de l'éditeur: toutes les fonctions en JavaScript sont des fermetures comme expliqué dans cet article . Cependant, nous ne sommes intéressés qu'à identifier un sous-ensemble de ces fonctions qui sont intéressantes d'un point de vue théorique. Désormais, toute référence au mot fermeture fera référence à ce sous-ensemble de fonctions, sauf indication contraire.
Une explication simple pour les fermetures:
Maintenant, utilisons-le pour déterminer qui utilise les fermetures et qui ne le fait pas (pour des raisons d'explication, j'ai nommé les fonctions):
Cas 1: le programme de votre ami
Dans le programme ci-dessus, il y a deux fonctions:
f
etg
. Voyons voir s'il s'agit de fermetures:Pour
f
:i2
est une variable locale .i
est une variable libre .setTimeout
est une variable libre .g
est une variable locale .console
est une variable libre .i
est lié à la portée mondiale.setTimeout
est lié à la portée mondiale.console
est lié à la portée mondiale.i
n'est pas fermé parf
.setTimeout
n'est pas fermé parf
.console
n'est pas fermé parf
.La fonction
f
n'est donc pas une fermeture.Pour
g
:console
est une variable libre .i2
est une variable libre .console
est lié à la portée mondiale.i2
est lié à la portée def
.setTimeout
.console
n'est pas fermé parg
.i2
est fermé parg
.Ainsi, la fonction
g
est une fermeture pour la variable librei2
(qui est une valeur positive pourg
) lorsqu'elle est référencée de l'intérieursetTimeout
.Mauvais pour vous: votre ami utilise une fermeture. La fonction intérieure est une fermeture.
Cas 2: votre programme
Dans le programme ci-dessus, il y a deux fonctions:
f
etg
. Voyons voir s'il s'agit de fermetures:Pour
f
:i2
est une variable locale .g
est une variable locale .console
est une variable libre .console
est lié à la portée mondiale.console
n'est pas fermé parf
.La fonction
f
n'est donc pas une fermeture.Pour
g
:console
est une variable libre .i2
est une variable libre .console
est lié à la portée mondiale.i2
est lié à la portée def
.setTimeout
.console
n'est pas fermé parg
.i2
est fermé parg
.Ainsi, la fonction
g
est une fermeture pour la variable librei2
(qui est une valeur positive pourg
) lorsqu'elle est référencée de l'intérieursetTimeout
.Bon pour vous: vous utilisez une fermeture. La fonction intérieure est une fermeture.
Donc, vous et votre ami utilisez des fermetures. Arrêter de se disputer. J'espère avoir clarifié le concept des fermetures et comment les identifier pour vous deux.
Edit: Une explication simple de la raison pour laquelle toutes les fonctions sont fermées (crédits @Peter):
Considérons d'abord le programme suivant (c'est le contrôle ):
lexicalScope
etregularFunction
ne sont pas des fermetures de la définition ci-dessus .message
à être alertés car ceregularFunction
n'est pas une fermeture (c'est-à-dire qu'il a accès à toutes les variables dans sa portée parent - y comprismessage
).message
est en effet alerté.Considérons maintenant le programme suivant (c'est l' alternative ):
closureFunction
n'est qu'une clôture de la définition ci-dessus .message
ne pas être alerté car ilclosureFunction
s'agit d'une fermeture (c'est-à-dire qu'il n'a accès qu'à toutes ses variables non locales au moment de la création de la fonction ( voir cette réponse ) - cela n'inclut pasmessage
).message
est effectivement alerté.Qu'en déduisons-nous?
la source
g
s'exécute dans la portée desetTimeout
, mais dans le cas 2, vous dites quef
s'exécute dans la portée globale. Ils sont tous les deux dans setTimeout, alors quelle est la différence?Selon la
closure
définition:Vous utilisez
closure
si vous définissez une fonction qui utilise une variable définie en dehors de la fonction. (nous appelons la variable une variable libre ).Ils utilisent tous
closure
(même dans le 1er exemple).la source
i2
qui est définie à l'extérieur.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;
appeler f imprime
Considérons maintenant le cas où nous avons une fonction
g
définie dans une autre fonctionf
.Nous appellerons
f
le parent lexical deg
. Comme expliqué précédemment, nous avons maintenant 2 portées; la portéef
et la portéeg
.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 parentf
.Envisagez de suivre;
appeler f imprime
Regardons la ligne
console.log(foo);
. À ce stade, nous sommes dans la portéeg
et nous essayons d'accéder à la variablefoo
déclarée dans la portéef
. 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 def
. Par conséquent,hello
est imprimé.Regardons maintenant la ligne
console.log(bar);
. À ce stade, nous sommes dans la portéef
et nous essayons d'accéder à la variablebar
déclarée dans la portéeg
.bar
n'est pas déclaré dans la portée actuelle et la fonctiong
n'est pas le parent def
,bar
n'est donc pas définieEn 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
alors
h
serait en mesure d'accéder à toutes les variables déclarées dans le cadre de la fonctionh
,g
etf
. 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;
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;
Bon, voyons ce qui se passe avec la première solution avec JavaScript-No-Closure;
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;
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
eti
seraient liées à l'window
objet et donc accessibles via l'window
objet en JavaScript et JavaScript-No-Closure.la source
i
pas être défini? Vous faites simplement référence à la portée parent, qui est toujours valide s'il n'y a pas eu de fermeture.i2
ài
au moment où vous définissez votre fonction. Cela ne faiti
PAS une variable libre. Pourtant, nous considérons votre fonction comme une fermeture, mais sans aucune variable libre, c'est le point.Je n'ai jamais été satisfait de la façon dont quelqu'un explique cela.
La clé pour comprendre les fermetures est de comprendre à quoi ressemblerait JS sans fermetures.
Sans fermetures, cela générerait une erreur
Une fois que externalFunc est revenu dans une version imaginaire de JavaScript désactivée par la fermeture, la référence à externalVar serait récupérée et ne laisserait rien là pour que la fonction interne fasse référence.
Les fermetures sont essentiellement les règles spéciales qui interviennent et permettent à ces variables d'exister lorsqu'une fonction interne fait référence à des variables d'une fonction externe. Avec les fermetures, les vars référencés sont conservés même après la fin de la fonction extérieure ou «fermés» si cela vous aide à vous souvenir du point.
Même avec des fermetures, le cycle de vie des vars locaux dans une fonction sans fonction interne qui fait référence à ses locaux fonctionne de la même manière que dans une version sans fermeture. Lorsque la fonction est terminée, les habitants récupèrent les ordures.
Une fois que vous avez une référence dans une fonction interne à une variable externe, c'est comme si un chambranle de porte se mettait en travers de la collecte des ordures pour ces variables référencées.
Une façon peut-être plus précise de regarder les fermetures est que la fonction interne utilise essentiellement la portée interne comme sa propre fondation de portée.
Mais le contexte référencé est en fait, persistant, pas comme un instantané. Le déclenchement répété d'une fonction interne renvoyée qui continue à incrémenter et à enregistrer la variable locale d'une fonction externe continuera à alerter des valeurs plus élevées.
la source
Vous utilisez tous les deux des fermetures.
Je vais avec la définition de Wikipedia ici:
La tentative de votre ami utilise clairement la variable
i
, qui n'est pas locale, en prenant sa valeur et en faisant une copie à stocker dans le locali2
.Votre propre tentative passe
i
(qui sur le site d'appel est dans la portée) à une fonction anonyme comme argument. Ce n'est pas une fermeture jusqu'à présent, mais cette fonction renvoie une autre fonction qui fait référence à la même chosei2
. Puisque l'intérieur de la fonction anonyme intérieurei2
n'est pas un local, cela crée une fermeture.la source
i
ài2
, puis définit une certaine logique et exécute cette fonction. Si je n'exécuter immédiatement, mais le stocker dans un var, et exécuté après la boucle, il imprimerait 10, ne serait - il? Il n'a donc pas capturé i.i
. Le comportement que vous décrivez n'est pas le résultat d'une fermeture ou d'une non-fermeture; c'est le résultat de la modification de la variable fermée en attendant. Vous faites la même chose en utilisant une syntaxe différente en appelant immédiatement une fonction et en la passanti
comme argument (qui copie sa valeur actuelle sur place). Si vous mettez le vôtresetTimeout
dans un autre,setTimeout
la même chose se produira.Vous et votre ami utilisez tous les deux des fermetures:
Dans la fonction de code de votre ami
function(){ console.log(i2); }
définie à l'intérieur de la fermeture de la fonction anonymefunction(){ var i2 = i; ...
et peut lire / écrire la variable localei2
.Dans votre code, la fonction est
function(){ console.log(i2); }
définie à l'intérieur de la fermeture de la fonctionfunction(i2){ return ...
et peut lire / écrire la valeur localei2
(déclarée dans ce cas comme paramètre).Dans les deux cas, la fonction est
function(){ console.log(i2); }
ensuite passéesetTimeout
.Un autre équivalent (mais avec moins d'utilisation de la mémoire) est:
la source
Fermeture
Une fermeture n'est pas une fonction ni une expression. Il doit être considéré comme une sorte de «instantané» des variables utilisées en dehors de la fonction et utilisé à l'intérieur de la fonction. Grammaticalement, il faut dire: «prenez la fermeture des variables».
Encore une fois, en d'autres termes: une fermeture est une copie du contexte pertinent des variables dont dépend la fonction.
Encore une fois (naïf): Une fermeture a accès à des variables qui ne sont pas passées en paramètre.
Gardez à l'esprit que ces concepts fonctionnels dépendent fortement du langage / environnement de programmation que vous utilisez. En JavaScript, la fermeture dépend de la portée lexicale (ce qui est vrai dans la plupart des langages c).
Ainsi, le retour d'une fonction revient principalement à une fonction anonyme / sans nom. Lorsque la fonction accède aux variables, non passées en paramètre, et dans sa portée (lexicale), une fermeture a été effectuée.
Donc, concernant vos exemples:
Tous utilisent des fermetures. Ne confondez pas le point d'exécution avec les fermetures. Si le «cliché» des fermetures est pris au mauvais moment, les valeurs peuvent être inattendues mais certainement une fermeture est prise!
la source
Regardons les deux façons:
Déclare et exécute immédiatement une fonction anonyme qui s'exécute
setTimeout()
dans son propre contexte. La valeur actuelle dei
est préservée en faisant une copie eni2
premier; cela fonctionne en raison de l'exécution immédiate.Déclare un contexte d'exécution pour la fonction interne dans lequel la valeur actuelle de
i
est conservée dansi2
; cette approche utilise également l'exécution immédiate pour conserver la valeur.Important
Il convient de mentionner que la sémantique d'exécution n'est PAS la même entre les deux approches; votre fonction intérieure est transmise
setTimeout()
alors que sa fonction intérieure s'appellesetTimeout()
.Envelopper les deux codes dans un autre
setTimeout()
ne prouve pas que seule la deuxième approche utilise des fermetures, il n'y a tout simplement pas la même chose pour commencer.Conclusion
Les deux méthodes utilisent des fermetures, ce qui revient au goût personnel; la deuxième approche est plus facile à "déplacer" ou à généraliser.
la source
setTimeout()
?i
peut être modifiée sans affecter ce que la fonction devrait imprimer, sans dépendre de l'endroit ou du moment où nous l'exécutons.()
, passant ainsi une fonction, et vous voyez 10 fois la sortie10
.()
c'est exactement ce qui fait fonctionner son code, tout comme votre(i)
; vous n'avez pas simplement encapsulé son code, vous y avez apporté des modifications ... par conséquent, vous ne pouvez plus faire de comparaison valide.J'ai écrit cela il y a quelque temps pour me rappeler ce qu'est une fermeture et comment elle fonctionne dans JS.
Une fermeture est une fonction qui, lorsqu'elle est appelée, utilise la portée dans laquelle elle a été déclarée, et non la portée dans laquelle elle a été appelée. En javaScript, toutes les fonctions se comportent comme ceci. Les valeurs variables dans une étendue persistent tant qu'il existe une fonction qui pointe toujours vers elles. L'exception à la règle est «ceci», qui fait référence à l'objet dans lequel se trouve la fonction lorsqu'elle est appelée.
la source
Après avoir inspecté de près, on dirait que vous utilisez tous les deux la fermeture.
Dans le cas de vos amis,
i
est accessible dans la fonction anonyme 1 eti2
est accessible dans la fonction anonyme 2 où leconsole.log
est présent.Dans votre cas, vous accédez à l'
i2
intérieur d'une fonction anonyme où seconsole.log
trouve. Ajoutez unedebugger;
déclaration avantconsole.log
et dans les outils de développement Chrome sous "Variables de portée", il indiquera sous quelle portée la variable est.la source
Considérer ce qui suit. Cela crée et recrée une fonction
f
qui se fermei
, mais différentes!:tandis que ce qui suit se ferme sur "une" fonction "elle-même"
(eux-mêmes! l'extrait de code suivant utilise un seul référent
f
)ou pour être plus explicite:
NB. la dernière définition de
f
estfunction(){ console.log(9) }
avant0
est imprimée.Caveat! Le concept de fermeture peut être une distraction coercitive de l'essence de la programmation élémentaire:
x-refs .:
Comment fonctionnent les fermetures JavaScript?
Fermetures Javascript Explication Une fermeture
(JS) nécessite-t-elle une fonction à l'intérieur d'une fonction
Comment comprendre les fermetures en Javascript?
Javascript confusion variable locale et globale
la source
Run' only was desired - not sure how to remove the
je ne sais pas comment contrôler - Copier`Je voudrais partager mon exemple et une explication sur les fermetures. J'ai fait un exemple python et deux figures pour montrer les états de pile.
La sortie de ce code serait la suivante:
Voici deux figures pour montrer les piles et la fermeture attachée à l'objet fonction.
lorsque la fonction est renvoyée par le fabricant
lorsque la fonction est appelée plus tard
Lorsque la fonction est appelée via un paramètre ou une variable non locale, le code a besoin de liaisons de variables locales telles que margin_top, padding ainsi que a, b, n. Afin de garantir le fonctionnement du code de fonction, le cadre de pile de la fonction de création qui a disparu depuis longtemps doit être accessible, ce qui est sauvegardé dans la fermeture que nous pouvons trouver avec l'objet de message de fonction.
la source
delete
lien sous la réponse.