一、JS 單線程執行
二、定時器函數詳解
1、setTimeout()接受兩個參數,第一個是回調函數,第二個是推遲執行的毫秒數。
setTimeout(fn,0)的含義是,指定某個任務在主線程最早可得的空閒時間執行,也就是說,儘可能早得執行。它在"任務隊列"的尾部添加一個事件,因此要等到同步任務和"任務隊列"現有的事件都處理完,纔會得到執行。
2、setInterval()
setTimeout()只是將事件插入了"任務隊列",必須等到當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數。要是當前代碼耗時很長,有可能要等很久,所以並沒有辦法保證,回調函數一定會在setTimeout()指定的時間執行。
3、process.nextTick()
process.nextTick方法可以在當前"執行棧"的尾部----下一次Event Loop(主線程讀取"任務隊列")之前----觸發回調函數。也就是說,它指定的任務總是發生在所有異步任務之前。
4、setImmediate()
setImmediate方法則是在當前"任務隊列"的尾部添加事件,也就是說,它指定的任務總是在下一次Event Loop時執行,這與setTimeout(fn, 0)很像。
三、任務隊列執行邏輯
一下均參考自https://juejin.im/post/5a63470bf265da3e2c383068
四、案例
解析:
我們代碼的開始執行都是從script(全局任務)開始,這個全局任務屬於宏任務;代碼由上向下執行;
第1行,遇到了一個timer異步任務,屬於宏任務,放入宏任務隊列;
第8行,遇到log,內部沒有其它函數,直接輸出
main1
;第10行,函數的定義,不執行;
第17行,遇到new Promise(),進入回調函數內部:
1) 遇到promise.nextTick,屬於微任務,放入微任務隊列
nextTick3
;2) 遇到log,內部沒有其它函數,直接輸出
promise 1
;3) 遇到resolve回調,屬於微任務,放入微任務隊列
promise then
;第27行,遇到log,內部沒有其它函數,直接輸出
main2
;第29行,遇到promise.nextTick,屬於微任務,放入微任務隊列
nextTick4
;第33行,執行say():
1)遇到log,內部沒有其它函數,直接輸出
hello
;2)遇到promise.nextTick,屬於微任務,放入微任務隊列
nextTick2
;到此爲止,我們已經做了如下事情:
1)宏任務隊列中放入了一個timer函數;
2)輸出了
main1
,promise 1
,main2
,hello1
;3)微任務隊列中已經放入了
promise then
,nextTick3
,nextTick4
,nextTick2
;
此時,我們的全局任務已執行完成了,就要馬上執行完整個微任務隊列。但是在微任務中,process.nextTick 是一個特殊的任務,它會被直接插入到微任務的隊首(當然了,多個process.nextTick 之間也是先入先出的),優先級最高。所以,依次輸出
nextTick3
,nextTick4
,nextTick2
,promise then
這時,執行棧爲空了,可是別忘了,我們的宏任務隊列還放者一個timer函數待執行,進入timer函數:
1)遇到log,內部沒有其它函數,直接輸出
setTimeout
;2)遇到promise.nextTick,屬於微任務,放入微任務隊列
nextTick1
;這個timer宏任務也執行完了,就馬上執行完整個微任務隊列,微任務隊列目前只有一個任務,直接輸出
nextTick1
;這時,執行棧又爲空了,還有其它任務嗎? 沒有了,大功告成;
以上的這種當函數執行棧爲空,從任務隊列中去一個任務來執行。再次爲空,再取一個任務來執行,如此循環,這就是Event Loop,事件循環機制;
參考:
JavaScript 運行機制詳解:再談Event Loop:(http://www.ruanyifeng.com/blog/2014/10/event-loop.html)
淺析nodejs事件循環機制:(https://juejin.im/post/5a63470bf265da3e2c383068)