Räckvidd, eller uppsättningen regler som bestämmer var dina variabler bor, är ett av de mest grundläggande begreppen i vilket programmeringsspråk som helst. Det är så grundläggande faktiskt att det är lätt att glömma hur subtila reglerna kan vara!
Förstå exakt hur JavaScript-motorn "tänker" om omfattning kommer att hålla dig från att skriva de vanliga buggarna som hissar kan orsaka, förbereda dig för att sätta på huvudet runt stängningar och få dig så mycket närmare att du aldrig skriver buggar någonsin igen.
... Tja, det hjälper dig att förstå hiss och stängningar, hur som helst.
I den här artikeln tar vi en titt på:
låta
och const
ändra speletLåt oss dyka in.
Om du är intresserad av att lära dig mer om ES6 och hur du utnyttjar syntaxen och funktionerna för att förbättra och förenkla JavaScript-koden, varför inte kolla in dessa två kurser:
Om du har skrivit en jämn linje av JavaScript innan vet du det var du definiera Dina variabler bestämmer var du kan använda sig av dem. Det faktum att en variabel sikt beror på strukturen i din källkod heter lexikalisk omfattning.
Det finns tre sätt att skapa utrymme i JavaScript:
låta
eller const
inuti ett kodblock. Sådana deklarationer är bara synliga inuti blocket. fånga
blockera. Tro det eller inte, det här faktiskt gör skapa ett nytt omfång!"använd strikt"; var mr_global = "Mr Global"; funktion foo () var mrs_local = "Mrs Local"; console.log ("Jag kan se" + mr_global + "och" + mrs_local + "."); funktionsfältet () console.log ("Jag kan också se" + mr_global + "och" + mrs_local + "."); foo (); // fungerar som förväntat försök console.log ("men / jag / kan inte se" + mrs_local + "."); fånga (err) console.log ("Du har bara en" + err + "."); let foo = "foo"; const bar = "bar"; console.log ("Jag kan använda" + foo + bar + "i dess block ..."); försök console.log ("men inte utanför den."); fånga (err) console.log ("Du har bara en annan" + err + "."); // Kasta ReferenceError! console.log ("Observera att" + err + "existerar inte utanför" catch "!")
Biten ovan visar alla tre räckviddsmekanismer. Du kan köra det i Node eller Firefox, men Chrome spelar inte bra med låta
, än.
Vi pratar om var och en i utsökta detaljer. Låt oss börja med en detaljerad titt på hur JavaScript anger vilka variabler som hör till vilket omfattning.
När du kör en bit av JavaScript råkar det hända att två saker gör att det fungerar.
Under de kompilering steg, JavaScript-motorn:
Det är bara under avrättning att JavaScript-motorn faktiskt anger värdet av variabla referenser lika med deras uppdelningsvärden. Fram till dess är de odefinierad
.
// Jag kan använda förnamn någonstans i det här programmet var first_name = "Peleke"; funktion popup (first_name) // Jag kan bara använda sista namnet inuti denna funktion var last_name = "Sengstacke"; alert (first_name + "+ last_name); popup (first_name);
Låt oss gå igenom vad kompilatorn gör.
Först läser den linjen var first_name = "Peleke"
. Därefter bestämmer det vad omfattning för att spara variabeln till. Eftersom vi är på toppen av skriptet inser vi att vi är i globalt räckvidd. Sedan sparar den variabeln förnamn
till det globala räckviddet och initierar sitt värde till odefinierad
.
För det andra läser kompilatorn linjen med funktion popup (first_name)
. Eftersom det fungera sökord är det första på linjen, det skapar ett nytt utrymme för funktionen, registrerar funktionens definition till det globala räckviddet och tittar inuti för att hitta variabla deklarationer.
Visst nog, hittar kompilatorn en. Eftersom vi har var last_name = "Sengstacke"
I den första raden av vår funktion sparar kompilatorn variabeln efternamn
till omfattning av dyka upp
-inte till det globala räckviddet - och sätter sitt värde till odefinierad
.
Eftersom det inte finns några variabla deklarationer i funktionen, går kompilatorn tillbaka i det globala räckviddet. Och eftersom det inte finns några variabla deklarationer där, denna fas är klar.
Observera att vi inte har faktiskt springa någonting ännu. Kompilatörens jobb vid denna punkt är bara för att se till att det känner till alla namn; det bryr sig inte om det Vad dom gör.
Vid detta tillfälle vet vårt program att:
förnamn
i det globala räckviddet.dyka upp
i det globala räckviddet.efternamn
inom ramen för dyka upp
.förnamn
och efternamn
är odefinierad
.Det bryr sig inte om att vi har tilldelat dessa variabler värden någon annanstans i vår kod. Motorn tar hand om det i avrättning.
Under nästa steg läser motorn vår kod igen, men den här gången, exekverar Det.
Först läser den linjen, var first_name = "Peleke"
. För att göra detta ser motorn upp den variabel som heter förnamn
. Eftersom kompilatorn redan har registrerat en variabel med det namnet, hittar motorn det och ställer in sitt värde till "Peleke"
.
Därefter läser den linjen, funktion popup (first_name)
. Eftersom vi inte är det exekvera funktionen här är motorn inte intresserad och hoppar över den.
Slutligen läser den linjen popup (first_name)
. Eftersom vi är utför en funktion här, motorn:
dyka upp
förnamn
dyka upp
som en funktion, som passerar värdet av förnamn
som en parameterNär det körs dyka upp
, Det går igenom samma process, men den här gången inuti funktionen dyka upp
. Det:
efternamn
efternamn
s värde lika med "Sengstacke"
varna
, exekvera det som en funktion med "Peleke Sengstacke"
som dess parameterDet visar sig mycket mer under kåpan än vi kanske trodde!
Nu när du förstår hur JavaScript läser och kör koden du skriver, är vi redo att ta itu med något lite närmare hemmet: hur hissar fungerar.
Låt oss börja med någon kod.
bar(); funktionsfältet () if (! foo) alert (foo + "? Detta är konstigt ..."); var foo = "bar"; bruten (); // Skrivfel! var broken = function () alert ("Denna varning kommer inte dyka upp!");
Om du kör den här koden märker du tre saker:
foo
innan du tilldelar det, men dess värde är odefinierad
.bruten
innan du definierar det, men du får en Skrivfel
.bar
innan du definierar det, och det fungerar som önskat.lyft hänvisar till det faktum att JavaScript gör alla våra deklarerade variabla namn tillgängliga överallt i sina omfattningar-inklusive innan vi tilldelar dem.
De tre fallen i snippet är de tre du behöver vara medvetna om i din egen kod, så vi kommer att gå igenom var och en av dem en efter en.
Kom ihåg, när JavaScript-kompilatorn läser en linje som var foo = "bar"
, Det:
foo
till närmaste räckviddfoo
till odefinieradAnledningen till att vi kan använda foo
innan vi tilldelar det är för att när motorn ser upp variabeln med det namnet, det gör existera. Det är därför det inte kastar en Reference
.
I stället blir värdet odefinierad
, och försöker använda det för att göra vad du frågade om det. Vanligtvis är det en bugg.
Med tanke på detta kan vi föreställa oss att det som JavaScript ser i vår funktion bar
är mer så här:
funktionsfältet () var foo; // odefinierad om (! foo) //! undefined är sant, så varning alert (foo + "? Detta är konstigt ..."); foo = "bar";
Det här är Första regel för lyftning, om du vill: Variabler är tillgängliga inom hela deras räckvidd, men har värdet odefinierad
tills din kod tilldelar dem.
Ett vanligt JavaScript-idiom är att skriva alla dina var
deklarationer högst upp i deras räckvidd, i stället för var du först använder dem. Att omskifta Doug Crockford hjälper det här med din kod läsa mer som det körningar.
När du tänker på det, är det meningsfullt. Det är ganska klart varför bar
beter sig som det gör när vi skriver vår kod så som JavaScript läser det, eller hur? Så varför inte bara skriva så Allt tiden?
Det faktum att vi fick en Skrivfel
när vi försökte utföra bruten
innan vi definierade det är bara ett speciellt fall av den första regeln för lyftning.
Vi definierade en variabel, kallad bruten
, som kompilatorn registrerar i det globala räckviddet och motsvarar odefinierad
. När vi försöker springa, ser motorn upp värdet av bruten
, finner att det är odefinierad
, och försöker att utföra odefinierad
som en funktion.
Självklart, odefinierad
är inte en funktion-det är därför vi får en Skrivfel
!
Slutligen, erinra om att vi kunde ringa bar
innan vi definierade det. Detta beror på Andra regeln för lyftning: När JavaScript-kompilatorn hittar en funktionsdeklaration gör den både sitt namn och definition tillgänglig överst i dess omfattning. Skriv om koden ännu en gång:
funktionsfältet () if (! foo) alert (foo + "? Detta är konstigt ..."); var foo = "bar"; var bruten // odefinierad bar (); // bar är redan definierad, exekverar finbruten (); // Kan inte utföras odefinierad! bruten = funktion () alert ("Den här varningen kommer inte dyka upp!");
Återigen är det mycket mer meningsfullt när du skriva som JavaScript läser, tror du inte?
Att recensera:
odefinierad
tills tilldelning.Låt oss nu titta på två nya verktyg som fungerar lite annorlunda: låta
och const
.
låta
, const
, & Temporal Dead Zone Till skillnad från var
deklarationer, variabler deklarerade med låta
och const
inte hissas av kompilatorn.
Åtminstone, inte exakt.
Kom ihåg hur vi kunde ringa bruten
, men fick en Skrivfel
eftersom vi försökte utföra odefinierad
? Om vi hade definierat bruten
med låta
, vi skulle ha fått en Reference
, istället:
"använd strikt"; // Du måste "använda strikt" för att prova detta i Node broken (); // ReferenceError! låt bruten = funktion () alert ("Denna varning kommer inte dyka upp!");
När JavaScript-kompilatorn registrerar variabler till sina omfattningar i sitt första pass behandlar det låta
och const
annorlunda än vad den gör var
.
När den finner a var
förklaring, registrerar vi namnet på variabelns omfattning och omedelbart initierar dess värde till odefinierad
.
Med låta
, dock kompilatorn gör registrera variabelns omfattning, men gör inteinitiera dess värde till odefinierad
. I stället lämnar variabeln uninitialiserad, fram tills motorn utför ditt uppdragsdeklaration. Åtkomst av värdet för en oinitierad variabel kastar a Reference
, vilket förklarar varför koden ovan kastar när vi kör det.
Utrymmet mellan början av toppen av omfattningen av a låta
deklarationen och uppdragsdeklarationen kallas Temporal Dead Zone. Namnet kommer från det faktum att även om motorn vet om en variabel som heter foo
högst upp till bar
, variabeln är "död", eftersom den inte har ett värde.
... Även för att det kommer att döda ditt program om du försöker använda det tidigt.
De const
sökord fungerar på samma sätt som låta
, med två viktiga skillnader:
const
.const
.Detta garanterar att const
kommer alltidha det värde som du ursprungligen tilldelade det.
// Detta är juridisk const React = kräver ('reagera'); // Detta är helt inte juridisk const crypto; krypto = kräver ('krypto');
låta
och const
skiljer sig från var
på ett annat sätt: storleken på deras omfång.
När du deklarerar en variabel med var
, det är synligt så högt upp på kedjan som möjligt - vanligen ovanpå närmaste funktionsdeklaration eller i det globala räckviddet om du förklarar det på toppnivå.
När du deklarerar en variabel med låta
eller const
, Det är dock synligt som lokalt som möjligt-endast inom närmaste kvarter.
en blockera är en del av koden avstängd med lockiga axlar, som du ser med om
/annan
block, för
slingor och i uttryckligen "blockerade" bitar av kod, som i det här snippet.
"använd strikt"; let foo = "foo"; om (foo) const bar = "bar"; var foobar = foo + bar; console.log ("Jag kan se" + bar + "i den här blocket."); försök console.log ("jag kan se" + foo + "i det här blocket, men inte" + bar + "."); fånga (err) console.log ("Du har en" + err + "."); försök console.log (foo + bar); // Kasta på grund av "foo", men båda är odefinierade fånga (err) console.log ("Du har bara en" + err + "."); console.log (foobar); // Fungerar bra
Om du deklarerar en variabel med const
eller låta
inuti ett block är det endast synlig inuti blocket, och endast efter att du har tilldelat den.
En variabel deklarerad med var
, Det är dock synligt så långt bort som möjligt-i det här fallet, i det globala räckviddet.
Om du är intresserad av nitty-gritty detaljer om låta
och const
, kolla vad Dr Rauschmayer har att säga om dem i Exploring ES6: Variables and Scoping och titta på MDN-dokumentationen på dem.
detta
& PilfunktionerPå ytan, detta
verkar inte ha en hel del att göra med räckvidd. Och i själva verket gör JavaScript inte lösa betydelsen av detta
enligt de regler som vi har pratat om här.
Åtminstone, vanligtvis inte. JavaScript, berömd, gör det inte lösa betydelsen av detta
sökord baserat på var du använde det:
var foo = namn: "Foo", språk: ['spanska', 'franska', 'italienska'], tala: funktion tala () this.languages.forEach (funktion (språk) console.log namn + "talar" + språk + ".";;); foo.speak ();
De flesta av oss skulle förvänta oss detta
att mena foo
inuti för varje
loop, för det var vad det menade precis utanför det. Med andra ord, vi förväntar oss JavaScript för att lösa betydelsen av detta
lexikaliskt.
Men det gör det inte.
Istället skapar det en ny detta
inuti varje funktion du definierar och bestämmer vad det betyder baserat på på vilket sätt du ringer funktionen-inte var du definierade det.
Den första punkten liknar omdefinieringen några variabel i ett barnomfattning:
funktion foo () var bar = "bar"; funktion baz () // Återanvändning av variabla namn så här heter "shadowing" var bar = "BAR"; console.log (bar); // BAR baz (); foo (); // BAR
Byta ut bar
med detta
, och hela saken ska rensa upp direkt!
Traditionellt att få detta
att arbeta som vi förväntar oss att vanliga gamla lexiskt scoped variabler ska fungera kräver en av två lösningar:
var foo = namn: "Foo", språk: ['spanska', 'franska', 'italienska'], speak_self: function speak_s () var själv = detta; self.languages.forEach (funktion (språk) console.log (self.name + "talar" + språk + ".";;), speak_bound: function speak_b () this.languages.forEach (funktion ) console.log (this.name + "talar" + språk + "."; .bind (foo)); // Mer vanligt: .bind (detta); ;
I speak_self
, vi räddar betydelsen av detta
till variabeln själv
, och använda den där variabel för att få den referens vi vill ha. I speak_bound
, vi använder binda
till permanent punkt detta
till ett visst objekt.
ES2015 ger oss ett nytt alternativ: pilfunktioner.
Till skillnad från "normala" funktioner gör pilfunktionerna inte skugga deras överordnade omfattning s detta
värde genom att ställa in egna. I stället löser de sin mening lexikaliskt.
Med andra ord, om du använder detta
i en pilfunktion ser JavaScript upp sitt värde som det skulle vara någon annan variabel.
För det första kontrollerar den lokala räckvidden för a detta
värde. Eftersom pilfunktionerna inte anger en, kommer den inte att hitta någon. Därefter kontrollerar den förälder utrymme för a detta
värde. Om den hittar en, kommer den att använda det istället.
Det här låter oss skriva om koden ovan så här:
var foo = namn: "Foo", språk: ['spanska', 'franska', 'italienska'], tala: funktion tala () this.languages.forEach ((language) => console.log .name + "talar" + språk + ".";;);
Om du vill ha mer information om pilfunktioner, ta en titt på Envato Tuts + Instructor Dan Wellmans utmärkta kurs på JavaScript ES6 Fundamentals, samt MDN-dokumentationen om pilfunktioner.
Vi har täckt mycket av marken hittills! I den här artikeln har du lärt dig att:
låta
eller const
innan uppdrag kastar a Reference
, och att sådana variabler scoped till närmaste block.detta
, och kringgå traditionell dynamisk bindning.Du har också sett de två reglerna för hissning:
var
deklarationer är tillgängliga i hela de områden där de definieras men har värdet odefinierad
tills dina uppdragsuppdrag utförs.Ett bra nästa steg är att använda din nyskapade kunskap om JavaScript-områdena för att linda huvudet runt stängningar. För det, kolla Kyle Simpsons Scopes & Closures.
Slutligen finns det mycket mer att säga om detta
än jag kunde täcka här. Om sökordet fortfarande verkar som så mycket svart magi, ta en titt på detta och Objekt Prototyper för att få huvudet runt det.
Under tiden ta vad du har lärt dig och skriv färre buggar!
Lär dig JavaScript: Den fullständiga guiden
Vi har byggt en komplett guide för att hjälpa dig att lära dig JavaScript, oavsett om du precis börjat som webbutvecklare eller vill utforska mer avancerade ämnen.