JavaScipt 中的事件循環(event loop),以及微任務 和宏任務的概念


說事件循環(event loop)之前先要搞清楚幾個問題。

1. js為什么是單線程的?
  試想一下,如果js不是單線程的,同時有兩個方法作用dom,一個刪除,一個修改,那么這時候瀏覽器該聽誰的?
 
2.js為什么需要異步?
  如果js不是異步的話,由於js代碼本身是自上而下執行的,那么如果上一行代碼需要執行很久,下面的代碼就會被阻塞,對用戶來說,就是”卡死”,這樣的話,會造成很差的用戶體驗。
 
3.js是如何實現異步的?
  既然js是單線程的,那么js是如何實現異步的呢,是通過事件循環(event loop),理解了event loop 就理解了js的執行機制。
 
4.瀏覽器中的多線程?
  js是單線程的,但是瀏覽器是多線程的,多個線程相互配合以保持同步,瀏覽器下的線程有
  • JavaScript引擎線程,用於解析JavaScript代碼
  • GUI渲染線程,(它與javaScript線程是互斥的)
  • 事件線程(onclick,onchange,…)
  • 定時器線程(setTimeout, setInterval)
  • 異步http線程(ajax),負責數據請求
  • EventLoop輪詢處理線程,事件被觸發時該線程會把事件添加到待處理隊列的隊尾
 
說道瀏覽器的多線程,我們可以說一下,主線程和異步線程之間是怎么配合的:主線程發起一個異步請求(比如http請求),相應的工作線程接收請求並告知主線程已收到通知(異步函數返回);主線程可以繼續執行后面的代碼,同時工作線程執行異步任務;工作線程完成工作之后,通知主線程;主線程收到通知后,執行一定的動作(調用回調函數);
 
5. javaScript 的事件循環(event loop
既然js是單線程的,那么所有的任務就需要排隊執行。
  • javaScript 中的任務可以被划分為宏任務(Macrotask)或者微任務(Microtask)
  • 像鼠標事件,鍵盤事件,"ajax","setTimeout"等就屬於宏任務,需要注意的是,主線程的整體代碼(script標簽),也是一個宏任務
  • process.nextTick,PromiseA.then(), MutaionObserver 就屬於微任務
簡單概括一下事件循環,就是
1.執行宏任務隊列中第一個任務,執行完后移除它
2.執行所有的微任務,執行完后移除它們
3.執行下一輪宏任務(重復步驟2)
如此循環就形成了event loop,其中,每輪執行 一個宏任務所有的微任務
 
下圖很形象的描述了event loop
網上有用異步任務和同步任務來說明這個問題的,但是我覺得用宏任務和微任務更好點。
 
下面我通過分析一個示例來說一下:
console.log(1);

setTimeout(function(){
    console.log(2)
},10);

new Promise(function(resolve){
    console.log(3)
    for( var i=100000 ; i>0 ; i-- ){
        i==1 && resolve()
    }
    console.log(4)
}).then(function(){
    console.log(5)
}).then(function(){ 
console.log(6)
})
console.log(
7);

打印出來的結果是:1 3 4 7 5 6 2

我們分析一下整個過程

1. 首先執行主線程這個宏任務,從上到下執行,遇到console.log(1); 打印1出來

2. 遇到setTimeout,把它丟給定時器線程處理,然后繼續往下執行,並不會阻塞10毫秒,而此處定時器線程會在,主線程執行完后的10毫秒,把回調函數放入宏任務隊列

3. 遇到new Promise,直接執行,先打印 ‘3‘ 出來,然后執行for循環,達到條件之后,把promise的狀態改為resolved,繼續執行打印 ‘4’ 出來

4.遇到promise的then, 屬於微任務,則把回調函數放入微任務隊列

5.又遇到promise的then, 屬於微任務,則把回調函數放入微任務隊列

6. 遇到console.log(7) 打印 ‘7’ 出來

7. 宏任務執行完后會執行所有待執行的微任務,所以會相繼打印 ‘6’, ‘7’ 出來。

至此第一輪循環已經結束了,第一輪循環里的宏任務和微任務都會被移除出任務隊列,接下來開啟第二輪循環,

1.首先查找是否有宏任務,由於setTimeout 的回調被放入了宏任務隊列,這里會執行回調函數的代碼,打印了 ‘2’ 出來

2. 接着查找是否有微任務,發現沒有微任務,則本輪循環結束

接下來會重復上面的步驟,這就是event loop 了。后續當我們觸發點擊事件,有回調函數的話,回調函數也會被放入宏任務隊列,一旦隊列里重新有了任務,就會被執行。

 

6. 擴展題目

如果能把上面這道題的流程說清楚,那么恭喜你,對event loop理解的不錯了。 下面我們再利用上面的題目擴展一下,加深理解。

下面的代碼打印出來的結果是什么?

console.log(1);

setTimeout(function(){
    new Promise(function(resolve){
    console.log('promise in setTimeout1');
    resolve();
    }).then(function(){
        console.log('then in setTimeout1');
    })
},10);

new Promise(function(resolve){
    console.log(3);
    for( var i=100000 ; i>0 ; i-- ){
        i==1 && resolve();
    }
    console.log(4)
}).then(function(){
    console.log(5);
});

setTimeout(function(){
    console.log('setTimeout2');
},10);

console.log(7);

結果如下:

可以發現,第二個setTimeout 的回調函數,執行的比第一個setTimeout里面的promise.then()的回調要晚,這是因為每次循環只執行一個宏任務,但是卻會執行所有待執行的微任務,而第二個setTimeout在宏任務隊列的位置在第一個setTimeout后面。

 

這個就是我理解的JavaScipt 事件循環機制,參考了很多文章,也自己做了很多思考寫出來的,碼字不易,覺得有幫助可以點個贊哦。也歡迎留言交流

參考文章

https://segmentfault.com/a/1190000012806637?utm_source=tag-newest

http://www.ruanyifeng.com/blog/2014/10/event-loop.html

https://zhuanlan.zhihu.com/p/33127885

https://zhuanlan.zhihu.com/p/33136054

https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM