我已经玩了一段时间的ES6,我注意到,而变量声明与var被提升的预期…

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

...使用let或const声明的变量似乎在提升时存在一些问题:

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

and

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

这是否意味着用let或const声明的变量不会被提升?这到底是怎么回事?let和const在这个问题上有什么区别吗?


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

变量是在实例化包含它们的lexicalenvironment时创建的,但在变量的LexicalBinding被求值之前,不能以任何方式访问变量。

因此,回答你的问题,是的,let和const提升,但在运行时评估实际声明之前不能访问它们。


@thefourtheye说的对,这些变量在声明之前不能被访问。然而,实际情况要比这复杂一些。

使用let或const声明的变量不被提升吗?这到底是怎么回事?

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

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

The difference between var/function/function* declarations and let/const/class declara­tions is the initialisation. The former are initialised with undefined or the (generator) function right when the binding is created at the top of the scope. The lexically declared variables however stay uninitialised. This means that a ReferenceError exception is thrown when you try to access it. It will only get initialised when the let/const/class statement is evaluated, everything before (above) that is called the temporal dead zone.

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

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

注意到a let y;语句用undefined初始化变量,如let y = undefined;会。

时间死区不是一个语法位置,而是变量(作用域)创建和初始化之间的时间。在声明上面的代码中引用变量并不是错误,只要该代码没有被执行(例如,函数体或简单的死代码),并且如果你在初始化之前访问变量,即使访问代码在声明下面(例如,在提升的函数声明中被过早调用),它也会抛出异常。

let和const在这个问题上有什么区别吗?

不,就吊装而言,它们的工作原理是一样的。它们之间唯一的区别是常量必须且只能在声明的初始化器部分赋值(const one = 1;,两者都是const one;并且以后像1 = 2这样的重赋是无效的)。

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


ES6引入了Let变量,用于块级作用域。在ES5之前,我们没有块级作用域,所以在块中声明的变量总是提升到函数级作用域。

基本上作用域指的是在你的程序中你的变量是可见的,这决定了你可以在哪里使用你声明的变量。在ES5中,我们有全局作用域、函数作用域和try/catch作用域,在ES6中,我们还使用Let来获得块级作用域。

When you define a variable with var keyword, it's known the entire function from the moment it's defined. When you define a variable with let statement it's only known in the block it's defined. 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只在循环中已知,而不是在循环前后已知。然而,我们的变量i在整个函数中是已知的,从它被定义的那一刻起。

使用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循环总是打印最后一个值,让它创建一个新的作用域并绑定新的值,打印出1,2,3,4,5。

对于常量,它的工作原理基本类似于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 .freeze([])


来自MDN web文档:

在ECMAScript 2015中,let和const被提升但没有初始化。在变量声明之前在块中引用变量会导致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

As代表ECMAScript 2021

Let和Const声明

let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Environment Record is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated. A variable defined by a LexicalBinding with an Initializer is assigned the value of its Initializer's AssignmentExpression when the LexicalBinding is evaluated, not when the variable is created. If a LexicalBinding in a let declaration does not have an Initializer the variable is assigned the value undefined when the LexicalBinding is evaluated.

块声明实例化

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

顶级词法声明的名称

在函数或脚本的顶层,函数声明被视为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'给出了一个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 中数


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

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