javascript中的Event-Loop
在之前的一篇文章中我們解釋了一下為什么JavaScript要設(shè)計(jì)成單線程以及這門(mén)語(yǔ)言的任務(wù)隊(duì)列的概念,這也幫助了我們簡(jiǎn)單了解了這門(mén)語(yǔ)言的運(yùn)行機(jī)制,那么今天我們就談?wù)勅蝿?wù)隊(duì)列相關(guān)的概念英古!
事件和回調(diào)函數(shù)
任務(wù)隊(duì)列其實(shí)是事件的一個(gè)隊(duì)列绊寻,也可以理解為消息隊(duì)列,當(dāng)IO設(shè)備完成一個(gè)任務(wù)的時(shí)候喘蟆,就會(huì)在任務(wù)隊(duì)列中添加一個(gè)事件季惯,用來(lái)表示當(dāng)前任務(wù)已經(jīng)執(zhí)行完了咬最,可以進(jìn)入執(zhí)行棧(也就是之前講過(guò)的主線程隊(duì)列)了翎嫡,主線程讀取任務(wù)隊(duì)列也就是讀取有哪些事件!
任務(wù)隊(duì)列中的事件除了IO設(shè)備之外永乌,還有用戶點(diǎn)擊惑申、鍵盤(pán)事件等,只要指定過(guò)回調(diào)函數(shù)翅雏,這些事件發(fā)生時(shí)就會(huì)進(jìn)入任務(wù)隊(duì)列圈驼,然后等待主線程讀取望几。
回調(diào)函數(shù)(callback)其實(shí)就是被主線程掛起來(lái)的代碼绩脆,主線程執(zhí)行異步任務(wù),其實(shí)就是執(zhí)行回調(diào)函數(shù)橄抹!
任務(wù)隊(duì)列其實(shí)是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)靴迫,也就是說(shuō)排在前面的事件會(huì)被優(yōu)先讀取,當(dāng)主線程的代碼執(zhí)行完楼誓,執(zhí)行棧被清空以后玉锌,就會(huì)立即執(zhí)行任務(wù)隊(duì)列中排在最前面的事件,但是由于定時(shí)器的功能疟羹,因此主守,主線程需要檢查一下執(zhí)行時(shí)間,某些事件只有等到規(guī)定的時(shí)間榄融,才可以返回主線程参淫!
Event Loop
主線程從任務(wù)隊(duì)列讀取事件這個(gè)過(guò)程是循環(huán)不斷的,因此整個(gè)過(guò)程又被稱為Event Loop(事件循環(huán))
setTimeout(() => {
console.log('timeout1');
}, 0)
console.log(1);
setTimeout(() => {
console.log('timeout2');
}, 0)
console.log(2);
// 1 2 timeout1 timeout2
上面代碼中有兩個(gè)定時(shí)器愧杯,定時(shí)器也會(huì)放在任務(wù)隊(duì)列中涎才,因此我們常說(shuō)在js中定時(shí)器可以模擬異步,其實(shí)是js默認(rèn)會(huì)把定時(shí)器放在任務(wù)隊(duì)列民效,前面我們講過(guò)憔维,js先會(huì)執(zhí)行主線程的代碼涛救,稱為執(zhí)行棧,當(dāng)執(zhí)行棧的代碼執(zhí)行結(jié)束业扒,執(zhí)行棧清空之后才會(huì)執(zhí)行任務(wù)隊(duì)列中的代碼, 因此上面的代碼不會(huì)因?yàn)槎〞r(shí)器在前面检吆,而先執(zhí)行定時(shí)器,當(dāng)定時(shí)器的間隔時(shí)間一致時(shí)程储,按照添加順序蹭沛,先進(jìn)任務(wù)隊(duì)列則先執(zhí)行!
NodeJs的Event Loop
NodeJs也是單線程的Event Loop章鲤,但是它區(qū)別于瀏覽器的運(yùn)行環(huán)境摊灭;
在Nodejs中提供了process.nextTice()和setImmediate()兩個(gè)與任務(wù)隊(duì)列有關(guān)的方法;
process.nextTice()就是在當(dāng)前執(zhí)行棧尾部添加任務(wù)败徊,也就是任務(wù)隊(duì)列(所有的異步任務(wù))開(kāi)始之前帚呼;
console.log(1);
setTimeout(function timeout() {
console.log('timeout1');
}, 0);
process.nextTick(function() {
console.log(3);
process.nextTick(function(){
console.log(4);
});
});
setTimeout(function timeout() {
console.log('timeout2');
}, 0);
console.log(2);
// 1 2 3 4 timeout1 timeout2
setImmediate()會(huì)在每一次Event Loop結(jié)束執(zhí)行霞怀,或者說(shuō)下一次Event Loop執(zhí)行之前執(zhí)行
console.log('start');
setTimeout(function() {
console.log('timeout1');
}, 0);
setImmediate(function (){
setImmediate(function() {
console.log(1);
setTimeout(function() {
console.log('timeout3');
}, 0);
setImmediate(function(){
console.log(2);
});
});
setTimeout(function() {
console.log('timeout2');
}, 0);
});
process.nextTick(function() {
console.log('nextTick1');
process.nextTick(function() {
console.log('nextTick2')
})
})
console.log('end');
// start end nextTick1 nextTick2 timeout1 1 timeout2 2 timeout3
上面代碼前三個(gè)輸出結(jié)果在沒(méi)有其他干擾拌汇,就目前代碼欺冀,不用質(zhì)疑锦溪,主線程肯定先執(zhí)行坪稽,接下來(lái)為nextTick阿迈,因?yàn)樗鼤?huì)被放在所有異步執(zhí)行之前毡代,不論是否嵌套(不包括嵌套在其他異步函數(shù)中)笤喳,timeout1在主線程程中被添加到任務(wù)隊(duì)列辜妓,不論它是否在start之后還是end之前枯途,它是區(qū)別于其他函數(shù)的唯一一個(gè)在任務(wù)隊(duì)列頂端的函數(shù),而setImmediate總會(huì)在一個(gè)Event Loop之后執(zhí)行籍滴!
下篇我們我們聊聊任務(wù)隊(duì)列中的微任務(wù)和宏任務(wù)酪夷!