JavaScript是单线程语言,通过事件循环协调同步代码、微任务(如Promise.then)和宏任务(如setTimeout),实现异步非阻塞执行;所有耗时操作由宿主环境后台处理,JS主线程仅负责调度与回调执行。

JavaScript 是单线程语言,意味着它在同一时间只能执行一个任务;但它通过事件循环(Event Loop)配合回调队列与微任务队列,实现了异步非阻塞的执行效果——代码不会因等待 I/O 或定时器而卡住主线程。
单线程并不等于“不能并发”
单线程指 JavaScript 引擎只有一个调用栈(Call Stack),所有同步代码按顺序压栈执行。但浏览器或 Node.js 运行时提供了 Web API(如 setTimeout、fetch、addEventListener)这些能力不在 JS 引擎内执行,而是由宿主环境在后台线程处理。它们完成后再把回调“推入”任务队列,等 JS 主线程空闲时由事件循环调度执行。
例如:
- setTimeout(fn, 0) 不是立刻执行,而是把 fn 推入宏任务队列,等当前调用栈清空、微任务清空后才执行;
- Promise.then() 的回调属于微任务,优先级高于 setTimeout,会在本轮同步代码结束后、下一轮宏任务开始前执行。
事件循环如何协调同步、微任务与宏任务
事件循环是一个持续运行的机制,每轮循环包含以下关键步骤:
立即学习“Java免费学习笔记(深入)”;
- 执行所有同步代码(进入调用栈,逐行执行);
- 同步代码执行完毕后,清空当前轮次的微任务队列(如 Promise.then/catch/finally、queueMicrotask 回调);
- 然后从宏任务队列中取出一个任务(如 setTimeout、setInterval、I/O 回调、UI 渲染)执行;
- 重复上述流程。
注意:每次只取一个宏任务,但会一次性执行完所有待处理的微任务——这是理解 Promise 和 setTimeout 执行顺序的关键。
为什么异步操作不阻塞主线程
因为耗时操作(如网络请求、文件读取、定时等待)交由浏览器或 Node.js 的底层线程池/C++ 模块处理,JS 主线程只负责发起请求并注册回调。期间它继续执行后续同步代码,完全不受影响。等外部操作完成,回调被加入对应队列,再由事件循环择机调用。
比如:
- fetch('/api') 立即返回一个 Promise,JS 不等响应就继续往下走;
- 响应到达后,浏览器将 resolve 回调放入微任务队列,等当前同步任务结束就执行 .then();
- 整个过程没有“暂停”或“等待”,主线程始终可用。
常见误区澄清
有些说法容易误导初学者:
- “Promise 是异步的” —— 不准确。new Promise(executor) 中的 executor 函数是同步执行的,只是其内部的 resolve/reject 调用会触发异步回调;
- “setTimeout(0) 是最快异步” —— 实际上它至少延迟 4ms(HTML5 规范限制),且仍属宏任务,一定晚于微任务;
- “JS 有多个线程” —— 主线程唯一,Web Worker 是独立上下文,无法直接访问 DOM,不属于 JS 引擎线程模型的一部分。










