使用 let 或 const 声明的变量是否被提升?

IT技术 javascript ecmascript-6 constants let hoisting
2021-01-27 20:39: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";

这是否意味着使用let或未const提升声明的变量这里到底发生了什么?在这件事上let之间有什么区别const吗?

6个回答

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

与声明的变量letconst不悬挂?这里到底发生了什么?

所有声明var, let, const, function, function*, 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这就是所谓的(上述)之前声明进行评估,一切时间盲区

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

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

请注意,let y;语句使用undefinedlikelet y = undefined;初始化变量

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

在这件事上let之间有什么区别const吗?

不,就起重而言,它们的工作原理相同。它们之间的唯一区别是constant 必须并且只能在声明的初始化部分分配(const one = 1;,两者const one;和以后的重新分配one = 2都是无效的)。

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

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

引用 ECMAScript 6 (ECMAScript 2015) 规范letconst声明部分,

变量在其包含的 Lexical Environment 被实例化时创建,但在评估变量的 LexicalBinding 之前不能以任何方式访问

因此,要回答您的问题,是的,let并且const提升但在运行时评估实际声明之前您无法访问它们。

换句话说,我们可以说:只有声明被提升,而不是初始化/赋值
2021-03-16 20:39:38

ES6引入Letblock level scoping. 直到ES5我们没有block level scoping,所以在块内声明的变量始终hoisted是函数级别的范围。

基本上Scope是指您的变量在程序中的可见位置,这决定了您可以在何处使用已声明的变量。ES5我们有global scope,function scope and try/catch scope, withES6我们还通过使用 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只在 the 中loop而不是 before 和 after 中是已知的然而,我们的变量从定义的那一刻起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循环总是打印最后一个值,let它创建一个新的范围并绑定打印 us 的新值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);

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

你没有回答真正的问题,如果let变量被提升,为什么不能访问它们?或者,如果在声明之前无法访问它们,我们如何证明它们已被提升。
2021-03-31 20:39:38

来自MDN 网络文档:

在2015年的ECMAScript,letconst高挂,但没有初始化。在变量声明之前引用块中的变量会导致 a,ReferenceError因为从块的开始直到处理声明,变量都处于“时间死区”中。

console.log(x); // ReferenceError
let x = 3;
undefined甚至在的情况下var因为,声明被提升,而不是初始化。如果您首先initialize->access->declare,在 的情况下var,它将被吊起,在let和 的情况下const,它将具有ReferenceError并且不会被吊起。
2021-03-24 20:39:38

根据 ECMAScript® 2021

Let 和 Const 声明

  • let 和 const 声明定义了作用域为运行执行上下文的 LexicalEnvironment 的变量。
  • 变量在其包含的环境记录被实例化时创建,但在评估变量的 LexicalBinding 之前不能以任何方式访问。
  • 当 LexicalBinding 被求值时,一个由 LexicalBinding 和一个 Initializer 定义的变量被分配了它的 Initializer 的 AssignmentExpression 的值,而不是在创建变量时
  • 如果let 声明中的 LexicalBinding没有 Initializer,则在评估 LexicalBinding 时,该变量会被赋值为 undefined

块声明实例化

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

顶级词法声明名称

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

结论

  • let 和 const 被提升但没有初始化。

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

下面的示例清楚地说明了“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' 给出了一个引用错误,这并不意味着它没有被提升。该变量是在实例化包含环境时创建的。但是它可能无法访问,因为它位于无法访问的“时间死区”。

示例 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”变量在日志语句之前没有初始化器,因此值为“undefined”。

来源

ECMA MDN