JavaScript是单线程语言,因DOM操作需避免多线程冲突而设计为单线程;通过事件循环协调调用栈、宏任务队列与微任务队列,实现异步非阻塞I/O。

JavaScript 是单线程语言,这意味着它同一时间只能执行一个任务;但通过事件循环(Event Loop)机制,它能高效处理异步操作,实现非阻塞式 I/O,比如定时器、网络请求、用户交互等。
为什么 JavaScript 是单线程的?
浏览器中 JavaScript 主要负责操作 DOM。如果允许多线程,多个线程同时修改同一个 DOM 节点,就会引发渲染冲突和状态不一致问题。为避免复杂同步机制,JS 从设计上就采用单线程模型——所有代码都在一个主线程上按顺序执行。
注意:Web Worker 是例外,它在独立线程运行 JS,但不能访问 DOM,与主线程通过 message 通信,不属于常规 JS 执行上下文。
调用栈、任务队列与事件循环三者关系
事件循环是协调“同步代码执行”和“异步回调触发”的核心机制,依赖三个关键部分:
立即学习“Java免费学习笔记(深入)”;
- 调用栈(Call Stack):记录当前正在执行的函数,遵循 LIFO(后进先出)。同步代码立即入栈、执行、出栈。
-
任务队列(Task Queue,也叫宏任务队列):存放由异步 API(如
setTimeout、setInterval、I/O 回调、UI 渲染)推入的回调函数,等待执行。 -
微任务队列(Microtask Queue):存放
Promise.then/catch/finally、MutationObserver等产生的微任务,优先级高于宏任务。
事件循环每轮会先清空当前调用栈,然后执行所有微任务(直到微任务队列为空),再从宏任务队列取一个任务执行——这就是“一次循环”的基本节奏。
宏任务与微任务的执行顺序示例
看这段代码,能帮你直观理解执行优先级:
console.log(1); setTimeout(() => console.log(2), 0); // 宏任务 Promise.resolve().then(() => console.log(3)); // 微任务 console.log(4);
输出顺序是:1 → 4 → 3 → 2。原因如下:
- 1 和 4 是同步代码,立刻执行;
- setTimeout 回调被放入宏任务队列,等待下一轮循环;
- Promise.then 回调被放入微任务队列,本轮同步代码执行完后立即执行;
- 微任务清空后,事件循环才取下一个宏任务(即 setTimeout 回调)。
实际开发中需要注意的点
事件循环不是黑盒,理解它有助于写出更可靠的异步逻辑:
- 不要依赖
setTimeout(fn, 0)实现“立刻执行”,它只是尽快安排到下一轮宏任务,实际延迟受系统负载影响; - 大量连续的
Promise.then会堆积微任务,可能造成界面响应延迟(虽不阻塞主线程,但会延后渲染); - 想让某段逻辑“让出主线程”以便浏览器渲染或响应用户输入,可用
queueMicrotask或Promise.resolve().then(),比setTimeout更及时; - DOM 变更后想立即获取布局信息(如 offsetHeight),需注意:浏览器通常在微任务之后、宏任务之前进行重排重绘,必要时可用
requestAnimationFrame对齐渲染时机。








