javascript,區別於後臺,就是javascript是單線程的。單線程作到不阻塞,起到做用的其實就是咱們常說的異步。javascript
首先,咱們來理解一下javascript的幾個概念java
當javascript運行的時候,首先,代碼會進入執行棧,變量之類的會存儲在堆中,而任務隊列存儲的就是javascript中的異步任務。node
咱們來看下下面的例子,首先,script代碼會進入執行棧,而後執行同步代碼,接着將異步任務放到任務隊列中。promise
先執行同步代碼,打印1,2,Promise(promise中的代碼是同步執行的),3。瀏覽器
接着將異步任務放入任務隊列中,promise回調放入微任務中,setTimeout回調放到宏任務中。異步
在event loop中,執行棧的代碼執行完以後,在微任務隊列取一個事件放到執行棧中執行,當微任務隊列爲空時,就從宏任務中取一個事件放到執行棧中執行,如此反覆循環。socket
console.log(1)
setTimeout(() => {
console.log('setTimeout')
})
console.log(2)
new Promise((resolve, reject) => {
console.log('Promise')
resolve()
}).then(() => {
console.log('then')
})
console.log(3)
複製代碼
咱們修改一下代碼,咱們在promise的回調中又加了一個promise。oop
其餘不變,當執行第一個promise的回調時,同步執行第二個promise,這個沒有問題,此時,把第二個promise的回調加入到微任務中。ui
在下一次event loop中,先查看微任務隊列,因而執行第二個promise的回調,打印了then1。spa
最後,微任務隊列清空了,因而查看宏任務,執行setTimeout的回調。
console.log(1)
setTimeout(() => {
console.log('setTimeout')
})
console.log(2)
new Promise((resolve, reject) => {
console.log('Promise')
resolve()
}).then(() => {
console.log('then')
new Promise((resolve1, reject1) => {
console.log('Promise1')
resolve1()
}).then(() => {
console.log('then1')
})
})
console.log(3)
複製代碼
咱們前面的例子其實都是當即執行的代碼,當發送http請求時,請求先掛起,當請求結果回來時,再將請求回調加入到任務隊列中。
node代碼也是javascript,解析javascript的是V8引擎。異步i/o採用的是libuv。
node的event loop,有六個事件,依次循環
咱們來看下代碼的運行狀況
結果是這樣的:同步任務 - nextTick - 微任務 - 宏任務 - setImmediate
setTimeout(() => {
console.log('setTimeout')
})
new Promise((resolve, reject) => {
console.log('Promise')
resolve()
}).then(() => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
process.nextTick(() => {
console.log('nextTick')
})
複製代碼
當咱們把代碼嵌到異步i/o裏面呢
結果是這樣的:同步任務 - nextTick - 微任務 - setImmediate -宏任務
與剛剛不一樣的是,代碼放到異步i/o裏面,執行完poll以後,執行的是check,因此setImmediate會在宏任務以前
setTimeout(() => {
console.log('setTimeout')
setTimeout(() => {
console.log('setTimeout1')
})
new Promise((resolve, reject) => {
console.log('Promise')
resolve()
}).then(() => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
process.nextTick(() => {
console.log('nextTick')
})
})
複製代碼
最後,咱們來看一下node中宏任務與微任務的順序
結果是先把宏任務隊列中的回調所有執行完畢,接着執行所有nextTick,最後執行全部的微任務。
這個就是跟瀏覽器不一樣了,瀏覽器是執行完一個任務以後,先執行全部微任務,而後再執行下一個宏任務。
setTimeout(() => {
console.log('setTimeout')
Promise.resolve().then(() => {
console.log('then')
})
process.nextTick(() => {
console.log('nextTick')
})
})
setTimeout(() => {
console.log('setTimeout2')
Promise.resolve().then(() => {
console.log('then2')
})
process.nextTick(() => {
console.log('nextTick2')
})
})
複製代碼
在上面的例子中,咱們會發現,nextTick的執行老是比微任務要快。
在node中,nextTick實際上是獨立於event loop以外的,nextTick擁有本身的任務隊列,event loop,執行完一個階段以後,就會將nextTick中的全部任務先清空,再執行微任務。
瀏覽器的event loop 與node的event loop仍是有稍許不一樣,不過大體的概念是差很少的,只要弄懂其中的關係以後,代碼中出現的問題就迎刃而解。