js的事件循环机制:同步与异步任务(setTimeout,setInterval)宏任务,微任务(Promise,process.nextTick)...
javascript是單線程,一切javascript版的"多線程"都是用單線程模擬出來的,通過事件循環(huán)(event loop)實(shí)現(xiàn)的異步。
javascript事件循環(huán)
事件循環(huán)中的同步任務(wù),異步任務(wù):
-
同步和異步任務(wù)在不同的執(zhí)行"場所",同步的進(jìn)入主線程,異步的進(jìn)入Event Table執(zhí)行并注冊函數(shù)。
-
當(dāng)指定的異步事情完成時(shí),Event Table會將這個(gè)函數(shù)移入Event Queue。
-
主線程內(nèi)的任務(wù)執(zhí)行完畢為空,會去Event Queue讀取對應(yīng)的函數(shù),推入主線程執(zhí)行。
-
js引擎的monitoring process進(jìn)程會持續(xù)不斷的檢查主線程執(zhí)行棧是否為空,一旦為空,就會去Event Queue那里檢查是否有等待被調(diào)用的函數(shù)。上述過程會不斷重復(fù),也就是常說的Event Loop(事件循環(huán))。
用個(gè)例子說明上述過程:
let data = []; $.ajax({url:www.javascript.com,data:data,success:() => {console.log('發(fā)送成功!');} }) console.log('代碼執(zhí)行結(jié)束');-
ajax(異步任務(wù))進(jìn)入Event Table,注冊回調(diào)函數(shù)success。
-
執(zhí)行console.log('代碼執(zhí)行結(jié)束')。(同步任務(wù)在主線程執(zhí)行)
-
ajax事件完成,回調(diào)函數(shù)success進(jìn)入Event Queue。
-
主線程從Event Queue讀取回調(diào)函數(shù)success并執(zhí)行。
setTimeout和setInterval中的執(zhí)行時(shí)間
console.log('先執(zhí)行這里'); setTimeout(() => {console.log('執(zhí)行啦') },3000);//先執(zhí)行這里
//?...?3s?later 3秒到了,計(jì)時(shí)事件timeout完成,定時(shí)器回調(diào)進(jìn)入Event Queue,主線程執(zhí)行已執(zhí)行完,此時(shí)執(zhí)行定時(shí)器回調(diào)。 //?執(zhí)行啦
?
setTimeout明明寫的延時(shí)3秒,實(shí)際卻5,6秒才執(zhí)行函數(shù),這咋回事?
setTimeout(() => {task() },3000)sleep(10000000)上述代碼在控制臺執(zhí)行task()需要的時(shí)間遠(yuǎn)遠(yuǎn)超過3秒,執(zhí)行過程:
-
task()進(jìn)入Event Table并注冊,計(jì)時(shí)開始。
-
執(zhí)行sleep函數(shù),很慢,非常慢,計(jì)時(shí)仍在繼續(xù)。
-
3秒到了,計(jì)時(shí)事件timeout完成,task()進(jìn)入Event Queue,但是sleep也太慢了吧,還沒執(zhí)行完,只好等著。
-
sleep終于執(zhí)行完了,task()終于從Event Queue進(jìn)入了主線程執(zhí)行。
上述的流程走完,setTimeout這個(gè)函數(shù)是經(jīng)過指定時(shí)間后,把要執(zhí)行的任務(wù)(本例中為task())加入到Event Queue中,又因?yàn)槭菃尉€程任務(wù)要一個(gè)一個(gè)執(zhí)行,如果前面的任務(wù)需要的時(shí)間太久,那么只能等著,導(dǎo)致真正的延遲時(shí)間遠(yuǎn)遠(yuǎn)大于3秒。
重點(diǎn)來了:定時(shí)器的毫秒,不是指過ms秒執(zhí)行一次fn,而是過ms秒,會有fn進(jìn)入Event Queue。
?
setTimeout(fn,0)的含義是,指定某個(gè)任務(wù)在主線程最早可得的空閑時(shí)間執(zhí)行,意思就是不用再等多少秒了,只要主線程執(zhí)行棧內(nèi)的同步任務(wù)全部執(zhí)行完成,棧為空就馬上執(zhí)行。
關(guān)于setTimeout要補(bǔ)充的是,即便主線程為空,0毫秒實(shí)際上也是達(dá)不到的。根據(jù)HTML的標(biāo)準(zhǔn),最低是4毫秒。
對于執(zhí)行順序來說,setInterval會每隔指定的時(shí)間將注冊的函數(shù)置入Event Queue,如果前面的任務(wù)耗時(shí)太久,那么同樣需要等待。一旦setInterval的回調(diào)函數(shù)fn執(zhí)行時(shí)間超過了延遲時(shí)間ms,那么就完全看不出來有時(shí)間間隔了。
宏任務(wù)和微任務(wù)
除了廣義的同步任務(wù)和異步任務(wù),我們對任務(wù)有更精細(xì)的定義:
-
macro-task(宏任務(wù)):包括整體代碼script,setTimeout,setInterval
-
micro-task(微任務(wù)):Promise,process.nextTick
不同類型的任務(wù)會進(jìn)入對應(yīng)的Event Queue,比如setTimeout和setInterval會進(jìn)入相同的Event Queue。
在宏任務(wù)和微任務(wù)概念中的事件循環(huán)機(jī)制:
主任務(wù)(宏任務(wù))完——所有微任務(wù)——宏任務(wù)(找到宏任務(wù)其中一個(gè)任務(wù)隊(duì)列執(zhí)行,其中如果又有微任務(wù),該任務(wù)隊(duì)列執(zhí)行完就執(zhí)行微任務(wù))——宏任務(wù)中另外一個(gè)任務(wù)隊(duì)列(里面偶微任務(wù)就再執(zhí)行微任務(wù))。
?
總的來說就是在宏任務(wù)和微任務(wù)之間來回切。下面列子執(zhí)行過程:
第一輪:主線程輸出:【1,7】,添加宏任務(wù)【set1,set2】,添加微任務(wù)【6,8】。執(zhí)行完主線程,然后執(zhí)行微任務(wù)輸出【6,8】
第二輪:執(zhí)行宏任務(wù)其中一個(gè)任務(wù)隊(duì)列set1:輸出【2,4】,執(zhí)行任務(wù)的過程,碰到有微任務(wù),所以在微任務(wù)隊(duì)列添加輸出【3,5】的微任務(wù),在set1宏任務(wù)執(zhí)行完就執(zhí)行該微任務(wù),第二輪總輸出:【2,4,3,5】
第三輪:執(zhí)行任務(wù)另一個(gè)任務(wù)隊(duì)列set2:輸出【9,11】,執(zhí)行任務(wù)的過程,碰到有微任任務(wù),所以在微任務(wù)隊(duì)列添加輸出【10,12】的微任務(wù),在set2宏任務(wù)執(zhí)行完就執(zhí)行該微任務(wù),第三輪總輸出:【9,11,10,12】
整段代碼,共進(jìn)行了三次事件循環(huán),完整的輸出為1,7,6,8,2,4,3,5,9,11,10,12。(請注意,node環(huán)境下的事件監(jiān)聽依賴libuv與前端環(huán)境不完全相同,輸出順序可能會有誤差)
?console.log('1'); //第一輪主線程【1】setTimeout(function()?{ //碰到set異步,丟入宏任務(wù)隊(duì)列【set1】:我將它命名為set1
????console.log('2');//第二輪宏任務(wù)執(zhí)行,輸出【2】
????process.nextTick(function()?{//第二輪宏任務(wù)執(zhí)行,碰到process,丟入微任務(wù)隊(duì)列,【3】
????????console.log('3');
????})
????new?Promise(function(resolve)?{//第二輪宏任務(wù)執(zhí)行,輸出【2,4】
????????console.log('4');
????????resolve();
????}).then(function()?{
????????console.log('5')//第二輪宏任務(wù)執(zhí)行,碰到then丟入微任務(wù)隊(duì)列,【3,5】
????})
})
process.nextTick(function()?{ //碰到process,丟入微任務(wù)隊(duì)列【6】
????console.log('6'); //第一輪微任務(wù)執(zhí)行
})
new?Promise(function(resolve)?{
????console.log('7');?//new的同時(shí)執(zhí)行代碼,第一輪主線程此時(shí)輸出【1,7】
????resolve();
}).then(function()?{
????console.log('8') //第一輪主線程中promise的then丟入微任務(wù)隊(duì)列,此時(shí)微任務(wù)隊(duì)列為【6,8】。當(dāng)?shù)谝惠單⑷蝿?wù)執(zhí)行,順序輸出【6,8】
})
setTimeout(function()?{ //碰到set異步丟入宏任務(wù)隊(duì)列,此時(shí)宏任務(wù)隊(duì)列【set1.set2】:我將它命名為set2
????console.log('9');//第三輪宏任務(wù)執(zhí)行,輸出【9】
????process.nextTick(function()?{ //第三輪宏中執(zhí)行過程中添加到微任務(wù)【10】
????????console.log('10');
????})
????new?Promise(function(resolve)?{
????????console.log('11');//第三輪宏任務(wù)執(zhí)行,宏任務(wù)累計(jì)輸出【9,11】
????????resolve();
????}).then(function()?{
????????console.log('12')?//第三輪宏中執(zhí)行過程中添加到微任務(wù)【10,12】
????})
})
?
參考文章:
這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制
10分鐘理解JS引擎的執(zhí)行機(jī)制
從瀏覽器多進(jìn)程到JS單線程,JS運(yùn)行機(jī)制最全面的一次梳理
前端基礎(chǔ)進(jìn)階(十二):深入核心,詳解事件循環(huán)機(jī)制
轉(zhuǎn)載于:https://www.cnblogs.com/yaoyao-sun/p/10475689.html
總結(jié)
以上是生活随笔為你收集整理的js的事件循环机制:同步与异步任务(setTimeout,setInterval)宏任务,微任务(Promise,process.nextTick)...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【《Objective-C基础教程 》笔
- 下一篇: 大数据之Linux常用命令