As variáveis ​​declaradas com let ou const não são içadas no ES6?

266

Eu jogo com o ES6 há um tempo e notei que enquanto as variáveis ​​declaradas com varsão içadas conforme o esperado ...

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

... variáveis ​​declaradas com letou constparecem ter alguns problemas com a elevação:

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

e

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

Isso significa que variáveis ​​declaradas com letou constnão são içadas? O que realmente está acontecendo aqui? Existe alguma diferença entre lete constneste assunto?

Luboš Turek
fonte

Respostas:

346

@thefourtheye está correto ao dizer que essas variáveis não podem ser acessadas antes de serem declaradas. No entanto, é um pouco mais complicado que isso.

As variáveis ​​são declaradas com letou constnão içadas? O que realmente está acontecendo aqui?

Todas as declarações ( var, let, const, function, function*, class) são "içada" em JavaScript. Isso significa que se um nome for declarado em um escopo, nesse escopo, o identificador sempre fará referência a essa variável específica:

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

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

    let/const/… x;
}

Isso vale para os escopos de função e bloco 1 .

A diferença entre var/ function/ function*declarações e let/ const/ classdeclarações é a inicialização .
Os primeiros são inicializados com undefinedou a função (gerador) logo que a ligação é criada na parte superior do escopo. As variáveis ​​declaradas lexicamente, no entanto, não são inicializadas . Isso significa que uma ReferenceErrorexceção é lançada quando você tenta acessá-la. Ele só será inicializado quando a instrução let/ const/ classfor avaliada, tudo antes (acima) que é chamado de zona morta temporal .

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

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

Observe que uma let y;instrução inicializa a variável com undefinedlike let y = undefined;.

A zona morta temporal não é um local sintático, mas o tempo entre a criação da variável (escopo) e a inicialização. Não é um erro referenciar a variável no código acima da declaração, desde que esse código não seja executado (por exemplo, um corpo de função ou simplesmente código morto), e será lançada uma exceção se você acessar a variável antes da inicialização, mesmo que o acesso código está abaixo da declaração (por exemplo, em uma declaração de função içada que é chamada muito cedo).

Existe alguma diferença entre lete constneste assunto?

Não, eles funcionam da mesma forma que o içamento é considerado. A única diferença entre eles é que uma constformiga deve ser e só pode ser atribuída na parte inicializadora da declaração ( const one = 1;tanto as const one;reatribuições quanto as posteriores one = 2são inválidas).

1: as vardeclarações ainda estão funcionando apenas no nível da função, é claro

Bergi
fonte
16
Eu acho que algo como let foo = () => bar; let bar = 'bar'; foo();ilustra todas as declarações são içadas efeito ainda melhor, porque não é óbvio devido à zona morta temporal.
Estus Flask
1
Eu estava prestes a perguntar sobre a referência a uma definição de let em uma função declarada antes do let (ou seja, um fechamento). Acho que isso responde à pergunta, é legal, mas será um erro ref se a função for invocada antes da instrução let ser executada e ficará bem se a função for invocada posteriormente. talvez isso possa ser adicionado à resposta se for verdade?
quer
2
@MikeLippert Sim, isso está correto. Você não deve chamar a função que acessa a variável antes que ela seja inicializada. Este cenário ocorre com todas as declarações de função içadas, por exemplo.
Bergi 25/10/16
1
A decisão de fazer o constmesmo leté uma falha de design. Dentro de um escopo, constdeveria ter sido feito para ser içado e inicializado just-in-time quando é acessado. Realmente, eles devem ter uma const, a lete outra palavra-chave que crie uma variável que funcione como um "somente leitura" let.
22717 Pacerier
1
" O primeiro foi inicializado com indefinido ..." pode estar ok para declarações var , mas não parece apropriado para declarações de função, às quais é atribuído um valor antes do início da execução.
RobG 23/03
87

Citando a seção de especificações lete constdeclarações do ECMAScript 6 (ECMAScript 2015) ,

As variáveis ​​são criadas quando seu Ambiente Lexical que contém é instanciado, mas não pode ser acessado de forma alguma até que o LexicalBinding da variável seja avaliado .

Portanto, para responder sua pergunta, sim, lete constiçar, mas você não pode acessá-las antes que a declaração real seja avaliada em tempo de execução.

thefourtheye
fonte
22

ES6apresenta Letvariáveis ​​que surgem block level scoping. Até ES5que não tivéssemos block level scoping, as variáveis ​​declaradas dentro de um bloco sempre hoisteddevem funcionar com escopo no nível.

ScopeRefere-se basicamente a onde no seu programa suas variáveis ​​estão visíveis, o que determina onde você tem permissão para usar as variáveis ​​declaradas. Em ES5temos global scope,function scope and try/catch scope, com ES6nós também obter o escopo de nível de bloco usando Let.

  • Quando você define uma variável com varpalavra - chave, é conhecida toda a função a partir do momento em que é definida.
  • Quando você define uma variável com letinstrução, ela é conhecida apenas no bloco em que está definida.

     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"]);

Se você executar o código, poderá ver que a variável jé conhecida apenas no loope não antes e depois. No entanto, nossa variável ié conhecida no entire functionmomento em que é definida em diante.

Há outra grande vantagem do uso do let, pois ele cria um novo ambiente lexical e também agrega novo valor ao invés de manter uma referência antiga.

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)
}

O primeiro forloop sempre imprime o último valor, com letele cria um novo escopo e vincula novos valores nos imprimindo 1, 2, 3, 4, 5.

Vindo para constants , funciona basicamente como let, a única diferença é que o valor deles não pode ser alterado. Em constantes, a mutação é permitida, mas a reatribuição não é permitida.

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);

Se uma constante se referir a um object, sempre se referirá ao objectmas o objectpróprio pode ser alterado (se for mutável). Se você gosta de ter um imutávelobject , você pode usarObject.freeze([])

Thalaivar
fonte
5

Dos documentos da Web MDN:

No ECMAScript 2015, lete constsão içados, mas não inicializados. A referência à variável no bloco antes da declaração da variável resulta em ReferenceErrorporque a variável está em uma "zona morta temporal" desde o início do bloco até a declaração ser processada.

console.log(x); // ReferenceError
let x = 3;
YourAboutMeIsBlank
fonte
0

em es6, quando usamos let ou const, temos que declarar a variável antes de usá-las. por exemplo. 1 -

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

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

por exemplo. 2-

// this code works as variable j is declared before it is used.
function doSmth() {
j = 9;
}
let j;
doSmth();
console.log(j); // 9
user260778
fonte