Tutoriel de fermeture JavaScript - avec un exemple de code de fermeture JS

Fermetures - beaucoup d'entre vous, développeurs JavaScript, ont probablement déjà entendu ce terme. Lorsque j'ai commencé mon parcours avec JavaScript, j'ai souvent rencontré des fermetures. Et je pense que c'est l'un des concepts les plus importants et les plus intéressants de 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 avec moi).

Donc dans cet article, je vais essayer de rendre les fermetures intéressantes pour vous.

Avant d'entrer dans le monde des fermetures, comprenons d'abord la portée lexicale . Si vous le savez déjà, sautez la partie suivante. Sinon, sautez-y pour mieux comprendre les fermetures.

Cadrage lexical

Vous pensez peut-être - je connais la portée locale et mondiale, mais qu'est-ce que la portée lexicale? J'ai réagi de la même manière quand j'ai entendu ce terme. Ne pas s'inquiéter! Regardons de plus près.

C'est simple comme les deux autres portées:

function greetCustomer() { var customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); // Hi! anchal } greetingMsg(); }

Vous pouvez voir à partir de la sortie ci-dessus que la fonction interne peut accéder à la variable de la fonction externe. Il s'agit de la portée lexicale, 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). Je l'ai?

Je sais que ce dernier élément vous a peut-être dérouté. Alors laissez-moi vous approfondir. Saviez-vous que la portée lexicale est également connue sous le nom de portée statique ? Oui, c'est son autre nom.

Il existe également une portée dynamique , prise en charge par certains langages de programmation. Pourquoi ai-je mentionné la portée dynamique? Parce que cela peut vous aider à mieux comprendre la portée lexicale.

Regardons quelques exemples:

function greetingMsg() { console.log(customerName);// ReferenceError: customerName is not defined } function greetCustomer() { var customerName = "anchal"; greetingMsg(); } greetCustomer();

Êtes-vous d'accord avec le résultat? Oui, cela 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 langues qui prennent en charge la portée lexicale donnerontreferenceError: number2 is not defined. Pourquoi?

Parce que dans la portée dynamique, la recherche a lieu d'abord dans la fonction locale, puis dans la fonction qui a appelé cette fonction locale. Ensuite, il recherche dans la fonction qui a appelé cette fonction, et ainsi de suite, dans 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 où la fonction est appelée. La signification d'une variable peut changer lors de l'exécution.

Vous avez l'essentiel de la portée dynamique? Si oui, rappelez-vous simplement que la portée lexicale est son contraire.

Dans la portée lexicale, la recherche s'effectue d'abord dans la fonction locale, puis dans la fonction à l'intérieur de laquelle cette fonction est définie. Ensuite, il recherche dans la fonction à l'intérieur de laquelle cette fonction est définie et ainsi de suite.

Ainsi, la portée lexicale ou statique signifie que la portée et la valeur d'une variable sont déterminées à partir de l'endroit où elles sont définies. Ça ne change pas.

Regardons à nouveau l'exemple ci-dessus et essayons de déterminer la sortie par vous-même. Juste une torsion - déclarez number2en haut:

var number2 = 2; function addNumbers(number1) { console.log(number1 + number2); } function addNumbersGenerate() { var number2 = 10; addNumbers(number2); } addNumbersGenerate(); 

Savez-vous quelle sera la sortie?

Correct - c'est 12 pour les langues à portée lexicale. C'est parce que d'abord, il regarde dans une addNumbersfonction (portée la plus interne) puis il recherche vers l'intérieur, où cette fonction est définie. Lorsqu'il obtient la number2variable, cela signifie que la sortie est 12.

Vous vous demandez peut-être pourquoi j'ai passé tant de temps sur la portée lexicale ici. Ceci est un article de clôture, pas un article sur la portée lexicale. Mais si vous ne connaissez pas la portée lexicale, vous ne comprendrez pas les fermetures.

Pourquoi? Vous obtiendrez votre réponse lorsque nous examinerons la définition d'une fermeture. Alors entrons dans la piste et revenons aux fermetures.

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 à ses variables et arguments de fonction externe. La fonction intérieure a accès à -

1. Ses propres variables.

2. Variables et arguments de la fonction externe.

3. Variables globales.

Attendez! Est-ce la définition d'une clôture ou d'un cadrage lexical? Les deux définitions se ressemblent. En quoi sont-ils différents?

Eh bien, c'est pourquoi j'ai défini la portée lexicale ci-dessus. Parce que les fermetures sont liées à la portée lexicale / statique.

Regardons à nouveau son autre définition qui vous dira en quoi les fermetures sont différentes.

La fermeture se produit lorsqu'une fonction est capable d'accéder à sa portée lexicale, même lorsque cette fonction s'exécute en dehors de sa portée lexicale.

Ou,

Les fonctions internes peuvent accéder à sa portée parente, même après que la fonction parente est déjà exécutée.

Confus? Ne vous inquiétez pas si vous n'avez pas encore compris. J'ai des exemples pour vous aider à mieux comprendre. Modifions le premier exemple de portée lexicale:

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. Une fois la fonction parent exécutée, la fonction interne (fonction retournée) peut toujours accéder aux variables de la fonction parent. Oui, vous avez bien deviné. Les fermetures en sont la raison.

La fonction interne conserve sa portée lexicale lorsque la fonction parent s'exécute et, par conséquent, plus tard, cette fonction interne peut accéder à ces variables.

Pour en avoir une meilleure idée, utilisons la dir()méthode de la console pour regarder dans la liste des propriétés de callGreetCustomer:

console.dir(callGreetCustomer);

À partir de l'image ci-dessus, vous pouvez voir comment la fonction interne préserve sa portée parente ( customerName) lorsqu'elle greetCustomer()est exécutée. Et plus tard, il a utilisé customerNamequand a callGreetCustomer()é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 fermetures un peu plus amusantes.

Quoi ensuite? Rendons ce sujet plus intéressant en regardant différents exemples.

Exemples de fermetures en action

function counter() { let count = 0; return function() { return count++; }; } const countValue = counter(); countValue(); // 0 countValue(); // 1 countValue(); // 2

Chaque fois que vous appelez countValue, la valeur de la variable count est incrémentée de 1. Attendez - avez-vous pensé que la valeur de count est 0?

Well, that would be wrong as a closure doesn’t work with a value. It stores the reference of the variable. That’s why, when we update the value, it reflects in the second or third call and so on as the closure stores the reference.

Feeling a bit clearer now? Let’s look at another example:

function counter() { let count = 0; return function () { return count++; }; } const countValue1 = counter(); const countValue2 = counter(); countValue1(); // 0 countValue1(); // 1 countValue2(); // 0 countValue2(); // 1 

I hope you guessed the right answer. If not, here is the reason. As countValue1 and countValue2, both preserve their own lexical scope. They have independent lexical environments. You can use dir() to check the [[scopes]] value in both the cases.

Let’s look at a third example.

This one's a bit different. In it, we have to write a function to achieve the output:

const addNumberCall = addNumber(7); addNumberCall(8) // 15 addNumberCall(6) // 13

Simple. Use your newly-gained closure knowledge:

function addNumber(number1) { return function (number2) { return number1 + number2; }; }

Now let’s look at some tricky examples:

function countTheNumber() { var arrToStore = []; for (var x = 0; x < 9; x++) { arrToStore[x] = function () { return x; }; } return arrToStore; } const callInnerFunctions = countTheNumber(); callInnerFunctions[0]() // 9 callInnerFunctions[1]() // 9

Every array element that stores a function will give you an output of 9. Did you guess right? I hope so, but still let me tell you the reason. This is because of the closure's behavior.

The closure stores the reference, not the value. The first time the loop runs, the value of x is 0. Then the second time x is 1, and so on. Because the closure stores the reference, every time the loop runs it's changing the value of x. And at last, the value of x will be 9. So callInnerFunctions[0]() gives an output of 9.

But what if you want an output of 0 to 8? Simple! Use a closure.

Think about it before looking at the solution below:

function callTheNumber() { function getAllNumbers(number) { return function() { return number; }; } var arrToStore = []; for (var x = 0; x < 9; x++) { arrToStore[x] = getAllNumbers(x); } return arrToStore; } const callInnerFunctions = callTheNumber(); console.log(callInnerFunctions[0]()); // 0 console.log(callInnerFunctions[1]()); // 1

Here, we have created separate scope for each iteration. You can use console.dir(arrToStore) to check the value of x in [[scopes]] for different array elements.

That’s it! I hope you can now say that you find closures interesting.

To read my other articles, check out my profile here.