Урок за затваряне на JavaScript - С JS Примерен код за затваряне

Затваряния - много от вас разработчици на JavaScript вероятно са чували този термин и преди. Когато започнах пътуването си с JavaScript, често срещах затваряния. И мисля, че те са една от най-важните и интересни концепции в JavaScript.

Не мислите, че са интересни? Това често се случва, когато не разбирате понятие - не ви се струва интересно. (Не знам дали това се случва с вас или не, но при мен е така).

Така че в тази статия ще се опитам да направя затварянията интересни за вас.

Преди да влезем в света на затварянията, нека първо разберем лексикалния обхват . Ако вече знаете за това, пропуснете следващата част. В противен случай скочете в него, за да разберете по-добре затварянията.

Лексикален обхват

Може би си мислите - знам локален и глобален обхват, но какво, по дяволите, е лексикален обхват? Реагирах по същия начин, когато чух този термин. Не се притеснявайте! Нека да разгледаме отблизо.

Това е просто като другите два обхвата:

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

От горния изход можете да видите, че вътрешната функция има достъп до променливата на външната функция. Това е лексикален обхват, където обхватът и стойността на дадена променлива се определя от това къде е дефинирана / създадена (т.е. нейната позиция в кода). Схванах го?

Знам, че последният бит може да ви обърка. Така че позволете ми да ви отведа по-дълбоко. Знаете ли, че лексикалният обхват е известен и като статичен обхват ? Да, това е другото му име.

Има и динамичен обхват , който някои езици за програмиране поддържат. Защо споменах динамичен обхват? Защото може да ви помогне да разберете по-добре лексикалния обхват.

Нека разгледаме няколко примера:

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

Съгласни ли сте с резултата? Да, това ще даде грешка в препратката. Това е така, защото и двете функции нямат достъп до обхвата една на друга, тъй като са дефинирани поотделно.

Нека разгледаме друг пример:

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

Горният изход ще бъде 20 за език с динамичен обхват. Езици, които поддържат лексикален обхват ще дадатreferenceError: number2 is not defined. Защо?

Тъй като при динамичен обхват първо се извършва търсене в локалната функция, след което се преминава във функцията, която е нарекла тази локална функция. След това търси във функцията, която е извикала тази функция, и така нататък, нагоре в стека повиквания.

Името му е обяснимо - „динамично“ означава промяна. Обхватът и стойността на променливата могат да бъдат различни, тъй като зависи от това къде се извиква функцията. Значението на променлива може да се промени по време на изпълнение.

Разбрахте същността на динамичния обхват? Ако отговорът е да, просто запомнете, че лексикалният обхват е неговата противоположност.

При лексикалния обхват първо се извършва търсене в локалната функция, след което се преминава във функцията, вътре в която тази функция е дефинирана. След това търси във функцията, в която тази функция е дефинирана и т.н.

Така че, лексикален или статичен обхват означава, че обхватът и стойността на променливата се определят от мястото, където е дефинирана. Не се променя.

Нека отново разгледаме горния пример и се опитаме да разберем резултата сами. Само един обрат - декларирайте number2отгоре:

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

Знаете ли какъв ще бъде изходът?

Правилно - това е 12 за езици с лексикален обхват. Това е така, защото първо той разглежда addNumbersфункция (най-вътрешен обхват), след което търси навътре, където тази функция е дефинирана. Когато получава number2променливата, което означава, че изходът е 12.

Може би се чудите защо съм прекарал толкова време на лексикален обхват тук. Това е статия за закриване, а не за лексикален обхват. Но ако не знаете за лексикалния обхват, тогава няма да разберете затварянията.

Защо? Ще получите своя отговор, когато разгледаме определението за затваряне. Така че нека да влезем в пистата и да се върнем към затварянията.

Какво е затваряне?

Нека разгледаме дефиницията на затваряне:

Затварянето се създава, когато вътрешна функция има достъп до своите променливи и аргументи на външната функция. Вътрешната функция има достъп до -

1. Собствените си променливи.

2. Променливи и аргументи на външната функция.

3. Глобални променливи.

Изчакайте! Това ли е определението за затваряне или лексикален обхват? И двете определения изглеждат еднакво. Как са различни?

Е, затова определих лексикален обхват по-горе. Тъй като затварянията са свързани с лексикален / статичен обхват.

Нека отново разгледаме другото му определение, което ще ви каже как затварянията са различни.

Затварянето е, когато дадена функция има достъп до своя лексикален обхват, дори когато тази функция се изпълнява извън нейния лексикален обхват.

Или,

Вътрешните функции имат достъп до своя родителски обхват, дори след като родителската функция вече е изпълнена.

Объркан? Не се притеснявайте, ако все още не сте разбрали точката. Имам примери, които да ви помогнат да разберете по-добре. Нека модифицираме първия пример за лексикално обхват:

function greetCustomer() { const customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); } return greetingMsg; } const callGreetCustomer = greetCustomer(); callGreetCustomer(); // output – Hi! anchal

Разликата в този код е, че връщаме вътрешната функция и я изпълняваме по-късно. В някои езици за програмиране локалната променлива съществува по време на изпълнението на функцията. Но след като функцията бъде изпълнена, тези локални променливи не съществуват и те няма да бъдат достъпни.

Тук обаче сцената е различна. След като родителската функция бъде изпълнена, вътрешната функция (върната функция) все още може да осъществява достъп до променливите на родителската функция. Да, познахте правилно. Причините са затварянията.

Вътрешната функция запазва своя лексикален обхват, когато родителската функция се изпълнява и следователно по-късно тази вътрешна функция може да осъществи достъп до тези променливи.

За да се почувствате по-добре, нека използваме dir()метода на конзолата, за да разгледаме списъка със свойствата на callGreetCustomer:

console.dir(callGreetCustomer);

От горното изображение можете да видите как вътрешната функция запазва родителския си обхват ( customerName), когато greetCustomer()се изпълнява. И по-късно се използва, customerNameкогато callGreetCustomer()е бил екзекутиран.

Надявам се този пример да ви помогне да разберете по-добре дефиницията по-горе за затваряне. И може би сега ще намерите затварянето малко по-забавно.

И какво следва? Нека направим тази тема по-интересна, като разгледаме различни примери.

Примери за затваряне в действие

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

Всеки път, когато се обадите countValue, стойността на променливата count се увеличава с 1. Изчакайте - мислехте ли, че стойността на count е 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.