438 字
2 分钟
07 · 执行上下文与调用栈

“单线程”的真正含义#

JS 在浏览器里是单线程的——同一时刻只做一件事。代码执行靠**调用栈(Call Stack)**管理:函数调用就压栈,执行完弹出。


执行上下文#

每段代码运行前,JS 引擎创建执行上下文,包含:变量环境、作用域链、this。分为:

  • 全局上下文:最外层,程序启动时创建,栈底。
  • 函数上下文:每次函数调用创建一个,压入栈顶。
  • eval 上下文:少用,忽略。

调用栈长这样#

function a() { b(); }
function b() { c(); }
function c() { console.log("end"); }
a();

执行时栈:a → a,b → a,b,c → 打印 → 依次弹出 c,b,a。调试时 DevTools 的 Call Stack 面板就是它。


栈溢出#

递归没有终止条件,栈被压满:

function recurse() { recurse(); }
recurse(); // RangeError: Maximum call stack size exceeded

注意:这是同步栈溢出。异步递归(在 setTimeout 里调自己)不会爆栈,因为每次都出栈后再入栈。


和事件循环的关系#

调用栈空了,事件循环才会把任务队列里的回调推入栈执行。这是下一篇异步的核心铺垫——同步代码永远先跑完,异步回调排队等。


小结#

  • JS 单线程,靠调用栈管理函数执行。
  • 每次调用创建执行上下文并压栈,结束弹出。
  • 递归无终止 → 栈溢出。
  • 栈空后事件循环才调度异步回调(下篇展开)。

练习#

  1. 在 DevTools 里给 c() 打断点,观察 Call Stack 从 a→b→c 的变化。
  2. 写一个会栈溢出的递归,再改成”尾递归+setTimeout”避免溢出,对比两者。
  3. 解释:为什么 setTimeout(fn, 0) 里的 fn 一定在同步代码之后才执行?
07 · 执行上下文与调用栈
https://haolo.cloud/blog/posts/js-roadmap-07-execution-context/
作者
Ethan
发布于
2026-09-29
许可协议
CC BY-NC-SA 4.0