背景
javascript
語言的一大特點就是單線程痕钢,也就是說同一時間只能做一件事冬筒。所以所有任務(wù)需要排隊,一個任務(wù)結(jié)束以后開始另一個任務(wù)大州。
HTML5
提出Web Worker
標準从祝,允許javascript
腳本創(chuàng)建多個線程襟己,但是子線程完全受主線程控制,且不得操作DOM
牍陌。
于是所有任務(wù)可以分為兩種擎浴,一種是同步任務(wù),一種是異步任務(wù)毒涧。運行機制如下:
- 所有同步任務(wù)都在主線程上執(zhí)行贮预,形成一個執(zhí)行棧。
- 主線程之外契讲,還存在一個任務(wù)隊列仿吞。只要異步任務(wù)有了運行結(jié)果,就在任務(wù)隊列之中防止一個事件捡偏。
- 一旦執(zhí)行棧中所有同步任務(wù)執(zhí)行完畢唤冈,系統(tǒng)就會讀取任務(wù)隊列,看看里面有哪些事件霹琼。那對應(yīng)的異步任務(wù)务傲,于是結(jié)束等待狀態(tài)凉当,進入執(zhí)行棧枣申,開始執(zhí)行。
- 主線程不斷重復(fù)上面的第三步看杭。
什么是Event Loop忠藤?為啥要弄懂Event Loop?
Event Loop
也就是事件循環(huán)楼雹,是指瀏覽器或者Node
解決javascript
單線程運行時不會阻塞的一種機制模孩,這種運行方式稱為“異步模式”。
- 增加自己的技術(shù)深度贮缅,了解
javascript
的運行機制榨咐。 - 各大互聯(lián)網(wǎng)公司的面試,懂得原理谴供,題目任其發(fā)揮块茁。
*** 瀏覽器的Event Loop
是在html5的規(guī)范中明確定義。NodeJS
的Event Loop
是基于libuv實現(xiàn)的。 ***
Event Loop的任務(wù)類型
-
宏任務(wù)(
MacroTack
)
一些異步任務(wù)的回調(diào)會一次進入macro task queue
数焊,等待后續(xù)被調(diào)用永淌,這些異步任務(wù)包括:-
script
全部代碼; -
setTimeout
; -
setInterval
; -
setImmediate
(node的,IE10 支持); -
I/O
佩耳、UI rendering
(瀏覽器獨有); -
requestAnimationFrame
(瀏覽器獨有);
-
-
微任務(wù)(
MicroTask
)
另一些異步任務(wù)的回調(diào)會一次進入micro task queue
遂蛀,等待后續(xù)被調(diào)用,這些異步任務(wù)包括:-
process.nextTick
(node獨有); -
Promise
; -
Object.observe
; -
MutationObserver
;
-
瀏覽器的Event Loop完整的流程
- 執(zhí)行全局
Script
同步代碼干厚,這些同步代碼有一些是同步語句李滴,有一些是異步語句(如setTimeout
就是異步語句); - 全局
Script
代碼執(zhí)行完畢后萍诱,調(diào)用棧Stack
會清空悬嗓; - 從微隊列
microtask queue
中取出位于隊首的回調(diào)任務(wù),放入調(diào)用棧Stack中執(zhí)行裕坊,執(zhí)行完后microtask queue
長度減1包竹; - 繼續(xù)取出位于隊首的任務(wù),放入調(diào)用棧Stack中執(zhí)行籍凝,以此類推周瞎,直到直到把microtask queue中的所有任務(wù)都執(zhí)行完畢。注意饵蒂,如果在執(zhí)行microtask的過程中声诸,又產(chǎn)生了microtask,那么會加入到隊列的末尾退盯,也會在這個周期被調(diào)用執(zhí)行
-
microtask queue
中的所有任務(wù)都執(zhí)行完畢彼乌,此時microtask queue為空隊列,調(diào)用棧Stack也為空渊迁; - 取出宏隊列macrotask queue中位于隊首的任務(wù)慰照,放入Stack中執(zhí)行;
- 執(zhí)行完畢后琉朽,調(diào)用棧Stack為空毒租;
- 重復(fù)第3-7個步驟;
- 重復(fù)第3-7個步驟箱叁;
- ......
重點
- 宏隊列
MacroTack
一次只從隊列中取一個任務(wù)來執(zhí)行墅垮,執(zhí)行完后就去執(zhí)行微任務(wù)隊列中的任務(wù); - 微任務(wù)隊列
MicroTask
中所有的任務(wù)都會被依次取出來執(zhí)行耕漱,直到microtask queue
為空算色;
隨堂測試
console.log(1);
setTimeout(() =>{
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
},0);
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
setTimeout(() => {
console.log(6);
},0)
console.log(7);
答案:
分析:
step 1
console.log(1)
Stack Queue: [console]
Macrotask Queue: []
Microtask Queue: []
打印結(jié)果: 1step 2
setTimeout(() =>{
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
Stack Queue: [setTimeout]
Macrotask Queue: [callback1]
Microtask Queue: []
打印結(jié)果: 1
- step 3
new Promise((resolve, reject) => {
console.log(4)
resolve(5)
}).then((data) => {
console.log(data);
})
Stack Queue: [promise]
Macrotask Queue: [callback1]
Microtask Queue: [callback2]
打印結(jié)果: 1 4
- step 4
setTimeout(() => {
console.log(6);
})
Stack Queue: [setTimeout]
Macrotask Queue: [callback1, callback3]
Microtask Queue: [callback2]
打印結(jié)果: 1 4
- step 5
console.log(7);
Stack Queue: [console]
Macrotask Queue: [callback1, callback3]
Microtask Queue: [callback2]
打印結(jié)果: 1 4 7
全部Script
代碼執(zhí)行完了,進入下一個步驟螟够,從microtask queue中依次取出任務(wù)執(zhí)行灾梦,直到microtask queue隊列為空。
step 6
console.log(data)
Stack Queue: [callback2]
Macrotask Queue: [callback1, callback3]
Microtask Queue: []
打印結(jié)果: 1 4 7 5step 7
console.log(2);
Stack Queue: [callback1]
Macrotask Queue: [callback3]
Microtask Queue: []
打印結(jié)果: 1 4 7 5 2
但是,執(zhí)行callback1
的時候又遇到了另一個Promise
斥废,Promise
異步執(zhí)行完后在microtask queue
中又注冊了一個callback4
回調(diào)函數(shù)
- step 8
Promise.resolve().then(() => {
console.log(3)
})
Stack Queue: [promise]
Macrotask Queue: [callback3]
Microtask Queue: [callback4]
打印結(jié)果: 1 4 7 5 2
取出一個宏任務(wù)macrotask執(zhí)行完畢椒楣,然后再去微任務(wù)隊列microtask queue中依次取出執(zhí)行
- step 9
console.log(3)
Stack Queue: [callback4]
Macrotask Queue: [callback3]
Microtask Queue: []
打印結(jié)果: 1 4 7 5 2 3
微任務(wù)隊列全部執(zhí)行完,再去宏任務(wù)隊列中取第一個任務(wù)執(zhí)行
- step 10
console.log(6)
Stack Queue: [callback3]
Macrotask Queue: []
Microtask Queue: []
打印結(jié)果: 1 4 7 5 2 3 6
以上牡肉,全部執(zhí)行完后捧灰,Stack Queue為空,Macrotask Queue為空统锤,Micro Queue為空
Stack Queue: []
Macrotask Queue: []
Microtask Queue: []
最終打印結(jié)果: 1 4 7 5 2 3 6
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0);
async1().then(
function () {
console.log('async1 then')
}
)
new Promise(function (resolve) {
console.log('promise1');
resolve()
}).then(function () {
console.log('promise2');
});
console.log('script end');
答案:
script start
async1 start
async2
promise1
script end
async1 end
promise2
async1 then
setTimeout
參考地址
JavaScript 運行機制詳解:再談Event Loop - 阮一峰的網(wǎng)絡(luò)日志
帶你徹底弄懂Event Loop_前端學(xué)習(xí) - SegmentFault 思否