前幾天聽公司一個公司三年的前端說「今天又學到了一個知識點-微任務、宏任務」,我問他這是什麼東西,因爲在吃飯他淺淺的說了下,當時沒太理解就私下學習整理一番,因爲談微任務、宏任務必談到事件循環,因而就有了這篇博客。javascript
在談到事件循環機制以前咱們須要知道一些基礎知識就是:前端
其實js是單線程在它做爲腳本語言操做dom的時候就決定了。那麼此時就有一個性能問題,那麼js在瀏覽器端是如何處理這個問題的呢?同時,js在後臺Node中又是如何解決的呢?這就是本篇須要介紹的事件循環機制,這裏我將分別以瀏覽器和Node兩個方面來分析。java
在講解事件循環以前先談談js中同步代碼、異步代碼的執行流程。node
js引擎在執行經過代碼的過程當中,會安裝順序依次存儲到一個地方去,這個地方就叫作執行棧
,當咱們調用一個方法的時候,js會生成一個和這個方法相對應的上下文(context)。這個執行環境中存在着這個方法的私有做用域,上層做用域的指向,方法的參數,這個做用域中定義的變量以及這個做用域的this對象。promise
function a() { console.log("method a execute..."); } function b() { a(); } function c() { b(); } c();
以上面例子分析:js在執行的時候會有一個全局上下文,咱們這裏就稱爲GContext,下面分析步驟瀏覽器
ok,上面是同步代碼的執行,上面會涉及到兩個核心概念:執行整個代碼的線程咱們稱之爲主線程
,存放方法執行的地方咱們稱之爲執行棧
.dom
上面說完了同步過程,那這裏來談談異步的過程。js引擎在遇到一個異步事件,不會一直等待返回結果而是將它掛起。當異步任務執行完以後會將結果加入到和執行棧中不一樣的任務隊列
當中,注意的是:此時放入隊列不會當即執行其回調
,而是當主線程執行完執行棧中全部的任務以後再去隊列中查找是否有任務,若是有則取出排在第一位的事件而後將回調放入執行棧並執行其代碼。如此反覆就構成了事件循環。異步
這裏一樣有一個核心概念:任務隊列
socket
上面提到js執行異步方法的時候會將其返回結果放到隊列中,這是比較籠統的,具體來講,js會根據任務的類型將其放入不一樣的隊列,任務類型有兩種:微任務、宏任務
。那麼其對應的哪些是微任務、哪些是宏任務呢?函數
瀏覽器在執行的時候,先從宏任務隊列中取出一個宏任務執行宏,而後在執行該宏任務下的全部的微任務,這是一個循環;而後再取出並執行下一個宏任務,再執行全部的微任務,這是第二個循環,以此類推.
注意:整個javascript代碼是第一個宏任務
const process = require('process') setTimeout(function () {// 分發宏任務到EventQueue console.log("1"); }, 0); setTimeout(() => { console.log("11"); }, 0); setTimeout(() => { console.log("111"); }, 0); new Promise(function (resolve) { console.log('2'); resolve(); }).then(function () {// 發送微任務 console.log('3'); });
// 輸出 2 3 1 11 111
在瀏覽器端,在咱們執行一片script的時候,當遇到同步代碼,依次進入執行棧
,遇到異步代碼,將其掛起,繼續執行其它方法,當異步方法執行完以後根據任務類型進入到任務隊列
,在執行棧執行完,主線程
空閒下來了以後會到任務隊列中取任務回調並執行。
我本身認爲Node的事件循環和瀏覽器端仍是有點區別的,它的事件循環依靠libuv引擎。
該圖來自官網,這裏展現了在node的事件循環的6個階段。
對於咱們來講咱們更關注 timer、poll、check這三個階段便可。
poll 階段有兩個主要的功能:
poll 階段的邏輯
若是event loop進入了 poll階段,且代碼未設定timer,將會發生下面狀況:
b、若是poll queue爲空,將會發生下面狀況:
* 若是代碼已經被setImmediate()設定了callback, event loop將結束poll階段進入check階段,並執行check階段的queue (check階段的queue是 setImmediate設定的) * 若是代碼沒有設定setImmediate(callback),event loop將阻塞在該階段等待callbacks加入poll queue;
若是event loop進入了 poll階段,且代碼設定了timer:
這兩個函數的功能仍是相似的,不一樣的是他們處於EventLoop的不一樣階段:timer、check。
setImmediate(()=>console.log("setInterval")); setTimeout(() => {console.log("setTimeout")},0);
上面兩行代碼會輸出順序是什麼呢?其實兩種可能都有.
1.當setTimeout的0ms並不能作到絕對0ms,若是已通過了timer階段,那麼此時setTimeout就會在下一次循環中執行,也就是說先setInterval、再setTimeout。
2.第二種可能就是正常流程了,先timer、再check
若是上面的代碼再一個IO操做做呢?如:
require('fs').readFile(__filename,()=>{ setImmediate(()=>console.log("setInterval")); setTimeout(() => {console.log("setTimeout")}); })
此時只可能出現一種狀況,先setInterval、再setTimeout,由於在io中已經執行過了timer(readFile時處於IO callback)。
下面一塊兒來看以下代碼:
setTimeout(() => { console.log("timer1") Promise.resolve().then(() => console.log("promise1")); process.nextTick(() => console.log("nextTick1")) }, 0); setTimeout(() => { console.log("timer2") Promise.resolve().then(() => console.log("promise2")); process.nextTick(() => console.log("nextTick2")) }, 0);
按照個人理解,它的輸出應該是以下:先timer、而後切換階段的時候執行微任務.
// 狀況1 timer1 timer2 nextTick1 nextTick2 promise1 promise2
但是並非,它的輸出一直是:
// 狀況2 timer1 nextTick1 promise1 timer2 nextTick2 promise2
後臺晚上查資料由於Node11對EventLoop做了修改,爲了和瀏覽器兼容。因而呼我切換到10.8.0,發現上面兩種狀況都有(狀況1比例大於狀況2)。這點暫時還未查明什麼緣由。
node中的6個階段每一個階段執行完都會伴隨着執行微任務,同個MicroTask隊列下process.tick()會優於Promise。
本篇主要介紹了瀏覽器和Node對於事件循環機制實現,因爲能力水平有限,其中可能有誤之處歡迎指出。
歡迎關注公衆號: