Javascript作為一種單線程語言削樊,是如何實現(xiàn)異步編程的呢欲险?
相信不少人對Javascript單線程表示懷疑:為何單線程可以實現(xiàn)異步操作呢?其實Javascript確實是單線程的(我們不妨把這個線程稱作主線程)钦听,但它實現(xiàn)異步操作的方式確實借助了瀏覽器的其他線程的幫助椿争。那其他線程是怎么幫助Javascript主線程來實現(xiàn)異步的呢?答案就是任務隊列(task queue)和事件循環(huán)(event loop)缀棍。
一宅此、任務隊列
首先,作為單線程語言爬范,在Javascript中定義的任務都會在主線程中執(zhí)行父腕。但是并不是每個任務都會立刻執(zhí)行,而這種不立刻執(zhí)行的任務我們稱作異步任務青瀑。相反璧亮,那些立刻執(zhí)行的任務我們把它們稱作同步任務。而這些異步任務都會交給瀏覽器的其他線程去執(zhí)行斥难,但是主線程需要了解這些異步任務執(zhí)行的狀態(tài)枝嘶,才方便進行下一步操作。
打個比方哑诊,主線程準備做飯群扶,所以下達一個異步任務去買菜,異步任務買完菜之后得告訴主線程:“我買完菜啦”镀裤,這個時候主線程才好開始做飯竞阐。
而我們知道因為Javascript是單線程,所以上述的“下一步操作”沒法直接定義在主函數(shù)里(不然就被當做同步任務直接執(zhí)行了)暑劝,那這些應該定義在哪里呢骆莹?答案就是異步任務的回調(diào)函數(shù)中。在Javascript異步機制中铃岔,任務隊列就是用來維護異步任務回調(diào)函數(shù)的隊列汪疮。這樣一個隊列用來存放這些回調(diào)函數(shù)峭火,它們會等到主線程執(zhí)行完所有的同步函數(shù)之后按照先進先出的方式挨個執(zhí)行。那么執(zhí)行完任務隊列之后呢智嚷?Javascript主線程就執(zhí)行完畢了嗎卖丸?當然不是,不然網(wǎng)頁加載完畢之后盏道,誰來處理后續(xù)與用戶的交互事件(比如點擊事件)呢稍浆?
二、事件循環(huán)
我們通過上圖來更加形象的了解Javascript的異步機制猜嘱。
執(zhí)行同步任務 -> 檢查任務隊列中是否有任務 -> [有如果則執(zhí)行] -> 檢查任務隊列中是否有任務 -> [有如果則執(zhí)行] -> ......
可見主線程在執(zhí)行完同步任務之后衅枫,會無限循環(huán)地去檢查任務隊列中是否有新的“任務”,如果有則執(zhí)行朗伶。而這些任務包括我們在異步任務中定義的回調(diào)函數(shù)弦撩,也包括用戶交互事件的回調(diào)函數(shù)。通過事件循環(huán)论皆,Javascript不僅很好的處理了異步任務益楼,也很好的完成了與用戶交互事件的處理。因為在完成異步任務的回調(diào)函數(shù)之后点晴,任務隊列中的任務都是由事件所產(chǎn)生的感凤,因此我們也把上述的循環(huán)過程叫做事件循環(huán)。
三粒督、異步機制實踐
console.log('定時器去買菜吧')
setTimeout(function(){
console.log('菜買完了陪竿,主線程去做菜吧')
}, 0)
console.log('你先去買菜,我先看個世界杯')
在瀏覽器中執(zhí)行上述代碼屠橄,興許能更好地理解Javascript的異步機制族跛。
四、宏任務與微任務
宏任務:script锐墙、setTimeout庸蔼、setInterval、setImmediate贮匕、I/O、UI rendering
微任務:Promise(原生)花枫、process.nextTick
執(zhí)行順序:
事件循環(huán)的順序刻盐,決定js代碼的執(zhí)行順序。進入整體代碼(宏任務)后劳翰,開始第一次循環(huán)敦锌。接著執(zhí)行所有的微任務。然后再次從宏任務開始佳簸,找到其中一個任務隊列執(zhí)行完畢乙墙,再執(zhí)行所有的微任務颖变。
觀察者優(yōu)先級:
idle觀察者 > I/O觀察者 > check觀察者。
idle觀察者:process.nextTick
I/O觀察者:一般性的I/O回調(diào)听想,如網(wǎng)絡腥刹,文件,數(shù)據(jù)庫I/O等
check觀察者:setImmediate汉买,setTimeout
setTimeout(() => {
console.log('timeout')
})
new Promise(function(resolve) {
console.log('promise')
resolve()
}).then(() => {
console.log('then')
})
console.log('main')
執(zhí)行順序是:
promise衔峰、main、then蛙粘、timeout
五垫卤、總結
總而言之,Javascript單線程的背后有瀏覽器的其他線程為其完成異步服務出牧,這些異步任務為了和主線程通信穴肘,通過將回調(diào)函數(shù)推入到任務隊列等待執(zhí)行。主線程所做的就是執(zhí)行完同步任務后舔痕,通過事件循環(huán)评抚,不斷地檢查并執(zhí)行任務隊列中回調(diào)函數(shù)。