js是一門單線程的編程語言,也就是說js在處理任務的時候,全部任務只能在一個線程上排隊被執行,那若是某一個任務耗時比較長呢?總不能等到它執行結束再去執行下一個。
因此在線程以內,又被分爲了兩個隊列:html
舉個例子來講:好比你去銀行辦理業務,都須要領號排隊。銀行櫃員一個個辦理業務,這時這個櫃員就至關於一個js線程,客戶排的隊就至關於同步任務隊列,每一個人對於櫃員至關於一個個的任務。
但這個時候,你的電話忽然響了,你去接電話接了半小時。這時候人家櫃員一看你這狀況,直接叫了下一個,而你領的號就做廢了,只能從新零號排隊。這時候你就是被分發到了異步任務隊列。
等你前邊的人都完事了,櫃員把你叫過去辦了你的業務,這時候就是同步隊列中的任務執行完了,主線程會處理異步隊列中的任務。前端
這裏說的異步任務,它的意思是包含了獨立於主執行棧以外的宏任務和微任務
。html5
先看一個簡單的例子,對這樣的執行機制有個簡單的認識:面試
console.log('start') console.log('end')
上邊的執行結果你們確定都明白,先輸出start,再輸出end,這一段代碼會進入同步隊列,順序執行。ajax
那麼咱們加點料:編程
console.log('start') setTimeout(function() { console.log('setTimeout') }, 0) console.log('end')
這樣的狀況,函數調用棧執行到setTimeout時,setTimeout會在規定的時間點將回調函數放入異步隊列,等待同步隊列的任務被執行完,當即執行,因此結果是:start、end、setTimeout。promise
但須要注意的一點是,廣泛認爲setTimeout定時執行的認知是片面的,由於假設setTimeout規定2秒後執行,但同步隊列中有一個函數,執行花了很長時間,甚至花了1秒。那麼這時setTimeout中的回調也會等上至少1秒以後,同步任務都執行完了,再去執行。這時候的setTimeout回調執行的時機就會超過2秒,也就是至少3秒。異步
宏任務與微任務都是獨立與主執行棧以外的另外兩個隊列,能夠在概念上劃分在異步任務隊列裏。而這些隊列由js的事件循環(EventLoop)來搞定編程語言
macro-task(宏任務)與micro-task(微任務),在最新標準中,它們被分別稱爲task與jobs。
因爲寫文章時沒有注意到,實際上宏任務與微任務的概念是不許確的,但因爲文章中涉及多處宏任務、微任務的解讀,因此本文暫時仍是用宏任務、微任務來分別代指task、jobs。但讀者要明白規範中沒有宏任務的概念,只有task與jobs函數
其中宏任務(task)包括:
ajax請求不屬於宏任務,js線程遇到ajax請求,會將請求交給對應的http線程處理,一旦請求返回結果,就會將對應的回調放入宏任務隊列,等請求完成執行。
微任務(jobs)包括:
這些咱們能夠理解爲它們在執行上下文中都是可執行代碼,會當即執行,只不過會將各自的回調函數放入對應的任務隊列中(宏任務微任務),也就至關於一個調度者。
咱們梳理一下事件循環的執行機制:
循環首先從宏任務開始,遇到script,生成執行上下文,開始進入執行棧,可執行代碼入棧,依次執行代碼,調用完成出棧。
執行過程當中遇到上邊提到的調度者,會同步執行調度者,由調度者將其負責的任務(回調函數)放到對應的任務隊列中,直到主執行棧清空,而後開始執行微任務的任務隊列。微任務也清空後,再次從宏任務開始,一直循環這一過程。
上邊說了那麼多,仍是用一些代碼來驗證一下是不是這樣的,先來一個簡單一點的。
console.log('start') setTimeout(function() { console.log('timeout') }, 0) new Promise(function(resolve) { console.log('promise') resolve() }).then(function() { console.log('promise resolved') }) console.log('end')
根據上邊的結論,分析一下執行過程:
start
promise
,可是當resolve後,.then會把其內部的回調函數放入微任務隊列end
。這時,主執行棧清空了,開始尋找微任務隊列裏有沒有可執行代碼promise resolved
,第一次循環結束timeout
因此,打印順序是:start
-->promise
-->end
-->promise resolved
-->timeout
上邊是一個簡單示例,比較好理解。那麼接下來看一個稍微複雜一點的(這裏直接用漢字直觀地代表了打印的時機,避免看起來費勁):
console.log('第一次循環主執行棧開始') setTimeout(function() { console.log('第二次循環開始,宏任務隊列的第一個宏任務執行中') new Promise(function(resolve) { console.log('宏任務隊列的第一個宏任務的微任務繼續執行') resolve() }).then(function() { console.log('第二次循環的微任務隊列的微任務執行') }) }, 0) new Promise(function(resolve) { console.log('第一次循環主執行棧進行中...') resolve() }).then(function() { console.log('第一次循環微任務,第一次循環結束') setTimeout(function() { console.log('第二次循環的宏任務隊列的第二個宏任務執行') }) }) console.log('第一次循環主執行棧完成')
一樣咱們分析一下執行過程:
第一次循環
第一次循環主執行棧開始
第一次循環主執行棧進行中...
,resolve後遇到.then,將回調放入微任務隊列第一次循環主執行棧完成
第一次循環微任務,第一次循環結束
,第一次循環結束,同時遇到setTimeout,將回調放入宏任務隊列第二次循環
第二次循環開始,宏任務隊列的第一個宏任務執行中
宏任務隊列的第一個宏任務繼續執行
,這時候又被resolve了,又會將.then中的回調放入微任務隊列,這是這個宏任務隊列中的第一個任務還沒執行完第二次循環的微任務隊列的微任務執行
,此時第一個宏任務執行完畢第二次循環的宏任務隊列的第二個宏任務執行
,全部任務隊列所有清空,執行完畢因此打印順序爲:
看一下gif,事件循環以肉眼可見的形式呈現出來(兩次循環之間有微小的時間間隔)
js的執行機制是面試中常考的點,也是很是繞的。但相信徹底瞭解事件循環機制,仔細分析的話,面試遇到這樣的題徹底不是問題。我在寫這篇文章的時候,發現本身以前理解的很大一部分是錯的。若是你們以爲哪裏有錯誤,還請幫忙指點出來。
歡迎關注個人公衆號: 一口一個前端,不按期分享我所理解的前端知識