聊聊前端面試之輸出順序
春節後,新一輪跳槽風暴不知道能否吹暖今冬的裁員寒冬。然而,職場現狀就是越來越多的小公司也在效仿各大網際網路牛場面試要求,對於底層知識要求越來越深,管他用著用不著的,先面了再說。本篇跟大家聊聊面試常見題型之顯示順序問題。
注:本篇分析為在瀏覽器環境中排序顯示。
Nodejs程式設計是全非同步,事件引擎為libuv。
一、JavaScript執行機制
關於執行機制聊四個關鍵點:
- 單執行緒:同一時間只能解決一件事,是JS核心特徵。JS為什麼不能多執行緒?因為JS是瀏覽器指令碼語言,主要用於使用者互動與DOM操作;
- 任務型別:同步任務與非同步任務。同步任務是主執行緒上的排隊等待解決的任務,只有完成一項任務,才可以進行下一個任務;異常任務,不進入主執行緒任務,而是進入“任務佇列”,只有主執行緒的任務完成後,才會召喚“任務佇列”上任務並執行;
- 任務佇列:是一個事件佇列,I/O裝置完成一項任務,就會在“任務佇列”裡新增一個事件。注意,必須I/O裝置完成有結果,才會進入到“任務佇列”;
- 事件迴圈(Event Loop):主執行緒不斷的迴圈從“任務佇列”讀取事件的執行機制。
二、任務的階級鬥爭
JavaScript任務執行機制圖
第一階級:同步任務
1、皇帝豪飲兩碗鹿血,問有個叫EventLoop的公公,今晚誰來陪睡,EventLoop掐指一算,正宮幾個“同步任務“娘娘都送過禮,我就按照送禮順序來給她們安排吧。
同步任務優先執行,先進先出。
第二階級:非同步任務(微任務)
2、幾晚過後,所有送過禮的“同步任務”娘娘輪流服侍皇帝一遍。如此同時,秀宮的宮女們忙著化妝整容。原來,宮裡有個不成文的規定,只有收拾好自己的宮女才可以排隊等待皇帝的召喚,到時候公公EventLoop會按照排隊次序向皇帝安排服侍。
3、什麼時候才輪到宮女們服侍呢?是“同步任務”娘娘們服侍一遍後,畢竟公公EventLoop為了保護自己的飯碗,得讓皇帝不斷嚐鮮。
同步任務執行的同時,非同步任務在事件棧完成後,非同步任務進入“任務佇列”;
事件迴圈(Event Loop)驅使主執行緒在全部同步任務完成後,從“任務佇列”讀取事件,如此不斷迴圈;
任務佇列執行先進先出;
第三階級:非同步任務--定時器(巨集任務)
4、在秀宮有兩個宮女姓SetTimeout和setInterval,人老珠黃,雖然化妝技術還可以,也送過了禮,無奈確實不上檔次,負責秀宮的公公就讓她倆排在了所有宮女的後邊,等著唄。
5、這倆宮女終於看到希望了,可誰知又來一批年輕漂亮的秀女,怎麼辦,等著唄。
6、終於,前前後後好幾批年輕秀麗的宮女們服侍完皇帝,輪到這兩位了,按照老規則,誰先來的誰優先,倆人對視苦笑,回頭一看,又來幾個姓SetTimeout和setInterval的宮女...
同時巢狀在非同步操作的任務,會將巢狀在非同步的下一次任務佇列中;
定時器任務新增到任務佇列的尾部;
三、案例分析
基於今日頭條的面試題改造
async function async1() {
console.log(1)
await async2()
console.log(2)
return await 3
}
async function async2() {
console.log(4)
}
setTimeout(function() {
console.log(5)
}, 0)
async1().then(v => console.log(v))
new Promise(function(resolve) {
console.log(6)
resolve();
console.log(7)
}).then(function() {
console.log(8)
})
console.log(9)複製程式碼
- 從程式碼第一行開始,非同步函式async1、async2構建完未執行。
- 按照程式碼順序,首先該執行的應該是
setTimeout
,怎奈又不是正宮,長得又不秀麗,只能在任務佇列最後等著。 - 執行到
async1.then()
時,async1返回一個Promise
物件(非同步函式其實是封裝的Promise.resolve()
,Promise
物件是一個建構函式),所以在執行async1時,首先將非resolve、reject函式加入主執行緒,上題中首先執行console.log(1)
和await async2
, 輸出1,4
。感覺await挺孫子的,執行完自己的函式就跳出來async
函式。 - 繼續看,非同步函式async1的then執行這個時候進入事件棧,因為還在等待非同步函式async1返回值,等著吧。
- 繼續向下執行遇到真正的
Promise
物件了,二話不說,執行下先,console.log(6)
、console.log(7)
直接加入到主執行緒,輸出6,7
。同時將非同步函式console.log(8)放在儲秀宮,雖然屬於第二階級,起碼還是第二階級的老大麼。 - 單純的console.log(9)說,咱也是第一階級的,雖然最後一位,侍架一晚,輸出了 9 。
- 同步人物結束了,Event Loop開始召喚任務佇列,console.log(8)說我是秀女中的第一位,好,就你了。於是,生出了
8
。 - setTimeout翹首以盼,誰知,執行完本輪又跳到到非同步函式async1,率先看到了
console.log(2)
,所以輸出了2
。 - 緊接著,又遇到了
await
了,還好,結束了,返回的值扔給了async
的非同步函式,得到了值,抓緊時間執行,得到結果了,終於可以進入任務佇列,這時主執行緒也是空的,執行下結果吧,輸出了3
。 - 所有微任務結束了,該巨集任務大展拳腳了,setTimeout輸出了值,
5
。
以上試題分析完畢,輸出結果順序為:
1,4,6,7,9,8,2,3,5
。
四、最後
以上是個人對於任務執行順序的理解,如理解有誤,希望大神多多指教。