文章 "原文地址" 前沿 從前有座山,山裡有座廟,廟裡有個小和尚在講故事、講什麼呢?講的是: 從前有座山,山裡有座廟,廟裡有個小和尚在講故事、講什麼呢?講的是: 從前有座山,山裡有座廟,廟裡有個小和尚在講故事、講什麼呢?講的是: ... 請看一個小故事 以前有一個餐廳,這個餐廳有一個老闆和一個廚師, ...
文章原文地址
前沿
從前有座山,山裡有座廟,廟裡有個小和尚在講故事、講什麼呢?講的是:
從前有座山,山裡有座廟,廟裡有個小和尚在講故事、講什麼呢?講的是:
從前有座山,山裡有座廟,廟裡有個小和尚在講故事、講什麼呢?講的是:
...
請看一個小故事
以前有一個餐廳,這個餐廳有一個老闆和一個廚師,自己創業的,剛開始起步階段,沒有資金請員工,所以自己來當老闆兼服務員。
由於剛開業,所以會有一個充值優惠的活動,充了1000元是超級VIP客戶,充了100元以上的是VIP客戶,
所以來這家餐廳的顧客有這四種類型
- 家裡有礦的超級VIP客戶
- 充了錢的VIP客戶
- 普通的客戶
- 每次吃飯都帶著一群人來吃的客戶
作為VIP顧客,肯定得有VIP特權。
- 優先上菜
- 同等VIP,先點菜的先上菜
所以這個店的上菜順序跟身份和點菜的順序有關,
超級VIP客戶 > VIP客戶 > 普通客戶 > 一桌的客戶
這裡為什麼普通客戶會大於一桌的客戶呢?主要是因為炒一個人的菜比炒一桌人的菜快
一天、老闆開始營業後,陸陸續續的來了一些人進來點餐吃飯。
第一個進來的是普通的客戶,點了一道回鍋肉
第二個進來的是充了錢的VIP客戶,點了一道小龍蝦
第三個進來的是一群人一起吃飯的客戶,點了很多菜
第四個進來的是一個超級VIP客戶,點了一道酸菜魚
由於這個店只有一個人,所以老闆招待好他們點完餐之後就去炒菜了。根據上面提到的順序,所以會先炒超級VIP客戶點的菜、然後到VIP客戶點的菜、然後到普通客戶點的菜、最後到一桌的客戶點的菜
讓我們用偽代碼看看如何實現這個邏輯
我們定義了四個function
- superVipOrder(name, dish) 用來表示超級VIP用戶下單點菜
- vipOrder(name, dish) 用來表示VIP用戶下單點菜
- order(name, dish) 用來表示普通用戶下單點菜
- groupOrder(name, dish) 用來表示一桌子客戶下單點菜
根據上面提到的上菜規則,
超級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
Event Loop
1. JavaScript的事件迴圈
我們知道 JavaScript
是單線程的,就像上面故事的老闆,他得去服務員去招待客人點菜,並將菜單給廚師,廚師炒好後再給到他去上菜。如果老闆不請個廚師,自己來炒菜的話,那麼在炒菜時就沒辦法接待客人,客人就會等待點菜。等著等著就會暴露出服務態度不行的問題。所以說,得有廚師專門處理炒菜的任務
所以在js中,任務分為同步任務和非同步任務,
- 同步任務 -> 服務員去接待客人點菜
- 非同步任務 -> 廚師炒菜、非同步回調函數相當於 服務員去上菜
JS的事件迴圈如圖所示,
- 在執行主線程的任務時,如果有非同步任務,會進入到Event Table並註冊回調函數,當指定的事情完成後,會將這個回調函數放到 callback queue 中
- 在主線程執行完畢之後,會去讀取 callback queue中的回調函數,進入主線程執行
- 不斷的重覆這個過程,也就是常說的Event Loop(事件迴圈)了
2. 非同步任務
非同步任務又分為巨集任務跟微任務、他們之間的區別主要是執行順序的不同。
在js中,微任務有
原生的Promise -> 其實就是我們上面提到的VIP用戶,
process.nextTick -> 其實就是我們上面提到的超級VIP用戶,
process.nextTick的執行優先順序高於Promise的
巨集任務
- 整體代碼 script
- setTimeout -> 其實就是我們上面提到的普通用戶,
- setImmediate -> 其實就是我們上面提到的群體用戶,
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 |
- 當第一個巨集任務執行完後,就會去判斷是否還有微任務,剛好有一個 微任務,執行process.nextTick的回調,輸出
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');
### 3. setImmediate
這裡為什麼要把 setImmediate
單獨拿出來說呢,因為它屬於巨集任務的範疇,但又有點不一樣的地方。
先看一段代碼
按照我們上面的分析邏輯,我們會認為這段代碼的輸出結果應該是a b c d
。
如果我們把使用Node 0.10.x的版本去執行這段代碼,結果確實是輸出a b c d
然而,在Node 大於 4.x 的版本後,在執行setImmediate
的,會使用while迴圈,把所有的immediate回調取出來依次進行處理。
這也是我為什麼把 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
、將回調加入到微任務隊列中。 - 判斷當前promise的回調隊列是否還有回調函數沒執行,如果有,將繼續執行,取出剛剛放入的promise的回調,輸出
g
,當Promise回調隊列執行完後,繼續判斷當前是否還有微任務。 - 取出
process.nextTick
的回調並執行,輸出g
巨集任務 | 微任務 |
---|---|
setImmediate | - |
setImmediate | - |
setTimeout | - |
- 當前微任務隊列為空後,開始執行巨集任務,因為
setTimeout
的優先順序大於setImmediate
,所以先取出setTimeout
的回調並執行,輸出h
- 當前微任務隊列還是為空,開始執行巨集任務,取出所有
setImmediate
的回調函數,並執行,輸出b d
,將process.next
與promise
的回調放入到微任務隊列中。 - 取出微任務隊列中的回調函數,並執行,輸出
c i
總結
Event Loop 作為面試的高頻題,靜下心來認真的分析一下,其實不難理解。
歡迎關註
歡迎關註公眾號“碼上開發”,每天分享最新技術資訊