Qu'est-ce qu'une «fermeture»?

432

J'ai posé une question sur le curry et les fermetures ont été mentionnées. Qu'est-ce qu'une fermeture? Comment est-ce lié au curry?

Ben
la source
22
Maintenant, quelle est exactement la fermeture ??? Certaines réponses disent que la fermeture est la fonction. Certains disent que c'est la pile. Certaines réponses disent que c'est la valeur "cachée". À ma connaissance, il s'agit de la fonction + variables incluses.
Roland
3
Explique ce qu'est une fermeture: stackoverflow.com/questions/4103750/…
dietbuddha
Jetez également un œil à Qu'est-ce qu'une fermeture? à softwareengineering.stackexchange
B12Toaster
Explique ce qu'est une fermeture et le cas d'utilisation courant: trungk18.com/experience/javascript-closure
Sasuke91

Réponses:

744

Portée variable

Lorsque vous déclarez une variable locale, cette variable a une portée. Généralement, les variables locales n'existent que dans le bloc ou la fonction dans laquelle vous les déclarez.

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

Si j'essaie d'accéder à une variable locale, la plupart des langues la rechercheront dans la portée actuelle, puis remonteront dans les portées parent jusqu'à ce qu'elles atteignent la portée racine.

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

Lorsqu'un bloc ou une fonction est terminé, ses variables locales ne sont plus nécessaires et sont généralement supprimées de la mémoire.

C'est ainsi que nous nous attendons normalement à ce que les choses fonctionnent.

Une fermeture est une portée de variable locale persistante

Une fermeture est une portée persistante qui conserve les variables locales même après que l'exécution du code a quitté ce bloc. Les langages qui prennent en charge la fermeture (tels que JavaScript, Swift et Ruby) vous permettront de conserver une référence à une étendue (y compris ses étendues parentes), même après l'exécution du bloc dans lequel ces variables ont été déclarées, à condition que vous gardiez une référence à ce bloc ou cette fonction quelque part.

L'objet scope et toutes ses variables locales sont liés à la fonction et persisteront tant que cette fonction persistera.

Cela nous donne la portabilité des fonctions. Nous pouvons nous attendre à ce que toutes les variables qui étaient dans la portée lorsque la fonction a été définie pour la première fois soient toujours dans la portée lorsque nous l'appellerons plus tard, même si nous appelons la fonction dans un contexte complètement différent.

Par exemple

Voici un exemple très simple en JavaScript qui illustre le point:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

Ici, j'ai défini une fonction au sein d'une fonction. La fonction interne accède à toutes les variables locales de la fonction externe, y compris a. La variable aest dans la portée de la fonction interne.

Normalement, lorsqu'une fonction se termine, toutes ses variables locales sont supprimées. Cependant, si nous renvoyons la fonction interne et l'affectons à une variable fncafin qu'elle persiste après outersa sortie, toutes les variables qui étaient dans la portée au moment de innersa définition persistent également . La variable aa été fermée au cours - elle est dans une fermeture.

Notez que la variable aest totalement privée de fnc. C'est un moyen de créer des variables privées dans un langage de programmation fonctionnel tel que JavaScript.

Comme vous pourriez le deviner, lorsque j'appelle, fnc()il affiche la valeur de a, qui est "1".

Dans un langage sans fermeture, la variable aaurait été récupérée et jetée à la outersortie de la fonction . L'appel de fnc aurait généré une erreur car il an'existe plus.

En JavaScript, la variable apersiste car la portée de la variable est créée lors de la première déclaration de la fonction et persiste aussi longtemps que la fonction continue d'exister.

aappartient à la portée de outer. La portée de innera un pointeur parent sur la portée de outer. fncest une variable qui pointe vers inner. apersiste aussi longtemps que fncpersiste. aest dans la fermeture.

superluminaire
la source
116
Je pensais que c'était un assez bon et facile à comprendre.
user12345613
16
Merci pour la formidable explication, j'en ai vu beaucoup mais c'est le moment où je l'ai vraiment compris.
Dimitar Dimitrov
2
Puis-je avoir un exemple de la façon dont cela fonctionne dans une bibliothèque comme JQuery comme indiqué dans le 2ème au dernier paragraphe? Je n'ai pas totalement compris ça.
DPM
6
Salut Jubbat, oui, ouvre jquery.js et jette un œil à la première ligne. Vous verrez qu'une fonction est ouverte. Maintenant, passez à la fin, vous verrez window.jQuery = window. $ = JQuery. Ensuite, la fonction est fermée et exécutée automatiquement. Vous avez maintenant accès à la fonction $, qui à son tour a accès aux autres fonctions définies dans la fermeture. Est-ce que ça répond à votre question?
superluminaire
4
Meilleure explication sur le web. Beaucoup plus simple que je ne le pensais
Mantis
95

Je vais donner un exemple (en JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

Ce que fait cette fonction, makeCounter, c'est qu'elle retourne une fonction, que nous avons appelée x, qui comptera de un à chaque fois qu'elle sera appelée. Puisque nous ne fournissons aucun paramètre à x, il doit en quelque sorte se souvenir du nombre. Il sait où le trouver en fonction de ce qu'on appelle la portée lexicale - il doit chercher l'endroit où il est défini pour trouver la valeur. Cette valeur «cachée» est ce qu'on appelle une fermeture.

Voici à nouveau mon exemple de curry:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);

add3(4); returns 7

Ce que vous pouvez voir, c'est que lorsque vous appelez add avec le paramètre a (qui est 3), cette valeur est contenue dans la fermeture de la fonction retournée que nous définissons comme étant add3. De cette façon, lorsque nous appelons add3, il sait où trouver la valeur a pour effectuer l'addition.

Kyle Cronin
la source
4
IDK, quelle langue (probablement F #) vous avez utilisée dans la langue ci-dessus. Pourriez-vous donner l'exemple ci-dessus en pseudocode? J'ai du mal à comprendre cela.
utilisateur
1
@crucifiedsoul It's Scheme. ftp.cs.indiana.edu/pub/scheme-repository/doc/pubs/intro.txt
Kyle Cronin
3
@KyleCronin Excellent exemple, merci. Q: Est-il plus correct de dire "la valeur cachée est appelée une fermeture", ou est-ce que "la fonction qui cache la valeur est la fermeture"? Ou "le processus de dissimulation de la valeur est la fermeture"? Merci!
2
@RobertHume Bonne question. Sémantiquement, le terme «fermeture» est quelque peu ambigu. Ma définition personnelle est que la combinaison de la valeur cachée et de son utilisation par la fonction englobante constitue la fermeture.
Kyle Cronin
1
@KyleCronin Merci - J'ai un programme à mi-parcours lundi. :) Je voulais avoir le concept de «fermeture» solide dans ma tête. Merci d'avoir posté cette excellente réponse à la question d'OP!
58

La réponse de Kyle est plutôt bonne. Je pense que la seule précision supplémentaire est que la fermeture est essentiellement un instantané de la pile au moment où la fonction lambda est créée. Ensuite, lorsque la fonction est réexécutée, la pile est restaurée à cet état avant d'exécuter la fonction. Ainsi, comme le mentionne Kyle, cette valeur cachée ( count) est disponible lorsque la fonction lambda s'exécute.

Ben Childs
la source
14
Ce n'est pas seulement la pile - ce sont les étendues lexicales englobantes qui sont préservées, qu'elles soient stockées sur la pile ou sur le tas (ou les deux).
Matt Fenwick
38

Tout d'abord, contrairement à ce que la plupart des gens ici vous disent, la fermeture n'est pas une fonction ! Alors qu'est - est - il?
Il s'agit d'un ensemble de symboles définis dans le "contexte environnant" d'une fonction (connu sous le nom d' environnement ) qui en font une expression CLOSED (c'est-à-dire une expression dans laquelle chaque symbole est défini et a une valeur, de sorte qu'il peut être évalué).

Par exemple, lorsque vous avez une fonction JavaScript:

function closed(x) {
  return x + 3;
}

c'est une expression fermée car tous les symboles qui s'y trouvent y sont définis (leur signification est claire), vous pouvez donc l'évaluer. En d'autres termes, il est autonome .

Mais si vous avez une fonction comme celle-ci:

function open(x) {
  return x*y + 3;
}

c'est une expression ouverte parce qu'il contient des symboles qui n'y sont pas définis. A savoir, y. En regardant cette fonction, nous ne pouvons pas dire ce qu'elle yest et ce qu'elle signifie, nous ne connaissons pas sa valeur, nous ne pouvons donc pas évaluer cette expression. C'est-à-dire que nous ne pouvons pas appeler cette fonction jusqu'à ce que nous disions ce qu'elle yest censée signifier. C'est ce yqu'on appelle une variable libre .

Cela ydemande une définition, mais cette définition ne fait pas partie de la fonction - elle est définie ailleurs, dans son "contexte environnant" (également connu sous le nom d' environnement ). C'est du moins ce que nous espérons: P

Par exemple, il pourrait être défini globalement:

var y = 7;

function open(x) {
  return x*y + 3;
}

Ou il pourrait être défini dans une fonction qui l'enveloppe:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

La partie de l'environnement qui donne aux variables libres dans une expression leur signification est la fermeture . Il est appelé ainsi, car il transforme une expression ouverte en expression fermée , en fournissant ces définitions manquantes pour toutes ses variables libres , afin que nous puissions l'évaluer.

Dans l'exemple ci-dessus, la fonction interne (à laquelle nous n'avons pas donné de nom parce que nous n'en avions pas besoin) est une expression ouverte car la variable yqu'elle contient est libre - sa définition est en dehors de la fonction, dans la fonction qui l'enveloppe . L' environnement de cette fonction anonyme est l'ensemble des variables:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Maintenant, la fermeture est la partie de cet environnement qui ferme la fonction interne en fournissant les définitions de toutes ses variables libres . Dans notre cas, la seule variable libre dans la fonction interne était y, donc la fermeture de cette fonction est ce sous-ensemble de son environnement:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Les deux autres symboles définis dans l'environnement ne font pas partie de la fermeture de cette fonction, car elle ne nécessite pas leur exécution. Ils ne sont pas nécessaires pour le fermer .

Plus d'informations sur la théorie derrière cela ici: https://stackoverflow.com/a/36878651/434562

Il convient de noter que dans l'exemple ci-dessus, la fonction wrapper renvoie sa fonction interne sous forme de valeur. Le moment où nous appelons cette fonction peut être éloigné dans le temps à partir du moment où la fonction a été définie (ou créée). En particulier, sa fonction d'habillage n'est plus en cours d'exécution, et ses paramètres qui étaient sur la pile d'appels ne sont plus là: P Cela pose problème, car la fonction interne doit yêtre là quand elle est appelée! En d'autres termes, il nécessite les variables de sa fermeture pour survivre en quelque sorte à la fonction wrapper et être là en cas de besoin. Par conséquent, la fonction interne doit faire un instantané de ces variables qui rendent sa fermeture et les stocker dans un endroit sûr pour une utilisation ultérieure. (Quelque part en dehors de la pile d'appels.)

Et c'est pourquoi les gens confondent souvent le terme fermeture comme étant ce type spécial de fonction qui peut faire de tels instantanés des variables externes qu'ils utilisent, ou la structure de données utilisée pour stocker ces variables pour plus tard. Mais j'espère que vous comprenez maintenant qu'il ne s'agit pas de la fermeture elle-même - ce sont juste des moyens d' implémenter des fermetures dans un langage de programmation, ou des mécanismes de langage qui permettent aux variables de la fermeture de la fonction d'être là en cas de besoin. Il y a beaucoup d'idées fausses autour des fermetures qui rendent (inutilement) ce sujet beaucoup plus confus et compliqué qu'il ne l'est réellement.

SasQ
la source
1
Une analogie qui pourrait aider les débutants à cela est une fermeture qui lie toutes les extrémités libres , ce que fait une personne lorsqu'elle cherche à fermer (ou elle résout toutes les références nécessaires, ou ...). Eh bien, cela m'a aidé à y penser de cette façon: o)
Will Crawford
J'ai lu beaucoup de définitions de la fermeture au fil des ans, mais je pense que celle-ci est ma préférée jusqu'à présent. Je suppose que nous avons tous notre propre façon de cartographier mentalement des concepts comme celui-ci et celui-ci correspond beaucoup au mien.
Jason S.
29

Une fermeture est une fonction qui peut référencer un état dans une autre fonction. Par exemple, en Python, cela utilise la fermeture "intérieure":

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1
John Millikin
la source
23

Pour faciliter la compréhension des clôtures, il pourrait être utile d'examiner comment elles pourraient être mises en œuvre dans un langage procédural. Cette explication suivra une implémentation simpliste des fermetures dans Scheme.

Pour commencer, je dois introduire le concept d'espace de noms. Lorsque vous entrez une commande dans un interpréteur Scheme, il doit évaluer les différents symboles dans l'expression et obtenir leur valeur. Exemple:

(define x 3)

(define y 4)

(+ x y) returns 7

Les expressions de définition stockent la valeur 3 à l'endroit pour x et la valeur 4 à l'endroit pour y. Ensuite, lorsque nous appelons (+ xy), l'interpréteur recherche les valeurs dans l'espace de noms et est capable d'effectuer l'opération et de retourner 7.

Cependant, dans Scheme, il existe des expressions qui vous permettent de remplacer temporairement la valeur d'un symbole. Voici un exemple:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

Le mot clé let introduit un nouvel espace de noms avec x comme valeur 5. Vous remarquerez qu'il peut toujours voir que y est 4, ce qui fait que la somme renvoyée est 9. Vous pouvez également voir qu'une fois l'expression terminée x revient à 3. Dans ce sens, x a été temporairement masqué par la valeur locale.

Les langages procéduraux et orientés objet ont un concept similaire. Chaque fois que vous déclarez une variable dans une fonction qui porte le même nom qu'une variable globale, vous obtenez le même effet.

Comment pourrions-nous mettre cela en œuvre? Une manière simple est avec une liste chaînée - la tête contient la nouvelle valeur et la queue contient l'ancien espace de noms. Lorsque vous devez rechercher un symbole, vous commencez par la tête et descendez la queue.

Passons maintenant à l'implémentation de fonctions de première classe pour le moment. Plus ou moins, une fonction est un ensemble d'instructions à exécuter lorsque la fonction est appelée aboutissant à la valeur de retour. Lorsque nous lisons dans une fonction, nous pouvons stocker ces instructions en arrière-plan et les exécuter lorsque la fonction est appelée.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

Nous définissons x pour être 3 et plus-x pour son paramètre, y, plus la valeur de x. Enfin, nous appelons plus-x dans un environnement où x a été masqué par un nouveau x, celui-ci valant 5. Si nous stockons simplement l'opération, (+ xy), pour la fonction plus-x, puisque nous sommes dans le contexte de x étant 5, le résultat renvoyé serait 9. C'est ce qu'on appelle la portée dynamique.

Cependant, Scheme, Common Lisp et de nombreux autres langages ont ce qu'on appelle la portée lexicale - en plus de stocker l'opération (+ xy), nous stockons également l'espace de noms à ce point particulier. De cette façon, lorsque nous recherchons les valeurs, nous pouvons voir que x, dans ce contexte, est vraiment 3. Il s'agit d'une fermeture.

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

En résumé, nous pouvons utiliser une liste chaînée pour stocker l'état de l'espace de noms au moment de la définition de la fonction, ce qui nous permet d'accéder aux variables à partir des étendues englobantes, ainsi que de nous fournir la possibilité de masquer localement une variable sans affecter le reste de la programme.

Kyle Cronin
la source
D'accord, grâce à votre réponse, je pense que j'ai enfin une idée de la fermeture. Mais il y a une grande question: "nous pouvons utiliser une liste chaînée pour stocker l'état de l'espace de noms au moment de la définition de la fonction, ce qui nous permet d'accéder à des variables qui autrement ne seraient plus dans la portée". Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Lazer
@Laser: Désolé, cette phrase n'avait pas beaucoup de sens, alors je l'ai mise à jour. J'espère que cela a plus de sens maintenant. Aussi, ne pensez pas à la liste chaînée comme un détail d'implémentation (car elle est très inefficace) mais comme un moyen simple de conceptualiser comment cela pourrait être fait.
Kyle Cronin
10

Voici un exemple concret de la raison pour laquelle les fermetures donnent un coup de pied au cul ... Cela vient tout droit de mon code Javascript. Permettez-moi d'illustrer.

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

Et voici comment vous l'utiliseriez:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

Imaginez maintenant que vous souhaitiez que la lecture démarre différée, comme par exemple 5 secondes plus tard après l'exécution de cet extrait de code. delayEt bien c'est facile avec et c'est la fermeture:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

Lorsque vous appelez delayavec 5000ms, le premier extrait s'exécute et stocke les arguments passés dans sa fermeture. Puis, 5 secondes plus tard, lorsque le setTimeoutrappel se produit, la fermeture conserve toujours ces variables, de sorte qu'elle peut appeler la fonction d'origine avec les paramètres d'origine.
Il s'agit d'un type de curry ou d'une décoration fonctionnelle.

Sans fermetures, vous devriez en quelque sorte maintenir l'état de ces variables en dehors de la fonction, jetant ainsi le code en dehors de la fonction avec quelque chose qui appartient logiquement à l'intérieur. L'utilisation de fermetures peut améliorer considérablement la qualité et la lisibilité de votre code.

adamJLev
la source
1
Il convient de noter que l'extension du langage ou des objets hôtes est généralement considérée comme une mauvaise chose car ils font partie de l'espace de noms global
Jon Cooke
9

Les fonctions ne contenant aucune variable libre sont appelées fonctions pures.

Les fonctions contenant une ou plusieurs variables libres sont appelées fermetures.

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

soundyogi
la source
Pourquoi est-ce minusé? Il est en réalité beaucoup plus "sur la bonne voie" avec cette distinction entre variables libres et variables liées, et fonctions pures / fermées et fonctions impures / ouvertes, que la plupart des autres réponses désemparées ici: P (actualisation pour les fermetures déroutantes avec des fonctions en cours de fermeture).
SasQ
Je n'ai vraiment aucune idée. C'est pourquoi StackOverflow craint. Il suffit de regarder la source de ma réponse. Qui pourrait contester cela?
soundyogi
Donc ça ne craint pas et je n'ai jamais entendu parler du terme "variable libre"
Kai
Il est difficile de parler de fermetures sans mentionner les variables libres. Cherchez-les. Terminologie CS standard.
ComDubh
"Les fonctions contenant une ou plusieurs variables libres sont appelées fermetures" n'est cependant pas une définition correcte - les fermetures sont toujours des objets de première classe.
ComDubh
7

tl; dr

Une fermeture est une fonction et sa portée affectée (ou utilisée comme) une variable. Ainsi, la fermeture du nom: la portée et la fonction sont enfermées et utilisées comme n'importe quelle autre entité.

Explication détaillée du style Wikipedia

Selon Wikipedia, une fermeture est:

Techniques pour implémenter la liaison de noms à portée lexicale dans les langues avec des fonctions de première classe.

Qu'est-ce que ça veut dire? Regardons quelques définitions.

Je vais expliquer les fermetures et autres définitions associées en utilisant cet exemple:

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

Fonctions de première classe

Fondamentalement, cela signifie que nous pouvons utiliser des fonctions comme n'importe quelle autre entité . Nous pouvons les modifier, les passer comme arguments, les renvoyer des fonctions ou les affecter à des variables. Techniquement parlant, ce sont des citoyens de première classe , d'où le nom: fonctions de première classe.

Dans l'exemple ci-dessus, startAtrenvoie une fonction ( anonyme ) à laquelle la fonction est affectée à closure1et closure2. Ainsi, comme vous le voyez, JavaScript traite les fonctions comme toutes les autres entités (citoyens de première classe).

Liaison de nom

La liaison de nom consiste à savoir quelles données une variable (identifiant) référence . La portée est vraiment importante ici, car c'est la chose qui déterminera comment une liaison est résolue.

Dans l'exemple ci-dessus:

  • Dans la portée de la fonction anonyme interne, yest lié à 3.
  • Dans startAtla portée de, xest lié à 1ou 5(selon la fermeture).

À l'intérieur de la portée de la fonction anonyme, xn'est liée à aucune valeur, elle doit donc être résolue dans une startAtportée supérieure ( 's).

Cadrage lexical

Comme le dit Wikipedia , la portée:

Région d'un programme informatique où la liaison est valide: où le nom peut être utilisé pour faire référence à l'entité .

Il existe deux techniques:

  • Portée lexicale (statique): la définition d'une variable est résolue en recherchant son bloc ou sa fonction conteneur, puis en cas d'échec de la recherche du bloc conteneur externe, et ainsi de suite.
  • Portée dynamique: la fonction appelante est recherchée, puis la fonction qui a appelé cette fonction appelante, et ainsi de suite, en remontant la pile d'appels.

Pour plus d'explications, consultez cette question et jetez un œil à Wikipedia .

Dans l'exemple ci-dessus, nous pouvons voir que JavaScript a une portée lexicale, car lorsqu'il xest résolu, la liaison est recherchée dans la startAtportée supérieure ( s), en fonction du code source (la fonction anonyme qui recherche x est définie à l'intérieur startAt) et pas basé sur la pile d'appels, la façon (la portée où) la fonction a été appelée.

Emballage (fermeture) vers le haut

Dans notre exemple, lorsque nous appelons startAt, il retournera une fonction (de première classe) qui sera affectée closure1et closure2donc une fermeture est créée, car les variables passées 1et 5seront enregistrées dans startAtla portée de ', qui seront jointes avec le retour fonction anonyme. Lorsque nous appelons cette fonction anonyme via closure1et closure2avec le même argument ( 3), la valeur de ysera trouvée immédiatement (car c'est le paramètre de cette fonction), mais xn'est pas liée dans la portée de la fonction anonyme, donc la résolution continue dans la portée de fonction supérieure (lexicalement) (qui a été enregistrée dans la fermeture) où xse trouve être lié à 1ou5. Maintenant, nous savons tout pour la sommation afin que le résultat puisse être retourné, puis imprimé.

Vous devez maintenant comprendre les fermetures et leur comportement, ce qui est un élément fondamental de JavaScript.

Currying

Oh, et vous avez également appris ce qu'est le curry : vous utilisez des fonctions (fermetures) pour passer chaque argument d'une opération au lieu d'utiliser une fonction avec plusieurs paramètres.

totymedli
la source
5

La fermeture est une fonctionnalité en JavaScript où une fonction a accès à ses propres variables de portée, accès aux variables de fonction externes et accès aux variables globales.

La fermeture a accès à sa portée de fonction externe même après le retour de la fonction externe. Cela signifie qu'une fermeture peut mémoriser et accéder aux variables et arguments de sa fonction externe même après la fin de la fonction.

La fonction interne peut accéder aux variables définies dans sa propre portée, la portée de la fonction extérieure et la portée globale. Et la fonction externe peut accéder à la variable définie dans sa propre portée et la portée globale.

Exemple de fermeture :

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

Le résultat sera 20, somme de sa propre fonction interne, de sa fonction externe et de sa valeur de variable globale.

rahul sharma
la source
4

Dans une situation normale, les variables sont liées par une règle de portée: les variables locales ne fonctionnent que dans la fonction définie. La fermeture est un moyen d'enfreindre temporairement cette règle pour plus de commodité.

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

dans le code ci-dessus, lambda(|n| a_thing * n}est la fermeture car elle a_thingest référencée par le lambda (un créateur de fonction anonyme).

Maintenant, si vous mettez la fonction anonyme résultante dans une variable de fonction.

foo = n_times(4)

foo enfreindra la règle de cadrage normale et commencera à utiliser 4 en interne.

foo.call(3)

renvoie 12.

Eugene Yokota
la source
2

En bref, le pointeur de fonction n'est qu'un pointeur vers un emplacement dans la base de code de programme (comme le compteur de programme). Tandis que Closure = Function pointer + Stack frame .

.

RoboAlex
la source
1

• Une fermeture est un sous-programme et l'environnement de référence où il a été défini

- L'environnement de référencement est nécessaire si le sous-programme peut être appelé à partir de n'importe quel endroit arbitraire du programme

- Un langage à portée statique qui n'autorise pas les sous-programmes imbriqués n'a pas besoin de fermetures

- Les fermetures ne sont nécessaires que si un sous-programme peut accéder aux variables dans les étendues d'imbrication et il peut être appelé de n'importe où

- Pour prendre en charge les fermetures, une implémentation peut devoir fournir une étendue illimitée à certaines variables (car un sous-programme peut accéder à une variable non locale qui n'est normalement plus active)

Exemple

function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);
BoraKurucu
la source
0

Voici un autre exemple réel, et en utilisant un langage de script populaire dans les jeux - Lua. J'ai dû changer légèrement la façon dont une fonction de bibliothèque fonctionnait pour éviter un problème avec stdin non disponible.

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

La valeur de old_dofile disparaît lorsque ce bloc de code termine sa portée (car il est local), mais la valeur a été incluse dans une fermeture, donc la nouvelle fonction de fichier redéfini PEUT y accéder, ou plutôt une copie stockée avec la fonction en tant que «upvalue».

Nigel Atkinson
la source
0

De Lua.org :

Lorsqu'une fonction est écrite enfermée dans une autre fonction, elle a un accès complet aux variables locales à partir de la fonction englobante; cette fonction est appelée portée lexicale. Bien que cela puisse sembler évident, ce n'est pas le cas. La portée lexicale, plus les fonctions de première classe, est un concept puissant dans un langage de programmation, mais peu de langages prennent en charge ce concept.

user5731811
la source
0

Si vous êtes du monde Java, vous pouvez comparer une fermeture avec une fonction membre d'une classe. Regardez cet exemple

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

La fonction gest une fermeture: se gferme a. Elle gpeut donc être comparée à une fonction membre, apeut être comparée à un champ de classe et la fonction fà une classe.

ericj
la source
0

Fermetures Chaque fois que nous avons une fonction définie à l'intérieur d'une autre fonction, la fonction interne a accès aux variables déclarées dans la fonction externe. Les fermetures sont mieux expliquées avec des exemples. Dans l'extrait 2-18, vous pouvez voir que la fonction interne a accès à une variable (variableInOuterFunction) à partir de la portée externe. Les variables de la fonction externe ont été fermées par (ou liées dans) la fonction interne. D'où le terme de fermeture. Le concept en lui-même est assez simple et assez intuitif.

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

source: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf

shohan
la source
0

Veuillez regarder ci-dessous le code pour comprendre la fermeture de manière plus approfondie:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

Voici ce qui sera produit? 0,1,2,3,4ce ne sera pas à 5,5,5,5,5cause de la fermeture

Alors, comment ça va résoudre? La réponse est ci-dessous:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

Permettez-moi d'expliquer simplement, quand une fonction créée rien ne se passe jusqu'à ce qu'elle appelle ainsi pour la boucle dans le 1er code appelé 5 fois mais pas appelé immédiatement alors quand elle est appelée, c'est-à-dire après 1 seconde et aussi c'est asynchrone donc avant que pour la boucle terminée et stocke la valeur 5 dans var i et enfin exécuter la setTimeoutfonction cinq fois et imprimer5,5,5,5,5

Voici comment cela se résout en utilisant IIFE, c'est-à-dire l'expression de la fonction d'appel immédiat

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

Pour en savoir plus, veuillez comprendre le contexte d'exécution pour comprendre la clôture.

  • Il existe une autre solution pour résoudre ce problème en utilisant let (fonctionnalité ES6), mais sous le capot, la fonction ci-dessus fonctionne

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 0,1,2,3,4
    

=> Plus d'explications:

En mémoire, quand pour boucle exécuter l'image, faites comme ci-dessous:

Boucle 1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Boucle 2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Boucle 3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Boucle 4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

Boucle 5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

Ici, i n'est pas exécuté, puis après la boucle complète, var i a stocké la valeur 5 en mémoire, mais sa portée est toujours visible dans sa fonction enfants, donc quand la fonction s'exécute à l' setTimeoutenvers cinq fois, elle s'imprime5,5,5,5,5

donc pour résoudre cette utilisation IIFE comme expliqué ci-dessus.

arun
la source
Merci pour votre réponse. il serait plus lisible si vous sépariez le code de l'explication. (ne pas mettre en retrait les lignes qui ne sont pas du code)
eMBee
0

Currying: Il vous permet d'évaluer partiellement une fonction en passant uniquement un sous-ensemble de ses arguments. Considère ceci:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

Clôture: Une fermeture n'est rien d'autre que l'accès à une variable en dehors de la portée d'une fonction. Il est important de se rappeler qu'une fonction à l'intérieur d'une fonction ou une fonction imbriquée n'est pas une fermeture. Les fermetures sont toujours utilisées lorsque vous avez besoin d'accéder aux variables en dehors de la portée de la fonction.

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21
user11335201
la source
0

La fermeture est très simple. On peut le considérer comme suit: Fermeture = fonction + son environnement lexical

Considérez la fonction suivante:

function init() {
    var name = “Mozilla”;
}

Quelle sera la fermeture dans le cas ci-dessus? Fonction init () et variables dans son environnement lexical ie nom. Fermeture = init () + nom

Considérons une autre fonction:

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

Quelles seront les fermetures ici? La fonction interne peut accéder aux variables de la fonction externe. displayName () peut accéder au nom de variable déclaré dans la fonction parent, init (). Cependant, les mêmes variables locales dans displayName () seront utilisées si elles existent.

Fermeture 1: fonction init + (variable name + fonction displayName ()) -> portée lexicale

Fermeture 2: fonction displayName + (variable de nom) -> portée lexicale

Rumel
la source
0

Les fermetures fournissent JavaScript à l'état.

État dans la programmation signifie simplement se souvenir des choses.

Exemple

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

Dans le cas ci-dessus, l'état est stocké dans la variable "a". Nous suivons en ajoutant 1 à "a" plusieurs fois. Nous ne pouvons le faire que parce que nous pouvons «nous souvenir» de la valeur. Le détenteur de l'état, "a", conserve cette valeur en mémoire.

Souvent, dans les langages de programmation, vous voulez garder une trace des choses, vous souvenir des informations et y accéder ultérieurement.

Ceci, dans d'autres langues , est généralement accompli grâce à l'utilisation de classes. Une classe, tout comme les variables, garde une trace de son état. Et les instances de cette classe, à leur tour, ont également un état en leur sein. État signifie simplement des informations que vous pouvez stocker et récupérer ultérieurement.

Exemple

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

Comment pouvons-nous accéder au "poids" depuis la méthode "render"? Eh bien, grâce à l'Etat. Chaque instance de la classe Bread peut rendre son propre poids en la lisant à partir de "l'état", un endroit en mémoire où nous pourrions stocker cette information.

Maintenant, JavaScript est un langage très unique qui n'a historiquement pas de classes (il en a maintenant, mais sous le capot, il n'y a que des fonctions et des variables), donc les fermetures permettent à JavaScript de se souvenir des choses et d'y accéder plus tard.

Exemple

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

L'exemple ci-dessus a atteint l'objectif de "maintien de l'état" avec une variable. C'est bien! Cependant, cela présente l'inconvénient que la variable (le détenteur de "l'état") est maintenant exposée. On peut faire mieux. Nous pouvons utiliser des fermetures.

Exemple

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

C'est fantastique.

Maintenant, notre fonction "count" peut compter. Il ne peut le faire que parce qu'il peut "tenir" l'état. L'état dans ce cas est la variable "n". Cette variable est maintenant fermée. Fermé dans le temps et l'espace. À temps parce que vous ne pourrez jamais le récupérer, le changer, lui attribuer une valeur ou interagir directement avec lui. Dans l'espace car il est géographiquement imbriqué dans la fonction "countGenerator".

Pourquoi est-ce fantastique? Parce que sans impliquer aucun autre outil sophistiqué et compliqué (par exemple des classes, des méthodes, des instances, etc.), nous sommes capables de 1. cacher 2. le contrôle à distance

On cache l'état, la variable "n", ce qui en fait une variable privée! Nous avons également créé une API qui peut contrôler cette variable d'une manière prédéfinie. En particulier, nous pouvons appeler l'API comme "count ()" et cela ajoute 1 à "n" à partir d'une "distance". En aucun cas, la forme ou la forme de quiconque ne pourra jamais accéder à "n" sauf via l'API.

JavaScript est vraiment incroyable dans sa simplicité.

Les fermetures expliquent en grande partie pourquoi.

Claudio
la source
0

Un exemple simple dans Groovy pour votre référence:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
GraceMeng
la source