函数执行上下文(fec)每次调用函数时都会新建一个独立环境,经历创建阶段(初始化变量对象、构建作用域链、确定this值)和执行阶段;执行栈按后进先出管理,函数调用时压入,返回时弹出。

函数执行上下文是怎么被创建的
每次调用函数时,JavaScript 引擎都会新建一个函数执行上下文(FEC),它不是复用、也不是修改旧的,而是完全独立的一份环境。这个过程发生在函数真正开始执行前的“创建阶段”,包含三步关键操作:
- 初始化变量对象(AO):收集形参、arguments 对象、函数声明(优先提升)、var 声明(值为 undefined);注意函数表达式和 var 同名时,函数声明会先被放入 AO,之后 var 声明不会覆盖它
- 构建作用域链:把当前函数的[[Scope]](定义时就确定的作用域链)与新创建的 AO 组合,AO 被推到链最前端,形成「AO → 外层AO → … → 全局GO」结构
- 确定 this 值:依据调用方式决定——普通调用为 undefined(严格模式)或全局对象(非严格),方法调用指向调用者对象,箭头函数则继承外层 this
执行栈如何反映函数调用顺序
JavaScript 使用后进先出(LIFO)的执行上下文栈(ECS)管理运行中的上下文。你可以把它想象成一摞纸杯:最上面那个是当前正在执行的函数,下面压着它的调用者,最底下永远是全局上下文(GEC)。
例如这段代码:
function a() { b(); }
function b() { c(); }
function c() { console.log('done'); }
a();
执行过程对应栈变化如下:
- 启动:[GEC]
- a() 调用:[GEC, FEC-a]
- b() 调用:[GEC, FEC-a, FEC-b]
- c() 调用:[GEC, FEC-a, FEC-b, FEC-c]
- c 执行完:弹出 FEC-c → [GEC, FEC-a, FEC-b]
- b 执行完:弹出 FEC-b → [GEC, FEC-a]
- a 执行完:弹出 FEC-a → [GEC]
创建阶段 vs 执行阶段:为什么 console.log(a) 输出 undefined
每个函数执行上下文分两个明确阶段,中间不可跳过:
- 创建阶段(不执行代码):仅做预处理——分配内存、设默认值(var 变量 = undefined,函数体完整挂载)、绑定 this、连好作用域链。此时所有 var 声明都已存在,但尚未赋值
-
执行阶段(逐行跑代码):才真正运行语句。遇到
var a = 10,才把 10 写入 AO 中已存在的a属性;遇到函数调用,就立刻为被调函数新建 FEC 并压栈
所以即使 console.log(a) 写在 var a = 10 前面,它仍能访问到 a(因已声明),但值是 undefined——这是创建阶段留下的初始状态,不是报错。
可视化小技巧:用 debugger + 控制台观察 FEC
在浏览器中快速验证上下文行为,不需要画图工具:
- 在函数第一行加
debugger;,刷新页面触发断点 - 打开开发者工具 → Sources 面板 → 右侧 Scope 区域,就能看到当前 FEC 的 AO(标为 “Closure” 或 “Function”)、外层作用域(“Script” 或 “Global”)、this 值等真实数据
- 点击调用栈(Call Stack)里的上层函数,可切换查看不同层级的 FEC,直观感受作用域链嵌套
这种原生调试方式比静态图示更直接,也避免了对“变量提升”等概念的抽象猜测。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











