從前有座山,山裏有座廟,廟裏有個小和尚在講故事、講什麼呢?講的是:node
從前有座山,山裏有座廟,廟裏有個小和尚在講故事、講什麼呢?講的是:面試
從前有座山,山裏有座廟,廟裏有個小和尚在講故事、講什麼呢?講的是:promise
...異步
之前有一個餐廳,這個餐廳有一個老闆和一個廚師,本身創業的,剛開始起步階段,沒有資金請員工,因此本身來當老闆兼服務員。函數
來這家餐廳的顧客有這四種類型oop
做爲VIP顧客,確定得有VIP特權。測試
因此這個店的上菜順序跟身份和點菜的順序有關,ui
超級VIP客戶 > VIP客戶 > 普通客戶 > 賴帳的客戶spa
一天、老闆開始營業後,陸陸續續的來了一些人進來點餐吃飯。線程
第一個進來的是普通的客戶,點了一道回鍋肉
第二個進來的是充了錢的VIP客戶,點了一道小龍蝦
第三個進來的是賴帳的客戶,點了不少菜
第四個進來的是一個超級VIP客戶,點了一道酸菜魚
因爲這個店只有一我的,因此老闆招待好他們點完餐以後就去炒菜了。根據上面提到的順序,因此會先炒超級VIP客戶點的菜、而後到VIP客戶點的菜、而後到普通客戶點的菜、最後到賴帳的客戶點的菜
讓咱們用僞代碼看看如何實現這個邏輯
咱們定義了四個function
根據上面提到的上菜規則,
超級VIP客戶 > VIP客戶 > 普通客戶 > 賴帳的客戶
實際的上菜順序咱們能夠知道是
那麼問題來了,那些function都是什麼呢?
其實很簡單,這些function都對應着JavaScript中的一些異步函數
// 超級VIP客戶
// 微任務,將回調加入到 執行隊列,優先執行
function superVipOrder(name, dish, order) {
process.nextTick(() => {
console.log(red(`superVip顧客 ${name} 點了 ${dish} 已經上了`));
});
}
複製代碼
// VIP客戶
// 微任務,將回調加入到 執行隊列,優先執行,優先級比process.nextTick低
function vipOrder(name, dish, order) {
new Promise((resolve, reject) => {
resolve(true);
}).then(() => {
console.log(blue(`vip顧客 ${name} 點了 ${dish} 已經上了`));
});
}
複製代碼
// 普通客戶下單
// 宏任務,將回調加入到 宏任務的執行隊列中
function order(name, dish, order) {
setTimeout(() => {
console.log(yellow(`普通顧客 ${name} 點了 ${dish} 已經上了`));
}, 0);
}
複製代碼
// 賴帳的客戶下單
function groupOrder(name, dish, order) {
setImmediate(() => {
console.log(green(`賴帳的顧客 ${name} 點了 ${dish} 已經上了`));
});
}
複製代碼
咱們能夠暫且先把 process.nextTick
認爲是超級vip用戶,優先級最高、
原生Promise
認爲是vip用戶,執行優先級高
setTimeout
認爲是普通用戶,執行優先級通常
setImmediate
認爲是賴帳的顧客,執行優先級低
咱們將僞代碼還原成這些異步函數,這會讓咱們看的更加直觀、親切一些
根據上面故事提到的優先級規則,咱們知道輸出的結果是這樣的
爲何會是這樣的結果呢?下面就來說講JavaScript中的Event Loop
咱們知道 JavaScript
是單線程的,就像上面故事的老闆,他得去服務員去招待客人點菜,並將菜單給廚師,廚師炒好後再給到他去上菜。若是老闆不請個廚師,本身來炒菜的話,那麼在炒菜時就沒辦法接待客人,客人就會等待點菜。等着等着就會暴露出服務態度不行的問題。因此說,得有廚師專門處理炒菜的任務
因此在js中,任務分爲同步任務和異步任務,
JS的事件循環如圖所示,
異步任務又分爲宏任務跟微任務、他們之間的區別主要是執行順序的不一樣。
原生的Promise -> 其實就是咱們上面提到的VIP用戶,
process.nextTick -> 其實就是咱們上面提到的超級VIP用戶,
process.nextTick的執行優先級高於Promise的
setTimeout的執行優先級高於 setImmediate 的
在一次事件循環中,JS會首先執行 總體代碼 script,執行完後會去判斷微任務隊列中是否有微任務,若是有,將它們逐一執行完後在一次執行宏任務。如此流程
下面咱們來看一段代碼是否瞭解了這個流程
<script>
setTimeout(() => {
console.log('a');
new Promise( res => {
res()
}).then( () => {
console.log('c');
})
process.nextTick(() => {
console.log('h');
})
}, 0)
console.log('b');
process.nextTick( () => {
console.log('d');
process.nextTick(() => {
console.log('e');
process.nextTick(() => {
console.log('f');
})
})
})
setImmediate( () => {
console.log('g');
})
</script>
複製代碼
執行結果爲:b d e f a h c g
讓咱們來分析一下這段代碼的執行流程
首頁執行第一個宏任務 整段script
標籤代碼,遇到第一個 setTimeout
,將其回調函數加入到宏任務隊列中,
輸出 console.log('b')
遇到process.nextTick,將其回調函數加入到微任務
遇到setImmediate 將其回調函數加入到宏任務隊列中
宏任務Event Queue | 微任務Event Queue |
---|---|
setTimeout | process.nextTick |
setImmediate |
console.log('d')
,而後又遇到了一個process.nextTick,又將其放入到微任務隊列console.log('e')
,而後又遇到了一個process.nextTick,又將其放入到微任務隊列console.log('f')
,而後又遇到了一個process.nextTick,又將其放入到微任務隊列宏任務Event Queue | 微任務Event Queue |
---|---|
setTimeout | |
setImmediate |
setTimeout
,執行 console.log('a')
,而後遇到Promise
,process.nextTick
將其回調加入到微任務隊列。執行完後宏任務Event Queue | 微任務Event Queue |
---|---|
setImmediate | promise.then |
- | process.nextTick |
process.nextTick
的執行優先級大於promise
,因此會先執行process.nextTick
的回調,輸出 console.log('h');
、若是有多個process.nextTick
的回調,會將process.nextTick
的全部回調執行完成後纔會去執行其它微任務的回調。 當nextTick全部的回調執行完後,執行promise
的回調,輸出console.log('c');
,直到promise的回調隊列執行完後,又會去判斷是否還有微任務。宏任務Event Queue | 微任務Event Queue |
---|---|
setImmediate |
setImmediate
的回調,輸出 console.log('g');
這裏爲何要把 setImmediate
單獨拿出來講呢,由於它屬於宏任務的範疇,但又有點不同的地方。
先看一段代碼
按照咱們上面的分析邏輯,咱們會認爲這段代碼的輸出結果應該是a b c d
。 若是咱們把使用Node 0.10.x的版本去執行這段代碼,結果確實是輸出a b c d
然而,在Node 大於 4.x 的版本後,在執行setImmediate
的,會使用while循環,把全部的immediate回調取出來依次進行處理。
ps:在 node 11版本以上,已經把setImmediate按照宏任務的執行順序進行處理了,不會循環依次進行調用了
若是尚未掌握,歡迎評論區吐槽
<script>
console.log("start");
process.nextTick(() => {
console.log("a");
setImmediate(() => {
console.log("d");
});
new Promise(res => res()).then(() => {
console.log("e");
process.nextTick(() => {
console.log("f");
});
new Promise(r => {
r()
})
.then(() => {
console.log("g");
});
setTimeout(() => {
console.log("h");
});
});
});
setImmediate(() => {
console.log("b");
process.nextTick(() => {
console.log("c");
});
new Promise(res => res()).then(() => {
console.log("i");
});
});
console.log("end");
</script>
複製代碼
輸出的結果爲: start end a e g f h b d c i
簡單分析一下代碼:
script
代碼,輸出 start
end
,將process.nextTick
的回調加入微任務隊列中,將setImmediate
的回調加入到宏任務的隊列中process.nextTick
的回調,輸出 a
、將setImmediate
的回調加入到宏任務的隊列中,遇到promise
、將回調加入到微任務隊列中。宏任務 | 微任務 |
---|---|
setImmediate | promise.then |
setImmediate | - |
promise.then
並執行,輸出e
,將process.nextTick
的回調放入到微任務中,遇到promise
、將回調加入到微任務隊列中。g
,當Promise回調隊列執行完後,繼續判斷當前是否還有微任務。process.nextTick
的回調並執行,輸出f
宏任務 | 微任務 |
---|---|
setImmediate | - |
setImmediate | - |
setTimeout | - |
setTimeout
的優先級大於setImmediate
,因此先取出setTimeout
的回調並執行,輸出h
setImmediate
的回調函數,並執行,輸出b d
,將 process.next
與 promise
的回調放入到微任務隊列中。c i
Event Loop 做爲面試的高頻題,靜下心來認真的分析一下,其實不難理解。
歡迎關注公衆號「碼上開發」,天天分享最新技術資訊