Grokking Scope în JavaScript

Domeniul de aplicare sau setul de reguli care determină locația variabilelor dvs. reprezintă unul dintre conceptele cele mai de bază ale oricărui limbaj de programare. Este atât de fundamental, de fapt, că este ușor să uități cât de subtile pot fi regulile!

Înțelegerea exactă a modului în care motorul JavaScript "gândește" cu privire la domeniul de aplicare vă va împiedica să scrieți bug-urile comune pe care le poate provoca ridicarea, să vă pregătească să vă înfășurați capul în jurul închiderilor și să vă apropiați cât mai mult de scrierea erorilor vreodată din nou.

... Ei bine, vă va ajuta să înțelegeți ridicarea și închiderea, oricum. 

În acest articol, vom arunca o privire la:

  • elementele de bază ale domeniilor în JavaScript
  • modul în care interpretul decide ce variabile aparțin domeniului
  • cum de ridicare într-adevăr lucrări
  • modul în care cuvinte cheie ES6 lăsa și const schimbați jocul

Hai să ne aruncăm.

Dacă sunteți interesat să aflați mai multe despre ES6 și cum să folosiți sintaxa și funcțiile pentru a îmbunătăți și simplifica codul JavaScript, de ce să nu verificați aceste două cursuri:

Lexical Scope

Dacă ați scris deja o linie de JavaScript înainte, veți ști asta unde tu defini variabilele determină unde puteți utilizare lor. Faptul că vizibilitatea unei variabile depinde de structura codului dvs. sursă se numește lexical domeniu.

Există trei moduri de a crea domeniul de aplicare în JavaScript:

  1. Creați o funcție. Variabilele declarate în interiorul funcțiilor sunt vizibile numai în interiorul acelei funcții, inclusiv în funcțiile imbricate.
  2. Declarați variabilele cu lăsa sau const în interiorul unui bloc de cod. Aceste declarații sunt vizibile numai în interiorul blocului.
  3. Creeaza o captură bloc. Credeți-vă sau nu, de fapt face creați un nou domeniu de aplicare!
"folosiți stricte"; var mr_global = "Domnul Global"; funcția foo () var mrs_local = "Doamna locală"; console.log ("Pot vedea" + mr_global + "și" + mrs_local + ""); funcția bar () console.log ("De asemenea, pot vedea" + mr_global + "și" + mrs_local + "");  foo (); // Funcționează conform așteptărilor încercați console.log ("Dar / I / nu pot vedea" + mrs_local + ".");  captură (err) console.log ("Ați primit doar" + err + ".);  lăsați foo = "foo"; const bar = "bar"; console.log ("Pot folosi" + foo + bar + "în blocul său ...");  încercați console.log ("Dar nu în afara acestuia");  captură (err) console.log ("Ați primit încă un" + err + ".");  // Throws ReferenceError! console.log ("Rețineți că" + err + "nu există în afara" capturii "!) 

Fragmentul de mai sus demonstrează toate cele trei mecanisme de aplicare. Puteți rula în Nod sau Firefox, dar Chrome nu se joacă frumos cu lăsa, inca.

Vom vorbi despre fiecare dintre ele în detalii deosebite. Să începem cu o analiză detaliată a modului în care JavaScript stabilește ce variabile aparțin domeniului.

Procesul de compilare: o vedere a păsărilor

Când rulați o bucată de JavaScript, două lucruri se întâmplă să o facă să funcționeze.

  1. În primul rând, sursa dvs. este compilată.
  2. Apoi, codul compilat este executat.

Pe parcursul  compilare Etapa, motorul JavaScript:

  1. ia notă de toate numele variabilelor
  2. le înregistrează în domeniul corespunzător
  3. își rezervă spațiul pentru valorile lor

Numai în timpul execuţie că motorul JavaScript stabilește, de fapt, valoarea referințelor variabile egale cu valorile lor de atribuire. Până atunci, sunt nedefinit

Pasul 1: Compilarea

// Pot folosi first_name oriunde în acest program var first_name = "Peleke"; popup de funcții (first_name) // Pot folosi doar last_name în interiorul acestei funcții var last_name = "Sengstacke"; alertă (first_name + "+ last_name); popup (first_name);

Să facem pasul cu ceea ce face compilatorul.

Mai întâi, citește linia var first_name = "Peleke". Apoi, determină ce domeniu pentru a salva variabila la. Pentru că suntem la cel mai înalt nivel al scenariului, realizăm că suntem în la nivel global. Apoi, salvează variabila Nume la domeniul global și inițiază valoarea acestuia nedefinit.

În al doilea rând, compilatorul citește linia cu funcția popup (first_name). Deoarece funcţie cuvântul cheie este primul lucru pe linie, creează un nou domeniu de aplicare al funcției, înregistrează definiția funcției în sfera globală și caută în interior pentru a găsi declarații variabile.

Destul de sigur, compilatorul găsește unul. De când avem var last_name = "Sengstacke" în prima linie a funcției noastre, compilatorul salvează variabila numele de familie la scopul Pop-up-nu la domeniul global - și își stabilește valoarea nedefinit

Deoarece în interiorul funcției nu mai există declarații variabile, compilatorul revine în domeniul global. Și deoarece nu mai există declarații variabile Acolo, această fază se face.

Rețineți că nu am avut de fapt alerga nimic încă. Funcția compilatorului în acest moment este doar să vă asigurați că știe numele tuturor; nu-i pasă ce ei fac. 

În acest moment, programul nostru știe că:

  1. Există o variabilă numită Nume în domeniul global.
  2. Există o funcție numită Pop-up în domeniul global.
  3. Există o variabilă numită numele de familie în domeniul de aplicare al Pop-up.
  4. Valorile ambelor Nume și numele de familie sunteți nedefinit.

Nu ne pasă că am atribuit valorile acestor variabile în altă parte a codului nostru. Motorul are grija de asta execuţie.

Pasul 2: Executarea

În timpul următorului pas, motorul ne citește din nou codul, dar de data aceasta, Executa aceasta. 

Mai întâi, citește linia, var first_name = "Peleke". Pentru a face acest lucru, motorul caută variabila numită Nume. Deoarece compilatorul a înregistrat deja o variabilă cu numele respectiv, motorul îl găsește și își stabilește valoarea "Peleke".

Apoi, citește linia, funcția popup (first_name). Din moment ce nu suntem executare funcția de aici, motorul nu este interesat și trece peste el.

În cele din urmă, citește linia pop-up (FIRST_NAME). De cand noi sunteți executând o funcție aici, motorul:

  1. caută valoarea Pop-up
  2. caută valoarea Nume
  3. Executa Pop-up ca o funcție, trecerea valorii Nume ca parametru

Când se execută Pop-up, trece prin același proces, dar de data aceasta în interiorul funcției Pop-up. Aceasta:

  1. caută variabila numită numele de familie
  2. seturi numele de familievaloarea lui egală cu "Sengstacke"
  3. se uita sus alerta, executându-l ca o funcție cu "Peleke Sengstacke" ca parametru

Se pare că se petrec mult mai mult sub capotă decât ne-am fi gândit!

Acum că înțelegeți modul în care JavaScript citește și rulează codul pe care îl scrieți, suntem gata să abordăm ceva mai aproape de casă: cum funcționează ascensorul.

Ridicarea sub microscop

Să începem cu un cod.

bar(); funcția bar () if (! foo) alert (foo + "? Aceasta este ciudat ...");  var foo = "bar";  rupt (); // Eroare de scris! var broken = function () alert ("Această avertizare nu va apărea!"); 

Dacă executați acest cod, veți observa trei lucruri:

  1. Tu poate sa a se referi la foo înainte de a le atribui, dar valoarea lor este nedefinit.
  2. Tu poate sa apel spart înainte de ao defini, dar veți obține o Eroare de scris.
  3. Tu poate sa apel bar înainte de ao defini, și funcționează așa cum doriți.

Ridicat se referă la faptul că JavaScript face disponibile toate numele de variabile declarate pretutindeni în domeniile lor - inclusiv inainte de le atribuim.

Cele trei cazuri din fragment sunt cele trei pe care va trebui să le cunoașteți în propriul cod, așa că vom trece prin fiecare unul câte unul.

Ridicarea declarațiilor variabile

Rețineți, atunci când compilatorul JavaScript citește o linie ca var foo = "bar", aceasta:

  1. înregistrează numele foo la cel mai apropiat domeniu de aplicare
  2. stabilește valoarea foo la nedefinit

Motivul pentru care putem folosi foo înainte de a le atribui este pentru că, atunci când motorul caută variabila cu acel nume, acesta face exista. De aceea nu arunca a ReferenceError

În schimb, devine valoarea nedefinit, și încearcă să o folosească pentru a face ceea ce ați cerut. De obicei, este un bug.

Ținând cont de acest lucru, ne-am putea imagina ceea ce vede JavaScript în funcția noastră bar este mai mult ca aceasta:

funcția bar () var foo; // undefined if (! foo) //! undefined este adevărat, alertă atât de alertă (foo + "" Aceasta este ciudată ... ");  foo = "bar"; 

Acesta este Prima regulă de ridicare, dacă: Variabilele sunt disponibile în întregul lor domeniu de aplicare, dar au valoarea nedefinit până când codul dvs. îi va atribui.

Un idiom comun JavaScript este de a scrie toate dvs. var declarații în partea de sus a domeniului lor de aplicare, în loc de locul în care le folosiți pentru prima dată. Pentru a paraframa Doug Crockford, acest lucru vă ajută codul citit mai mult ca asta ruleaza.

Când te gândești la asta, are sens. Este destul de clar de ce bar se comportă așa cum o face atunci când scriem codul nostru așa cum îl citește JavaScript, nu-i așa? Deci, de ce nu scrieți așa toate timpul?  

Expresii ale funcțiilor ascendente

Faptul că avem a Eroare de scris când am încercat să executăm spart înainte de a ne defini că este doar un caz special al primei reguli de ridicare.

Am definit o variabilă, numită spart, pe care compilatorul o înregistrează în domeniul global și stabilește egal cu nedefinit. Când încercăm să o executăm, motorul caută valoarea spart, constată că este nedefinit, și încearcă să execute nedefinit ca o funcție.

Evident, nedefinit nu este o funcție - de aceea avem un a Eroare de scris!

Declarații de funcții ascendente

În cele din urmă, amintiți-vă că am reușit să sunăm bar înainte de ao defini. Acest lucru se datorează A doua regulă de ridicare: Atunci când compilatorul JavaScript găsește o declarație a funcției, aceasta face atât numele său și definiție disponibilă în partea de sus a domeniului său de aplicare. Rescrierea codului nostru încă o dată:

funcția bar () if (! foo) alert (foo + "? Aceasta este ciudat ...");  var foo = "bar";  var rupt; // undefined bar (); // barul este deja definit, execută bine rupt (); // Nu se poate executa nedefinit! broken = function () alert ("Această avertizare nu va apărea!"); 

 Din nou, are mult mai mult sens când tu scrie ca JavaScript citește, nu crezi?

Pentru a revizui:

  1. Numele atât a declarațiilor variabile cât și a expresiilor funcționale sunt disponibile pe întregul lor domeniu de aplicare, valorile sunteți nedefinit până la alocare.
  2. Numele și definițiile declarațiilor de funcții sunt disponibile în întregul lor domeniu de aplicare, chiar si inainte definițiile acestora.

Acum, să aruncăm o privire asupra a două instrumente noi care funcționează puțin diferit: lăsa și const.

lăsaconst, & Zona Dead Temporală

Spre deosebire de var declarații, variabile declarate cu lăsa și const nu face să fie ridicată de compilator.

Cel puțin, nu exact. 

Amintiți-vă cum am putut să sunăm spart, dar a primit o Eroare de scris pentru că am încercat să executăm nedefinit? Dacă am fi definit spart cu lăsa, am fi ajuns ReferenceError, in schimb:

"folosiți stricte"; // Trebuie să utilizați "stricte" pentru a încerca acest lucru în Nod rupt (); // ReferenceError! let broken = function () alert ("Această avertizare nu va apărea!"); 

Atunci când compilatorul JavaScript înregistrează variabilele la domeniile lor în prima sa trecere, acesta tratează lăsa și const diferit decât face var

Când găsește a var declarație, vom înregistra numele variabilei în domeniul său de aplicare și imediat vom inițializa valoarea acesteia nedefinit.

Cu lăsa, cu toate acestea, compilatorul face înregistrați variabila în domeniul său de aplicare, dar nuinițializați valoarea acestuia nedefinit. În schimb, ea lasă variabila neinitializată, pana cand motorul execută instrucțiunea de atribuire. Accesarea valorii unei variabile neinitializate aruncă a ReferenceError, ceea ce explică de ce fragmentul de mai sus aruncă atunci când îl executăm.

Spațiul dintre începutul părții superioare a domeniului lăsa declarația și instrucțiunea de atribuire se numește Zona Dead Zone temporară. Numele vine de la faptul că, chiar dacă motorul știe despre o variabilă numită foo în partea de sus a domeniului de aplicare al bar, variabila este "moartă", deoarece nu are valoare.

De asemenea, pentru că vă va ucide programul dacă încercați să îl utilizați mai devreme.

 const cuvântul cheie funcționează la fel ca lăsa, cu două diferențe cheie:

  1. Tu trebuie sa atribuiți o valoare atunci când declarați cu const.
  2. Tu nu poti realocați valorile la o variabilă declarată cu const.

Acest lucru garantează acest lucru const voi mereuau valoarea pe care ați atribuit-o inițial.

// Aceasta este legala const React = require ('react'); // Aceasta nu este în întregime legală cri cripto; crypto = necesită ('crypto');

Domeniul de acoperire

lăsa și const sunt diferite de var într-un alt mod: dimensiunea domeniilor lor.

Când declarați o variabilă cu var, este vizibil cât mai sus în lanțul de aplicare posibil - de obicei, în partea superioară a celei mai apropiate declarații de funcții sau în sfera globală, dacă îl declarați la nivel superior. 

Când declarați o variabilă cu lăsa sau const, totuși, este vizibil ca la nivel local pe cat posibil-numai în cel mai apropiat bloc.

bloc este o secțiune a codului declanșată de acolade, așa cum vedeți cu dacă/altfel blocuri, pentru bucle și în blocuri explicite "blocate" de cod, ca în acest fragment.

"folosiți stricte"; lăsați foo = "foo"; dacă (foo) const bar = "bar"; var foobar = foo + bar; console.log ("Pot vedea" + bar + "în acest bloc.");  try console.log ("Pot vedea" + foo + "în acest bloc, dar nu" + bar + ".);  captură (err) console.log ("Ați primit" + err + ".);  încercați console.log (foo + bar); // Aruncă din cauza "foo", dar ambele sunt nedefinite catch (err) console.log ("Ați primit doar" + err + ".);  console.log (foobar); // Merge bine

Dacă declarați o variabilă cu const sau lăsa în interiorul unui bloc, este numai vizibil în interiorul blocului și numai după ce l-ați desemnat.

O variabilă declarată cu var, este totuși vizibilă cât mai departe posibil-în acest caz, în domeniul global.

Dacă sunteți interesat de detaliile de tip dură lăsa și const, verificați ce are de spus Dr. Rauschmayer despre ei în Explorarea ES6: Variabile și scopuri și aruncați o privire la documentația MDN pe ele.  

Lexical acest & Funcții cu săgeți

La suprafață, acest nu pare să aibă o mulțime de a face cu domeniul de aplicare. Și, de fapt, JavaScript nu nu rezolva sensul acest în conformitate cu regulile de domeniu pe care le-am vorbit aici.

Cel puțin, nu de obicei. JavaScript, notoriu, nu nu rezolva sensul acest cuvânt cheie bazat pe locul în care l-ați folosit:

var foo = nume: 'Foo', limbi: ['spaniolă', 'franceză', 'italiană'], vorbesc: functie vorbesc () this.languages.forEach (funcția (limbă) console.log nume + "vorbește" + limbă + ".");); foo.speak ();

Cei mai mulți dintre noi s-ar aștepta acest a insemna foo în interiorul pentru fiecare buclă, pentru că asta însemna chiar în afara ei. Cu alte cuvinte, ne așteptăm ca JavaScript să rezolve semnificația acest lexical.

Dar nu.

În schimb, creează o nou acest în interiorul fiecărei funcții pe care o definiți și decide ce înseamnă pe el Cum numiți funcția - nu Unde ai definit-o.

Acest prim punct este similar cu cel al redefinirii orice variabilă în domeniul copilului:

funcția foo () var bar = "bar"; function baz () // Reutilizarea numelor de variabile ca aceasta se numește "shadowing" var bar = "BAR"; console.log (bar); // BAR baz ();  foo (); // BAR

A inlocui bar cu acest, și totul ar trebui să se limpezească instantaneu!

În mod tradițional, obținerea acest să lucrăm așa cum ne așteptăm ca variabilele vechi lexicale vechi obișnuite să funcționeze necesită una din cele două soluții:

var foo = nume: 'Foo', limbi: ['spaniolă', 'franceză', 'italiană'], speak_self: funcția speak_s () var self = this; auto.languages.forEach (functie (limbă) console.log (auto.name + "vorbește" + limbă + ".");), speak_bound: funcția speak_b () this.languages.forEach ) console.log (acest nume + "vorbește" + limbă + "."); .bind (foo)); // Mai frecvent: .bind (acest lucru); ;

În speak_self, am salvat sensul acest la variabila de sine, si foloseste acea pentru a obține referința pe care o dorim. În speak_bound, folosim lega la permanent punct acest la un obiect dat.

ES2015 ne aduce o nouă alternativă: funcțiile săgeților.

Spre deosebire de funcțiile "normale", funcțiile săgeților fac nu umbra scopurilor părintelui lor acest valoare prin stabilirea propriilor lor. Mai degrabă, ei își rezolvă semnificația lexical. 

Cu alte cuvinte, dacă folosiți acest într-o funcție de săgeată, JavaScript își caută valoarea așa cum ar fi orice altă variabilă.

În primul rând, verifică domeniul de aplicare local pentru o acest valoare. Deoarece funcțiile săgeților nu stabilesc una, nu va găsi una. Apoi, verifică mamă domeniul de aplicare pentru o acest valoare. Dacă găsește una, va folosi asta, în schimb.

Acest lucru ne permite să rescrieți codul de mai sus astfel:

var foo = nume: 'Foo', limbi: ['spaniolă', 'franceză', 'italiană'], vorbesc: function speak () this.languages.forEach ((language) .name + "vorbește" + limbă + "."););   

Dacă doriți mai multe detalii cu privire la funcțiile săgeților, aruncați o privire la cursul excelent al lui Envato Tuts + Instructor Dan Wellman privind JavaScript ES6 Fundamentals, precum și documentația MDN privind funcțiile săgeților.

Concluzie

Am acoperit mult teren până acum! În acest articol, ați aflat că:

  • Variabilele sunt înregistrate în scopurile lor în timpul compilare, și asociate cu valorile lor de atribuire în timpul execuţie.
  • Referindu - se la variabilele declarate culăsa sau const înainte de alocare aruncă a ReferenceError, și că astfel de variabile se referă la cel mai apropiat bloc.
  • Funcțiile cu săgeține permite să realizăm legarea lexicală a acest, și ocolind legarea dinamică tradițională.

De asemenea, ați văzut cele două reguli de ridicare:

  •  Prima regulă de ridicare: Această expresie a funcției și var declarațiile sunt disponibile în toate domeniile în care sunt definite, dar au valoarea nedefinit până când executa declarațiile de atribuire.
  •  A doua regulă de ridicare: Că numele declarațiilor de funcții și corpurile lor sunt disponibile în toate domeniile în care sunt definite.

Un pas bun următor este să vă folosiți cunoștințele de bază ale domeniului JavaScript pentru a vă împacheta capul în jurul închiderilor. Pentru asta, verificați Scopes & Closures lui Kyle Simpson.

În sfârșit, mai sunt multe de spus despre asta acest decât am putut să acopăr aici. În cazul în care cuvântul cheie pare încă o magie neagră, aruncați o privire asupra acestei prototipuri Object Prototypes pentru a obține capul în jurul său.

În același timp, ia ceea ce ați învățat și mergeți să scrieți mai puține bug-uri!

Aflați JavaScript: Ghidul complet

Am creat un ghid complet care vă ajută să învățați JavaScript, indiferent dacă începeți doar ca dezvoltator web sau doriți să explorați subiecte mai avansate.

Cod