Bezárások - valószínűleg sok JavaScript fejlesztő már hallotta ezt a kifejezést. Amikor JavaScript-sel kezdtem az utamat, gyakran találkoztam bezárásokkal. És azt hiszem, hogy ezek az egyik legfontosabb és legérdekesebb fogalom a JavaScript-ben.
Szerinted nem érdekesek? Ez gyakran akkor fordul elő, ha nem ért egy fogalmat - nem találja érdekesnek. (Nem tudom, hogy ez történik-e veled vagy sem, de velem ez a helyzet).
Tehát ebben a cikkben megpróbálom érdekessé tenni a lezárásokat.
Mielőtt belépnénk a bezárások világába, kezdjük megérteni a lexikális hatókört . Ha már tud róla, hagyja ki a következő részt. Egyébként ugorjon bele, hogy jobban megértse a bezárásokat.
Lexikai hatókör
Lehet, hogy gondolkodik - ismerem a helyi és globális hatókört, de mi a fene a lexikális hatókör? Ugyanígy reagáltam, amikor meghallottam ezt a kifejezést. Ne aggódj! Nézzük meg közelebbről.
Ez egyszerű, mint a másik két hatókör:
function greetCustomer() { var customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); // Hi! anchal } greetingMsg(); }
A fenti kimenetből láthatja, hogy a belső függvény hozzáférhet a külső függvény változójához. Ez lexikális hatókör, ahol a változó hatókörét és értékét az határozza meg, hogy hol definiálják / létrehozzák (vagyis a kódban elfoglalt helyét). Megvan?
Tudom, hogy az utolsó kicsit összezavarhatta. Tehát hadd vigyelek mélyebbre. Tudta, hogy a lexikai hatókör más néven statikus hatókör ? Igen, ez a másik neve.
Dinamikus hatókör is létezik , amelyet egyes programozási nyelvek támogatnak. Miért említettem a dinamikus hatókört? Mert ez segíthet jobban megérteni a lexikális hatókört.
Nézzünk meg néhány példát:
function greetingMsg() { console.log(customerName);// ReferenceError: customerName is not defined } function greetCustomer() { var customerName = "anchal"; greetingMsg(); } greetCustomer();
Egyetért-e a kimenettel? Igen, referencia hibát fog adni. Ennek oka, hogy mindkét funkció nem fér hozzá egymás hatóköréhez, mivel külön vannak meghatározva.
Nézzünk meg egy másik példát:
function addNumbers(number1) { console.log(number1 + number2); } function addNumbersGenerate() { var number2 = 10; addNumbers(number2); } addNumbersGenerate();
A fenti kimenet 20 lesz egy dinamikus hatókörű nyelv esetén. A lexikális hatókört támogató nyelvek adni fognakreferenceError: number2 is not defined
. Miért?
Mivel a dinamikus hatókörben a keresés először a helyi függvényben történik, majd belemegy abba a függvénybe, amely ezt a helyi függvényt hívta . Ezután megkeresi a funkciót, hogy a nevezett , hogy a funkció, és így tovább, egészen a hívás verem.
A neve magától értetődő - a „dinamikus” változást jelent. A változó hatóköre és értéke eltérő lehet, mivel attól függ, honnan hívják a függvényt. A változó jelentése futás közben változhat.
Megvan a dinamikus hatókör áttekintése? Ha igen, akkor ne feledje, hogy a lexikális hatókör az ellentéte.
A lexikális hatókör, a keresés zajlik a helyi függvény először, akkor bemegy a funkciót, amelynek belsejében , amely funkció határozza. Ezután megkeresi a függvény belsejében, amely , hogy a funkció határozza meg, és így tovább.
Tehát a lexikális vagy statikus hatókör azt jelenti, hogy a változó hatókörét és értékét onnan határozzuk meg, ahol meghatározták. Ez nem változik.
Nézzük meg újra a fenti példát, és próbáljuk meg kitalálni a kimenetet egyedül. Csak egy csavar - deklarálja number2
a tetején:
var number2 = 2; function addNumbers(number1) { console.log(number1 + number2); } function addNumbersGenerate() { var number2 = 10; addNumbers(number2); } addNumbersGenerate();
Tudja, mi lesz a kimenet?
Helyes - a lexikailag lefedett nyelveknél ez 12. Ennek oka, hogy először egy addNumbers
funkcióba (a legbelső hatókörbe) néz, majd befelé keres, ahol ez a függvény meg van határozva. Amint megkapja a number2
változót, vagyis a kimenet 12.
Lehet, hogy kíváncsi vagy arra, miért töltöttem ennyi időt a lexikális körvizsgálattal itt. Ez egy záró cikk, nem a lexikális hatókörről szól. De ha nem tudsz a lexikális hatókörről, akkor nem fogod megérteni a lezárásokat.
Miért? A választ akkor kapja meg, amikor megvizsgáljuk a bezárás definícióját. Menjünk hát be a pályára és térjünk vissza a lezárásokhoz.
Mi az a bezárás?
Nézzük meg a bezárás definícióját:
A bezárás akkor jön létre, amikor egy belső függvény hozzáfér a külső függvényváltozóihoz és argumentumaihoz. A belső funkció hozzáférhet -1. Saját változói.
2. Külső függvény változói és argumentumai.
3. Globális változók.
Várjon! Ez a lezárás vagy a lexikális hatókör meghatározása? Mindkét meghatározás ugyanúgy néz ki. Miben különböznek egymástól?
Nos, ezért határoztam meg a lexikális hatókört fentebb. Mivel a lezárások összefüggenek a lexikális / statikus hatókörrel.
Vizsgáljuk meg újra annak másik definícióját, amely megmondja, hogy mennyire különböznek a zárások.
A bezárás akkor történik, amikor egy függvény hozzáférhet a lexikális hatóköréhez, még akkor is, ha a függvény a lexikális hatókörén kívül hajt végre.Vagy,
A belső függvények hozzáférhetnek a szülő hatóköréhez, még akkor is, ha a szülő függvény már végrehajtásra került.Zavaros? Ne aggódjon, ha még nem ért rá a lényeg. Vannak példáim, amelyek segítenek jobban megérteni. Módosítsuk a lexikális hatókör első példáját:
function greetCustomer() { const customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); } return greetingMsg; } const callGreetCustomer = greetCustomer(); callGreetCustomer(); // output – Hi! anchal
A különbség ebben a kódban az, hogy visszaadjuk a belső funkciót, és később végrehajtjuk. Egyes programozási nyelvekben a helyi változó létezik a függvény végrehajtása során. De a függvény végrehajtása után ezek a helyi változók nem léteznek, és nem lesznek elérhetők.
Itt azonban más a jelenet. A szülő függvény végrehajtása után a belső függvény (visszatérő függvény) továbbra is hozzáférhet a szülő függvény változóihoz. Igen, jól sejtetted. A bezárások az oka.
A belső függvény megőrzi lexikális hatókörét, amikor a szülő függvény végrehajtódik, és így később ez a belső funkció hozzáférhet ezekhez a változókhoz.
A jobb érzés érdekében használjuk dir()
a konzol módszerét, és keressük meg a következők tulajdonságainak listáját callGreetCustomer
:
console.dir(callGreetCustomer);

A fenti képből láthatja, hogy a belső függvény miként őrzi meg szülő hatókörét ( customerName
), amikor greetCustomer()
végrehajtásra kerül. És később, régen customerName
, amikor callGreetCustomer()
kivégezték.
Remélem, hogy ez a példa segített jobban megérteni a bezárás fenti definícióját. És talán most egy kicsit szórakoztatóbbnak találja a bezárásokat.
És mi lesz ezután? Tegyük érdekesebbé ezt a témát különböző példák segítségével.
Példák működés közbeni bezárásokra
function counter() { let count = 0; return function() { return count++; }; } const countValue = counter(); countValue(); // 0 countValue(); // 1 countValue(); // 2
Minden alkalommal, amikor hív countValue
, a számláló változó értéke 1-gyel növekszik. Várjon - gondoltad, hogy a számlálás értéke 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.