老是隻知其一;不知其二的Event Loop

做者:孫輝,美團金融前端團隊成員。15年畢業加入美團,相信技術,更相信技術只是大千世界裏知識的一種,我的博客: sunyuhui.comhtml

前言

JavaScript中的事件循環一直都是一個不少人都或多或少了解,但說不清楚的知識點,停留在只知其一;不知其二的層面。之前只須要使用回調函數、定時器還好說,可是自從有了Promise以後,對事件循環的透徹瞭解就比較重要了。前端

本篇文章不打算從頭開始敘述,那樣篇幅太長,略過最基本的概念,咱們簡單粗暴的把事件循環說清楚。promise

理論:關於MacroTask和MicroTask

一張圖展現JavaScript中的事件循環:bash



一次事件循環:先運行macroTask隊列中的一個,而後運行microTask隊列中的全部任務。接着開始下一次循環(只是針對macroTask和microTask,一次完整的事件循環會比這個複雜的多)。異步

其中macroTaskmicroTask是兩種任務隊列,相比而言,你們更熟悉的一個詞是任務隊列task queue,其實就是macroTask),你們更熟悉的關於事件循環的機制說法大概是:主進程執行完了以後,每次從任務隊列裏取一個任務執行。可是promise出現以後,這個說法就不太準確了。函數

JavaScript引擎對這兩種隊列有不一樣的處理,簡單的說就是引擎會把咱們的全部任務分門別類,一部分歸爲macroTask,另一部分歸爲microTask,下面是類別劃分:oop

  • macroTask: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
  • microTask: process.nextTick, Promise, Object.observe, MutationObserver

咱們所熟悉的定時器就屬於macroTask,可是僅僅瞭解macroTask的機制仍是不夠的。學習

上面這些都是理論啊,咱們怎麼直觀的感覺兩種隊列的區別呢?說的再多也不如來一段實踐,咱們感知這種區別最好的方式就是經過任務的執行順序ui

實踐:小二,上代碼

咱們以setTimeoutprocess.nextTickpromise爲例直觀感覺下兩種任務隊列的運行方式。spa

console.log('main1');

process.nextTick(function() {
    console.log('process.nextTick1');
});

setTimeout(function() {
    console.log('setTimeout');
    process.nextTick(function() {
        console.log('process.nextTick2');
    });
}, 0);

new Promise(function(resolve, reject) {
    console.log('promise');
    resolve();
}).then(function() {
    console.log('promise then');
});

console.log('main2');複製代碼

彆着急看答案,先以上面的理論本身想一想,運行結果會是啥?

來個圖片佔個位



最終結果是這樣的:

main1
promise
main2
process.nextTick1
promise then
setTimeout
process.nextTick2複製代碼

process.nextTickpromise thensetTimeout 前面輸出,已經證實了macroTaskmicroTask的執行順序。可是有一點必需要指出的是。上面的圖容易給人一個錯覺,就是主進程的代碼執行以後,會先調用macroTask,再調用microTask,這樣在第一個循環裏必定是macroTask在前,microTask在後。

可是最終的實踐證實:在第一個循環裏,process.nextTick1promise then這兩個microTask是在setTimeout這個macroTask裏以前輸出的,這是爲何呢?

由於主進程的代碼也屬於macroTask(這一點我比較疑惑的是主進程都是一些同步代碼,而macroTask和microTask包含的都是一些異步任務,爲啥主進程的代碼會被劃分爲macroTask,不過從實踐來看確實是這樣,並且也有理論支撐:【翻譯】Promises/A+規範)。

主進程這個macroTask(也就是main1promisemain2)執行完了,天然會去執行process.nextTick1promise then這兩個microTask。這是第一個循環。以後的setTimeoutprocess.nextTick2屬於第二個循環

別看上面那段代碼好像特別繞,把原理弄清楚了,都同樣 ~

requestAnimationFrameObject.observe(已廢棄)MutationObserver這三個任務的運行機制你們能夠從上面看到,不一樣的只是具體用法不一樣。重點說下UI rendering。在HTML規範:event-loop-processing-model裏敘述了一次事件循環的處理過程,在處理了macroTaskmicroTask以後,會進行一次Update the rendering,其中細節比較多,總的來講會進行一次UI的從新渲染。

後續

不知道你們有沒有發現一個現象,在學習技術點的時候,若是太淺,得來的知識點可能不完整甚至是錯的,若是追究的太深,又會給人一種太偏學究的感受,其中的平衡點,你們本身留心把握。

done

另外,你們端午快樂 ~

最後,團隊爲了招聘方便,整了個公衆號,主要是一些招聘信息,團隊信息,全部的技術文章在公衆號裏也能夠看到,對了,若是你想去美團其餘團隊,咱們也能夠幫你內推哦 ~

二維碼
二維碼

參考:

  1. HTML規範:event-loop-processing-model
  2. 【翻譯】Promise/A規範
相關文章
相關標籤/搜索