js 事件循环
- 从宏任务队列中移除并运行最早的任务
- 执行所有微任务
- 渲染任何更改
- 去第1步
微任务
- Promise .then / .catch / .finally
- queueMicrotask
- MutationObserver
- process.nextTick(仅 Node.js)
微任务之间没有 UI 或网络事件处理,它们会立即一个接一个地运行 微任务可以不断添加,导致宏任务不断推迟
宏任务
- setTimeout / setInterval / setImmediate(仅 Node.js)
- requestAnimationFrame(仅浏览器)
- 脚本加载(如 <script> 标签)
- 鼠标事件(如 mousemove, click)
- I/O 操作(如 fs.readFile)
- UI 渲染
定时器为什么是宏任务?
计时是实时的,不能被阻塞,所以定时器在另一个进程中管理。时间管理中心(定时器进程)和执行的任务(js运行时)在不同的进程中,有进程切换是宏任务。
零延迟的setTimeout(func)
- 将一个大的计算密集型任务拆分成多个部分
- 在当前事件处理完所有事情 (包括冒泡) 后才运行
func
示例
console.log(1);
// The first line executes immediately, it outputs `1`.
// Macrotask and microtask queues are empty, as of now.
setTimeout(() => console.log(2));
// `setTimeout` appends the callback to the macrotask queue
// - macrotask queue content:
// `console.log(2)`
Promise.resolve().then(() => console.log(3));
// The callback is appended to the microtask queue
// - microtask queue content:
// `console.log(3)`
Promise.resolve().then(() => setTimeout(() => console.log(4)));
// The callback with `setTimeout(...4..)` is appended to microtasks
// - microtask queue content:
// `console.log(3); setTimeout(...4..)`
Promise.resolve().then(() => console.log(5));
// The callback is appended to the microtask queue
// - microtask queue content:
// `console.log(3); setTimeout(...4..); console.log(5)`
setTimeout(() => console.log(6));
// `setTimeout` appends the callback to macrotasks
// - macrotask queue content:
// `console.log(2); console.log(6)`
console.log(7);
// Outputs 7 immediately.输出:1 7 3 5 2 6 4
示例
console.log(1);
// 同步代码先执行,输出"1"。
await new Promise(res => setTimeout(res, 0));
// setTimeout(res,0)被放入宏任务队列,当它执行时调用resolve(),使得Promise被解决。
// await当前函数暂停执行,直到Promise被解决。
console.log(2);
// await后的语句被放入微任务队列。
// 微任务队列中的任务,会在当前执行栈的同步代码执行完之后,在任何宏任务之前执行。加上await new Promise(res => setTimeout(res, 0));,让await后的代码从当前执行栈移到:当前宏任务队列加宏任务setTimeout(res, 0)都执行完成后的微任务队列