关于浏览器 eventloop

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ 这个网站可以帮助理解,下面是我的笔记

chrome浏览器每一个tab标签都会开启一个渲染进程,渲染进程会开启一个渲染主线程,这个主线程包含了执行js,css,html解析,dom渲染等。

注意:新的w3c规范已经撤销了宏任务的说法,代替它的是多种任务队列,但是为了解释方便,下面依然使用宏任务,代替这些乱七八糟的任务队列。

渲染主线程会开启一个无限循环执行机制,每次循环执行完都会去询问微任务和其他任务(宏任务)

微任务(MutationObserver,Promise)的执行优先级是最高的。

其次是用户交互任务,宏任务的一种(点击等事件)。

最后是定时器任务,宏任务的一种(setTimeout,setInterval)。

渲染主线程每次循环执行完,都会按照优先级拿任务:

先看微任务队列,如果微任务有东西,会把微任务的所有任务,一次性执行完

渲染主线程从微队列中拿任务是一个一个拿,还是一次性都拿出来,目前还没找到可靠的说法,但是无论是哪一种机制,执行结果都是相同的,所以这点不用过分纠结,等我找到答案会补充,下面的解释都将使用“一个一个拿”这种机制。

执行过程中如果产生了新的微任务,将会把新的微任务放到微任务队列末尾,并在当前这一轮微任务处理过程中被执行,而不会等到下一个宏任务。这就是为什么在处理Promise链时,所有的.then回调会在一次事件循环中连续执行,而不会被其他宏任务(如setTimeout)打断。

微任务队列全部执行完页面会重新渲染一次,然后执行宏任务,在 vue 和 react 中,我们经常为了确保一段代码在dom渲染后执行而把代码写在setTimeout中,这就是原因。

如果微任务队列是空的,渲染主线程回去宏任务队列拿一个任务,注意!是一个任务!

requestAnimationFrame 处于渲染阶段,不在微任务队列,也不在宏任务队列

eventloop

举例说明(下面代码中的【p】是标记,后文会引用)

js
var a【p1】 = new Promise(res => { setTimeout(res); }); a.then【p2】(() => { console.log(1); }) .then【p3】(() => { console.log(2); }) .then【p4】(() => { console.log(3); }); a.then【p5】(() => { console.log(4); }) .then 【p6】(() => { console.log(5); }) .then 【p7】(() => { console.log(6); }); setTimeout(() => { console.log(7); Promise.resolve().then(() => { console.log(8); }); }); setTimeout(() => { console.log(9); }); Promise.resolve().then(() => { console.log(10); }); console.log(11); // 11,10,1,4,2,5,3,6,7,8,9

下面拆解执行步骤

1. 执行全局代码(第一次eventloop)

任务当前执行代码
渲染主线程全局代码
微任务
宏任务

全局代码执行顺序

将 setTimeout(res) 丢进定时器任务,p1 = pending,一旦p1的状态为fulfilled,执行的是console.log(1);

js
p1 = pending【 待执行 () => {console.log(1);}p2 = pending【 待执行 () => {console.log(2);}p3 = pending【 待执行 () => {console.log(3);}P4 = pending【 无待执行 】 p1 = pending【 待执行 () => {console.log(1); console.log(4);}p5 = pending【 待执行 () => {console.log(5);}p6 = pending【 待执行 () => {console.log(6);}p7 = pending【 无待执行 () => { console.log(7); Promise.resolve().then(() => { console.log(8); }); } 进入定时器任务 () => {console.log(9)} 进入定时器任务 () => {console.log(10)} 进入微队列

console.log(11); 直接执行,此时控制台打印11

此时任务队列的内容为:
任务当前执行代码
渲染主线程
微任务() => { console.log(10);}
宏任务res ,

() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

2. 渲染主线程执行完全局代码后,查看微队列,返现有东西,拿出一个来执行。(第二次eventloop)

任务当前执行代码
渲染主线程() => { console.log(10);}
微任务
宏任务res ,

() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

执行() => { console.log(10);}, 此时控制台打印 10

渲染主线程执行完代码后,查看微队列,没了,去看宏任务,有东西,拿出第一个来执行。

任务当前执行代码
渲染主线程res
微任务
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

res执行后,p1状态变成fufilled,() => {console.log(1);}, () => {console.log(4);} 被放入微队列

任务当前执行代码
渲染主线程
微任务() => {console.log(1);},

() => {console.log(4);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

3. 宏任务执行完,开始新的eventloop,去微队列看,发现有,拿出第一个来放入主线程执行。(第三次eventloop)

任务当前执行代码
渲染主线程() => {console.log(1);}
微任务() => {console.log(4);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

执行() => {console.log(1);}, 此时控制台打印 1

() => {console.log(1);}执行后,p2状态变成fufilled,() => {console.log(2);} 被放入微队列;

任务当前执行代码
渲染主线程
微任务() => {console.log(4);}

() => {console.log(2);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

去微队列看,发现有,拿出一个来放入主线程执行。

任务当前执行代码
渲染主线程() => {console.log(4);}
微任务() => {console.log(2);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

执行() => {console.log(4);}, 此时控制台打印 4

() => {console.log(4);}执行后,p5状态变成fufilled,() => {console.log(5);} 被放入微队列;

任务当前执行代码
渲染主线程
微任务() => {console.log(2);}

() => {console.log(5);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

去微队列看,发现有,拿出一个来放入主线程执行。

任务当前执行代码
渲染主线程() => {console.log(2);}
微任务() => {console.log(5);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

执行() => {console.log(2);}, 此时控制台打印 2

() => {console.log(2);}执行后,p3状态变成fufilled,() => {console.log(3);} 被放入微队列;

任务当前执行代码
渲染主线程
微任务() => {console.log(5);}

() => {console.log(3);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

去微队列看,发现有,拿出一个来放入主线程执行。

任务当前执行代码
渲染主线程() => {console.log(5);}
微任务() => {console.log(3);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

执行() => {console.log(5);}, 此时控制台打印 5

() => {console.log(5);}执行后,p6状态变成fufilled,() => {console.log(6);} 被放入微队列;

任务当前执行代码
渲染主线程
微任务() => {console.log(3);}

() => {console.log(6);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

去微队列看,发现有,拿出一个来放入主线程执行。

任务当前执行代码
渲染主线程() => {console.log(3);}
微任务() => {console.log(6);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

执行() => {console.log(3);}, 此时控制台打印 3

() => {console.log(3);}执行后,p4状态变成fufilled,但是p4没有任务放入微队列;

任务当前执行代码
渲染主线程
微任务() => {console.log(6);}
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

去微队列看,发现有,拿出一个来放入主线程执行。

任务当前执行代码
渲染主线程() => {console.log(6);}
微任务
宏任务() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}

() => {console.log(9);}

执行() => {console.log(6);}, 此时控制台打印 6

() => {console.log(6);}执行后,p7状态变成fufilled,但是p7没有任务放入微队列;

去微队列看,发现没有,去看宏任务,有东西,拿出第一个来执行。

任务当前执行代码
渲染主线程() => {console.log(7);Promise.resolve().then(() => {console.log(8);});}
微任务
宏任务() => {console.log(9);}

执行() => {console.log(7);Promise.resolve().then(() => {console.log(8);});} , 此时控制台打印 7

执行后,Promise.resolve() 本身就是 fufilled,() => {console.log(8);}放入微队列;

任务当前执行代码
渲染主线程
微任务() => {console.log(8);}
宏任务() => {console.log(9);}

4. 宏任务执行完,开始新的eventloop,去微队列看,发现有,拿出第一个来放入主线程执行。(第四次eventloop)

任务当前执行代码
渲染主线程() => {console.log(8);}
微任务
宏任务() => {console.log(9);}

执行() => {console.log(8);}, 此时控制台打印 8

执行完去微队列看,发现没有,去看宏任务,有东西,拿出第一个来执行。

任务当前执行代码
渲染主线程() => {console.log(9);}
微任务
宏任务

执行() => {console.log(9);}, 此时控制台打印 9

任务当前执行代码
渲染主线程
微任务
宏任务

至此,全部代码执行完成。

最后的练习题

js
Promise.resolve().then(() => { console.log("1"); Promise.resolve().then(() => { console.log("2"); }); }); Promise.resolve().then(() => { console.log("3"); Promise.resolve().then(() => { console.log("4"); }); }); setTimeout(() => { console.log("5"); Promise.resolve().then(() => { console.log("6"); }); }); setTimeout(() => { console.log("7"); });

答案: 1,3,2,4,5,6,7