JavaScript单线程靠事件循环实现非阻塞异步:I/O由宿主环境处理,主线程仅调度;事件循环分阶段(Timers、Poll等),微任务在每阶段末优先清空。

JavaScript 的单线程模型靠事件循环(Event Loop)协调异步 I/O,实现非阻塞——核心不是“多线程并发”,而是“把耗时操作交给宿主环境(如浏览器或 Node.js)处理,主线程只负责调度和回调执行”。
主线程只做计算和调度,不直接做 I/O
JS 引擎本身不处理网络请求、文件读写、定时器等 I/O 操作。这些任务被委托给运行环境(Web API 或 libuv)去执行。主线程发出请求后立即返回,继续执行后续同步代码,不会等待结果。
- 比如调用 fetch(),JS 引擎只是向浏览器发起一个网络请求指令,立刻继续跑下面的代码
- 浏览器在后台用独立线程或系统调用完成 HTTP 请求,完成后把回调(如 then 回调)放入任务队列
- 主线程空闲时,事件循环从队列中取出回调并推入调用栈执行
事件循环分阶段调度不同类型的回调
事件循环不是简单地“轮询一个队列”,而是一套有优先级的阶段式流程(尤其在 Node.js 中明显,浏览器略有简化但逻辑一致):
- Timers 阶段:执行已到期的 setTimeout / setInterval 回调
- Pending callbacks:处理系统级错误(如 TCP 错误)的回调
- Idle/Prepare:内部使用,可忽略
- Poll 阶段:最核心——获取 I/O 回调(如 fs.readFile 完成、网络响应到达),若队列为空且有 setImmediate 则跳过;若有回调则逐个执行
- Check 阶段:执行 setImmediate 回调
- Close callbacks:执行 close 事件(如 socket.close)
微任务(Promise.then、MutationObserver)则在每个阶段结束后、进入下一阶段前**立即清空**,优先级高于宏任务。
立即学习“Java免费学习笔记(深入)”;
非阻塞的关键:I/O 由系统或宿主接管
所谓“非阻塞”,本质是 JS 主线程不参与实际 I/O 等待:
- 浏览器中:XMLHttpRequest、fetch、addEventListener 的底层由 C++ 实现,利用操作系统异步 I/O(如 epoll/kqueue/IOCP)或线程池
- Node.js 中:libuv 封装了不同平台的异步 I/O 接口,对文件操作常用线程池模拟异步(因多数文件系统不原生支持异步),网络操作则直接用系统异步能力
- JS 代码里看不到“等待”,只有“注册回调”——等宿主通知“好了”,再回来执行你写的逻辑
一个典型生命周期示例
以 setTimeout(() => console.log('A'), 0); Promise.resolve().then(() => console.log('B')); 为例:
- 同步代码执行完,调用栈清空
- 事件循环进入 Poll 阶段 → 发现无 I/O 回调,且 timer 已到期 → 进入 Timers 阶段 → 执行 setTimeout 回调?不急,先检查微任务队列
- 发现有一个 Promise 回调 → 立即执行,输出 'B'
- 微任务队列清空 → 回到 Timers 阶段 → 执行 setTimeout 回调,输出 'A'
这个顺序说明:宏任务排队进阶段,微任务插队在每阶段末尾执行——这是控制异步粒度的重要机制。


















