大半个月前,去重温了一下 冴羽 的博客。就想着把执行上下文的东西写写,但是因为考试,一直没有写出来,现在有空,刚好就把它给写完,作为 2018 年最后一篇技术文。
执行上下文栈
当 JavaScript
顺序执行下来的时候,会遇到三种可执行代码,全局代码
,函数代码
,eval代码
当 JavaScript 遇到执行到这些代码的时候,会创建一个环境,执行上下文
,与此同时,会将这个环境压入一个名为执行上下文栈
的环境去管理不同的执行上下文。
执行上下文(Execution context);
执行上下文栈(Execution context stack, ECS);
形象点可以模拟成一个数组 ECStack = [];
在 Javascript 开始解释代码的时候,最先遇到的是全局代码,所以全局代码的执行上下文最先入栈,globalContext.
|
|
当遇到需要执行一个函数时,生成执行上下文,会将其压入 ECStack 栈的栈底,当函数执行完毕的时候,将其弹出执行上下文栈
全局代码的执行上下文贯穿整份代码前后,所以不管怎样,只要 js 代码还没运行完,ECStack 中始终会有 globalContext.
执行上下文
每个执行上下文都有三个重要的属性
- 变量对象 / 活动对象(variable object / activation object, VO / AO)
- 作用域链(scope chain)
- this
变量对象 (Variable object VO)
变量对象是与执行上下文相关的数据作用域,存储了上下文种定义的变量和函数声明
也就是说,变量对象其实是存了该环境下的变量和函数。
全局上下文的变量对象就是全局对象
函数上下文中用活动对象 (activation object, AO) 表示变量对象
变量对象是在进入执行上下文时创建的,所以变量对象在一开始执行代码的时候就被创建了,因为此时全局代码的执行上下文被创建了;但是活动对象,是在函数被调用时通过函数的 arguments 属性初始化,然后生成对应的执行上下文,随函数执行完毕而销毁,除了闭包这种情况。
函数的执行过程:分析和执行,可分为 进入执行上下文
代码执行
进入执行上下文
这时候是初始化 arguement,函数声明,变量声明
这个阶段中,函数声明优先变量声明,如果变量名称和函数名称相同,则被忽略,不处理名称相同的变量名称。
- 如果函数体中没有用 var 去声明变量,这个变量不会进入函数执行上下文中
|
|
1、调用这段代码时,先通过 arguements 初始化
|
|
2、进入执行上下文,添加形参、函数声明、变量声明,函数在此阶段会优先得到声明
|
|
代码执行
3、顺序执行,改变 AO 中变量对象的值
|
|
作用域链(Scope chain)
查找变量的时候,现在当前上下文的变量对象中查找,如果没有,就往上一级找,最终一直找到全局上下文的变量对象,这就构成了作用域链
以函数的创建和激活两个时期来讲解作用域链是如何创建和变化的
创建
在函数创建时,会将所有父级的变量对象保存到函数的属性中,[[scope]]
|
|
如上创建函数的作用域链
激活
当函数激活时,创建完活动对象时,会将当前活动对象加入作用域链的顶端
|
|
1、创建时,生成函数的[[scope]]
属性
|
|
2、开始执行的时候,函数入执行上下文栈
|
|
3、准备开始之前,会将函数的[[scope]]
复制创建函数执行上下文的作用域链
|
|
4、执行函数第一步,分析函数,进入执行上下文,生成活动对象
|
|
5、执行函数第二步,将活动对象压入 Scope 顶端
|
|
6、执行函数第三步,代码执行,按代码顺序执行,改变 AO 中变量对象的值
|
|
7、顺序执行完函数代码,函数上下文从执行上下文栈中弹出
|
|
至此,函数执行上下文的变量对象和作用域链已经创建完成,至于
this
嘛,有点麻烦,加之天色已经不早了,那就明年再写了吧。
- 情不知所起,一往而深