Werden Variablen, die mit let oder const deklariert wurden, in ES6 nicht angehoben?

266

Ich habe eine Weile mit ES6 gespielt und festgestellt, dass Variablen, die mit deklariert wurden, varwie erwartet hochgezogen werden ...

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

... Variablen , die mit dem Heben deklariert wurden letoder constProbleme damit haben:

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

und

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

Bedeutet dies, dass Variablen deklariert mit letoder constnicht gehisst werden? Was ist hier wirklich los? Gibt es einen Unterschied zwischen letund constin dieser Angelegenheit?

Luboš Turek
quelle

Antworten:

346

@thefourtheye sagt zu Recht, dass auf diese Variablen nicht zugegriffen werden kann, bevor sie deklariert wurden. Es ist jedoch etwas komplizierter.

Werden Variablen mit deklariert letoder constnicht gehisst? Was ist hier wirklich los?

Alle Erklärungen ( var, let, const, function, function*, class) werden als "hochgezogen" in JavaScript. Dies bedeutet, dass, wenn ein Name in einem Bereich deklariert ist, der Bezeichner in diesem Bereich immer auf diese bestimmte Variable verweist:

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

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

    let/const/… x;
}

Dies gilt sowohl für Funktions- als auch für Blockbereiche 1 .

Der Unterschied zwischen var/ function/ function*Deklarationen und let/ const/ classDeklarationen ist die Initialisierung .
Erstere werden mit undefinedoder mit der (Generator-) Funktion initialisiert, sobald die Bindung oben im Bereich erstellt wird. Die lexikalisch deklarierten Variablen bleiben jedoch nicht initialisiert . Dies bedeutet, dass eine ReferenceErrorAusnahme ausgelöst wird, wenn Sie versuchen, darauf zuzugreifen. Es wird nur initialisiert, wenn die let/ const/ class-Anweisung ausgewertet wird, alles vor (oben), was als zeitliche Totzone bezeichnet wird .

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

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

Beachten Sie, dass eine let y;Anweisung die Variable mit undefinedlike initialisiert let y = undefined;.

Die zeitliche Totzone ist kein syntaktischer Ort, sondern die Zeit zwischen der Erstellung der Variablen (Bereich) und der Initialisierung. Es ist kein Fehler, auf die Variable im Code über der Deklaration zu verweisen, solange dieser Code nicht ausgeführt wird (z. B. ein Funktionskörper oder einfach toter Code), und es wird eine Ausnahme ausgelöst, wenn Sie vor der Initialisierung auf die Variable zugreifen, auch wenn der Zugriff erfolgt Code befindet sich unterhalb der Deklaration (z. B. in einer zu früh aufgerufenen angehobenen Funktionsdeklaration).

Gibt es einen Unterschied zwischen letund constin dieser Angelegenheit?

Nein, sie funktionieren genauso, was das Heben betrifft. Der einzige Unterschied zwischen ihnen besteht darin, dass eine constAmeise nur im Initialisierungsteil der Deklaration zugewiesen werden muss und kann ( const one = 1;beide const one;und spätere Neuzuweisungen wie one = 2sind ungültig).

1: varDeklarationen funktionieren natürlich immer noch nur auf Funktionsebene

Bergi
quelle
16
Ich finde, dass so etwas wie let foo = () => bar; let bar = 'bar'; foo();illustriert, dass alle Deklarationen noch besser gehisst werden , weil es aufgrund der zeitlichen Totzone nicht offensichtlich ist.
Estus Flask
1
Ich wollte gerade nach einer referenzierten let-Definition in einer vor dem let deklarierten Funktion fragen (dh nach einem Closure). Ich denke, dies beantwortet die Frage, es ist legal, wird aber ein Ref-Fehler sein, wenn die Funktion aufgerufen wird, bevor die let-Anweisung ausgeführt wird, und wird in Ordnung sein, wenn die Funktion danach aufgerufen wird. Vielleicht könnte dies zur Antwort hinzugefügt werden, wenn es wahr ist?
Mike Lippert
2
@ MikeLippert Ja, das ist richtig. Sie dürfen die Funktion, die auf die Variable zugreift, nicht aufrufen, bevor sie initialisiert wird. Dieses Szenario tritt beispielsweise bei jeder angehobenen Funktionsdeklaration auf.
Bergi
1
Die Entscheidung, constwie zu machen, letist ein Designfehler. Innerhalb eines Bereichs constsollte dafür gesorgt werden, dass er beim Zugriff angehoben und just-in-time-initialisiert wird. Eigentlich sollten sie ein const, ein letund ein anderes Schlüsselwort haben, das eine Variable erstellt, die wie "schreibgeschützt" funktioniert let.
Pacerier
1
" Die ersteren werden mit undefined ... initialisiert ..." ist möglicherweise für var- Deklarationen in Ordnung, scheint jedoch für Funktionsdeklarationen nicht geeignet zu sein, denen vor Beginn der Ausführung ein Wert zugewiesen wird.
RobG
87

Zitieren der Spezifikationen von ECMAScript 6 (ECMAScript 2015) letundconst Erklärungen zu ,

Die Variablen werden erstellt, wenn ihre enthaltene Lexikalische Umgebung instanziiert wird, aber es kann in keiner Weise auf sie zugegriffen werden, bis die Lexikalische Bindung der Variablen ausgewertet ist .

Um Ihre Frage zu beantworten, ja, letund constheben Sie, aber Sie können nicht darauf zugreifen, bevor die tatsächliche Deklaration zur Laufzeit ausgewertet wird.

thefourtheye
quelle
22

ES6führt LetVariablen ein, die mit kommen block level scoping. Bis ES5wir nicht hatten block level scoping, also sind die Variablen, die innerhalb eines Blocks deklariert werden, immerhoisted auf Funktionsebene Scoping.

ScopeBezieht sich im Wesentlichen darauf, wo in Ihrem Programm Ihre Variablen sichtbar sind, wodurch bestimmt wird, wo Sie von Ihnen deklarierte Variablen verwenden dürfen. In haben ES5wir global scope,function scope and try/catch scope, mit bekommen ES6wir auch das Block-Level-Scoping mit Let.

  • Wenn Sie eine Variable mit einem varSchlüsselwort definieren, ist die gesamte Funktion vom Moment ihrer Definition an bekannt.
  • Wenn Sie eine Variable mit letAnweisung definieren, ist sie nur in dem Block bekannt, in dem sie definiert ist.

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

Wenn Sie den Code ausführen, können Sie sehen, dass die Variable jnur im loopund nicht vorher und nachher bekannt ist. Unsere Variable iist jedoch in der bekanntentire function ab dem Zeitpunkt ihrer Definition bekannt.

Es gibt einen weiteren großen Vorteil bei der Verwendung von let, da es eine neue lexikalische Umgebung schafft und auch neuen Wert bindet, anstatt eine alte Referenz beizubehalten.

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

Die erste forSchleife druckt immer den letzten Wert, leterstellt einen neuen Bereich und bindet neue Werte, die uns drucken 1, 2, 3, 4, 5.

Kommen zu constants, es funktioniert im Grunde wie let, ist der einzige Unterschied ihr Wert kann nicht geändert werden. In Konstanten ist eine Mutation zulässig, eine Neuzuweisung jedoch nicht.

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

Wenn sich eine Konstante auf eine bezieht, bezieht objectsie sich immer auf die, objectaber die objectselbst kann geändert werden (wenn sie veränderbar ist). Wenn Sie eine unveränderliche haben möchten object, können Sie verwendenObject.freeze([])

Thalaivar
quelle
5

Aus MDN-Webdokumenten:

In ECMAScript 2015 letund constwerden gehisst, aber nicht initialisiert. Das Verweisen auf die Variable im Block vor der Variablendeklaration führt zu a, ReferenceErrorda sich die Variable vom Beginn des Blocks bis zur Verarbeitung der Deklaration in einer "zeitlichen Totzone" befindet.

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

Wenn wir in es6 let oder const verwenden, müssen wir die Variable deklarieren, bevor wir sie verwenden. z.B. 1 -

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

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

z.B. 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
quelle