因为说let没有变量提升,我被嘲笑了

最近在和同事闲聊var和let的区别时,我被嘲笑了,起因是我提出了var具有变量提升的特性而let没有的观点。在我看来这不是最常刷到的面试题吗?但是在一番仔细研究之后我发现事情并不是我想象的这样,即let同样存在变量提升,只是let存在暂时性死区。

变量提升

证明var声明存在变量提升

通常我们是这样子来证明var存在变量提升的。

function fn(){
    console.log(a) // undefined
    var a = 12
}
fn()
复制代码

为什么这段代码会输出undefined,而不是报错呢?

原因就是js在创建执行上下文时,会检查代码,找出变量声明和函数声明,并将函数声明完全存储在环境中,而将通过var声明的变量设定为undefined,这就是所谓的变量提升。从字面上理解就是变量和函数声明会被移动到函数或者全局代码的开头位置。

那么当我们将var替换为let时,结果又会如何?

function fn(){
    console.log(a) // Uncaught ReferenceError: a is not defined
    let a = 12
}
fn()
复制代码

意料之中,代码报错了。很多人通过这个反例,便认为let没有变量提升,但其实这是错误的。上面举的例子只能证明,var存在变量提升,但是并不能证明let不存在变量提升。

证明let声明存在变量提升

我们再举一个例子:

var x = 'parent value';
(function() {
  console.log(x); // parent value
}())
复制代码

代码会输出parent value,原因很简单,涉及到了作用域链的知识。在匿名函数作用域中没有找到x变量,便会沿着作用域链,找到父级作用域,然后便再父级作用域中找到了x变量,并输出。

接着我们在匿名函数中,加入let进行变量声明,此时结果会是如何呢?

var x = 'parent value';
(function() {
  console.log(x); // Uncaught ReferenceError: x is not defined
  let x = 'child value'
}())
复制代码

想不到吧!此时的代码又会报错了,从这里其实可以看出let也是存在变量提升的,知识在变量显式赋值之前不能对变量进行读写,否则就会报错,这也就是所谓的let和const的暂时性死区

暂时性死区(Temporal Dead Zone )

引用MDN上的定义

let bindings are created at the top of the (block) scope containing the declaration, commonly referred to as “hoisting”. Unlike variables declared with var, which will start with the value undefined, let variables are not initialized until their definition is evaluated. Accessing the variable before the initialization results in a ReferenceError. The variable is in a “temporal dead zone” from the start of the block until the initialization is processed.

大概意思便是let同样存在变量提示(hoisting),只是形式与var不同,var定义的变量将会被赋予undefined的初始值,而let在被显式赋值之前不会被赋予初始值,并且在赋值之前读写变量都会导致 ReferenceError 的报错。从代码块(block)起始到变量求值(包括赋值)以前的这块区域,称为该变量的暂时性死区。

var x = 'parent value';
(function() {
  // let x 此时暂时性死区开始
  console.log(x); // Uncaught ReferenceError: x is not defined
  //暂时性死区结束
  let x = 'child value' 
}())
复制代码

总结

事实证明let和var同样存在变量提升,而且let声明还具有暂时性死区的概念。

猜你喜欢

转载自juejin.im/post/6983702070293430303