JavaScript 是单线程的,这使得它无法同时执行多个任务。如果不使用异步机制,IO 操作(网络请求、定时器)会导致页面卡死。
Event Loop 是 JavaScript 运行时的并发模型。它负责协调调用栈 (Call Stack)(同步任务)与任务队列 (Task Queue)(异步回调)之间的执行顺序。
这是面试中最关键的点。两者的执行时机完全不同。
script (首次运行)。setTimeout。setInterval。setImmediate (Node.js/IE)。I/O 操作。UI Rendering (浏览器渲染)。Promise.then/catch/finally。MutationObserver (DOM 变动监听)。queueMicrotask。process.nextTick (Node.js 特有,优先级最高)。简化口诀:一宏 -> 清微 -> 渲染 -> 下一宏。
解析步骤:
宏任务 (Script):
1。setTimeout,回调放入 Macrotask Queue。new Promise,构造函数同步执行 -> 打印 4。resolve(),.then 回调放入 Microtask Queue。6。微任务 (Microtask Queue):
Promise.then 回调 -> 打印 5。渲染 (略)。
宏任务 (Macrotask Queue):
setTimeout 回调 -> 打印 2。Promise.then,放入 Microtask Queue。微任务 (Microtask Queue):
3。最终输出:1 -> 4 -> 6 -> 5 -> 2 -> 3。
在 Node 11 之前,Node 会一次执行完所有同类型的宏任务(比如所有的 timer),再清空微任务。 但在 Node 11 之后,Node 的行为已经修改为与浏览器一致:每执行一个宏任务,就清空一次微任务。
setTimeout 准时吗?不准时。它只能保证延时最小时间。如果主线程被阻塞(例如计算密集型任务),或者宏任务队列前面有很多任务,setTimeout 的执行时间会被推迟。
requestAnimationFrame 是宏任务吗?它既不是宏任务也不是微任务。它是在UI 渲染之前执行的回调,用于执行动画。它的执行频率跟随屏幕刷新率(通常 60Hz)。
因为 Promise 是微任务,在当前宏任务结束后立即执行。而 setTimeout 需要等待至少下一次 Event Loop。