js運(yùn)行機(jī)制
event loop事件循環(huán)
- js分為同步任務(wù)和異步任務(wù)潦蝇,所有的同步任務(wù)都在主線程上執(zhí)行
- 另外存在著一個(gè)“任務(wù)隊(duì)列”涉枫,只要異步的任務(wù)有了結(jié)果梧田,便在任務(wù)隊(duì)列里面加入一個(gè)事件
- 當(dāng)主線程的的同步任務(wù)都執(zhí)行完了萌业,執(zhí)行棧清空,這個(gè)時(shí)候會去讀取任務(wù)隊(duì)列乡范,依次把他們?nèi)拥街骶€程執(zhí)行
- 這個(gè)過程不斷循環(huán)配名,就成了js的事件循環(huán)機(jī)制。
所以我們不難理解有的時(shí)候setTimeout(fn, 0)沒有立即執(zhí)行晋辆,它只是被立即加入到任務(wù)隊(duì)列了渠脉,可能那個(gè)時(shí)候主線程還沒有執(zhí)行完畢,所以它要等著瓶佳,等js引擎空閑的時(shí)候再執(zhí)行芋膘。
任務(wù)隊(duì)列是由js的事件觸發(fā)線程控制,不是js引擎所控制霸饲,可能js引擎太忙了为朋,瀏覽器又單獨(dú)開了一個(gè)線程
宏任務(wù)(macrotask) 、微任務(wù)(microtask)
如果我們遇到諸如類似的,那么誰先執(zhí)行呢厚脉,這就引出了宏任務(wù)习寸,微任務(wù)的概念。
console.log(1)
Promise.resolve().then(() => {
console.log('promise')
})
setTimeout(() => {
console.log('settimeout')
})
宏任務(wù)
每次執(zhí)行棧執(zhí)行的代碼就是一個(gè)宏任務(wù)傻工,包括把任務(wù)隊(duì)列的事件加入到主線程的執(zhí)行霞溪,一個(gè)宏任務(wù)的執(zhí)行相當(dāng)于一個(gè)task孵滞,瀏覽器會在一個(gè)task結(jié)束之后對頁面進(jìn)行重新渲染。
常見的macrotask:setTimeout鸯匹、setInterval坊饶、setImmediate、I/O等
微任務(wù)
microtask又是個(gè)什么東東殴蓬?在一個(gè)task執(zhí)行之后緊跟著執(zhí)行的東西匿级,也就是一個(gè)task執(zhí)行完畢,接下來會把它在執(zhí)行期間產(chǎn)生等所有微任務(wù)都進(jìn)行執(zhí)行科雳,也就是要把微任務(wù)隊(duì)列執(zhí)行清空掉根蟹,接下來瀏覽器開始對頁面進(jìn)行重新渲染。
常見的microtask:Promise.then糟秘、process.nextTick、MutaionObserver
小結(jié)
結(jié)合上宏任務(wù)球散、微任務(wù)再來總結(jié)一下js的事件循環(huán)機(jī)制尿赚。
- 執(zhí)行同步代碼,也就是開始一個(gè)宏任務(wù)的執(zhí)行
- 如果遇到異步蕉堰,等異步任務(wù)有了運(yùn)行結(jié)果后再把他們放到事件隊(duì)列凌净,如果屬于微任務(wù)的話加入微任務(wù)隊(duì)列。
- 宏任務(wù)執(zhí)行完畢屋讶,接下來把這個(gè)task執(zhí)行過程中產(chǎn)生的微任務(wù)依次執(zhí)行
- 微任務(wù)全部執(zhí)行完畢冰寻,瀏覽器開始重新渲染
- 去事件隊(duì)列讀取下一個(gè)宏任務(wù),不斷循環(huán)上面過程皿渗。
// 舉個(gè)栗子??
console.log('1');
setTimeout(function() {
console.log('2');
Promise.resolve().then(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
}, 100)
Promise.resolve().then(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
Promise.resolve().then(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
console.log('13')
第一次事件循環(huán)斩芭,首先打印了1,然后遇到setTimeout乐疆,我們記為setA划乖,setA去外面排隊(duì),接下來Promise.then屬于微任務(wù)挤土,加入微任務(wù)隊(duì)列琴庵。再往下遇到new Promise,直接執(zhí)行打印7仰美,then函數(shù)加入微任務(wù)隊(duì)列迷殿。再繼續(xù)又遇到setTimeout,我們記為setB咖杂,排隊(duì)等著庆寺。然后打印13。所以第一輪事件循環(huán)結(jié)束翰苫,我們直接打印了1止邮,7这橙,13,緊接著依次打印微任務(wù)隊(duì)列里面的6导披,8屈扎。
現(xiàn)在第一輪事件循環(huán)結(jié)束,還有setA,setB等待執(zhí)行撩匕,先執(zhí)行哪個(gè)呢鹰晨?答案是setB。雖然定時(shí)器線程先捕捉到了setA止毕,但是setA延遲時(shí)間是100ms模蜡,而setB是立即執(zhí)行的,setB會在setA之前被加入到事件隊(duì)列扁凛。所以當(dāng)?shù)谝惠喪录h(huán)結(jié)束忍疾,此時(shí)會把setB從事件隊(duì)列拉到執(zhí)行棧中執(zhí)行,第二輪事件循環(huán)開始了谨朝。
第二次循環(huán)首先打印了9卤妒,然后遇到微任務(wù),把10加入到微任務(wù)隊(duì)列字币,往下又遇到了new Promise直接執(zhí)行则披,打印11,then函數(shù)的12被加入到微任務(wù)洗出,本次事件循環(huán)結(jié)束士复。依次打印了9,11翩活,10阱洪,12.
第三輪事件循環(huán)開始,setA所在的宏任務(wù)隅茎,首先打印了2澄峰,然后3被加入到微任務(wù),同樣的辟犀,打印4俏竞,then函數(shù)的5被加入到微任務(wù)隊(duì)列。本輪事件循環(huán)結(jié)束堂竟,依次打印2魂毁,4,3出嘹,5.
最終結(jié)果:1席楚,7,13税稼,6烦秩,8垮斯,9,11只祠,10兜蠕,12,2抛寝,4熊杨,3,5