Pytanie Jaki jest zakres zmiennych w JavaScript?


Jaki jest zakres zmiennych w javascript? Czy mają ten sam zasięg w środku, a nie poza funkcją? Czy to nawet ma znaczenie? Gdzie są przechowywane zmienne, jeśli są zdefiniowane globalnie?


1715
2018-02-01 08:27


pochodzenie


Oto kolejny miły połączyć aby wziąć pod uwagę tę kwestię: "Wyjaśnienie zakresu i zamknięć JavaScript". - Full-Stack Software Engineer
Oto artykuł, który bardzo ładnie to wyjaśnia. Wszystko, co musisz wiedzieć o zakresie zmiennych JavaScript - Saurab Parakh
The poprzednio wspomniane e-book Kyle'a Simpsona jest dostępny do czytania na Githubie i zawiera wszystkie niezbędne informacje na temat zakresów i zamknięć JavaScript. Możesz go znaleźć tutaj: github.com/getify/You-Dont-Know-JS/blob/master/... To część Seria książek "Nie znasz JS", co jest dobre dla wszystkich, którzy chcieliby dowiedzieć się więcej o JavaScript. - 3rik82


Odpowiedzi:


Myślę, że najlepsze, co mogę zrobić, to podać kilka przykładów do studiowania. Programiści JavaScript są praktycznie klasyfikowani według tego, jak dobrze rozumieją zakres. Czasami może to być całkiem sprzeczne z intuicją.

  1. Zmienna o zasięgu globalnym

    // global scope
    var a = 1;
    
    function one() {
      alert(a); // alerts '1'
    }
    
  2. Zakres lokalny

    // global scope
    var a = 1;
    
    function two(a) {
      // local scope
      alert(a); // alerts the given argument, not the global value of '1'
    }
    
    // local scope again
    function three() {
      var a = 3;
      alert(a); // alerts '3'
    }
    
  3. Pośredni: Nie ma czegoś takiego jak zakres bloków w JavaScript (ES5; ES6 wprowadza let)

    za.

    var a = 1;
    
    function four() {
      if (true) {
        var a = 4;
      }
    
      alert(a); // alerts '4', not the global value of '1'
    }
    

    b.

    var a = 1;
    
    function one() {
      if (true) {
        let a = 4;
      }
    
      alert(a); // alerts '1' because the 'let' keyword uses block scoping
    }
    
  4. Pośredni: Właściwości obiektu

    var a = 1;
    
    function Five() {
      this.a = 5;
    }
    
    alert(new Five().a); // alerts '5'
    
  5. zaawansowane: Zamknięcie

    var a = 1;
    
    var six = (function() {
      var a = 6;
    
      return function() {
        // JavaScript "closure" means I have access to 'a' in here,
        // because it is defined in the function in which I was defined.
        alert(a); // alerts '6'
      };
    })();
    
  6. zaawansowane: Prototypowa rozdzielczość zakresu

    var a = 1;
    
    function seven() {
      this.a = 7;
    }
    
    // [object].prototype.property loses to
    // [object].property in the lookup chain. For example...
    
    // Won't get reached, because 'a' is set in the constructor above.
    seven.prototype.a = -1;
    
    // Will get reached, even though 'b' is NOT set in the constructor.
    seven.prototype.b = 8;
    
    alert(new seven().a); // alerts '7'
    alert(new seven().b); // alerts '8'
    

  7. Globalny + lokalny: Dodatkowy skomplikowany przypadek

    var x = 5;
    
    (function () {
        console.log(x);
        var x = 10;
        console.log(x); 
    })();
    

    To zostanie wydrukowane undefined i 10 zamiast 5 i 10 ponieważ JavaScript zawsze przenosi deklaracje zmiennych (bez inicjalizacji) na szczyt zakresu, czyniąc kod równoważny z:

    var x = 5;
    
    (function () {
        var x;
        console.log(x);
        x = 10;
        console.log(x); 
    })();
    
  8. Zmienna o wielkości klauzuli catch

    var e = 5;
    console.log(e);
    try {
        throw 6;
    } catch (e) {
        console.log(e);
    }
    console.log(e);
    

    To zostanie wydrukowane 5, 6, 5. Wewnątrz klauzuli catch e cienie zmienne globalne i lokalne. Ale ten specjalny zakres dotyczy tylko złapanej zmiennej. Jeśli piszesz var f; wewnątrz klauzuli catch, to jest dokładnie tak samo, jak gdybyś zdefiniował ją przed lub po bloku try-catch.


2267
2018-02-01 08:58



Nawet nie będąc w pełni wszechstronnym, ale jest to być może niezbędny zestaw sztuczek JavaScript, których trzeba efektywnie jeszcze ZAPEWNIĆ nowoczesny javascript. - Triptych
Wysoko oceniona odpowiedź, nie wiem dlaczego. Jest to tylko kilka przykładów bez odpowiedniego wyjaśnienia, a następnie wydaje się mylić dziedziczenie prototypów (to jest rozdzielczość właściwości) z łańcuchem zasięgu (to jest zmienną rozdzielczością). Kompleksowe (i dokładne) wyjaśnienie zakresu i właściwości znajduje się w pliku comp.lang.javascript Często zadawane pytania. - RobG
@RobG Jest wysoko oceniany, ponieważ jest przydatny i zrozumiały dla szerokiej gamy programistów, bez względu na kataklizm. Opublikowany link, choć przydatny dla niektórych profesjonalistów, jest niezrozumiały dla większości osób piszących dziś Javascript. Możesz rozwiązać problemy związane z nomenklaturą, edytując odpowiedź. - Triptych
@ Tryptyk - ja tylko edytuję odpowiedzi, aby naprawić drobne rzeczy, a nie poważne. Zmiana "zakresu" na "własność" naprawi błąd, ale nie będzie kwestii mieszania dziedziczenia i zakresu bez wyraźnego rozróżnienia. - RobG
Jeśli zdefiniujesz zmienną w zewnętrznym zasięgu, a następnie będziesz miał instrukcję if, zdefiniuj zmienną wewnątrz funkcji o tej samej nazwie, nawet jeśli nie zostanie osiągnięty oddział to jest na nowo zdefiniowane. Przykład - jsfiddle.net/3CxVm - Chris S


JavaScript używa łańcuchów zasięgu do ustalenia zakresu dla danej funkcji. Zwykle istnieje jeden zasięg globalny, a każda zdefiniowana funkcja ma własny zakres zagnieżdżony. Każda funkcja zdefiniowana w ramach innej funkcji ma zasięg lokalny powiązany z funkcją zewnętrzną. To zawsze pozycja w źródle określa zakres.

Element w łańcuchu zasięgu jest w zasadzie Mapą ze wskaźnikiem do jej zakresu nadrzędnego.

Podczas rozwiązywania zmiennej javascript zaczyna się w najgłębszym zakresie i wyszukuje na zewnątrz.


219
2018-02-01 08:35



Łańcuchy zasięgu to inne określenie [pamięci] Zamknięcia... dla tych, którzy czytają tutaj, aby nauczyć się / dostać do javascript. - New Alexandria


Zmienne deklarowane globalnie mają zasięg globalny. Zmienne zadeklarowane w funkcji są ograniczone do tej funkcji i śledzą zmienne globalne o tej samej nazwie.

(Jestem pewien, że istnieje wiele subtelności, które prawdziwi programiści JavaScript będą w stanie wskazać w innych odpowiedziach. ta strona o czym dokładnie this oznacza w dowolnym momencie. Ufnie ten bardziej wstępny link wystarczy, abyś zaczął.)


93
2018-02-01 08:31



Boję się nawet zacząć odpowiadać na to pytanie. Jako programista JavaScript, wiem, jak szybko odpowiedź może wymknąć się spod kontroli. Ładne artykuły. - Triptych
@ Triptych: Wiem, co masz na myśli, gdy sprawy wymykają się spod kontroli, ale Proszę dodaj odpowiedź mimo to. Otrzymałem powyższe wyniki po zrobieniu kilku wyszukiwań ... odpowiedź napisana przez kogoś z faktycznym doświadczeniem granica by być lepszym. Proszę poprawić moją odpowiedź, która jest zdecydowanie zła! - Jon Skeet
Jakimś sposobem Jon Skeet jest odpowiedzialny za MOJĄ najbardziej popularną odpowiedź na przepełnienie stosu. - Triptych


Old school JavaScript

Tradycyjnie JavaScript ma tylko dwa rodzaje zasięgu:

  1. Zakres globalny : Zmienne są znane w całej aplikacji, od początku aplikacji (*)
  2. Zakres funkcjonalny : Zmienne są znane wewnątrz funkcja są one zadeklarowane od początku funkcji (*)

Nie będę się nad tym rozwodził, ponieważ istnieje już wiele innych odpowiedzi wyjaśniających różnicę.


Nowoczesny JavaScript

The najnowsze specyfikacje JavaScript teraz także pozwalają na trzeci zakres:

  1. Zakres blokowy : Zmienne są znane wewnątrz bloksą one zadeklarowane od momentu ich zadeklarowania (**)

Jak utworzyć zmienne zakresu bloku?

Tradycyjnie tworzysz swoje zmienne w ten sposób:

var myVariable = "Some text";

Zmienne zasięgu bloku są tworzone w następujący sposób:

let myVariable = "Some text";

Jaka jest więc różnica między zakresem funkcjonalnym a zakresem bloku?

Aby zrozumieć różnicę między zakresem funkcjonalnym a zakresem bloku, należy rozważyć następujący kod:

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

Tutaj widzimy naszą zmienną j jest znana tylko w pierwszej pętli for, ale nie przed i po. A jednak nasza zmienna i jest znana w całej funkcji.

Należy również wziąć pod uwagę, że zmienne o zakresie bloków nie są znane, zanim nie zostaną zadeklarowane, ponieważ nie zostały podniesione. Nie możesz także redeclare tej samej zmiennej zakresu bloku w tym samym bloku. Powoduje to, że zmienne o ograniczonym zakresie kodu są mniej podatne na błędy niż globalnie lub zmienne o zasięgu funkcjonalnym, które są podnoszone i które nie powodują błędów w przypadku wielu deklaracji.


Czy dziś bezpiecznie można używać zmiennych zakresu bloków?

To, czy dziś jest bezpieczne, zależy od środowiska:

  • Jeśli piszesz kod JavaScript po stronie serwera (Node.js), możesz bezpiecznie korzystać z let komunikat.

  • Jeśli piszesz kod JavaScript po stronie klienta i używasz transpilatora (np Traceur), możesz bezpiecznie korzystać z let oświadczenie, jednak twój kod prawdopodobnie nie będzie optymalny pod względem wydajności.

  • Jeśli piszesz kod JavaScript po stronie klienta i nie używasz transpilatora, musisz rozważyć obsługę przeglądarki.

    Dzisiaj, 23 lutego 2016 r., Są to niektóre przeglądarki, które albo nie obsługują let lub mają tylko częściowe wsparcie:

    • Internet Explorer 10 i poniżej (brak wsparcia)
    • Firefox 43 i poniżej (brak wsparcia)
    • Safari 9 i poniżej (brak wsparcia)
    • Opera Mini 8 i poniżej (brak wsparcia)
    • Przeglądarka Androida 4 i poniżej (brak wsparcia)
    • Opera 36 i poniżej (częściowe wsparcie)
    • Chome 51 i poniżej (częściowe wsparcie)

enter image description here


Jak śledzić obsługę przeglądarki

Aby uzyskać aktualny przegląd obsługiwanych przeglądarek let oświadczenie w momencie czytania tej odpowiedzi, patrz to Can I Use strona.


(*) Zmienne globalne i funkcjonalne można zainicjować i używać przed ogłoszeniem, ponieważ są to zmienne JavaScript podniesiony. Oznacza to, że deklaracje są zawsze bardzo ważne.

(**) Zmienne o zakresie blokowym nie są podnoszone


55
2018-02-23 18:51



"NIE JEST znana" wprowadza w błąd, ponieważ zmienna jest tam zadeklarowana ze względu na podnoszenie. - Oriol
Powyższy przykład wprowadza w błąd, zmienne "i" i "j" nie są znane poza blokiem. Zmienne "Let" mają zasięg tylko w tym konkretnym bloku poza blokiem. Niech ma też inne zalety, nie można ponownie redeclare zmienną i ma to zasięg leksykalny. - zakir
@Oriol: W końcu poprawiłem odpowiedź i podniosłem adres. Dzięki za wskazanie mojej odpowiedzi potrzebowałem poprawy. Wprowadziłem także kilka innych ulepszeń. - John Slegers
@ JonSchneider: Dokładnie! Tam, gdzie mówię "oldschoolowy JavaScript", mówię o ECMAScript 5 i tam, gdzie mówię o "nowoczesnym JavaScriptu", zajmuję się ECMAScript 6 (inaczej ECMAScript 2015). Nie sądziłem jednak, że bardzo ważne jest, aby przejść do szczegółów tutaj, ponieważ większość ludzi po prostu chce wiedzieć (1) jaka jest różnica między zakresem bloku a zakresem funkcjonalnym, (2) jakie przeglądarki obsługują zakres bloku i (3) czy dzisiaj można bezpiecznie używać zakresu blokowego dla każdego projektu, nad którym pracują. Skupiłem się więc na odpowiedzi na te problemy. - John Slegers
@JonSchneider: (kontynuacja) Dodałem jednak link do artykułu Smashing Magazine na temat ES6 / ES2015 dla tych, którzy chcą dowiedzieć się więcej o tym, które funkcje zostały dodane do JavaScript w ciągu ostatnich kilku lat ... kogokolwiek, kto może zastanawiać się, co mam na myśli mówiąc "nowoczesny JavaScript". - John Slegers


Oto przykład:

<script>

var globalVariable = 7; //==window.globalVariable

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function
  var scopedToFunction = {
    //can't be accessed outside of this function

    nested : 3 //accessible by: scopedToFunction.nested
  };

  anotherGlobal = {
    //global because there's no `var`
  }; 

}

</script>

Będziesz chciał zbadać zamknięcia i sposób ich wykorzystania członkowie prywatni.


35
2018-02-01 08:48





Kluczem, jak rozumiem, jest to, że JavaScript ma zasięg na poziomie funkcji w porównaniu z bardziej powszechnym scopingiem w bloku C.

Oto dobry artykuł na ten temat.


28
2018-05-15 17:38





W "Javascript 1.7" (rozszerzenie Mozilli do Javascript) można również zadeklarować zmienne zakresu blokowego let komunikat:

 var a = 4;
 let (a = 3) {
   alert(a); // 3
 }
 alert(a);   // 4

23
2018-04-06 11:19



Tak, ale czy można go bezpiecznie używać? Chodzi mi o to realistycznie wybrać tę implementację, jeśli mój kod będzie działać w WebKit? - Igor Ganapolsky
@Python: Nie, WebKit nie obsługuje let. - kennytm
Sądzę, że jedynym ważnym zastosowaniem tego byłoby, gdybyś wiedział, że wszyscy klienci będą używać przeglądarki Mozilla, jak w wewnętrznym systemie firmy. - GazB
Lub jeśli programujesz używając XUL framework, framework interfejsu Mozilli, gdzie budujesz używając css, xml i javascript. - Gerard ONeill
@GazB nawet to jest okropny pomysł! Więc dzisiaj wiesz, że twoi klienci używają Mozilli, a potem pojawia się nowa notatka stwierdzająca, że ​​teraz używają czegoś innego. TO ZNACZY. powód, dla którego nasz system płatniczy jest do bani ... Musisz używać IE8 i nigdy IE9, IE10, Firefoxa czy Chrome, ponieważ nie będzie działać ... - buzzsawddog


Pomysł na scoping w JavaScript, gdy został pierwotnie zaprojektowany przez Brendan Eich pochodził z HyperCard język skryptowy HyperTalk.

W tym języku wyświetlacze były wykonane podobnie jak stos kart indeksowych. Była karta główna określana jako tło. Był przezroczysty i można go było zobaczyć jako dolną kartę. Wszelkie treści na tej podstawowej karcie zostały udostępnione z umieszczonymi na niej kartami. Każda umieszczona na niej karta miała swoją własną treść, która miała pierwszeństwo przed poprzednią kartą, ale nadal miała dostęp do wcześniejszych kart, jeśli było to pożądane.

Dokładnie tak zaprojektowano system scopingu JavaScript. Ma tylko różne nazwy. Karty w JavaScript są znane jako Konteksty wykonaniaECMA. Każdy z tych kontekstów zawiera trzy główne części. Zmienne środowisko, środowisko leksykalne i to powiązanie. Wracając do odniesienia do karty, środowisko leksykalne zawiera całą treść z poprzednich kart znajdujących się niżej w stosie. Bieżący kontekst znajduje się na szczycie stosu, a zadeklarowana zawartość zostanie zapisana w zmiennym środowisku. Środowisko zmiennych będzie miało pierwszeństwo w przypadku nazwania kolizji.

To powiązanie wskaże obiekt zawierający. Czasami zakresy lub konteksty wykonania zmieniają się bez zmiany obiektu zawierającego, na przykład w zadeklarowanej funkcji, w której może znajdować się obiekt zawierający window lub funkcja konstruktora.

Te konteksty wykonawcze są tworzone za każdym razem, gdy przekazywana jest kontrola. Sterowanie jest przenoszone, gdy kod zaczyna się wykonywać, a dzieje się tak przede wszystkim w wyniku wykonania funkcji.

Oto wyjaśnienie techniczne. W praktyce należy pamiętać o tym w JavaScript

  • Zakresy są technicznie "Kontekstami wykonania"
  • Konteksty tworzą zbiór środowisk, w których przechowywane są zmienne
  • Górna część stosu ma pierwszeństwo (dno jest kontekstem globalnym)
  • Każda funkcja tworzy kontekst wykonania (ale nie zawsze jest to nowe powiązanie)

Stosując to do jednego z poprzednich przykładów (5. "Zamknięcie") na tej stronie, możliwe jest śledzenie stosu kontekstów wykonawczych. W tym przykładzie są trzy konteksty w stosie. Są one definiowane przez kontekst zewnętrzny, kontekst w funkcji wywoływanej bezpośrednio przez zmienną szóstą oraz kontekst w funkcji zwracanej wewnątrz funkcji natychmiastowej wywoływanej przez zmienną szóstą.

ja) Zewnętrzny kontekst. Ma zmienne środowisko a = 1
ii) Kontekst IIFE, ma środowisko leksykalne a = 1, ale zmienne środowisko a = 6, które ma pierwszeństwo w stosie
iii) Zwrócony kontekst funkcji, ma środowisko leksykalne a = 6 i jest wartością przywoływaną w alarmie po wywołaniu.

enter image description here


18
2017-09-14 20:29