理解瀏覽器和node.js中的Event loop事件循環

你們都知道,javascript是一門單線程語言,所以爲了實現主線程的不阻塞,Event Loop這樣的方案應運而生。javascript

瀏覽器和node中Event loop並不同,瀏覽器的Event loop是在HTML5中定義的規範,而node中則由libuv庫實現。java

瀏覽器中的Event loopnode

  • 全部同步任務都在主線程上執行,造成一個執行棧
  • 主線程以外,還存在一個任務隊列。promise

    • 任務隊列分爲macro-task(宏任務)和micro-task(微任務)。
    • macro-task(宏任務): setTimeout, setInterval, setImmediate, I/O等
    • micro-task(微任務): process.nextTick, 原生Promise(有些實現的promise將then方法放到了宏任務中),Object.observe(已廢棄), MutationObserver等

整個最基本的Event Loop如圖所示:瀏覽器

圖片描述

具體過程:異步

  1. 瀏覽器中,先執行當前棧,執行完主執行線程中的任務。
  2. 取出Microtask微任務隊列中任務執行直到清空。
  3. 取出Macrotask宏任務中 一個 任務執行。
  4. 檢查Microtask微任務中有沒有任務,若是有任務執行直到清空。
  5. 重複3和4。

整個的這種運行機制又稱爲Event Loop(事件循環)socket

例子oop

瞭解瀏覽器的Event loop後,查看下面例子,猜想瀏覽器是怎麼輸出的ui

console.log(1);
console.log(2);
setTimeout(function(){
  console.log('setTimeout1');
  Promise.resolve().then(function(){
    console.log('Promise')
  })
})
setTimeout(function(){
  console.log('setTimeout2');
})

//瀏覽器輸出:1 2 setTimeout1 Promise setTimeout2

node中的Event loopthis

  • 在libuv內部有這樣一個事件環機制。在node啓動時會初始化事件環。
  • node中的event loop分爲6個階段,不一樣於瀏覽器的是,這裏每個階段都對應一個事件隊列,node會在當前階段中的所有任務執行完,清空NextTick Queue,清空Microtask Queue,再執行下一階段。
  • 在node.js裏,process 對象表明node.js應用程序,能夠獲取應用程序的用戶,運行環境等各類信息。process.nextTick()方法將 callback 添加到next tick 隊列,而且nextTick優先級比promise等microtask高。
  1. timers:執行setTimeout() 和 setInterval()中到期的callback。
  2. I/O callbacks:上一輪循環中有少數的I/Ocallback會被延遲到這一輪的這一階段執行
  3. idle, prepare:隊列的移動,僅內部使用
  4. poll:最爲重要的階段,執行I/O callback,在適當的條件下會阻塞在這個階段
  5. check:執行setImmediate的callback
  6. close callbacks:執行close事件的callback,例如socket.on("close",func)

圖片描述

例子

查看下面例子加深對event loop的理解

在node執行下面代碼,發現每次執行前後順序不同,由於node須要啓動時間,執行過程當中setTimeout可能到時間了也可能沒到時間,因此這個前後順序取決於node的執行時間。

setTimeout(function(){
  console.log('timeout')  
})
setImmediate(function(){
  console.log('immediate')
})

i/o操做階段完成後,會走check階段,因此setImmediate會優先走

let fs=require('fs');
fs.readFile('./1.log',function(){
  console.log('fs');
  setTimeout(function(){
    console.log('timeout')
  })
  setImmediate(funciton(){
     console.log('setTimmediate')          
  })
})

nextTick應用場景

function Fn(){
  this.arrs;
  process.nextTick(()=>{ //根據nextTick的特性,能夠先賦值,再在下一個隊列中使用
    this.arrs();
  })
}
Fn.prototype.then=function(){
  this.arrs=function(){console.log(1)}
}
let fn=new Fn();
fn.then();

//注意:nextTick千萬不要寫遞歸,否則會形成死循環。能夠放一些比setTimeout優先執行的任務

總結

  • 同一個上下文下,MicroTask微任務會比MacroTask宏任務先運行。
  • 瀏覽器是先取出一個MacroTask宏任務執行,再執行MicroTask微任務中的全部任務。Node是按照六個階段執行,每一個階段切換時,再執行MicroTask微任務隊列
  • 同個MicroTask隊列下process.tick()會優於Promise
  • setImmdieate()和setTimeout(),若是他們在異步i/o callback以外調用(在i/o內調用由於下一階段爲check階段),其執行前後順序是不肯定的,須要看loop的執行前的耗時狀況。
相關文章
相關標籤/搜索