【问题标题】:Are variables declared with let or const hoisted?使用 let 或 const 声明的变量是否提升?
【发布时间】:2015-07-04 10:12:38
【问题描述】:

我使用 ES6 已经有一段时间了,我注意到虽然用 var 声明的变量按预期提升了...

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

...用letconst 声明的变量似乎有一些提升问题:

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

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

这是否意味着使用letconst 声明的变量不会被提升?这里到底发生了什么?在这件事上letconst有什么区别吗?

【问题讨论】:

    标签: javascript ecmascript-6 constants let hoisting


    【解决方案1】:

    @thefourtheye 说这些变量在声明之前无法访问是正确的。但是,它比这要复杂一些。

    使用letconst 声明的变量是否未提升?这里到底发生了什么?

    所有声明varletconstfunctionfunction*class在 JavaScript 中被“提升” .这意味着如果在一个范围内声明了一个名称,那么在该范围内,标识符将始终引用该特定变量:

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

    对于函数和块范围都是如此1

    var/function/function* 声明和let/const/class 声明之间的区别在于初始化
    当绑定在作用域的顶部创建时,前者使用undefined 或(生成器)函数进行初始化。然而,词法声明的变量保持未初始化。这意味着当您尝试访问它时会引发ReferenceError 异常。它只会在 let/const/class 语句被评估时被初始化,之前(上面)的所有东西都被称为 temporal dead zone

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

    注意let y; 语句使用undefined 初始化变量,就像let y = undefined; 一样。

    时间死区不是句法位置,而是变量(范围)创建和初始化之间的时间。只要未执行该代码(例如函数体或简单的死代码),在声明上方的代码中引用变量就不是错误,如果您在初始化之前访问该变量,即使访问代码位于声明下方(例如,在过早调用的提升函数声明中)。

    letconst在这件事上有什么区别吗?

    不,就提升而言,它们的工作方式相同。它们之间的唯一区别是 constant 必须并且只能在声明的初始化部分中分配(const one = 1;const one; 和后来的重新分配,如 one = 2 都是无效的)。

    1:var 声明仍然只在函数级别起作用,当然

    【讨论】:

    • 啊,这是暗示的。提升总是发生在一个作用域内,块是一切的作用域(var除外)。
    • 我发现像let foo = () => bar; let bar = 'bar'; foo(); 这样的东西说明了所有声明都被提升效果更好,因为由于时间死区,它并不明显。
    • 我正要问关于在 let 之前声明的函数中引用 let 定义(即闭包)。我认为这回答了这个问题,它是合法的,但如果在执行 let 语句之前调用该函数将是一个 ref 错误,如果之后调用该函数就可以了。如果属实,也许这可以添加到答案中?
    • @MikeLippert 是的,这是正确的。在初始化之前,您不能调用访问变量的函数。例如,每个提升的函数声明都会出现这种情况。
    • const 设为let 是一个设计缺陷。在一个范围内,const 应该在被访问时被提升并及时初始化。真的,他们应该有一个const、一个let和另一个关键字,它创建一个像“只读”let一样工作的变量。
    【解决方案2】:

    引用 ECMAScript 6 (ECMAScript 2015) 规范的let and const declarations 部分,

    变量是在实例化包含它们的词法环境时创建的,但在评估变量的词法绑定之前不能以任何方式访问

    所以,回答您的问题,是的,letconst 提升,但在运行时评估实际声明之前您无法访问它们。

    【讨论】:

    • 换句话说,我们可以说:只有声明被提升,而不是初始化/分配
    【解决方案3】:

    ES6 引入了Let 变量,这些变量与block level scoping 一起出现。在ES5 之前,我们没有block level scoping,因此在块内声明的变量始终是hoisted 函数级别范围。

    基本上Scope 是指您的变量在程序中的可见位置,这决定了您可以在何处使用已声明的变量。在ES5 中,我们有global scope,function scope and try/catch scope,在ES6 中,我们还可以使用Let 获得块级范围。

    • 当您使用 var 关键字定义变量时,它从定义的那一刻起就知道整个函数。
    • 当您使用let 语句定义变量时,它仅在它定义的块中知道。

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

    如果您运行代码,您会看到变量j 仅在loop 中已知,而不是之前和之后。然而,我们的变量 i 从定义的那一刻起就在 entire function 中已知。

    使用 let 还有一个很大的优势,因为它创建了一个新的词法环境,并且还绑定了新的值而不是保留旧的引用。

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

    第一个 for 循环总是打印 last 值,let 它创建一个新范围并绑定新值,打印我们 1, 2, 3, 4, 5

    来到constants,它基本上和let一样工作,唯一的区别是它们的值不能改变。在常量中允许改变,但不允许重新分配。

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

    如果常量引用object,它将始终引用object,但object 本身可以更改(如果它是可变的)。如果你想要一个不可变的object,你可以使用Object.freeze([])

    【讨论】:

    • 你没有回答真正的问题,如果let变量被提升,为什么不能访问它们?或者如果在声明它们之前无法访问它们,我们如何证明它们被提升了。
    【解决方案4】:

    根据 ECMAScript® 2021

    Let 和 Const 声明

    • let 和 const 声明定义了范围为正在运行的执行上下文的 LexicalEnvironment 的变量。
    • 变量在包含环境记录的实例化时创建,但在评估变量的 LexicalBinding 之前不得以任何方式访问。
    • 在评估 LexicalBinding 时,不是在创建变量时,为由具有 Initializer 的 LexicalBinding 定义的变量分配其 Initializer 的 AssignmentExpression 的值。
    • 如果 let 声明中的 LexicalBinding 没有 Initializer,则在评估 LexicalBinding 时会为该变量分配 undefined 值

    块声明实例化

    • 在评估 Block 或 CaseBlock 时,会创建一个新的声明性环境记录,并在该环境记录中实例化每个在块中声明的块范围变量、常量、函数或类的绑定。
    • 无论控制如何离开 Block,LexicalEnvironment 总是会恢复到之前的状态

    顶级词法声明的名称

    在函数或脚本的顶层,函数声明被视为 var 声明,而不是词法声明。

    结论

    • let 和 const 已提升但未初始化。

      在变量声明之前引用块中的变量会导致 ReferenceError,因为从块开始到处理声明之前,该变量处于“临时死区”。。 p>

    下面的示例清楚地说明了“let”变量在词法范围/嵌套词法范围中的行为。

    示例 1

    var a;
    console.log(a); //undefined
    
    console.log(b); //undefined
    var b;
    
    
    let x;
    console.log(x); //undefined
    
    console.log(y); // Uncaught ReferenceError: y is not defined
    let y; 
    

    变量'y'给出了一个referenceError,这并不意味着它没有被提升。该变量是在实例化包含环境时创建的。但它可能无法访问,因为它处于无法访问的“临时死区”中。

    示例 2

    let mylet = 'my value';
     
    (function() {
      //let mylet;
      console.log(mylet); // "my value"
      mylet = 'local value';
    })();
    

    示例 3

    let mylet = 'my value';
     
    (function() {
      let mylet;   
      console.log(mylet); // undefined
      mylet = 'local value';
    })();
    

    在示例 3 中,函数内新声明的“mylet”变量在 log 语句之前没有 Initializer,因此值为“undefined”。

    来源

    ECMA MDN

    【讨论】:

      【解决方案5】:

      来自MDN web docs:

      在 ECMAScript 2015 中,letconst 被提升但未初始化。在变量声明之前引用块中的变量会导致ReferenceError,因为从块开始到处理声明为止,该变量都处于“临时死区”中。

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

      【讨论】:

      • 即使是var,也是undefined。因为,声明被提升,而不是初始化。如果你先initialize-&gt;access-&gt;declare,如果是var,它会被吊起,如果是letconst,它会有ReferenceError,不会被吊起。
      【解决方案6】:

      在 es6 中,当我们使用 let 或 const 时,我们必须在使用它们之前声明变量。 例如。 1 -

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

      例如。 2-

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

      【讨论】:

        【解决方案7】:

        let 和 const 也被提升。 但是如果使用 let 或 const 声明的变量在初始化之前由于以下原因被读取,则会引发异常。

        • 与 var 不同,它们在提升时不会使用默认值进行初始化。
        • 它们在完全初始化之前无法读取/写入。

        【讨论】:

          猜你喜欢
          • 2021-08-09
          • 2020-05-31
          • 1970-01-01
          • 2022-08-19
          • 2023-04-05
          • 2015-09-11
          相关资源
          最近更新 更多