JS 事件機制 Event Loop

談談Event Loop

不管是瀏覽器的Event loop 仍是 Nodejs 中的Event loop,都是基於JS 的單線程設計而生的(純屬我的理解),經過事件循環實現非阻塞異步執行效果。node

幾個概念

談到事件機制首先弄清楚主線程執行棧任務隊列這幾個概念。web

主線程:運行JS 代碼。JS 的單線程針對的是單一的主線程,不是隻能有一個線程。編程

執行棧:關於執行棧的概念結合執行上下文來看,《JavaScript深刻之執行上下文棧》promise

任務隊列Task Queue:即隊列,是一種先進先出的數據結構瀏覽器

三者的關係是:主線程要執行的都在執行棧裏,執行棧裏的內容是從任務隊列裏拿過來的。bash

關於任務

任務能夠劃分爲宏任務微任務數據結構

  • 宏任務MacroTask: 整個script , setTimeout, setInterval, setImmidiate(瀏覽器暫時不支持,只有IE10,可是在nodejs 中經常使用到)、I/O、UI Redering。異步

  • 微任務(MicroTask):Promise, Async/Await(底層就是Promise)、Process.nextTick (node獨有)、MutationObserver。async

具體執行時,任務還有另外一種的劃分就是同步任務異步任務函數

對於同步任務來講 ,放入執行棧裏按序依次執行的,可是對於異步任務而言,是在異步任務有告終果以後將異步任務的回調函數放到任務隊列裏等待主線程空閒時執行。注意這裏是在執行棧中若是遇到異步任務,是註冊異步任務的回調函數 ,把回調函數放到任務隊列中(這一步會產出任務)。

瀏覽器中的Event Loop

執行流程

事件循環的進程模型

  • 執行棧一開始認爲是空,將宏任務: 總體的script 壓入棧執行。
  • 執行過程當中遇到同步任務按序一步一步執行,遇到異步任務註冊異步任務的回調函數放到對應的任務隊列中(這裏有宏任務隊列,微任務隊列)。這樣就產生了新的macro-task 和 micro-task。
  • script 中代碼執行完,將script 出棧,也是一次宏任務出棧 。
  • 檢測任務隊列中是否有微任務,清理微任務隊列。這裏須要注意的是宏任務出隊是一個一個出,而微任務是一隊一隊出。
  • 更新頁面渲染
  • 檢測是否有web worker 任務,處理web worker。
  • 上述過程循環往復,直到兩個隊列都清空。

總結一下總體的流程:

主線程首先會執行一個宏任務,當此宏任務執行完以後,會去查看是否有微任務隊列。若是有,那就清空整個微任務隊列。若是沒有,會去查看宏任務隊列,取宏任務隊列中的第一個去執行,執行宏任務的過程當中遇到微任務,依次加入微任務隊列等待下次執行。(固然執行微任務的時候也會產生宏任務,主線程會放入宏任務隊列)。棧空後,再次讀取任務隊列中的任務,以此類推。

舉個例子

console.log('script start')
 setTimeout(() => {
     console.log('setTimeout 1');
     Promise.resolve().then(() => {
          console.log('promise1')
    })
}, 0)

 new Promise(resolve => {
    console.log('promise2');
    setTimeout(() => {
        console.log('setTimeout2');
        resolve();
    }, 0)
 })

 Promise.resolve().then(() => {
     console.log('promise3');
     setTimeout(() => {
          console.log('setTimeout 3')
    },0)
 }).then(() => console.log('promise4'))
 console.log('script end');
複製代碼

輸出結果

script start 
promise2
script end
promise3
promise4
setTimeout1
promise1
setTimeout2
setTimeout3
複製代碼

詳細執行流程以下:

1. 初始宏任務隊列只有總體script,執行棧、微任務隊列皆爲空

宏任務script入棧執行:

輸出 script start

碰見setTimeout1放入宏任務隊列

執行同步代碼輸出 promise2,碰見setTimeout2放入宏任務隊列

碰見微任務promise3promise4放入微任務隊列

輸出 script end

咱們看下第一個宏任務執行完的狀態:

宏任務隊列: setTimeout1setTimeout2

微任務隊列: promise3promise4


2. 接下來去清理微任務隊列

微任務隊列遵循先進先出原則,promise3入棧執行:輸出 promise三、,將setTimeout3放入宏任務隊列。promise3出棧 ,promise4入棧執行,輸出 promise4

清理完微任務後的狀態:

宏任務隊列setTimeout1setTimeout2setTimeout3

微任務隊列:空


3. 從宏任務隊列中取出第一個入棧執行

setTimeout1 入棧執行,輸出 setTimeout1 ,將 promise1放入微任務隊列

本次執行完的狀態:

宏任務隊列setTimeout2setTimeout3

微任務隊列promise1


4. 清理微任務隊列

輸出 promise1

本輪執行完狀態:

宏任務隊列setTimeout2setTimeout3

微任務隊列: 空


五、 從宏任務隊列中取出第一個執行

輸出 setTimeout2

本輪執行完狀態:

宏任務隊列setTimeout3

微任務隊列: 空


6. 微任務隊列爲空,繼續從宏任務隊列取出第一個執行

輸出 setTimeout3

至此宏任務隊列微任務隊列都爲空。

從上述執行過程當中, 不難看出,瀏覽器的Event Loop 老是一個宏任務 ——> 一隊微任務 這種順序依次循環

另外一個例子

var a = async function () {
    await Promise.resolve().then(() => console.log(111));
    console.log(222)
}
a().then(() => console.log(333))

var b = async function () {
    await setTimeout(() => console.log('aaa'), 0);
    console.log('bbb')
}
b().then(() => console.log('ccc'))

var c = async function () {
    await console.log('A');
    console.log('B')
}
c().then(() => console.log('C'))

複製代碼

輸出結果:

A -> 111 -> bbb -> B -> 222 -> ccc -> C -> 333 -> aaa

執行順序:

1. script入棧

執行同步代碼,調用a():將111放入微任務隊列。繼續往下走執行b(),將aaa 放入宏任務隊列,將 bbb 放入微任務隊列,接着執行c(), 打印 A, 將B 放入微任務隊列。

這次執行完的狀態:

宏任務隊列: aaa

微任務隊列: 111bbbB

2. 清理微任務隊列

執行 111, 打印111 、將222放入微任務隊列,執行bbb:打印bbb並將 ccc放入微任務隊列,執行B: 打印B 並將C放入微任務隊列,此時微任務隊列並無清理完,因此接着打印222cccC,打印完222333放置微任務隊列,因此接着打印333

這次執行完的狀態:

宏任務隊列: aaa

微任務隊列: 空

  1. 執行aaa, 打印出aaa

上述代碼如果很差理解能夠轉化爲promise的方式來理解,效果以下:

new Promise(resolve => {
   	Promise.resolve()
        .then(() => console.log(111))
        .then(() => { 
	    console.log(222)
	    resolve()
	})
})
.then(() => console.log(333))

new Promise(resolve => {
	setTimeout(() => console.log('aaa'), 0)
	Promise.resolve().then(() => {
            console.log('bbb')
            resolve()
	})
	
})
.then(() => console.log('ccc'))

new Promise(resolve => {
	console.log('A')
	Promise.resolve()
	.then(() => {
	    console.log('B')
	    resolve()
	})
})
.then(() => console.log('C'))

複製代碼

但這純屬我的理解,如有問題,還請大神指導。

Nodejs中的Event Loop

nodejs

1.簡介

Node 中的 Event Loop和瀏覽器中的是徹底不相同的東西。Node.js採用V8做爲js的解析引擎,而I/O處理方面使用了本身設計的libuvlibuv是一個跨平臺、專門寫給nodejs的庫,使用異步,事件驅動的編程方式,核心是提供I/O的事件循環和異步回調。封裝了不一樣操做系統一些底層特性,對外提供統一的API。

2.六個階段

未完待續...

相關文章
相關標籤/搜索