一、执行上下文的核心概念
1. 定义
执行上下文(Execution Context)是 JavaScript 引擎对代码进行解析和执行时创建的环境,它定义了变量、函数和 this 的访问规则。
2. 作用
- 确定代码中变量和函数的作用域;
- 管理 this 的指向;
- 控制代码的执行顺序。
二、执行上下文的类型
| 类型 | 触发场景 | 核心特性 |
|---|---|---|
| 全局上下文 | 浏览器加载 JavaScript 文件时创建 | - 包含全局对象(如 window) - 定义全局变量和函数 - 仅存在一个 |
| 函数上下文 | 函数被调用时创建 | - 每个函数调用对应独立上下文 - 包含 arguments 对象 - 支持闭包 |
| eval 上下文 | 使用 eval() 执行字符串代码时创建 | - 已较少使用,存在安全风险 |
三、执行上下文的创建过程
以函数上下文为例,其创建分为两个阶段:编译阶段和执行阶段。
1. 编译阶段:创建执行上下文(EC)
function example(a) {
var b = 2;
function c() {}
let d = 3;
}
example(1);
执行上下文创建步骤:
-
1. 确定 this 指向
- 函数调用时:
this指向调用者(如obj.example()中this指向obj); - 普通函数调用:默认指向全局对象(浏览器中为
window); - 箭头函数:继承外层上下文的
this。
- 函数调用时:
-
2. 生成变量环境(Variable Environment)
- 变量提升:var 声明的变量、函数声明会被提升到作用域顶部,初始值为
undefined; - let/const 暂时性死区:let/const 声明的变量在声明前无法访问(TDZ 机制)。
- 变量提升:var 声明的变量、函数声明会被提升到作用域顶部,初始值为
-
3. 生成词法环境(Lexical Environment)
- 存储函数的参数、变量和 inner 函数;
- 包含指向外层词法环境的引用(形成作用域链)。
2. 执行阶段:执行代码
- 按顺序执行代码,为变量赋值,调用函数;
- 函数执行完毕后,其执行上下文从调用栈中弹出(除非存在闭包引用)。
四、执行上下文栈(调用栈)
1. 定义
- 用于管理执行上下文的栈结构,遵循「后进先出(LIFO)」原则。
2. 工作流程
// 示例代码
function a() {
console.log('a 开始');
b();
console.log('a 结束');
}
function b() {
console.log('b 执行');
}
// 执行过程
console.log('全局开始');
a();
console.log('全局结束');
调用栈变化:
- 全局上下文入栈 →
[全局上下文] - 执行
console.log('全局开始') - 调用
a(),a 函数上下文入栈 →[全局上下文, a 上下文] - 执行
console.log('a 开始') - 调用
b(),b 函数上下文入栈 →[全局上下文, a 上下文, b 上下文] - 执行
console.log('b 执行'),b 函数执行完毕,出栈 →[全局上下文, a 上下文] - 执行
console.log('a 结束'),a 函数执行完毕,出栈 →[全局上下文] - 执行
console.log('全局结束'),全局上下文在页面关闭时出栈
五、经典案例:闭包与执行上下文
function outer() {
var count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const counter = outer();
counter(); // 1
counter(); // 2
执行上下文分析:
- 调用
outer()时,创建 outer 上下文,入栈; - outer 执行完毕后返回 inner 函数,其上下文出栈,但 inner 引用了 outer 中的
count; - 由于闭包存在,outer 上下文中的
count被保留在内存中; - 每次调用
counter()(即 inner 函数)时,创建 inner 上下文,通过作用域链访问 outer 的count,实现累加。
六、问题
1. 问:变量提升和暂时性死区的区别?
- 答:
- 变量提升:var 声明的变量和函数声明会被提升到作用域顶部,初始值为
undefined; - 暂时性死区:let/const 声明的变量在声明前处于 TDZ,访问会报错(
ReferenceError)。
- 变量提升:var 声明的变量和函数声明会被提升到作用域顶部,初始值为
2. 问:执行上下文和作用域的关系?
- 答:
- 执行上下文是代码执行时的环境,包含作用域信息;
- 作用域是变量的访问规则,由词法环境中的作用域链决定。
3. 问:为什么递归函数可能导致栈溢出?
- 答:
- 每次递归调用都会创建新的函数上下文入栈;
- 若递归深度过大,调用栈超出内存限制,会抛出
RangeError: Maximum call stack size exceeded。
总结
执行上下文是 JavaScript 引擎的核心机制,它定义了代码执行时的变量访问、this 指向和作用域规则。理解执行上下文的创建过程、调用栈机制和闭包对上下文的影响,是掌握 JavaScript 异步编程、作用域链和性能优化的基础。在实际开发中,合理管理执行上下文(如避免过深的递归、正确处理闭包)能有效避免内存泄漏和栈溢出问题。