Omfang, eller sett med regler som bestemmer hvor variablene dine bor, er et av de mest grunnleggende konseptene i et hvilket som helst programmeringsspråk. Det er så grunnleggende at det er lett å glemme hvor subtile reglene kan være!
Forstå nøyaktig hvordan JavaScript-motoren "tenker" om omfanget, vil holde deg i å skrive de vanlige feilene som heiser kan forårsake, forberede deg til å vikle hodet rundt lukkene, og få deg så mye nærmere å aldri skrive feil noensinne en gang til.
... Vel, det vil hjelpe deg å forstå heising og nedleggelser, uansett.
I denne artikkelen tar vi en titt på:
la
og konst
endre spilletLa oss dykke inn.
Hvis du er interessert i å lære mer om ES6 og hvordan du kan utnytte syntaksen og funksjonene for å forbedre og forenkle JavaScript-koden, hvorfor ikke sjekke disse to kursene:
Hvis du har skrevet enda en linje med JavaScript før, vet du det Var du definere Dine variabler bestemmer hvor du kan bruk dem. Det faktum at en variabel sikt er avhengig av strukturen til kildekoden din kalles leksikalske omfang.
Det er tre måter å skape omfang på i JavaScript:
la
eller konst
inne i en kodeblokk. Slike erklæringer er bare synlige inne i blokken. å fange
blokkere. Tro det eller ei, dette faktisk gjør opprett et nytt omfang!"bruk strenge"; var mr_global = "Mr Global"; funksjon foo () var mrs_local = "Mrs Local"; console.log ("Jeg kan se" + mr_global + "og" + mrs_local + "."); funksjonslinje () console.log ("Jeg kan også se" + mr_global + "og" + mrs_local + "."); foo (); // Fungerer som forventet forsøk console.log ("Men / I / kan ikke se" + mrs_local + "."); catch (err) console.log ("Du har nettopp fått en" + feil "."); la foo = "foo"; const bar = "bar"; console.log ("Jeg kan bruke" + foo + bar + "i sin blokk ..."); prøv console.log ("men ikke utenfor det."); catch (err) console.log ("Du har nettopp fått en annen" + feil + "."); // Kaster ReferenceError! console.log ("Merk at" + err + "ikke eksisterer utenfor" catch "!")
Utsnittet over viser alle tre anvendelsesmekanismer. Du kan kjøre den i Node eller Firefox, men Chrome spiller ikke fint med la
, ennå.
Vi snakker om hver av disse i utsøkt detalj. La oss begynne med et detaljert blikk på hvordan JavaScript angir hvilke variabler som hører til hvilket omfang.
Når du kjører et stykke JavaScript, skjer det to ting for å få det til å fungere.
Under de kompilering skritt, JavaScript-motoren:
Det er bare under henrettelse at JavaScript-motoren faktisk angir verdien av variable referanser som er lik deres oppføringsverdier. Inntil da er de det udefinert
.
// Jeg kan bruke first_name hvor som helst i dette programmet var first_name = "Peleke"; funksjon popup (first_name) // Jeg kan bare bruke sistnavn inni denne funksjonen var last_name = "Sengstacke"; varsling (fornavn + "+ sistenavn); popup (first_name);
La oss gå gjennom hva kompilatoren gjør.
Først leser den linjen var first_name = "Peleke"
. Deretter bestemmer det hva omfang å lagre variabelen til. Fordi vi er på toppen av skriptet, innser vi at vi er i globalt omfang. Deretter sparer den variabelen fornavn
til det globale omfanget og initierer sin verdi til udefinert
.
For det andre leser kompilatoren linjen med funksjon popup (first_name)
. Fordi det funksjon Søkeordet er det første på linjen, det skaper et nytt omfang for funksjonen, registrerer funksjonens definisjon i det globale omfanget, og kikker inn for å finne variabeldeklarasjoner.
Sikker nok finner kompilatoren en. Siden vi har var last_name = "Sengstacke"
I første linje av vår funksjon lagrer kompilatoren variabelen etternavn
til omfanget av popup
-ikke til det globale omfanget - og setter sin verdi til udefinert
.
Siden det ikke er flere variable deklarasjoner inne i funksjonen, går kompilatoren tilbake i det globale omfanget. Og siden det ikke er flere variable deklarasjoner der, denne fasen er ferdig.
Legg merke til at vi egentlig ikke har det løpe noe enda. Kompilatorens jobb på dette punktet er bare for å sikre at det kjenner alles navn; det bryr seg ikke hva de gjør.
På dette punktet vet vårt program at:
fornavn
i det globale omfanget.popup
i det globale omfanget.etternavn
i omfanget av popup
.fornavn
og etternavn
er udefinert
.Det bryr seg ikke om at vi har tilordnet disse variablene verdiene andre steder i vår kode. Motoren tar vare på det i henrettelse.
Under neste trinn leser motoren vår kode igjen, men denne gangen, Utfører den.
Først leser den linjen, var first_name = "Peleke"
. For å gjøre dette ser motoren opp variabelen som kalles fornavn
. Siden kompilatoren allerede har registrert en variabel med det navnet, finner motoren det, og setter dets verdi til "Peleke"
.
Deretter leser linjen, funksjon popup (first_name)
. Siden vi ikke er det utførende funksjonen her, motoren er ikke interessert og hopper over den.
Til slutt leser den linjen popup (FIRST_NAME)
. Siden vi er utfører en funksjon her, motoren:
popup
fornavn
popup
som en funksjon, bestått verdien av fornavn
som en parameterNår den kjøres popup
, Det går gjennom samme prosess, men denne gangen inne i funksjonen popup
. Den:
etternavn
etternavn
s verdi lik "Sengstacke"
varsling
, utfører det som en funksjon med "Peleke Sengstacke"
som parameterDet viser seg mye mer under hetten enn vi kanskje trodde!
Nå som du forstår hvordan JavaScript leser og kjører koden du skriver, er vi klare til å takle noe litt nærmere hjemme: hvordan heising virker.
La oss starte med noen kode.
bar (); funksjonslinje () hvis (! foo) alert (foo + "? Dette er rart ..."); var foo = "bar"; gått i stykker(); // TypeError! var ødelagt = funksjon () alert ("Dette varselet vil ikke dukke opp!");
Hvis du kjører denne koden, vil du legge merke til tre ting:
foo
før du tilordner det, men verdien er udefinert
.gått i stykker
før du definerer det, men du får en Typeerror
.Bar
før du definerer det, og det fungerer som ønsket.heising refererer til det faktum at JavaScript gjør at alle våre deklarerte variable navn er tilgjengelige overalt i deres omfang - inkludert før Vi tilordner dem.
De tre tilfellene i koden er de tre du må være oppmerksom på i din egen kode, så vi vil gå gjennom hver av dem en etter en.
Husk når JavaScript-kompilatoren leser en linje som var foo = "bar"
, den:
foo
til nærmeste omfangfoo
til udefinertGrunnen til at vi kan bruke foo
før vi tilordner det er fordi når motoren ser opp variabelen med det navnet, det gjør eksistere. Det er derfor det ikke kaster en ReferenceError
.
I stedet blir det verdien udefinert
, og prøver å bruke det for å gjøre hva du spurte om det. Vanligvis er det en feil.
Når vi tenker på det, kan vi tenke oss at det JavaScript ser i vår funksjon Bar
er mer som dette:
funksjonslinje () var foo; // undefined hvis (! foo) //! undefined er sant, så varselvarsel (foo + "? Dette er rart ..."); foo = "bar";
Dette er Første løfteregel, hvis du vil: Variabler er tilgjengelige gjennom hele deres omfang, men har verdien udefinert
til koden tilordner dem.
Et vanlig JavaScript-idiom er å skrive alle dine Var
erklæringer øverst i deres omfang, i stedet for hvor du først bruker dem. For å omskrive Doug Crockford, hjelper dette koden din lese mer som det runs.
Når du tenker på det, er det fornuftig. Det er ganske klart hvorfor Bar
oppfører seg som det gjør når vi skriver vår kode som JavaScript leser det, ikke sant? Så hvorfor ikke bare skrive slik alle tiden?
Det faktum at vi fikk en Typeerror
da vi prøvde å utføre gått i stykker
før vi har definert det, er det bare et spesielt tilfelle av den første heisregelen.
Vi definerte en variabel, kalt gått i stykker
, som kompilatoren registrerer i det globale omfanget og setter lik udefinert
. Når vi prøver å kjøre den, ser motoren opp verdien av gått i stykker
, finner at det er udefinert
, og prøver å utføre udefinert
som en funksjon.
Åpenbart, udefinert
er ikke en funksjon-det er derfor vi får en Typeerror
!
Til slutt husker vi at vi kunne ringe Bar
før vi definerte det. Dette skyldes Andre heiseregler: Når JavaScript-kompilatoren finner en funksjonsdeklarasjon, gjør den både navnet sitt og definisjon tilgjengelig på toppen av sitt omfang. Skriv om koden enda en gang:
funksjonslinje () hvis (! foo) alert (foo + "? Dette er rart ..."); var foo = "bar"; var ødelagt // undefined bar (); // bar er allerede definert, utfører fint ødelagt (); // Kan ikke utføre udefinert! ødelagt = funksjon () alert ("Dette varselet vil ikke dukke opp!");
Igjen, det gir mye mer mening når du skrive som JavaScript leser, tror du ikke?
For å gjennomgå:
udefinert
til oppdrag.La oss nå se på to nye verktøy som fungerer litt annerledes: la
og konst
.
la
, konst
, & Temporal Dead Zone I motsetning til Var
erklæringer, variabler deklarert med la
og konst
ikke bli hevet av kompilatoren.
I hvert fall ikke akkurat.
Husk hvordan vi kunne ringe gått i stykker
, men fikk en Typeerror
fordi vi prøvde å utføre udefinert
? Hvis vi hadde definert gått i stykker
med la
, vi hadde fått en ReferenceError
, i stedet:
"bruk strenge"; // Du må "bruke strenge" for å prøve dette i Node broken (); // ReferenceError! la broken = function () alert ("Dette varselet vil ikke dukke opp!");
Når JavaScript-kompilatoren registrerer variabler i deres rekkevidde i sin første pass, behandler den la
og konst
annerledes enn det gjør Var
.
Når den finner a Var
erklæring, registrerer vi navnet på variabelen i omfanget og umiddelbart initierer verdien til udefinert
.
Med la
, men kompilatoren gjør registrer variabelen i omfang, men gjør ikkeinitialiserer verdien til udefinert
. I stedet lar den variabelen uninitialized, før motoren utfører oppgaven din. Å få tilgang til verdien av en uninitialisert variabel kaster a ReferenceError
, som forklarer hvorfor koden over kaster når vi kjører den.
Rommet mellom begynnelsen av toppen av omfanget av a la
erklæring og oppdragserklæringen kalles Temporal Dead Zone. Navnet kommer fra det faktum at, selv om motoren vet om en variabel som heter foo
øverst i omfanget av Bar
, variabelen er "død", fordi den ikke har en verdi.
... Også fordi det vil drepe programmet hvis du prøver å bruke det tidlig.
De konst
Søkeord fungerer på samme måte som la
, med to hovedforskjeller:
konst
.konst
.Dette garanterer at konst
vil alltidha verdien som du opprinnelig tildelte den.
// Dette er juridisk const React = kreve ('reagere'); // Dette er helt ikke lovlig const crypto; krypto = krever ('krypto');
la
og konst
er forskjellig fra Var
på en annen måte: størrelsen på deres rekkevidde.
Når du erklærer en variabel med Var
, det er synlig så høyt opp på omfanget av kjeden som mulig - typisk øverst på nærmeste funksjonsdeklarasjon, eller i det globale omfanget, hvis du erklærer det på toppnivå.
Når du erklærer en variabel med la
eller konst
, Det er imidlertid synlig som lokalt som mulig-bare innen nærmeste blokk.
EN blokkere er en del av koden satt av krøllete braces, som du ser med hvis
/ellers
blokker, til
sløyfer, og i eksplisitt "blokkert" stykker av kode, som i denne brikken.
"bruk strenge"; la foo = "foo"; hvis (foo) const bar = "bar"; var foobar = foo + bar; console.log ("Jeg kan se" + bar + "i denne blokken."); prøv console.log ("Jeg kan se" + foo + "i denne blokken, men ikke" + bar + "."); fangst (err) console.log ("Du har en" + feil + "."); prøv console.log (foo + bar); // Kaster på grunn av 'foo', men begge er udefinerte catch (err) console.log ("Du har nettopp fått en" + feil + "."); console.log (foobar); // Fungerer fint
Hvis du erklærer en variabel med konst
eller la
inne i en blokk, er det bare synlig inne i blokken, og bare etter at du har tildelt det.
En variabel erklært med Var
, Det er imidlertid synlig så langt unna som mulig-i dette tilfellet, i det globale omfanget.
Hvis du er interessert i nitty-gritty detaljer av la
og konst
, sjekk ut hva Dr Rauschmayer har å si om dem i Utforsking av ES6: Variabler og Scoping, og se på MDN-dokumentasjonen på dem.
dette
Og pilfunksjonerPå overflaten, dette
ser ikke ut til å ha mye å gjøre med omfanget. Og faktisk gjør JavaScript ikke løse betydningen av dette
i henhold til anvendelsesområdet har vi snakket om her.
I det minste, ikke vanligvis. JavaScript, beryktet, gjør det ikke løse betydningen av dette
søkeord basert på hvor du brukte det:
var foo = navn: 'Foo', språk: ['spansk', 'fransk', 'italiensk'], snakk: funksjon tale () this.languages.forEach (funksjon (språk) console.log navn + "snakker" + språk + ".";;); foo.speak ();
De fleste av oss ville forvente dette
å mene foo
inne i for hver
loop, fordi det var det det betydde rett utenfor det. Med andre ord, vi forventer JavaScript for å løse betydningen av dette
leksikalsk.
Men det gjør det ikke.
I stedet skaper det en ny dette
inne i hver funksjon du definerer, og bestemmer hva det betyr basert på hvordan du ringer funksjonen-ikke hvor du definerte det.
Det første punktet ligner på omdefinering noen variabel i et barns omfang:
funksjon foo () var bar = "bar"; funksjon baz () // Gjenbruk av variable navn som dette kalles "shadowing" var bar = "BAR"; console.log (bar); // BAR baz (); foo (); // BAR
Erstatte Bar
med dette
, og hele greien bør rydde opp umiddelbart!
Tradisjonelt, får dette
å jobbe som vi forventer at vanlige gamle leksisk-scoped-variabler skal fungere, krever en av to løsninger:
var foo = navn: 'Foo', språk: ['spansk', 'fransk', 'italiensk'], speak_self: funksjon speak_s () var selv = dette; self.languages.forEach (funksjon (språk) console.log (self.name + "snakker" + språk + ".";), speak_bound: function speak_b () this.languages.forEach (funksjon ) console.log (dette.navnet + "snakker" + språk + "."; .bind (foo)); // Mer vanlig: .bind (dette); ;
I speak_self
, vi redder meningen med dette
til variabelen selv-
, og bruk at variabel for å få referansen vi ønsker. I speak_bound
, vi bruker binde
til permanent punkt dette
til et gitt objekt.
ES2015 gir oss et nytt alternativ: pilfunksjoner.
I motsetning til "normale" funksjoner, gjør pilfunksjoner ikke skygge deres foreldre omfang er dette
verdi ved å sette sin egen. I stedet løser de sin mening leksikalsk.
Med andre ord, hvis du bruker dette
i en pilfunksjon ser JavaScript opp sin verdi som det ville være en hvilken som helst annen variabel.
Først sjekker den det lokale omfanget for a dette
verdi. Siden pilfunksjonene ikke angir en, vil den ikke finne en. Deretter sjekker den forelder mulighet for a dette
verdi. Hvis den finner en, vil den bruke det, i stedet.
Dette lar oss omskrive koden ovenfor slik:
var foo = navn: 'Foo', språk: ['spansk', 'fransk', 'italiensk'], snakk: funksjon tale () this.languages.forEach ((language) => console.log .name + "snakker" + språk + ".";;);
Hvis du vil ha mer informasjon om pilfunksjoner, ta en titt på Envato Tuts + Instruktør Dan Wellmans fremragende kurs på JavaScript ES6 Fundamentals, samt MDN-dokumentasjonen på pilfunksjoner.
Vi har dekket mye bakken så langt! I denne artikkelen har du lært at:
la
eller konst
før oppdrag kaster a ReferenceError
, og at slike variabler er scoped til nærmeste blokk.dette
, og omgå tradisjonell dynamisk binding.Du har også sett de to regler for heising:
Var
erklæringer er tilgjengelige i hele omfanget der de er definert, men har verdien udefinert
til oppgavene dine utføres.Et godt neste skritt er å bruke din nybegynnde kunnskap om JavaScript-målene for å pakke hodet rundt lukninger. For det, sjekk ut Kyle Simpsons Scopes & Closures.
Til slutt er det mye mer å si om dette
enn jeg kunne dekke her. Hvis søkeordet fortsatt virker som så mye svart magi, ta en titt på dette og Objekt Prototyper for å få hodet rundt det.
I mellomtiden ta hva du har lært og gå skrive færre feil!
Lær JavaScript: Den komplette veiledningen
Vi har bygget en komplett guide for å hjelpe deg med å lære JavaScript, enten du er bare i gang som webutvikler eller du vil utforske mer avanserte emner.