Les variables déclarées avec let ou const ne sont-elles pas hissées dans ES6?

266

Je joue avec ES6 depuis un moment et j'ai remarqué que les variables déclarées avec varsont hissées comme prévu ...

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

... les variables déclarées avec letou constsemblent avoir des problèmes de levage:

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

et

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

Est-ce à dire que les variables déclarées avec letou constne sont pas hissées? Que se passe-t-il vraiment ici? Y a-t-il une différence entre letet constdans cette affaire?

Luboš Turek
la source

Réponses:

346

@thefourtheye a raison de dire que ces variables ne sont pas accessibles avant d'être déclarées. Cependant, c'est un peu plus compliqué que ça.

Les variables sont-elles déclarées avec letou constnon hissées? Que se passe-t-il vraiment ici?

Toutes les déclarations ( var, let, const, function, function*, class) sont "hissés" en JavaScript. Cela signifie que si un nom est déclaré dans une étendue, dans cette étendue, l'identifiant fera toujours référence à cette variable particulière:

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

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

    let/const/… x;
}

Cela est vrai pour les portées de fonction et de bloc 1 .

La différence entre var/ function/ function*déclarations et let/ const/ classdéclarations est l' initialisation .
Les premiers sont initialisés avec undefinedou la fonction (générateur) dès que la liaison est créée en haut de l'étendue. Les variables lexicalement déclarées restent cependant non initialisées . Cela signifie qu'une ReferenceErrorexception est levée lorsque vous essayez d'y accéder. Elle ne sera initialisée que lorsque l' instruction let/ const/ classsera évaluée, tout ce qui précède (ci-dessus) est appelé la zone morte temporelle .

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

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

Notez qu'une let y;instruction initialise la variable avec undefinedlike let y = undefined;aurait.

La zone morte temporelle n'est pas un emplacement syntaxique, mais plutôt le temps entre la création de la variable (portée) et l'initialisation. Ce n'est pas une erreur de référencer la variable dans le code au-dessus de la déclaration tant que ce code n'est pas exécuté (par exemple un corps de fonction ou simplement un code mort), et cela lèvera une exception si vous accédez à la variable avant l'initialisation même si l'accès le code est en dessous de la déclaration (par exemple dans une déclaration de fonction hissée appelée trop tôt).

Y a-t-il une différence entre letet constdans cette affaire?

Non, ils fonctionnent de la même manière en ce qui concerne le levage. La seule différence entre eux est qu'une constfourmi doit être et ne peut être affectée que dans la partie initialiseur de la déclaration ( const one = 1;, les deux const one;réaffectations et les affectations ultérieures comme ne one = 2sont pas valides).

1: les vardéclarations ne fonctionnent toujours qu'au niveau de la fonction, bien sûr

Bergi
la source
16
Je trouve que quelque chose comme let foo = () => bar; let bar = 'bar'; foo();illustre toutes les déclarations sont effet hissé encore mieux, car ce n'est pas évident en raison de la zone morte temporelle.
Estus Flask
1
J'étais sur le point de poser des questions sur le référencement d'une définition de let dans une fonction déclarée avant le let (c'est-à-dire une fermeture). Je pense que cela répond à la question, c'est légal, mais ce sera une erreur de référence si la fonction est invoquée avant l'exécution de l'instruction let, et ce sera bien si la fonction est invoquée après. peut-être que cela pourrait être ajouté à la réponse si c'est vrai?
Mike Lippert
2
@MikeLippert Oui, c'est exact. Vous ne devez pas appeler la fonction qui accède à la variable avant son initialisation. Ce scénario se produit avec chaque déclaration de fonction hissée, par exemple.
Bergi
1
La décision de faire constcomme letest un défaut de conception. Dans un périmètre, constaurait dû être fait pour être hissé et juste-à-temps-initialisé lors de son accès. Vraiment, ils devraient avoir un const, unlet , et un autre mot - clé qui crée une variable qui fonctionne comme une « lecture seule » let.
Pacerier
1
" Les premiers sont initialisés avec undefined ..." pourrait être ok pour déclarations var mais ne semble pas approprié pour les déclarations de fonctions, auxquelles une valeur est affectée avant le début de l'exécution.
RobG
87

Citant la spécification ECMAScript 6 (ECMAScript 2015) letet laconst section des déclarations ,

Les variables sont créées lorsque leur environnement lexical contenant est instancié, mais il est impossible d'y accéder de quelque manière que ce soit jusqu'à ce que la variable LexicalBinding soit évaluée .

Donc, pour répondre à votre question, oui, letet consthissez, mais vous ne pouvez pas y accéder avant que la déclaration réelle ne soit évaluée au moment de l'exécution.

thefourtheye
la source
22

ES6introduit des Letvariables qui vient avec block level scoping. Jusqu'à ce que ES5nous n'en ayons pas block level scoping, les variables déclarées dans un bloc sont donc toujourshoisted fonctionner au niveau de la portée.

ScopeSe réfère essentiellement à l'endroit où dans votre programme vos variables sont visibles, ce qui détermine où vous êtes autorisé à utiliser les variables que vous avez déclarées. Dans ES5nous avons global scope,function scope and try/catch scope, avec ES6nous obtenons également la portée au niveau du bloc en utilisant Let.

  • Lorsque vous définissez une variable avec un varmot clé, la fonction entière est connue à partir du moment où elle est définie.
  • Lorsque vous définissez une variable avec une letinstruction, elle n'est connue que dans le bloc auquel elle est définie.

     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 vous exécutez le code, vous pouvez voir que la variable jest uniquement connue dans le loopet non avant et après. Pourtant, notre variable iest connue à entire functionpartir du moment où elle est définie.

Il y a un autre grand avantage à utiliser let car cela crée un nouvel environnement lexical et lie également une nouvelle valeur plutôt que de conserver une ancienne référence.

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

La première forboucle imprime toujours la dernière valeur, avec letelle crée une nouvelle portée et lie les nouvelles valeurs nous imprimant 1, 2, 3, 4, 5.

En ce qui concerne constants, cela fonctionne essentiellement comme let, la seule différence est que leur valeur ne peut pas être modifiée. Dans les constantes, la mutation est autorisée mais la réaffectation n'est pas autorisée.

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 une constante fait référence à un object, elle fera toujours référence au objectmais le objectlui - même peut être modifié (s'il est modifiable). Si vous aimez avoir un immuable object, vous pouvez utiliserObject.freeze([])

Thalaivar
la source
5

Depuis les documents Web MDN:

Dans ECMAScript 2015, letet constsont hissés mais pas initialisés. Le fait de référencer la variable dans le bloc avant la déclaration de variable entraîne un ReferenceErrorcar la variable se trouve dans une "zone morte temporelle" depuis le début du bloc jusqu'à ce que la déclaration soit traitée.

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

dans es6 lorsque nous utilisons let ou const, nous devons déclarer la variable avant de les utiliser. par exemple. 1 -

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

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

par exemple. 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
la source