Scope en sluitingen

In JavaScript is bereik de context waarin code wordt uitgevoerd. Er zijn drie soorten reikwijdte: globaal bereik, lokaal bereik (soms ook wel "functiemaximum" genoemd) en eval scope.

Code gedefinieerd met var binnen een functie is lokaal scoped, en is alleen "zichtbaar" voor andere uitdrukkingen in die functie, die code bevat binnen alle geneste / onderliggende functies. Variabelen gedefinieerd in het globale bereik zijn overal toegankelijk, omdat dit het hoogste niveau en de laatste halte in de consolidatieketen is.

Bestudeer de code die volgt en zorg ervoor dat u begrijpt dat elke verklaring van foo is uniek vanwege de reikwijdte.

Voorbeeld: sample110.html

 

Zorg dat je dat begrijpt foo variabele bevat een andere waarde omdat elke waarde wordt gedefinieerd in een specifiek afgebakende scope.

Een onbeperkt aantal functies en eval scopes kunnen worden gemaakt, terwijl slechts één globaal bereik door een JavaScript-omgeving wordt gebruikt.

De wereldwijde scope is de laatste stop in de scope-keten.

Functies die functies bevatten, maken gestapelde uitvoeringsscopes. Deze stapels, die aan elkaar worden geketend, worden vaak de scopeketen genoemd.


JavaScript heeft geen block scope

Sinds logische verklaringen (als) en looping statements (voor) geen bereik maken, variabelen kunnen elkaar overschrijven. Bestudeer de volgende code en zorg ervoor dat u begrijpt dat de waarde van foo wordt opnieuw gedefinieerd wanneer het programma de code uitvoert.

Voorbeeld: sample111.html

 

Zo foo wordt gewijzigd terwijl de code wordt uitgevoerd, omdat JavaScript geen functie met alleen het bereik Blokkering, Globaal of EVal heeft.


Gebruik var Binnenkant van functies om variabelen te declareren en scopes te vermijden Gotchas

JavaScript declareert alle variabelen zonder a var aangifte (zelfs die in een functie of ingekapselde functies) om in de globale scope te zijn in plaats van de beoogde lokale scope. Bekijk de code die volgt en merk dat zonder het gebruik van var om de balk te declareren, wordt de variabele feitelijk gedefinieerd in de globale scope en niet de lokale scope, waar deze zou moeten zijn.

Voorbeeld: sample112.html

 

Het concept om hier weg te nemen is dat je altijd moet gebruiken var bij het definiëren van variabelen binnen een functie. Dit voorkomt dat u te maken krijgt met potentieel verwarrende scopeproblemen. De uitzondering op deze conventie is natuurlijk wanneer u vanuit een functie eigenschappen in het globale bereik wilt maken of wijzigen.


The Scope Chain (ook bekend als Lexical Scoping)

Er is een opzoekketen die wordt gevolgd wanneer JavaScript zoekt naar de waarde die aan een variabele is gekoppeld. Deze keten is gebaseerd op de hiërarchie van de reikwijdte. In de code die volgt, registreer ik de waarde van sayHiText van de func2 functiebereik.

Voorbeeld: sample113.html

 

Hoe is de waarde van sayHiText gevonden wanneer het niet binnen de reikwijdte van de func2 functie? JavaScript ziet er eerst in de func2 functie voor een variabele met de naam sayHiText. Niet vinden func2 daar kijkt het naar op func2s ouderfunctie, func1. De sayHiText variabele is niet gevonden in de func1 bereik, dus, dan gaat JavaScript verder tot het wereldwijde bereik waar sayHiText wordt gevonden, op welk punt de waarde van sayHiText is geleverd. Als sayHiText was niet gedefinieerd in de globale reikwijdte, onbepaald zou zijn geretourneerd door JavaScript.

Dit is een heel belangrijk begrip om te begrijpen. Laten we een ander codevoorbeeld onderzoeken, een waarin we drie waarden uit drie verschillende bereiken pakken.

Voorbeeld: sample114.html

 

De waarde voor z is lokaal voor de bar functie en de context waarin de console.log wordt aangeroepen. De waarde voor Y is in de foo functie, die de ouder is van bar(), en de waarde voor X is in de mondiale reikwijdte. Al deze zijn toegankelijk voor de bar functie via de scopeketen. Zorg ervoor dat u begrijpt dat verwijzingsvariabelen in de bar De functie controleert de scopeketen helemaal naar de variabelen waarnaar wordt verwezen.

De reikwijdte, als je erover nadenkt, is niet zo verschillend van de prototypeketen. Beide zijn eenvoudigweg een manier om een ​​waarde op te zoeken door een systematische en hiërarchische reeks locaties te controleren.


De Scope Chain Lookup Retourneert de eerste gevonden waarde

In het codevoorbeeld dat volgt, wordt een variabele genoemd X bestaat in hetzelfde bereik als waarin het wordt onderzocht console.log. Deze "lokale" waarde van X wordt gebruikt, en je zou kunnen zeggen dat het schaduwen of maskers met dezelfde naam draagt X variabelen die verder in de consolidatieketen zijn gevonden.

Voorbeeld: sample115.html

 

Onthoud dat de zoekopdracht naar het bereik eindigt wanneer de variabele wordt gevonden in de dichtstbijzijnde beschikbare link van de keten, zelfs als dezelfde variabelenaam verderop in de keten wordt gebruikt.


Scope wordt bepaald tijdens functiedefinitie, niet aanroeping

Omdat functies het bereik bepalen en functies kunnen worden doorgegeven, net zoals elke JavaScript-waarde, zou men kunnen denken dat het ontcijferen van de scopeketen ingewikkeld is. Het is eigenlijk heel simpel. De scopeketen wordt bepaald op basis van de locatie van een functie tijdens de definitie, niet tijdens de aanroeping. Dit wordt ook lexicale scoping genoemd. Denk hier lang en hard over na, omdat de meeste mensen er vaak in JavaScript-code over struikelen.

De scopeketen wordt gemaakt voordat u een functie aanroept. Hierdoor kunnen we sluitingen maken. We kunnen bijvoorbeeld een functie een geneste functie laten retourneren naar het globale bereik, maar onze functie kan via de bereikketen nog steeds de scope van de bovenliggende functie openen. In het volgende voorbeeld definiëren we een parentFunction die een anonieme functie retourneert en we roepen de geretourneerde functie van de globale scope. Omdat onze anonieme functie werd gedefinieerd als onderdeel van parentFunction, het heeft nog steeds toegang tot parentFunctions bereik wanneer het wordt opgeroepen. Dit wordt een afsluiting genoemd.

Voorbeeld: sample116.html

 

Het idee dat je hier moet wegnemen is dat de scopeketen tijdens de definitie letterlijk wordt bepaald op de manier waarop de code is geschreven. Als u functies in uw code doorgeeft, verandert dit niet de reikwijdte.


Sluitingen worden veroorzaakt door de Scope-keten

Neem wat u hebt geleerd over de bereikketen en de scope-lookup van dit artikel, en een afsluiting moet niet overdreven ingewikkeld zijn om te begrijpen. In het volgende voorbeeld maken we een functie genaamd countUpFromZero. Deze functie retourneert in feite een verwijzing naar de onderliggende functie die erin is opgenomen. Wanneer deze onderliggende functie (geneste functie) wordt aangeroepen, heeft deze nog steeds toegang tot het bereik van de bovenliggende functie vanwege de bereikketen.

Voorbeeld: sample117.html

 

Elke keer dat de countUpFromZero functie wordt aangeroepen, de anonieme functie vervat in (en teruggestuurd van) de countUpFromZero functie heeft nog steeds toegang tot het bereik van de ouderfunctie. Deze techniek, gefaciliteerd via de scopeketen, is een voorbeeld van een afsluiting.


Conclusie

Als je denkt dat ik te veel vereenvoudigde sluitingen heb, heb je waarschijnlijk gelijk in deze gedachte. Maar ik deed dit met opzet omdat ik geloof dat de belangrijke delen voortkomen uit een goed begrip van functies en reikwijdte, niet noodzakelijk de complexiteit van de uitvoeringscontext. Als u behoefte heeft aan een grondige duik in sluitingen, bekijk dan JavaScript-sluitingen.