javascript執(zhí)行機(jī)制是基于事件循環(huán)的并發(fā)式的,事件循環(huán)負(fù)責(zé)處理代碼,收集和處理事件以及執(zhí)行隊(duì)列中的子任務(wù)
棧(stack)
js運(yùn)行時(shí)形成一個(gè)執(zhí)行棧,
function foo(b) {
let a = 10;
return a + b + 11;
}
function bar(x) {
let y = 3;
return foo(x * y);
}
console.log(bar(7)); // 返回 42
當(dāng)調(diào)用 bar 時(shí)氛改,第一個(gè)幀被創(chuàng)建并壓入棧中,幀中包含了 bar 的參數(shù)和局部變量。 當(dāng) bar 調(diào)用 foo 時(shí)佩憾,第二個(gè)幀被創(chuàng)建并被壓入棧中沸停,放在第一個(gè)幀之上嵌巷,幀中包含 foo 的參數(shù)和局部變量衣屏。當(dāng) foo 執(zhí)行完畢然后返回時(shí)施绎,第二個(gè)幀就被彈出棧(剩下 bar 函數(shù)的調(diào)用幀 )耕赘。當(dāng) bar 也執(zhí)行完畢然后返回時(shí)骄蝇,第一個(gè)幀也被彈出,棧就被清空了操骡。
堆(heap)
對(duì)象被分配在堆中九火,堆是一個(gè)用來表示一大塊(通常是非結(jié)構(gòu)化的)內(nèi)存區(qū)域的計(jì)算機(jī)術(shù)語赚窃。
隊(duì)列(queue)
一個(gè) JavaScript 運(yùn)行時(shí)包含了一個(gè)待處理消息的消息隊(duì)列。每一個(gè)消息都關(guān)聯(lián)著一個(gè)用以處理這個(gè)消息的回調(diào)函數(shù)岔激。
setTimeout(function(){console.log('時(shí)間一到,請(qǐng)執(zhí)行改回調(diào)函數(shù)')},1000)
在 事件循環(huán) 期間的某個(gè)時(shí)刻勒极,運(yùn)行時(shí)會(huì)從最先進(jìn)入隊(duì)列的消息開始處理隊(duì)列中的消息。被處理的消息會(huì)被移出隊(duì)列虑鼎,并作為輸入?yún)?shù)來調(diào)用與之關(guān)聯(lián)的函數(shù)辱匿。正如前面所提到的,調(diào)用一個(gè)函數(shù)總是會(huì)為其創(chuàng)造一個(gè)新的棧幀炫彩。
事件循環(huán)(event loop)
queue.waitForMessage() 會(huì)同步地等待消息到達(dá)(如果當(dāng)前沒有任何消息等待被處理)
while (queue.waitForMessage()) {
queue.processNextMessage();
}
特點(diǎn)
- 單線程
每一個(gè)消息完整地執(zhí)行后匾七,其它消息才會(huì)被執(zhí)行
缺點(diǎn):當(dāng)一個(gè)消息需要太長(zhǎng)時(shí)間才能處理完畢時(shí),Web應(yīng)用程序就無法處理與用戶的交互江兢,例如點(diǎn)擊或滾動(dòng)
2.永不阻塞
JavaScript的事件循環(huán)模型與許多其他語言不同的一個(gè)非常有趣的特性是乐尊,它永不阻塞。 處理 I/O 通常通過事件和回調(diào)來執(zhí)行划址,所以當(dāng)一個(gè)應(yīng)用正等待一個(gè) IndexedDB 查詢返回或者一個(gè) XHR 請(qǐng)求返回時(shí)扔嵌,它仍然可以處理其它事情,比如用戶輸入夺颤。
總結(jié)一下事件循環(huán)的機(jī)制:
(1) 所有任務(wù)在執(zhí)行棧上執(zhí)行,
(2) 綁定事件和異步事件(消息)放置于任務(wù)隊(duì)列
(3) 執(zhí)行棧執(zhí)行完畢,一直詢問任務(wù)隊(duì)列是否有消息需要執(zhí)行,如果有就將關(guān)聯(lián)的回調(diào)函數(shù)放置于執(zhí)行棧,準(zhǔn)備執(zhí)行
宏任務(wù)(macrotask)和微任務(wù)(mircrotask)
異步任務(wù)細(xì)分為宏任務(wù)和微任務(wù),當(dāng)一個(gè)宏任務(wù)執(zhí)行完痢缎,會(huì)在渲染前,將執(zhí)行期間所產(chǎn)生的所有微任務(wù)都執(zhí)行完
宏任務(wù) -> 微任務(wù) -> GUI渲染 -> 宏任務(wù) -> ...
宏任務(wù):
- 主代碼塊
- setTimeout
- setTimeInterval
- setImmediate()-node
- requestAnimationFrame -瀏覽器
- postMessage
- I/O
- UI交互事件
微任務(wù): - process.nextTick ()-Node
- Promise.then()
- catch
- finally
- Object.observe
- MutationObserver
注意點(diǎn)
- 瀏覽器會(huì)先執(zhí)行一個(gè)宏任務(wù)世澜,緊接著執(zhí)行當(dāng)前執(zhí)行棧產(chǎn)生的微任務(wù)独旷,再進(jìn)行渲染,然后再執(zhí)行下一個(gè)宏任務(wù)
- 微任務(wù)和宏任務(wù)不在一個(gè)任務(wù)隊(duì)列
總結(jié)
*執(zhí)行主線程,遇到異步置入任務(wù)隊(duì)列,并在任務(wù)隊(duì)列根據(jù)宏任務(wù)和微任務(wù)進(jìn)行區(qū)分
*執(zhí)行棧完成,查看任務(wù)隊(duì)列,首先查看宏任務(wù)隊(duì)列有沒有要執(zhí)行的任務(wù),沒有就過,有就執(zhí)行
- 每個(gè)宏任務(wù)執(zhí)行完都要查看微任務(wù)隊(duì)列,有沒有要執(zhí)行的任務(wù),沒有就過,有就執(zhí)行,直到微任務(wù)隊(duì)列為空
測(cè)試:
function test() {
console.log(1)
setTimeout(function () { // timer1
console.log(2)
}, 1000)
}
test();
setTimeout(function () { // timer2
console.log(3)
})
new Promise(function (resolve) {
console.log(4)
setTimeout(function () { // timer3
console.log(5)
}, 100)
resolve()
}).then(function () {
setTimeout(function () { // timer4
console.log(6)
}, 0)
console.log(7)
})
console.log(8)
結(jié)合我們上述的JS運(yùn)行機(jī)制再來看這道題就簡(jiǎn)單明了的多了
JS是順序從上而下執(zhí)行
執(zhí)行到test()寥裂,test方法為同步嵌洼,直接執(zhí)行,console.log(1)打印1
test方法中setTimeout為異步宏任務(wù)封恰,回調(diào)我們把它記做timer1放入宏任務(wù)隊(duì)列
接著執(zhí)行麻养,test方法下面有一個(gè)setTimeout為異步宏任務(wù),回調(diào)我們把它記做timer2放入宏任務(wù)隊(duì)列
接著執(zhí)行promise诺舔,new Promise是同步任務(wù)鳖昌,直接執(zhí)行,打印4
new Promise里面的setTimeout是異步宏任務(wù)低飒,回調(diào)我們記做timer3放到宏任務(wù)隊(duì)列
Promise.then是微任務(wù)许昨,放到微任務(wù)隊(duì)列
console.log(8)是同步任務(wù),直接執(zhí)行褥赊,打印8
主線程任務(wù)執(zhí)行完畢糕档,檢查微任務(wù)隊(duì)列中有Promise.then
開始執(zhí)行微任務(wù),發(fā)現(xiàn)有setTimeout是異步宏任務(wù)拌喉,記做timer4放到宏任務(wù)隊(duì)列
微任務(wù)隊(duì)列中的console.log(7)是同步任務(wù)速那,直接執(zhí)行俐银,打印7
微任務(wù)執(zhí)行完畢,第一次循環(huán)結(jié)束
檢查宏任務(wù)隊(duì)列琅坡,里面有timer1、timer2残家、timer3榆俺、timer4,四個(gè)定時(shí)器宏任務(wù)坞淮,按照定時(shí)器延遲時(shí)間得到可以執(zhí)行的順序茴晋,即Event Queue:timer2、timer4回窘、timer3诺擅、timer1,依次拿出放入執(zhí)行棧末尾執(zhí)行 (插播一條:瀏覽器 event loop 的 Macrotask queue啡直,就是宏任務(wù)隊(duì)列在每次循環(huán)中只會(huì)讀取一個(gè)任務(wù))
執(zhí)行timer2烁涌,console.log(3)為同步任務(wù),直接執(zhí)行酒觅,打印3
檢查沒有微任務(wù)撮执,第二次Event Loop結(jié)束
執(zhí)行timer4,console.log(6)為同步任務(wù)舷丹,直接執(zhí)行抒钱,打印6
檢查沒有微任務(wù),第三次Event Loop結(jié)束
執(zhí)行timer3颜凯,console.log(5)同步任務(wù)谋币,直接執(zhí)行,打印5
檢查沒有微任務(wù)症概,第四次Event Loop結(jié)束
執(zhí)行timer1蕾额,console.log(2)同步任務(wù),直接執(zhí)行彼城,打印2
檢查沒有微任務(wù)凡简,也沒有宏任務(wù),第五次Event Loop結(jié)束
結(jié)果:1精肃,4秤涩,8,7司抱,3筐眷,6,5习柠,2
console.log('start')
setTimeout(function(){
console.log('宏任務(wù)1號(hào)')
})
Promise.resolve().then(function(){
console.log('微任務(wù)0號(hào)')
})
console.log('執(zhí)行棧執(zhí)行中')
setTimeout(function(){
console.log('宏任務(wù)2號(hào)')
Promise.resolve().then(function(){
console.log('微任務(wù)1號(hào)')
})
},500)
setTimeout(function(){
console.log('宏任務(wù)3號(hào)')
setTimeout(function(){
console.log('宏任務(wù)4號(hào)')
Promise.resolve().then(function(){
console.log('微任務(wù)2號(hào)')
})
},500)
Promise.resolve().then(function(){
console.log('微任務(wù)3號(hào)')
})
},600)
console.log('end')