调用栈管理同步执行,异步回调不进栈只入队。调用栈按lifo执行同步代码,死循环会阻塞后续所有任务;异步回调由宿主环境处理后分别进入宏/微任务队列,必须等调用栈为空才执行,如“settimeout(0)”实际延迟取决于同步代码执行时长。

调用栈管同步执行,异步回调不进栈、只入队——这是理解 JavaScript 执行模型最核心的分界线。
调用栈:同步任务的“执行流水线”
调用栈是 JS 引擎执行同步代码时的实时记录器,遵循后进先出(LIFO)原则:
- 每调用一个函数,就生成一个栈帧压入栈顶,包含参数、局部变量和执行位置
- 函数返回时,栈帧立即弹出;栈为空,才代表当前同步任务彻底结束
- 死循环、长耗时计算会一直占满调用栈,后续任何代码(包括已到期的定时器)都无法插入执行
异步回调:不进栈,只排队等待“空档期”
异步操作(如 setTimeout、Promise.then、fetch 响应)本身不进入调用栈,而是由浏览器或 Node.js 的宿主环境在后台处理。完成后,其回调函数被分类放入对应的任务队列:
- 宏任务队列:setTimeout、setInterval、I/O、UI事件等,每次事件循环只取一个执行
- 微任务队列:Promise.then/catch/finally、MutationObserver、await 后续代码,会在当前宏任务结束后、渲染前全部清空
- 回调入队时机由宿主环境决定(例如 setTimeout 到时即入队),但执行时机完全取决于调用栈是否为空
关键对比点:谁控制“何时开始”,谁决定“何时真正运行”
两者分工明确,不可混淆:
- 调用栈决定同步代码的执行顺序和阻塞关系;它不管理异步回调,也不感知任务队列
- 任务队列(宏/微)决定回调函数的存放位置与优先级;但它无法绕过调用栈——必须等栈清空,事件循环才会从中取任务推入栈
- 所谓“setTimeout(0)”不是立刻执行,而是“尽快入队+等待栈空”,实际延迟取决于同步代码执行时长
一个典型执行链看清协作关系
以这段代码为例:
console.log('1');<br>setTimeout(() => console.log('2'), 0);<br>Promise.resolve().then(() => console.log('3'));<br>console.log('4');
执行过程是:
- 同步部分依次入栈执行 → 输出 1、4
- setTimeout 回调入宏任务队列;Promise.then 回调入微任务队列
- 同步结束,调用栈清空 → 事件循环启动:先清空微任务队列 → 输出 3
- 再取下一个宏任务 → 输出 2











