Dlaczego niektóre zmienne zadeklarowane za pomocą opcji let wewnątrz funkcji stają się dostępne w innej funkcji, a inne powodują błąd odniesienia?

158

Nie rozumiem, dlaczego zmienne działają tak dziwnie, gdy są deklarowane w funkcji.

  1. W firstfunkcji deklaruję letzmiennymi bi cwartością 10 :

    b = c = 10;

    W secondfunkcji pokazuję:

    b + ", " + c

    A to pokazuje:

    10, 10
  2. Również w firstfunkcji deklaruję awartością 10 :

    let a = b = c = 10;

    Ale w secondfunkcji pokazuje błąd:

    Nie można znaleźć zmiennej: a

  3. Teraz w firstfunkcji deklaruję dz wartością 20 :

    var d = 20;

    Ale w secondfunkcji pokazuje ten sam błąd co poprzednio, ale ze zmienną d:

    Nie można znaleźć zmiennej: d

Przykład:

function first() {
  let a = b = c = 10;
  var d = 20;
  second();
}

function second() {
  console.log(b + ", " + c); //shows "10, 10"

  try{ console.log(a); }  // Rreference error
  catch(e){ console.error(e.message) }

  try{ console.log(d); } // Reference error
  catch(e){ console.error(e.message) }
}
first()

beczenie interteiment
źródło
31
Jesteś deklarowania globalnych, ponieważ bi cnie są poprzedzone ze varsłów kluczowych. ai dsą lokalni first.
VLAZ
1
Komentarze nie są przeznaczone do rozszerzonej dyskusji; na czacie została zarchiwizowana styczna rozmowa o tym, czy byłoby to dobre pytanie do rozmowy kwalifikacyjnej .
Cody Gray
1
To przypomina mi podobną sytuację w Visual Basic; Dim Apple, Banana, Pear As Fruitoznacza Dim Apple / Dim Banana / Dim Pear As Fruit, a nie Dim Apple As Fruit / ....
Eric Lippert

Odpowiedzi:

179

To dlatego, że tak naprawdę mówisz:

c = 10;
b = c;
let a = b;

I nie to, co myślisz, co mówisz:

let a = 10;
let b = 10;
let c = 10;

Zauważysz, że bez względu na to, ile zmiennych dodasz do łańcucha, to tylko pierwsza (a) spowoduje błąd.

Wynika to z faktu, że „pozwól” zakresuje zmienną do bloku (lub „lokalnie”, co mniej więcej oznacza „w nawiasach”), w którym ją deklarujesz.

Jeśli zadeklarujesz zmienną bez „let”, będzie ona mierzyć zasięg globalnie.

Tak więc w funkcji, w której ustawiasz zmienne, wszystko otrzymuje wartość 10 (możesz to zobaczyć w debuggerze, jeśli umieścisz punkt przerwania). Jeśli w pierwszej funkcji umieścisz dziennik konsoli dla a, b, c, wszystko będzie dobrze.

Ale jak tylko opuścisz tę funkcję, pierwsza (a) - i znowu, pamiętaj, technicznie w kolejności przypisania, jest to ostatnia - „znika” (znowu, możesz to zobaczyć w debugger, jeśli ustawisz punkt przerwania w drugiej funkcji), ale pozostałe dwa (lub dowolna ich liczba) są nadal dostępne.

Wynika to z tego, że „pozwól” dotyczy TYLKO (więc tylko lokalnie zakresów) PIERWSZEJ ZMIENNEJ - znowu, która jest technicznie ostatnią zadeklarowaną i przypisaną wartością - w łańcuchu. Reszta technicznie nie ma „przed sobą”. Więc są one technicznie deklarowane globalnie (to znaczy na obiekcie globalnym), dlatego pojawiają się w twojej drugiej funkcji.

Spróbuj: usuń słowo kluczowe „let”. Wszystkie twoje zmienne będą teraz dostępne.

„var” ma podobny efekt lokalny, ale różni się sposobem, w jaki zmienna jest „podnoszona”, co zdecydowanie powinieneś zrozumieć, ale które nie jest bezpośrednio związane z twoim pytaniem.

(BTW, to pytanie zaskoczyłoby wystarczająco wielu profesjonalnych programistów JS, aby było dobre).

Zdecydowanie sugeruj spędzanie czasu z różnicami w sposobie deklarowania zmiennych w JS: bez słowa kluczowego, z „let” i „var”.

Tim Consolazio
źródło
4
Jest to jednocześnie najlepsza i najgorsza rzecz w programowaniu: komputer zrobi dokładnie to , co mu każesz. Niekoniecznie to, co zamierzałeś powiedzieć. Programy są idealne. Tworzymy problemy.
Niet the Dark Absol
8
@Thevs Dlaczego zaleca varsię letw kontekście tej odpowiedzi? Nie rozumiem.
Klaycon
4
@ Thevs Zdecydowanie się z tobą nie zgadzam. varmoże być podatny na błędy, jeśli jest używany niedbale. Sprawdź to skrzypce
Cid
2
@Thevs w jakim przypadku varma jakąkolwiek przewagę let? Pozwól, że wyjaśnię: nowoczesny kontekst, w którym obie opcje są opcjami, i proszę o kod, który należy napisać. Kiedy już o to pytałem, otrzymałem odpowiedzi na temat „możesz ponownie zadeklarować zmienną za pomocą var”, w którym to przypadku muszę przypomnieć ludziom, że nie powinieneś ponownie deklarować zmiennych . Jest to albo błąd, albo błąd w logice kodu - więc zaletą ponownej deklaracji jest ... to, że pozwala ona napisać wadliwy kod. Mam jeszcze zobaczyć żadnego sensownego powodu, z przedzierania varkiedy letjest również opcja.
VLAZ
2
Wykreśl kolejny znak przeciwko javascript. Wszystkie zmienne są globalne, chyba że zadeklarowane lokalnie. :(
JRE
68

W funkcji first()zmienne bi csą tworzone w locie, bez użycia varlub let.

let a = b = c = 10; // b and c are created on the fly

Jest inny niż

let a = 10, b = 10, c = 10; // b and c are created using let (note the ,)

Stają się domyślnie globalne. Dlatego są dostępne wsecond()

Z dokumentacji

Przypisanie wartości do niezadeklarowanej zmiennej domyślnie tworzy ją jako zmienną globalną (staje się własnością obiektu globalnego) podczas przypisywania.

Aby tego uniknąć, możesz użyć "use strict", który zapewni błędy, gdy użyjesz niezadeklarowanej zmiennej

"use strict"; // <-------------- check this

function first() {
   /*
    * With "use strict" c is not defined.
    * (Neither is b, but since the line will be executed from right to left,
    * the variable c will cause the error and the script will stop)
    * Without, b and c become globals, and then are accessible in other functions
    */
   let a = b = c = 10;
   var d = 20;
   second();
}

function second() {
   console.log(b + ", " + c); //reference error
   console.log(a); //reference error
   console.log(d); //reference error
}

first();

Cid
źródło
15
Ponadto: let a = 10, b = 10, c = 10;lub let a, b, c; a = b = c = 10;inaczej byłby prawidłowy sposób deklarowania zmiennych.
Rickard Elimää
Więc w trybie ścisłym, co ze zmienną b?
Tick20
2
@ Tick20 zmienna bnie zostanie oceniona / osiągnięta, błąd wystąpi w linii let a = b = c = 10;, czytany od prawej do lewej . cjako pierwsza zmienna powodująca ReferenceError, że pozostała część wiersza nie zostanie wykonana (skrypt został zatrzymany)
Cid
2
coś w tym rodzaju let a = 10, b = a, c = b;jest również ważne
Kaddath
8
głosowano głównie za „use strict”. W kontekście pytania do rozmowy kwalifikacyjnej byłby to również początek mojego komentarza do tego kodu.
Pac0
23

Przed nazwaniem rzeczy dziwnymi, najpierw zapoznaj się z podstawami:

Zmienne var i let są używane do deklaracji zmiennych w JavaScript. Na przykład,

var one = 1;
let two = 2;

Zmienne można również deklarować bez użycia varlub let. Na przykład,

three = 3;

Teraz różnica między powyższych rozwiązań jest to, że:

var ma zakres funkcji

i

let ma zakres blokowy.

podczas gdy zakres zmiennych zadeklarowanych bez var/ letsłowa kluczowego staje się globalny, niezależnie od tego, gdzie jest zadeklarowany.

Dostęp do zmiennych globalnych można uzyskać z dowolnego miejsca na stronie internetowej (niezalecane, ponieważ globale można przypadkowo zmodyfikować).

Teraz zgodnie z tymi pojęciami spójrzmy na dany kod:

 function first() {
   let a = b = c = 10;
   /* The above line means:
    let a=10; // Block scope
    b=10; // Global scope
    c=10; // Global scope
    */

   var d = 20; // Function scope
   second();
}

function second() {
   alert(b + ", " + c); // Shows "10, 10" //accessible because of global scope
   alert(a); // Error not accessible because block scope has ended
   alert(d); // Error not accessible because function scope has ended
}
fatimasajjad
źródło
1
Plagiatowałeś część odpowiedzi JonoJames . Dlaczego?
Peter Mortensen
2
Przepraszam, ale nie miałem takich zamiarów, może być coś podobnego, ponieważ mogliśmy zebrać informacje z tego samego źródła.
fatimasajjad
2
Możliwe, że obie odpowiedzi zawierają treść skopiowaną z tego samego oryginalnego źródła bez widocznego cytowania - prawdopodobnie tutorialsteacher.com/javascript/javascript-variable . Obecność plagiatu jest widoczna, ponieważ błąd gramatyczny jest odtwarzany z oryginału - „zakres zmiennych zadeklarowanych bez varsłowa kluczowego staje się globalny bez względu na to, gdzie jest zadeklarowany”, powinien być albo „zasięg ... staje się”, albo „ zakresy ... stają się " . Używanie cudzych słów wymaga cytowania, czy to stąd, czy gdzie indziej. meta.stackexchange.com/q/160071/211183
Michael - sqlbot
Dzięki chłopaki, dodałem link referencyjny do źródła.
fatimasajjad
6

Zmienne używające letsłowa kluczowego powinny być dostępne tylko w zakresie bloku i niedostępne w funkcji zewnętrznej ...

Każda zmienna, którą deklarujesz w ten sposób, nie używa letlub var. W deklaracji zmiennych brakuje przecinka.

Nie zaleca się deklarowania zmiennej bez varsłowa kluczowego. Może przypadkowo zastąpić istniejącą zmienną globalną. Zakres zmiennych zadeklarowanych bez varsłowa kluczowego staje się globalny bez względu na to, gdzie jest zadeklarowany. Do zmiennych globalnych można uzyskać dostęp z dowolnego miejsca na stronie internetowej.

function first() {
   let a = 10;
   let b = 10;
   let c = 10;
   var d = 20;
   second();
}

function second() {
   console.log(b + ", " + c); //shows "10, 10"
   console.log(a); //reference error
   console.log(d); //reference error
}

first();

JonoJames
źródło
3

Wynika to z tego, że kiedy nie używasz letlub varzmienna jest deklarowana w locie, lepiej zadeklaruj następującą.

let a = 10;
let b = 10;
let c = 10;
Mac Rathod
źródło
2

Dziwny problem jest spowodowany przez reguły zakresu w JavaScript

function first() {
   let a = b = c = 10; // a is in local scope, b and c are in global scope
   var d = 20; // d is in local scope
   second(); // will have access to b and c from the global scope
}

Zakładając, że chcesz zadeklarować 3 zmienne lokalne zainicjowane na tę samą wartość (100). Twój pierwszy () będzie wyglądał jak poniżej. W takim przypadku second () nie będzie miał dostępu do żadnej ze zmiennych, ponieważ są one lokalne dla first ()

function first() {
   let a = 100; // a is in local scope init to 100
   let b = a; // b is in local scope init to a
   let c = b // c is in local scope init to b

   var d = 20; // d is in local scope
   second(); // will not have access a, b, c, or d
}

Jeśli jednak chcesz zmiennych globalnych, twoja pierwsza () będzie wyglądać jak poniżej. W takim przypadku drugi będzie miał dostęp do wszystkich zmiennych, ponieważ mają one zasięg globalny

function first() {
   a = 100; // a is in global scope
   b = a; // b is in global scope
   c = b // c is in global scope

   d = 20; // d is in global scope
   second(); // will have access to a, b, c, and d from the global scope
}

Zmienne lokalne (inaczej. Dostępne w bloku kodu, w którym są zadeklarowane).
Blok kodu to dowolny {} z wierszami kodu między.

  • function () {var, let, const tutaj jest dostępny dla całej funkcji},
  • for () {var tutaj jest dostępny dla zewnętrznego zasięgu, let, const dostępny tylko tutaj},
  • itp.

Zmienne globalne (dostępne również w zakresie globalnym).
Te zmienne są dołączone do obiektu globalnego. Obiekt globalny jest zależny od środowiska. Jest to obiekt okna w przeglądarkach.

Uwaga specjalna: Możesz zadeklarować zmienne w JavaScript bez użycia słów kluczowych var, let, const. Zmienna zadeklarowana w ten sposób jest dołączona do obiektu globalnego, a zatem dostępna w zasięgu globalnym.
a = 100 // is valid and is in global scope

Niektóre artykuły do ​​dalszego czytania: https://www.sitepoint.com/demystifying-javascript-variable-scope-hoisting/ https://scotch.io/tutorials/understanding-scope-in-javascript https: //www.digitalocean .com / community / tutorials / zrozumienia-zmiennych-zakres-podnoszenie-w-javascript

Funwie
źródło
0

Główną różnicą są zasady określania zakresu. Zmienne zadeklarowane przez słowo kluczowe var są kierowane do bezpośredniego ciała funkcji (stąd zakres funkcji), podczas gdy zmienne są kierowane do bezpośredniego otaczającego bloku oznaczonego przez {} (stąd zakres bloku). A kiedy mówisz

c = 10;
b = c;
let a = b;

c i b mają żywotność tak, jak zabawa, ale tylko mają okres blokowania, a jeśli spróbujesz uzyskać dostęp przez odwołanie, zawsze pokaż błąd, ale c i b są globalnie, więc nie. Zauważysz to bez względu na to, ile zmiennych, które dodasz do łańcucha, będzie to tylko pierwsza (a), która powoduje błąd. Jest tak, ponieważ „pozwól” zakresuje zmienną do bloku (lub „lokalnie”, mniej więcej w znaczeniu „w nawiasach”) jeśli deklarujesz zmienną bez „let”, to mierzy ona zmienną globalnie, więc w funkcji, w której ustawiasz zmienne, wszystko otrzymuje wartość 10 (możesz to zobaczyć w debuggerze, jeśli umieścisz punkt przerwania). Jeśli w pierwszej funkcji umieścisz dziennik konsoli dla a, b, c, wszystko będzie dobrze. Ale jak tylko opuścisz tę funkcję, pierwsza (a) - i znowu, pamiętaj,

Muhammad Fahad
źródło
0

Oto 3 interesujące aspekty deklaracji zmiennych w JavaScript:

  1. var ogranicza zakres zmiennej do bloku, w którym jest zdefiniowany. ( „var” dotyczy zakresu lokalnego ).

  2. let umożliwia czasowe nadpisywanie wartości zewnętrznego zmiennej wewnątrz bloku.

  3. Po prostu zadeklarowanie zmiennej bez zmiennej var lub let spowoduje, że zmienna będzie globalna, bez względu na to, gdzie zostanie zadeklarowana.

Oto demo let , która jest najnowszym dodatkiem do języka:

// File name:  let_demo.js

function first() {
   a = b = 10
   console.log("First function:    a = " + a)
   console.log("First function:    a + b = " + (a + b))
}

function second() {
    let a = 5
    console.log("Second function:    a = " + a)
    console.log("Second function:    a + b = " + (a + b))
}

first()   

second()

console.log("Global:    a = " + a)
console.log("Global:    a + b = " + (a + b))

Wynik:

$ node let_demo.js 

First function:    a = 10
First function:    a + b = 20

Second function:    a = 5
Second function:    a + b = 15

Global:    a = 10
Global:    a + b = 20

Wyjaśnienie:

Zmienne a i b zostały zapisane w „ first () ”, bez słów kluczowych var i let.

Dlatego i b są globalne, a co za tym idzie, są dostępne w całym programie.

W funkcji o nazwie „drugi” instrukcja „let a = 5” tymczasowo ustawia wartość „ a ” na „ 5 ”, tylko w zakresie funkcji.

Poza zakresem „ second () ”, IE, w zasięgu globalnym wartość „ a ” będzie taka, jak zdefiniowano wcześniej.

Gopinath
źródło