寫在前面
面過前端的小夥伴們都見過這麼一道關於異步的小題:javascript
for(var i = 0; i<10; i++){
setTimeout(function(){
console.log(i)
},1000)
}
複製代碼
稍微瞭解異步的同窗都會對答案呼之欲出。BUT! 若是問題升級爲:html
setTimeout(function(){
console.log(1)
},0)
new Promise(function executor(resolve) {
console.log(2)
for(var j = 0;j<100;j++){
j=99&&resolve()
}
console.log(3)
}).then(function(){
console.log(4)
})
console.log(5)
複製代碼
是否是稍微的有那麼點小蒙圈? 彆着急,本篇內容結束後以上問題都再也不是事兒。解決以上問題的要點,首先須要清楚Javascript異步處理模塊,事件隊列,以及事件環-Eventloop.前端
進程是操做系統分配資源和調度任務的基本單位,線程是創建在進程上的一次程序運行單位,一個進程上能夠有多個線程。
進程和線程屬基礎概念,再也不贅述。有個最生動易懂的解釋,詳情請移步參考:進程與線程的一個簡單解釋java
對Javascript而言,從誕生之日起,它就是單線程的。爲何呢?舉個小栗子:若是能夠多線程,a線程要添加某DOM節點,b線程要刪除它,瀏覽器怎麼辦?難道要精分? 因此說,單線程減小了不少情境的複雜性。
既然js是單線程的,它又以什麼樣的規則來處理併發的任務呢?千軍萬馬要過獨木橋的時候,不能靠力氣來搶路。單線程,任務多,就得有個規矩來安排你們。因而祕密終於被咱們發現——事件環(Event Loop)就要出馬了。 對於首次據說這個概念的同窗,有必要鋪墊下基礎知識:node
對象被分配在一個堆中,即用以表示一個大部分非結構化的內存區域。web
函數調用造成一個棧幀;vim
棧的特色:先進後出(First in, last out,具體是怎樣讓那些函數先入後出的?看下圖會恍然大明白,圖中的帥哥是Philip Roberts,看解釋,別光看臉! api
一個 JavaScript 運行時包含了一個待處理的消息隊列。 每個消息都有一個爲了處理這個消息相關聯的函數promise
說到了任務隊列,就到了重點部分:事件環(Eventloop)了瀏覽器
Defined by webappapis:
To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section.
任務隊列以事件環來協調事件,用戶交互,腳本,渲染,網絡等。
Each 'thread' gets its own event loop, so each web worker gets its own, so it can execute independently
This is a constantly running process that checks if the call stack is empty. Imagine it like a clock and every time it ticks it looks at the Call Stack and if it is empty it looks into the Event Queue.
簡單來講,每一個線程都有他本身的事件環,瀏覽器也擁有本身的事件環;事件環是一種運行時機制,它像個鐘錶同樣,每滴答一下,就去看看stack
裏有沒有事須要處理,沒有的話就去事件隊列(Event Queue
)看看有沒有事作。
此處你們須要明白,事件環並非定死的某個規矩,須要根據不一樣的運行時進行本身的一套規則。
There are two kinds of event loops: those for browsing contexts, and those for workers.--from webapis.
如node下的事件環與瀏覽器環境下的事件環就不是相同的規則。必定要記清楚哦!首先討論瀏覽器事件環。
依據webapis裏聲明的事件環(event loop)存在時的執行步驟,歸納以下:
一圖頂千言,做圖解釋下:
看似按照這個規則,咱們文章開頭的問題就能夠有答案了。 回顧一下:
setTimeout(function(){
console.log(1)
},0)
new Promise(function executor(resolve) {
console.log(2)
for(var j = 0;j<100;j++){
j=99&&resolve()
}
console.log(3)
}).then(function(){
console.log(4)
})
console.log(5)
複製代碼
代碼執行後,按照執行順序:
Explained by webappapis
Each event loop has a microtask queue. A microtask is a task that is originally to be queued on the microtask queue rather than a task queue. There are two kinds of microtasks: solitary callback microtasks, and compound microtasks.
翻譯:每一個eventloop都有一個微任務隊列,微任務最初是被放到微任務隊列而不是任務隊列。
這裏你們請把這句話放在內心,這是解決宏任務的一把鑰匙所在。
雖然知道任務隊列分爲宏任務,微任務,可是一直未找到宏任務的定義,直到看到stackoverflow上的解釋。
One go-around of the event loop will have exactly one task being processed from the macrotask queue (this queue is simply called the task queue in the WHATWG specification).
每一個事件環必須有一個來自宏任務隊列的任務正在執行。 這裏將宏任務解釋爲whatwg上定義的任務隊列.
是否是有點迷惑,這不對啊? 雖然把任務隊列分紅宏任務,微任務理解比較容易,但我認爲這後面更大的祕密纔是我苦苦找尋的真相。 若是隻執行宏任務隊列,那微任務隊列怎麼執行呢?
這正好與上文中提到微任務隊列是單獨的隊列,跟任務隊列不是一回事符合。 整個任務能夠理解爲,全部的宏任務放在一個宏任務隊列(即任務隊列),處理完一個宏任務(從sccript開始),將微任務隊列(包含當時全部的微任務)壓入任務隊列(宏任務隊列)並執行,以後再取下一個任務隊列(宏任務)中的宏任務。
因而,咱們的題目能夠翻譯成如下解決思路:
eventloop
中爲何必須在全部的微任務microtask
都執行結束後再取新的宏任務macrotask
呢?這涉及microtask
的執行機制:
step2中作了明確的解釋,當微任務隊列 的標記被寫爲true以後,只要microtask的隊列不爲空,eventloop中的當前任務就會按順序執行microtask隊列中的全部任務。
這裏能夠看到二者的一點區別,微任務microtask
隊列是獨立的一個隊列,在eventloop執行過程當中才進入到任務隊列task queue
一次執行。
沒有盡興的朋友推薦如下幾篇好文,這些是我我的認爲講解事件環,異步事件隊列等最爲具體清晰的文章:
Best reference:
- Tasks, microtasks, queues and schedules by Jake Archibald
- How does Javascript event work? or
Help, I'm stuck in an event-loop by Philip Roberts- stackoverflow.com/questions/2…
其餘參考
但願你們看文本文能有收貨。歡迎批評指正。
Author: Yanni Jia
Nickname: 很是兔
Email: 385067638@qq.com