Event Loop 梳理
为什么会有事件循环?
JavaScript 是一门单线程语言,因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个。如果使用同步方式,极有可能导致主线程阻塞,使得页面卡死。所以浏览器采用异步方式来避免,当某些任务发生时,主线程将任务交给其他县城去处理,自身立即结束任务的执行,转而执行后续代码。当其他县城完成时,将回调函数包装成任务加入消息队列的末尾。主线程空闲时,会从消息队列中取出任务执行。而事件循环可以保证浏览器用不阻塞,最大限度的保证了单线程的流畅运行。
Event Loop 执行顺序
在最开始的时候,渲染主线程会进入一个无限循环 (参考 Chromium 源码 第 35 行)
每一次循环检查消息队列 message queue 是否有任务存在。如果有,取出第一个任务执行,完成后进入下一次循环。如果没有,进入休眠。
其他所有线程可以随时往消息队列尾添加任务,这时候渲染主线程会被唤醒,开始下一次循环。 注意事项: - 宏任务概念已经被 W3C 取消- Timers are not exact. If the nesting level is greater than 5, the interval is rounded up to the nearest 4ms- Promise callbacks can be delayed- Slow tasks can be completely blocking - 同类型的任务必须在同一个队列,不同的任务可以属于不同的队列
任务的优先级
微队列:Promise.then/catch/finally、async await、process.nextTick、MutationObserver, queueMicrotask (优先级最高) 交互队列:click、DOMContentLoaded、load、hashchange、scroll(优先级高) 延时队列:setTimeout、setInterval、setImmediate (优先级中) 队列里放的是回调函数的任务,而不是回调函数本身。回调函数本身是在其他线程执行的,执行完毕后,将回调函数包装成任务加入队列。
解题步骤
画出主线程队列,并且执行全局同步代码(跳过变量函数申明)
画出 微队列,交互队列,延时队列
不要忘了其他线程,其他线程也可以向队列中添加任务
渲染主线程: 微队列: 延时队列: 交互队列: 其他线程:
练习题实战
console.log(1);
function a() {
console.log(2);
b();
}
function b() {
setTimeout(() => {
console.log(3);
new Promise((resolve) => {
console.log(9);
resolve();
}).then(() => {
console.log(10);
});
}, 0);
console.log(4);
}
a();
new Promise((resolve) => {
console.log(5);
resolve();
}).then(() => {
console.log(6);
setTimeout(() => {
console.log(7);
}, 0);
});
console.log(8);
答案:1 2 4 5 8 6 3 9 10 7
console.log(1);
setTimeout(() => console.log(2), 1000);
setTimeout(() => console.log(3), 0);
console.log(4);
答案:1 4 3 2
function logOne() {
console.log(1);
}
function main() {
setTimeout(logOne, 0);
Promise.resolve(2)
.then((val) => val * 2)
.then(console.log);
console.log(3);
}
main();
答案:3 4 1
更多练习题:https://drive.google.com/file/d/1-LHMuN8zvHvxicPvvc5LCv2_Y8zpatxs/view?usp=sharing