Back to list执行上下文与调用栈
Site Owner
Published on 2026-05-21
本文系统讲解 JavaScript 执行上下文(全局、函数、Eval)的创建与销毁时机,调用栈的工作原理与栈溢出预防,变量对象 VO/AO 的生命周期与作用域链查找机制,以及尾调用优化 TCO 在 V8、SpiderMonkey、JavaScriptCore 中的差异。
执行上下文与调用栈
全局/函数/Eval 上下文的创建与销毁时机;变量对象(VO/AO)与作用域链的演变;调用栈(Call Stack)的隐式限制与尾调用优化(TCO)的现实境况。
目录
- 执行上下文 (Execution Context)
- 调用栈 (Call Stack)
- VO/AO 与作用域链
- 栈溢出与尾调用优化
- 实战案例
- 深度追问
执行上下文
什么是执行上下文?
执行上下文(Execution Context, EC)是代码执行时的环境快照,包含:
- VO (Variable Object) / AO (Activation Object) —— 变量及函数声明的容器
- 作用域链 (Scope Chain) —— 变量查找的链路
- this 绑定 —— 执行主体
三种上下文类型
1. 全局执行上下文 (Global EC)
console.log(this);
.(globalThis);
.();
.(globalThis);
JavaScript 执行上下文与调用栈深度解析 — 理解代码运行的底层机制 | 新宇宙博客console
log
console
log
this
console
log
创建时机: 脚本加载时立即创建
销毁时机: 页面卸载 / 进程退出
VO: 全局对象(window / global / globalThis)
2. 函数执行上下文 (Function EC)
function foo(a, b) {
var c = a + b;
console.log(c);
}
foo(1, 2);
创建时机: 函数调用时
销毁时机: 函数返回时
AO (Activation Object):
- 函数参数:
a=1, b=2
- 本地变量:
c=3
arguments 对象
this 指向
3. Eval 执行上下文
var x = 10;
function test() {
var x = 20;
eval('console.log(x)');
eval('var y = 30;');
console.log(y);
}
test();
console.log(typeof y);
⚠️ 避免使用 eval() —— 安全风险、性能问题、作用域污染
调用栈
什么是调用栈?
调用栈(Call Stack)是一个 后进先出 (LIFO) 的数据结构,记录函数调用序列。
可视化栈操作
function a() {
console.log('a 开始');
b();
console.log('a 结束');
}
function b() {
console.log('b 开始');
c();
console.log('b 结束');
}
function c() {
console.log('c 开始');
console.log('c 结束');
}
a();
初始状态:
┌─────────────────┐
│ Global Stack │
└─────────────────┘
a() 被调用:
┌─────────────────┐
│ a() Context │
├─────────────────┤
│ Global Stack │
└─────────────────┘
b() 被调用:
┌─────────────────┐
│ b() Context │
├─────────────────┤
│ a() Context │
├─────────────────┤
│ Global Stack │
└─────────────────┘
c() 被调用:
┌─────────────────┐
│ c() Context │
├─────────────────┤
│ b() Context │
├─────────────────┤
│ a() Context │
├─────────────────┤
│ Global Stack │
└─────────────────┘
通过错误栈追踪
function test() {
throw new Error('堆栈跟踪');
}
function wrapper() {
test();
}
wrapper();
VO/AO 与作用域链
变量对象的生命周期
进入执行上下文阶段(函数调用但代码未执行)
function test(a, b) {
var c = 1;
var d = function() {};
function e() {}
}
test(10, 20);
AO = {
a: 10,
b: 20,
e: <function reference>,
// 3. 变量声明(仅声明,值为 undefined)
c: undefined,
d: undefined,
// 4. arguments 对象
arguments: { 0: 10, 1: 20, length: 2 }
}
执行阶段(代码逐行执行)
AO = {
a: 10,
b: 20,
c: 1,
d: <function reference>, // ← 赋值
e: <function reference>,
arguments: { 0: 10, 1: 20, length: 2 }
}
作用域链的构建
var global_var = '全局';
function outer() {
var outer_var = '外层';
function inner() {
var inner_var = '内层';
console.log(inner_var);
console.log(outer_var);
console.log(global_var);
}
inner();
}
outer();
innerScope = [innerAO, outerAO, globalVO]
↓ ↓ ↓
[变量] [变量] [全局变量]
- 在
innerAO 中查找 global_var —— ✗ 未找到
- 在
outerAO 中查找 —— ✗ 未找到
- 在
globalVO 中查找 —— ✓ 找到 '全局'
栈溢出与尾调用优化
栈溢出 (Stack Overflow)
每次函数调用都会占用栈内存。调用过深导致内存耗尽:
function infiniteRecursion() {
return infiniteRecursion();
}
infiniteRecursion();
let depth = 0;
function measureStackDepth() {
depth++;
try {
measureStackDepth();
} catch (e) {
console.log(`栈深度限制: ${depth}`);
}
}
measureStackDepth();
尾调用优化 (Tail Call Optimization, TCO)
尾调用(Tail Call): 函数的最后一条语句是另一个函数调用
✗ 非尾调用(无法优化)
function f(x) {
return g(x) + 1;
}
✓ 尾调用(可优化)
function f(x) {
return g(x);
}
ES6 严格模式下的尾调用优化
'use strict';
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
console.log(factorial(1000));
| 引擎 | TCO 支持 | 备注 |
|---|
| V8 (Chrome/Node.js) | ❌ 不支持 | 社区一直有争议 |
| SpiderMonkey (Firefox) | ✓ 支持 | 严格模式下默认启用 |
| JavaScriptCore (Safari) | ✓ 支持 | 性能最优 |
实战案例
案例 1:闭包与 AO 的延迟释放
function createCounter() {
let count = 0;
return function() {
return ++count;
};
}
const counter1 = createCounter();
const counter2 = createCounter();
console.log(counter1());
console.log(counter1());
console.log(counter2());
console.log(counter1());
- 每个
counter 都保持一个 createCounter 的 AO 副本
- 在 counter 被垃圾回收前,AO 不会释放
案例 2:函数表达式的变量提升
console.log(typeof fn1);
console.log(typeof fn2);
function fn1() {}
var fn2 = function() {};
AO = {
fn1: <function reference>,
fn2: undefined
}
案例 3:this 的动态绑定
const obj = {
value: 42,
getValueArrow: () => {
console.log(this.value);
},
getValueNormal: function() {
console.log(this.value);
},
nested: {
value: 100,
method: function() {
console.log(this.value);
}
}
};
obj.getValueArrow();
obj.getValueNormal();
obj.nested.method();
深度追问
Q1: 为什么函数表达式中的 name 属性只在函数内部有效?
const fn = function namedFunc() {
console.log(typeof namedFunc);
};
console.log(typeof namedFunc);
原因: namedFunc 仅在函数 AO 中,不在外层作用域
Q2: 为什么 arguments 对象在箭头函数中不存在?
const arrow = () => {
console.log(arguments);
};
function normal() {
console.log(arguments);
}
normal(1, 2);
Q3: 为什么块级作用域内的 let/const 不被提升?
console.log(x);
{
let x = 10;
}
原因: let/const 存在"暂时性死区(TDZ)",进入块作用域后立即初始化但不赋值
总结表格
| 特性 | 全局 EC | 函数 EC | Eval EC |
|---|
| 创建时机 | 脚本加载 | 函数调用 | eval() 执行 |
| VO/AO | 全局对象 | 函数 AO | 当前 EC 的 AO/VO |
| this | Window/Global | 取决于调用方式 | 继承外层 this |
| 作用域链 | [VO] | [AO, outer AO, ..., VO] | 当前 EC 链 |
| 销毁 | 程序终止 | 函数返回 | eval 结束 |
进阶阅读