Event Loop 中的 microtask 與 macrotask

Javascript 的事件循環(huán)會(huì)被常常提及, 而且在實(shí)際開(kāi)發(fā)中, 經(jīng)常需要使用事件相關(guān)的知識(shí), 所以特地深入了解一下.

深入 event loop

事件循環(huán)是用來(lái)做異步任務(wù)處理的, 與之相同的做異步任務(wù)處理的還有多線程, 但是由于 javascript 的單線程特性, 最終使用 event loop 的方式.
或許你可以從下面的簡(jiǎn)略的偽代碼看出 event loop 是什么:

eventQueue = [];
event;
while(1){
    if(eventQueue.length > 0) {
        event = eventQueue.shift();

        try {
            event();
        } catch(err) {
            reportError(err);
        }
    }
}

event loop 有兩種, 一種是在瀏覽器上下文, 一種是在 worker 上下文的.瀏覽器上下文一般會(huì)至少有一個(gè) event loop, 像一個(gè) iframe, 瀏覽器窗口
都會(huì)有一個(gè) event loop, 而對(duì)于 worker 上下文的則比較簡(jiǎn)單, worker 進(jìn)程管理著一個(gè) event loop.這里著重將瀏覽器上下文的 event loop.

event loop 運(yùn)行流程

根據(jù)規(guī)范 event loop 所講的, 它的流程如下:

當(dāng)一個(gè) event loop 存在, 它會(huì)按照以下的步驟運(yùn)行:
1\. 在最老的任務(wù)隊(duì)列中取出最老的任務(wù), 如果沒(méi)有任務(wù), 那么就會(huì)跳到 microtask 隊(duì)列的執(zhí)行.這里的最老我個(gè)人理解是像任務(wù)調(diào)度算法中的等待時(shí)間最長(zhǎng)的意思.
2\. 將 event loop 當(dāng)前任務(wù)設(shè)置為最老的那個(gè)任務(wù).
3\. 執(zhí)行當(dāng)前任務(wù)隊(duì)列中最老的那個(gè)任務(wù)
4\. 將 event loop 當(dāng)前運(yùn)行任務(wù)設(shè)置為 null
5\. 將剛剛執(zhí)行的那個(gè)最老的任務(wù)從它的隊(duì)列中移除
6\. 執(zhí)行檢查 microtask 隊(duì)列算法(microtask checkpoint 這個(gè)稍后詳談), 這里先粗略理解為執(zhí)行 microtask 隊(duì)列
7\. 更新渲染(update the rendering)
8\. 如果是一個(gè) worker event loop, 但是沒(méi)有任務(wù), 并且 WorkerGlobalScope 對(duì)象的 closing flag 值為 true 的, 就銷(xiāo)毀 event loop 并中止這些步驟,
并進(jìn)行 web worker 中的 run worker 算法.
9\. 返回到第一步, 此為一個(gè)事件循環(huán).

由上面的步驟我們可以知道, 在一個(gè)循環(huán)當(dāng)中, 每執(zhí)行一個(gè)任務(wù), event loop 都會(huì)嘗試去清空 microtask 隊(duì)列, 也就是對(duì)應(yīng)的第六步.同時(shí)我們可以看到, 在做完
上面的操作之后, 才會(huì)進(jìn)行渲染操作, 防止過(guò)多的操作重復(fù)渲染造成性能問(wèn)題.

microtask checkpoint

每一個(gè) event loop 都有一個(gè) microtask 的隊(duì)列.我們從規(guī)范看到 event loop, microtask 隊(duì)列的流程如下:

如果用戶代理的 checkout point flag 值為 false 的時(shí)候, 就會(huì)按照下面的步驟進(jìn)行執(zhí)行:
1\. 設(shè)置 performing a microtask checkpoint flag 值為 true.
2\. 當(dāng) microtask 隊(duì)列不為空時(shí):
    2.1 選擇隊(duì)列中最老的任務(wù)隊(duì)列
    2.2 設(shè)置當(dāng)前運(yùn)行任務(wù)為選擇的最老任務(wù)
    2.3 執(zhí)行這個(gè)最老的任務(wù)
    2.4 設(shè)置當(dāng)前運(yùn)行任務(wù)為 null
    2.5 將剛剛運(yùn)行的任務(wù)從它的任務(wù)隊(duì)列中移除.
    2.6 回到 2
3\. 每一個(gè) environment settings object 他們的 responsible event loop 就是當(dāng)前 event loop, 會(huì)給 environment settings object 發(fā)出一個(gè) rejected promise 的通知.
4\. 清理 indexed db 事務(wù)
5\. 將 performing a microtask checkpoint flag 設(shè)置為 false

microtask 與 macrotask 的區(qū)別

這個(gè)應(yīng)該是 event loop 中比較核心的問(wèn)題, 究竟 timer 一類(lèi)設(shè)定的 macrotask 與 promise 一類(lèi)設(shè)定的 microtask 有什么區(qū)別?
從上面對(duì)規(guī)范的解讀可以看出, microtask 與 macrotask 在執(zhí)行上有區(qū)別, 一次 event loop 會(huì)取一個(gè) macrotask 執(zhí)行, 但是會(huì)將一個(gè) microtask 隊(duì)列
清空, 也就是說(shuō), 如果一個(gè) microtask 隊(duì)列過(guò)長(zhǎng), 確實(shí)會(huì)阻塞下一個(gè) macrotask 的開(kāi)始執(zhí)行時(shí)間.可以看出, 在異步中, js 雖然是異步非阻塞, 但是卻是使用
同步的方式來(lái)執(zhí)行 microtask 的.
另外從字面上來(lái)說(shuō), macrotask 屬于 task, 也就是大型任務(wù), microtask 屬于 job, 也就是小型任務(wù), 而對(duì)于如何更詳細(xì)的區(qū)分, 規(guī)范并沒(méi)有說(shuō), 而是從產(chǎn)生類(lèi)型
上將兩類(lèi)分開(kāi):

macroTask: setTimeout, setInterval, setImmediate, I/O, rendering
microTask: promise, process.nextTick, Object.observe, MutationObserver

我提供一個(gè)巧記的方式, 越靠近定時(shí)器一類(lèi)的就是 macrotask, 越靠近 promise 一類(lèi)的是 microtask.
至于什么時(shí)候需要使用 microtask 呢? 我覺(jué)得這個(gè)問(wèn)題很好地指出兩者(macrotask, microtask)的不同, 在你覺(jué)得需要將這個(gè)異步任務(wù)同步化的時(shí)候, 就使用
microtask , 否則就使用 macrotask.換種說(shuō)法, 也就是這個(gè)任務(wù)你需要盡可能快地執(zhí)行, 就使用 microtask.
舉個(gè)例子:

console.log('script start');

setTimeout(() => {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(() => {
    console.log('promise1');
}).then(() => {
    console.log('promise2');
});

console.log('script end');

上面的例子在 chrome 中的順序是 script start, script end, promise1, promise2, setTimeout.

event loop 的產(chǎn)生源

event loop 的 macrotask 的產(chǎn)生源有很多個(gè), 其中包括:

  1. DOM 元素操作, 比如以非阻塞的方式插入一個(gè)元素
  2. 用戶交互
  3. 網(wǎng)絡(luò)
  4. history 操作源, 比如 history.back() 等等.

task 的任務(wù)源很多, 像常見(jiàn)的 ajax, setTimeout, DOM click 事件都可以產(chǎn)生任務(wù).當(dāng)然, 不同的任務(wù)源會(huì)被加到不同的任務(wù)隊(duì)列中去.比如 ajax 操作的異步非阻塞任務(wù)就會(huì)被加到 ajax 源的
隊(duì)列中, DOM 事件產(chǎn)生的任務(wù)就會(huì)被添加到 DOM 事件的任務(wù)隊(duì)列中去.

總結(jié)

下面, 我使用一個(gè)圖來(lái)總結(jié)一下:


image.png

參考資料:

  1. html.spec.whatwg.org/multipage/w…
  2. jakearchibald.com/2015/tasks-…
    3.https://zhuanlan.zhihu.com/p/33058983
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末溜畅,一起剝皮案震驚了整個(gè)濱河市光坝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浩螺,老刑警劉巖拉盾,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件血巍,死亡現(xiàn)場(chǎng)離奇詭異擅腰,居然都是意外死亡趟咆,警方通過(guò)查閱死者的電腦和手機(jī)挣磨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事∫曷罚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵菩彬,是天一觀的道長(zhǎng)缠劝。 經(jīng)常有香客問(wèn)我,道長(zhǎng)骗灶,這世上最難降的妖魔是什么剩彬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮矿卑,結(jié)果婚禮上喉恋,老公的妹妹穿的比我還像新娘。我一直安慰自己母廷,他們只是感情好轻黑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著琴昆,像睡著了一般氓鄙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上业舍,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天抖拦,我揣著相機(jī)與錄音,去河邊找鬼舷暮。 笑死态罪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的下面。 我是一名探鬼主播复颈,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沥割!你這毒婦竟也來(lái)了耗啦?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤机杜,失蹤者是張志新(化名)和其女友劉穎帜讲,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體椒拗,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡似将,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年获黔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玩郊。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖枉阵,靈堂內(nèi)的尸體忽然破棺而出译红,到底是詐尸還是另有隱情,我是刑警寧澤兴溜,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布侦厚,位于F島的核電站,受9級(jí)特大地震影響拙徽,放射性物質(zhì)發(fā)生泄漏刨沦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一膘怕、第九天 我趴在偏房一處隱蔽的房頂上張望想诅。 院中可真熱鬧,春花似錦岛心、人聲如沸来破。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)徘禁。三九已至,卻和暖如春髓堪,著一層夾襖步出監(jiān)牢的瞬間送朱,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工干旁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驶沼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓争群,卻偏偏與公主長(zhǎng)得像商乎,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祭阀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容