1. 程式人生 > >JavaScript事件輪詢、微任務和巨集任務

JavaScript事件輪詢、微任務和巨集任務

JavaScript是單執行緒語言,也就是說同一個事件只能做一件事。雖然HTML5提出了Web Worker,允許JavaScript指令碼建立多個執行緒,但是子執行緒完全受主執行緒控制,且不得操作DOM和BOM。所以,依然沒有改變JavaScript是單執行緒的本質。
為了解決單執行緒導致的執行緒等待資源,cpu空閒,而其他任務一直等待的問題。將所有的任務分為兩種,一種是同步任務,一種是非同步任務。同步任務指的是,在主執行緒上排隊執行的任務,只有前一個任務執行完畢,才能執行下一個任務。非同步任務指的是,不進入主執行緒,而進入“任務佇列”的任務,自由“任務佇列”通知主執行緒,某個非同步任務可以執行了,該任務才會進入主執行緒執行。
主任務和任務佇列示意圖:
在這裡插入圖片描述


執行過程:
(1)所有的同步任務都在主執行緒上指向,形成一個執行棧
(2)主執行緒之外,還存在一個“任務佇列”。只要非同步任務有了執行結果,就在“任務佇列”之中放置一個事件。
(3)一旦“執行棧”中的所有同步任務執行完畢,系統就會讀取“任務佇列”,將可執行的任務放在主執行緒執行。任務佇列是一個先進先出的資料結構,排在前面的事件,優先被主執行緒讀取。
(4)主執行緒不短重複上面的第三步。
只要主執行緒空了,就會去讀取“任務佇列”。
Event Loop(事件輪詢)
主執行緒從“任務佇列”中讀取事件,這個過程是迴圈不斷的,所以整個過程的這種執行機制又稱為Event Loop(事件迴圈)。
在這裡插入圖片描述
除了放置非同步任務的佇列,“任務佇列還放置定時器”,即指定某些程式碼在多長時間之後執行。定時器功能的主要由setTimeout()和setInterval()這兩個函式執行。setTimeout()只執行一次,setInterval()反覆執行。Node規定,process.nextTick和Promise的回撥函式,追加在本輪迴圈,即同步任務一旦執行完成,就開始執行它們。而setTimeout、setInterval、setImmediate的回撥函式,追加在次輪迴圈。
除了廣義的同步任務和非同步的任務,更精細的定義為:
macro-task(巨集任務):包括整體程式碼script、setTimeout,setInterval
micro-task(微任務):Promise、process.nextTick
下面程式碼可以幫助理解上面的內容:

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') }) })

js程式碼都是從上到下,一行一行指向,首先遇到第一行console.log(‘1’);執行輸出1,然後第二行setTimeout非同步任務,放入任務佇列。下面遇到promise.nextTick是微任務放到本輪迴圈的結尾,之後遇到new Promise直接指向輸出7,then被放到本輪迴圈的結尾,接著執行又遇到的setTimeout放到任務佇列,本輪程式碼執行完,開始依次執行本輪結尾的程式碼,輸出6,8。然後主執行緒的任務執行完畢,無任務佇列中取出一個setTimeout放入主執行緒開始執行,輸出2,然後遇到process.nextTick,放到本輪迴圈的結尾,執行new Promise輸出4,then放入本輪迴圈結尾,主執行緒程式碼執行完,開始執行本輪結尾輸出3,5。然後再去任務佇列中取第二個setTimeout執行輸出9,11,10,12。
所以輸出的順序為1,7,6,8,2,4,3,5,9,11,10,12。
請注意,node環境下的事件監聽依賴libuv與前端環境不完全相同,輸出順序可能會有誤差。

上述內容來源於以下兩篇內容,本人只整理了自己的盲區,有些內容講的不是很清楚,具體可查閱:
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
https://juejin.im/post/59e85eebf265da430d571f89