Comment fonctionnent les fermetures JavaScript?

7636

Comment expliqueriez-vous les fermetures JavaScript à une personne connaissant les concepts qui les composent (par exemple, les fonctions, les variables, etc.), mais ne comprenant pas les fermetures elles-mêmes?

J'ai vu l'exemple de Scheme donné sur Wikipédia, mais malheureusement, cela n'a pas aidé.

Zaheer Ahmed
la source
391
Mon problème avec ces réponses et de nombreuses réponses est qu'elles les abordent dans une perspective abstraite et théorique, plutôt que de commencer par expliquer simplement pourquoi les fermetures sont nécessaires en Javascript et les situations pratiques dans lesquelles vous les utilisez. Vous vous retrouvez avec un article tl; dr que vous devez parcourir, tout en pensant, "mais, pourquoi?". Je commencerais simplement par: les fermetures sont une façon intéressante de gérer les deux réalités suivantes de JavaScript: a. la portée est au niveau de la fonction, pas au niveau du bloc et, b. une grande partie de ce que vous faites dans la pratique en JavaScript est asynchrone / pilotée par les événements.
Jeremy Burton
53
@Redsandro D'une part, il rend le code événementiel beaucoup plus facile à écrire. Je peux déclencher une fonction lorsque la page se charge pour déterminer les détails du HTML ou des fonctionnalités disponibles. Je peux définir et définir un gestionnaire dans cette fonction et avoir toutes ces informations de contexte disponibles à chaque appel du gestionnaire sans avoir à le réinterroger. Résolvez le problème une fois, réutilisez-le sur chaque page où ce gestionnaire est nécessaire avec une surcharge réduite lors de la réinvocation du gestionnaire. Vous avez déjà vu les mêmes données être remappées deux fois dans une langue qui ne les a pas? Les fermetures permettent d'éviter beaucoup plus facilement ce genre de choses.
Erik Reppen
1
@Erik Reppen merci pour la réponse. En fait, j'étais curieux de savoir les avantages de ce closurecode difficile à lire , par opposition à celui Object Literalqui se réutilise et réduit tout de même les frais généraux, tout en nécessitant 100% moins de code d'emballage.
Redsandro
6
Pour les programmeurs Java, la réponse courte est qu'il s'agit de l'équivalent fonction d'une classe interne. Une classe interne contient également un pointeur implicite vers une instance de la classe externe et est utilisée dans le même but (c'est-à-dire la création de gestionnaires d'événements).
Boris van Schooten
8
J'ai trouvé cet exemple pratique très utile: youtube.com/watch?v=w1s9PgtEoJs
Abhi

Réponses:

7360

Une fermeture est un couple de:

  1. Une fonction, et
  2. Une référence à la portée externe de cette fonction (environnement lexical)

Un environnement lexical fait partie de chaque contexte d'exécution (cadre de pile) et est une correspondance entre les identificateurs (c'est-à-dire les noms de variables locaux) et les valeurs.

Chaque fonction en JavaScript conserve une référence à son environnement lexical externe. Cette référence est utilisée pour configurer le contexte d'exécution créé lors de l'appel d'une fonction. Cette référence permet au code à l'intérieur de la fonction de "voir" les variables déclarées à l'extérieur de la fonction, indépendamment du moment et de l'endroit où la fonction est appelée.

Si une fonction a été appelée par une fonction, qui à son tour a été appelée par une autre fonction, une chaîne de références aux environnements lexicaux externes est créée. Cette chaîne est appelée chaîne de portée.

Dans le code suivant, innerforme une fermeture avec l'environnement lexical du contexte d'exécution créé quand fooest invoqué, se fermant sur la variable secret:

function foo() {
  const secret = Math.trunc(Math.random()*100)
  return function inner() {
    console.log(`The secret number is ${secret}.`)
  }
}
const f = foo() // `secret` is not directly accessible from outside `foo`
f() // The only way to retrieve `secret`, is to invoke `f`

En d'autres termes: en JavaScript, les fonctions portent une référence à une "boîte d'état" privée, à laquelle seules elles (et toutes les autres fonctions déclarées dans le même environnement lexical) ont accès. Cette boîte d'état est invisible pour l'appelant de la fonction, offrant un excellent mécanisme de masquage et d'encapsulation des données.

Et rappelez-vous: les fonctions en JavaScript peuvent être transmises comme des variables (fonctions de première classe), ce qui signifie que ces paires de fonctionnalités et d'état peuvent être transmises autour de votre programme: similaire à la façon dont vous pourriez passer une instance d'une classe en C ++.

Si JavaScript n'avait pas de fermetures, alors plus d'état devrait être passé explicitement entre les fonctions , rendant les listes de paramètres plus longues et le code plus bruyant.

Donc, si vous voulez qu'une fonction ait toujours accès à un état privé, vous pouvez utiliser une fermeture.

... et souvent nous ne voulons l' état associé à une fonction. Par exemple, en Java ou C ++, lorsque vous ajoutez une variable d'instance privée et une méthode à une classe, vous associez l'état à la fonctionnalité.

En C et dans la plupart des autres langages courants, après le retour d'une fonction, toutes les variables locales ne sont plus accessibles car le stack-frame est détruit. En JavaScript, si vous déclarez une fonction dans une autre fonction, les variables locales de la fonction externe peuvent rester accessibles après son retour. De cette façon, dans le code ci-dessus, secretreste disponible pour l'objet fonction inner, après son retour foo.

Utilisations des fermetures

Les fermetures sont utiles lorsque vous avez besoin d'un état privé associé à une fonction. Il s'agit d'un scénario très courant - et rappelez-vous: JavaScript n'avait pas de syntaxe de classe jusqu'en 2015, et il n'a toujours pas de syntaxe de champ privé. Les fermetures répondent à ce besoin.

Variables d'instance privée

Dans le code suivant, la fonction se toStringferme sur les détails de la voiture.

function Car(manufacturer, model, year, color) {
  return {
    toString() {
      return `${manufacturer} ${model} (${year}, ${color})`
    }
  }
}
const car = new Car('Aston Martin','V8 Vantage','2012','Quantum Silver')
console.log(car.toString())

Programmation fonctionnelle

Dans le code suivant, la fonction se innerferme sur les deux fnet args.

function curry(fn) {
  const args = []
  return function inner(arg) {
    if(args.length === fn.length) return fn(...args)
    args.push(arg)
    return inner
  }
}

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

const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5

Programmation orientée événement

Dans le code suivant, la fonction se onClickferme sur la variable BACKGROUND_COLOR.

const $ = document.querySelector.bind(document)
const BACKGROUND_COLOR = 'rgba(200,200,242,1)'

function onClick() {
  $('body').style.background = BACKGROUND_COLOR
}

$('button').addEventListener('click', onClick)
<button>Set background color</button>

Modularisation

Dans l'exemple suivant, tous les détails d'implémentation sont masqués dans une expression de fonction immédiatement exécutée. Les fonctions ticket toStringfermer sur l'état privé et les fonctions dont ils ont besoin pour terminer leur travail. Les fermetures nous ont permis de modulariser et d'encapsuler notre code.

let namespace = {};

(function foo(n) {
  let numbers = []
  function format(n) {
    return Math.trunc(n)
  }
  function tick() {
    numbers.push(Math.random() * 100)
  }
  function toString() {
    return numbers.map(format)
  }
  n.counter = {
    tick,
    toString
  }
}(namespace))

const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())

Exemples

Exemple 1

Cet exemple montre que les variables locales ne sont pas copiées dans la fermeture: la fermeture conserve une référence aux variables d'origine elles-mêmes . C'est comme si le cadre de pile restait vivant en mémoire même après la sortie de la fonction externe.

function foo() {
  let x = 42
  let inner  = function() { console.log(x) }
  x = x+1
  return inner
}
var f = foo()
f() // logs 43

Exemple 2

Dans le code suivant, trois méthodes log, incrementet updatetoutes se ferment sur le même environnement lexical.

Et à chaque createObjectappel, un nouveau contexte d'exécution (cadre de pile) est créé et une toute nouvelle variable x, et un nouvel ensemble de fonctions ( logetc.) sont créés, qui se ferment sur cette nouvelle variable.

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

Exemple 3

Si vous utilisez des variables déclarées en utilisant var, veillez à bien comprendre quelle variable vous fermez. Les variables déclarées à l'aide varsont hissées. C'est beaucoup moins un problème dans le JavaScript moderne en raison de l'introduction de letet const.

Dans le code suivant, chaque fois dans la boucle, une nouvelle fonction innerest créée, qui se referme i. Mais parce qu'elle var iest hissée hors de la boucle, toutes ces fonctions internes se ferment sur la même variable, ce qui signifie que la valeur finale de i(3) est imprimée, trois fois.

function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }
  return result
}

const result = foo()
// The following will print `3`, three times...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

Points finaux:

  • Chaque fois qu'une fonction est déclarée en JavaScript, une fermeture est créée.
  • Le renvoi d'un functionde l'intérieur d'une autre fonction est l'exemple classique d'une fermeture, car l'état à l'intérieur de la fonction externe est implicitement disponible pour la fonction interne renvoyée, même après l'exécution de la fonction externe.
  • Chaque fois que vous utilisez à l' eval()intérieur d'une fonction, une fermeture est utilisée. Le texte que vous evalpouvez référencer les variables locales de la fonction, et en mode non strict, vous pouvez même créer de nouvelles variables locales en utilisant eval('var foo = …').
  • Lorsque vous utilisez new Function(…)(le constructeur Function ) à l'intérieur d'une fonction, il ne se ferme pas sur son environnement lexical: il se ferme sur le contexte global à la place. La nouvelle fonction ne peut pas référencer les variables locales de la fonction externe.
  • Une fermeture en JavaScript revient à conserver une référence ( PAS une copie) à la portée au point de déclaration de la fonction, qui à son tour conserve une référence à sa portée extérieure, et ainsi de suite, jusqu'à l'objet global en haut de la chaîne de portée.
  • Une fermeture est créée lorsqu'une fonction est déclarée; cette fermeture est utilisée pour configurer le contexte d'exécution lorsque la fonction est invoquée.
  • Un nouvel ensemble de variables locales est créé chaque fois qu'une fonction est appelée.

Liens

Ben Aston
la source
74
Cela semble bien: "Une fermeture en JavaScript, c'est comme garder une copie de toutes les variables locales, tout comme elles l'étaient lorsqu'une fonction se fermait." Mais c'est trompeur pour deux raisons. (1) L'appel de fonction n'a pas besoin de quitter pour créer une fermeture. (2) Il ne s'agit pas d'une copie des valeurs des variables locales mais des variables elles-mêmes. (3) Il ne dit pas qui a accès à ces variables.
dlaliberte
27
L'exemple 5 montre un "gotcha" où le code ne fonctionne pas comme prévu. Mais cela ne montre pas comment y remédier. Cette autre réponse montre un moyen de le faire.
Matt
190
J'aime la façon dont cet article commence par de grosses lettres en gras indiquant "Les fermetures ne sont pas magiques" et termine son premier exemple par "La magie est qu'en JavaScript, une référence de fonction a également une référence secrète à la fermeture dans laquelle elle a été créée".
Andrew Macheret du
6
L'exemple n ° 3 mélange les fermetures avec le levage de javascripts. Maintenant, je pense qu'expliquer uniquement les fermetures est assez difficile sans introduire le comportement de levage. Cela m'a le plus aidé: Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.de developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
caramba
3
ECMAScript 6 peut changer quelque chose dans ce grand article sur la fermeture. Par exemple, si vous utilisez let i = 0au lieu de var i = 0dans l'exemple 5, alors le testList()imprimera ce que vous voulez à l'origine.
Nier
3989

Chaque fonction en JavaScript maintient un lien vers son environnement lexical externe. Un environnement lexical est une carte de tous les noms (par exemple, variables, paramètres) dans une étendue, avec leurs valeurs.

Ainsi, chaque fois que vous voyez le functionmot - clé, le code à l'intérieur de cette fonction a accès aux variables déclarées à l'extérieur de la fonction.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Cela se connectera 16car la fonction se barferme sur le paramètre xet la variable tmp, qui existent tous les deux dans l'environnement lexical de la fonction externe foo.

La fonction bar, avec son lien avec l'environnement lexical de la fonction, fooest une fermeture.

Une fonction n'a pas besoin de revenir pour créer une fermeture. Simplement en vertu de sa déclaration, chaque fonction se referme sur son environnement lexical englobant, formant une fermeture.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2);
bar(10); // 16
bar(10); // 17

La fonction ci-dessus enregistrera également 16, car le code à l'intérieur barpeut toujours faire référence à un argument xet à une variable tmp, même s'ils ne sont plus directement dans la portée.

Cependant, comme il tmpest toujours suspendu à l'intérieur de barla fermeture, il est disponible pour être incrémenté. Il sera incrémenté à chaque appel bar.

L'exemple le plus simple de fermeture est le suivant:

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Lorsqu'une fonction JavaScript est invoquée, un nouveau contexte d'exécution ecest créé. Avec les arguments de fonction et l'objet cible, ce contexte d'exécution reçoit également un lien vers l'environnement lexical du contexte d'exécution appelant, ce qui signifie que les variables déclarées dans l'environnement lexical externe (dans l'exemple ci-dessus, les deux aet b) sont disponibles à partir de ec.

Chaque fonction crée une fermeture parce que chaque fonction a un lien avec son environnement lexical externe.

Notez que les variables elles - mêmes sont visibles de l'intérieur d'une fermeture, pas des copies.

Ali
la source
24
@feeela: Oui, chaque fonction JS crée une fermeture. Les variables qui ne sont pas référencées seront probablement rendues éligibles pour le garbage collection dans les moteurs JS modernes, mais cela ne change pas le fait que lorsque vous créez un contexte d'exécution, ce contexte a une référence au contexte d'exécution englobant et à ses variables, et cette fonction est un objet qui a le potentiel d'être déplacé vers une portée variable différente, tout en conservant cette référence d'origine. Voilà la fermeture.
@Ali Je viens de découvrir que le jsFiddle que j'ai fourni ne prouve rien, car il deleteéchoue. Néanmoins, l'environnement lexical que la fonction transportera en tant que [[Scope]] (et utilisera finalement comme base pour son propre environnement lexical lorsqu'elle sera invoquée) est déterminé lorsque l'instruction qui définit la fonction est exécutée. Cela signifie que la fonction se ferme sur le contenu ENTIER de la portée en cours d'exécution, quelles que soient les valeurs auxquelles elle se réfère réellement et si elle échappe à la portée. Veuillez consulter les sections 13.2 et 10 du
cahier des charges
8
C'était une bonne réponse jusqu'à ce qu'il essaie d'expliquer les types et références primitifs. Il se trompe complètement et parle de copie de littéraux, ce qui n'a vraiment rien à voir avec quoi que ce soit.
Ry-
12
Les fermetures sont la réponse de JavaScript à la programmation orientée objet basée sur les classes. JS n'est pas basé sur une classe, il fallait donc trouver un autre moyen d'implémenter certaines choses qui ne pourraient pas être implémentées autrement.
Bartłomiej Zalewski
2
ce devrait être la réponse acceptée. La magie ne se produit jamais dans la fonction intérieure. Cela se produit lorsque vous affectez la fonction externe à une variable. Cela crée un nouveau contexte d'exécution pour la fonction interne, de sorte que la "variable privée" peut être accumulée. Bien sûr, il peut le faire puisque la variable à laquelle la fonction externe assignée a maintenu le contexte. La première réponse ne fait que rendre le tout plus complexe sans expliquer ce qui s'y passe réellement.
Albert Gao
2442

AVANT-PROPOS: cette réponse a été écrite lorsque la question était:

Comme le disait le vieil Albert: «Si vous ne pouvez pas l'expliquer à un enfant de six ans, vous ne le comprenez vraiment pas vous-même.» Eh bien, j'ai essayé d'expliquer les fermetures JS à un ami de 27 ans et j'ai complètement échoué.

Quelqu'un peut-il considérer que j'ai 6 ans et que je m'intéresse étrangement à ce sujet?

Je suis presque sûr que j'étais l'une des seules personnes à avoir tenté de prendre la question initiale au pied de la lettre. Depuis lors, la question a muté plusieurs fois, donc ma réponse peut maintenant sembler incroyablement idiote et hors de propos. Espérons que l'idée générale de l'histoire reste amusante pour certains.


Je suis un grand fan d'analogie et de métaphore lorsque j'explique des concepts difficiles, alors laissez-moi tenter ma chance avec une histoire.

Il était une fois:

Il y avait une princesse ...

function princess() {

Elle vivait dans un monde merveilleux plein d'aventures. Elle a rencontré son prince charmant, parcouru son monde sur une licorne, combattu des dragons, rencontré des animaux parlants et bien d'autres choses fantastiques.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Mais elle devrait toujours retourner dans son monde terne de corvées et d'adultes.

    return {

Et elle leur racontait souvent sa dernière aventure incroyable en tant que princesse.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Mais tout ce qu'ils verraient, c'est une petite fille ...

var littleGirl = princess();

... raconter des histoires sur la magie et la fantaisie.

littleGirl.story();

Et même si les adultes connaissaient de vraies princesses, ils ne croiraient jamais aux licornes ou aux dragons parce qu'ils ne pourraient jamais les voir. Les adultes ont dit qu'ils n'existaient que dans l'imagination de la petite fille.

Mais nous connaissons la vraie vérité; que la petite fille avec la princesse à l'intérieur ...

... est vraiment une princesse avec une petite fille à l'intérieur.

Jacob Swartwood
la source
340
J'aime vraiment cette explication. Pour ceux qui le lisent et ne le suivent pas, l'analogie est la suivante: la fonction princess () est une portée complexe contenant des données privées. En dehors de la fonction, les données privées ne peuvent être ni vues ni accessibles. La princesse garde les licornes, les dragons, les aventures, etc. dans son imagination (données privées) et les adultes ne peuvent pas les voir par eux-mêmes. MAIS l'imagination de la princesse est capturée dans la fermeture de la story()fonction, qui est la seule interface que l' littleGirlinstance expose dans le monde de la magie.
Patrick M
Voici storydonc la fermeture, mais si le code avait été var story = function() {}; return story;alors, ce littleGirlserait la fermeture. C'est du moins l'impression que j'ai de l'utilisation par MDN des méthodes «privées» avec fermetures : «Ces trois fonctions publiques sont des fermetures qui partagent le même environnement.
icc97
16
@ icc97, oui, storyest une fermeture faisant référence à l'environnement fourni dans le cadre de princess. princessest également une autre fermeture implicite , c'est-à-dire que le princesset le littleGirlpartageraient toute référence à un parentstableau qui existerait dans l'environnement / la portée où littleGirlexiste et princessest défini.
Jacob Swartwood du
6
@BenjaminKrupp J'ai ajouté un commentaire de code explicite pour montrer / impliquer qu'il y a plus d'opérations dans le corps de princessce qui est écrit. Malheureusement, cette histoire est maintenant un peu hors de propos sur ce fil. À l'origine, la question demandait «d'expliquer les fermetures JavaScript à un ancien de 5 ans»; ma réponse a été la seule à avoir tenté de le faire. Je ne doute pas que cela aurait échoué lamentablement, mais au moins cette réponse aurait pu avoir la chance de retenir l'intérêt d'un enfant de 5 ans.
Jacob Swartwood
11
En fait, pour moi, cela était parfaitement logique. Et je dois admettre que finalement comprendre une fermeture JS en utilisant des histoires de princesses et d'aventures me fait me sentir un peu bizarre.
Cristalliser
753

Prenant la question au sérieux, nous devrions découvrir ce qu'un enfant typique de 6 ans est capable de faire sur le plan cognitif, mais il est vrai que celui qui s'intéresse à JavaScript n'est pas si typique.

Sur le développement de l'enfant: 5 à 7 ans, il dit:

Votre enfant pourra suivre des instructions en deux étapes. Par exemple, si vous dites à votre enfant: «Va à la cuisine et apporte-moi un sac poubelle», il pourra se souvenir de cette direction.

Nous pouvons utiliser cet exemple pour expliquer les fermetures, comme suit:

La cuisine est une fermeture qui a une variable locale, appelée trashBags. Il y a une fonction à l'intérieur de la cuisine appelée getTrashBagqui récupère un sac poubelle et le renvoie.

Nous pouvons coder cela en JavaScript comme ceci:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

Autres points qui expliquent pourquoi les fermetures sont intéressantes:

  • Chaque fois que vous makeKitchen()appelez, une nouvelle fermeture est créée avec sa propre séparation trashBags.
  • La trashBagsvariable est locale à l'intérieur de chaque cuisine et n'est pas accessible à l'extérieur, mais la fonction intérieure de la getTrashBagpropriété y a accès.
  • Chaque appel de fonction crée une fermeture, mais il ne serait pas nécessaire de maintenir la fermeture à moins qu'une fonction intérieure, qui a accès à l'intérieur de la fermeture, puisse être appelée de l'extérieur de la fermeture. Le retour de l'objet avec la getTrashBagfonction fait cela ici.
dlaliberte
la source
6
En fait, prêter à confusion, la fonction makeKitchen appel est la fermeture réelle, et non l'objet de cuisine qu'il retourne.
dlaliberte
6
Après avoir parcouru les autres, j'ai trouvé cette réponse comme le moyen le plus simple d'expliquer quoi et pourquoi les fermetures.
Chetabahana
3
Trop de menu et d'amuse-gueule, pas assez de viande et de pommes de terre. Vous pouvez améliorer cette réponse avec une seule phrase courte comme: "Une fermeture est le contexte scellé d'une fonction, faute de mécanisme de cadrage fourni par les classes."
Staplerfahrer
584

L'homme de paille

J'ai besoin de savoir combien de fois un bouton a été cliqué et de faire quelque chose à chaque troisième clic ...

Solution assez évidente

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Maintenant, cela fonctionnera, mais cela empiète sur la portée externe en ajoutant une variable, dont le seul but est de garder une trace du nombre. Dans certaines situations, cela serait préférable car votre application externe pourrait avoir besoin d'accéder à ces informations. Mais dans ce cas, nous ne modifions que le comportement de chaque troisième clic, il est donc préférable de placer cette fonctionnalité dans le gestionnaire d'événements .

Considérez cette option

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Remarquez quelques choses ici.

Dans l'exemple ci-dessus, j'utilise le comportement de fermeture de JavaScript. Ce comportement permet à n'importe quelle fonction d'avoir accès à l'étendue dans laquelle elle a été créée, indéfiniment. Pour l'appliquer pratiquement, j'appelle immédiatement une fonction qui renvoie une autre fonction, et parce que la fonction que je retourne a accès à la variable de comptage interne (en raison du comportement de fermeture expliqué ci-dessus), cela entraîne une étendue privée pour l'utilisation par le résultat fonction ... Pas si simple? Allons le diluer ...

Une fermeture simple sur une ligne

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

Toutes les variables en dehors de la fonction retournée sont disponibles pour la fonction retournée, mais elles ne sont pas directement disponibles pour l'objet fonction retourné ...

func();  // Alerts "val"
func.a;  // Undefined

Tu piges? Ainsi, dans notre exemple principal, la variable count est contenue dans la fermeture et toujours disponible pour le gestionnaire d'événements, de sorte qu'il conserve son état d'un clic à l'autre.

En outre, cet état de variable privée est entièrement accessible, tant pour les lectures que pour l'affectation à ses variables de portée privées.

Voilà; vous encapsulez maintenant complètement ce comportement.

Article de blog complet (y compris les considérations jQuery)

jondavidjohn
la source
11
Je ne suis pas d'accord avec votre définition de ce qu'est une fermeture. Il n'y a aucune raison pour qu'il soit auto-invocable. Il est également un peu simpliste (et inexact) de dire qu'il doit être "renvoyé" (beaucoup de discussions à ce sujet dans les commentaires de la première réponse à cette question)
James Montagne
40
@James même si vous n'êtes pas d'accord, son exemple (et son article entier) est l'un des meilleurs que j'ai vus. Bien que la question ne soit pas ancienne et résolue pour moi, elle mérite totalement un +1.
e-satis
84
"J'ai besoin de savoir combien de fois un bouton a été cliqué, et de faire quelque chose à chaque troisième clic ..." CECI a attiré mon attention. Un cas d'utilisation et la solution montrant comment une fermeture n'est pas si mystérieuse et que beaucoup d'entre nous les ont écrits mais ne connaissaient pas exactement le nom officiel.
Chris22
Bel exemple car il montre que "count" dans le 2ème exemple conserve la valeur de "count" et n'est pas remis à 0 à chaque fois que "élément" est cliqué. Très instructif!
Adam
+1 pour le comportement de fermeture . Peut-on limiter le comportement de fermeture aux fonctions en javascript ou ce concept peut également être appliqué à d'autres structures du langage?
Dziamid
493

Les fermetures sont difficiles à expliquer car elles sont utilisées pour faire fonctionner certains comportements que tout le monde s'attend intuitivement à travailler de toute façon. Je trouve que la meilleure façon de les expliquer (et la façon dont j'ai appris ce qu'ils font) est d'imaginer la situation sans eux:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

Que se passerait-il ici si JavaScript ne connaissait pas les fermetures? Remplacez simplement l'appel dans la dernière ligne par son corps de méthode (qui est essentiellement ce que font les appels de fonction) et vous obtenez:

console.log(x + 3);

Maintenant, où est la définition de x? Nous ne l'avons pas défini dans le champ d'application actuel. La seule solution est de laisser plus5 porter sa portée (ou plutôt celle de son parent). De cette façon, xest bien défini et il est lié à la valeur 5.

Konrad Rudolph
la source
11
C'est exactement le genre d'exemple qui induit de nombreuses personnes en erreur en leur faisant croire que ce sont les valeurs qui sont utilisées dans la fonction renvoyée, et non la variable modifiable elle-même. Si elle était changée pour "retourner x + = y", ou mieux encore à la fois cela et une autre fonction "x * = y", alors il serait clair que rien n'est copié. Pour les personnes habituées à empiler des cadres, imaginez utiliser des cadres de tas à la place, qui peuvent continuer d'exister après le retour de la fonction.
Matt
14
@Matt je ne suis pas d'accord. Un exemple n'est pas censé documenter de manière exhaustive toutes les propriétés. Il se veut réducteur et illustre la caractéristique saillante d'un concept. Le PO a demandé une explication simple («pour un enfant de six ans»). Prenez la réponse acceptée: elle échoue totalement à fournir une explication concise, précisément parce qu'elle tente d'être exhaustive. (Je suis d'accord avec vous que c'est une propriété importante de JavaScript que la liaison est par référence plutôt que par valeur… mais encore une fois, une explication réussie est celle qui se réduit au strict minimum.)
Konrad Rudolph
@KonradRudolph J'aime le style et la brièveté de votre exemple. Je recommande simplement de le changer légèrement pour que la dernière partie, "La seule solution soit ...", devienne vraie. À l' heure actuelle , il est en fait une autre solution , plus simple à votre scénario, qui ne pas correspondre à continuations javascript, et ne correspond à une idée fausse de ce que sont les continuations. Ainsi, l'exemple sous sa forme actuelle est dangereux. Cela n'a pas à voir avec la liste exhaustive des propriétés, mais avec la compréhension de ce qu'est le x dans la fonction retournée, qui est après tout le point principal.
Matt
@Matt Hmm, je ne suis pas sûr de bien vous comprendre, mais je commence à voir que vous avez peut-être un argument valable. Puisque les commentaires sont trop courts, pourriez-vous peut-être expliquer ce que vous voulez dire dans un gist / pastie ou dans une salle de chat? Merci.
Konrad Rudolph
2
@KonradRudolph Je pense que je n'étais pas clair sur le but du x + = y. Le but était simplement de montrer que les appels répétés à la fonction retournée continuent à utiliser la même variable x (par opposition à la même valeur , que les gens imaginent être "insérée" lorsque la fonction est créée). C'est comme les deux premières alertes de votre violon. Le but d'une fonction supplémentaire x * = y serait de montrer que plusieurs fonctions retournées partagent toutes le même x.
Matt
379

TLDR

Une fermeture est un lien entre une fonction et son environnement lexical externe (c'est-à-dire tel qu'écrit), de sorte que les identificateurs (variables, paramètres, déclarations de fonction, etc.) définis dans cet environnement sont visibles de l'intérieur de la fonction, indépendamment du moment ou de où la fonction est invoquée.

Détails

Dans la terminologie de la spécification ECMAScript, une fermeture peut être dite implémentée par la [[Environment]]référence de chaque objet-fonction, ce qui pointe vers l' environnement lexical dans lequel la fonction est définie.

Lorsqu'une fonction est invoquée via la [[Call]]méthode interne , la [[Environment]]référence sur l'objet fonction est copiée dans la référence d'environnement externe de l' enregistrement d'environnement du contexte d'exécution nouvellement créé (cadre de pile).

Dans l'exemple suivant, la fonction se fferme sur l'environnement lexical du contexte d'exécution global:

function f() {}

Dans l'exemple suivant, la fonction se hferme sur l'environnement lexical de la fonction g, qui, à son tour, se ferme sur l'environnement lexical du contexte d'exécution global.

function g() {
    function h() {}
}

Si une fonction interne est renvoyée par un externe, l'environnement lexical externe persistera après le retour de la fonction externe. En effet, l'environnement lexical externe doit être disponible si la fonction interne est finalement invoquée.

Dans l'exemple suivant, la fonction se jferme sur l'environnement lexical de la fonction i, ce qui signifie que la variable xest visible de l'intérieur de la fonction j, longtemps après l' iexécution de la fonction:

function i() {
    var x = 'mochacchino'
    return function j() {
        console.log('Printing the value of x, from within function j: ', x)
    }
} 

const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms

Dans une fermeture, les variables de l'environnement lexical externe elles - mêmes sont disponibles, pas des copies.

function l() {
  var y = 'vanilla';

  return {
    setY: function(value) {
      y = value;
    },
    logY: function(value) {
      console.log('The value of y is: ', y);
    }
  }
}

const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate

La chaîne des environnements lexicaux, liée entre les contextes d'exécution via des références d'environnement externes, forme une chaîne de portée et définit les identifiants visibles à partir d'une fonction donnée.

Veuillez noter que dans le but d'améliorer la clarté et la précision, cette réponse a été substantiellement modifiée par rapport à l'original.

Ben
la source
56
Wow, je n'ai jamais su que vous pouviez utiliser des substitutions de chaînes console.logcomme ça. Si quelqu'un d'autre est intéressé, il y en a plus: developer.mozilla.org/en-US/docs/DOM/…
Flash
7
Les variables qui se trouvent dans la liste des paramètres de la fonction font également partie de la fermeture (par exemple, non seulement limitée à var).
Thomas Eding
Les fermetures ressemblent plus à des objets et des classes, etc. Je ne sais pas pourquoi beaucoup de gens ne comparent pas ces deux - ce serait plus facile pour nous, novices, d'apprendre!
almaruf
376

OK, fan de fermetures de 6 ans. Voulez-vous entendre l'exemple le plus simple de fermeture?

Imaginons la situation suivante: un conducteur est assis dans une voiture. Cette voiture est à l'intérieur d'un avion. L'avion est à l'aéroport. La capacité du conducteur d'accéder à des choses à l'extérieur de sa voiture, mais à l'intérieur de l'avion, même si cet avion quitte un aéroport, est une fermeture. C'est ça. Lorsque vous atteignez 27 ans, regardez l' explication plus détaillée ou l'exemple ci-dessous.

Voici comment je peux convertir mon histoire d'avion en code.

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");

Max Tkachenko
la source
26
Bien joué et répond à l'affiche originale. Je pense que c'est la meilleure réponse. J'allais utiliser les bagages de la même manière: imaginez que vous alliez chez grand-mère et que vous emballiez votre étui nintendo DS avec des cartes de jeu à l'intérieur de votre étui, mais ensuite emballiez l'étui à l'intérieur de votre sac à dos et mettiez également des cartes de jeu dans les poches de votre sac à dos, et ALORS vous mettez le tout dans une grande valise avec plus de cartes de jeu dans les poches de la valise. Lorsque vous arrivez chez grand-mère, vous pouvez jouer à n'importe quel jeu sur votre DS tant que tous les boîtiers extérieurs sont ouverts. Ou quelque chose à cet effet.
slartibartfast
366

Il s'agit d'une tentative de dissiper plusieurs malentendus (possibles) sur les fermetures qui apparaissent dans certaines des autres réponses.

  • Une fermeture n'est pas uniquement créée lorsque vous retournez une fonction interne. En fait, la fonction englobante n'a pas du tout besoin de revenir pour que sa fermeture soit créée. Vous pouvez plutôt affecter votre fonction interne à une variable dans une étendue externe, ou la passer comme argument à une autre fonction où elle pourrait être appelée immédiatement ou à tout moment plus tard. Par conséquent, la fermeture de la fonction de fermeture est probablement créée dès que la fonction de fermeture est appelée, car toute fonction interne a accès à cette fermeture à chaque fois que la fonction de fermeture est appelée, avant ou après le retour de la fonction de fermeture.
  • Une fermeture ne fait pas référence à une copie des anciennes valeurs de variables dans sa portée. Les variables elles-mêmes font partie de la fermeture, et donc la valeur vue lors de l'accès à l'une de ces variables est la dernière valeur au moment de l'accès. C'est pourquoi les fonctions internes créées à l'intérieur des boucles peuvent être délicates, car chacune a accès aux mêmes variables externes plutôt que de récupérer une copie des variables au moment où la fonction est créée ou appelée.
  • Les "variables" dans une fermeture incluent toutes les fonctions nommées déclarées dans la fonction. Ils incluent également des arguments de la fonction. Une fermeture a également accès à ses variables de fermeture contenant, jusqu'à la portée globale.
  • Les fermetures utilisent de la mémoire, mais elles ne provoquent pas de fuites de mémoire car JavaScript nettoie lui-même ses propres structures circulaires qui ne sont pas référencées. Des fuites de mémoire Internet Explorer impliquant des fermetures sont créées lorsqu'il ne parvient pas à déconnecter les valeurs d'attribut DOM qui font référence à des fermetures, conservant ainsi les références à des structures éventuellement circulaires.
dlaliberte
la source
15
James, j'ai dit que la fermeture est "probablement" créée au moment de l'appel de la fonction englobante car il est plausible qu'une implémentation puisse différer la création d'une fermeture jusqu'à un certain temps plus tard, lorsqu'elle décide qu'une fermeture est absolument nécessaire. S'il n'y a pas de fonction interne définie dans la fonction englobante, aucune fermeture ne sera nécessaire. Ainsi, il pourrait peut-être attendre que la première fonction interne soit créée pour ensuite créer une fermeture à partir du contexte d'appel de la fonction englobante.
dlaliberte
9
@ Betterave-Betterave Supposons que nous ayons une fonction interne qui est passée à une autre fonction où elle est utilisée avant le retour de la fonction externe, et supposons que nous renvoyions également la même fonction interne à partir de la fonction externe. Il s'agit à l'identique de la même fonction dans les deux cas, mais vous dites qu'avant le retour de la fonction externe, la fonction interne est "liée" à la pile des appels, tandis qu'après son retour, la fonction interne est soudainement liée à une fermeture. Il se comporte de façon identique dans les deux cas; la sémantique est identique, alors ne parlez-vous pas seulement des détails d'implémentation?
dlaliberte
7
@ Beetroot-Beetroot, merci pour vos commentaires, et je suis heureux de vous avoir fait réfléchir. Je ne vois toujours pas de différence sémantique entre le contexte en direct de la fonction externe et ce même contexte quand il devient une fermeture au retour de la fonction (si je comprends votre définition). La fonction intérieure s'en fiche. Le garbage collection ne se soucie pas car la fonction interne conserve une référence au contexte / fermeture dans tous les cas, et l'appelant de la fonction externe laisse simplement tomber sa référence au contexte d'appel. Mais c'est déroutant pour les gens, et peut-être mieux de simplement l'appeler un contexte d'appel.
dlaliberte
9
Cet article est difficile à lire, mais je pense qu'il soutient en fait ce que je dis. Il dit: "Une fermeture est formée en renvoyant un objet fonction [...] ou en affectant directement une référence à un tel objet fonction, par exemple, à une variable globale." Je ne veux pas dire que GC n'est pas pertinent. Au lieu de cela, en raison de GC et parce que la fonction interne est attachée au contexte d'appel de la fonction externe (ou [[portée]] comme le dit l'article), alors peu importe si l'appel de fonction externe retourne parce que cette liaison avec le interne la fonction est la chose importante.
dlaliberte
3
Très bonne réponse! Une chose que vous devez ajouter est que toutes les fonctions se ferment sur tout le contenu de la portée d'exécution dans laquelle elles sont définies. Peu importe qu'ils fassent référence à certaines ou à aucune des variables de la portée parent: une référence à l'environnement lexical de la portée parent est stockée sous la forme [[Scope]] sans condition. Cela peut être vu dans la section sur la création de fonctions dans la spécification ECMA.
Asad Saeeduddin
236

J'ai écrit un article de blog il y a quelque temps pour expliquer les fermetures. Voici ce que j'ai dit à propos des fermetures pour savoir pourquoi vous en voudriez une.

Les fermetures sont un moyen de laisser une fonction avoir des variables privées persistantes - c'est-à-dire des variables dont une seule fonction est au courant, où elle peut garder la trace des informations des temps précédents où elle a été exécutée.

En ce sens, ils laissent une fonction agir un peu comme un objet avec des attributs privés.

Article complet:

Alors, quels sont ces trucs de fermeture?

Nathan Long
la source
Ainsi, le principal avantage des fermetures pourrait-il être souligné avec cet exemple? Disons que j'ai une fonction emailError (sendToAddress, errorString) Je pourrais alors dire devError = emailError("[email protected]", errorString)et avoir ma propre version personnalisée d'une fonction emailError partagée?
Devin G Rhode
Cette explication et l'exemple parfait associé dans le lien vers (thingys de fermeture) est la meilleure façon de comprendre les fermetures et devrait être tout en haut!
HopeKing
215

Les fermetures sont simples:

L'exemple simple suivant couvre tous les points principaux des fermetures JavaScript. *  

Voici une usine qui produit des calculatrices qui peuvent s'ajouter et se multiplier:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

Le point clé: chaque appel à make_calculatorcrée une nouvelle variable locale n, qui continue d'être utilisable par cette calculatrice addet multiplyfonctionne longtemps après les make_calculatorretours.

Si vous connaissez les cadres de pile, ces calculatrices semblent étranges: comment peuvent-elles continuer à accéder naprès les make_calculatorretours? La réponse est d'imaginer que JavaScript n'utilise pas de "cadres de pile", mais utilise à la place des "cadres de tas", qui peuvent persister après l'appel de la fonction qui les a renvoyés.

Les fonctions internes comme addet multiply, qui accèdent aux variables déclarées dans une fonction externe ** , sont appelées fermetures .

C'est à peu près tout ce qu'il y a à fermer.



* Par exemple, il couvre tous les points de l'article "Fermetures pour les nuls" donné dans une autre réponse , à l'exception de l'exemple 6, qui montre simplement que les variables peuvent être utilisées avant d'être déclarées, un fait agréable à savoir mais sans aucun rapport avec les fermetures. Il couvre également tous les points de la réponse acceptée , à l'exception des points (1) que les fonctions copient leurs arguments dans des variables locales (les arguments de la fonction nommée) et (2) que la copie des nombres crée un nouveau nombre, mais la copie d'une référence d'objet vous donne une autre référence au même objet. Celles-ci sont également bonnes à savoir, mais encore une fois sans aucun rapport avec les fermetures. Il est également très similaire à l'exemple de cette réponse mais un peu plus court et moins abstrait. Il ne couvre pas le point decette réponse ou ce commentaire , qui est que JavaScript rend difficile de brancher le courantvaleur d'une variable de boucle dans votre fonction interne: l'étape de "connexion" ne peut être effectuée qu'avec une fonction d'assistance qui entoure votre fonction interne et est invoquée à chaque itération de boucle. (Strictement parlant, la fonction interne accède à la copie de la fonction d'aide de la variable, plutôt que d'avoir quoi que ce soit branché.) Encore une fois, très utile lors de la création de fermetures, mais ne fait pas partie de ce qu'est une fermeture ou de son fonctionnement. Il y a une confusion supplémentaire due aux fermetures fonctionnant différemment dans des langages fonctionnels comme ML, où les variables sont liées à des valeurs plutôt qu'à de l'espace de stockage, fournissant un flux constant de personnes qui comprennent les fermetures d'une manière (à savoir la manière de "se brancher") qui est tout simplement incorrect pour JavaScript, où les variables sont toujours liées à l'espace de stockage et jamais aux valeurs.

** Toute fonction externe, si plusieurs sont imbriquées, ou même dans le contexte global, comme cette réponse le montre clairement.

Matt
la source
Que se passerait-il si vous appeliez: second_calculator = first_calculator (); au lieu de second_calculator = make_calculator (); ? Ça devrait être pareil, non?
Ronen Festinger
4
@Ronen: Puisque first_calculatorc'est un objet (pas une fonction), vous ne devez pas utiliser de parenthèses second_calculator = first_calculator;, car c'est une affectation, pas un appel de fonction. Pour répondre à votre question, il n'y aurait alors qu'un seul appel à make_calculator, donc une seule calculatrice serait effectuée, et les variables first_calculator et second_calculator feraient toutes deux référence à la même calculatrice, donc les réponses seraient 3, 403, 4433, 44330.
Matt
204

Comment je l'expliquerais à un enfant de six ans:

Vous savez comment les adultes peuvent posséder une maison, et ils l'appellent chez eux? Quand une maman a un enfant, l'enfant ne possède vraiment rien, non? Mais ses parents possèdent une maison, donc chaque fois que quelqu'un demande à l'enfant "Où est ta maison?", Il / elle peut répondre "cette maison!" Et pointer la maison de ses parents. Une «fermeture» est la capacité de l'enfant de toujours (même à l'étranger) être en mesure de dire qu'il a un foyer, même si ce sont vraiment les parents qui sont propriétaires de la maison.

Magne
la source
200

Pouvez-vous expliquer les fermetures à un enfant de 5 ans? *

Je pense toujours que l'explication de Google fonctionne très bien et est concise:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

Preuve que cet exemple crée une fermeture même si la fonction interne ne revient pas

* Question AC #

Chris S
la source
11
Le code est "correct", comme exemple de fermeture, même s'il ne traite pas la partie du commentaire sur l'utilisation de la fermeture après le retour de la fonction externalFunction. Ce n'est donc pas un bon exemple. Il existe de nombreuses autres façons d'utiliser une fermeture qui n'impliquent pas le retour de la fonction interne. Par exemple, innerFunction pourrait être passée à une autre fonction où elle est appelée immédiatement ou stockée et appelée quelque temps plus tard, et dans tous les cas, elle a accès au contexte externalFunction qui a été créé lors de son appel.
dlaliberte
6
@syockit Non, Moss a tort. Une fermeture est créée indépendamment du fait que la fonction ait jamais échappé à la portée dans laquelle elle est définie, et une référence créée inconditionnellement à l'environnement lexical du parent rend toutes les variables de la portée parent disponibles pour toutes les fonctions, qu'elles soient invoquées à l'extérieur ou à l'intérieur la portée dans laquelle ils ont été créés.
Asad Saeeduddin
176

J'ai tendance à mieux apprendre en comparant BON / MAUVAIS. J'aime voir du code de travail suivi d'un code non fonctionnel que quelqu'un est susceptible de rencontrer. J'ai mis en place un jsFiddle qui fait une comparaison et essaie de résumer les différences avec les explications les plus simples que j'ai pu trouver.

Fermetures bien faites:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • Dans le code ci-dessus createClosure(n)est invoqué à chaque itération de la boucle. Notez que j'ai nommé la variable npour souligner qu'il s'agit d'une nouvelle variable créée dans une nouvelle étendue de fonction et n'est pas la même variable que celle indexqui est liée à l'étendue externe.

  • Cela crée une nouvelle étendue et nest lié à cette étendue; cela signifie que nous avons 10 étendues distinctes, une pour chaque itération.

  • createClosure(n) renvoie une fonction qui renvoie le n dans cette étendue.

  • Dans chaque portée nest liée à la valeur qu'elle avait lorsqu'elle a createClosure(n)été invoquée, de sorte que la fonction imbriquée qui est renvoyée renvoie toujours la valeur de ncelle qu'elle avait lors de createClosure(n)son appel.

Fermetures mal faites:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • Dans le code ci-dessus, la boucle a été déplacée à l'intérieur de la createClosureArray()fonction et la fonction ne renvoie maintenant que le tableau terminé, ce qui à première vue semble plus intuitif.

  • Ce qui pourrait ne pas être évident, c'est que puisque createClosureArray()n'est invoqué qu'une seule fois, une seule étendue est créée pour cette fonction au lieu d'une pour chaque itération de la boucle.

  • Dans cette fonction, une variable nommée indexest définie. La boucle s'exécute et ajoute des fonctions au tableau qui retournent index. Notez que cela indexest défini dans la createClosureArrayfonction qui n'est invoquée qu'une seule fois.

  • Parce qu'il n'y avait qu'une seule étendue dans la createClosureArray()fonction, indexn'est lié qu'à une valeur dans cette étendue. En d'autres termes, chaque fois que la boucle change la valeur de index, elle la change pour tout ce qui la référence dans cette étendue.

  • Toutes les fonctions ajoutées au tableau renvoient la indexvariable SAME de l'étendue parent où elle a été définie au lieu de 10 différentes de 10 étendues différentes comme le premier exemple. Le résultat final est que les 10 fonctions renvoient la même variable de la même portée.

  • Une fois la boucle terminée et la indexmodification terminée, la valeur finale était 10, donc chaque fonction ajoutée au tableau renvoie la valeur de la indexvariable unique qui est maintenant définie sur 10.

Résultat

FERMETURES FAITES À DROITE
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

FERMETURES EFFECTUÉES FAUX
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10

Chev
la source
1
Belle addition, merci. Pour être plus clair, on peut imaginer comment le "mauvais" tableau est créé dans la "mauvaise" boucle à chaque itération: 1ère itération: [function () {return 'n =' + 0;}] 2ème itération: [(( function () {return 'n =' + 1;}), (function () {return 'n =' + 1;})] 3e itération: [(function () {return 'n =' + 2;}) , (function () {return 'n =' + 2;}), (function () {return 'n =' + 2;})] etc. Ainsi, chaque fois que la valeur d'index change, elle est reflétée dans toutes les fonctions déjà ajouté au tableau.
Alex Alexeev
3
L'utilisation de letfor varcorrige la différence.
Rupam Datta
N'est-ce pas ici que «la fermeture est bien faite» est un exemple de «fermeture à l'intérieur de la fermeture»?
TechnicalSmile
Je veux dire, chaque fonction est techniquement une fermeture, mais l'important est que la fonction définit une nouvelle variable à l'intérieur. La fonction qui obtient renvoie uniquement les références ncréées dans une nouvelle fermeture. Nous retournons simplement une fonction afin de pouvoir la stocker dans le tableau et l'invoquer plus tard.
Chev
Si vous voulez simplement stocker le résultat dans le tableau dans la première itération , vous pouvez ensuite inline comme ceci: arr[index] = (function (n) { return 'n = ' + n; })(index);. Mais alors vous stockez la chaîne résultante dans le tableau plutôt qu'une fonction à invoquer, ce qui va à l'encontre du point de mon exemple.
Chev
164

Wikipédia sur les fermetures :

En informatique, une fermeture est une fonction associée à un environnement de référence pour les noms non locaux (variables libres) de cette fonction.

Techniquement, en JavaScript , chaque fonction est une fermeture . Il a toujours accès aux variables définies dans le périmètre environnant.

Étant donné que la construction définissant la portée en JavaScript est une fonction , et non un bloc de code comme dans de nombreux autres langages, ce que nous entendons généralement par fermeture en JavaScript est une fonction fonctionnant avec des variables non locales définies dans une fonction environnante déjà exécutée .

Les fermetures sont souvent utilisées pour créer des fonctions avec des données privées cachées (mais ce n'est pas toujours le cas).

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

L'exemple ci-dessus utilise une fonction anonyme, qui a été exécutée une fois. Mais cela ne doit pas l'être. Il peut être nommé (par exemple mkdb) et exécuté plus tard, générant une fonction de base de données chaque fois qu'il est invoqué. Chaque fonction générée aura son propre objet de base de données caché. Un autre exemple d'utilisation des fermetures est lorsque nous ne renvoyons pas une fonction, mais un objet contenant plusieurs fonctions à des fins différentes, chacune de ces fonctions ayant accès aux mêmes données.

mykhal
la source
2
Il s'agit de la meilleure explication des fermetures JavaScript. Doit être la réponse choisie. Les autres sont assez divertissants, mais celui-ci est en fait utile de manière pratique pour les codeurs JavaScript du monde réel.
geoidesic
136

J'ai mis en place un tutoriel JavaScript interactif pour expliquer le fonctionnement des fermetures. Qu'est-ce qu'une fermeture?

Voici l'un des exemples:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here
Nathan Whitehead
la source
128

Les enfants se souviendront toujours des secrets qu'ils ont partagés avec leurs parents, même après le départ de leurs parents. C'est ce que sont les fermetures pour les fonctions.

Les secrets des fonctions JavaScript sont les variables privées

var parent = function() {
 var name = "Mary"; // secret
}

Chaque fois que vous l'appelez, la variable locale "nom" est créée et prend le nom "Mary". Et chaque fois que la fonction se termine, la variable est perdue et le nom est oublié.

Comme vous pouvez le deviner, parce que les variables sont recréées à chaque appel de la fonction et que personne d'autre ne les connaîtra, il doit y avoir un endroit secret où elles sont stockées. Cela pourrait s'appeler Chamber of Secrets ou pile ou portée locale, mais cela n'a pas vraiment d'importance. Nous savons qu'ils sont là, quelque part, cachés dans la mémoire.

Mais, en JavaScript, il y a cette chose très spéciale que les fonctions qui sont créées à l'intérieur d'autres fonctions, peuvent également connaître les variables locales de leurs parents et les conserver aussi longtemps qu'elles vivent.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

Donc, tant que nous sommes dans la fonction parent, il peut créer une ou plusieurs fonctions enfants qui partagent les variables secrètes de l'endroit secret.

Mais ce qui est triste, c'est que si l'enfant est aussi une variable privée de sa fonction parentale, il mourrait également à la fin du parent et les secrets mourraient avec eux.

Donc pour vivre, l'enfant doit partir avant qu'il ne soit trop tard

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

Et maintenant, même si Mary ne "court plus", sa mémoire n'est pas perdue et son enfant se souviendra toujours de son nom et des autres secrets qu'il a partagés pendant son temps ensemble.

Donc, si vous appelez l'enfant "Alice", elle répondra

child("Alice") => "My name is Alice, child of Mary"

C'est tout ce qu'il y a à dire.

Tero Tolonen
la source
15
C'est l'explication qui avait le plus de sens pour moi car elle ne suppose pas une connaissance préalable significative des termes techniques. L'explication la mieux votée ici suppose que la personne qui ne comprend pas les fermetures a une compréhension pleine et entière de termes comme «portée lexicale» et «contexte d'exécution» - même si je peux les comprendre conceptuellement, je ne pense pas que je suis aussi à l'aise avec les détails d'eux comme je devrais être, et l'explication sans aucun jargon est ce qui a finalement fait que les fermetures cliquent pour moi, merci. En prime, je pense que cela explique également quelle est la portée de manière très concise.
Emma W
103

Je ne comprends pas pourquoi les réponses sont si complexes ici.

Voici une fermeture:

var a = 42;

function b() { return a; }

Oui. Vous l'utilisez probablement plusieurs fois par jour.


Il n'y a aucune raison de croire que les fermetures sont un hack de conception complexe pour résoudre des problèmes spécifiques. Non, les fermetures consistent simplement à utiliser une variable provenant d'une portée plus élevée du point de vue de l'endroit où la fonction a été déclarée (non exécutée) .

Maintenant, ce que cela vous permet de faire peut être plus spectaculaire, voir d'autres réponses.

floribon
la source
5
Cette réponse ne semble pas susceptible d'aider à confondre les gens. Un équivalent approximatif dans un langage de programmation traditionnel pourrait être de créer b () comme méthode sur un objet qui a également une constante ou une propriété privée a. À mon avis, la surprise est que l'objet scope JS fournit effectivement aune propriété plutôt qu'une constante. Et vous ne remarquerez ce comportement important que si vous le modifiez, comme dansreturn a++;
Jon Coombs
1
Exactement ce que Jon a dit. Avant de fermer définitivement, j'ai eu du mal à trouver des exemples pratiques. Oui, floribon a créé une fermeture, mais pour moi sans instruction, cela ne m'aurait absolument rien appris.
Chev
3
Cela ne définit pas ce qu'est une fermeture - c'est simplement un exemple qui en utilise une. Et il ne traite pas de la nuance de ce qui se passe lorsque la portée se termine; Je ne pense pas que quiconque ait eu une question sur la portée lexicale alors que toutes les portées sont toujours là, et surtout dans le cas d'une variable globale.
Gerard ONeill
92

Exemple pour le premier point de dlaliberte:

Une fermeture n'est pas uniquement créée lorsque vous retournez une fonction interne. En fait, la fonction englobante n'a pas du tout besoin de revenir. Vous pouvez plutôt affecter votre fonction interne à une variable dans une étendue externe, ou la passer comme argument à une autre fonction où elle pourrait être utilisée immédiatement. Par conséquent, la fermeture de la fonction englobante existe probablement déjà au moment où la fonction englobante a été appelée car toute fonction interne y a accès dès qu'elle est appelée.

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
someisaac
la source
Petite clarification sur une éventuelle ambiguïté. Quand j'ai dit "En fait, la fonction englobante n'a pas du tout besoin de revenir." Je ne voulais pas dire "ne retourner aucune valeur" mais "toujours actif". Ainsi, l'exemple ne montre pas cet aspect, bien qu'il montre une autre façon de transmettre la fonction interne à la portée externe. Le point principal que j'essayais de faire concerne le moment de la création de la fermeture (pour la fonction englobante), car certaines personnes semblent penser que cela se produit lorsque la fonction englobante revient. Un autre exemple est nécessaire pour montrer que la fermeture est créée lors de l' appel d' une fonction .
dlaliberte
89

Une fermeture est l'endroit où une fonction interne a accès aux variables de sa fonction externe. C'est probablement l'explication d'une ligne la plus simple que vous pouvez obtenir pour les fermetures.

Rakesh Pai
la source
35
Ce n'est que la moitié de l'explication. La chose importante à noter à propos des fermetures est que si la fonction interne est toujours référencée après la sortie de la fonction externe, les anciennes valeurs de la fonction externe sont toujours disponibles pour la fonction interne.
pcorcoran
22
En fait, ce ne sont pas les anciennes valeurs de la fonction externe qui sont disponibles pour la fonction interne, mais les anciennes variables , qui pourraient avoir de nouvelles valeurs si une fonction pouvait les changer.
dlaliberte
86

Je sais qu'il existe déjà de nombreuses solutions, mais je suppose que ce petit et simple script peut être utile pour démontrer le concept:

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined
Gerardo Lima
la source
82

Vous dormez et vous invitez Dan. Vous dites à Dan d'apporter un contrôleur XBox.

Dan invite Paul. Dan demande à Paul d'apporter un contrôleur. Combien de contrôleurs ont été amenés à la fête?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");
StewShack
la source
80

L'auteur de Closures a assez bien expliqué les fermetures, expliquant la raison pour laquelle nous en avons besoin et expliquant également LexicalEnvironment qui est nécessaire pour comprendre les fermetures.
Voici le résumé:

Que faire si une variable est accédée, mais qu'elle n'est pas locale? Comme ici:

Entrez la description de l'image ici

Dans ce cas, l'interpréteur trouve la variable dans l' LexicalEnvironmentobjet externe .

Le processus comprend deux étapes:

  1. Premièrement, lorsqu'une fonction f est créée, elle n'est pas créée dans un espace vide. Il existe un objet LexicalEnvironment actuel. Dans le cas ci-dessus, c'est une fenêtre (a n'est pas défini au moment de la création de la fonction).

Entrez la description de l'image ici

Lorsqu'une fonction est créée, elle obtient une propriété masquée, nommée [[Scope]], qui fait référence au LexicalEnvironment actuel.

Entrez la description de l'image ici

Si une variable est lue, mais ne peut être trouvée nulle part, une erreur est générée.

Fonctions imbriquées

Les fonctions peuvent être imbriquées les unes dans les autres, formant une chaîne de LexicalEnvironments qui peut également être appelée une chaîne de portée.

Entrez la description de l'image ici

Ainsi, la fonction g a accès à g, a et f.

Fermetures

Une fonction imbriquée peut continuer à vivre une fois la fonction externe terminée:

Entrez la description de l'image ici

Marquage de LexicalEnvironments:

Entrez la description de l'image ici

Comme nous le voyons, this.sayest une propriété dans l'objet utilisateur, il continue donc à vivre après la fin de l'utilisateur.

Et si vous vous en souvenez, quand this.sayest créé, il (comme chaque fonction) obtient une référence interne this.say.[[Scope]]à l'environnement Lexical actuel. Ainsi, l'environnement Lexical de l'exécution actuelle de l'utilisateur reste en mémoire. Toutes les variables de l'utilisateur sont également ses propriétés, elles sont donc également soigneusement conservées, et non junk comme d'habitude.

L'objectif est de garantir que si la fonction interne souhaite accéder à une variable externe à l'avenir, elle est en mesure de le faire.

Résumer:

  1. La fonction interne conserve une référence à l'environnement Lexical externe.
  2. La fonction interne peut y accéder à tout moment, même si la fonction externe est terminée.
  3. Le navigateur conserve le LexicalEnvironment et toutes ses propriétés (variables) en mémoire jusqu'à ce qu'il y ait une fonction interne qui le référence.

C'est ce qu'on appelle une fermeture.

Arvand
la source
78

Les fonctions JavaScript peuvent accéder à leurs:

  1. Arguments
  2. Les sections locales (c'est-à-dire leurs variables locales et leurs fonctions locales)
  3. Environnement, qui comprend:
    • mondiaux, y compris les DOM
    • quoi que ce soit dans les fonctions extérieures

Si une fonction accède à son environnement, alors la fonction est une fermeture.

Notez que les fonctions externes ne sont pas requises, bien qu'elles offrent des avantages dont je ne parle pas ici. En accédant aux données dans son environnement, une fermeture maintient ces données en vie. Dans le sous-cas des fonctions externes / internes, une fonction externe peut créer des données locales et éventuellement quitter, et pourtant, si une ou plusieurs fonctions internes survivent après la sortie de la fonction externe, alors la ou les fonctions internes conservent les données locales de la fonction externe vivant.

Exemple de fermeture utilisant l'environnement global:

Imaginez que les événements des boutons Vote-Up et Vote-Down de Stack Overflow soient implémentés comme des fermetures, voteUp_click et voteDown_click, qui ont accès aux variables externes isVotedUp et isVotedDown, qui sont définies globalement. (Par souci de simplicité, je fais référence aux boutons de vote aux questions de StackOverflow, pas au tableau des boutons de vote aux réponses.)

Lorsque l'utilisateur clique sur le bouton VoteUp, la fonction voteUp_click vérifie si isVotedDown == true pour déterminer s'il faut voter pour ou simplement annuler un vote négatif. La fonction voteUp_click est une fermeture car elle accède à son environnement.

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

Ces quatre fonctions sont des fermetures car elles accèdent toutes à leur environnement.

John Pick
la source
59

En tant que père d'un enfant de 6 ans, qui enseigne actuellement aux jeunes enfants (et un novice relatif au codage sans éducation formelle, des corrections seront nécessaires), je pense que la leçon se prolongerait mieux par le jeu pratique. Si l'enfant de 6 ans est prêt à comprendre ce qu'est une fermeture, il est alors assez âgé pour tenter sa chance. Je suggère de coller le code sur jsfiddle.net, d'expliquer un peu et de les laisser seuls pour concocter une chanson unique. Le texte explicatif ci-dessous est probablement plus approprié pour un enfant de 10 ans.

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

INSTRUCTIONS

DONNÉES: Les données sont un ensemble de faits. Il peut s'agir de chiffres, de mots, de mesures, d'observations ou même simplement de descriptions de choses. Vous ne pouvez pas le toucher, le sentir ou le goûter. Vous pouvez l'écrire, le parler et l'entendre. Vous pouvez l'utiliser pour créer une odeur tactile et un goût à l'aide d'un ordinateur. Il peut être rendu utile par un ordinateur utilisant du code.

CODE: Toute l'écriture ci-dessus est appelée code . Il est écrit en JavaScript.

JAVASCRIPT: JavaScript est un langage. Comme l'anglais ou le français ou le chinois sont des langues. De nombreux langages sont compris par les ordinateurs et autres processeurs électroniques. Pour que JavaScript soit compris par un ordinateur, il a besoin d'un interprète. Imaginez si un enseignant qui ne parle que le russe vient enseigner votre classe à l'école. Quand le professeur dit "все садятся", la classe ne comprendrait pas. Mais heureusement, vous avez un élève russe dans votre classe qui dit à tout le monde que cela signifie "tout le monde s'assoit" - alors vous le faites tous. La classe est comme un ordinateur et l'élève russe est l'interprète. Pour JavaScript, l'interpréteur le plus courant est appelé navigateur.

NAVIGATEUR: Lorsque vous vous connectez à Internet sur un ordinateur, une tablette ou un téléphone pour visiter un site Web, vous utilisez un navigateur. Vous connaissez peut-être par exemple Internet Explorer, Chrome, Firefox et Safari. Le navigateur peut comprendre JavaScript et indiquer à l'ordinateur ce qu'il doit faire. Les instructions JavaScript sont appelées fonctions.

FONCTION: Une fonction en JavaScript est comme une usine. Ce pourrait être une petite usine avec une seule machine à l'intérieur. Ou il peut contenir de nombreuses autres petites usines, chacune avec de nombreuses machines effectuant des tâches différentes. Dans une véritable usine de vêtements, vous pourriez avoir des rames de tissu et des bobines de fil qui entrent et des t-shirts et des jeans qui sortent. Notre usine JavaScript ne traite que les données, elle ne peut pas coudre, percer un trou ou faire fondre du métal. Dans notre usine JavaScript, les données entrent et sortent.

Toutes ces données semblent un peu ennuyeuses, mais c'est vraiment très cool; nous pourrions avoir une fonction qui indique à un robot ce qu'il faut faire pour le dîner. Disons que je vous invite, vous et votre ami, chez moi. Vous préférez les cuisses de poulet, j'aime les saucisses, votre ami veut toujours ce que vous voulez et mon ami ne mange pas de viande.

Je n'ai pas le temps de faire du shopping, donc la fonction doit savoir ce que nous avons dans le réfrigérateur pour prendre des décisions. Chaque ingrédient a un temps de cuisson différent et nous voulons que tout soit servi chaud par le robot en même temps. Nous devons fournir à la fonction les données sur ce que nous aimons, la fonction pourrait «parler» au réfrigérateur et la fonction pourrait contrôler le robot.

Une fonction a normalement un nom, des parenthèses et des accolades. Comme ça:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

Notez cela /*...*/et //arrêtez la lecture du code par le navigateur.

NOM: Vous pouvez appeler une fonction à peu près n'importe quel mot que vous voulez. L'exemple "cookMeal" est typique de l'union de deux mots et de la mise en majuscule du second - mais ce n'est pas nécessaire. Il ne peut pas y avoir d'espace, ni un nombre à lui seul.

PARENTHÈSES: "Parenthèses" ou ()sont la boîte aux lettres sur la porte de l'usine de la fonction JavaScript ou une boîte aux lettres dans la rue pour l'envoi de paquets d'informations à l'usine. Parfois, la boîte aux lettres peut être marquée par exemple cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime) , auquel cas vous savez quelles données vous devez lui fournir.

BRACELETS: "Bretelles" qui ressemblent à ceci {}sont les fenêtres teintées de notre usine. De l'intérieur de l'usine, vous pouvez voir dehors, mais de l'extérieur, vous ne pouvez pas voir dedans.

L'EXEMPLE DE CODE LONG CI-DESSUS

Notre code commence par le mot fonction , nous savons donc que c'est un! Ensuite, le nom de la fonction chante - c'est ma propre description de la fonction. Puis entre parenthèses () . Les parenthèses sont toujours là pour une fonction. Parfois , ils sont vides, et parfois ils ont quelque chose dans celui - ci a un mot.: (person). Après cela, il y a une accolade comme celle-ci {. Cela marque le début de la fonction sing () . Il a un partenaire qui marque la fin du chant () comme celui-ci}

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

Cette fonction pourrait donc avoir quelque chose à voir avec le chant et pourrait avoir besoin de certaines données sur une personne. Il contient des instructions pour faire quelque chose avec ces données.

Maintenant, après la fonction sing () , vers la fin du code se trouve la ligne

var person="an old lady";

VARIABLE: Les lettres var représentent "variable". Une variable est comme une enveloppe. A l'extérieur, cette enveloppe est marquée "personne". À l'intérieur, il contient une feuille de papier avec les informations dont notre fonction a besoin, des lettres et des espaces réunis comme un morceau de ficelle (ce qu'on appelle une ficelle) qui font une phrase se lisant "une vieille dame". Notre enveloppe pourrait contenir d'autres types de choses comme des nombres (appelés entiers), des instructions (appelées fonctions), des listes (appelées tableaux ). Parce que cette variable est écrite en dehors de tous les accolades {}et parce que vous pouvez voir à travers les fenêtres teintées lorsque vous êtes à l'intérieur des accolades, cette variable peut être vue de n'importe où dans le code. Nous appelons cela une «variable globale».

VARIABLE GLOBALE: personne est une variable globale, ce qui signifie que si vous changez sa valeur de "une vieille dame" en "un jeune homme", la personne continuera d'être un jeune homme jusqu'à ce que vous décidiez de la changer à nouveau et que toute autre fonction dans le code peut voir que c'est un jeune homme. Appuyez sur le F12bouton ou regardez les paramètres Options pour ouvrir la console développeur d'un navigateur et tapez "personne" pour voir quelle est cette valeur. Tapez person="a young man"pour le modifier, puis tapez à nouveau "personne" pour voir qu'il a changé.

Après cela, nous avons la ligne

sing(person);

Cette ligne appelle la fonction, comme si elle appelait un chien

"Viens chanter , viens chercher quelqu'un !"

Lorsque le navigateur a chargé le code JavaScript et atteint cette ligne, il démarre la fonction. Je mets la ligne à la fin pour m'assurer que le navigateur a toutes les informations dont il a besoin pour l'exécuter.

Les fonctions définissent les actions - la fonction principale consiste à chanter. Il contient une variable appelée firstPart qui s'applique au chant sur la personne qui s'applique à chacun des versets de la chanson: "Il y avait" + personne + "qui a avalé". Si vous tapez firstPart dans la console, vous n'obtiendrez pas de réponse car la variable est verrouillée dans une fonction - le navigateur ne peut pas voir à l'intérieur des fenêtres teintées des accolades.

FERMETURES: Les fermetures sont les petites fonctions qui se trouvent à l'intérieur de la grande fonction sing () . Les petites usines à l'intérieur de la grande usine. Ils ont chacun leurs propres accolades, ce qui signifie que les variables à l'intérieur ne peuvent pas être vues de l'extérieur. C'est pourquoi les noms des variables ( créature et résultat ) peuvent être répétés dans les fermetures mais avec des valeurs différentes. Si vous tapez ces noms de variables dans la fenêtre de la console, vous n'obtiendrez pas sa valeur car elle est masquée par deux couches de fenêtres teintées.

Les fermetures savent toutes ce qu'est la variable de la fonction sing () appelée firstPart , car elles peuvent voir depuis leurs fenêtres teintées.

Après les fermetures viennent les lignes

fly();
spider();
bird();
cat();

La fonction sing () appellera chacune de ces fonctions dans l'ordre où elles sont données. Ensuite, le travail de la fonction sing () sera terminé.

reconnaissants
la source
56

D'accord, en parlant avec un enfant de 6 ans, j'utiliserais peut-être les associations suivantes.

Imaginez - vous jouez avec vos petits frères et sœurs dans toute la maison, et vous vous déplacez avec vos jouets et en avez amené certains dans la chambre de votre frère aîné. Au bout d'un moment, votre frère est rentré de l'école et est allé dans sa chambre, et il s'y est enfermé, donc maintenant vous ne pouvez plus accéder aux jouets qui y sont restés de manière directe. Mais vous pourriez frapper à la porte et demander à votre frère ces jouets. C'est ce qu'on appelle la fermeture du jouet ; votre frère a compensé pour vous, et il est maintenant dans une portée extérieure .

Comparez avec une situation où une porte était verrouillée par le courant d'air et personne à l'intérieur (exécution de la fonction générale), puis un incendie local se produit et brûle la pièce (ramasse-miettes: D), puis une nouvelle pièce a été construite et maintenant vous pouvez partir un autre jouet là-bas (nouvelle instance de fonction), mais n'obtenez jamais les mêmes jouets qui ont été laissés dans la première instance de la pièce.

Pour un enfant avancé, je mettrais quelque chose comme ceci. Ce n'est pas parfait, mais cela vous fait ressentir ce que c'est:

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

Comme vous pouvez le voir, les jouets laissés dans la pièce sont toujours accessibles via le frère et peu importe si la pièce est verrouillée. Voici un jsbin pour jouer avec.

dmi3y
la source
49

Une réponse pour un enfant de six ans (en supposant qu'il sait ce qu'est une fonction et ce qu'est une variable, et quelles sont les données):

Les fonctions peuvent renvoyer des données. Un type de données que vous pouvez renvoyer d'une fonction est une autre fonction. Lorsque cette nouvelle fonction est renvoyée, toutes les variables et tous les arguments utilisés dans la fonction qui l'a créée ne disparaissent pas. Au lieu de cela, cette fonction parent «se ferme». En d'autres termes, rien ne peut regarder à l'intérieur et voir les variables qu'il a utilisées à l'exception de la fonction qu'il a renvoyée. Cette nouvelle fonction a une capacité spéciale à regarder en arrière à l'intérieur de la fonction qui l'a créée et à voir les données à l'intérieur.

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

Une autre façon très simple de l'expliquer est en termes de portée:

Chaque fois que vous créez une portée plus petite à l'intérieur d'une portée plus grande, la plus petite portée sera toujours en mesure de voir ce qui est dans la plus grande portée.

Stupide Stupide
la source
49

Une fonction en JavaScript n'est pas seulement une référence à un ensemble d'instructions (comme en langage C), mais elle comprend également une structure de données cachée qui est composée de références à toutes les variables non locales qu'elle utilise (variables capturées). Ces fonctions en deux parties sont appelées fermetures. Chaque fonction en JavaScript peut être considérée comme une fermeture.

Les fermetures sont des fonctions avec un état. Il est quelque peu similaire à "ceci" dans le sens où "ceci" fournit également un état pour une fonction mais la fonction et "ceci" sont des objets séparés ("ceci" n'est qu'un paramètre de fantaisie, et le seul moyen de le lier de manière permanente à un est de créer une fermeture). Bien que "ceci" et la fonction vivent toujours séparément, une fonction ne peut pas être séparée de sa fermeture et le langage ne fournit aucun moyen d'accéder aux variables capturées.

Parce que toutes ces variables externes référencées par une fonction lexicalement imbriquée sont en fait des variables locales dans la chaîne de ses fonctions englobantes lexicales (les variables globales peuvent être supposées être des variables locales d'une fonction racine), et chaque exécution unique d'une fonction crée de nouvelles instances de ses variables locales, il s'ensuit que chaque exécution d'une fonction renvoyant (ou autrement la transférant, comme l'enregistrer comme rappel) une fonction imbriquée crée une nouvelle fermeture (avec son propre ensemble potentiellement unique de variables non locales référencées qui représentent son exécution le contexte).

En outre, il faut comprendre que les variables locales en JavaScript sont créées non pas sur le cadre de pile, mais sur le tas et détruites uniquement lorsque personne ne les référence. Lorsqu'une fonction retourne, les références à ses variables locales sont décrémentées, mais elles peuvent toujours être non nulles si pendant l'exécution en cours elles font partie d'une fermeture et sont toujours référencées par ses fonctions lexicalement imbriquées (ce qui ne peut se produire que si les références à ces fonctions imbriquées ont été retournées ou autrement transférées vers un code externe).

Un exemple:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();
srgstm
la source
47

Peut-être un peu au-delà de tous, mais le plus précoce des enfants de six ans, mais quelques exemples qui ont aidé à faire en sorte que le concept de fermeture en JavaScript clique pour moi.

Une fermeture est une fonction qui a accès à la portée d'une autre fonction (ses variables et fonctions). La façon la plus simple de créer une fermeture est d'utiliser une fonction dans une fonction; la raison étant qu'en JavaScript, une fonction a toujours accès à la portée de sa fonction conteneur.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

ALERTE: singe

Dans l'exemple ci-dessus, la fonction externalFunction est appelée, elle-même appelée innerFunction. Notez la façon dont externalVar est disponible pour innerFunction, comme en témoigne son alerte correcte de la valeur de externalVar.

Considérez maintenant ce qui suit:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

ALERTE: singe

referenceToInnerFunction est définie sur externalFunction (), qui renvoie simplement une référence à innerFunction. Lorsque referenceToInnerFunction est appelé, il retourne externalVar. Encore une fois, comme ci-dessus, cela démontre que innerFunction a accès à externalVar, une variable de externalFunction. De plus, il est intéressant de noter qu'il conserve cet accès même après l'exécution de la fonction externalFunction.

Et c'est là que les choses deviennent vraiment intéressantes. Si nous devions nous débarrasser de externalFunction, disons le mettre à null, vous pourriez penser que referenceToInnerFunction perdrait son accès à la valeur de externalVar. Mais ce n'est pas le cas.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

ALERTE: singe ALERTE: singe

Mais comment en est-il ainsi? Comment referenceToInnerFunction peut-il toujours connaître la valeur de externalVar maintenant que externalFunction a été défini sur null?

La raison pour laquelle referenceToInnerFunction peut toujours accéder à la valeur de externalVar est que, lorsque la fermeture a été créée pour la première fois en plaçant innerFunction à l'intérieur de externalFunction, innerFunction a ajouté une référence à la portée de externalFunction (ses variables et fonctions) à sa chaîne de portée. Cela signifie que innerFunction a un pointeur ou une référence à toutes les variables de externalFunction, y compris externalVar. Ainsi, même lorsque la fonction externalFunction a terminé son exécution, ou même si elle est supprimée ou définie sur null, les variables de sa portée, comme externalVar, restent en mémoire en raison de la référence exceptionnelle à elles de la part de la fonction innerFunction qui a été renvoyée à referenceToInnerFunction. Pour vraiment libérer externalVar et le reste des variables de externalFunction de la mémoire, vous devez vous débarrasser de cette référence exceptionnelle à eux,

//////////

Deux autres choses sur les fermetures à noter. Tout d'abord, la fermeture aura toujours accès aux dernières valeurs de sa fonction conteneur.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

ALERTE: gorille

Deuxièmement, lorsqu'une fermeture est créée, elle conserve une référence à toutes les variables et fonctions de sa fonction englobante; il ne peut pas choisir. Et pourtant, les fermetures doivent être utilisées avec parcimonie, ou du moins avec précaution, car elles peuvent consommer beaucoup de mémoire; de nombreuses variables peuvent être conservées en mémoire longtemps après la fin de l'exécution d'une fonction conteneur.

Michael Dziedzic
la source
45

Je les pointerais simplement vers la page des fermetures de Mozilla . C'est l' explication la meilleure, la plus concise et la plus simple des bases de la fermeture et de l'utilisation pratique que j'ai trouvée. Il est fortement recommandé à quiconque apprend JavaScript.

Et oui, je le recommanderais même à un enfant de 6 ans - si l'enfant de 6 ans apprend les fermetures, il est logique qu'il soit prêt à comprendre l' explication concise et simple fournie dans l'article.

mjmoody383
la source
Je suis d'accord: ladite page Mozilla est particulièrement simple et concise. Étonnamment, votre message n'a pas été aussi largement apprécié que les autres.
Brice Coustillas