Closures – velen van jullie JavaScript-ontwikkelaars hebben deze term waarschijnlijk al eens gehoord. Toen ik mijn reis met JavaScript begon, kwam ik closures vaak tegen. En ik denk dat het een van de belangrijkste en interessantste concepten in JavaScript is.
Denk je dat ze niet interessant zijn? Dat gebeurt vaak als je een concept niet begrijpt – je vindt het niet interessant. (Ik weet niet of dit bij jou gebeurt of niet, maar bij mij is dit het geval).
Dus in dit artikel zal ik proberen closures interessant voor je te maken.
Voordat we in de wereld van closures duiken, eerst maar eens lexical scoping begrijpen. Als je dat al weet, sla dan het volgende deel over.
Lexical Scoping
Je denkt misschien – ik ken local en global scope, maar wat is lexical scope in hemelsnaam? Ik reageerde ook zo toen ik deze term hoorde. Maak je geen zorgen! Laten we het eens van dichterbij bekijken.
Het is eenvoudig, net als de andere twee scopes:
function greetCustomer() { var customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); // Hi! anchal } greetingMsg();}
U kunt in de bovenstaande uitvoer zien dat de binnenste functie toegang heeft tot de variabele van de buitenste functie. Dit is lexical scoping, waarbij het bereik en de waarde van een variabele wordt bepaald door waar deze is gedefinieerd/aangemaakt (dat wil zeggen, zijn positie in de code). Snap je het?
Ik weet dat dat laatste je misschien in de war heeft gebracht. Dus laat me wat dieper op de zaak ingaan. Wist je dat lexical scoping ook bekend staat als static scoping? Ja, dat is de andere naam.
Er is ook dynamische scoping, die door sommige programmeertalen wordt ondersteund. Waarom heb ik dynamische scoping genoemd? Omdat het je kan helpen om lexical scoping beter te begrijpen.
Laten we eens kijken naar een paar voorbeelden:
function greetingMsg() { console.log(customerName);// ReferenceError: customerName is not defined}function greetCustomer() { var customerName = "anchal"; greetingMsg();}greetCustomer();
Ben je het eens met de uitvoer? Ja, het zal een referentiefout geven. Dat komt omdat beide functies geen toegang hebben tot elkaars scope, omdat ze apart zijn gedefinieerd.
Laten we eens kijken naar een ander voorbeeld:
function addNumbers(number1) { console.log(number1 + number2);}function addNumbersGenerate() { var number2 = 10; addNumbers(number2);}addNumbersGenerate();
De bovenstaande uitvoer zal 20 zijn voor een dynamisch scoped taal. Talen die lexical scoping ondersteunen zullen referenceError: number2 is not defined
geven. Waarom?
Omdat bij dynamische scoping eerst in de lokale functie wordt gezocht, daarna in de functie die de lokale functie heeft aangeroepen. Dan wordt er gezocht in de functie die die functie heeft aangeroepen, enzovoort, de hele aanroepstack door.
De naam spreekt voor zich – “dynamisch” betekent verandering. Het bereik en de waarde van een variabele kunnen verschillen, omdat het afhangt van waar de functie wordt aangeroepen. De betekenis van een variabele kan in runtime veranderen.
Begrijp je de essentie van dynamische scoping? Zo ja, onthoud dan dat lexical scoping het tegenovergestelde is.
In lexical scoping wordt eerst gezocht in de lokale functie, dan in de functie waarbinnen die functie is gedefinieerd. Dan wordt er gezocht in de functie waarbinnen die functie is gedefinieerd, enzovoort.
Dus, lexicale of statische scoping betekent dat het bereik en de waarde van een variabele wordt bepaald door waar deze is gedefinieerd. Het verandert niet.
Laten we nog eens naar bovenstaand voorbeeld kijken en proberen zelf de uitvoer te achterhalen. Maar één draai – declareer number2
bovenaan:
var number2 = 2;function addNumbers(number1) { console.log(number1 + number2);}function addNumbersGenerate() { var number2 = 10; addNumbers(number2);}addNumbersGenerate();
Weet je wat de uitvoer zal zijn?
Correct – het is 12 voor lexically scoped talen. Dat komt omdat het eerst kijkt in een addNumbers
functie (binnenste bereik) dan zoekt het naar binnen, waar deze functie is gedefinieerd. Daar krijgt het de number2
variabele, wat betekent dat de output 12 is.
Je vraagt je misschien af waarom ik hier zoveel tijd heb besteed aan lexical scoping. Dit is een afsluitend artikel, niet een over lexical scoping. Maar als je niets weet over lexical scoping, dan zul je closures ook niet begrijpen.
Waarom? U krijgt uw antwoord als we naar de definitie van een closure kijken. Dus laten we het spoor bijster raken en teruggaan naar closures.
Wat is een Closure?
Laten we eens kijken naar de definitie van een closure:
Closure ontstaat wanneer een binnenste functie toegang heeft tot zijn buitenste functie variabelen en argumenten. De binnenste functie heeft toegang tot –
1. Haar eigen variabelen.
2. Variabelen en argumenten van de buitenste functie.
3. Globale variabelen.
Wacht! Is dit de definitie van een closure of van lexical scoping? Beide definities zien er hetzelfde uit. Hoe zijn ze verschillend?
Wel, dat is waarom ik lexical scoping hierboven heb gedefinieerd. Omdat closures gerelateerd zijn aan lexical/static scoping.
Laten we nog eens kijken naar de andere definitie die je zal vertellen hoe closures verschillend zijn.
Closure is wanneer een functie in staat is om toegang te krijgen tot zijn lexical scope, zelfs als die functie buiten zijn lexical scope aan het uitvoeren is.
Of,
Inner-functies kunnen toegang krijgen tot hun bovenliggende bereik, zelfs als de bovenliggende functie al is uitgevoerd.
Verbaasd? Maak je geen zorgen als je het nog niet begrepen hebt. Ik heb voorbeelden om je te helpen het beter te begrijpen. Laten we het eerste voorbeeld van lexical scoping aanpassen:
function greetCustomer() { const customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); } return greetingMsg;}const callGreetCustomer = greetCustomer();callGreetCustomer(); // output – Hi! anchal
Het verschil in deze code is dat we de binnenste functie teruggeven en later uitvoeren. In sommige programmeertalen bestaat de lokale variabele tijdens de uitvoering van de functie. Maar als de functie eenmaal is uitgevoerd, bestaan die lokale variabelen niet meer en zijn ze niet meer toegankelijk.
Hier, echter, is de scène anders. Nadat de bovenliggende functie is uitgevoerd, kan de binnenliggende functie (teruggekeerde functie) nog steeds toegang krijgen tot de variabelen van de bovenliggende functie. Ja, je hebt het goed geraden. Closures zijn de reden.
De binnenste functie behoudt zijn lexicale bereik als de bovenliggende functie wordt uitgevoerd en dus kan die binnenste functie later bij die variabelen.
Om er een beter gevoel bij te krijgen, gebruiken we de dir()
methode van de console om in de lijst van de eigenschappen van callGreetCustomer
te kijken:
console.dir(callGreetCustomer);
Van de bovenstaande afbeelding, kunt u zien hoe de binnenste functie zijn bovenliggende scope behoudt (customerName
) wanneer greetCustomer()
wordt uitgevoerd. En later gebruikt het customerName
wanneer callGreetCustomer()
wordt uitgevoerd.
Ik hoop dat dit voorbeeld je heeft geholpen om de bovenstaande definitie van een closure beter te begrijpen. En misschien vind je closures nu een beetje leuker.
Dus wat nu? Laten we dit onderwerp interessanter maken door naar verschillende voorbeelden te kijken.
Voorbeelden van closures in actie
function counter() { let count = 0; return function() { return count++; };}const countValue = counter();countValue(); // 0countValue(); // 1countValue(); // 2
Iedere keer dat je countValue
aanroept, wordt de waarde van de variabele count verhoogd met 1. Wacht – dacht je dat de waarde van count 0 is?
Wel, dat zou fout zijn, want een closure werkt niet met een waarde. Het slaat de referentie van de variabele op. Dat is de reden waarom, wanneer we de waarde bijwerken, het weerspiegelt in de tweede of derde oproep en ga zo maar door, omdat de sluiting de referentie opslaat.
Heb je nu een duidelijker gevoel? Laten we eens kijken naar een ander voorbeeld:
function counter() { let count = 0; return function () { return count++; };}const countValue1 = counter();const countValue2 = counter();countValue1(); // 0countValue1(); // 1countValue2(); // 0countValue2(); // 1
Ik hoop dat je het goede antwoord hebt geraden. Zo niet, dan is hier de reden. Als countValue1
en countValue2
, behouden beide hun eigen lexicale bereik. Ze hebben onafhankelijke lexicale omgevingen. U kunt dir()
gebruiken om de ]
waarde in beide gevallen te controleren.
Laten we eens kijken naar een derde voorbeeld.
Deze is een beetje anders. In dit voorbeeld moeten we een functie schrijven om de uitvoer te bereiken:
const addNumberCall = addNumber(7);addNumberCall(8) // 15addNumberCall(6) // 13
Eenvoudig. Gebruik je nieuw verworven afsluitkennis:
function addNumber(number1) { return function (number2) { return number1 + number2; };}
Nu wat lastige voorbeelden:
function countTheNumber() { var arrToStore = ; for (var x = 0; x < 9; x++) { arrToStore = function () { return x; }; } return arrToStore;}const callInnerFunctions = countTheNumber();callInnerFunctions() // 9callInnerFunctions() // 9
Elk array-element dat een functie opslaat, geeft je een output van 9. Heb je het goed geraden? Ik hoop het, maar laat me je toch de reden vertellen. Dit komt door het gedrag van de closure.
De closure slaat de referentie op, niet de waarde. De eerste keer dat de loop loopt, is de waarde van x 0. Dan de tweede keer is x 1, enzovoort. Omdat de closure de referentie opslaat, verandert elke keer dat de lus loopt de waarde van x. En uiteindelijk zal de waarde van x 9 zijn. Dus callInnerFunctions()
geeft een output van 9.
Maar wat als je een output van 0 tot 8 wilt? Simpel! Gebruik een afsluiting.
Denk er eens over na voordat je naar onderstaande oplossing kijkt:
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
Hier hebben we voor elke iteratie een aparte scope gemaakt. Je kunt console.dir(arrToStore)
gebruiken om de waarde van x in ]
voor verschillende array-elementen te controleren.
Dat was het! Ik hoop dat je nu kunt zeggen dat je closures interessant vindt.
Om mijn andere artikelen te lezen, kijk hier op mijn profiel.