update 2019-07-03html
js事件循環前端
先來看一張圖(這張圖來自於http://www.zcfy.cc/article/node-js-at-scale-understanding-the-node-js-event-loop-risingstack-1652.html)node
再來看一張圖(這張圖來自於https://www.cnblogs.com/hity-tt/p/6733062.html)
web
從一開始,js有一個執行棧(stack)中存放了該頁面全部的代碼即該頁面主線程,當執行遇到異步操做(async)時,瀏覽器的webcore模塊(如network,timer,domBinding模塊等)將其放置到一個幕後線程中等待,而後瀏覽器接着執行主線程,當幕後線程中的代碼準備好了(如按期器時間到了,請求的響應來了),該線程就會把這個函數的回調放到taskQueue(任務隊列)中等待,當主線程的執行棧所有執行完畢時(全部同步操做所有執行完畢),主線程對任務對列進行檢測是否有任務要執行,若是有,就把該任務放到執行棧中進行,若是沒有,就保持等待(循環檢測)任務到來。promise
上述的過程,就是事件循環(EventLoop)。瀏覽器
由於web前端端用戶的豐富交互性問題,js這邊採起了單線程來應對多個交互產生時的程序操做複雜性問題。因爲單線程的出現,當同步操做出現長時間等待時就會出現線程阻塞,爲了解決阻塞,出現了異步操做(變相多線程)來解決線程阻塞。bash
而異步操做就是經過EventLoop的實現的。數據結構
主線程執行完畢後,檢測任務隊列有則取出一個插入主線程,重複該動做。多線程
如上圖,任務隊列中的任務分爲兩種,對應大型工做場景和微處理場景,即對應的傳說中的Macrotask和Microtask。dom
顧名思義,即多個任務同時出現時的執行優先等級。
Macrotask: setTimeout,setInterval,setImmediate,用戶交互操做/UI渲染(請求回調執行)etc
Microtask: process.nextTick(nodejs)>promise.then etc
當任務隊列中有多個任務時,事件優先級就須要出現了。
大體多任務時處理步驟以下四小步(來自網上,感受該步驟描述有問題,我後續修復了)
1. 抽取任務中全部MicroTask到2
2. 從Microtask隊列中取隊首(在隊列時間最長)的任務去執行棧中執行(僅僅一個),執行完後進入下一步3
3. 檢查Microtask隊列是否爲空,空則到4,不然到2
4. 檢測Macrotask中的MicroTask,非空1, 空5
5. 從Macrotask隊列中取隊尾(在隊列時間最長)的任務進去執行棧執行,執行完後,跳到4
Important
在一個循環中,或者說在處理完執行棧後,在處理任務隊列時,永遠先處理微任務隊列。換句話說,若是在當前循環中一個微任務中出現了一個異步微任務,那麼該微任務屬於本次循環,會被推入微任務隊列中!你仍然須要優先將while (microtask.chain.length) { // }執行下去。直到微任務處理完畢。再執行宏任務。至於同次循環中多個微任務執行時機,視推入時機而定!
簡而言之:同步環境執行 -> 事件循環1(microtask queue的All)-> 事件循環2(macrotask queue中的一個) -> 事件循環1(microtask queue的All)-> 事件循環2(macrotask queue中的一個)...
通俗點說任務隊列中的任務所執行的順序:
開始以前簡單講下隊列數據結構,一種先進先出的數據結構,能夠理解爲一個雙開的口徑爲一個豆子大小的竹筒,隊列中的數據就像豆子,豆子們排着隊從竹筒的一頭倒進去再從另外一頭倒出來,就是這麼理解。
看完上面後來理解下下面的代碼(基於掘金小美娜娜登峯造極版修改,添加Promise鏈式調用第二層then)順便理解下上面說的微任務處理器的清空處理是什麼意思,理解後相信對事件循環理解更深了。
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log( 'async2');
}
console.log("script start");
setTimeout(function () {
console.log("settimeout");
},0);
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
}).then(()=>{console.log('promise3')});
console.log('script end'); /*
script start
VM955:2 async1 start
VM955:8 async2
VM955:20 promise1
VM955:25 script end
VM955:4 async1 end
VM955:23 promise2
VM955:24 promise3
undefined
VM955:14 settimeout
*/複製代碼
我知道你看到這有點暈,回過頭去看看Important吧。
原理的話,我這兩天會把Promise以及用於鏈式的then方法實現一下,能夠來看看而後再看上面這個例子。
Special
demo來源於網上,作抽象概念具現化理解用
setTimeout(function(){console.log('111')},0);
new
Promise(function(resolve,reject){
console.log("2222");//此處尚未執行異步操做,執行異步操做及執行回調函數,在promise中即then中的回調
resolve();
}).then(function(){console.log('3333')})
console.log("44444");
//輸出
// 2222
// 44444//上面的兩個輸出屬於同步操做
// 3333//promise加入到隊列的優先級高於setTimeout
//111
嵌套微任務
new
Promise(
function
(resolve,reject){
resolve();
}).then(
function
(){
console.log(
"111"
);
return
new
Promise(
function
(resolve,reject){
resolve();
})
}).then(
function
(){ console.log(
"222"
);})
new
Promise(
function
(resolve,reject){
resolve();
}).then(
function
(){ console.log(
"33333"
);})
//輸出
111 33333 222