一链蕊、前言
好久沒有更新簡書了,因?yàn)楣ぷ髅Φ脑颍ㄖ饕菓?- -)馒疹。今天給大家分享一篇js運(yùn)行機(jī)制的東西佳簸,希望對(duì)讀者有幫助。
二颖变、js單線程
setTimeout(() => { console.log('runing') }, 3000);
通常我們會(huì)這樣理解:這段代碼表示1秒后生均,會(huì)執(zhí)行setTimeout里面那個(gè)函數(shù)。
然而腥刹,這種解釋并不準(zhǔn)確疯特, 那應(yīng)該怎么理解呢? 我們先來理解JavaScript的遠(yuǎn)行機(jī)制肛走。
本文最后我會(huì)給出JavaScript的運(yùn)行機(jī)制完整圖漓雅。但在這之前,我想先來給大家解釋幾個(gè)問題朽色。
1. JavaScript為什么是單線程的邻吞?
JavaScript設(shè)計(jì)的初衷是用在瀏覽器中, 那么葫男,我們來想象一下抱冷,如果JavaScript是多線程的話。
必然可以有兩個(gè)進(jìn)程梢褐,process1和process2旺遮,那么這兩個(gè)進(jìn)程可以同時(shí)對(duì)同一個(gè)DOM進(jìn)行操作。如果這個(gè)時(shí)候盈咳,一個(gè)進(jìn)程要?jiǎng)h除這個(gè)DOM耿眉,另一個(gè)進(jìn)程要編輯這個(gè)DOM。啟不是矛盾嘛鱼响。
所以鸣剪,這樣應(yīng)該更好理解,JS為什么是單線程了丈积。
2. JavaScript為什么需要異步筐骇?
單線程為什么需要異步呢?
JavaScript如果不存在異步江滨,而是自上而下執(zhí)行铛纬,這樣的話,假如上一行解析時(shí)間很長唬滑,那么下面的代碼直接就會(huì)被阻塞告唆。這種現(xiàn)象對(duì)于用戶來說莫秆,意味著"卡死"。嚴(yán)重影響用戶流失悔详,這樣解釋好理解吧镊屎,所以JavaScript需要異步處理。
3. 單線程如何實(shí)現(xiàn)異步呢茄螃?
JavaScript竟然需要異步缝驳,那么它是如何實(shí)現(xiàn)異步的呢?
JavaScript是通過事件循環(huán)(event loop)來實(shí)現(xiàn)的归苍,事件循環(huán)機(jī)制也就是今天要說的JavaScript運(yùn)行機(jī)制用狱。
(一)同步任務(wù)和異步任務(wù)
來看一段代碼:
console.log('Number 1');
setTimeout(() => { console.log('Number 2'); }, 3000);
console.log('Number 3');
首先這段代碼輸出結(jié)果是啥?
輸出:1 3 2
其中setTimeout需要延遲一段時(shí)間才去執(zhí)行拼弃,這類代碼就是異步代碼夏伊。
看到這個(gè)結(jié)果,所以通常我們都這么理解JS的執(zhí)行原理:
第一吻氧,判斷JS是同步還是異步溺忧,同步進(jìn)入主線程,異步則進(jìn)入event table盯孙。
第二鲁森,異步任務(wù)在event table中注冊函數(shù),當(dāng)滿足觸發(fā)條件后振惰,被推入event queue(事件隊(duì)列)歌溉。
第三,同步任務(wù)進(jìn)入主線程后一直執(zhí)行骑晶,直到主線程空閑痛垛,才會(huì)去event queue中查看是否有可執(zhí)行的異步任務(wù),如果有就推入主線程桶蛔。
按到這個(gè)邏輯匙头,上面這段實(shí)例代碼,是不是就很好理解了羽圃。1乾胶,3是同步任務(wù)進(jìn)入主要線程,自上而下執(zhí)行朽寞,2是異步任務(wù),滿足觸發(fā)條件后斩郎,推入事件隊(duì)列脑融,等待主線程有空時(shí)調(diào)用。
(二)宏任務(wù)(macro-task)和微任務(wù)(micro-task)
然而缩宜,按照同步和異步任務(wù)來理解JS的運(yùn)行機(jī)制似乎并不準(zhǔn)確肘迎。
來看一段代碼甥温。看看它的輸出順序妓布。
上面這段代碼姻蚓,按同步和異步的理解,輸出結(jié)果是:2匣沼,4狰挡,1,3释涛。因?yàn)?加叁,4是同步任務(wù),按順序在主線程自上而下執(zhí)行唇撬,而1它匕,3是異步任務(wù),按順序在主線程有空后自先而后執(zhí)行窖认。
可事實(shí)輸出并不是這個(gè)結(jié)果豫柬,而是這樣的:2,4扑浸,3轮傍,1。為什么呢首装?來理解一下宏任務(wù)和微任務(wù)创夜。
寵任務(wù):包括整體script代碼,setTimeout仙逻,setInterval驰吓。
微任務(wù):Promise,process.nextTick系奉。
來看原理圖:
嗯檬贰,對(duì),這就是JS的運(yùn)行機(jī)制缺亮。也就是事件循環(huán)翁涤。解釋一下:
第一,執(zhí)行一個(gè)宏任務(wù)(主線程的同步script代碼)萌踱,過程中如果遇到微任務(wù)葵礼,就將其放到微任務(wù)的事件隊(duì)列里。
第二并鸵,當(dāng)前宏任務(wù)執(zhí)行完成后鸳粉,會(huì)查微任務(wù)的事件隊(duì)列,將將全部的微任務(wù)依次執(zhí)行完园担,再去依次執(zhí)行宏任務(wù)事件隊(duì)列届谈。
上面代碼中promise的then是一微任務(wù)枯夜,因此它的執(zhí)行在setTimeout之前。
需要注意的是:在node環(huán)境下艰山,process.nextTick的優(yōu)先級(jí)高于promise湖雹。也就是可以簡單理解為,在宏任務(wù)結(jié)束后會(huì)先執(zhí)行微任務(wù)隊(duì)列中的nextTickQueue部分曙搬,然后才會(huì)執(zhí)行微任務(wù)中的promise部分摔吏。
三、總結(jié)
js在執(zhí)行事件時(shí)织鲸,遇到一個(gè)宏任務(wù)(script(整體代碼舔腾、setTimeout、setInterval搂擦、UI 渲染稳诚、 I/O、postMessage瀑踢、 MessageChannel扳还、setImmediate(Node.js 環(huán)境) ),先去執(zhí)行橱夭;檢測是否存在微任務(wù)氨距,如果有,先放入事件隊(duì)列棘劣,等到宏任務(wù)執(zhí)行完畢后俏让,再去執(zhí)行,直到所有微任務(wù)執(zhí)行完畢茬暇,然后再依次去執(zhí)行宏任務(wù)首昔,就這樣不斷的宏任務(wù)-->微任務(wù)-->宏任務(wù)的循環(huán)。