JavaScript代碼是一行一行按順序執(zhí)行的坏晦?
是萝玷,不是?
準確的說同步的代碼確實是按順序執(zhí)行的昆婿,然鵝異步代碼又是怎么執(zhí)行的球碉,那么我們今天重點討論的話題就是JS的異步任務是如何執(zhí)行的?
事件循環(huán)
了解JS的小伙伴都知道JS是單線程的仓蛆,可是我們經(jīng)常會聽到異步執(zhí)行任務睁冬,那么只有一個線程的JS是怎么來實現(xiàn)異步任務呢,想象一下只有一個主線程的工作模式看疙,如果有一個需要花費很長時間的任務占據(jù)著主線程豆拨,那么后面的任務都需要等待,這將給用戶帶來很差的體驗狼荞,所以JS把耗時的任務交給另外一個線程來處理(JS執(zhí)行的宿主環(huán)境:如瀏覽器會創(chuàng)建一個專門處理異步任務的線程辽装,以下簡稱瀏覽器)。瀏覽器的該線程會維護一個事件隊列相味,等瀏覽器執(zhí)行完任務后拾积,會把注冊的回調函數(shù)推入事件隊列中,當JS的主線程中的執(zhí)行棧為空丰涉,便會去讀取事件隊列中的回調函數(shù)拓巧,進入主線程執(zhí)行,上述過程會不斷的重復一死,也就是事件循環(huán)
,通過這樣的工作機制才能使得異步任務很好的完成肛度,
圖上所描述的任務執(zhí)行流程可以通過下面的代碼加深理解
console.log(1);
setTimeout(function(){console.log(2);}, 0);
console.log(3);
上面這段代碼的執(zhí)行流程:
- 全局執(zhí)行環(huán)境入棧,console入棧投慈,打印1承耿,出棧,
- 遇到setTimeout交給宿主環(huán)境線程執(zhí)行伪煤,注冊回調函數(shù)
- 繼續(xù)執(zhí)行console加袋,接著出棧,
- setTimeout執(zhí)行完后抱既,回調函數(shù)進入event queue职烧,
*此時, 執(zhí)行棧空閑后蚀之,主線程從Event Queue中讀取回調函數(shù)并執(zhí)行
異步任務的類型
如果我們對異步任務進行更精細的區(qū)分蝗敢,可以分為以下兩種且不同類型的任務也存在優(yōu)先級:
- macro-task(宏任務):主代碼塊 > setTimeout/setInterval
- micro-task(微任務):process.nextTick > Promise > async await
不同類型的異步任務會進入不同的事件隊列
事件循環(huán)是從宏任務開始,執(zhí)行完后足删,看是否有可執(zhí)行的微任務寿谴,如果有則執(zhí)行,執(zhí)行完又開始新的宏任務壹堰;無則執(zhí)行新的宏任務拭卿。
console.log('start');
setTimeout(function() {
console.log('setTimeout');
process.nextTick(function() {
console.log('node');
})
})
async function asyncFun1(){
console.log('asyncFun1');
await asyncFun2();
console.log('asyncFun1 callback');
}
async function asyncFun2(){
console.log('asyncFun2');
}
asyncFun1();
new Promise(function(resolve) {
console.log('promise');
resolve();
}).then(function() {
console.log('then');
})
console.log('end');
//執(zhí)行結果:start—>asyncFun1—>asyncFun2—>promise—>end—>then—>asyncFun1 callback—>setTimeout—>node
第一次循環(huán):
- 整體代碼塊被作為第一個宏任務進行主線程骡湖,遇到第一行的console贱纠,輸出
start
- 遇到setTimeout,回調函數(shù)被放在宏任務Event Queue中响蕴,記為setTimeout1
- 遇到asyncFun1函數(shù)谆焊,執(zhí)行輸出
asyncFun1
,再遇到asyncFun2函數(shù)浦夷,輸出asyncFun2
辖试,然后把該函數(shù)的回調放入微任務Event Queue中,記為async1 - 遇到new promise直接執(zhí)行劈狐,輸出
promise
罐孝,then被放入微任務Event Queue,記為then
當?shù)谝淮问录h(huán)宏任務完成時肥缔,我們發(fā)現(xiàn)還有兩個微任務莲兢,分別執(zhí)行這兩個任務輸出:then ,asyncFun1 callback(微任務執(zhí)行的優(yōu)先級前面有提到
)
宏任務 | 微任務 |
---|---|
setTimeout1 | async1 |
then |
到現(xiàn)在為止一次完整的事件循環(huán)才算結束了,輸出了start—>asyncFun1—>asyncFun2—>promise—>end—>then—>asyncFun1 callback
第二次循環(huán):
- 接著從setTimeout宏任務入手续膳,打印
setTimeout
- 遇到process改艇,把函數(shù)放入微任務Event Loop中,記為process
第二次事件循環(huán)宏任務結束坟岔,還有一個微任務谒兄,執(zhí)行輸出node
,第二次事件循環(huán)輸出:setTimeout—>node
宏任務 | 微任務 |
---|---|
process |
整段代碼的共執(zhí)行了兩次事件循環(huán)社付,完整輸出為:start—>asyncFun1—>asyncFun2—>promise—>end—>then—>asyncFun1 callback—>setTimeout—>node
以上是我對JS異步任務執(zhí)行的理解承疲,如后續(xù)有新的理解和認識,會加以補充~~