¿Las variables declaradas con let o const no se izan en ES6?

266

He estado jugando con ES6 durante un tiempo y noté que, aunque las variables declaradas con varse alzan como se esperaba ...

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

... las variables declaradas con leto constparecen tener algunos problemas con la elevación:

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

y

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

¿Significa esto que las variables declaradas con leto constno son izadas? ¿Qué está pasando aquí realmente? ¿Hay alguna diferencia entre lety consten este asunto?

Luboš Turek
fuente

Respuestas:

346

@thefourtheye tiene razón al decir que no se puede acceder a estas variables antes de declararlas. Sin embargo, es un poco más complicado que eso.

¿Las variables se declaran con leto constno izadas? ¿Qué está pasando aquí realmente?

Todas las declaraciones ( var, let, const, function, function*, class) se "izan" en JavaScript. Esto significa que si se declara un nombre en un ámbito, en ese ámbito el identificador siempre hará referencia a esa variable en particular:

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

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

    let/const/… x;
}

Esto es cierto tanto para la función como para los ámbitos de bloque 1 .

La diferencia entre var/ function/ function*declaraciones y let/ const/ classdeclaraciones es la inicialización .
Los primeros se inicializan con undefinedo la función (generador) justo cuando el enlace se crea en la parte superior del alcance. Sin embargo, las variables declaradas léxicamente permanecen sin inicializar . Esto significa que se ReferenceErrorproduce una excepción cuando intenta acceder a ella. Solo se inicializará cuando se evalúe la instrucción let/ const/ class, todo lo anterior (arriba) que se denomina zona muerta temporal .

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

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

Observe que una let y;instrucción inicializa la variable con undefinedlike let y = undefined;habría.

La zona muerta temporal no es una ubicación sintáctica, sino el tiempo entre la creación de la variable (alcance) y la inicialización. No es un error hacer referencia a la variable en el código sobre la declaración, siempre y cuando ese código no se ejecute (por ejemplo, un cuerpo de función o simplemente un código muerto), y arrojará una excepción si accede a la variable antes de la inicialización, incluso si el acceso el código está debajo de la declaración (por ejemplo, en una declaración de función izada que se llama demasiado pronto).

¿Hay alguna diferencia entre lety consten este asunto?

No, funcionan igual en lo que respecta a la elevación. La única diferencia entre ellos es que una consthormiga debe ser y solo puede asignarse en la parte inicial de la declaración ( const one = 1;tanto las const one;reasignaciones como las posteriores one = 2no son válidas).

1: las vardeclaraciones siguen funcionando solo en el nivel de función, por supuesto

Bergi
fuente
16
Encuentro que algo así let foo = () => bar; let bar = 'bar'; foo();ilustra que todas las declaraciones son efecto de elevación aún mejor, porque no es obvio debido a la zona muerta temporal.
Estus Flas
1
Estaba a punto de preguntar sobre hacer referencia a una definición let en una función declarada antes de let (es decir, un cierre). Creo que esto responde a la pregunta, es legal, pero será un error de referencia si se invoca la función antes de que se ejecute la instrucción let, y estará bien si la función se invoca después. tal vez esto podría agregarse a la respuesta si es cierto?
Mike Lippert
2
@MikeLippert Sí, eso es correcto. No debe llamar a la función que accede a la variable antes de que se inicialice. Este escenario ocurre con cada declaración de función izada, por ejemplo.
Bergi
1
La decisión de hacer constcomo letes un defecto de diseño. Dentro de un alcance, constdebería haber sido hecho para ser izado e inicializado justo a tiempo cuando se accede a él. Realmente, deberían tener una const, una let, y otra palabra clave que cree una variable que funcione como un "solo lectura" let.
Pacerier
1
" Los primeros se inicializan con indefinido ..." podría estar bien para las declaraciones var, pero no parece apropiado para las declaraciones de funciones, a las que se les asigna un valor antes de que comience la ejecución.
RobG
87

Citando la sección de especificaciones lety constdeclaraciones de ECMAScript 6 (ECMAScript 2015) ,

Las variables se crean cuando se crea una instancia de su entorno léxico que contiene, pero no se puede acceder de ninguna manera hasta que se evalúe el enlace léxico de la variable .

Entonces, para responder a su pregunta, sí, lety constizar pero no puede acceder a ellos antes de que se evalúe la declaración real en tiempo de ejecución.

thefourtheye
fuente
22

ES6introduce Letvariables que surgen block level scoping. Hasta ES5que no lo teníamos block level scoping, entonces las variables que se declaran dentro de un bloque siempre tienen un hoistedalcance de nivel funcional.

Básicamente se Scoperefiere a en qué parte de su programa están visibles sus variables, lo que determina dónde puede usar las variables que ha declarado. En ES5tenemos global scope,function scope and try/catch scope, con ES6también obtenemos el alcance del nivel de bloque mediante el uso de Let.

  • Cuando define una variable con una varpalabra clave, se conoce toda la función desde el momento en que se define.
  • Cuando define una variable con una letdeclaración, solo se conoce en el bloque que está definido.

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

Si ejecuta el código, podría ver que la variable jsolo se conoce en loopy no antes y después. Sin embargo, nuestra variable ise conoce entire functiondesde el momento en que se define en adelante.

Existe otra gran ventaja al usar let, ya que crea un nuevo entorno léxico y también une un valor fresco en lugar de mantener una referencia antigua.

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

El primer forciclo siempre imprime el último valor, con letél crea un nuevo alcance y enlaza valores nuevos imprimiéndonos 1, 2, 3, 4, 5.

En resumen constants, funciona básicamente como let, la única diferencia es que su valor no se puede cambiar. En constantes, la mutación está permitida pero la reasignación no está 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);

Si una constante se refiere a una object, siempre se referirá a la objectpero la objectmisma se puede cambiar (si es mutable). Si te gusta tener un inmutable object, puedes usarObject.freeze([])

Thalaivar
fuente
5

De los documentos web de MDN:

En ECMAScript 2015, lety constse izan pero no se inicializan. Hacer referencia a la variable en el bloque antes de que la declaración de variable resulte en un ReferenceErrorporque la variable está en una "zona muerta temporal" desde el comienzo del bloque hasta que se procese la declaración.

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

en es6 cuando usamos let o const tenemos que declarar la variable antes de usarlos. p.ej. 1 -

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

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

p.ej. 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
fuente