Closures in JavaScript zijn een van die concepten waar velen moeite mee hebben. In het volgende artikel zal ik in duidelijke bewoordingen uitleggen wat een closure is, en ik zal het punt naar huis brengen met behulp van eenvoudige codevoorbeelden.
Een closure is een functie in JavaScript waarbij een binnenste functie toegang heeft tot de variabelen van de buitenste (omsluitende) functie – een scope chain.
De closure heeft drie scope chains:
- hij heeft toegang tot zijn eigen scope – variabelen gedefinieerd tussen de accolades
- hij heeft toegang tot de variabelen van de buitenste functie
- hij heeft toegang tot de globale variabelen
Voor niet-ingewijden lijkt deze definitie misschien alleen maar een hoop jargon!
Maar wat is een closure eigenlijk?
Laten we eens kijken naar een eenvoudig closure-voorbeeld in JavaScript:
function outer() { var b = 10;
function inner() {
var a = 20;
console.log(a+b);
}
return inner;
}
Hier hebben we twee functies:
- een buitenste functie
outer
die een variabeleb
heeft, en deinner
functie teruggeeft - een binnenfunctie
inner
die een variabelea
heeft, en toegang heeft tot eenouter
variabeleb
, binnen zijn functielichaam
Het bereik van variabele b
is beperkt tot de outer
functie, en de reikwijdte van variabele a
is beperkt tot de inner
functie.
Laten we nu de functie outer()
aanroepen, en het resultaat van de functie outer()
in een variabele X
opslaan. Laten we vervolgens de outer()
functie een tweede keer aanroepen en opslaan in variabele Y
.
Laten we stap-voor-stap zien wat er gebeurt als de outer()
functie voor het eerst wordt aangeroepen:
- Variabele
b
wordt aangemaakt, de reikwijdte ervan wordt beperkt tot deouter()
functie, en de waarde ervan wordt ingesteld op10
. - De volgende regel is een functie-declaratie, dus niets om uit te voeren.
- Op de laatste regel zoekt
return inner
naar een variabele genaamdinner
, vindt dat deze variabeleinner
eigenlijk een functie is, en retourneert dus de hele body van de functieinner
. - De inhoud die wordt teruggegeven door de return-instructie wordt opgeslagen in
X
.
Dus,X
slaat het volgende op:function inner() {
var a=20;
console.log(a+b);
} - De functie
outer()
is klaar met uitvoeren, en alle variabelen binnen de scope vanouter()
bestaan nu niet meer.
Dit laatste deel is belangrijk om te begrijpen. Zodra een functie zijn uitvoering heeft voltooid, houden alle variabelen die binnen het functiebereik zijn gedefinieerd op te bestaan.
De levensduur van een variabele die binnen een functie is gedefinieerd, is de levensduur van de uitvoering van de functie.
Wat dit betekent is dat in console.log(a+b)
, de variabele b
alleen bestaat tijdens de uitvoering van de outer()
functie. Zodra de uitvoering van de outer
functie is voltooid, bestaat de variabele b
niet meer.
Wanneer de functie de tweede keer wordt uitgevoerd, worden de variabelen van de functie opnieuw aangemaakt, en blijven slechts bestaan tot de uitvoering van de functie is voltooid.
Dus, wanneer outer()
voor de tweede keer wordt aangeroepen:
- Een nieuwe variabele
b
wordt aangemaakt, de reikwijdte ervan wordt beperkt tot deouter()
functie, en de waarde ervan wordt ingesteld op10
. - De volgende regel is een functie-declaratie, dus niets om uit te voeren.
-
return inner
retourneert de gehele body van de functieinner
. - De inhoud die door het return statement wordt geretourneerd, wordt opgeslagen in
Y
. - De functie
outer()
is klaar met uitvoeren, en alle variabelen binnen de scope vanouter()
bestaan nu niet meer.
Het belangrijke punt hier is dat wanneer de outer()
functie voor de tweede keer wordt aangeroepen, de variabele b
opnieuw wordt aangemaakt. Ook wanneer de outer()
functie de tweede keer wordt uitgevoerd, houdt deze nieuwe variabele b
weer op te bestaan.
Dit is het belangrijkste punt om te beseffen. De variabelen in de functies ontstaan alleen als de functie wordt uitgevoerd, en houden op te bestaan zodra de functie is voltooid.
Nu gaan we terug naar ons codevoorbeeld en kijken naar X
en Y
. Aangezien de outer()
functie bij uitvoering een functie teruggeeft, zijn de variabelen X
en Y
functies.
Dit kan eenvoudig worden geverifieerd door het volgende aan de JavaScript-code toe te voegen:
console.log(typeof(X)); //X is of type function
console.log(typeof(Y)); //Y is of type function
Omdat de variabelen X
en Y
functies zijn, kunnen we ze uitvoeren. In JavaScript kan een functie worden uitgevoerd door ()
achter de functienaam te zetten, zoals X()
en Y()
.
Wanneer we X()
en Y()
uitvoeren, voeren we in wezen de inner
functie uit.
Laten we stap-voor-stap bekijken wat er gebeurt als X()
de eerste keer wordt uitgevoerd:
- Variabele
a
wordt aangemaakt, en de waarde ervan wordt ingesteld op20
. - JavaScript probeert nu
a + b
uit te voeren. Hier wordt het interessant. JavaScript weet data
bestaat omdat het het zojuist gemaakt heeft. De variabeleb
bestaat echter niet meer. Aangezienb
deel uitmaakt van de buitenste functie, zoub
alleen bestaan terwijl deouter()
functie in uitvoering is. Aangezien deouter()
functie klaar is met uitvoeren lang voordat weX()
aanroepen, houden alle variabelen binnen de scope van deouter
functie op te bestaan, en dus bestaat variabeleb
niet meer.
Hoe gaat JavaScript hiermee om?
Closures
De inner
functie kan toegang krijgen tot de variabelen van de omsluitende functie door closures in JavaScript. Met andere woorden, de inner
functie behoudt de scope chain van de enclosing functie op het moment dat de enclosing functie werd uitgevoerd, en heeft dus toegang tot de variabelen van de enclosing functie.
In ons voorbeeld had de inner
functie de waarde van b=10
behouden toen de outer()
functie werd uitgevoerd, en bleef deze behouden (sluiten).
Het verwijst nu naar zijn scope chain en merkt op dat het de waarde van variabele b
binnen zijn scope chain heeft, omdat het de waarde van b
binnen een closure had ingesloten op het moment dat de outer
functie was uitgevoerd.
Dus, JavaScript kent a=20
en b=10
, en kan a+b
berekenen.
U kunt dit verifiëren door de volgende regel code aan het bovenstaande voorbeeld toe te voegen:
Open het Inspect element in Google Chrome en ga naar de Console. U kunt het element uitbreiden om daadwerkelijk het Closure
element te zien (weergegeven in de op twee na laatste regel hieronder). Merk op dat de waarde van b=10
behouden blijft in de Closure
zelfs nadat de outer()
functie zijn uitvoering heeft voltooid.
Laten we nu de definitie van closures die we aan het begin zagen nog eens bekijken en zien of het nu logischer is.
Dus de binnenste functie heeft drie bereikketens:
- toegang tot zijn eigen scope – variabele
a
- toegang tot de
outer
variabelen van de functie – variabeleb
, die het omsloot - toegang tot alle globale variabelen die kunnen worden gedefinieerd
Closures in actie
Om het punt van closures duidelijk te maken, laten we het voorbeeld eens uitbreiden met drie regels code:
Wanneer u deze code uitvoert, ziet u de volgende uitvoer in de console.log
:
a=20 b=10
a=20 b=11
a=20 b=12
a=20 b=10
Laten we deze code eens stap-voor-stap bekijken om te zien wat er precies gebeurt en om closures in actie te zien!
var X = outer(); // outer() invoked the first time
De functie outer()
wordt de eerste keer aangeroepen. De volgende stappen vinden plaats:
- Variabele
b
wordt aangemaakt, en wordt ingesteld op10
Variabelec
wordt aangemaakt, en ingesteld op100
Laten we dezeb(first_time)
enc(first_time)
noemen voor onze eigen referentie. - De
inner
functie wordt teruggegeven en toegewezen aanX
Op dit punt, is de variabeleb
ingesloten binnen deinner
functie scope keten als een sluiting metb=10
, aangezieninner
de variabeleb
gebruikt. - De functie
outer
voltooit de uitvoering, en al haar variabelen houden op te bestaan. De variabelec
bestaat niet meer, hoewel de variabeleb
als een afsluiting binneninner
bestaat.
var Y= outer(); // outer() invoked the second time
- Variabele
b
wordt opnieuw aangemaakt en wordt ingesteld op10
Variabelec
wordt opnieuw aangemaakt.
Merk op dat hoewelouter()
één keer is uitgevoerd voordat de variabelenb
enc
ophielden te bestaan, zij na uitvoering van de functie zijn aangemaakt als gloednieuwe variabelen.
Laten we dezeb(second_time)
enc(second_time)
noemen voor onze eigen referentie. - De
inner
functie wordt teruggegeven en toegewezen aanY
Op dit punt wordt de variabeleb
ingesloten binnen deinner
functie scope keten als een sluiting metb(second_time)=10
, aangezieninner
de variabeleb
gebruikt. - De
outer
functie voltooit de uitvoering, en al zijn variabelen houden op te bestaan.
De variabelec(second_time)
bestaat niet meer, hoewel de variabeleb(second_time)
bestaat als afsluiting binneninner
.
Nu gaan we eens kijken wat er gebeurt als de volgende regels code worden uitgevoerd:
X(); // X() invoked the first time
X(); // X() invoked the second time
X(); // X() invoked the third timeY(); // Y() invoked the first time
Wanneer X()
de eerste keer wordt aangeroepen,
- variabele
a
wordt aangemaakt, en ingesteld op20
- de waarde van
a=20
, de waarde vanb
is van de sluitingswaarde.b(first_time)
, dusb=10
- variabelen
a
enb
worden opgehoogd met1
-
X()
voltooit de uitvoering en al zijn binnenste variabelen – variabelea
– houden op te bestaan.
Hoewel,b(first_time)
is bewaard gebleven als afsluiting, dusb(first_time)
blijft bestaan.
Wanneer X()
de tweede keer wordt aangeroepen,
- variabele
a
wordt opnieuw aangemaakt, en ingesteld op20
Een eventuele eerdere waarde van variabelea
bestaat niet meer, omdat deze ophield te bestaan toenX()
de uitvoering de eerste keer voltooide. - de waarde van
a=20
de waarde vanb
is overgenomen van de sluitingswaarde –b(first_time)
Merk ook op dat we de waarde vanb
hadden verhoogd met1
van de vorige uitvoering, dusb=11
- variabelen
a
enb
worden verhoogd met1
opnieuw -
X()
voltooit de uitvoering en al zijn binnenste variabelen – variabele a – houden op te bestaan
Hoewel,b(first_time)
blijft bewaard omdat de afsluiting blijft bestaan.
Wanneer X()
voor de derde keer wordt aangeroepen,
- variabele
a
wordt opnieuw aangemaakt, en ingesteld op20
Een eventuele eerdere waarde van variabelea
bestaat niet meer, omdat deze ophield te bestaan toenX()
de uitvoering de eerste keer voltooide. - de waarde van
a=20
, de waarde vanb
is van de sluitingswaarde –b(first_time)
Merk ook op dat we de waarde vanb
hadden verhoogd met1
bij de vorige uitvoering, dusb=12
- variabelen
a
enb
worden door1
weer verhoogd -
X()
voltooit de uitvoering en al zijn binnenste variabelen – variabelea
– houden op te bestaan
Hetb(first_time)
blijft echter behouden omdat de closure blijft bestaan
Wanneer Y() voor de eerste keer wordt aangeroepen,
- variabele
a
wordt opnieuw aangemaakt, en ingesteld op20
- de waarde van
a=20
, de waarde vanb
is van de sluitingswaarde –b(second_time)
, dusb=10
- variabelen
a
enb
worden verhoogd met1
-
Y()
voltooit de uitvoering, en al zijn binnenste variabelen – variabelea
– houden op te bestaan
Hetb(second_time)
is echter bewaard gebleven als afsluiting, dusb(second_time)
blijft bestaan.
Slotopmerkingen
Sluitingen zijn een van die subtiele concepten in JavaScript die in het begin moeilijk te begrijpen zijn. Maar als je ze eenmaal begrijpt, realiseer je je dat het niet anders had gekund.
Hopelijk heeft deze stap-voor-stap uitleg je geholpen om het concept van closures in JavaScript echt te begrijpen!
Andere artikelen:
- Een snelle gids voor “self invoking” functies in Javascript
- Het begrijpen van Functie scope vs. Blok scope in Javascript
- Hoe gebruik je Beloftes in JavaScript
- Hoe bouw je een eenvoudige Sprite animatie in JavaScript