JavaScript是單線程執行的,即 js 中任務是按順序依次執行的,但若其中一個任務執行時間過長,后續任務會一直等待,造成程序假死。 為了解決這個問題,將任務分為同步任務和異步任務,其中異步任務又分為宏任務和微任務。
同步任務與異步任務:
同步任務:又叫做非耗時任務,指的是在主線程上排隊執行的那些任務
只有前一個任務執行完畢,才能執行后一個任務
異步任務:又叫做耗時任務,異步任務由JavaScript 委托給宿主環境進行執行
當異步任務執行完成后,會通知JavaScript 主線程執行異步任務的回調函數
- 同步任務由JavaScript主線程次序執行
- 異步任務委托給宿主環境執行
- 已完成的異步任務對應的回調函數,會被加入到任務隊列中等待執行
- JavaScript 主線程執行棧被清空后會讀取任務隊列中的回調函數,次序執行
- JavaScript 主線程不斷重復上面的第4步
JavaScript主線程從“任務隊列”中讀取異步任務的回調函數,放到執行棧中依次執行。這個過程是循環不斷的,所以整個的這種運行機制又稱為 EventLoop (事件循環)
宏任務與微任務
宏任務:異步Ajax請求,setTimeout,setInterval,文件操作,new Promise等
微任務:Promise.then、.catch、.finally,process.nextTick等
宏任務與微任務是交替執行的,每次執行完宏任務都會檢查是否有微任務
代碼示例:
console.log('A'); setTimeout(function() { console.log('B'); }, 0); Promise.resolve().then(function() { console.log('C'); }).then(function() { console.log('D'); }); console.log('E');
先執行同步任務打印A和E,再執行異步任務中的微任務,打印C和D,最后執行宏任務打印B
最終打印結果:AECDB
可能有人會問,為什么微任務優先於宏任務執行,其實並不是,這里先執行微任務的原因是,script本身也是一個宏任務,這個宏任務執行結果就是添加各種微任務與宏任務,比如下面代碼中,同步任務執行完成后,會先執行script的宏任務,即添加一個setTimeout的宏任務與一個Promise.then的微任務,這個宏任務執行完成后,就該執行Promise.then的微任務了。並不是微任務優先級大於宏任務,而是這個宏任務執行感知不強,會讓人感覺並沒有執行宏任務,其實是同樣遵循上面流程,執行了宏任務
下面是一個多層次代碼,可進行練習:
1 console.log('1') 2 3 setTimeout(function () { 4 console.log('2') 5 process.nextTick(function () { 6 console.log('3') 7 }) 8 new Promise(function (resolve) { 9 console.log('4') 10 resolve() 11 }).then(function () { 12 console.log('5') 13 }) 14 }) 15 Promise.resolve().then(function () { 16 console.log('6') 17 }) 18 new Promise(function (resolve) { 19 console.log('7') 20 resolve() 21 }).then(function () { 22 console.log('8') 23 }) 24 25 setTimeout(function () { 26 console.log('9') 27 process.nextTick(function () { 28 console.log('10') 29 }) 30 new Promise(function (resolve) { 31 console.log('11') 32 resolve() 33 }).then(function () { 34 console.log('12') 35 }) 36 })
分析:
第一遍:
-
首先執行第一行的同步任務,打印1
- 第三行的setTimeout是異步任務中宏任務,加入宏任務記為setTimeout1
-
下面第15行Promise.then是異步任務中的微任務,加入微任務記為 then
-
第18行new Promise是同步任務,執行第一個log直接打印7,后面的 .then 是微任務,存入微任務中記為 then1
-
第25行setTimeout是異步任務中宏任務,加入宏任務記為 setTimeout2
此時整個程序狀態如下:
第二遍:
- 首先執行微任務區中的任務,then打印6,then1打印8
- 微任務區任務執行完成,再執行宏任務區 setTimeout1 ,打印2,將第5行process微任務放入微任務區記作 process2
- 第8行new Promise為同步任務,立即執行打印4,將后續 .then 微任務 放入微任務區記作 then2
此時整個程序狀態如下:
第三遍:
- 首先執行微任務區,process2,打印3,再執行then2,打印5
- 微任務區執行完成,再去執行宏任務區中的setTimeout2,首先是第26行log直接打印9
- 將第27行process微任務放入微任務區記作 process3
- 第30行new Promise為同步任務,立即執行打印11,將后續 .then 微任務 放入微任務區記作 then3
此時整個程序狀態如下:
最后一遍:
- 執行微任務區process3,打印10
- 執行微任務區then3,打印12
最終打印結果:1,7,6,8,2,4,3,5,9,11,10,12