不管是瀏覽器的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
具體執行時,任務還有另外一種的劃分就是同步任務
和異步任務
函數
對於同步任務來講 ,放入執行棧裏按序依次執行的,可是對於異步任務而言,是在異步任務有告終果以後將異步任務的回調函數放到任務隊列裏等待主線程空閒時執行。注意這裏是在執行棧中若是遇到異步任務,是註冊異步任務的回調函數 ,把回調函數放到任務隊列中(
這一步會產出任務
)。
事件循環的進程模型
總結一下總體的流程:
主線程首先會執行一個宏任務,當此宏任務執行完以後,會去查看是否有微任務隊列。若是有,那就清空整個微任務隊列。若是沒有,會去查看宏任務隊列,取宏任務隊列中的第一個去執行,執行宏任務的過程當中遇到微任務,依次加入微任務隊列等待下次執行。(
固然執行微任務的時候也會產生宏任務,主線程會放入宏任務隊列
)。棧空後,再次讀取任務隊列中的任務,以此類推。
舉個例子
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
放入宏任務隊列碰見微任務
promise3
、promise4
放入微任務隊列輸出
script end
咱們看下第一個宏任務執行完的狀態:
宏任務隊列: setTimeout1
、setTimeout2
微任務隊列: promise3
、 promise4
2. 接下來去清理微任務隊列
微任務隊列遵循先進先出原則,
promise3
入棧執行:輸出promise三、
,將setTimeout3
放入宏任務隊列。promise3
出棧 ,promise4
入棧執行,輸出promise4
清理完微任務後的狀態:
宏任務隊列:setTimeout1
、setTimeout2
、setTimeout3
微任務隊列:空
3. 從宏任務隊列中取出第一個入棧執行
setTimeout1
入棧執行,輸出setTimeout1
,將promise1
放入微任務隊列
本次執行完的狀態:
宏任務隊列: setTimeout2
、setTimeout3
微任務隊列: promise1
4. 清理微任務隊列
輸出
promise1
本輪執行完狀態:
宏任務隊列: setTimeout2
、setTimeout3
微任務隊列: 空
五、 從宏任務隊列中取出第一個執行
輸出
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
微任務隊列:
111
、bbb
、B
2. 清理微任務隊列:
執行 111, 打印
111
、將222
放入微任務隊列,執行bbb
:打印bbb
並將ccc
放入微任務隊列,執行B
: 打印B
並將C
放入微任務隊列,此時微任務隊列並無清理完,因此接着打印222
、ccc
、C
,打印完222
將333
放置微任務隊列,因此接着打印333
。
這次執行完的狀態:
宏任務隊列:
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'))
複製代碼
但這純屬我的理解,如有問題,還請大神指導。
1.簡介
Node 中的 Event Loop和瀏覽器中的是徹底不相同的東西。Node.js採用V8做爲js的解析引擎,而I/O處理方面使用了本身設計的libuv
,libuv
是一個跨平臺、專門寫給nodejs的庫,使用異步,事件驅動的編程方式,核心是提供I/O的事件循環和異步回調。封裝了不一樣操做系統一些底層特性,對外提供統一的API。
2.六個階段
未完待續...