JS事件循環機制初探

做者:葉@毛豆前端javascript

事件循環機制

js的事件循環機制,在我理解起來就是執行上下文,對函數的出棧和入棧的一個過程。都知道js的一大特色是單線程,這也是這個語言的核心特徵。試想一下,若是js是個多線程的語言,同時存在兩個線程,一個線程是在某個Dom上添加節點,而另外一個線程倒是刪除節點,這個時候瀏覽器就會產生錯亂,而咱們關心的js事件循環機制,正是由js的單線程特質決定的。前端

先上圖,看下大體流程java

事件循環
在上圖的過程當中:

  1. js引擎逐句的執行script總體代碼,造成執行棧
  2. 當執行遇到異步代碼時,會指給對應的異步進程進行處理(WEB API)
  3. 等待異步任務有了運行結果,js的運行環境就在相應的"任務隊列"之中push事件任務,等待着被js引擎執行,而當異步沒有產生回調(callback)或者說是事件任務,那他就不會push到任務隊列裏面去。
  4. 執行棧任務執行完畢,便會查詢任務隊列,若是不爲空,則讀取一個任務推入主線程處理
  5. 重複第4個步驟,直到任務隊列爲空,這樣過程即爲事件循環,在這執行過程當中若是有產生新的異步任務也會按照上述(3)的方式進行處理。在這個過程當中step(1)同步環境執行,step(4)這樣的循環過程即爲事件循環

涉及的到異步進程:git

  • DOM事件,是由瀏覽器的DOM模塊處理,達到觸發條件時,在任務隊列中添加相應的回調函數
  • 定時器setTimeout等,是由瀏覽器的Timer模塊處理,達到設置的時間點時,在任務隊列中添加回調
  • ajax請求,是由瀏覽器的Network模塊處理,等待請求返回,在任務隊列中添加回調

知道了js的大體執行的過程了,咱們看下其中涉及到的一些概念github

棧(stack)又名堆棧,它是一種運算受限的線性表。其限制是僅容許在表的一端進行插入和刪除運算ajax

棧
知道了棧是一種後進先出的數據結構後,咱們運行一下代碼,並模擬它的入棧和出棧過程

function fun2() {
    console.log('fun2')
}
function fun1() {
    fun2();
}
setTimeout(() => {
	console.log('setTimeout')
})
fun1();
複製代碼

以上的出入棧如圖所示瀏覽器

隊列

任務隊列

js中存在着多個任務隊列,而且不一樣的任務隊列之間優先級不一樣,優先級高的先被獲取。同一隊列中按照隊列順序被取用 任務隊列分爲兩種類型數據結構

  • macrotask queue(宏任務隊列):script(總體代碼),setTimeout, setInterval,setImmediate,I/O,UI rendering
  • microtask queue(微任務隊列):process.nextTick, Promises(這裏指瀏覽器實現的原生 Promise), MutationObserver

二者的區別:多線程

  • macrotask queue:存在多個,存在優先級
  • microtask queue:僅一個,按照隊列順序執行

事實上,事件循環的順序,決定了JavaScript代碼的執行順序。執行時從script(總體代碼)開始第一次循環,以後全局上下文進入函數調用棧,直到執行棧清空,而後開始執行全部的microtask,當全部可執行的micro-task執行完畢以後。循環再次從macro-task開始,找到其中一個任務隊列執行完畢,而後再執行全部的micro-task,這樣一直循環下去。事件循環每次只會入棧一個 macrotask ,主線程執行完該任務後又會先檢查 microtasks 隊列並完成裏面的全部任務後再執行 macrotask。異步

咱們舉個例子驗證下吧:

console.log('-----start-----')
setTimeout(()=>{
	console.log('setTimeout1-macroTask')
},1000)
Promise.resolve().then(() => {
    console.log('Promise1-microTask')
});
Promise.resolve().then(() => {
    console.log('Promise2-microTask')
});
setTimeout(()=>{
	console.log('setTimeout2-macroTask')
},100)
console.log('-----end-----')

輸出結果
-----start-----
-----end-----
Promise1-microTask
Promise2-microTask
setTimeout2-microTask
setTimeout1-microTask

複製代碼

具體過程,咱們稍微歸納下:

  1. 遇到console.log,當即執行,輸出「-----start-----」
  2. setTimeout,交給異步模塊處理,執行完後,回調放入macrotask queue中
  3. 遇到Promise,執行then部分,回調放入microtask queue
  4. 遇到Promise,執行then部分,回調放入microtask queue,此時的microtask queue隊列中就有兩個事件了
  5. 仍是遇到setTimeout,一樣交給異步處理模塊,執行完後,回調一樣是一個macrotask任務,但由於setTimeout2的時間要比setTimeout1的短,所以先輸出setTimeout2
  6. 再次遇到console.log,依舊當即執行,輸出「-----end-----」
  7. 執行結束
相關文章
相關標籤/搜索