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

我使用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之间有什么区别吗?

84281 次浏览

引用ECMAScript 6 (ECMAScript 2015)规范,__ABC0和const声明部分,

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

因此,为了回答你的问题,是的,letconst提升,但在运行时计算实际声明之前不能访问它们。

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

letconst声明的变量是否未提升?这到底是怎么回事?

所有的声明 (varlet constfunction 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;
}

对于函数和块作用域__abc0都是如此。

var/function/function*声明和let/const/class声明之间的区别是初始化.
. 0声明 前者在作用域顶部创建绑定时使用undefined或(generator)函数初始化。然而,词法声明的变量保持uninitialised。这意味着当你试图访问它时,会抛出ReferenceError异常。它只会在let/const/class语句被求值时才会被初始化,在(上面)之前的所有被称为时间死区.

. 0
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声明当然仍然只在函数级别上工作

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

If you run the code, you could see the variable j is only known in the loop and not before and after. Yet, our variable i is known in the entire function from the moment it is defined onward.

There is another great advantage using let as it creates a new lexical environment and also binds fresh value rather than keeping an old reference.

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

MDN web文档:

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

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

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

// 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值在创建变量时不会
  • 如果一个LexicalBinding在let声明没有初始化器,当LexicalBinding被求值时,变量被赋值为undefined

块声明实例化

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

顶级词法声明的名称

在函数或脚本的顶层,函数声明被视为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中,新声明的“mylet"函数中的变量在log语句之前没有初始化器,因此值为“undefined"”。

ECMA 中数 < / p >

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

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