js closure Comment fonctionnent les fermetures de JavaScript?



15 Answers

Chaque fois que vous voyez le mot-clé de fonction dans une autre fonction, la fonction interne a accès aux variables de la fonction externe.

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

Cela consignera toujours 16, car bar peut accéder au x défini comme argument de foo et à tmp de foo .

C'est une fermeture. Une fonction n'a pas à retourner pour s'appeler une fermeture. Accéder simplement à des variables en dehors de votre portée lexicale immédiate crée 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 is now a closure.
bar(10);

La fonction ci-dessus consignera également le journal 16, car bar peut toujours se référer à x et à tmp , même s’il ne se trouve plus directement dans la portée.

Cependant, étant donné que tmp traîne toujours à l'intérieur de la fermeture du bar , il est également incrémenté. Il sera incrémenté chaque fois que vous appelez bar .

L'exemple le plus simple d'une 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 appelée, un nouveau contexte d'exécution est créé. Avec les arguments de la fonction et l'objet parent, ce contexte d'exécution reçoit également toutes les variables déclarées en dehors de celui-ci (dans l'exemple ci-dessus, les deux expressions "a" et "b").

Il est possible de créer plusieurs fonctions de fermeture, soit en renvoyant une liste de celles-ci, soit en les définissant comme variables globales. Tous feront référence au même x et au même tmp , ils ne font pas leurs propres copies.

Ici, le nombre x est un nombre littéral. Comme avec les autres littéraux en JavaScript, lorsque foo est appelé, le nombre x est copié dans foo tant qu'argument x .

D'autre part, JavaScript utilise toujours des références lorsqu'il traite des objets. Si par exemple, vous avez appelé foo avec un objet, la fermeture qu'il renvoie fera référence à cet objet d'origine!

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

Comme prévu, chaque appel à la bar(10) incrémentera x.memb . Ce qui pourrait ne pas être attendu, c'est que x fait simplement référence au même objet que la variable age ! Après quelques appels au bar , age.memb sera 2! Ce référencement constitue la base des fuites de mémoire avec les objets HTML.

closure programmation

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

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




Si nous prenons la question au sérieux, nous devrions découvrir ce qu’un enfant typique de 6 ans est capable d’acquérir sur le plan cognitif, même s’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 est écrit:

Votre enfant sera capable de suivre des instructions en deux étapes. Par exemple, si vous dites à votre enfant: "Va à la cuisine et apporte-moi un sac poubelle", ils pourront 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 . Dans la cuisine, une fonction appelée getTrashBag récupère un sac à ordures 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 expliquant pourquoi les fermetures sont intéressantes:

  • Chaque fois que makeKitchen() est appelée, une nouvelle fermeture est créée avec ses propres trashBags distincts.
  • La variable trashBags est locale à l'intérieur de chaque cuisine et n'est pas accessible à l'extérieur, mais la fonction interne de la propriété getTrashBag a accès.
  • Chaque appel de fonction crée une fermeture, mais il ne serait pas nécessaire de garder la fermeture sauf si une fonction interne, qui a accès à l'intérieur de la fermeture, peut être appelée de l'extérieur de la fermeture. Renvoyer l'objet avec la fonction getTrashBag fait ici.



Les fermetures sont difficiles à expliquer car elles sont utilisées pour faire fonctionner un comportement que tout le monde s'attend intuitivement à travailler de toute façon. Je trouve 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? Il suffit de remplacer l'appel de la dernière ligne par son corps de méthode (ce qui est fondamentalement le cas des 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 la portée actuelle. La seule solution consiste à laisser plus5 transporter son périmètre (ou plutôt celui de son parent). De cette façon, x est bien défini et il est lié à la valeur 5.




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

Imaginons la situation suivante: un conducteur est assis dans une voiture. Cette voiture est dans un avion. L'avion est à l'aéroport. La possibilité pour le conducteur d'accéder à des objets extérieurs à sa voiture, mais à l'intérieur de l'avion, même si cet avion quitte un aéroport, constitue une fermeture. C'est tout.Quand vous aurez 27 ans, regardez l' explication plus détaillée ou l'exemple ci-dessous.

Voici comment je peux convertir mon histoire d'avion dans le 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");




J'ai écrit un article de blog il y a quelque temps expliquant les fermetures. Voici ce que j'ai dit à propos des fermetures en ce qui concerne les raisons pour lesquelles vous en voudriez une.

Les fermetures sont un moyen de laisser une fonction avoir des variables persistantes et privées , c'est-à-dire des variables que seule une fonction connaît, où elle peut garder une trace des informations de ses précédentes exécutions.

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

Message complet:

Alors, quelles sont ces choses de fermeture?




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

Vous savez comment les adultes peuvent posséder une maison, et ils appellent à la maison? Quand une mère a un enfant, l'enfant ne possède pas vraiment quelque chose, non? Mais ses parents possèdent une maison, alors chaque fois que quelqu'un demande à l'enfant "Où est ta maison?", Il / elle peut répondre à "cette maison!" Et indiquer la maison de ses parents. Une "fermeture" est la capacité de l'enfant à toujours (même à l'étranger) de pouvoir dire qu'il a une maison, même si c'est vraiment le parent qui est propriétaire de la maison.




J'ai tendance à apprendre mieux par des comparaisons BON / BAD. J'aime voir le code de travail suivi du code de non-travail que quelqu'un est susceptible de rencontrer J'ai assemblé un jsFiddle qui fait une comparaison et essaie de résumer les différences aux explications les plus simples que je pourrais 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 ce qui précède, le code 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 portée de fonction et n'est pas la même que indexcelle liée à la portée externe.

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

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

  • Chaque portée nest liée à la valeur qu'elle avait lors de l' createClosure(n)appel, de sorte que la fonction imbriquée renvoyée renvoie toujours la valeur de ncelle qu'elle avait lors de l' createClosure(n)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 dans la createClosureArray()fonction et celle-ci ne fait que renvoyer le tableau complet, ce qui, à première vue, semble plus intuitif.

  • Ce qui n’est peut-être pas évident, c’est que puisque createClosureArray()n’est appelé qu’une fois, une seule portée 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 renvoyé index. Notez que cela indexest défini dans la createClosureArrayfonction qui n'est appelée qu'une seule fois.

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

  • Toutes les fonctions ajoutées au tableau renvoient la indexvariable SAME de la portée parente où elle a été définie au lieu de 10 variables différentes parmi 10 étendues différentes, comme dans le premier exemple. Le résultat final est que toutes les 10 fonctions renvoient la même variable à partir de la même portée.

  • Une fois la boucle terminée et en indexcours de modification, la valeur finale était de 10; par conséquent, 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 fait de mal
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10




J'ai créé un didacticiel 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



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

Voici une fermeture:

var a = 42;

function b() { return a; }

Oui. Vous utilisez probablement cela 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 qui provient d'une étendue supérieure 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 les autres réponses.




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




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.");



L'auteur de Closures a très bien expliqué les fermetures, en expliquant pourquoi nous en avons besoin et en expliquant LexicalEnvironment, qui est nécessaire à la compréhension des fermetures.
Voici le résumé:

Que se passe-t-il si une variable est accédée, mais qu'elle n'est pas locale? Comme ici:

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

Le processus comprend deux étapes:

  1. Tout d'abord, lorsqu'une fonction f est créée, elle n'est pas créée dans un espace vide. Il existe un objet LexicalEnvironment en cours. Dans le cas ci-dessus, il s'agit de la fenêtre (a n'est pas défini au moment de la création de la fonction).

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

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 d’environnements Lexical pouvant également être appelée chaîne d’étendue.

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

Fermetures

Une fonction imbriquée peut continuer à vivre après la fin de la fonction externe:

Marquage des environnements lexicaux:

Comme nous le voyons, il this.says’agit d’une propriété dans l’objet utilisateur qui continue à vivre après la fin de l’utilisateur.

Et si vous vous en souvenez, lorsqu’elle this.sayest créée, elle (comme chaque fonction) obtient une référence interne this.say.[[Scope]]au LexicalEnvironment actuel. Ainsi, l'environnement LexicalEnvironment de l'exécution actuelle de l'utilisateur reste en mémoire. Toutes les variables de l'utilisateur sont également ses propriétés, de sorte qu'elles sont également conservées avec soin, et non mises à l'abri de la manière habituelle.

Le but est de s'assurer que si la fonction interne veut accéder à une variable externe dans le futur, elle peut le faire.

Résumer:

  1. La fonction interne conserve une référence à l'environnement LexicalEnvironment externe.
  2. La fonction interne peut accéder aux variables à partir de celle-ci à 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'une fonction interne le référence.

Ceci s'appelle une fermeture.




D'accord, parlant avec un enfant de 6 ans, j'utiliserais probablement les associations suivantes.

Imaginez - vous jouez avec vos petits frères et soeurs dans toute la maison, et vous vous déplacez avec vos jouets et en emportez certains dans la chambre de votre grand frère. Après un moment, votre frère est rentré de l'école et est allé dans sa chambre. Il s'est enfermé à l'intérieur. Vous ne pouvez donc plus accéder directement aux jouets qui y sont restés. Mais vous pouvez frapper à la porte et demander à votre frère ces jouets. Ceci s'appelle la fermeture de toy ; votre frère fait pour vous, et il est maintenant en extérieur portée .

Comparez avec une situation où une porte a été verrouillée par un tirage et qu'il n'y a personne à l'intérieur (exécution générale des fonctions), puis un incendie local se produit et brûle la pièce (éboueur: D), puis une nouvelle pièce a été construite et vous pouvez maintenant quitter il y a un autre jouet (nouvelle instance de fonction), mais ne récupérez jamais les mêmes jouets qui ont été laissés dans la première instance.

Pour un enfant avancé, je mettrais quelque chose comme ceci. Ce n'est pas parfait, mais cela vous fait sentir 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 constater, les jouets laissés dans la pièce sont toujours accessibles via le frère, que la pièce soit ou non verrouillée. Voici un jsbin pour jouer avec.




Une fonction en JavaScript n’est pas simplement une référence à un ensemble d’instructions (comme en langage C), elle inclut également une structure de données masquée 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. C'est un peu similaire à "this" dans le sens où "this" fournit également un état pour une fonction mais function et "this" sont des objets séparés ("this" n'est qu'un paramètre de fantaisie, et le seul moyen de le lier de manière permanente à la fonction est de créer une fermeture). Bien que "this" 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.

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 englobant lexicalement (les variables globales peuvent être supposées être des variables locales d'une fonction racine), et chaque exécution d'une fonction crée de nouvelles instances de Il en résulte que chaque exécution d’une fonction renvoyant (ou transférant d’une autre manière, par exemple son enregistrement en tant que callback) 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ésente son exécution. le contexte).

De plus, il faut bien comprendre que les variables locales en JavaScript ne sont pas créées sur le cadre de la pile, mais sur le tas, et ne sont détruites que lorsque personne ne les référence. Quand une fonction est retournée, les références à ses variables locales sont décrémentées, mais elles peuvent rester non nul si, au cours de l'exécution en cours, elles font partie d'une fermeture et sont toujours référencées par ses fonctions imbriquées lexiquement ces fonctions imbriquées ont été retournées ou autrement transférées à 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();



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

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




Related