javascript - event loop

javascript,區別於後臺,就是javascript是單線程的。單線程作到不阻塞,起到做用的其實就是咱們常說的異步。javascript

運行時概念

首先,咱們來理解一下javascript的幾個概念java

  • 堆(heap)
  • 棧(stack)
  • 任務隊列(queue),這裏又分爲宏任務 & 微任務

瀏覽器的event loop

當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的event loop

node代碼也是javascript,解析javascript的是V8引擎。異步i/o採用的是libuv。

node的event loop,有六個事件,依次循環

  • poll:獲取新的i/o事件,大部分事件都在這裏執行
  • check:執行setImmediate的回調
  • close:執行socket的close事件回調
  • timers:執行setTimeout、setInterval的回調
  • i/o:處理上一輪循環中少許未執行的i/o回調
  • idle,prepare:node內部使用

咱們來看下代碼的運行狀況

結果是這樣的:同步任務 - 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')
	})
})
複製代碼

process.nextTick()

在上面的例子中,咱們會發現,nextTick的執行老是比微任務要快。

在node中,nextTick實際上是獨立於event loop以外的,nextTick擁有本身的任務隊列,event loop,執行完一個階段以後,就會將nextTick中的全部任務先清空,再執行微任務。

寫在最後

瀏覽器的event loop 與node的event loop仍是有稍許不一樣,不過大體的概念是差很少的,只要弄懂其中的關係以後,代碼中出現的問題就迎刃而解。

相關文章
相關標籤/搜索