JavaScript 的循环机制核心是 事件循环(Event Loop),它协调同步任务和异步任务的执行。以下是详细解释:
核心概念
- 单线程模型
JavaScript 是单线程语言,所有任务按顺序执行,但通过 事件循环 实现异步非阻塞。 - 事件循环组成
-调用栈(Call Stack) :存放同步任务的执行上下文(函数调用),后进先出(LIFO)。
-任务队列(Task Queue) :存放待处理的异步回调(分为宏任务和微任务)。
-事件循环(Event Loop):持续检查调用栈是否为空,若为空则从任务队列中取出任务执行。
异步任务分类
- 宏任务(MacroTask)
-来源:setTimeout、setInterval、I/O 操作、UI 渲染、requestAnimationFrame、事件回调。
-队列:独立的宏任务队列。 - 微任务(MicroTask)
-来源:Promise.then/catch/finally、MutationObserver、queueMicrotask、process.nextTick(Node.js)。
-队列:独立的微任务队列,优先级高于宏任务。
事件循环流程
- 执行同步代码
所有同步任务进入调用栈并执行。 - 处理微任务队列
调用栈清空后,按顺序执行所有微任务(包括微任务中产生的微任务)。 - 处理宏任务队列
每次从宏任务队列中取出一个任务执行(浏览器可能执行渲染更新)。 - 循环重复
重复步骤 1~3,形成事件循环。
执行顺序示例
javascript
console.log("Start"); // 同步
setTimeout(() => console.log("Timeout"), 0); // 宏任务
Promise.resolve()
.then(() => console.log("Promise 1")) // 微任务
.then(() => console.log("Promise 2")); // 微任务
console.log("End"); // 同步
输出顺序:
bash
Start
End
Promise 1
Promise 2
Timeout
步骤解析:
- 同步任务:Start → End。
- 微任务队列:执行所有微任务(Promise 1 → Promise 2)。
- 宏任务队列:执行 Timeout。
常见问题场景
javascript
// 问题:为什么 Timeout 最后输出?
setTimeout(() => console.log("Timeout"), 0);
Promise.resolve().then(() => console.log("Promise"));
// 答案:微任务在宏任务之前执行
总结
- 同步任务:立即执行,阻塞调用栈。
- 微任务:调用栈清空后立即执行(优先级最高)。
- 宏任务:在微任务队列清空后执行(每次取一个)。
掌握事件循环机制能有效解决异步代码顺序问题,避免常见陷阱(如 setTimeout 延迟不精确)。