JavaScript事件循環(Event Loop)

一、爲何要有事件循環?

由於js是單線程的,事件循環是js的執行機制,也是js實現異步的一種方法。程序員

既然js是單線程,那就像只有一個窗口的銀行,客戶須要排隊一個一個辦理業務,同理js任務也要一個一個順序執行。若是一個任務耗時
過長,那麼後一個任務也必須等着。那麼問題來了,假如咱們想瀏覽新聞,可是新聞包含的超清圖片加載很慢,難道咱們的網頁要一直卡着
直到圖片徹底顯示出來?所以聰明的程序員將任務分爲兩類:segmentfault

  • 同步任務
  • 異步任務

當咱們打開網站時,網頁的渲染過程就是一大堆同步任務,好比頁面骨架和頁面元素的渲染。而像加載圖片音樂之類佔用資源大耗時久的任務,
就是異步任務。promise

二、宏任務與微任務

JavaScript中除了普遍的同步任務和異步任務,咱們對任務有更精細的定義:瀏覽器

  • macro-task(宏任務): 包括總體代碼scriptsetTimeoutsetInterval
  • micro-task(微任務): Promiseprocess.nextTick

不一樣的類型的任務會進入不一樣的Event Queue(事件隊列),好比setTimeout、setInterval會進入一個事件隊列,而Promise會進入
另外一個事件隊列。異步

一次事件循環中有宏任務隊列和微任務隊列。事件循環的順序,決定js代碼執行的順序。進入總體代碼(宏任務-<script>包裹的代碼能夠
理解爲第一個宏任務),開始第一次循環,接着執行全部的微任務。而後再次從宏任務開始,找到其中一個任務隊列的任務執行完畢,
再執行全部的微任務。如:
<script>
    setTimeout(function() {
        console.log('setTimeout');
    })

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

    console.log('console');

    /* ----------------------------分析 start--------------------------------- */

    一、`<script>`中的整段代碼做爲第一個宏任務,進入主線程。即開啓第一次事件循環
    二、遇到setTimeout,將其回調函數放入Event table中註冊,而後分發到宏任務Event Queue中
    三、接下來遇到new Promise、Promise,當即執行;將then函數分發到微任務Event Queue中。輸出: promise
    四、遇到console.log,當即執行。輸出: console
    五、總體代碼做爲第一個宏任務執行結束,此時去微任務隊列中查看有哪些微任務,結果發現了then函數,而後將它推入主線程並執行。
輸出: then
    六、第一輪事件循環結束
    開啓第二輪事件循環。先從宏任務開始,去宏任務事件隊列中查看有哪些宏任務,在宏任務事件隊列中找到了setTimeout對應的回調函數,
當即執行之。此時宏任務事件隊列中已經沒有事件了,而後去微任務事件隊列中查看是否有事件,結果沒有。此時第二輪事件循環結束;
輸出:setTimeout

    /* ----------------------------分析 end--------------------------------- */
</script>

三、分析更復雜的代碼

<script>
    console.log('1');

    setTimeout(function() {
        console.log('2');
        process.nextTick(function() {
            console.log('3');
        })
        new Promise(function(resolve) {
            console.log('4');
            resolve();
        }).then(function() {
            console.log('5')
        })
    })
    process.nextTick(function() {
        console.log('6');
    })
    new Promise(function(resolve) {
        console.log('7');
        resolve();
    }).then(function() {
        console.log('8')
    })

    setTimeout(function() {
        console.log('9');
        process.nextTick(function() {
            console.log('10');
        })
        new Promise(function(resolve) {
            console.log('11');
            resolve();
        }).then(function() {
            console.log('12')
        })
    })
</script>

1、第一輪事件循環

a)、整段<script>代碼做爲第一個宏任務進入主線程,即開啓第一輪事件循環
b)、遇到console.log,當即執行。輸出:1
c)、遇到setTimeout,將其回調函數放入Event table中註冊,而後分發到宏任務事件隊列中。咱們將其標記爲setTimeout1
d)、遇到process.nextTick,其回調函數放入Event table中註冊,而後被分發到微任務事件隊列中。記爲process1
e)、遇到new Promise、Promise,當即執行;then回調函數放入Event table中註冊,而後被分發到微任務事件隊列中。記爲then1。
輸出: 7
f)、遇到setTimeout,將其回調函數放入Event table中註冊,而後分發到宏任務事件隊列中。咱們將其標記爲setTimeout2

此時第一輪事件循環宏任務結束,下表是第一輪事件循環宏任務結束時各Event Queue的狀況函數

- 宏任務事件隊列 微任務事件隊列
第一輪事件循環 (宏任務已結束) process一、then1
第二輪事件循環(未開始) setTimeout1
第三輪事件循環(未開始) setTimeout2

能夠看到第一輪事件循環宏任務結束後微任務事件隊列中還有兩個事件待執行,所以這兩個事件會被推入主線程,而後執行post

g)、執行process1。輸出:6
h)、執行then1。輸出:8

第一輪事件循環正式結束!網站


2、第二輪事件循環

a)、第二輪事件循環從宏任務setTimeout1開始。遇到console.log,當即執行。輸出: 2
b)、遇到process.nextTick,其回調函數放入Event table中註冊,而後被分發到微任務事件隊列中。記爲process2
c)、遇到new Promise,當即執行;then回調函數放入Event table中註冊,而後被分發到微任務事件隊列中。記爲then2。輸出: 4

此時第二輪事件循環宏任務結束,下表是第二輪事件循環宏任務結束時各Event Queue的狀況線程

- 宏任務事件隊列 微任務事件隊列
第一輪事件循環(已結束)
第二輪事件循環 (宏任務已結束) process二、then2
第三輪事件循環(未開始) setTimeout2

能夠看到第二輪事件循環宏任務結束後微任務事件隊列中還有兩個事件待執行,所以這兩個事件會被推入主線程,而後執行code

d)、執行process2。輸出:3
e)、執行then2。輸出:5

第二輪事件循環正式結束!


3、第三輪事件循環

a)、第三輪事件循環從宏任務setTimeout2開始。遇到console.log,當即執行。輸出: 9
d)、遇到process.nextTick,其回調函數放入Event table中註冊,而後被分發到微任務事件隊列中。記爲process3
c)、遇到new Promise,當即執行;then回調函數放入Event table中註冊,而後被分發到微任務事件隊列中。記爲then3。輸出: 11

此時第三輪事件循環宏任務結束,下表是第三輪事件循環宏任務結束時各Event Queue的狀況

- 宏任務事件隊列 微任務事件隊列
第一輪事件循環(已結束)
第二輪事件循環(已結束)
第三輪事件循環(未開始) (宏任務已結束) process三、then3

能夠看到第二輪事件循環宏任務結束後微任務事件隊列中還有兩個事件待執行,所以這兩個事件會被推入主線程,而後執行

d)、執行process3。輸出:10
e)、執行then3。輸出:12

四、js事件循環總結

1)、js執行從最早進入任務隊列的宏任務開始,一般是總體<scirpt>代碼
2)、宏任務隊列事件所有執行完畢後,檢查微任務隊列是否有事件,有則執行,直到沒有事件爲止
3)、更新render(每一次事件循環,瀏覽器均可能會去更新渲染)
4)、重複以上步驟

五、參考文章

https://juejin.im/post/59e85e...
https://segmentfault.com/a/11...

相關文章
相關標籤/搜索