事件循环
JS 的代码分为同步任务和异步任务:
同步任务:在主线程上立即执行的任务,按照代码的顺序依次执行;
异步任务:不进入主线程,进入任务队列。只有任务队列通知主线程执行某个异步任务,该异步任务才会进入主线程;
异步任务又分为宏任务和微任务,任务队列又分为宏任务队列和微任务队列:
宏任务:主代码块(script)、setTimeout、setInterval、setImmediate、I/O、UI rendering,requestAnimationFrame 等;
微任务:Promise.then(async)、MutationObserver、process.nextTick、Object.observe 等;
注意:为方便总结,我将同步任务(主代码块 script)归结到宏任务中,这样就可以以宏任务开启事件循环。
- 执行一个宏任务(初始是 script,而后是从任务队列中获取);
script 的执行本身是一个宏任务,是事件循环的第一个任务。
- 执行过程中如果遇到微任务,就将它添加到微任务队列中,遇到宏任务就将其放入宏任务队列中;
- 当前宏任务执行完毕后,立即依次执行当前微任务队列中所有的微任务;
- 在执行微任务时,如果遇到了宏任务,会将其放入宏任务队列中,并继续执行微任务队列中的微任务;
- 执行完微任务后,执行 UI 渲染,再执行下一个宏任务,重复以上过程。
此为 js 的事件循环机制。以宏任务开始一个事件循环,当所有的微任务执行完毕后(包括执行时微任务时又加入的微任务),再去执行下一个宏任务。
初始示例:
- 主线程:(script:第一个宏任务,同步执行)
- 宏任务:script(给主线程)
- 微任务:null
console.log("script-open");
console.log("1");
setTimeout(() => {
console.log("9-macro");
Promise.resolve("10").then((res) => {
console.log(res);
});
}, 0);
new Promise((resolve, reject) => {
console.log("2");
resolve("4");
})
.then((res) => {
console.log(res);
setTimeout(() => {
console.log("11-macro");
}, 0);
return Promise.resolve("7");
})
.then((res) => {
console.log(res);
return Promise.resolve("8");
})
.then((res) => {
console.log(res);
});
new Promise((resolve, reject) => {
console.log("3");
resolve("5");
}).then((res) => {
console.log(res);
});
const el = document.querySelector("#app");
const observer = new MutationObserver((mutationList, observer) => {
console.log("6-observer", mutationList);
});
observer.observe(el, { subtree: true, childList: true });
// 1 2 3 4 5 6-observer 7 8 9-macro 10 11-macro
Vue 与事件循环
Vue 中的一些语法和钩子函数与事件循环的关系。
- 创建阶段(setup、create):同步任务(script);
- onMounted:第一个微任务;
- nextTick:下一轮事件循环(下一个 tick)的微任务队首;
例如:如果和 onMounted 在同一层,则在 onMounted 后的一个事件循环的队首。
- watchEffect:同步任务(script);