Event Loop解读(待完善)
本文最后更新于:9 个月前
浏览器Event Loop
定义
To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. Each agent has an associated event loop, which is unique to that agent.
要想了解浏览器的事件机制,我们需要知道,浏览器的线程基本概念
浏览器进程线程
浏览器中是多线程多进程的,我们每打开一个新的页面就会打开一个新的进程,每个进程中都会有多个线程在运作,其中常见的线程有以下几种:
- GUI渲染线程
- 负责页面渲染,处理HTML,CSS解析,DOM树的构建、布局和绘制;
- 当页面发生重绘重排时会重新调用此线程;
- 该线程和JS线程互斥,所以当GUI执行时,JS会被挂起,JS线程执行时,GUI会被挂起,直到任务队列空了,才会去执行GUI
- JS线程
- 负责js代码的执行;
- 执行准备好的事件;
- 定时器线程
- 负责执行定时器任务例如:setTimeout,setInterval;
- 主线程执行遇到定时器任务时,将任务加入定时器线程,在计时完毕后,任务会加入任务队列,等待JS线程的执行
- 事件触发线程
负责将准备好的事件添加到任务队列,等待JS线程执行
- 异步请求线程
负责执行异步请求一类的操作,如Promis,ajax,axios
主线程执行到了异步任务后,将任务讲给异步请求线程执行,执行完后,将回调函数添加到微任务队列,等待JS线程执行
事件队列
浏览器中的Event-loop
浏览器中的事件循环机制中有两中队列,一个是宏任务一个是微任务,当主线程遇到同步操作时加入宏任务,遇到异步操作时加入微任务。常见的宏任务:setTimeout,setInterval,script(整段代码),I/O操作,UI渲染常见的微任务:Promise.then,MutationObserver
其中的执行机制是当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。其中微任务是不打断的直接执行完一整个队列的任务。
宏任务
微任务
一些题目解读
可以通过一个例子来进行分析:
在这个例子里:console.log('script start'); async function async1(){ await async2().then(_=>console.log(_)); console.log('async1 end') return 'async1 return'; } async function async2(){ Promise.resolve(console.log('async2 promise')).then(_=>console.log('async2 then')); await console.log('async2 await'); console.log('async2 end'); return 'async2 return'; } async1().then(_=>console.log(_,'async1 then')); setTimeout(function(){ console.log('settimeout'); },0); new Promise(resolve => { console.log('promise'); resolve(); }).then(function () { console.log('function1'); }).then(function(){ console.log('function2'); }); console.log('script end')
- 首先输出
script start
- 然后是
async1
的内容,此刻await
执行async2
输出async2 promise
,然后then
进入微任务 - 然后下面的
await
输出async2 await
,然后后面的阻塞,进入微任务 - 然后继续执行,输出
promise
,then
进入微任务 - 然后输出
script end
- 此时宏任务执行完一轮
- 微任务开始执行,先输出
async2 then
,async2 end
,然后return
值进入微任务,同一批次的微任务中继续执行输出function1
,然后2then
又进入微任务 - 继续执行微任务,此时先输出
async2 return
,然后async1
的await
后的进入微任务 - 输出
function2
- 在执行微任务输出
async1 end
,然后1return
进入async1().then(_=>console.log(_,'async1 then'));
的微任务 - 输出
async1 return
,async1 then
,微任务为空,执行宏任务 - 输出
settimeout
最终顺序为
script start
async2 promise
async2 await
promise
script end
async2 then
async2 end
function1
async2 return
function2
async1 end
async1 return
async1 then
settimeout
在这个例子里:console.log('e1') new Promise((r,j)=>{r()}).then(()=>{ console.log('promise3'); setTimeout(()=>{ console.log('timeout3') },0) }) setTimeout(()=>{ console.log('timeout1') new Promise((r,j)=>{r()}).then(()=>{ console.log('promise1') }) },5000) console.log('e2') new Promise((r,j)=>{r(console.log('1'))}).then(()=>{ console.log('promise2'); setTimeout(()=>{ console.log('timeout2') },0) }) console.log('e3')
- 主线程
main
进入栈执行操作,此时执行script
代码,会把输出e1
- 之后遇到
Promise.then
,会将其加入微任务队列 - 再往下执行,遇到
setTimeout
,将其加入定时器线程,等待计数完成后加入宏任务队列 - 再往下执行输出
e2
- 往下执行遇到
Promise
,先将resolve
输出1,然后将then
加入微任务队列 - 再往下执行输出e3,此时
script
代码执行完毕,立刻搜索微任务是否为空,不为空就立刻执行此时微任务的队列 - 此时微任务队列先进先出输出
promise3
,然后遇到setTimeout
,将其加入定时器线程,等待计数完成后加入宏任务队列 - 再往下执行第二个微任务,输出
promise2
,然后遇到setTimeout
,将其加入定时器线程,等待计数完成后加入宏任务队列 - 再往下执行发现微任务队列为空,去宏任务队列去寻找是否有任务
- 在宏任务发现此时先执行完的
setTimeout3
,输出timeout3
,将then
加入微任务队列 - 执行完该次宏任务后立刻寻找微任务队列,为空,去寻找宏任务队列
- 在宏任务发现先执行完的
setTimeout2
任务,执行输出timeout2
- 此时任务队列为空,等待新任务加入
- 此时
setTimeout1
计数完毕,加入宏任务队列,并执行,输出timeout1
,将then
加入微任务队列 - 执行完该次宏任务后立刻寻找微任务队列,并执行,输出
promise1
最终结果为e1 e2 1 e3 promise3 promise2 timeout3 timeout2 timeout1 promise
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!