闲言
最近在看《高性能Js》,里边提到了作用域相关的知识点,回头看了之前我写过关于作用域和闭包的博客,发现写得过于简洁。在翻阅了《Js权威指南》和《你不知道的Js》等相关文章后,有了一些更深刻的感悟,并尽可能详细、清晰地描述相关概念。
执行上下文
Execution Contexts 简称 EC。
每次当控制器转到 ECMAScript 可执行代码(Executable Code)的时候,会创建并进入一个执行上下文中。每个 EC 都包含三个重要属性:
变量对象(Variable Object,VO)
Scope(作用域链,Scope Chain)
this
注:Executable Code 包含了全局代码(不包括任何函数体内的代码)、函数代码(不包括内部函数代码)、Eval 代码
EC 类型
全局执行上下文:Global Context,它是最外围的一个 EC。当执行全局代码时,会创建全局执行上下文。
函数执行上下文:Function Context,每个函数都有自己的 EC,当执行函数代码时,会创建函数执行上下文。
这些 EC 会通过执行上下文栈(Execution context stack,ECS)管理,ECS 底部永远都是全局上下文,而顶部就是当前(活动的)EC。
EC 生命周期
1.创建阶段
在执行 Executable Code 时 EC 被创建。在这个阶段中,EC 会分别创建变量对象 VO,建立作用域链,以及确定 this 指向。
2.EC 中代码执行阶段
创建完成之后,就会开始执行代码,这个时候,会完成 V0 中变量赋值,函数引用,以及执行其他代码。
3.EC 中代码执行完毕
对应 EC 被销毁,保存在其中的变量和函数定义也随之销毁。
注:全局 EC 直到应用程序退出(如关闭网页或浏览器)时才被销毁。
变量对象的修改变化与前两个阶段紧密相关,我们会在变量对象那节进行详细介绍。
过程模拟
声明:此处仅对 EC 进行模拟,EC 内部变化会在相关概念介绍完时,进行一次整体模拟。
我们直接通过伪代码来模拟执行上下文栈:
首先,ESC我们这样表示:
ECStack = [];
Js 解析时,首先执行全局代码
创建 globalContext;
将 globalContext 压入 ECS: ECStack.push(globalContext);
ECStack = [
globalContext
];
接着,遇到如下代码:
function fun3() {
console.log('fun3')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
当执行一个函数的时候,就会创建一个 EC,并且压入 ECS,当函数执行完毕的时候,就会将函数的 EC 从 ECS 中弹出。过程如下:
执行fun1 ==> ECStack.push(<fun1> functionContext);
执行fun2 ==> ECStack.push(<fun2> functionContext);
执行fun3 ==> ECStack.push(<fun3> functionContext);
fun3执行完毕 ==> ECStack.pop();
fun2执行完毕 ==> ECStack.pop();
fun1执行完毕 ==> ECStack.pop();
相关代码执行完毕后,ECStack 只会保持着 globalContext,一直到整个应用程序结束。