JavaScipt 中的事件迴圈機制,以及微任務 和巨集任務的概念
說事件迴圈(event loop)之前先要搞清楚幾個問題。
1. js為什麼是單執行緒的?
試想一下,如果js不是單執行緒的,同時有兩個方法作用dom,一個刪除,一個修改,那麼這時候瀏覽器該聽誰的?這就是js被設計成單執行緒的原因。
2.js為什麼需要非同步?
如果js不是非同步的話,由於js程式碼本身是自上而下執行的,那麼如果上一行程式碼需要執行很久,下面的程式碼就會被阻塞,對使用者來說,就是”卡死”,這樣的話,會造成很差的使用者體驗。
3.js是如何實現非同步的?
既然js是單執行緒的,那麼js是如何實現非同步的呢,是通過事件迴圈(event loop),理解了event loop 就理解了js的執行機制。
4.瀏覽器中的多執行緒?
js是單執行緒的,但是瀏覽器是多執行緒的,多個執行緒相互配合以保持同步,瀏覽器下的常駐執行緒有
- js執行緒
- GUI 渲染執行緒,(它與javaScript執行緒是互斥的)
- 事件執行緒(onclick,onchange,…)
- 定時器執行緒(setTimeout, setInterval)
- 非同步http執行緒(ajax)
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. 擴充套件題目
上面的題目擴充套件一下,大家看一下打印出來的結果是什麼?
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