Round 1

- 我现在把所有东西都注释掉,改成这样
function a() {
return inner;
let inner;
}
返回什么?

- 那就应该 undefined 了,反正后面不执行

自信的不行,出了门掏出全世界最牛逼的千元机坚果 Pro 2S,验证了一下。

inner is not defined

确实是不执行,但好像不是 undefined 啊╭(*゚Д゚*)╮

Round 2

- 如果改成这样呢
function a() {
function inner() {}
return inner;
let inner;
}
返回什么?

- 返回方法

自信的不行,同样用全世界最牛逼的千元机坚果 Pro 2S,验证了一下。

Identifier ‘inner’ has already been declared

(゚Д゚≡゚д゚)!? 你在返回你🐴呢?

Why?

原本我以为只有 var 会变量提升,实际上错了,var/let/const都会。

varlet 在某种时候是相似的,他们都会提升,但 let 少了初始化的过程。MDN 对 let 死区是这么解释的

在 ECMAScript 2015 中,let 绑定不受变量提升的约束,这意味着 let 声明不会被提升到当前执行上下文的顶部。在块中的变量初始化之前,引用它将会导致 ReferenceError(而使用 var 声明变量则恰恰相反,该变量的值是 undefined )。这个变量处于从块开始到 let 初始化处理的”暂存死区“之中。 —— MDN

暂存死区又有这么个说法,用一个代码块解释一下

function () {

  a = 'caonima' // 下面有 let,a 直接被该块锁定,这里报错

  let a // 到这里才完成初始化,变量开始正常使用,但变量早已提升
  console.log(a) // 这里不是 caonima 而是 undefined

  a = 'woshinidie'
  console.log(a) // woshinidie
}

You-Dont-Know-JS#767 讨论了这个问题, creeperyang 总结了四点

  1. Hoisting includes both declare and initialize.
  2. Only initialized variable can be used in a scope.
  3. var do both declare and initialize, the two cannot be split for var.
  4. let do firstly declare in the top of the scope, and do initialize when encounter the let xxx statements.
    —— @creeperyang

另外,方应杭在知乎专栏的文章《我用了两个月的时间才理解 let》也总结出

  1. let 的「创建」过程被提升了,但是初始化没有提升。
  2. var 的「创建」和「初始化」都被提升了。
  3. function 的「创建」「初始化」和「赋值」都被提升了。
    —— @方应杭

理论上 const 应该是 let 的不可修改版本,所以应该是类似的。但也仅限提升原理相同,因为 const 只能初始化时赋值一次。

不过,阮一峰的《ECMAScript 6 入门》是什么意思?在 let 有单独一小节讲 不存在变量提升,虽然后面解释了暂时性死区,但……

总结:被安排明白了。根据 @Ahonn 所说,这个问题在高程有讲过,书读少了读少了

(最后感谢所有被引用的文章和作者,谢谢。)