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

Are variables declared with let or const hoisted?

我使用 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在这件事上有什么区别吗?

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

The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.

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

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

Are variables declared with let or const not hoisted? What is really going on here?

所有声明 (var, let, const, function, function*, class) 在JavaScript中是"hoisted"。这意味着如果在范围内声明了名称,则在该范围内标识符将始终引用该特定变量:

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 或 (generator) 函数初始化的。然而,词法声明的变量保持未初始化。这意味着当您尝试访问它时会抛出 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; 那样。

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

Is there any difference between let and const in this matter?

不,就吊装而言,它们的工作原理相同。它们之间的唯一区别是 constant 必须并且只能在声明的初始化部分进行赋值(const one = 1;const one; 和后来的重新赋值,如 one = 2无效)。

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

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([])

来自MDN web docs:

在 ECMAScript 2015 中,letconst 被提升但未初始化。在变量声明之前引用块中的变量会导致 ReferenceError,因为从块开始到声明被处理,变量都在 "temporal dead zone" 中。

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

在 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

根据 ECMAScript® 2021

Let 和 Const 声明

  • let 和 const 声明定义范围为 运行 执行上下文的 LexicalEnvironment 的变量。
  • 变量是在实例化包含它们的环境记录时创建的,但在评估变量的 LexicalBinding 之前不能以任何方式访问。
  • 由带有初始化器的 LexicalBinding 定义的变量在评估 LexicalBinding 时被赋予其初始化器的 AssignmentExpression 的值,不是在创建变量时
  • 如果 let 声明中的 LexicalBinding 没有 Initializer,则在计算 LexicalBinding 时,变量被赋值为 undefined

块声明实例化

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

顶级词汇声明的名称

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

结论

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

    Referencing the variable in the block before the variable declaration results in a ReferenceError, because the variable is in a "temporal dead zone" from the start of the block until the declaration is processed.

下面的示例清楚地说明了“let”变量在词法 scope/nested-lexical 范围内的行为方式。

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

来源

ECMA MDN

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

  • 与 var 不同,它们在提升时不会使用默认值进行初始化。
  • 在完全初始化之前,它们不能 read/written。