JS 非同步執行順序 -- 從一道面試題說起
這道題可以說是面試必考了,我在筆試中就遇到過好多次,你們應該都遇到過吧?。。以前拿到這道題時,我整個人都是懵的,看著程式碼就覺得又長又繞的,最後總是不能完全做對。
1.題目
請輸出下面的執行結果:
new Promise(resolve => {
setTimeout(() => {console.log(0)}, 0)
resolve()
}).then(() => {
setTimeout(() => {console.log(1)}, 0)
})
setTimeout(() => {console.log(2)}, 0)
new Promise(resolve => {
setTimeout(resolve, 0)
}).then(() => {
console.log(3)
setTimeout(() => {console.log(4)}, 0)
new Promise(r => r()).then(() => {console.log(5)})
})
setTimeout(() => {console.log(6)}, 0)
new Promise(resolve => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
for(var i = 9; i < 12; i++) {
setTimeout(() => {console.log(i)}, 0)
console.log(i)
}
可以先自己想想奧~
先講原理或結論可能會讓你們看得比較枯燥,所以我決定先講我的解題步驟;然後再去總結 JS 非同步執行過程中的一些規則和結論。
結合圖片來講會比較清楚,下面使用到的圖片也都是我自己畫的。
想起了之前耐心給我講解過這個題的面試官,他是在紙上邊畫邊給我講的,講完的那一瞬間我感覺真的懂了(大部分),原本懵C的我,看到他畫的圖後思路清晰了不少。
還有,像理解原型、原型鏈啊這些相對比較抽象的概念也可以試試畫圖,自己沒事就多畫幾遍,隔段時間忘了就再畫幾遍,對於我這種笨笨的人是真的挺有效的。看了再多的文章真的不如自己動手做一遍!!就是這個道理昂~
有點囉嗦了叭,那麼下面進入正題了!!
2.解題步驟
非同步任務裡面有兩種:巨集任務、微任務。下面為了描述方便,我將巨集任務簡稱為 H,微任務簡稱為 W。
2.1 思路:
注:看每一步的時候,最好結合前面的圖片一起對比看奧~
執行順序如下:
①如上圖1,JS 按照從上到下的順序執行,執行過程中遇到非同步任務(包括巨集任務和微任務),都會先被加入到任務佇列中,再繼續向下執行同步程式碼。
這一步中巨集任務 H1-H7 加入到巨集任務佇列,微任務 W1-W2 加入到微任務佇列。
這裡要注意,Promise 是會立即執行的,Promise 在建立的時候就會執行,所以 7 會被直接列印出來。
這一步列印: 7 9 10 11
此時,同步程式碼執行完。
②如上圖2,執行微任務 W1。會遇到巨集任務 H8,它被新增到巨集任務佇列末尾。
③執行微任務 W2。這一步列印: 8
這一步執行完後,當前的微任務佇列就清空了,下面會開始執行巨集任務。
④執行巨集任務 H1。這一步列印: 0
這一步沒有產生其它的微任務,所以會繼續向下執行其它的巨集任務。
⑤執行巨集任務 H2,這一步列印: 2
⑥如上圖3,執行巨集任務 H3。微任務 W3 被推入微任務列表。
之前,H3 還未執行時,Promise 中的 resolve 函式也未執行,Promise 處於 pending 狀態,所以 then 中的函式是不會被推到微任務佇列中的;
而 H3 執行後,resolve 函式也會執行,Promise 的狀態由 pending 變為 fulfilled,then 才會被 Promise 呼叫。
注意:
1.只有 Promise 呼叫了 then 的時候,then 中的函式才會被推入到微任務佇列中;
2.而 Promise 呼叫 then 的前提是 Promise 的狀態為 fullfilled
Promise 這一塊不清楚的話,可以去阮一峰的 ES6 中補一下奧~
⑦如上圖,執行微任務 W3。這一步列印: 3
巨集任務 H9 和 微任務 W4 分別加入到巨集任務佇列和微任務佇列。
⑧執行微任務 W4,這一步列印: 5
同樣地,和 ③ 一樣,這一步執行完後,當前的微任務佇列就清空了,下面會開始執行巨集任務。
注意:
1.當前微任務佇列清空後才會繼續執行巨集任務;
2.而巨集任務是每執行一個,就去看下微任務佇列是否有內容,有的話就挨個執行微任務,直到將微任務佇列清空,才會再執行下一個巨集任務。
⑨執行巨集任務 H4,這一步列印: 6
⑩執行巨集任務 H5 H6 H7,這一步列印: 12 12 12
這裡一定要注意!!容易搞錯。for 迴圈了 3 次,會產生 3 個巨集任務。
⑪執行巨集任務 H8,這一步列印: 1
⑫執行巨集任務 H9,這一步列印: 4
哇~到此,就執行完了!!
2.2 答案:
所以最後執行結果為:7 9 10 11 8 0 2 3 5 6 12 12 12 1 4
3.總結
3.1 為什麼需要非同步
- JS 是單執行緒的,同一時間只能做一件事
- JS 和 DOM 渲染共用一個執行緒,因為 JS 可修改 DOM 結構,當 JS 執行時 DOM 渲染要停止,DOM 渲染時 JS 也要停止
- 遇到等待(如網路請求,定時任務等),不應該被卡住
- 同步會阻塞程式碼執行,而非同步不會阻塞程式碼執行
3.2 關於 Promise 【重要】
- Promise 解決了什麼問題
主要解決了回撥地獄的問題。 - 三種狀態
pending(進行中)、fulfilled(已成功)和 rejected(已失敗) - 狀態變化
只有兩種情況:
①pending -> fulfilled(成功了)
②pending -> rejected(失敗了)
還要注意:變化不可逆!! - 狀態的表現【重要重要】
①pending 狀態,不會觸發 then 和 catch
②fulfilled 狀態,會觸發後續的 then 回撥函式
③rejected 狀態,會觸發後續的 catch 回撥函式 - then 和 catch 對狀態的影響
then 正常返回 fulfilled,裡面有報錯則返回 rejected ;
catch 正常返回 fulfilled(注意!!),裡面有報錯則返回 rejected。
這裡對 Promise 只是做了一個簡單的總結,詳細的我之後打算再專門寫一篇文章(先挖個坑啦)。
3.3 JS 執行順序【簡單版】
- 按照從上到下的順序,一行一行執行
- 如果某一行執行報錯,則停止下面程式碼的執行
- 先把同步程式碼執行完,再執行非同步程式碼
3.4 JS 執行順序【加上 Event Loop】
- 同步程式碼會一行一行放在 Call Stack 中執行
- 遇到非同步,會先記錄下,等待時機(定時器、網路請求等)
- 時機到了,就移動到 Callback Queue 中
- 如果 Call Stack 為空(即同步程式碼執行完),Event Loop 開始工作
- 輪詢查詢 Callback Queue,如果其中有內容,則移到 Call Stack 中執行
如下圖(圖5):簡單畫了個圖…畫的不太好,別見怪~~
3.5 JS 執行順序【加上微任務、巨集任務】
- 同步程式碼會一行一行放在 Call Stack 中執行
- 遇到非同步,會先記錄下,等待時機(定時器、網路請求等)
- 時機到了,就移到佇列中
- 巨集任務會經過 Web API 後,再移到巨集任務佇列中。
如:執行程式碼時遇到 setTimeout,會先將它扔給 Web API 中的 timer,當定時時間到了(即時機到了)之後,再被推到巨集任務佇列中。 - 微任務不會經過 Web API,會直接移動到微任務佇列 micro task queue 中。
如:執行程式碼時遇到了 Promise,會將 then 中內容移到微任務佇列中。
- 巨集任務會經過 Web API 後,再移到巨集任務佇列中。
- 如果 Call Stack 為空(即同步程式碼執行完),Event Loop 開始工作
- 先去查詢微任務佇列,如果有內容,則推到 Call Stack 中執行
- 當微任務佇列清空後,再去查詢巨集任務佇列,如果有內容,則推到 Call Stack 中執行
- 如上步驟迴圈
如下圖(圖6):
3.6 微任務 & 巨集任務
- 常見的巨集任務、微任務分別有哪些
巨集任務:setTimeout、setInterval、Ajax、DOM事件
微任務:Promise.then()、async/await - 微任務和巨集任務的區別
①巨集任務:DOM 渲染後觸發
②微任務:DOM 渲染前觸發
③微任務執行時機比巨集任務要早
注意:
微任務執行時會放到一個單獨的 micro task queue(微任務佇列)中,和巨集任務佇列是分開的。
原因:微任務是 ES6 語法規定的,巨集任務是由瀏覽器規定的 - 巨集任務、微任務和 DOM 渲染的關係
- Call Stack 清空
- 執行當前的微任務
- 嘗試 DOM 渲染
- 執行巨集任務
以上,如有不正確或描述不準確的地方歡迎評論留下意見。一起加油鴨~
相關文章
- 從 Google 的一道面試題說起·Go面試題
- 從一道Promise執行順序的題目看Promise實現Promise
- 從一道面試題說起—js隱式轉換踩坑合集面試題JS
- ajax非同步導致js方法順序執行不了非同步JS
- 從一道筆試題題說起筆試
- JavaScript同步、非同步、回撥執行順序之經典閉包setTimeout面試題分析JavaScript非同步面試題
- js執行順序Event LoopJSOOP
- [一道題搞蒙你] - setTimeout 與 Promise 執行順序Promise
- 從一道面試題說開來面試題
- 從一道前端面試題談起前端面試題
- 從一道場景面試題談起面試題
- 從FMDB執行緒安全問題說起執行緒
- 從兩道面試題說起面試題
- js解惑-函式執行順序JS函式
- 同步任務與非同步任務執行順序非同步
- ajax回撥函式執行順序帶來的同步非同步問題函式非同步
- 從一道執行題,瞭解Node中JS執行機制JS
- 從一道春招筆試題說起 [上]筆試
- 關於defer執行順序問題
- Sql執行順序SQL
- 一道setTimeout async promise執行順序的筆試題引發的思考Promise筆試
- 類script標籤,非同步載入,順序執行非同步
- promise、async、await非同步原理與執行順序PromiseAI非同步
- 從一道執行題,瞭解瀏覽器中JS執行機制瀏覽器JS
- promise執行順序面試題令我頭禿,你能作對幾道Promise面試題
- Java面試題:@PostConstruct、init-method和afterPropertiesSet執行順序?Java面試題Struct
- 又被奪命連環問了!從一道關於定時任務的面試題說起。面試題
- js程式碼執行順序簡單介紹JS
- 從 JSON 說起JSON
- JavaScript執行順序分析JavaScript
- 任務執行順序
- for語句執行順序
- JavaScript的執行順序JavaScript
- laravel Event執行順序Laravel
- mySQL 執行語句執行順序MySql
- js基礎進階–promise和setTimeout執行順序的問題JSPromise
- SQL語句中的AND和OR執行順序問題SQL
- sql中的or與and的執行順序問題SQL