De temps en temps, je vois des "fermetures" mentionnées, et j'ai essayé de les rechercher, mais Wiki ne donne pas d'explication que je comprends. Quelqu'un pourrait-il m'aider ici?
language-features
closures
Gablin
la source
la source
Réponses:
(Avertissement: ceci est une explication de base; en ce qui concerne la définition, je simplifie un peu)
Le moyen le plus simple de penser à une fermeture est une fonction qui peut être stockée en tant que variable (appelée "fonction de première classe"), qui a une capacité spéciale d'accéder à d'autres variables locales à l'étendue dans laquelle elle a été créée.
Exemple (JavaScript):
Les fonctions 1 assignées à
document.onclick
etdisplayValOfBlack
sont des fermetures. Vous pouvez voir qu'ils font tous les deux référence à la variable booléenneblack
, mais cette variable est affectée en dehors de la fonction. Comme ilblack
est local à la portée où la fonction a été définie , le pointeur sur cette variable est préservé.Si vous mettez ceci dans une page HTML:
Cela montre que les deux ont le même accès
black
et peuvent être utilisés pour stocker l’état sans objet encapsuleur.L'appel à
setKeyPress
est pour montrer comment une fonction peut être passée comme n'importe quelle variable. La portée conservée dans la fermeture est toujours celle où la fonction a été définie.Les fermetures sont couramment utilisées comme gestionnaires d'événements, notamment dans JavaScript et ActionScript. Une bonne utilisation des fermetures vous aidera à lier implicitement des variables aux gestionnaires d'événements sans avoir à créer un wrapper d'objet. Cependant, une utilisation négligente entraînera des fuites de mémoire (par exemple, lorsqu'un gestionnaire d'événements inutilisé mais préservé est la seule chose à conserver pour les objets volumineux en mémoire, notamment les objets DOM, empêchant ainsi le garbage collection).
1: En fait, toutes les fonctions en JavaScript sont des fermetures.
la source
black
est déclaré dans une fonction, cela ne serait-il pas détruit lorsque la pile se déroulera ...?black
est déclaré dans une fonction, cela ne serait-il pas détruit". Rappelez-vous également que si vous déclarez un objet dans une fonction, puis l'affectez à une variable qui se trouve ailleurs, cet objet est préservé car il contient d'autres références.Une fermeture est fondamentalement juste une façon différente de regarder un objet. Un objet est une donnée à laquelle une ou plusieurs fonctions sont liées. Une fermeture est une fonction à laquelle une ou plusieurs variables sont liées. Les deux sont fondamentalement identiques, au moins au niveau de la mise en œuvre. La vraie différence réside dans leur origine.
En programmation orientée objet, vous déclarez une classe d'objet en définissant ses variables de membre et ses méthodes (fonctions de membre) en amont, puis vous créez des instances de cette classe. Chaque instance est accompagnée d'une copie des données du membre, initialisée par le constructeur. Vous avez alors une variable de type d'objet que vous transmettez sous forme de donnée, car l'accent est mis sur sa nature en tant que donnée.
En revanche, dans une fermeture, l'objet n'est pas défini au préalable comme une classe d'objet, ni instancié via un appel de constructeur dans votre code. Au lieu de cela, vous écrivez la fermeture en tant que fonction dans une autre fonction. La fermeture peut faire référence à l'une des variables locales de la fonction externe. Le compilateur le détecte et déplace ces variables de l'espace de pile de la fonction externe vers la déclaration d'objet masqué de la fermeture. Vous avez alors une variable de type fermeture, et même s'il s'agit essentiellement d'un objet situé sous le capot, vous la transmettez en tant que référence de fonction, car l'accent est mis sur sa nature en tant que fonction.
la source
Le terme de fermeture vient du fait qu'un morceau de code (bloc, fonction) peut avoir des variables libres qui sont fermées (c'est-à-dire liées à une valeur) par l'environnement dans lequel le bloc de code est défini.
Prenons par exemple la définition de la fonction Scala:
Dans le corps de la fonction , il existe deux noms (variables)
v
etk
indiquant deux valeurs entières. Le nomv
est lié car il est déclaré en tant qu'argument de la fonctionaddConstant
(en regardant la déclaration de fonction, nous savons qu'une valeurv
sera attribuée lorsque la fonction est appelée). Le nomk
est libre par rapport à la fonctionaddConstant
car celle-ci ne contient aucune indication sur la valeurk
liée (et comment).Afin d'évaluer un appel comme:
nous devons assigner
k
une valeur, ce qui ne peut se produire que si le nomk
est défini dans le contexte dans lequeladdConstant
est défini. Par exemple:Maintenant que nous avons défini
addConstant
dans un contexte oùk
est défini,addConstant
est devenu une clôture car toutes ses variables libres sont maintenant fermées (liées à une valeur):addConstant
peuvent être invoquées et transmises comme s'il s'agissait d'une fonction. Notez que la variable librek
est liée à une valeur lorsque la fermeture est définie , alors que la variable d'argumentv
est liée lorsque la fermeture est invoquée .Ainsi, une fermeture est fondamentalement une fonction ou un bloc de code qui peut accéder à des valeurs non locales via ses variables libres après que celles-ci ont été liées par le contexte.
Dans de nombreuses langues, si vous utilisez une fermeture une seule fois, vous pouvez la rendre anonyme , par exemple:
Notez qu'une fonction sans variables libres est un cas particulier de fermeture (avec un ensemble vide de variables libres). De manière analogue, une fonction anonyme est un cas particulier de fermeture anonyme. En d'autres termes , une fonction anonyme est une fermeture anonyme sans variables libres.
la source
Une explication simple en JavaScript:
alert(closure)
utilisera la valeur précédemment créée declosure
. L'alertValue
espace de noms de la fonction retournée sera connecté à l'espace de noms dans lequelclosure
réside la variable. Lorsque vous supprimez l'intégralité de la fonction, la valeur de laclosure
variable sera supprimée, mais jusqu'à cette date, laalertValue
fonction sera toujours en mesure de lire / écrire la valeur de la variableclosure
.Si vous exécutez ce code, la première itération attribue la valeur 0 à la
closure
variable et réécrit la fonction pour:Et comme il a
alertValue
besoin de la variable localeclosure
pour exécuter la fonction, il se lie à la valeur de la variable locale précédemment assignéeclosure
.Et maintenant, chaque fois que vous appelez la
closure_example
fonction, elle écrira la valeur incrémentée de laclosure
variable caralert(closure)
est liée.la source
Une "fermeture" est, en substance, un état local et du code, combinés dans un paquet. En général, l'état local provient d'une portée (lexicale) environnante et le code est (essentiellement) une fonction interne qui est ensuite renvoyée à l'extérieur. La fermeture est alors une combinaison des variables capturées vues par la fonction interne et du code de la fonction interne.
C'est une de ces choses qui est, malheureusement, un peu difficile à expliquer, à cause de son ignorance.
Une analogie que j'ai utilisée avec succès dans le passé était «imaginons que nous ayons quelque chose que nous appelons« le livre », dans la fermeture de la salle,« le livre »est cette copie là-bas, dans le coin, de TAOCP, mais sur la table de fermeture. , c’est cette copie d’un livre de Dresden Files. Ainsi, en fonction de la fermeture dans laquelle vous vous trouvez, le code "donnez-moi le livre" a pour résultat que différentes choses se passent. "
la source
static
variable locale peut-elle être considérée comme une fermeture? Les fermetures à Haskell impliquent-elles un état?static
variable locale, vous en avez exactement une).Il est difficile de définir ce qu'est la clôture sans définir le concept d '«État».
Fondamentalement, dans un langage avec une portée lexicale complète qui traite les fonctions comme des valeurs de première classe, quelque chose de spécial se produit. Si je devais faire quelque chose comme:
La variable
x
non seulement référencefunction foo()
mais aussi référence à l’état qui afoo
été laissé lors du dernier retour. La vraie magie se produit quandfoo
d'autres fonctions sont définies plus en détail dans son champ d'application; c'est comme son propre mini-environnement (tout comme 'normalement' nous définissons des fonctions dans un environnement global).Sur le plan fonctionnel, il peut résoudre bon nombre des mêmes problèmes que le mot clé 'statique' de C ++ (C?), Qui conserve l'état d'une variable locale tout au long de plusieurs appels de fonction; Cependant, cela ressemble plus à l'application du même principe (variable statique) à une fonction, car les fonctions sont des valeurs de première classe; fermeture ajoute la prise en charge de l’ensemble de l’état de la fonction à enregistrer (rien à voir avec les fonctions statiques de C ++).
Traiter les fonctions comme des valeurs de première classe et ajouter la prise en charge des fermetures signifie également que vous pouvez avoir plus d'une instance de la même fonction en mémoire (similaire aux classes). Cela signifie que vous pouvez réutiliser le même code sans avoir à réinitialiser l'état de la fonction, comme cela est nécessaire pour traiter les variables statiques C ++ dans une fonction (cela peut être faux à ce sujet?).
Voici quelques tests du support de fermeture de Lua.
résultats:
Cela peut devenir compliqué, et cela varie probablement d’une langue à l’autre, mais il semble en Lua que chaque fois qu’une fonction est exécutée, son état est réinitialisé. Je dis cela parce que les résultats du code ci-dessus seraient différents si nous accédions
myclosure
directement à la fonction / à l’état (au lieu de passer par la fonction anonymepvalue
renvoyée ), ce qui reviendrait à 10; mais si nous accédons à l'état de myclosure par x (la fonction anonyme), vous pouvez voir qu'ilpvalue
est bien vivant quelque part dans la mémoire. J'imagine qu'il y a un peu plus, mais quelqu'un peut peut-être mieux expliquer la nature de la mise en œuvre.PS: Je ne connais pas une couche de C ++ 11 (autre que celles des versions précédentes), alors notez qu'il ne s'agit pas d'une comparaison entre les fermetures en C ++ 11 et Lua. En outre, toutes les "lignes" tracées de Lua à C ++ sont des similitudes en tant que variables statiques et fermetures ne sont pas identiques à 100%; même s’ils sont parfois utilisés pour résoudre des problèmes similaires.
Ce dont je ne suis pas sûr, c’est, dans l’exemple de code ci-dessus, si la fonction anonyme ou la fonction d’ordre supérieur est considérée comme la fermeture?
la source
Une fermeture est une fonction à laquelle l'état associé:
En perl, vous créez des fermetures comme celle-ci:
Si nous regardons la nouvelle fonctionnalité fournie avec C ++.
Il vous permet également de lier l’état actuel à l’objet:
la source
Considérons une fonction simple:
Cette fonction s'appelle une fonction de niveau supérieur car elle n'est imbriquée dans aucune autre fonction. Chaque fonction JavaScript s’associe à une liste d’objets appelée "chaîne d’étendue" . Cette chaîne d'étendue est une liste ordonnée d'objets. Chacun de ces objets définit des variables.
Dans les fonctions de niveau supérieur, la chaîne d'étendue est constituée d'un seul objet, l'objet global. Par exemple, la fonction
f1
ci-dessus a une chaîne d'étendue qui contient un seul objet qui définit toutes les variables globales. (Notez que le terme "objet" ne signifie pas ici d'objet JavaScript, il s'agit simplement d'un objet défini par l'implémentation qui agit comme un conteneur de variables, dans lequel JavaScript peut "rechercher" des variables.)Lorsque cette fonction est appelée, JavaScript crée un objet appelé "objet d'activation" et le place en haut de la chaîne d'étendue. Cet objet contient toutes les variables locales (par exemple
x
ici). Nous avons donc maintenant deux objets dans la chaîne d’étendue: le premier est l’objet d’activation et, au-dessous, l’objet global.Notez très attentivement que les deux objets sont placés dans la chaîne de l'oscilloscope à des moments différents. L'objet global est placé lorsque la fonction est définie (c'est-à-dire lorsque JavaScript a été analysé et que l'objet fonction a été créé) et l'objet d'activation est entré lorsque la fonction a été appelée.
Donc, nous savons maintenant ceci:
La situation devient intéressante lorsque nous traitons avec des fonctions imbriquées. Alors, créons-en un:
Une fois
f1
défini, nous obtenons une chaîne de portée contenant uniquement l'objet global.Désormais, lorsqu’elle
f1
est appelée, la chaîne d’étendue def1
obtient l’objet d’activation. Cet objet d'activation contient la variablex
et la variablef2
qui est une fonction. Et, notez que celaf2
est en train de se définir. Par conséquent, à ce stade, JavaScript enregistre également une nouvelle chaîne d'étendue pourf2
. La chaîne d'étendue enregistrée pour cette fonction interne est la chaîne d'étendue actuelle en vigueur. La chaîne de champs d'application en vigueur est celle def1
's. Par conséquentf2
« la chaîne de portée estf1
» de courant chaîne de portée - qui contient l'objet d'activationf1
et de l'objet global.Quand
f2
est appelé, il obtient son propre objet d'activation contenanty
, ajouté à sa chaîne d'étendue qui contient déjà l'objet d'activation def1
et l'objet global.Si une autre fonction imbriquée était définie au sein de celle-ci
f2
, sa chaîne d'étendue contiendrait trois objets au moment de la définition (2 objets d'activation de deux fonctions externes et l'objet global) et 4 au moment de l'appel.Nous comprenons donc maintenant comment fonctionne la chaîne d’étendue, mais nous n’avons pas encore parlé de fermeture.
La plupart des fonctions sont appelées à l'aide de la même chaîne d'étendue que celle qui était en vigueur lors de la définition de la fonction, et peu importe qu'il y ait une fermeture. Les fermetures deviennent intéressantes quand elles sont appelées dans une chaîne de champs différente de celle qui était en vigueur lors de leur définition. Cela se produit le plus souvent lorsqu'un objet de fonction imbriqué est renvoyé à partir de la fonction dans laquelle il a été défini.
Lorsque la fonction revient, cet objet d'activation est supprimé de la chaîne d'étendue. S'il n'y a pas de fonctions imbriquées, il n'y a plus de référence à l'objet d'activation et il est récupéré. Si des fonctions imbriquées ont été définies, chacune de ces fonctions a une référence à la chaîne d'étendue et cette chaîne d'étendue fait référence à l'objet d'activation.
Si ces fonctions imbriquées restent dans leur fonction externe, elles seront elles-mêmes collectées, ainsi que l'objet d'activation auquel elles font référence. Mais si la fonction définit une fonction imbriquée et la renvoie ou la stocke dans une propriété quelque part, il y aura une référence externe à la fonction imbriquée. Il ne sera pas nettoyé et l'objet d'activation auquel il fait référence ne le sera pas non plus.
Dans notre exemple ci-dessus, nous ne retournons pas
f2
depuisf1
. Par conséquent, lorsqu'un appelf1
retourne, son objet d'activation est supprimé de la chaîne d'étendue et les ordures collectées. Mais si nous avions quelque chose comme ça:Ici, le retour
f2
aura une chaîne d'étendue qui contiendra l'objet d'activation def1
, et par conséquent, il ne sera pas récupéré. À ce stade, si nous appelonsf2
, il sera en mesure d'accéder àf1
la variable,x
même si nous sommes en dehors def1
.Nous pouvons donc voir qu’une fonction garde sa chaîne d’étendue avec elle et qu’elle contient tous les objets d’activation des fonctions externes. C'est l'essence de la fermeture. Nous disons que les fonctions en JavaScript ont une "portée lexicale" , ce qui signifie qu'elles sauvegardent la portée qui était active quand elles ont été définies par opposition à la portée qui était active quand elles ont été appelées.
Il existe un certain nombre de techniques de programmation puissantes qui impliquent des fermetures telles que l'approximation de variables privées, la programmation événementielle, l'application partielle , etc.
Notez également que tout cela s'applique à toutes les langues prenant en charge les fermetures. Par exemple, PHP (5.3+), Python, Ruby, etc.
la source
Une fermeture est une optimisation du compilateur (aka sucre syntaxique?). Certaines personnes ont également qualifié cet objet d'objet du pauvre .
Voir la réponse d'Eric Lippert : (extrait ci-dessous)
Le compilateur générera le code comme ceci:
Avoir un sens?
En outre, vous avez demandé des comparaisons. VB et JScript créent tous deux des fermetures de la même manière.
la source