执行上下文是JS运行时的环境快照,由变量对象、作用域链和this值组成,按需创建、压栈管理、逐个销毁;分创建(含提升)与执行两阶段;函数调用触发入栈,return触发出栈。

执行上下文不是“上下文对象”,而是执行时的环境快照
JavaScript 中的 执行上下文(Execution Context) 并不是一个你可以直接访问或操作的 JS 对象,而是一个抽象概念,用来描述代码在某一时刻“正在怎么运行”。它由三部分组成:变量对象(VO)(含函数声明、var 变量、形参)、作用域链(Scope Chain) 和 this 值。全局代码、函数调用、eval 都会创建各自的执行上下文。
关键点在于:上下文是**按需创建、压栈管理、逐个销毁**的。你写的一段代码不会“同时存在多个活跃上下文”,只有当前正在执行的那个上下文是活跃的。
常见误解是以为 console.log(this) 输出的就是执行上下文本身——其实只是其中的一个属性值;也有人把 arguments 或闭包中捕获的自由变量当成上下文本体,这也不对。
执行顺序由调用栈 + 提升(Hoisting)共同决定
JS 的执行顺序不是简单地从上到下逐行跑,而是分两阶段:创建阶段(初始化上下文)和 执行阶段(真正运行语句)。创建阶段会完成变量提升和函数提升,但注意:
立即学习“Java免费学习笔记(深入)”;
-
var声明会被提升并初始化为undefined -
function声明会被提升且**完整赋值**(可立即调用) -
let/const也会被提升,但处于“暂时性死区(TDZ)”,访问会抛出ReferenceError - 箭头函数、
class声明不提升,且class本身也受 TDZ 约束
这意味着下面这段代码不会报错:
foo(); // OK
function foo() { return 'ok'; }
但换成这样就会报错:
bar(); // ReferenceError const bar = () => 'no';
函数调用触发新执行上下文入栈,return 触发出栈
每次函数被调用,引擎都会为其创建一个新的执行上下文,并把它推入**执行上下文栈(Execution Context Stack, ECS)**。栈顶永远是当前正在运行的上下文。函数返回时,该上下文被弹出,控制权交还给前一个上下文。
例如:
function a() {
console.log('in a');
b();
}
function b() {
console.log('in b');
}
a();
执行过程如下:
- 全局上下文入栈
- 调用
a()→a上下文入栈 - 在
a中调用b()→b上下文入栈 -
b执行完 →b上下文出栈 -
a执行完 →a上下文出栈 - 只剩全局上下文
注意:setTimeout、Promise.then 等异步回调不会立即创建新上下文,它们先进入任务队列,等当前栈清空后,才由事件循环取出并推入新上下文执行。
容易被忽略的细节:eval 和 with 会动态修改作用域链
eval 在非严格模式下执行字符串代码时,会创建一个**新执行上下文**,并将其作用域链插入到当前上下文的作用域链前端;with 语句则会把指定对象临时加到作用域链头部。这两者都会让变量查找变慢,且破坏静态分析,现代代码应避免使用。
例如:
function f() {
const x = 1;
eval('console.log(x)'); // OK,在非严格模式下能访问外层变量
}
但在严格模式下,eval 有自己的词法环境,无法读取外部 const 或 let 声明;with 则在严格模式下直接语法错误。
真正影响执行顺序的,往往不是代码写了什么,而是这些隐式创建/修改上下文的行为是否发生——尤其在调试异步嵌套、闭包内存泄漏或 this 绑定时,漏掉这点就很难定位问题根源。










