变量环境创建
变量环境(VariableEnvironment
对象)其实也是一种词法环境,它与上面的词法环境不同的是,其解析的是var
声明变量,存储标识符与对应的引用,其创建过程发生的事情和词法环境差不多,但是关于初始值上有一些差别,如下:
如果我们使用var
声明一个变量b
那么会在变量环境创建的时候解析,存放在环境记录器中,但是其值为undefined
VariableEnvironment: { EnvironmentRecord: { Type: "Object", b: undefined, } outer: <null> } 复制代码
完整结构
好了,我们现在能够将一个完整的上下文结构以伪代码形式表现出来
ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment: { EnvironmentRecord: { Type: "Object", a: < uninitialized >, } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", b: undefined, } outer: <null> } } 复制代码
上下文的创建基本就是这样,不知道掘友有没有理解,如果没有理解可以反复阅读思考(我参考官方文档和各种文章学习了一个礼拜才算对上下文比较透彻),如果理解到位,那么就可以开始学习上下文执行都发生了什么。
上下文执行🚶
上下文的创建是在脚本或函数执行前,而执行过程中会进行执行栈和负责相关的行为
执行栈
栈是一种先进后出数据结构,我们的上下文在执行过程正是存储在栈中,我们称作执行栈。
function fn_1 () { fn_2(); } function fn_2() {} fn_1(); 复制代码
我拿上面的脚本来给大家描述这一过程,上面的脚本包含了三个上下文,分别是全局上下文,fn_1
函数上下文,fn_2
函数上下文
- 脚本执行,创建全局上下文并推入栈底
fn_1()
被触发,执行前创建fn_1
函数上下文并推入栈,执行内部代码fn_2()
在fn_1()
执行中被触发,创建fn_2
函数上下文并推入栈,此刻fn_2
为栈顶,执行内部代码fn_2()
执行完毕,出栈fn_1()
执行完毕,出栈- 应用程序关闭,全局上下文出栈
赋值
在上下文的创建阶段,我们在词法环境内部的环境记录器存储了标识符,而在执行阶段,就会进行赋值,执行的伪代码如下
LexicalEnvironment: { EnvironmentRecord: { Type: "Object", a: "猪痞恶霸", } outer: <null> }, 复制代码
销毁回收🗑️
在上下文弹出栈后不会立刻被销毁,想要了解销毁的内容可以查阅垃圾回收相关的知识,垃圾回收不是本文的重点。
带着知识看问题❔
通过上面我们已经大致掌握了执行上下文的原理,带着知识看问题,文章开头我提出了变量提升与作用域问题,那么我们来一一解答。
变量提升问题
关于变量提升:使用var
声明变量,在声明前调用为undefined
,undefined
就是我们熟悉的声明但未赋值,这种现象叫做变量提升,但是let
与const
禁止了这一行为,使用let
所声明的变量一定需要在声明后使用。
console.log(bar_1) // undefined console.log(bar) // Cannot access 'bar' before initialization let bar = 2 var bar_1 = 1 复制代码
那么let
和const
是如何阻止变量提升的呢?
我们回到上下文创建这个过程中,词法环境和变量环境在创建过程中会解析不同形式声明的变量,词法环境的创建会解析let
与const
声明的变量并存入环境记录器对象中,并标记其并没有初始化,而变量环境的创建会解析var
声明的变量存入环境记录器对象中,其值为undefined
,这就是为什么使用var
声明的变量会发生变量提升并且打印值为undefined
作用域问题
为什么能访问上层作用域中的变量,不能访问下层作用域中的变量,那么我们就需要思考我们内部是以什么形式访问上层作用域的
还记得我们词法环境中的外部环境引用outer
吗,作为词法环境的一个组成部分,可以访问父级上下文的词法环境,也就可以访问到上层作用域的成员,而词法环境中并没有内部环境引用的组成,所以无法对子级词法环境进行一个访问。
最后◀️
本文参考了javascript
高级程序设计,译文,其他相关作者的参考文章,经过一个星期的学习理解,耗费三天时间总结出这篇,如有一些相关问题或者看法,欢迎各位掘友大佬在评论区留言,我们一起学习交流!✌️