外观
Event Loop 的执行顺序是什么?
当前调用栈清空之后,先把微任务队列全部跑完,再取一个宏任务执行,然后再次清空微任务。浏览器绘制发生在这几轮之间,不是每执行一个任务就画一帧。
原理
在浏览器里可以先按这两类记:
- 宏任务:整段脚本、
setTimeout、setInterval、I/O、用户事件、postMessage - 微任务:
Promise.then/catch/finally、queueMicrotask、MutationObserver
js
console.log("1")
setTimeout(() => console.log("2"), 0)
Promise.resolve().then(() => console.log("3"))
console.log("4")
// 1 4 3 2同步代码先打印 1 和 4。then 是微任务,排在 setTimeout 前面,所以 3 在 2 前面。
async/await 建立在 Promise 上。await 之前的代码同步执行,await 之后的代码相当于放进 then:
js
async function run() {
console.log("a")
await null
console.log("b")
}
run()
console.log("c")
// a c bsetTimeout(fn, 0) 也要等当前栈和微任务结束。嵌套层数变多时,浏览器还会把定时器延迟夹到至少 4ms,所以它不是「立刻执行」。
requestAnimationFrame 在下一次绘制前调用,既不是宏任务也不是微任务。用它做动画,不要用 setTimeout 去卡 16ms。
Node.js 的轮询阶段和浏览器不一样。没限定环境时先讲浏览器。如果问到 Node,再补:process.nextTick 比 Promise 微任务更早,setImmediate 在 check 阶段。
追问
微任务一直排队会怎样?
下一轮宏任务和渲染都会被推迟,页面点了没反应。不要在微任务里无限制地 queueMicrotask 或链式 then。
先 setTimeout 再 Promise,和反过来,结果一样吗?
谁先注册不改变类别。只要都是在同一次同步代码里注册的,微任务仍然全部先于下一个宏任务。
易错点
async函数在第一个await之前都是同步的。then的回调至少要晚一个微任务,即使 Promise 已经完成。- 把
requestAnimationFrame说成宏任务或微任务都不准确。