Czy zmienne deklarowane za pomocą let lub const nie są podnoszone w ES6?

266

Gram od jakiegoś czasu ES6 i zauważyłem, że podczas gdy zmienne zadeklarowane za pomocą varsą podnoszone zgodnie z oczekiwaniami ...

console.log(typeof name); // undefined
var name = "John";

... zmienne zadeklarowane letlub constmające problemy z podnoszeniem:

console.log(typeof name); // ReferenceError
let name = "John";

i

console.log(typeof name); // ReferenceError
const name = "John";

Czy to oznacza, że ​​zmienne zadeklarowane za pomocą letlub constnie są podnoszone? Co się tu naprawdę dzieje? Czy jest jakaś różnica między leti constw tej kwestii?

Luboš Turek
źródło

Odpowiedzi:

346

@ theourourtheye ma rację mówiąc, że do tych zmiennych nie można uzyskać dostępu przed ich zadeklarowaniem. Jest to jednak nieco bardziej skomplikowane.

Czy zmienne są zadeklarowane z podniesieniem letczy constnie? Co się tu naprawdę dzieje?

Wszystkie deklaracje ( var, let, const, function, function*, class) są "podniósł" w JavaScript. Oznacza to, że jeśli nazwa zostanie zadeklarowana w zakresie, w tym zakresie identyfikator zawsze będzie odnosił się do tej konkretnej zmiennej:

x = "global";
// function scope:
(function() {
    x; // not "global"

    var/let/… x;
}());
// block scope (not for `var`s):
{
    x; // not "global"

    let/const/… x;
}

Dotyczy to zarówno zakresów funkcji, jak i zakresów bloków 1 .

Różnica między var/ function/ function*deklaracjami i let/ const/ classdeklaracjami polega na inicjalizacji .
Te pierwsze są inicjowane za pomocą undefinedfunkcji (generator) zaraz po utworzeniu powiązania u góry zakresu. Jednak zmienne zadeklarowane leksykalnie pozostają niezainicjowane . Oznacza to, że ReferenceErrorzgłaszany jest wyjątek podczas próby uzyskania do niego dostępu. Zostanie zainicjowany tylko podczas oceny instrukcji let/ const/ class, wszystko przed (powyżej), które nazywa się tymczasową martwą strefą .

x = y = "global";
(function() {
    x; // undefined
    y; // Reference error: y is not defined

    var x = "local";
    let y = "local";
}());

Zauważ, że let y;instrukcja inicjuje zmienną za pomocą undefinedpodobnego let y = undefined;.

Czasowa strefa martwa nie jest składniowym lokalizacja, ale raczej czas pomiędzy zmiennej (zakres) utworzenia i inicjalizacji. Odwoływanie się do zmiennej w kodzie powyżej deklaracji nie jest błędem, dopóki ten kod nie jest wykonywany (np. Treść funkcji lub po prostu martwy kod), i zgłosi wyjątek, jeśli uzyskasz dostęp do zmiennej przed inicjalizacją, nawet jeśli dostęp kod znajduje się poniżej deklaracji (np. w deklaracji funkcji podniesionej, która jest wywoływana zbyt wcześnie).

Czy jest jakaś różnica między leti constw tej kwestii?

Nie, działają one tak samo, jak w odniesieniu do podnoszenia. Jedyna różnica między nimi polega na tym, że constmrówka musi być i może być przypisana tylko w części inicjalizującej deklaracji ( const one = 1;zarówno const one;przypisania , jak i późniejsze, takie jak one = 2są nieprawidłowe).

1: varDeklaracje nadal działają oczywiście tylko na poziomie funkcji

Bergi
źródło
16
Uważam, że coś podobnego let foo = () => bar; let bar = 'bar'; foo();ilustruje, że wszystkie deklaracje są podnoszone jeszcze lepiej, ponieważ nie jest to oczywiste ze względu na czasową martwą strefę.
Estus Flask,
1
Już miałem zapytać o odwołanie się do definicji let w funkcji zadeklarowanej przed let (tj. Zamknięcie). Myślę, że to odpowiada na pytanie, to jest legalne, ale będzie to błąd ref, jeśli funkcja zostanie wywołana przed wykonaniem instrukcji let, i będzie dobrze, jeśli funkcja zostanie później wywołana. może to może być dodane do odpowiedzi, jeśli prawda?
Mike Lippert,
2
@MikeLippert Tak, to prawda. Nie wolno wywoływać funkcji, która uzyskuje dostęp do zmiennej przed jej zainicjowaniem. Ten scenariusz występuje na przykład przy każdej deklaracji funkcji podniesionej.
Bergi,
1
Decyzja o podjęciu consttakiej decyzji letjest wadą projektową. W ramach zakresu constpowinno się było go podnosić i inicjować dokładnie w czasie, gdy jest dostępny. Naprawdę, powinni mieć const, jak leti innego słowa kluczowego, który tworzy zmienną, która działa jak „tylko do odczytu” let.
Pacerier
1
Te pierwsze są inicjowane niezdefiniowanym …” może być odpowiednie dla deklaracji var, ale nie wydaje się odpowiednie dla deklaracji funkcji, którym przypisano wartość przed rozpoczęciem wykonywania.
RobG
87

Cytując specyfikację ECMAScript 6 (ECMAScript 2015) leticonst sekcję deklaracji ,

Zmienne są tworzone, gdy tworzy się instancję zawierającą środowisko leksykalne, ale nie można uzyskać do nich dostępu w żaden sposób, dopóki nie zostanie ocenione powiązanie leksykalne zmiennej .

Tak więc, aby odpowiedzieć na twoje pytanie, tak, leti constwciągnij, ale nie możesz uzyskać do nich dostępu, dopóki rzeczywista deklaracja nie zostanie oceniona w czasie wykonywania.

thefourtheye
źródło
22

ES6wprowadza Letzmienne, które powstają block level scoping. Dopóki ES5tego nie mieliśmy block level scoping, więc zmienne zadeklarowane w bloku zawsze hoistedmają zakres funkcji.

Zasadniczo Scopeodnosi się do tego, gdzie w twoim programie są widoczne twoje zmienne, co określa, gdzie możesz używać deklarowanych zmiennych. W ES5mamy global scope,function scope and try/catch scope, dzięki ES6zakresowi określania poziomu bloków również za pomocą Let.

  • Kiedy definiujesz zmienną za pomocą varsłowa kluczowego, znana jest cała funkcja od momentu jej zdefiniowania.
  • Kiedy definiujesz zmienną z letinstrukcją, jest ona znana tylko w jej bloku.

     function doSomething(arr){
         //i is known here but undefined
         //j is not known here
    
         console.log(i);
         console.log(j);
    
         for(var i=0; i<arr.length; i++){
             //i is known here
         }
    
         //i is known here
         //j is not known here
    
         console.log(i);
         console.log(j);
    
         for(let j=0; j<arr.length; j++){
             //j is known here
         }
    
         //i is known here
         //j is not known here
    
         console.log(i);
         console.log(j);
     }
    
     doSomething(["Thalaivar", "Vinoth", "Kabali", "Dinesh"]);

Jeśli uruchomisz kod, zobaczysz, że zmienna jjest znana tylko w, loopa nie przed i po. Jednak nasza zmienna ijest znana entire functionod momentu jej zdefiniowania.

Jest jeszcze jedna wielka zaleta użycia let, ponieważ tworzy nowe środowisko leksykalne, a także wiąże nową wartość, zamiast utrzymywać stare odniesienie.

for(var i=1; i<6; i++){
   setTimeout(function(){
      console.log(i);
   },1000)
}

for(let i=1; i<6; i++){
   setTimeout(function(){
      console.log(i);
   },1000)
}

Pierwsza forpętla zawsze wypisuje ostatnią wartość, lettworząc nowy zakres i wiążąc świeże wartości drukujące nas 1, 2, 3, 4, 5.

Jeśli chodzi o constantsto, działa w zasadzie tak let, jedyną różnicą jest to, że ich wartości nie można zmienić. W stałych mutacja jest dozwolona, ​​ale ponowne przypisanie nie jest dozwolone.

const foo = {};
foo.bar = 42;
console.log(foo.bar); //works

const name = []
name.push("Vinoth");
console.log(name); //works

const age = 100;
age = 20; //Throws Uncaught TypeError: Assignment to constant variable.

console.log(age);

Jeśli stała odnosi się do object, zawsze będzie odnosić się do, objectale objectsama w sobie może zostać zmieniona (jeśli jest zmienna). Jeśli chcesz mieć niezmienny object, możesz użyćObject.freeze([])

Thalaivar
źródło
5

Z dokumentów internetowych MDN:

W ECMAScript 2015, leti constsą wydobywanych ale nie zainicjowany. Odwołanie do zmiennej w bloku przed deklaracją zmiennej powoduje, ReferenceErrorże zmienna znajduje się w „czasowej martwej strefie” od początku bloku do momentu przetworzenia deklaracji.

console.log(x); // ReferenceError
let x = 3;
YourAboutMeIsBlank
źródło
0

w es6, kiedy używamy let lub const, musimy zadeklarować zmienną przed ich użyciem. na przykład. 1 -

// this will work
u = 10;
var u;

// this will give an error 
k = 10;
let k;  // ReferenceError: Cannot access 'k' before initialization.

na przykład. 2-

// this code works as variable j is declared before it is used.
function doSmth() {
j = 9;
}
let j;
doSmth();
console.log(j); // 9
użytkownik260778
źródło