Scope, of de set regels die bepalen waar uw variabelen leven, is een van de meest elementaire concepten van elke programmeertaal. Het is zelfs zo fundamenteel dat je gemakkelijk vergeet hoe subtiel de regels kunnen zijn!
Als u precies begrijpt hoe de JavaScript-engine over scope denkt, hoeft u niet de meest voorkomende bugs te schrijven die hijsen kan veroorzaken, bereidt u zich voor op het sluiten van sluitingen en komt u dichter bij het schrijven van bugs ooit nog een keer.
... Nou, het zal je hoe dan ook helpen om hijs- en sluitingen te begrijpen.
In dit artikel zullen we kijken naar:
laat
en const
verander het spelLaten we erin duiken.
Als u meer wilt weten over ES6 en hoe u de syntaxis en functies kunt gebruiken om uw JavaScript-code te verbeteren en te vereenvoudigen, kunt u deze twee cursussen bekijken:
Als je al eerder een regel JavaScript hebt geschreven, weet je dat waar jij bepalen je variabelen bepalen waar je kunt gebruik hen. Het feit dat de zichtbaarheid van een variabele afhankelijk is van de structuur van uw broncode, wordt genoemd lexicale strekking.
Er zijn drie manieren om bereik in JavaScript te creëren:
laat
of const
in een codeblok. Dergelijke verklaringen zijn alleen zichtbaar binnen het blok. vangst
blok. Geloof het of niet, dit eigenlijk doet maak een nieuwe scope!"gebruik strikt"; var mr_global = "Mr Global"; function foo () var mrs_local = "Mrs Local"; console.log ("Ik kan zien" + mr_global + "en" + mrs_local + "."); functiebalk () console.log ("Ik kan ook" + mr_global + "en" + mrs_local + "."); foo (); // Werkt zoals verwacht, probeer console.log ("But / I / can not see" + mrs_local + "."); catch (err) console.log ("Je hebt zojuist een" + err + "gekregen."); let foo = "foo"; const bar = "bar"; console.log ("Ik kan" + foo + bar + "gebruiken in zijn blok ..."); probeer console.log ("Maar niet daarbuiten."); catch (err) console.log ("Je hebt zojuist een nieuwe" + err + "gekregen."); // Throws ReferenceError! console.log ("Merk op dat" + err + "niet bestaat buiten 'catch'!")
Het bovenstaande fragment demonstreert alle drie scoopmechanismen. Je kunt het in Node of Firefox uitvoeren, maar Chrome speelt niet leuk met laat
, nog.
We zullen over elk van deze in prachtige details praten. Laten we beginnen met een gedetailleerd overzicht van de manier waarop JavaScript uitzoekt welke variabelen bij welk bereik horen.
Wanneer u JavaScript uitvoert, gebeuren er twee dingen om het te laten werken.
Gedurende de compilatie stap, de JavaScript-engine:
Het is alleen tijdens uitvoering dat de JavaScript-engine de waarde van variabele referenties eigenlijk gelijk aan hun toewijzingswaarden instelt. Tot die tijd zijn ze dat onbepaald
.
// Ik kan first_name overal in dit programma gebruiken var first_name = "Peleke"; functie popup (first_name) // Ik kan alleen achternaam gebruiken in deze functie var last_name = "Sengstacke"; alert (first_name + "+ last_name); popup (first_name);
Laten we doorlopen wat de compiler doet.
Eerst leest het de regel var first_name = "Peleke"
. Vervolgens bepaalt het wat strekking om de variabele op te slaan. Omdat we op het hoogste niveau van het script zitten, realiseren we ons dat we ons in de wereldwijde reikwijdte. Vervolgens wordt de variabele opgeslagen Voornaam
naar de globale scope en initialiseert de waarde ervan onbepaald
.
Ten tweede leest de compiler de regel mee functie popup (first_name)
. Omdat het functie keyword is het eerste ding op de regel, het creëert een nieuwe scope voor de functie, registreert de definitie van de functie in de globale scope en gluurt naar binnen om variabele declaraties te vinden.
En ja hoor, de compiler vindt er een. Omdat we hebben var last_name = "Sengstacke"
in de eerste regel van onze functie slaat de compiler de variabele op achternaam
naar de reikwijdte van pop-up
-niet naar de mondiale reikwijdte - en bepaalt de waarde ervan onbepaald
.
Omdat er binnen de functie geen variabele declaraties meer zijn, treedt de compiler terug in de globale scope. En aangezien er geen variabele declaraties meer zijn er, deze fase is voltooid.
Merk op dat we dat eigenlijk niet hebben gedaan rennen Al iets. De taak van de compiler is op dit moment alleen maar om ervoor te zorgen dat iedereen de naam kent; het maakt niet uit wat zij doen.
Op dit punt weet ons programma dat:
Voornaam
in de mondiale reikwijdte.pop-up
in de mondiale reikwijdte.achternaam
in de reikwijdte van pop-up
.Voornaam
en achternaam
zijn onbepaald
.Het maakt ons niet uit dat we die variabelenwaarden elders in onze code hebben toegewezen. Daar zorgt de motor voor uitvoering.
Tijdens de volgende stap leest de motor onze code opnieuw, maar deze keer, Voert het.
Eerst leest het de regel, var first_name = "Peleke"
. Om dit te doen, kijkt de motor naar de variabele genaamd Voornaam
. Omdat de compiler al een variabele met die naam heeft geregistreerd, vindt de engine deze en stelt hij de waarde ervan in "Peleke"
.
Vervolgens wordt de regel gelezen, functie popup (first_name)
. Omdat we dat niet zijn uitvoeren de functie hier, de motor is niet geïnteresseerd en springt er overheen.
Eindelijk, het leest de regel popup (voornaam)
. Sinds we zijn hier een functie uitvoeren, de motor:
pop-up
Voornaam
pop-up
als een functie, waarbij de waarde van wordt doorgegeven Voornaam
als een parameterWanneer het wordt uitgevoerd pop-up
, het gaat door hetzelfde proces, maar deze keer binnen de functie pop-up
. Het:
achternaam
achternaam
De waarde is gelijk aan "Sengstacke"
alarm
, het uitvoeren als een functie met "Peleke Sengstacke"
als zijn parameterBlijkt dat er veel meer onder de motorkap gebeurt dan we misschien hadden gedacht!
Nu u begrijpt hoe JavaScript de code leest en uitvoert die u schrijft, kunnen we iets dichter bij huis aanpakken: hoe hijsen werkt.
Laten we beginnen met wat code.
bar(); functiebalk () if (! foo) alert (foo + "? Dit is raar ..."); var foo = "bar"; broken (); // Typefout! var broken = function () alert ("Deze waarschuwing verschijnt niet!");
Als u deze code uitvoert, ziet u drie dingen:
foo
voordat u het toewijst, maar de waarde is onbepaald
.gebroken
voordat je het definieert, maar je krijgt een Typefout
.bar
voordat je het definieert, en het werkt zoals gewenst.Hijsen verwijst naar het feit dat JavaScript al onze gedeclareerde variabelenamen beschikbaar maakt overal in hun scopes - inclusief voor we wijzen ze toe.
De drie gevallen in het fragment zijn de drie die u in uw eigen code moet kennen, dus we zullen ze stuk voor stuk doornemen.
Onthoud dat wanneer de JavaScript-compiler een regel zoals leest var foo = "bar"
, het:
foo
naar de dichtstbijzijnde scopefoo
naar undefinedDe reden die we kunnen gebruiken foo
voordat we het toewijzen, is omdat, wanneer de motor de variabele met die naam opzoekt, het doet bestaan. Dit is waarom het niet gooit ReferenceError
.
In plaats daarvan krijgt het de waarde onbepaald
, en probeert dat te gebruiken om te doen wat je erom vroeg. Meestal is dat een fout.
Als we dat in ons achterhoofd houden, kunnen we ons voorstellen dat wat JavaScript in onze functie ziet bar
is meer als volgt:
functiebalk () var foo; // undefined if (! foo) //! undefined is true, dus alert alert (foo + "? Dit is raar ..."); foo = "bar";
Dit is de Eerste regel van hijsen, als je wilt: variabelen zijn beschikbaar binnen hun bereik, maar hebben de waarde onbepaald
totdat uw code deze toekent.
Een veelvoorkomend JavaScript-idioom is het schrijven van al uw var
verklaringen bovenaan hun toepassingsgebied, in plaats van waar u ze voor het eerst gebruikt. Om Doug Crockford te parafraseren, helpt dit je code lezen meer leuk vinden runs.
Als je erover nadenkt, is dat logisch. Het is vrij duidelijk waarom bar
gedraagt zich zoals het doet wanneer we onze code schrijven zoals JavaScript het leest, is het niet? Dus waarom niet gewoon zo schrijven allemaal de tijd?
Het feit dat we een hebben Typefout
toen we probeerden uit te voeren gebroken
voordat we het hebben gedefinieerd, is het slechts een speciaal geval van de eerste regel van hijsen.
We definieerden een variabele, genaamd gebroken
, die de compiler registreert in de globale scope en sets gelijk aan onbepaald
. Wanneer we het proberen uit te voeren, wordt de waarde van de motor opgezocht gebroken
, vindt dat het zo is onbepaald
, en probeert uit te voeren onbepaald
als een functie.
Duidelijk, onbepaald
is niet een functie - daarom krijgen we een Typefout
!
Vergeet tot slot niet dat we konden bellen bar
voordat we het definieerden. Dit komt door de Tweede regel van hijsen: Wanneer de JavaScript-compiler een functie-declaratie vindt, maakt deze beide de naam en definitie beschikbaar aan de top van de reikwijdte. Onze code opnieuw herschrijven:
functiebalk () if (! foo) alert (foo + "? Dit is raar ..."); var foo = "bar"; var gebroken; // undefined bar (); // -balk is al gedefinieerd, voert fijne onderverdeling uit (); // Can not execute undefined! broken = function () alert ("Deze waarschuwing verschijnt niet!");
Nogmaals, het is veel logischer als je schrijven als JavaScript leest, vind je niet??
Beoordelen:
onbepaald
tot opdracht.Laten we nu eens kijken naar twee nieuwe tools die een beetje anders werken: laat
en const
.
laat
, const
, & de tijdelijke dode zone anders var
aangiften, variabelen gedeclareerd met laat
en const
niet doen worden gehesen door de compiler.
Tenminste, niet precies.
Weet je nog hoe we konden bellen gebroken
, maar kreeg een Typefout
omdat we hebben geprobeerd uit te voeren onbepaald
? Als we hadden gedefinieerd gebroken
met laat
, we zouden een gekregen hebben ReferenceError
, in plaats daarvan:
"gebruik strikt"; // Je moet "strict gebruiken" om dit te proberen in Node broken (); // ReferenceError! laat broken = function () alert ("Deze waarschuwing verschijnt niet!");
Wanneer de JavaScript-compiler variabelen in hun scopes registreert tijdens de eerste doorvoer, wordt deze behandeld laat
en const
anders dan het doet var
.
Wanneer het een vindt var
verklaring, we registreren de naam van de variabele in zijn scope en initialiseren onmiddellijk de waarde ervan onbepaald
.
Met laat
, echter, de compiler doet registreer de variabele in zijn scope, maar doet nietinitialiseer de waarde ervan onbepaald
. In plaats daarvan laat het de variabele niet-geïnitialiseerd achter, tot de motor voert je opdrachtverklaring uit. Toegang tot de waarde van een niet-geïnitialiseerde variabele gooit een ReferenceError
, wat verklaart waarom het bovenstaande fragment gooit wanneer we het uitvoeren.
De ruimte tussen het begin van de bovenkant van de reikwijdte van een laat
verklaring en de toewijzingsinstructie wordt de Temporal Dead Zone. De naam komt van het feit dat, hoewel de motor weet over een variabele genaamd foo
aan de top van de scope van bar
, de variabele is "dood", omdat deze geen waarde heeft.
... Ook omdat het je programma zal doden als je het vroeg probeert te gebruiken.
De const
sleutelwoord werkt op dezelfde manier als laat
, met twee belangrijke verschillen:
const
.const
.Dit garandeert dat const
zullen altijdhebben de waarde die u in eerste instantie hebt toegewezen.
// Dit is juridische const React = vereisen ('reageren'); // Dit is helemaal geen crypto voor juridische zaken; crypto = vereisen ('crypto');
laat
en const
zijn anders dan var
op een andere manier: de grootte van hun scopes.
Wanneer u een variabele declareert bij var
, het is zichtbaar zo hoog mogelijk in de scope-keten, meestal bovenaan de dichtstbijzijnde functie-declaratie, of in de globale scope, als u deze op het hoogste niveau declareert.
Wanneer u een variabele declareert bij laat
of const
, het is echter zichtbaar als plaatselijk als mogelijk-enkel en alleen binnen het dichtstbijzijnde blok.
EEN blok is een gedeelte van de code dat wordt weergegeven door accolades, zoals u ziet met als
/anders
blokken, voor
loops, en in expliciet "geblokkeerde" stukjes code, zoals in dit fragment.
"gebruik strikt"; let foo = "foo"; if (foo) const bar = "bar"; var foobar = foo + bar; console.log ("Ik kan zien" + bar + "in dit blok."); probeer console.log ("Ik kan" + foo + "zien in dit blok, maar niet" + bar + "."); catch (err) console.log ("Je hebt een" + err + "."); probeer console.log (foo + bar); // Gooit vanwege 'foo', maar beide zijn undefined catch (err) console.log ("Je hebt zojuist een" + err + "gekregen."); console.log (foobar); // Werkt prima
Als u een variabele declareert bij const
of laat
in een blok, het is enkel en alleen zichtbaar in het blok, en enkel en alleen nadat je het hebt toegewezen.
Een variabele verklaard met var
, is echter zichtbaar zo ver mogelijk weg-in dit geval, in de globale reikwijdte.
Als je geïnteresseerd bent in de details van laat
en const
, Lees wat Dr. Rauschmayer te zeggen heeft over Exploration ES6: Variables and Scoping, en bekijk de MDN-documentatie hierover.
deze
& PijlfunctiesOp het oppervlak, deze
lijkt niet veel te maken te hebben met bereik. En eigenlijk doet JavaScript dat wel niet los de betekenis van op deze
volgens de regels van de reikwijdte waar we het hier over hebben gehad.
Tenminste, meestal niet. JavaScript, berucht, doet niet los de betekenis van de deze
sleutelwoord op basis van waar u het gebruikte:
var foo = name: 'Foo', talen: ['Spaans', 'Frans', 'Italiaans'], speak: function speak () this.languages.forEach (function (language) console.log (this. naam + "spreekt" + taal + ".");); foo.speak ();
De meesten van ons zouden verwachten deze
om te betekenen foo
binnen in de forEach
loop, want dat is precies wat het betekende. Met andere woorden, we verwachten dat JavaScript de betekenis van deze
lexicaal.
Maar dat doet het niet.
In plaats daarvan maakt het een nieuwe deze
in elke functie die u definieert en bepaalt op basis van wat deze inhoudt hoe je noemt de functie-niet waar je hebt het gedefinieerd.
Dat eerste punt is vergelijkbaar met het geval van opnieuw definiëren ieder variabele in een child scope:
function foo () var bar = "bar"; function baz () // Hergebruik van namen van variabelen zoals deze wordt "shadowing" genoemd var bar = "BAR"; console.log (bar); // BAR baz (); foo (); // BAR
Vervangen bar
met deze
, en het hele ding zou onmiddellijk moeten verdwijnen!
Traditioneel krijgen deze
werken zoals we verwachten dat gewone oude variabelen met lexicisch bereik werken, vereist een van de twee oplossingen:
var foo = name: 'Foo', talen: ['Spaans', 'Frans', 'Italiaans'], speak_self: function speak_s () var self = this; self.languages.forEach (functie (taal) console.log (self.name + "spreekt" + taal + ".");), speak_bound: function speak_b () this.languages.forEach (function (language ) console.log (this.name + "spreekt" + taal + "."); .bind (foo)); // Meer algemeen: .bind (this); ;
In speak_self
, we redden de betekenis van deze
naar de variabele zelf
, en gebruiken dat variabele om de gewenste referentie te krijgen. In speak_bound
, we gebruiken binden
naar blijvend punt deze
naar een bepaald object.
ES2015 brengt ons een nieuw alternatief: pijlfuncties.
In tegenstelling tot de "normale" functies, doen pijlfuncties dat wel niet schaduw hun bovenliggende werkingssfeer deze
waarde door hun eigen waarde in te stellen. Integendeel, ze lossen de betekenis ervan op lexicaal.
Met andere woorden, als u gebruikt deze
in een pijlfunctie zoekt JavaScript de waarde op zoals bij elke andere variabele.
Ten eerste controleert het de lokale ruimte voor een deze
waarde. Omdat de pijlfuncties er geen instellen, vindt deze er geen. Vervolgens controleert het de ouder ruimte voor een deze
waarde. Als het er een vindt, zal het dat gebruiken.
Dit laat ons de code hierboven herschrijven zoals dit:
var foo = name: 'Foo', talen: ['Spaans', 'Frans', 'Italiaans'], speak: function speak () this.languages.forEach ((language) => console.log (this .name + "spreekt" + taal + "."););
Als u meer informatie wilt over pijlfuncties, bekijk dan de uitstekende cursus van Envato Tuts + Instructeur Dan Wellman over JavaScript ES6 Fundamentals, evenals de MDN-documentatie over pijlfuncties.
Tot nu toe hebben we veel terreinen bedekt! In dit artikel heb je geleerd dat:
laat
of const
voor opdracht gooit a ReferenceError
, en dat dergelijke variabelen naar het dichtstbijzijnde blok worden geschaald.deze
, en omzeilen traditionele dynamische binding.Je hebt ook de twee regels voor hijsen gezien:
var
Verklaringen zijn overal in de bereiken beschikbaar waar ze zijn gedefinieerd, maar hebben de waarde onbepaald
tot je toewijzingsopdrachten worden uitgevoerd.Een goede volgende stap is om uw nieuwe kennis van de scopes van JavaScript te gebruiken om uw hoofd rond sluitingen te wikkelen. Kijk daarvoor eens naar de Scopes & Closures van Kyle Simpson.
Eindelijk, er valt nog veel meer te vertellen over deze
dan ik hier kon bedekken. Als het zoekwoord nog steeds zoveel zwarte magie lijkt, bekijk dan deze & Object Prototypes om je hoofd erover heen te krijgen.
Neem in de tussentijd wat u hebt geleerd en ga minder fouten schrijven!
Leer JavaScript: de complete gids
We hebben een complete handleiding samengesteld om u te helpen JavaScript te leren, of u net bent begonnen als een webontwikkelaar of dat u meer geavanceerde onderwerpen wilt verkennen.