Clôtures – beaucoup d’entre vous, devs JavaScript, ont probablement déjà entendu ce terme. Lorsque j’ai commencé mon voyage avec JavaScript, j’ai souvent rencontré des fermetures. Et je pense qu’ils sont l’un des concepts les plus importants et les plus intéressants en JavaScript.
Vous ne pensez pas qu’ils sont intéressants ? Cela se produit souvent lorsque vous ne comprenez pas un concept – vous ne le trouvez pas intéressant. (Je ne sais pas si cela vous arrive ou non, mais c’est le cas pour moi).
Donc, dans cet article, je vais essayer de rendre les closures intéressantes pour vous.
Avant d’entrer dans le monde des closures, comprenons d’abord le scoping lexical. Si vous le savez déjà, sautez la partie suivante. Sinon, sautez dedans pour mieux comprendre les closures.
La portée lexicale
Vous vous dites peut-être – je connais la portée locale et globale, mais que diable est la portée lexicale ? J’ai réagi de la même manière lorsque j’ai entendu ce terme. Ne vous inquiétez pas ! Regardons de plus près.
C’est simple comme les deux autres scopes:
function greetCustomer() { var customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); // Hi! anchal } greetingMsg();}
Vous pouvez voir dans la sortie ci-dessus que la fonction interne peut accéder à la variable de la fonction externe. C’est le scoping lexical, où la portée et la valeur d’une variable sont déterminées par l’endroit où elle est définie/créée (c’est-à-dire sa position dans le code). Compris ?
Je sais que cette dernière partie a pu vous déconcerter. Alors laissez-moi vous emmener plus loin. Saviez-vous que le scoping lexical est également connu sous le nom de scoping statique ? Oui, c’est son autre nom.
Il existe également un scoping dynamique, que certains langages de programmation prennent en charge. Pourquoi ai-je mentionné le scoping dynamique ? Parce que cela peut vous aider à mieux comprendre le scoping lexical.
Regardons quelques exemples :
function greetingMsg() { console.log(customerName);// ReferenceError: customerName is not defined}function greetCustomer() { var customerName = "anchal"; greetingMsg();}greetCustomer();
Etes-vous d’accord avec la sortie ? Oui, il donnera une erreur de référence. C’est parce que les deux fonctions n’ont pas accès à la portée de l’autre, car elles sont définies séparément.
Regardons un autre exemple:
function addNumbers(number1) { console.log(number1 + number2);}function addNumbersGenerate() { var number2 = 10; addNumbers(number2);}addNumbersGenerate();
La sortie ci-dessus sera de 20 pour un langage à portée dynamique. Les langages qui prennent en charge le scoping lexical donneront referenceError: number2 is not defined
. Pourquoi ?
Parce que dans le scoping dynamique, la recherche se fait d’abord dans la fonction locale, puis elle va dans la fonction qui a appelé cette fonction locale. Ensuite, elle cherche dans la fonction qui a appelé cette fonction, et ainsi de suite, en remontant la pile d’appels.
Son nom est explicite – « dynamique » signifie changement. La portée et la valeur de la variable peuvent être différentes car elles dépendent de l’endroit d’où la fonction est appelée. La signification d’une variable peut changer au moment de l’exécution.
Vous avez compris l’essentiel du scoping dynamique ? Si oui, alors rappelez-vous que le scoping lexical est son opposé.
Dans le scoping lexical, la recherche a lieu dans la fonction locale d’abord, puis elle va dans la fonction à l’intérieur de laquelle cette fonction est définie. Ensuite, elle cherche dans la fonction à l’intérieur de laquelle cette fonction est définie et ainsi de suite.
Donc, le scoping lexical ou statique signifie que la portée et la valeur d’une variable sont déterminées à partir de l’endroit où elle est définie. Elles ne changent pas.
Regardons à nouveau l’exemple ci-dessus et essayez de comprendre la sortie par vous-même. Une seule entorse – déclarez number2
en haut :
var number2 = 2;function addNumbers(number1) { console.log(number1 + number2);}function addNumbersGenerate() { var number2 = 10; addNumbers(number2);}addNumbersGenerate();
Savez-vous ce que sera la sortie ?
Correct – c’est 12 pour les langages à portée lexicale. C’est parce que d’abord, il regarde dans une fonction addNumbers
(portée la plus interne) puis il cherche vers l’intérieur, où cette fonction est définie. Comme il obtient la variable number2
, ce qui signifie que la sortie est 12.
Vous vous demandez peut-être pourquoi j’ai passé autant de temps sur le scoping lexical ici. Il s’agit d’un article sur la fermeture, et non sur le scoping lexical. Mais si vous ne connaissez pas le scoping lexical, alors vous ne comprendrez pas les fermetures.
Pourquoi ? Vous aurez votre réponse lorsque nous regarderons la définition d’une fermeture. Alors entrons dans la piste et revenons aux closures.
Qu’est-ce qu’une fermeture ?
Regardons la définition d’une fermeture:
La fermeture est créée lorsqu’une fonction interne a accès aux variables et aux arguments de sa fonction externe. La fonction interne a accès à –
1. Ses propres variables.
2. Les variables et arguments de la fonction externe.
3. Les variables globales.
Attendez ! Est-ce la définition d’une fermeture ou du scoping lexical ? Les deux définitions se ressemblent. En quoi sont-elles différentes ?
Eh bien, c’est pour cela que j’ai défini le scoping lexical ci-dessus. Parce que les fermetures sont liées au scoping lexical/statique.
Regardons à nouveau son autre définition qui vous dira en quoi les closures sont différentes.
On parle de closure lorsqu’une fonction est capable d’accéder à son champ lexical, même lorsque cette fonction s’exécute en dehors de son champ lexical.
Or,
Les fonctions internes peuvent accéder à sa portée parentale, même lorsque la fonction parentale est déjà exécutée.
Confusé ? Ne vous inquiétez pas si vous n’avez pas encore saisi l’essentiel. J’ai des exemples pour vous aider à mieux comprendre. Modifions le premier exemple de scoping lexical :
function greetCustomer() { const customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); } return greetingMsg;}const callGreetCustomer = greetCustomer();callGreetCustomer(); // output – Hi! anchal
La différence dans ce code est que nous retournons la fonction interne et l’exécutons plus tard. Dans certains langages de programmation, la variable locale existe pendant l’exécution de la fonction. Mais une fois la fonction exécutée, ces variables locales n’existent pas et elles ne seront pas accessibles.
Ici, cependant, la scène est différente. Après l’exécution de la fonction parent, la fonction interne (fonction retournée) peut toujours accéder aux variables de la fonction parent. Oui, vous avez deviné. Les fermetures en sont la raison.
La fonction interne préserve son champ lexical lorsque la fonction parent s’exécute et donc, plus tard, cette fonction interne peut accéder à ces variables.
Pour mieux comprendre, utilisons la méthode dir()
de la console pour consulter la liste des propriétés de callGreetCustomer
:
console.dir(callGreetCustomer);
D’après l’image ci-dessus, vous pouvez voir comment la fonction interne préserve la portée de son parent (customerName
) lorsque greetCustomer()
est exécutée. Et plus tard, il a utilisé customerName
lorsque callGreetCustomer()
a été exécuté.
J’espère que cet exemple vous a aidé à mieux comprendre la définition ci-dessus d’une fermeture. Et peut-être que maintenant vous trouvez les closures un peu plus amusantes.
Alors, quelle est la suite ? Rendons ce sujet plus intéressant en examinant différents exemples.
Exemples de closures en action
function counter() { let count = 0; return function() { return count++; };}const countValue = counter();countValue(); // 0countValue(); // 1countValue(); // 2
Chaque fois que vous appelez countValue
, la valeur de la variable count est incrémentée de 1. Attendez – vous pensiez que la valeur de count est 0 ?
Eh bien, ce serait faux car une fermeture ne fonctionne pas avec une valeur. Elle stocke la référence de la variable. C’est pourquoi, lorsque nous mettons à jour la valeur, cela se reflète dans le deuxième ou troisième appel et ainsi de suite car la fermeture stocke la référence.
Vous vous sentez un peu plus clair maintenant ? Regardons un autre exemple :
function counter() { let count = 0; return function () { return count++; };}const countValue1 = counter();const countValue2 = counter();countValue1(); // 0countValue1(); // 1countValue2(); // 0countValue2(); // 1
J’espère que vous avez deviné la bonne réponse. Si ce n’est pas le cas, en voici la raison. Comme countValue1
et countValue2
, les deux préservent leur propre portée lexicale. Ils ont des environnements lexicaux indépendants. Vous pouvez utiliser dir()
pour vérifier la valeur ]
dans les deux cas.
Regardons un troisième exemple.
Celui-ci est un peu différent. Dans celui-ci, nous devons écrire une fonction pour obtenir le résultat:
const addNumberCall = addNumber(7);addNumberCall(8) // 15addNumberCall(6) // 13
Simple. Utilisez vos nouvelles connaissances en matière de fermeture:
function addNumber(number1) { return function (number2) { return number1 + number2; };}
Maintenant, examinons des exemples délicats:
function countTheNumber() { var arrToStore = ; for (var x = 0; x < 9; x++) { arrToStore = function () { return x; }; } return arrToStore;}const callInnerFunctions = countTheNumber();callInnerFunctions() // 9callInnerFunctions() // 9
Chaque élément de tableau qui stocke une fonction vous donnera une sortie de 9. Avez-vous bien deviné ? Je l’espère, mais laissez-moi quand même vous expliquer la raison. C’est à cause du comportement de la fermeture.
La fermeture stocke la référence, pas la valeur. La première fois que la boucle s’exécute, la valeur de x est 0. Puis la deuxième fois, x est 1, et ainsi de suite. Parce que la fermeture stocke la référence, chaque fois que la boucle s’exécute, elle change la valeur de x. Et au final, la valeur de x sera 9. Donc callInnerFunctions()
donne une sortie de 9.
Mais que faire si vous voulez une sortie de 0 à 8 ? Simple ! Utilisez une fermeture.
Réfléchissez-y avant de regarder la solution ci-dessous :
function callTheNumber() { function getAllNumbers(number) { return function() { return number; }; } var arrToStore = ; for (var x = 0; x < 9; x++) { arrToStore = getAllNumbers(x); } return arrToStore;}const callInnerFunctions = callTheNumber();console.log(callInnerFunctions()); // 0console.log(callInnerFunctions()); // 1
Ici, nous avons créé une portée séparée pour chaque itération. Vous pouvez utiliser console.dir(arrToStore)
pour vérifier la valeur de x dans ]
pour différents éléments du tableau.
C’est tout ! J’espère que vous pouvez maintenant dire que vous trouvez les fermetures intéressantes.
Pour lire mes autres articles, consultez mon profil ici.
Les fermetures ne sont pas un problème.