执行上下文(Execution Context):创建阶段做了什么?为什么会有提升、作用域链、this?
面试速答(30 秒版 TL;DR)
- 执行上下文是 JS 引擎执行代码的“运行环境对象”,每次进入全局/函数/(很少用的)
eval都会创建一个上下文。 - 上下文创建阶段会:建立作用域链(词法环境)、创建变量/函数绑定(解释提升与 TDZ)、确定
this绑定。 - 运行时用**调用栈(Call Stack)**管理上下文入栈出栈:函数调用入栈,返回出栈。
- “执行上下文”解决的是运行时;“作用域(Scope)”是静态(写在哪决定能访问谁),两者要区分。
心智模型:两件事
- 代码写在哪里:决定作用域(Scope,词法作用域)。
- 代码怎么调用:决定this(除箭头函数外)。
执行上下文把这两类信息组合在一起,给引擎一个可执行的“帧”。
三类执行上下文
- 全局执行上下文(Global EC):脚本开始执行时创建一次。
- 函数执行上下文(Function EC):每次调用函数都会创建一个新的。
- Eval 执行上下文:一般不建议使用,面试可简单带过。
调用栈:上下文怎么管理
创建阶段:你要能讲清“提升为什么发生”
面试里常用的说法是“创建阶段”和“执行阶段”(不同资料命名略有差异,但核心一致)。
创建阶段(概念化)会做:
- 创建 Lexical Environment(词法环境):
- 环境记录(Environment Record):保存标识符到值的映射
- 外部引用(Outer):指向外层词法环境,形成作用域链
- 创建 Variable Environment:
- 历史上用来承载
var(很多实现细节会合并理解即可)
- 历史上用来承载
- 确定
this绑定(全局/函数调用方式/new等)
“变量提升/TDZ”就是创建阶段对绑定的不同处理方式在运行时的表现。
相关文档:
一个最小例子:把上下文和提升串起来
console.log(x); // undefined
var x = 1;
// 相当于(帮助理解,不是真实重写)
// var x;
// console.log(x);
// x = 1;
再看 let 的 TDZ:
console.log(y); // ReferenceError
let y = 1;
解释口述要点:
var:创建阶段就绑定并初始化为undefinedlet/const:创建阶段绑定但不初始化,访问落在 TDZ
典型题 & 标准答法
Q1:执行上下文和作用域有什么区别?
- 作用域是静态概念:代码写在哪里决定能访问哪些变量(词法作用域)。
- 执行上下文是运行时概念:函数每次调用都会产生一个上下文实例,保存当前调用的绑定与状态。
Q2:闭包和执行上下文的关系?
闭包不是“函数 + 变量”,而是:函数能够“带着”其创建时的外部词法环境引用。函数返回后,该词法环境仍可达,因此不会被回收。
(闭包详解见:closure)
易错点/坑
- 把“调用栈”和“事件循环”混在一起:调用栈是同步执行的上下文栈;事件循环涉及任务队列调度。
- 把“作用域链”和“原型链”混在一起:一个是变量查找路径,一个是属性查找路径。
速记要点(可背诵)
- 上下文:执行时的帧;作用域:写代码时就确定的可见性。
- 创建阶段:建词法环境与作用域链,建绑定(解释提升/TDZ),定 this。
- 运行阶段:按调用栈入栈出栈执行。