Fermetures d'avant en arrière

Les fermetures sont souvent considérées comme un art mystérieux au pays de JavaScript. Une fois maîtrisés, ils vous permettent d’écrire du code JavaScript vraiment étonnant. Cet article vous familiarisera avec la magie des fermetures de JavaScript.


Qu'est-ce qu'une fermeture??

L'une des vérités clés de JavaScript est que tout est un objet. Cela inclut bien sûr des fonctions.

Une fermeture n'est rien d'autre qu'un objet de fonction avec une portée associée dans laquelle les variables de la fonction sont résolues.

Les bouchons portent leur nom à cause de la façon dont ils Fermer sur leur contenu. Considérez le code JavaScript suivant:

 topping = "anchovi"; function pizzaParty (numSlices) var topping = "pepperoni", innerFunction = function () var topping = "jambon"; console.log ("… mais mettez" + topping + "sur" + numSlices + "slices"); ; console.log ("Cette pizza est tout au sujet de" + garniture); fonction intérieure ();  pizzaParty (3);

Si vous ouvrez votre console favorite et dirigez ce méchant garçon, vous recevrez un message délicieux: "Cette pizza est une histoire de pepperoni ... Mais mettez du jambon sur 3 tranches." Cet exemple illustre quelques concepts clés de JavaScript indispensables à la maîtrise des fermetures..

Une fermeture est un objet de fonction

Combien d'objets de fonction sont dans le code ci-dessus? Bien… nous avons notre soirée pizza fonction, et imbriquée dans cette fonction est fonction intérieure. Les maths n'ont pas toujours été mon point fort, mais 1 + 1 = 2 dans mon livre. Chaque objet de fonction a son propre ensemble de variables, qui sont résolues dans les fonctions de chaque fonction. portée.

Une fermeture a sa propre portée

Les fermetures ne peuvent être entièrement comprises sans une base solide. Le mécanisme de portée de JavaScript est ce qui permet à chaque fonction d'avoir sa propre Garniture variable, et sans cela nous pourrions avoir trop de pepperoni, trop peu de jambon, ou * halètement *… quelques anchois à notre soirée pizza. Utilisons une illustration rapide pour mieux illustrer cette idée.

Les fonctions sont exécutées à l'aide de la portée qui était en vigueur lors de la définition de la fonction. Cela n’a rien à voir avec la portée en vigueur lorsque la fonction est appelée.

L'accessibilité variable fonctionne à l'extérieur

Les flèches vertes indiquent que l'accessibilité fonctionne de l'extérieur vers l'intérieur. Les variables définies dans l'étendue en dehors d'une fonction sont accessibles de l'intérieur..

Si nous devions omettre le Garniture variable de l'intérieur du soirée pizza fonction, alors nous aurions un message comme "Cette pizza est tout au sujet de l'anchovi", mais depuis soirée pizza a un Garniture variable dans sa propre portée; ces ventouses salées ne s'approcheront jamais de notre soirée pizza.

De même, le numSlices paramètre accessible de l'intérieur fonction intérieure car il est défini dans le champ d'application ci-dessus - dans ce cas, le champ soirée pizza.

L'accessibilité variable ne fonctionne pas à l'envers

Les flèches rouges indiquent que les variables entrant dans le champ d'une fonction ne sont jamais accessibles en dehors de cette fonction. C'est le cas uniquement lorsqu'une variable remplit l'une des conditions suivantes:

  1. le var le mot clé est utilisé.
  2. La variable est un paramètre de la fonction ou une fonction externe.
  3. La variable est une fonction imbriquée.

Omettant le var Lorsque vous définissez une variable, le mot-clé amène JavaScript à définir la variable nommée la plus proche dans les fonctions externes jusqu’à la portée globale. Donc, en utilisant notre exemple, le jambon Garniture dans fonction intérieure ne peut pas être consulté à partir de soirée pizza, et le pepperoni Garniture dans soirée pizza ne peut pas être consulté dans la portée globale où habite anchovi.

JavaScript utilise le cadrage lexical

Portée lexicale signifie que les fonctions sont exécutées à l'aide de la portée variable en vigueur au moment défini. Cela n'a rien à voir avec la portée en vigueur lorsque la fonction est appelée. Ce fait est crucial pour libérer le pouvoir des fermetures.

Maintenant que nous comprenons ce qu'est une fermeture et ce que cela signifie pour les fermetures, passons à quelques cas d'utilisation classiques..


Utilisation de fermetures pour la confidentialité

Les fermetures sont la moyen de dissimuler votre code à l’œil du public. Avec les fermetures, vous pouvez facilement avoir des membres privés protégés du monde extérieur:

 (function (exports) function myPrivateMultiplyFunction (num, num2) return num * num2; // équivalent à window.multiply = fonction (num1, num2) … exports.multiply = fonction (num1, num2) console.log (myPrivateMultiplyFunction (num1, num2));) (fenêtre);

Avec les fermetures, vous pouvez facilement avoir des membres privés protégés du monde extérieur.

Faisons le décomposer. Notre objet de fonction de niveau supérieur est une fonction anonyme:

 (fonction (exports) ) (fenêtre);

Nous appelons cette fonction anonyme tout de suite. Nous passons le contexte global (la fenêtre dans ce cas) pour pouvoir "exporter" une fonction publique, mais masquer tout le reste. Parce que la fonction myPrivateMultiplyFunction est une fonction imbriquée, elle n’existe que dans le cadre de notre fermeture; afin que nous puissions l'utiliser n'importe où dans cette portée, et seulement dans cette portée.

JavaScript tiendra une référence à notre fonction privée pour une utilisation à l'intérieur de la fonction multiply, mais myPrivateMultiplyFunction ne peut pas être consulté en dehors de la fermeture. Essayons ceci:

 multiplier (2,6) // => 12 myPrivateMultiplyFunction (2,6) // => ReferenceError: myPrivateMultiplyFunction n'est pas défini

La fermeture nous a permis de définir une fonction à usage privé, tout en nous permettant de contrôler ce que le reste du monde voit. Que peuvent faire les fermetures??


Utilisation de fermetures pour la méta-programmation

Les fermetures sont assez utiles pour générer du code. Fatigué de vous souvenir de tous ces codes de clés embêtants pour les événements de clavier? Une technique courante consiste à utiliser une carte de clé:

 var KeyMap = "Entrée": 13, "Shift": 16, "Onglet": 9, "LeftArrow": 37;

Ensuite, dans notre événement de clavier, nous voulons vérifier si une certaine touche a été enfoncée:

 var txtInput = document.getElementById ('myTextInput'); txtInput.onkeypress = fonction (e) code var = e.keyCode || e.which // tarif habituel pour obtenir la touche enfoncée if (code === KeyMap.Enter) console.log (txtInput.value); 

Capturer un moment dans le temps

L'exemple ci-dessus n'est pas le pire, mais nous pouvons utiliser la méta-programmation et les fermetures pour créer une solution encore meilleure. En utilisant notre existant KeyMap objet, nous pouvons générer des fonctions utiles:

 for (clé var dans KeyMap) // objet d'accès avec accesseur de tableau pour définir le nom de la fonction "dyanamic" KeyMap ["est" + clé] = (fonction (comparer) return fonction (ev) var code = ev.keyCode | | ev.which; code retour === comparer;) (KeyMap [key]); 

Les fermetures sont si puissantes car elles peuvent capturer la variable locale et les liaisons de paramètres de la fonction dans laquelle elles sont définies.

Cette boucle génère un est fonction pour chaque clé KeyMap, et notre txtInput.onkeypress la fonction devient un peu plus lisible:

 var txtInput = document.getElementById ('myTextInput'); txtInput.onkeypress = fonction (e) if (KeyMap.isEnter (e)) console.log (txtInput.value); 

La magie commence ici:

 KeyMap ["est" + key] = (fonction (comparer) ) (KeyMap [clé]); // invoque immédiatement et passe la valeur actuelle à KeyMap [key]

Comme nous passons en boucle sur les clés KeyMap, nous passons la valeur référencée par cette clé à la fonction externe anonyme et l'invoquons immédiatement. Ceci lie cette valeur à la comparer paramètre de cette fonction.

La fermeture qui nous intéresse est celle que nous revenons de l'intérieur de la fonction anonyme:

 fonction de retour (ev) var code = ev.keyCode || ev.which; code retour === comparer; 

N'oubliez pas que les fonctions sont exécutées avec la portée qui était en place lors de leur définition. le comparer paramètre est lié à la KeyMap valeur qui était en place lors d'une itération de boucle, notre fermeture imbriquée est donc capable de la capturer. Nous prenons un instantané dans le temps de la portée qui était en vigueur à ce moment-là.

Les fonctions que nous avons créées nous permettent d’ignorer la configuration du code variable à chaque fois que nous voulons vérifier le code de la clé, et nous avons maintenant des fonctions pratiques et lisibles à utiliser.


Utilisation de fermetures pour étendre la langue

À ce stade, il devrait être relativement facile de voir que les fermetures sont essentielles à l’écriture de JavaScript de qualité supérieure. Appliquons ce que nous savons sur les fermetures à l’augmentation de l’un des types natifs de JavaScript (gasp!). Avec notre focus sur les objets de fonction, augmentons le natif Une fonction type:

 Function.prototype.cached = function () var self = this, // "this" fait référence à la fonction d'origine cache = ; // notre fonction de retour de mémoire cache locale au périmètre lexique (args) if (args dans la mémoire cache) return cache [args]; cache de retour [args] = self (args); ; ;

Ce petit bijou permet à n’importe quelle fonction de créer sa propre version en cache. Vous pouvez voir que la fonction retourne une fonction elle-même, donc cette amélioration peut être appliquée et utilisée comme suit:

 Math.sin = Math.sin.cached (); Math.sin (1) // => 0.8414709848078965 Math.sin (1) // => 0.8414709848078965 cette fois extrait de la mémoire cache

Notez les compétences de fermeture qui entrent en jeu. Nous avons un local cache variable qui est gardée privée et protégée du monde extérieur. Cela empêchera toute falsification qui pourrait invalider notre cache.

La fermeture renvoyée a accès aux liaisons de la fonction externe, ce qui signifie que nous sommes en mesure de renvoyer une fonction avec un accès complet au cache à l'intérieur, ainsi que la fonction d'origine! Cette petite fonction peut faire des merveilles pour la performance. Cette extension particulière est configurée pour gérer un argument, mais j'aimerais voir votre coup de poignard à une fonction de cache à plusieurs arguments.


Fermetures à l'état sauvage

En prime, examinons quelques utilisations des fermetures dans la nature.

jQuery

Parfois, le fameux jQuery $ l’usine n’est pas disponible (pensez à WordPress) et nous voulons l’utiliser comme nous le faisons habituellement. Plutôt que d’atteindre jQuery.noConflict, nous pouvons utiliser une fermeture pour permettre aux fonctions internes d'accéder à notre $ liaison de paramètre.

 (function ($) $ (document) .ready (function () // comme d'habitude…);) (jQuery);

Backbone.js

Sur les grands projets Backbone.js, il peut s'avérer utile de garder vos modèles d'application privés, puis d'exposer une API publique sur la vue de votre application principale. En utilisant une fermeture, vous pouvez facilement obtenir cette confidentialité.

 (fonction (exportations) var Product = Backbone.Model.extend (urlRoot: '/ products',); var ProductList = Backbone.Collection.extend (url: '/ products', modèle: Product); var Products = new ProductList; var ShoppingCartView = Backbone.View.extend (addProduct: function (produit, opté) rendu CartItems.create (produit, opté);, removeProduct: fonction (produit, opté) Produits.remove (produit , opts);, getProduct: function (productId) return Products.get (productId);, getProducts: function () return Products.models;); // exporte uniquement la vue principale de l'application exports.ShoppingCart = nouveau ShoppingCartView;) (fenêtre);

Conclusion

Un récapitulatif rapide de ce que nous avons appris:

  • Une fermeture n'est rien d'autre qu'un objet fonction avec une portée.
  • Les bouchons portent leur nom par la façon dont ils "se ferment" sur leur contenu.
  • Les fermetures encaissent très rapidement sur la portée lexicale de JavaScript.
  • Les fermetures sont le moyen d'obtenir la confidentialité en JavaScript.
  • Les fermetures sont capables de capturer la variable locale et les liaisons de paramètres d'une fonction externe.
  • JavaScript peut être puissamment étendu avec un peu de magie de fermeture.
  • Les fermetures peuvent être utilisées avec beaucoup de vos bibliothèques préférées pour les rendre encore plus fraîches!

Merci beaucoup d'avoir lu! N'hésitez pas à poser des questions. Maintenant profitons de la soirée pizza!