而在编译阶段,我们将var定义的变量全都在编译过程在变量环境初始化为undefined,但是用let和const定义的变量,其实他们并未在变量环境初始化,而是在词法环境初始化,也就是执行代码位置初始化。
词法环境的特点:按照{}划分的一个栈结构。
变量查找方式
JavaScript中变量查找的方式:沿着词法环境的栈顶向下查找,找不到的变量去变量环境中查找,这样就形成了先查找代码块中的变量,再查找提升之后的变量环境,这样就形成了块级作用域的概念。
上面的代码形成两种环境的情况如下:
一、全局环境的执行上下文
变量环境:函数声明function doSomething() { … }
词法环境栈:执行到let name = ‘someName';让语句name = ‘someName'入栈。
二、doSomething的执行上下文(被全局环境包裹)
变量环境:无
词法环境栈情况:执行到let name = ‘otherName',语句的时候,name = ‘other'才会入栈;
JavaScript代码执行方式
执行doSomething的时候,还未执行let name = ‘otherName',所以,此时doSomething的词法环境中并未有name = ‘otherName',这个时候查找,只能向外部作用域查找(全局作用域)
此时查找到全局作用域name = ‘someName'所以此时就打印了someName
代码接着执行到了if语句内部,才会将name = ‘otherName'入栈,但是此时因为语句已经执行完毕,所以也就无关痛痒了。
JavaScript也就通过这种方式,实现了块级别作用域。
总结
JavaScript中的作用域总的来说,分为块级作用域、函数作用域、全局作用域。
而每个作用域都会创建自身的执行上下文,每一个执行上下文又分为了变量环境和词法环境两部分。
块级作用域的实现,其实是根据定义的let和const声明的变量放置在词法环境栈中这一特性来实现。
这一特性被社区的人叫做‘暂时性死区',但是在JavaScript标准中并未有这个概念。
只有理解了作用域的概念,才能真正明白JavaScript的执行机制,才能减少我们因为变量定义等发生的错误。