初探 event loop

事件循環

瞭解知識點

  • 線程
  • 執行棧
  • task queue
  • web api
  • macro task
  • micro task

線程

javascript是單線程的語言,
能夠單線程將理解爲只有一條車道,在車道里後面的車在等前面的車經過後,才能經過.
即當前面的程序沒有執行,後面的程序也不能執行.javascript

執行棧

執行棧像"車道",被執行的程序會放入執行棧裏,
但它的執行的順序是,後面進來的程序先執行.前端

示例

視頻源地址vue

task queue

在線程中有不少等待運行的任務(程序,而執行棧只會放入一個任務.
其餘可運行任務會放入任務隊列中.
這裏雖然說是個隊列, 它的執行的順序,不會先進的程序先執行.
每一個event loop都會有一個或多個任務隊列java

web api

javascript是單線程,但也能實現異步,這種實現基與頁面提供不少API,如(setTimeout, ajax, addEventListener ...)
這些都是異步函數,也就是說,運行到異步函數時,
把異步函數裏閉包放入web api裏,等待正確的時機,
web api會把閉包放入task queue裏執行.git

示例

圖片描述

macro task

macro task有setTimeout ,setInterval, setImmediate,requestAnimationFrame,I/O ,UI渲染...
task queue 是由不少個macro task組成的隊列,github

micro task

micro task有Promise, process.nextTick, Object.observe, MutationObserver...
每一個event loop都會有一個micro taskweb

event loop

執行流程ajax


  1. 當執行棧爲null時
  2. 看task queue的第一個macro task是否是null,若是不是取出放入執行棧,若是是跳轉5
  3. 執行棧運行task
  4. 運行完畢,把task設置null,並移出
  5. 執行 micro task隊列
    a. 看micro task隊列第一task個是否是null,若是不是取出放入執行棧,若是是跳轉f
    b. 執行棧運行task
    c. 運行完畢,把task設置null,並移出
    d. 看micro task隊列下一個task是否是null,若是不是跳轉b
    f. 結束micro task隊列執行
  6. 跳轉到1

示例

圖片描述

實戰

<script>// 建立macro task, 由於stack 爲null, 執行
(function test() {
  setTimeout(() => { // 建立macro task, 當前task queue['第一setTimeout閉包']
    console.log(4)
  }, 0);
  new Promise(resolve => {
    console.log(1); // 執行, 控制檯 1
    for(var i = 0; i < 10000; i++) {
      i == 9999 && resolve(); // 建立micro task, 當前micro task queue['第一then閉包']
    }
    console.log(2); // 執行, 控制檯 1 2
  }).then(() => {
    console.log(5);
    return new Promise(resolve => {
        resolve()
    })
  }).then(() => {
      console.log(6)
  });
  console.log(3); /* 執行, 控制檯 1 2 3, 第一個script標籤建立的macro task結束
                     運行micro task queue首個micro task === 第一then閉包
                       執行, 控制檯 1 2 3 5, 
                     並建立了micro task 放入 micro task queue['第二then 閉包']
                     運行micro task queue首個micro task === 第二then 閉包
                     執行, 控制檯 1 2 3 5 6
                  */
})()
</script>

<script>// 建立macro task, 由於stack 爲null, 執行
(function test2() {
  setTimeout(function () { // 建立一個macro task 放入 task queue['第一setTimeout閉包' '第二setTimeout閉包']
    console.log(42)
  }, 0);
  new Promise(function executor (resolve) {
    console.log(12); // 執行 控制檯1 2 3 5 6 12
    for(var i = 0; i < 10000; i++) {
      i == 9999 && resolve(); // 建立micro task, 當前micro task queue['第一then閉包']
    }
    console.log(22); // 執行 控制檯1 2 3 5 6 12 22
  }).then(function() {
    console.log(52);
  });
  console.log(32); /* 執行, 控制檯 1 2 3 5 6 12 22 32, 第二個script標籤建立的macro task結束
                     運行micro task queue首個micro task === 第一then閉包
                       執行, 控制檯 1 2 3 5 6 12 22 32 52, 
                     查看micro task queue首個task爲null, 查看task queue
                     task queue爲空,向web 獲取可執行的任務['第一setTimeout閉包', '第二setTimeout閉包']
                     運行task queue首個 macro task === 第一setTimeout閉包
                     執行, 控制檯 1 2 3 5 6 12 22 32 52 4   
                     查看micro task queue首個task爲null, 查看task queue
                     運行task queue首個 macro task === 第二setTimeout閉包
                     執行, 控制檯 1 2 3 5 6 12 22 32 52 4 42
                  */
})()
</script>

猜測

macro/micro task 進入執行棧時,中間應該會有一個緩存區,
例如api

<script> // 建立macro task,進入執行棧
var a = () => {
    console.log('a');
}

var b = () => {
    console.log('b'); 
}

a(); //這時函數A進入stack,執行,打印出a
b(); //這時函數B進入stack,執行,打印出b
</script>

若是是把整個macro task放入執行棧,
按後進程序先執行的機制, 應該會先打印'b',
但打印的是'a',說明函數b是等函數a執行完後再進入執行棧的,
因此在macro task 會把裏面的函數拆分爲一個執行的隊列,放入執行棧裏.緩存

示例

圖片描述

參考資料

https://github.com/ccforward/cc/issues/48
https://juejin.im/entry/596d78ee6fb9a06bb752475c

Make a friend

做者有一年半的前端開發經驗,比較擅長性能優化和vue,喜歡對各類原理的深究,
喜歡籃球和電影
若是有趣味相投能夠加入微信羣
圖片描述

相關文章
相關標籤/搜索