今天看了大佬一个文章我用了两个月的时间才理解 let - 知乎 (zhihu.com),文章中其实说得很清楚,还有大佬解决这个问题的整个心路历程。我这里做一个总结记录,专注于“变量提升”、暂时性死区这两个点做一个讨论。
现象
讨论下面这两段代码,我们都知道这段代码在控制台会打印undefined
console.log(x); // undefined
var x = 1
而下面这段代码,在控制台打印是会报错的,报什么错先不用在意
console.log(x); // Uncaught ReferenceError: x is not defined
分析其原因的时候,很多人会说因为v8在进行预解析的时候进行了“变量提升”,var x
被提升到了最前面,所以第一段代码没有报错,而是打印了undefined。我们暂时认可这种说法,因为目前还能解释我们看到的现象。
接下来讨论下面这两段代码:
console.log(x); // Uncaught ReferenceError: Cannot access 'x' before initialization
let x = 1
console.log(x); // Uncaught ReferenceError: x is not defined
这个时候问题就来了,let存在的时候,到底有没有进行“变量提升”呢?
- 如果没有进行变量提升,那么上面两段代码应该都报一样x is not defined的错误才对;
- 如果进行了“变量提升”,那么为什么使用let声明的变量不会与var一样,打印undefined?这时我们可能会想到,这是由于暂时性死区的存在,是它限制了我们不能在let声明之前使用let定义的变量。
这样看,第二种说法可以自圆其说,也是大多数人认为的,但是它不够深入。本文开头提到的知乎大佬的文章中说他咨询了TC39成员,得到的回复是let hoisting不是一个正式词汇,也就是说我们常说的“变量提升”并不是一种确定的行为,而是对行为的一种归纳解释。那么js创建变量时真正的行为是什么呢?
从“提升”角度看解析js时的变量创建过程
在v8执行js代码之前,会对代码进行解析,此时可能会对变量进行三种操作:「创建create、初始化initialize 和赋值assign」。而对于不同的变量声明方式来说:
- let、const:
「创建」过程被提升(或者说执行)了。 - var:
「创建」和「初始化」都被提升(或者说执行)了。 - function:
「创建」「初始化」和「赋值」都被提升(或者说执行)了。
所以总结来说,任何变量声明,都存在提升(或者说预先执行),只不过对于不同的声明方式,提升(或者说预先执行)的部分不同。
而暂时性死区(TDZ),在这个Gist hoisting-vs-tdz.md · GitHub中也说得很明确,结合「创建create、初始化initialize 和赋值assign」三种操作来说,暂时性死区就是在进入作用域之后(也就是「创建」之后),直到let x
这个变量声明语句所代表的「初始化」过程中的这段区域。此时由于变量未初始化,所以使用时会抛出Uncaught ReferenceError: Cannot access 'x' before initialization
错误。