在上一篇文章中讲了 执行上下文栈,本篇文章对执行上下文相关的概念和内部的操作通过代码做具体的分析。
1、什么是执行上下文?
执行上下文描述了代码在运行时的环境和条件。在 JavaScript 中,有三种执行上下文:全局执行上下文、函数执行上下文和 eval 执行上下文。
-
全局执行上下文:
- 全局执行上下文是整个程序的默认执行上下文,它在代码开始执行时创建。
- 包含全局变量和函数,以及
this的值指向全局对象(在浏览器中通常是window)。
-
函数执行上下文:
- 每当调用一个函数时,都会创建一个新的函数执行上下文。
- 包含局部变量、参数、函数内部声明的其他函数,以及
this的值。 - 函数执行完毕后,其执行上下文被销毁。
-
eval 执行上下文:
- 在使用
eval函数时,会创建一个 eval 执行上下文。 - eval 中的代码在 eval 执行上下文中运行,可以访问 eval 执行上下文外部的变量。
- 在使用
执行上下文通过执行栈(调用栈)的形式组织,栈顶始终是当前正在执行的上下文。当函数被调用时,新的执行上下文被推入栈顶;当函数执行完成时,对应的执行上下文从栈中弹出。
这种执行上下文的管理机制确保了代码的执行顺序和变量的作用域
2、执行上下文的三个重要属性
-
变量对象(Variable Object, VO):
- 每个执行上下文都有一个关联的变量对象,用于存储函数内的变量和函数声明。
- 在全局上下文中,变量对象即全局对象。
- 在函数上下文中,变量对象包括函数的参数、内部声明的变量和函数。
-
作用域链(Scope Chain):
- 作用域链是一个指向变量对象的链表,用于解析变量和函数的作用域。
- 由当前执行上下文的变量对象和所有外部(包括父级)执行上下文的变量对象组成。
- 作用域链的顶端是当前执行上下文的变量对象。
-
this 值:
- this 值表示当前执行上下文的对象,它的值取决于函数的调用方式。
- 在全局上下文中,this 指向全局对象。
- 在函数上下文中,this 的值由函数的调用方式决定,可以是对象实例、指定的对象或者 undefined。
3、执行上下文的执行顺序
- 创建阶段(Creation Phase)
- 创建变量对象(VO):储存函数的形参、函数声明、变量声明(对于全局上下文,VO 就是全局对象)
- 确定作用域链(Scope Chain):
- 确定可访问的变量范围。
- 将当前执行上下文的变量对象添加到作用域链中。
- 对于函数上下文,还包括其父级执行上下文的作用域链
- 确定this的值
- 对于函数上下文,确定 this 的引用。
- 在全局上下文中,this 指向全局对象。
- 执行阶段(Execution Phase)
- 变量分配(Variable instantiation):
- 执行阶段会分配内存空间给变量,并进行初始化(如果有赋值操作)。这确保在代码执行时可以使用先前声明的变量
- 代码执行:
- 实际执行代码的阶段。JavaScript 引擎按照代码的顺序逐行执行,包括条件语句、循环、函数调用等。
- 处理函数调用(Function Invocation):
- 对于函数调用,执行阶段会创建一个新的执行上下文,并将其推入执行栈。执行函数体,处理函数内部的变量和代码。
- 更新词法环境(Lexical Environment Update):
- 根据执行过程中的变量赋值等操作,可能会更新词法环境中的值。
- 处理 this 关键字:
- 确定执行上下文中
this关键字的值,具体取决于函数的调用方式
- 确定执行上下文中
- 变量分配(Variable instantiation):
- 上下文销毁阶段
- 变量释放(Variable Cleanup):
- 在销毁阶段,JavaScript 引擎会释放执行上下文中的变量,包括局部变量和函数声明。这有助于释放内存并减少不再需要的变量的占用
- 解绑引用(Reference Detachment):
- 执行上下文销毁时,引擎会解绑对变量的引用,这有助于垃圾回收机制判断是否可以回收这些变量所占用的内存。
- 函数调用栈的弹出(Pop from Call Stack):
- 如果执行上下文是由函数调用而创建的,那么在销毁阶段,该执行上下文将从调用栈中移除,让控制权返回到上一个执行上下文。
- 资源释放:
- 如果执行上下文中涉及到了一些外部资源的分配,例如事件监听器或定时器,销毁阶段可能会涉及释放这些资源,以避免内存泄漏。
- 变量释放(Variable Cleanup):
4、代码分析
const globalVar = "I am global";
function outerFunction(outerParam) {
const outerVar = "I am outer";
function innerFunction(innerParam) {
const innerVar = "I am inner";
console.log("outerVar:", outerVar);
}
innerFunction("Inner");
}
outerFunction("Outer");
1. 执行全局代码
1.1、创建全局执行上下文
const globalEC = {
VO: {
globalVar: undefined,
outerFunction: reference to function outerFunction() {},
// 其他的全局属性
},
scopeChain: [globalEC.VO],
this: globalEC.VO,
};
1.2、全局执行上下文被推入执行上下文栈
Execution Context Stack:
-------------------------
| globalEC |
-------------------------
1.3、与此同时,在代码解析阶段,outerFunction函数被创建,并在函数的[[Scope]]属性中创建作用域链
outerFunction.[[Scope]] = [globalEC.VO];
2. 执行outerFunction函数
2.1、创建outerFunction函数的执行上下文
const outerFunctionEC = {
AO: {
arguments: {
0: outerParam,
length: 1
},
outerParam: undefined,
outerVar: undefined,
outerFunction: reference to function outerFunction() {},
// 其他的全局属性
},
scopeChain: [outerFunctionEC.VO, globalEC.VO],
this: undefined,
};
// 在这一步具体做了以下操作
// 1 用函数 [[scope]] 属性创建作用域链
// 2 创建互动对象,将形参、函数声明、变量声明添加到VO上
// 3 将活动对象推入作用域的顶端
2.2、 推入执行上下文栈
Execution Context Stack:
-------------------------
| outerFunctionEC |
| globalEC |
-------------------------
2.3、 和1.3一样,代码解析阶段innerFunction函数被创建,并在函数的[[Scope]]属性中创建作用域链
innerFunction.[[Scope]] = [outerFunctionEC.VO, globalEC.VO];
下面的步骤和outerFunction的执行过程一样
3. 执行innerFunction函数
3.1、创建innerFunction函数的执行上下文
const innerFunctionEC = {
AO: {
arguments: {
0: innerParam
length: 1
},
innerParam: undefined,
innerVar: undefined,
innerFunction: reference to function innerFunction() {},
// 其他的全局属性
},
scopeChain: [innerFunctionEC.VO, outerFunctionEC.VO, globalEC.VO],
this: undefined,
};
// 在这一步具体做了以下操作
// 1 用函数 [[scope]] 属性创建作用域链
// 2 创建互动对象,将形参、函数声明、变量声明添加到VO上
// 3 将活动对象推入作用域的顶端
3.2、推入执行上下文栈
Execution Context Stack:
-------------------------
| innerFunctionEC |
| outerFunctionEC |
| globalEC |
-------------------------
4. innerFunction函数执行,查找outerVar,自身作用域没查到,向上查找,在上一层找到,返回outerVar的值
5. innerFunction函数执行完毕,从上下文栈中弹出
Execution Context Stack:
-------------------------
| outerFunctionEC |
| globalEC |
-------------------------
6. outerFunction函数执行完毕,从上下文栈中弹出
Execution Context Stack:
-------------------------
| globalEC |
-------------------------
文章中描述不到位的地方,欢迎大家在评论区多多指正🙏