以前有看過一些事件循環的博客,不過一陣子沒看就發現本身忘光了,因此決定來本身寫一個博客總結下!html
就咱們所知,瀏覽器的js是單線程的,也就是說,在同一時刻,最多也只有一個代碼段在執行,但是瀏覽器又能很好的處理異步請求,那麼究竟是爲何呢?咱們先來看一張圖(這張圖來自於http://www.zcfy.cc/article/node-js-at-scale-understanding-the-node-js-event-loop-risingstack-1652.html)
從上圖咱們能夠看出,js主線程它是有一個執行棧的,全部的js代碼都會在執行棧裏運行。在執行代碼過程當中,若是遇到一些異步代碼(好比setTimeout,ajax,promise.then以及用戶點擊等操做),那麼瀏覽器就會將這些代碼放到一個線程(在這裏咱們叫作幕後線程)中去等待,不阻塞主線程的執行,主線程繼續執行棧中剩餘的代碼,當幕後線程(background thread)裏的代碼準備好了(好比setTimeout時間到了,ajax請求獲得響應),該線程就會將它的回調函數放到任務隊列中等待執行。而當主線程執行完棧中的全部代碼後,它就會檢查任務隊列是否有任務要執行,若是有任務要執行的話,那麼就將該任務放到執行棧中執行。若是當前任務隊列爲空的話,它就會一直循環等待任務到來。所以,這叫作事件循環。node
其實(正如上圖所示),js是有兩個任務隊列的,一個叫作Macrotask Queue(Task Queue),一個叫作Microtask Queuegit
那麼,二者有什麼具體的區別呢?或者說,若是兩種任務同時出現的話,應該選擇哪個呢?
其實事件循環作的事情以下:github
其中,在執行代碼過程當中新增的microtask任務會在當前事件循環週期內執行,而新增的macrotask任務只能等到下一個事件循環才能執行了(一個事件循環只執行一個macrotask)
首先,咱們先來看一段代碼ajax
console.log(1) setTimeout(function() { //settimeout1 console.log(2) }, 0); const intervalId = setInterval(function() { //setinterval1 console.log(3) }, 0) setTimeout(function() { //settimeout2 console.log(10) new Promise(function(resolve) { //promise1 console.log(11) resolve() }) .then(function() { console.log(12) }) .then(function() { console.log(13) clearInterval(intervalId) }) }, 0); //promise2 Promise.resolve() .then(function() { console.log(7) }) .then(function() { console.log(8) }) console.log(9)
你以爲結果應該是什麼呢?
我在node環境和chrome控制檯輸出的結果以下:chrome
1 9 7 8 2 3 10 11 12 13
在上面的例子中
第一次事件循環:promise
第二次事件循環:瀏覽器
第三次事件循環:異步
第四次事件循環:函數
在這裏,你們能夠會想,在第一次循環中,爲何不是macrotask先執行?由於按照流程的話,不該該是先檢查macrotask隊列是否爲空,再檢查microtask隊列嗎?
緣由:由於一開始js主線程中跑的任務就是macrotask任務,而根據事件循環的流程,一次事件循環只會執行一個macrotask任務,所以,執行完主線程的代碼後,它就去從microtask隊列裏取隊首任務來執行。
因爲在執行microtask任務的時候,只有當microtask隊列爲空的時候,它纔會進入下一個事件循環,所以,若是它源源不斷地產生新的microtask任務,就會致使主線程一直在執行microtask任務,而沒有辦法執行macrotask任務,這樣咱們就沒法進行UI渲染/IO操做/ajax請求了,所以,咱們應該避免這種狀況發生。在nodejs裏的process.nexttick裏,就能夠設置最大的調用次數,以此來防止阻塞主線程。
以此,咱們來引入一個新的問題,定時器的問題。定時器是不是真實可靠的呢?好比我執行一個命令:setTimeout(task, 100),他是否就能準確的在100毫秒後執行呢?其實根據以上的討論,咱們就能夠得知,這是不可能的。
緣由我想你們應該也都知道了,由於你執行setTimeout(task,100)後,其實只是確保這個任務,會在100毫秒後進入macrotask隊列,但並不意味着他能馬上運行,可能當前主線程正在進行一個耗時的操做,也可能目前microtask隊列有不少個任務,因此這也多是你們一直詬病setTimeout的緣由吧哈哈哈哈
以上,只是我我的對事件循環的一些見解, 以及借鑑了其餘優秀文章
參考:
http://www.zcfy.cc/article/node-js-at-scale-understanding-the-node-js-event-loop-risingstack-1652.html
https://github.com/ccforward/cc/issues/47