Fermetures en JavaScript expliquées avec des exemples

Que sont les fermetures?

Une fermeture est la combinaison d'une fonction et de l'environnement lexical (portée) dans lequel cette fonction a été déclarée. Les fermetures sont une propriété fondamentale et puissante de Javascript. Cet article traite du `` comment '' et du `` pourquoi '' des fermetures:

Exemple

//we have an outer function named walk and an inner function named fly function walk (){ var dist = '1780 feet'; function fly(){ console.log('At '+dist); } return fly; } var flyFunc = walk(); //calling walk returns the fly function which is being assigned to flyFunc //you would expect that once the walk function above is run //you would think that JavaScript has gotten rid of the 'dist' var flyFunc(); //Logs out 'At 1780 feet' //but you still can use the function as above //this is the power of closures

Un autre exemple

function by(propName) { return function(a, b) { return a[propName] - b[propName]; } } const person1 = {name: 'joe', height: 72}; const person2 = {name: 'rob', height: 70}; const person3 = {name: 'nicholas', height: 66}; const arr_ = [person1, person2, person3]; const arr_sorted = arr_.sort(by('height')); // [ { name: 'nicholas', height: 66 }, { name: 'rob', height: 70 },{ name: 'joe', height: 72 } ]

La fermeture «se souvient» de l'environnement dans lequel elle a été créée. Cet environnement se compose de toutes les variables locales qui étaient dans la portée au moment de la création de la fermeture.

function outside(num) { var rememberedVar = num; // In this example, rememberedVar is the lexical environment that the closure 'remembers' return function inside() { // This is the function which the closure 'remembers' console.log(rememberedVar) } } var remember1 = outside(7); // remember1 is now a closure which contains rememberedVar = 7 in its lexical environment, and //the function 'inside' var remember2 = outside(9); // remember2 is now a closure which contains rememberedVar = 9 in its lexical environment, and //the function 'inside' remember1(); // This now executes the function 'inside' which console.logs(rememberedVar) => 7 remember2(); // This now executes the function 'inside' which console.logs(rememberedVar) => 9 

Les fermetures sont utiles car elles vous permettent de «vous souvenir» des données et vous permettent ensuite d'opérer sur ces données via des fonctions renvoyées. Cela permet à javascript d'émuler des méthodes privées que l'on trouve dans d'autres langages de programmation. Les méthodes privées sont utiles pour restreindre l'accès au code ainsi que pour gérer votre espace de noms global.

Variables et méthodes privées

Les fermetures peuvent également être utilisées pour encapsuler des données / méthodes privées. Jetez un œil à cet exemple:

const bankAccount = (initialBalance) => { const balance = initialBalance; return { getBalance: function() { return balance; }, deposit: function(amount) { balance += amount; return balance; }, }; }; const account = bankAccount(100); account.getBalance(); // 100 account.deposit(10); // 110

Dans cet exemple, nous ne pourrons pas accéder à balancepartir de n'importe où en dehors de la bankAccountfonction, ce qui signifie que nous venons de créer une variable privée. Où est la fermeture? Eh bien, pensez à ce qui bankAccount()revient. Il retourne en fait un objet avec un tas de fonctions à l'intérieur, et pourtant, lorsque nous appelons account.getBalance(), la fonction est capable de «se souvenir» de sa référence initiale balance. C'est la puissance de la fermeture, où une fonction «se souvient» de sa portée lexicale (portée du temps de compilation), même lorsque la fonction est exécutée en dehors de cette portée lexicale.

Émulation de variables à portée de bloc.

Javascript n'avait pas de concept de variables à portée de bloc. Cela signifie que lors de la définition d'une variable à l'intérieur d'une boucle for par exemple, cette variable est également visible de l'extérieur de la boucle for. Alors, comment les fermetures peuvent-elles nous aider à résoudre ce problème? Nous allons jeter un coup d'oeil.

 var funcs = []; for(var i = 0; i < 3; i++){ funcs[i] = function(){ console.log('My value is ' + i); //creating three different functions with different param values. } } for(var j = 0; j < 3; j++){ funcs[j](); // My value is 3 // My value is 3 // My value is 3 }

Étant donné que la variable i n'a pas de portée de bloc, sa valeur dans les trois fonctions a été mise à jour avec le compteur de boucle et a créé des valeurs malveillantes. Closure peut nous aider à résoudre ce problème en créant un instantané de l'environnement dans lequel se trouvait la fonction lors de sa création, tout en préservant son état.

 var funcs = []; var createFunction = function(val){ return function() {console.log("My value: " + val);}; } for (var i = 0; i < 3; i++) { funcs[i] = createFunction(i); } for (var j = 0; j < 3; j++) { funcs[j](); // My value is 0 // My value is 1 // My value is 2 }

Les dernières versions de javascript es6 + ont un nouveau mot-clé appelé let qui peut être utilisé pour donner à la variable une portée de bloc. Il existe également de nombreuses fonctions (forEach) et des bibliothèques entières (lodash.js) qui sont dédiées à la résolution de problèmes tels que ceux expliqués ci-dessus. Ils peuvent certainement augmenter votre productivité, mais il reste extrêmement important de connaître tous ces problèmes lorsque vous essayez de créer quelque chose de grand.

Les fermetures ont de nombreuses applications spéciales qui sont utiles lors de la création de grands programmes javascript.

  1. Émulation de variables privées ou d'encapsulation
  2. Appels côté serveur asynchrones
  3. Création d'une variable à portée de bloc.

Émulation de variables privées.

Contrairement à de nombreux autres langages, Javascript n'a pas de mécanisme qui vous permet de créer des variables d'instance encapsulées dans un objet. Avoir des variables d'instance publiques peut causer de nombreux problèmes lors de la création de programmes de taille moyenne à grande. Cependant, avec les fermetures, ce problème peut être atténué.

Tout comme dans l'exemple précédent, vous pouvez créer des fonctions qui renvoient des littéraux d'objet avec des méthodes qui ont accès aux variables locales de l'objet sans les exposer. Ainsi, les rendant effectivement privés.

Les fermetures peuvent également vous aider à gérer votre espace de noms global pour éviter les collisions avec des données partagées globalement. Habituellement, toutes les variables globales sont partagées entre tous les scripts de votre projet, ce qui vous posera certainement beaucoup de problèmes lors de la construction de programmes de taille moyenne à grande. C'est pourquoi les auteurs de bibliothèques et de modules utilisent des fermetures pour masquer les méthodes et les données d'un module entier. C'est ce qu'on appelle le modèle de module, il utilise une expression de fonction immédiatement invoquée qui n'exporte que certaines fonctionnalités vers le monde extérieur, ce qui réduit considérablement la quantité de références globales.

Voici un court exemple d'un squelette de module.

var myModule = (function() = { let privateVariable = 'I am a private variable'; let method1 = function(){ console.log('I am method 1'); }; let method2 = function(){ console.log('I am method 2, ', privateVariable); }; return { method1: method1, method2: method2 } }()); myModule.method1(); // I am method 1 myModule.method2(); // I am method 2, I am a private variable

Les fermetures sont utiles pour capturer de nouvelles instances de variables privées contenues dans l'environnement «mémorisé», et ces variables ne sont accessibles que via la fonction ou les méthodes retournées.

Vecteurs

A vector is perhaps the most simple type of collection in Clojure. You can think of it like an array in Javascript. Let’s define a simple vector:

(def a-vector [1 2 3 4 5]) ;; Alternatively, use the vector function: (def another-vector (vector 1 2 3 4 5)) ;; You can use commas to separate items, since Clojure treats them as whitespace. (def comma-vector [1, 2, 3, 4, 5])

You’ll see that it uses square brackets, just like an array in JS. Since Clojure, like JS, is dynamically typed, vectors can hold elements of any type, including other vectors.

(def mixed-type-vector [1 "foo" :bar ["spam" 22] #"^baz$"])

Adding items to a vector

You can append items to a vector using conj. You can also prepend to a list using into, but note that into is intended for merging two vectors, so both its arguments must be vectors, and using into is slower than using conj.

(time (conj [1 2] 3)) ; => "Elapsed time: 0.032206 msecs" ; [1 2 3] (time (into [1] [2 3])) ; => "Elapsed time: 0.078499 msecs" ; [1 2 3]
:rocket:

IDEOne it!

Retrieving items from a vector

You can retrieve items from a vector using get. This is equivalent to using bracket notation to access items in an array in many imperative languages. Items in a vector are 0-indexed, counting from the left.

var arr = [1, 2, 3, 4, 5]; arr[0]; // => 1

In Clojure, this would be written like so:

(def a-vector [1 2 3 4 5]) (get a-vector 0) ; => 1

You can also give get a default value, if you give it an index that isn’t in the array.

;; the list doesn't have 2147483647 elements, so it'll return a string instead. (get a-vector 2147483646 "sorry, not found!") ; => "sorry, not found!"

Converting other collections into vectors

Non-vector data structures can be converted into vectors using the vec function. With hashmaps, this produces a 2D vector containing pairs of keys and values.

(vec '(1 2 3 4 5)) ; => [1 2 3 4 5] (vec {:jack "black" :barry "white"}) ; => [[:jack "black"] [:barry "white"]]

When to use a vector?

A vector should be used in almost all cases if you need a collection, because they have the shortest random-access times, which makes it easy to retrieve items from a vector. Note that vectors are ordered. If order doesn’t matter, it may be better to use a set. Also note that vectors are designed for appending items; if you need to prepend items, you might want to use a list.

More info on Closures:

  • Learn JavaScript closures in six minutes
  • A basic guide to closures in JavaScript
  • Discover the power of closures in VueJS
  • JavaScript closures explained by mailing a package