Vodič za zatvaranje JavaScript-a - s JS primjerom koda za zatvaranje

Zatvaranja - mnogi od vas JavaScript programera vjerojatno su već čuli ovaj izraz. Kad sam započeo putovanje s JavaScriptom, često sam nailazio na zatvaranja. I mislim da su oni jedan od najvažnijih i najzanimljivijih koncepata u JavaScript-u.

Mislite da nisu zanimljivi? To se često događa kad ne razumijete koncept - ne čini vam se zanimljivim. (Ne znam događa li vam se to ili ne, ali to je slučaj kod mene).

Stoga ću vam u ovom članku pokušati učiniti zatvaranje zanimljivim.

Prije nego što krenemo u svijet zatvaranja, prvo shvatimo leksičko opseg . Ako već znate za to, preskočite sljedeći dio. Inače uskočite u nju da biste bolje razumjeli zatvaranja.

Leksički opseg

Možda razmišljate - znam lokalni i globalni opseg, ali koji je vrag leksički opseg? I ja sam reagirao kad sam čuo ovaj izraz. Bez brige! Pogledajmo izbliza.

Jednostavno je poput druga dva područja:

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

Iz gornjeg rezultata možete vidjeti da unutarnja funkcija može pristupiti varijabli vanjske funkcije. Ovo je leksičko opseg, gdje se opseg i vrijednost varijable određuje prema tome gdje je definirana / stvorena (odnosno njezin položaj u kodu). Razumiješ?

Znam da te posljednji dio mogao zbuniti. Pa da te odvedem dublje. Jeste li znali da je leksičko opseg također poznato i kao statičko opseg ? Da, to je drugo ime.

Tu je i dinamičko opseg , što neki programski jezici podržavaju. Zašto sam spomenuo dinamičko opseg? Jer vam može pomoći da bolje razumijete leksički opseg.

Pogledajmo nekoliko primjera:

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

Slažete li se s rezultatima? Da, dat će pogrešku u referenci. To je zato što obje funkcije nemaju pristup međusobnom opsegu, jer su definirane zasebno.

Pogledajmo još jedan primjer:

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

Gornji izlaz bit će 20 za jezik s dinamičkim opsegom. Dati će jezici koji podržavaju leksičko opsegreferenceError: number2 is not defined. Zašto?

Budući da se u dinamičkom opsegu pretraživanje prvo odvija u lokalnoj funkciji, a zatim se prelazi u funkciju koja je nazvala tu lokalnu funkciju. Zatim pretražuje funkciju koja je pozvala tu funkciju, i tako dalje, gore niz poziva.

Ime mu je samoobjašnjivo - "dinamičan" znači promjena. Opseg i vrijednost varijable mogu biti različiti, jer ovisi o mjestu pozivanja funkcije. Značenje varijable može se promijeniti tijekom izvođenja.

Imate suštinu dinamičkog opsega? Ako da, samo upamtite da je leksičko opseg njegova suprotnost.

U leksičkom opsegu, pretraživanje se prvo odvija u lokalnoj funkciji, a zatim se prelazi u funkciju unutar koje je ta funkcija definirana. Zatim pretražuje funkciju unutar koje je ta funkcija definirana i tako dalje.

Dakle, leksički ili statički opseg znači da se opseg i vrijednost varijable određuje od mjesta na kojem je definirana. Ne mijenja se.

Pogledajmo opet gornji primjer i pokušajmo sami dokučiti izlaz. Samo jedan zaokret - number2na vrhu izjavite:

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

Znate li kakav će biti izlaz?

Točno - 12 je za jezike s leksičkim opsegom. To je zato što prvo pregledava addNumbersfunkciju (najunutarnji opseg), a zatim pretražuje prema unutra, gdje je ta funkcija definirana. Kako dobiva number2varijablu, što znači da je izlaz 12.

Možda se pitate zašto sam ovdje proveo toliko vremena na leksičkom opsegu. Ovo je članak o zatvaranju, a ne onaj o leksičkom opsegu. Ali ako ne znate o leksičkom opsegu, nećete razumjeti zatvaranja.

Zašto? Odgovor ćete dobiti kada pogledamo definiciju zatvaranja. Pa krenimo na stazu i vratimo se zatvaranju.

Što je zatvaranje?

Pogledajmo definiciju zatvaranja:

Zatvaranje se stvara kada unutarnja funkcija ima pristup svojim vanjskim varijablama funkcije i argumentima. Unutarnja funkcija ima pristup -

1. Vlastite varijable.

2. Varijable i argumenti vanjske funkcije.

3. Globalne varijable.

Čekati! Je li ovo definicija zatvaranja ili leksičkog opsega? Obje definicije izgledaju isto. Kako se razlikuju?

Pa, zato sam gore definirao leksičko opseg. Budući da su zatvaranja povezana s leksičkim / statičkim opsegom.

Pogledajmo opet njegovu drugu definiciju koja će vam reći kako se zatvaranja razlikuju.

Zatvaranje je kada funkcija može pristupiti svom leksičkom opsegu, čak i kada se ta funkcija izvršava izvan svog leksičkog opsega.

Ili,

Unutarnje funkcije mogu pristupiti svom nadređenom opsegu, čak i nakon što je nadređena funkcija već izvršena.

Zbunjen? Ne brinite ako još niste shvatili poantu. Imam primjere koji će vam pomoći da bolje razumijete. Izmijenimo prvi primjer leksičkog opsega:

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

Razlika u ovom kodu je u tome što vraćamo unutarnju funkciju i izvršavamo je kasnije. U nekim programskim jezicima lokalna varijabla postoji tijekom izvršavanja funkcije. Ali kada se funkcija izvrši, te lokalne varijable ne postoje i neće im biti dostupne.

Međutim, ovdje je scena drugačija. Nakon izvršenja nadređene funkcije, unutarnja funkcija (vraćena funkcija) i dalje može pristupiti varijablama nadređene funkcije. Da, dobro ste pogodili. Razlozi su zatvaranja.

Unutarnja funkcija zadržava svoj leksički opseg kada se nadređena funkcija izvršava, a time i kasnije, ta unutarnja funkcija može pristupiti tim varijablama.

Da bismo ga bolje osjećali, upotrijebimo dir()metodu konzole da pogledamo popis svojstava callGreetCustomer:

console.dir(callGreetCustomer);

Iz gornje slike možete vidjeti kako unutarnja funkcija čuva svoj nadređeni opseg ( customerName) kada greetCustomer()se izvršava. A kasnije se koristilo customerNamekada callGreetCustomer()je izvršeno.

Nadam se da vam je ovaj primjer pomogao da bolje razumijete gornju definiciju zatvaranja. A možda su vam zatvaranja malo zabavnija.

Pa što dalje? Učinimo ovu temu zanimljivijom gledajući različite primjere.

Primjeri zatvaranja u akciji

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

Svaki put kad pozovete countValue, vrijednost varijable count povećava se za 1. Čekajte - jeste li mislili da je vrijednost count 0?

Pa, to bi bilo pogrešno jer zatvaranje ne funkcionira s vrijednošću. Pohranjuje referencu varijable. Zbog toga se, kada ažuriramo vrijednost, ona odražava u drugom ili trećem pozivu i tako dalje dok zatvaranje pohranjuje referencu.

Sad se osjećate malo jasnije? Pogledajmo još jedan primjer:

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

Nadam se da ste pogodili točan odgovor. Ako ne, evo razloga. Kao countValue1i countValue2, oboje čuvaju vlastiti leksički opseg. Imaju neovisna leksička okruženja. U oba slučaja možete dir()provjeriti [[scopes]]vrijednost.

Pogledajmo treći primjer.

Ovaj je malo drugačiji. U nju moramo napisati funkciju da bismo postigli izlaz:

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

Jednostavan. Koristite novostečeno znanje o zatvaranju:

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

Pogledajmo sada neke škakljive primjere:

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.