上篇講異步的時候,提到了同步隊列和異步隊列的說法,其實只是一種形象的稱呼,分別代表主線程中的任務和任務隊列中的任務,那么此篇我們就來詳細探討這兩者。
一、來張圖感受一下
如果看完覺得一臉懵逼,請繼續往下看。
二、解析
我們還是拿上篇的例子做解析
step1:f1、Promise對象實例化、f2被放入主線程的堆內存中;
step2:Promise對象實例化后的同步代碼塊被放入主線程的執行棧中執行,並且產生的異步任務被放入任務隊列;
step3:f1被放入主線程的執行棧中執行(打印“我是F1”),for循環產生的1000個定時器(異步任務)放入任務隊列;
step4:f2被放入主線程的執行棧中執行,連續5次;
step5:至此,主線程的執行棧中已經沒有任務了,於是事件循環(event loop)機制從任務隊列中取出一個任務放入主線程的執行棧中執行;
step6:等待主線程的執行棧中又沒有任務了,事件循環機制再次去任務隊列中取出任務;
step7:重復第6步。
三、任務隊列
上面提到了任務隊列,任務隊列就是等候執行的一系列任務,就好比鍋里的飯,你只有把碗里的飯吃完了,才能再次去鍋里再盛一碗(不要杠!);
只有主線程的執行棧中沒有了任務,事件循環機制才會去任務隊列拿任務去執行。
由剛開始的圖,你也看到了,任務隊列是分不同類別並且是有優先級的。
- 任務隊列又分為macro-task(宏任務)和micro-task(微任務);
- macro-task大概包括:script(整體代碼),setTimeout,setInterval,setImmediate,I/O,UI rendering;
- micro-task大概包括:process.nextTick,Promise,Object.observe(已廢棄),MutationObserver(html5新特性)
- setTimeout/Promise等我們稱之為任務源。而進入任務隊列的是他們指定的具體執行任務。
- 來自不同任務源的任務會進入到不同的任務隊列
優先級的話,micro-task > macro-task;
對於micro-task:process.nextTick > Promise.then
對於macro-task:setTimeout > setImmediate
Promise.resolve().then(()=>{ console.log('Promise1') setTimeout(()=>{ console.log('setTimeout2') },0) }) setTimeout(()=>{ console.log('setTimeout1') Promise.resolve().then(()=>{ console.log('Promise2') }) },0)
step1:
分析:開始任務隊列里有微任務promise1和宏任務timeout1,第一次事件循環一看有微任務,二話不說,直接拿promise1到主線程跑(其實是跑完所有的微任務);promise1運行結果是,控制台打印promise1,並生成一個宏任務timeout2。
step2:
分析:因為此時任務隊列里只有宏任務,於是,根據隊列規則以及優先級(這里只有一種宏任務,所以沒有涉及到優先級),事件循環拿timeout1去主線程跑;
timeout1運行結果,打印setTimeout1,並生成一個微任務promise2,至此第一次事件循環結束。
step3:
分析:任務隊列里有微任務promise2和宏任務timeout2,事件循環一看有微任務,二話不說,直接拿promise2到主線程跑;
運行結果,控制台打印promise2,此時任務隊列只剩下一個宏任務timeout2。
step4:
分析:此時任務隊列只有一個宏任務timeout2,事件循環二話不說,因為沒得挑了嘛,直接拿到主線程去跑,控制台打印timeout2,至此結束,也是第二次事件循環結束。
所以,一次事件循環是跑完所有微任務並推一個宏任務到主線程的過程。