是用let或const声明的变量

Are variables declared with let or const hoisted?

本文关键字:变量 声明 const let 是用      更新时间:2023-09-26

我玩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在这个问题上有什么不同吗?

@thefortheye说这些变量在声明之前不能访问是正确的。然而,这比这要复杂一些。

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

所有声明varletconstfunctionfunction*class都是";"吊装"。这意味着,如果在一个作用域中声明了一个名称,那么在该作用域中,标识符将始终引用该特定变量:

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;语句使用undefined初始化变量,就像let y = undefined;一样。

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

letconst在这个问题上有什么不同吗?

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

1:var声明仍然只在函数级上工作,当然

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

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

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

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循环总是打印最后一个值,使用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([])

根据ECMAScript®2021

Let和Const声明

  • let和const声明定义了作用域为正在运行的执行上下文的LexicalEnvironment的变量
  • 变量是在实例化其包含的环境记录时创建的,但在评估变量的LexicalBinding之前,可能无法以任何方式访问这些变量
  • 当评估LexicalBinding时,由具有Initializer的Lexicalbind定义的变量将被分配其Initializer赋值表达式的值,而不是在创建变量时被分配
  • 如果let声明中的LexicalBinding没有Initializer,则在评估Lexicalbind时会为变量分配未定义的值

块声明实例化

  • 当评估块或CaseBlock时,将创建一个新的声明性环境记录,并在环境记录中实例化块中声明的每个块范围的变量、常量、函数或类的绑定
  • 无论控件如何离开块,LexicalEnvironment都将始终恢复到以前的状态

顶级词汇声明名称

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

结论

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

    在变量声明之前引用块中的变量会导致ReferenceError;时间死区"从块开始直到处理声明为止

下面的例子清楚地说明了";设";变量在词法作用域/嵌套词法作用域中表现。

示例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中;聚酯薄膜";函数内部的变量在log语句之前没有Initializer,因此值";未定义";。

来源

ECMAMDN

来自MDN web文档:

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

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
let和const也被吊起。但是,由于以下原因,如果在初始化之前读取了用let或const声明的变量,则会引发异常。
  • 与var不同,它们在提升时不会使用默认值进行初始化
  • 在完全初始化之前,无法读取/写入它们