1. 什么是执行上下文?
执行上下文 是一个包含了 JavaScript 执行时所有信息的环境。它决定了哪些变量和函数是可访问的,并控制代码执行的顺序和上下文的生命周期。每当 JavaScript 引擎开始执行代码时,都会创建一个执行上下文,并按照一定规则管理执行过程。
2. 执行上下文的三种类型
JavaScript 的执行上下文有以下三种类型:
-
全局执行上下文(Global Execution Context):当 JavaScript 引擎加载并执行全局代码时,会创建一个全局执行上下文。全局上下文是执行栈中的第一个上下文,且只有一个实例,代表整个应用程序的环境。
-
函数执行上下文(Function Execution Context):每当调用一个函数时,JavaScript 会为该函数创建一个执行上下文,执行该函数的代码,并在执行完毕后销毁。
-
Eval 执行上下文(Eval Execution Context):
eval()
函数中执行的代码会在一个特定的上下文中执行。虽然现代 JavaScript 中很少使用eval
,但它仍然会创建新的执行上下文。
3. 执行上下文的生命周期
执行上下文的生命周期分为三个阶段:
-
创建阶段(Creation Phase):
- 创建执行上下文的变量对象(Variable Object,简称 VO),并初始化变量、函数声明等。全局上下文的变量对象称为 Global Object。
- 在创建阶段,函数声明会被提升到顶部,变量声明(不包括赋值)会初始化为
undefined
。
-
执行阶段(Execution Phase):
- 按照代码的顺序执行,赋值操作会按照代码中的顺序进行。
-
销毁阶段(Destruction Phase):
- 执行完毕后,执行上下文会被销毁。
4. 作用域链
每个执行上下文都会创建一个 作用域链(Scope Chain),用于查找变量和函数。作用域链的构成包括:
- 局部作用域:当前执行上下文中的变量和函数。
- 外部作用域:如果当前上下文中没有找到所需变量或函数,JavaScript 会向外部上下文查找。
作用域链确保了 JavaScript 变量的访问顺序和作用范围。
5. 执行上下文栈
JavaScript 引擎使用 执行上下文栈(Execution Context Stack)(也称为 调用栈)管理执行上下文。每当一个新的执行上下文被创建时,它会被压入栈顶。栈遵循 后进先出(LIFO) 的原则。
- 执行全局代码时,创建一个全局执行上下文并将其压入栈顶。
- 调用函数时,创建函数的执行上下文并将其压入栈顶。
- 函数执行完成后,它的执行上下文会从栈顶弹出,控制权返回给上一个上下文。
6. 执行上下文的组成
每个执行上下文包含以下部分:
- 变量对象(VO):存储当前上下文中声明的变量和函数。
- 作用域链(Scope Chain):用于访问当前执行上下文及其外部上下文中的变量。
this
:指向当前执行上下文中的执行者对象。
7. 全局执行上下文
全局执行上下文是 JavaScript 执行栈中的第一个上下文。当浏览器加载网页时,默认执行的是全局代码,创建全局执行上下文。在浏览器环境中,全局上下文会被赋予一个 window
对象,在 Node.js 中是 global
对象。
- 全局变量:在全局执行上下文中声明的变量和函数成为全局对象的属性。
- 全局作用域链:全局执行上下文的作用域链只有全局作用域。
8. 函数执行上下文
每当函数被调用时,JavaScript 会为该函数创建新的执行上下文。该上下文包含了函数的局部变量、参数等信息。
- 参数变量:函数的参数会成为变量对象的一部分。
- 作用域链:函数执行上下文的作用域链包含函数自身的作用域以及调用该函数的外部作用域。
9. 示例代码
// 全局执行上下文
var a = 1;
function foo() {
// 函数执行上下文
var b = 2;
console.log(a + b); // 输出 3
}
foo();
在执行 foo()
时,JavaScript 会为该函数创建一个执行上下文,并将其压入调用栈。该上下文有自己的作用域链,会先查找局部变量 b
,如未找到,则会向外部作用域(全局上下文)查找变量 a
。
10. 变量提升(Hoisting)
JavaScript 中的 变量提升 是指变量和函数声明会被提升到其作用域的顶部。函数声明会被提升并赋予相应的值,而变量声明会被提升但赋值为 undefined
。
console.log(a); // 输出 undefined
var a = 10;
上述代码中,变量 a
被提升到顶部,但其值在执行阶段才会赋为 10
,因此打印出来的是 undefined
。
11. 总结
执行上下文是 JavaScript 执行过程中的核心概念之一。每当代码执行时,JavaScript 会根据调用栈的顺序创建执行上下文,并管理代码的执行和变量的访问。理解执行上下文对于理解 JavaScript 的作用域、闭包、this
等概念至关重要。
代码示例:执行上下文的嵌套
// 1. 全局执行上下文
const globalVar = '全局变量';
function outer() {
// 2. 函数执行上下文
const outerVar = '外层变量';
function inner() {
// 3. 嵌套函数执行上下文
const innerVar = '内层变量';
console.log(globalVar + outerVar + innerVar);
}
inner();
}
outer();
代码示例:执行上下文的创建过程
- 创建阶段
function example(a) {
// 创建阶段:
// 1. 创建变量对象(VO)
// - 创建 arguments 对象
// - 扫描函数声明(优先)
// - 扫描变量声明(初始值为 undefined)
// 2. 建立作用域链
// 3. 确定 this 指向
console.log(b); // undefined(变量提升)
var b = 10;
function c() {} // 函数声明优先
}
// 执行阶段:
// 变量赋值 → 执行代码
- 执行栈(调用栈)
// 后进先出(LIFO)结构示例:
function first() {
console.log('进入 first');
second();
console.log('离开 first');
}
function second() {
console.log('进入 second');
third();
console.log('离开 second');
}
function third() {
console.log('进入 third');
console.log('离开 third');
}
first();
// 输出顺序:
// 进入 first → 进入 second → 进入 third → 离开 third → 离开 second → 离开 first
关键特性:作用域链与 this
// 作用域链示例
function parent() {
const parentVar = '父级';
return function child() {
const childVar = '子级';
console.log(parentVar + childVar); // 通过作用域链访问父级变量
};
}
// this 绑定示例
const obj = {
name: '对象',
logName: function() {
console.log(this.name); // this 指向调用对象
}
};