所属板块:2. 执行上下文与闭包(JS 的核心引擎)
记录日期:2026-03-xx
更新:遇到执行上下文或调用栈相关输出题时补充
1. JS 是单线程的同步执行语言
JS 主线程一次只能做一件事,所有代码都在主调用栈上按顺序执行。
这也是为什么异步(setTimeout、Promise)需要事件循环来配合——但那是后面板块的内容,这里先记下:JS 本身没有多线程,所有上下文都在同一个调用栈里调度。
2. 执行上下文(Execution Context):代码运行的“容器”
执行上下文就是 JS 引擎为一段代码创建的运行环境。它包含当前代码能访问的所有信息。
主要分类:
- 全局执行上下文(Global EC):程序启动时自动创建,只有一个,this 指向全局对象(浏览器 window / Node global)
- 函数执行上下文(Function EC):每次函数调用时创建一个新的
- eval 执行上下文(极少使用,可忽略)
3. 调用栈(Call Stack):上下文的调度机制
JS 用 栈结构(LIFO 后进先出) 管理所有执行上下文:
- 全局上下文永远在栈底
- 函数调用时,新上下文压入栈顶
- 函数执行完毕,出栈并销毁
栈溢出(Stack Overflow)本质:调用栈空间耗尽(无限递归或调用链过深)。
简单示例 + 栈变化:
function outer() {
inner();
}
function inner() {
console.log("inner");
}
outer();
栈过程:
- 全局 EC 入栈(栈底)
- outer() 调用 → outer EC 入栈
- inner() 调用 → inner EC 入栈
- inner 执行完 → inner 出栈
- outer 执行完 → outer 出栈
- 全局结束 → 全局出栈
4. 执行上下文的生命周期(创建阶段 vs 执行阶段)
每个上下文都严格经历两个阶段:
创建阶段(引擎在“编译”时就做的准备工作)
-
创建变量对象(Variable Object / VO)
- ES3 视角:也叫活动对象(Activation Object / AO)
- 收集 var 声明 → 初始化 undefined
- 收集 function 声明 → 完整函数体
- 收集形参
-
创建作用域链(Scope Chain)
- 作用域链本质是一系列外部环境引用的链表(每个上下文都持有指向外层上下文的 [[outer]] 指针)。
- 它的作用是在后续查找变量时,提供“从内向外顺藤摸瓜”的路径(词法作用域的物理载体)。
- (详细规则将在【2-2】展开)
-
确定 this 指向
- this 是执行上下文中的一个特殊属性,它的绑定规则在函数被调用时才最终确定(默认绑定、隐式绑定、显式绑定、new 绑定、箭头函数词法 this)。
- 一旦确定,在整个执行阶段都不会再改变。
- (详细五大规则将在【2-3】展开)
ES6 后更准确的说法是:
- 词法环境(Lexical Environment):负责 let/const、块级作用域、闭包
- 变量环境(Variable Environment):负责 var、函数声明(兼容老规范)
执行阶段
逐行运行代码,进行赋值、函数调用等操作。
5. 变量提升(Hoisting)——创建阶段的核心表现
提升的是声明,不是赋值:
- var:提升到当前上下文顶部,值为 undefined
- function 声明:完整提升(函数名 + 函数体),优先级高于 var
- let / const:有提升,但进入暂时性死区(TDZ),提前访问报 ReferenceError
示例对比:
console.log(a); // undefined
var a = 100;
console.log(b); // ReferenceError(TDZ)
let b = 200;
foo(); // "函数提升"
function foo() {
console.log("函数提升");
}
同名优先级:函数声明 > var 声明(函数先占位,后被 var 赋值覆盖)。
6. 暂时性死区(TDZ)的设计哲学
let / const 被故意锁在 TDZ 中,是为了避免 var 那种“声明前可用却 undefined”的隐蔽 bug,同时实现块级作用域。
经典 TDZ 题:
let x = 1;
function test() {
console.log(x); // ReferenceError(TDZ,函数内 let x 遮蔽了外部 x)
let x = 2;
}
test();
7. 小结 & 复习时的“引擎视角”
遇到任何“变量 undefined / ReferenceError / 函数提前可用”的题,第一反应都是:
- 当前在哪个执行上下文?
- 调用栈当前栈顶是谁?
- 变量是在创建阶段的 VO / Lexical Environment 里如何初始化的?
- 有没有触发 TDZ 或提升优先级问题?
掌握了执行上下文和调用栈,就等于拿到了 JS 引擎的“运行日志”,后面作用域链、this、闭包都会变得顺理成章。
下一篇文章会进入:作用域与作用域链(词法作用域、LHS/RHS 查询等)。
返回总目录:戳这里