Question Comment fonctionnent les fermetures JavaScript?


Comment expliqueriez-vous les fermetures JavaScript à quelqu'un qui connaît les concepts qui les composent (par exemple, les fonctions, les variables, etc.), mais ne comprend pas les fermetures elles-mêmes?

J'ai vu l'exemple du schéma donné sur Wikipedia, mais malheureusement, cela n'a pas aidé.


7654


origine


Réponses:


Fermetures JavaScript pour les débutants

Soumis par Morris le mar, 2006-02-21 10:19. Édité par la communauté depuis.

Les fermetures ne sont pas magiques

Cette page explique les fermetures afin qu'un programmeur puisse les comprendre - en utilisant le code JavaScript. Ce n'est pas pour les gourous ou les programmeurs fonctionnels.

Les fermetures sont pas dur pour comprendre une fois que le concept de base est grokked. Cependant, ils sont impossibles à comprendre en lisant des articles académiques ou des informations académiques sur eux!

Cet article est destiné aux programmeurs ayant une certaine expérience de la programmation dans un langage courant, et qui peut lire la fonction JavaScript suivante:

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Un exemple de fermeture

Deux résumés d'une phrase:

  • Une fermeture est un moyen de soutenir fonctions de première classe; c'est une expression qui peut référencer des variables dans sa portée (quand elle a été déclarée pour la première fois), être assignée à une variable, être passée en tant qu'argument à une fonction, ou être retournée en tant que résultat de fonction.

  • Ou, une fermeture est une trame de pile qui est allouée lorsqu'une fonction commence son exécution, et pas libéré après le retour de la fonction (comme si un 'frame de pile' était alloué sur le tas plutôt que sur la pile!).

Le code suivant renvoie une référence à une fonction:

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

La plupart des programmeurs JavaScript comprendront comment une référence à une fonction est renvoyée à une variable (say2) dans le code ci-dessus. Si vous ne le faites pas, alors vous devez regarder cela avant de pouvoir apprendre les fermetures. Un programmeur utilisant C penserait à la fonction comme renvoyant un pointeur vers une fonction, et que les variables say et say2 étaient chacun un pointeur vers une fonction.

Il existe une différence critique entre un pointeur C vers une fonction et une référence JavaScript à une fonction. En JavaScript, vous pouvez considérer une variable de référence de fonction comme ayant à la fois un pointeur sur une fonction ainsi que comme un pointeur caché à une fermeture.

Le code ci-dessus a une fermeture parce que la fonction anonyme function() { console.log(text); } est déclaré à l'intérieur une autre fonction, sayHello2() dans cet exemple. En JavaScript, si vous utilisez le function mot-clé dans une autre fonction, vous créez une fermeture.

En C et la plupart des autres langages communs, après une fonction renvoie, toutes les variables locales ne sont plus accessibles car la pile-trame est détruite.

En JavaScript, si vous déclarez une fonction dans une autre fonction, les variables locales peuvent rester accessibles après le retour de la fonction que vous avez appelée. Ceci est démontré ci-dessus, parce que nous appelons la fonction say2()après notre retour de sayHello2(). Notez que le code que nous appelons référence la variable text, qui était un variable locale de la fonction sayHello2().

function() { console.log(text); } // Output of say2.toString();

En regardant la sortie de say2.toString(), nous pouvons voir que le code se réfère à la variable text. La fonction anonyme peut faire référence text qui détient la valeur 'Hello Bob' parce que les variables locales de sayHello2() sont conservés dans une fermeture.

La magie est qu'en JavaScript une référence de fonction a aussi une référence secrète à la fermeture dans laquelle elle a été créée - similaire à la façon dont les délégués sont un pointeur de méthode plus une référence secrète à un objet.

Plus d'exemples

Pour une raison ou une autre, les fermetures semblent vraiment difficiles à comprendre lorsque vous les lisez, mais quand vous voyez des exemples, il devient clair comment ils fonctionnent (ça m'a pris du temps). Je recommande de travailler attentivement sur les exemples jusqu'à ce que vous compreniez comment ils fonctionnent. Si vous commencez à utiliser des fermetures sans vraiment comprendre comment ils fonctionnent, vous créerez bientôt des bugs très bizarres!

Exemple 3

Cet exemple montre que les variables locales ne sont pas copiées - elles sont conservées par référence. C'est un peu comme garder une pile-frame en mémoire quand la fonction externe sort!

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Exemple 4

Les trois fonctions globales ont une référence commune à même fermeture parce qu'ils sont tous déclarés dans un seul appel à setupSomeGlobals().

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Les trois fonctions ont un accès partagé à la même fermeture - les variables locales de setupSomeGlobals() quand les trois fonctions ont été définies.

Notez que dans l'exemple ci-dessus, si vous appelez setupSomeGlobals() à nouveau, une nouvelle fermeture (stack-frame!) est créée. L'ancien gLogNumber, gIncreaseNumber, gSetNumber les variables sont remplacées par Nouveau fonctions qui ont la nouvelle fermeture. (En JavaScript, chaque fois que vous déclarez une fonction dans une autre fonction, la / les fonction (s) interne (s) est / sont recréée (s) à nouveau chaque heure à laquelle la fonction extérieure est appelée.)

Exemple 5

Cet exemple montre que la fermeture contient toutes les variables locales qui ont été déclarées dans la fonction externe avant sa fermeture. Notez que la variable alice est effectivement déclaré après la fonction anonyme. La fonction anonyme est déclarée en premier; et quand cette fonction est appelée, il peut accéder à la alice variable parce que alice est dans la même portée (JavaScript levage variable). Aussi sayAlice()() appelle directement la référence de fonction renvoyée par sayAlice() - c'est exactement la même chose que ce qui a été fait précédemment mais sans la variable temporaire.

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Tricky: notez aussi que le say variable est également à l'intérieur de la fermeture, et pourrait être consulté par toute autre fonction qui pourrait être déclarée dans sayAlice()ou il peut être accédé récursivement dans la fonction interne.

Exemple 6

Celui-ci est un vrai gotcha pour beaucoup de gens, donc vous devez le comprendre. Soyez très prudent si vous définissez une fonction dans une boucle: les variables locales de la fermeture peuvent ne pas agir comme vous le pensez d'abord.

Vous devez comprendre la fonction «variable hissage» en Javascript pour comprendre cet exemple.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

La ligne result.push( function() {console.log(item + ' ' + list[i])} ajoute une référence à une fonction anonyme trois fois dans le tableau des résultats. Si vous n'êtes pas si familier avec les fonctions anonymes, pensez-y comme:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

Notez que lorsque vous exécutez l'exemple, "item2 undefined" est enregistré trois fois! En effet, tout comme les exemples précédents, il n'y a qu'une seule fermeture pour les variables locales pour buildList (qui sont result, i et item). Lorsque les fonctions anonymes sont appelées sur la ligne fnlist[j](); ils utilisent tous la même fermeture unique, et ils utilisent la valeur actuelle pour i et item dans cette fermeture (où i a une valeur de 3 parce que la boucle était terminée, et item a une valeur de 'item2'). Notez que nous indexons de 0 donc item a une valeur de item2. Et le i ++ va augmenter i à la valeur 3.

Il peut être utile de voir ce qui se passe quand une déclaration de la variable au niveau du bloc item est utilisé (via let mot-clé) au lieu d'une déclaration de variable à portée de var mot-clé. Si ce changement est fait, alors chaque fonction anonyme dans le tableau result a sa propre fermeture; Lorsque l'exemple est exécuté, la sortie est la suivante:

item0 undefined
item1 undefined
item2 undefined

Si la variable i est également défini en utilisant let au lieu de var, alors la sortie est:

item0 1
item1 2
item2 3

Exemple 7

Dans ce dernier exemple, chaque appel à la fonction principale crée une fermeture séparée.

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Résumé

Si tout semble complètement flou, alors la meilleure chose à faire est de jouer avec les exemples. Lire une explication est beaucoup plus difficile que de comprendre des exemples. Mes explications des fermetures et des cadres de pile, etc. ne sont pas techniquement correctes - ce sont des simplifications grossières destinées à aider à comprendre. Une fois l'idée de base est grokked, vous pouvez récupérer les détails plus tard.

Derniers points:

  • Chaque fois que vous utilisez function à l'intérieur d'une autre fonction, une fermeture est utilisée.
  • Chaque fois que vous utilisez eval() à l'intérieur d'une fonction, une fermeture est utilisée. Le texte que vous eval peut référencer des variables locales de la fonction, et dans eval vous pouvez même créer de nouvelles variables locales en utilisant eval('var foo = …')
  • Lorsque vous utilisez new Function(…) (la Constructeur de fonction) à l'intérieur d'une fonction, il ne crée pas de fermeture. (La nouvelle fonction ne peut pas référencer les variables locales de la fonction externe.)
  • Une fermeture en JavaScript revient à conserver une copie de toutes les variables locales, exactement comme lorsqu'une fonction est terminée.
  • Il est probablement préférable de penser qu'une fermeture est toujours créée juste une entrée dans une fonction, et les variables locales sont ajoutées à cette fermeture.
  • Un nouvel ensemble de variables locales est conservé chaque fois qu'une fonction avec une fermeture est appelée (étant donné que la fonction contient une déclaration de fonction et qu'une référence à cette fonction interne est renvoyée ou qu'une référence externe est conservée pour elle) ).
  • Deux fonctions peuvent sembler avoir le même texte source, mais ont un comportement complètement différent en raison de leur fermeture «cachée». Je ne pense pas que le code JavaScript puisse réellement savoir si une référence de fonction a une fermeture ou non.
  • Si vous essayez d'apporter des modifications au code source dynamique (par exemple: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), cela ne fonctionnera pas si myFunction est une fermeture (bien sûr, vous ne penseriez même pas à faire une substitution de chaîne de code source à l'exécution, mais ...).
  • Il est possible d'obtenir des déclarations de fonctions dans les déclarations de fonctions dans les fonctions - et vous pouvez obtenir des fermetures à plus d'un niveau.
  • Je pense normalement qu'une fermeture est un terme à la fois pour la fonction et les variables capturées. Notez que je n'utilise pas cette définition dans cet article!
  • Je soupçonne que les fermetures en JavaScript diffèrent de celles normalement trouvées dans les langages fonctionnels.

Liens

Merci

Si tu as juste Fermetures apprises (ici ou ailleurs!), alors je suis intéressé par vos commentaires sur les changements que vous pourriez suggérer qui pourraient rendre cet article plus clair. Envoyer un email à morrisjohns.com (morris_closure @). S'il vous plaît noter que je ne suis pas un gourou sur JavaScript - ni sur les fermetures.


Le message original de Morris peut être trouvé dans le Archive Internet.


6160



Chaque fois que vous voyez le mot clé function 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 se connectera toujours 16, car bar peut accéder à la x qui a été défini comme un argument fooet il peut aussi accéder tmp de foo.

Cette est une fermeture. Une fonction n'a pas à revenir afin d'être appelé une fermeture. Simplement accéder à 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 va également se connecter 16, car bar peut toujours se référer à x et tmp, même si ce n'est plus directement à l'intérieur de la portée.

Cependant, depuis tmp est toujours en train de traîner à l'intérieur barFermeture, il est également incrémenté. Il sera incrémenté chaque fois que vous appelez bar.

L'exemple le plus simple d'une fermeture est celui-ci:

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éé. Conjointement 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, à la fois '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 sur des variables globales. Tout cela se référera à la même  x et le même tmp, ils ne font pas leurs propres copies.

Voici le numéro x est un nombre littéral. Comme avec d'autres littéraux en JavaScript, quand foo est appelé, le nombre x est copié dans foo comme son argument x.

D'un autre côté, JavaScript utilise toujours des références lorsqu'il traite des objets. Si vous dites, vous avez appelé fooavec un objet, la fermeture qu'il retourne sera référence cet objet original!

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 à bar(10) va incrémenter x.memb. Ce qui pourrait ne pas être prévu, c'est que x fait simplement référence au même objet que le age variable! Après quelques appels à bar, age.memb sera 2! Ce référencement est à la base des fuites de mémoire avec les objets HTML.


3804



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

Comme le vieux Albert a dit: "Si vous ne pouvez pas l'expliquer à un enfant de six ans, vous ne le comprenez pas vraiment." Eh bien j'ai essayé d'expliquer les fermetures de JS à un ami de 27 ans et 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 à peu près certain que j'étais l'une des seules personnes à avoir tenté de prendre la question initiale à la lettre. Depuis lors, la question a muté à plusieurs reprises, alors ma réponse peut maintenant sembler incroyablement stupide 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 de l'analogie et de la métaphore quand j'explique des concepts difficiles, alors laissez-moi essayer ma main avec une histoire.

Il était une fois:

Il y avait une princesse ...

function princess() {

Elle a vécu dans un monde merveilleux plein d'aventures. Elle a rencontré son prince charmant, a parcouru son monde sur une licorne, combat des dragons, a rencontré des animaux parlant, et beaucoup d'autres choses fantastiques.

    var adventures = [];

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

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

    /* ... */

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

    return {

Et elle leur parlait souvent de 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.


2251



En prenant la question au sérieux, nous devrions découvrir ce qu'un enfant de 6 ans est capable de faire sur le plan cognitif, même si, bien sûr, celui qui s'intéresse à JavaScript n'est pas si typique.

Sur Développement de l'enfance: 5 à 7 ans  ça dit:

Votre enfant sera capable de suivre deux directions. Par exemple, si vous dites à votre enfant: «Allez dans la cuisine et prenez-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. Il y a une fonction dans la cuisine appelée getTrashBag qui obtient un sac poubelle et le renvoie.

Nous pouvons coder ceci en JavaScript comme ceci:

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

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

var kitchen = makeKitchen();

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

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

  • Chaque fois makeKitchen() est appelé, une nouvelle fermeture est créée avec son propre séparé trashBags.
  • le trashBags variable est locale à l'intérieur de chaque cuisine et n'est pas accessible à l'extérieur, mais la fonction interne sur le getTrashBagla propriété a accès à elle.
  • Chaque appel de fonction crée une fermeture, mais il n'y aurait pas besoin de garder la fermeture à moins qu'une fonction interne, qui a accès à l'intérieur de la fermeture, puisse être appelée depuis l'extérieur de la fermeture. Retourner l'objet avec le getTrashBag la fonction fait cela ici.

691



L'homme de paille

J'ai besoin de savoir combien de fois un bouton a été cliqué et faire quelque chose tous les trois clics ...

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 il 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, ce serait préférable car votre application externe pourrait avoir besoin d'accéder à cette information. Mais dans ce cas, nous ne modifions que le comportement de chaque troisième clic, il est donc préférable de inclure 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 petites choses ici.

Dans l'exemple ci-dessus, j'utilise le comportement de fermeture de JavaScript. Ce comportement permet à toute fonction d'accéder indéfiniment à la portée dans laquelle elle a été créée. Pour l'appliquer pratiquement, j'appelle immédiatement une fonction qui retourne une autre fonction, et parce que la fonction que je retourne a accès à la variable de comptage interne (à cause du comportement de fermeture expliqué ci-dessus) cela donne une portée privée à l'utilisation Fonction ... Pas si simple? Allons le diluer ...

Une simple fermeture d'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

Trouver? 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'elle conserve son état de cliquer pour cliquer.

En outre, cet état variable privé est pleinement accessible, à la fois pour les lectures et 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)


526



Les fermetures sont difficiles à expliquer car elles sont utilisées pour faire un travail de comportement que tout le monde s'attend intuitivement à faire quand même. Je trouve la meilleure façon de les expliquer (et la façon dont je 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 n'a pas connaître les fermetures? Il suffit de remplacer l'appel dans la dernière ligne par son corps de méthode (ce qui est essentiellement ce que les appels de fonction font) 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 est de laisser plus5  porter sa portée (ou plutôt, la portée de ses parents) autour. Par ici, x est bien défini et il est lié à la valeur 5.


444



Ceci est une tentative d'éclaircir plusieurs (possibles) malentendus au sujet des fermetures qui apparaissent dans certaines des autres réponses.

  • Une fermeture n'est pas seulement créée lorsque vous renvoyez une fonction interne. En fait, la fonction englobante n'a pas besoin de revenir du tout afin que sa fermeture soit créée. Vous pouvez à la place assigner 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 n'importe quand plus tard. Par conséquent, la fermeture de la fonction englobante est probablement créée dès que la fonction englobante est appelée puisque toute fonction interne a accès à cette fermeture chaque fois que la fonction interne est appelée, avant ou après le retour de la fonction englobante.
  • Une fermeture ne fait pas référence à une copie du anciennes valeurs des variables dans son champ d'application. 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 difficiles, car chacune a accès aux mêmes variables externes plutôt que de saisir 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é dans la fonction. Ils incluent également des arguments de la fonction. Une fermeture a également accès à ses variables de fermeture contenant, tout le chemin jusqu'à la portée globale.
  • Les fermetures utilisent la mémoire, mais ne causent pas de fuites de mémoire puisque JavaScript par lui-même nettoie ses propres structures circulaires qui ne sont pas référencées. Les fuites de mémoire d'Internet Explorer impliquant des fermetures sont créées lorsqu'il ne parvient pas à déconnecter les valeurs d'attribut DOM qui référencent les fermetures, conservant ainsi des références à des structures éventuellement circulaires.

342



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

Imaginons la prochaine situation: un conducteur est assis dans une voiture. Cette voiture est à l'intérieur d'un avion. L'avion est dans l'aéroport. La capacité du conducteur à 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 tout. Quand vous aurez 27 ans, regardez le 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");

330



UNE fermeture ressemble beaucoup à un objet. Il est instancié chaque fois que vous appelez une fonction.

La portée d'un fermeture en JavaScript est lexical, ce qui signifie que tout ce qui est contenu dans la fonction du fermeture appartient à, a accès à toute variable qui s'y trouve.

Une variable est contenue dans le fermeture si tu

  1. l'assigner avec var foo=1; ou
  2. Ecrivez var foo;

Si une fonction interne (une fonction contenue dans une autre fonction) accède à une telle variable sans la définir dans sa propre portée avec var, elle modifie le contenu de la variable dans la variable externe fermeture.

UNE fermeture survit à l'exécution de la fonction qui l'a engendré. Si d'autres fonctions le font sortir du fermeture / portéedans lequel ils sont définis (par exemple comme valeurs de retour), ceux-ci continueront à faire référence à fermeture.

Exemple

 function example(closure) {
   // define somevariable to live in the closure of example
   var somevariable = 'unchanged';

   return {
     change_to: function(value) {
       somevariable = value;
     },
     log: function(value) {
       console.log('somevariable of closure %s is: %s',
         closure, somevariable);
     }
   }
 }

 closure_one = example('one');
 closure_two = example('two');

 closure_one.log();
 closure_two.log();
 closure_one.change_to('some new value');
 closure_one.log();
 closure_two.log();

Sortie

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged

319



J'ai écrit un billet de blog il y a quelques temps pour expliquer les fermetures. Voici ce que j'ai dit à propos des fermetures en termes de Pourquoi tu en veux un.

Les fermetures sont un moyen de laisser une fonction   avoir variables persistantes privées -   c'est-à-dire, des variables qu'un seul   fonction connaît, où il peut   garder une trace de l'information des temps précédents   que c'était couru.

En ce sens, ils permettent à une fonction d'agir un peu comme un objet avec des attributs privés.

Message complet:

Alors, quels sont ces thingys de fermeture?


214



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 ajouter et 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_calculator crée une nouvelle variable locale n, qui continue d'être utilisable par cette calculatrice add et multiply fonctions longtemps après make_calculator résultats.

Si vous êtes familier avec les cadres de pile, ces calculateurs semblent étranges: Comment peuvent-ils continuer à accéder n après make_calculator résultats? 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 rendus.

Fonctions internes comme add et multiply, qui accède aux variables déclarées dans une fonction externe**, sont appelés fermetures.

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



* Par exemple, il couvre tous les points de l'article "Closures for Dummies" donné en une autre réponse, sauf l'exemple 6, qui montre simplement que les variables peuvent être utilisées avant d'être déclarées, un fait intéressant à connaître mais totalement indépendant des fermetures. Il couvre également tous les points de la réponse acceptée, sauf pour les points (1) qui copient leurs arguments dans les variables locales (les arguments de la fonction nommée), et (2) que la copie 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. Ce sont également bon à savoir mais encore complètement sans rapport avec les fermetures. Il est également très similaire à l'exemple cette réponse mais un peu plus court et moins abstrait. Cela ne couvre pas le point de cette réponse ou ce commentaire, qui est que JavaScript rend difficile de brancher le actuel valeur d'une variable de boucle dans votre fonction interne: L'étape de "branchement" ne peut être faite qu'avec une fonction d'aide 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 variable de la fonction d'aide, plutôt que d'avoir quelque chose branché.) Encore une fois, très utile lors de la création de fermetures, mais pas de fermeture ou de 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 aux valeurs plutôt qu'à l'espace de stockage, fournissant un flux constant de personnes qui comprennent les fermetures d'une certaine façon (à savoir 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 souligne clairement.


199