話很少說先看代碼來引出今天的問題javascript
//下面兩個定時器的輸出的前後順序是啥呢?
setTimeout(function(){
console.log("200")
},200)
//不瞭解ES6的朋友,把let 當成var 就好
for(let i = 0 ; i < 1000 ; i++){
console.log('---');
}
setTimeout(function(){
console.log('0')
//實際不可能會是0ms,定時器有一個最低的延時爲4ms,形成這個的緣由,我相信聰明的你,
//確定能在下面的世界輪循機制中找到答案(定時器觸發線程和主線程的取出,會有必定的執行時間)
},0)
//而下面兩個定時器的輸出結果又是啥呢?
setTimeout(function(){
console.log("200")
},200)
for(let i= 0; i < 5000 ; i++){
console.log("---");
}
setTimeout(function(){
console.log("0")
},0)
複製代碼
//上面兩個的答案分別是 0 200; 200 0 複製代碼
。那麼問題來了,第二個定時的delay(延遲時間,如下都用這個單詞表示了)明明是 0ms(實際大約4ms,代碼中解釋了,下面再也不作解釋)。第一個定時器的delay 是 200ms,爲啥第一個代碼正常輸出,而第二個代碼確實 delay爲200ms 的先輸出?
前端
如今帶着咱們的問題來看看js的事件輪循(Event Loop)機制:java
再來看看同步任務具體執行的過程ajax
function foo(){
function bar(){
console.log("bar"); }
bar();
console.log("foo");
}
foo();複製代碼
咱們來具體看看上面的執行過程json
咱們再來深刻了解下執行棧:瀏覽器
上面代碼咱們只套了一層函數,若是套多層函數,或者有多個bar的同級函數是有區別的。bash
多層嵌套很簡單,就按照上面的流程依次內推就行了,網絡
同級函數則是是重複 3,4,5的步驟。bar執行完畢,彈出棧,bar後面的代碼繼續執行碰到函數執行則走3,4,5,步驟。dom
4.異步任務具體的執行過程異步
$.ajax({
url: ‘localhost:/js/demo.json’,
data: {},
success: function (data) {
console.log(data);
}
});
console.log(‘run’);
複製代碼
5.換一張圖繼續理解
對2 作一點補充:
細心的朋友已經發行,我在上面寫 主線程的時候()裏面寫了一個調用棧。沒錯 執行棧其實至關於js主線程。個人我的理解,js單線程執行是,遇到同步的代碼,從上到下依次(預編譯的問題另說),遇到異步的代碼就一腳踢開,讓該管異步代碼的去管理(參考第一點瀏覽器常駐線程)。等同步代碼執行完畢以後,再去看看Event Queue(任務隊列)裏面看看有沒有,能夠執行的代碼(回調,定時器,事件),有就拿過來執行,沒有就一會再來看看(這個事件特別短,也多是有專門的觸發機制,總的就是 只有執行棧爲空,Event Queue裏面有任務就會立刻拿來執行)
好了,說到這裏,就能夠回頭來看看咱們最開始拋出的問題:
對上面代碼的分析:
由上面的文字能夠分析出,只要for循環的執行時間超過了200ms,第一個定時器就先進入Event Queue中(任務隊列,先進先出,後進後出。先進去的就先執行),第二個定時器是在第一個定時器已經進入了Event Queue 以後再觸發的,無論他的delay多小也只有後輸出。
而for循環的執行時間沒有超過200ms時(低於先觸發的定時器的delay),for循環執行完畢後,他還在面壁思過的數數,js主線程繼續往下走,觸發了第二個定時器,依舊一腳踢開,去面壁思過數數,這個時候,只要誰先數完,誰就先進入Event Queue 就先執行 。 上面代碼的狀況是 delay 爲0ms 的先數完,因此先執行,delay爲200ms後進入Event Queue 後執行。
你覺得這樣就完了嗎?若是是這樣敢說深度剖析定時器?看代碼
//表示執行次數的變量
let count = 0; /
/開始時間,用來定時的,記錄執行的間隔時間
// + 爲一元 '+' 號運算符,將其操做數隱式轉換成數字
let starTime = +new Date();
function sleep (num){
for(let i = 0 ;i < num ; i++){
console.log(i);
}
}
setInterval(function(){
count++;
console.log(+new Date() - starTime , count);
starTime = +new Date();
},1000)
sleep(20000);複製代碼
先上執行結果
上面的執行結果除了第二次的都很好解釋。第一次執行,時間這麼多的緣由是,運行for循環完了以後才能執行定一次的定時器,3以後的就趨於穩定 大概等於delay。
先拋出問題:
首先主線程一直在運行的時候,setInterval是每到一個delay就往Event Queue推出一個執行函數嗎?若是是這樣的話,如圖所示第一次執行被阻塞的時候爲3000 + ,因此能往Evnet Queue裏面註冊三個定時器,爲啥只有第二次的執行間隔時間發生比較大的差距,第三次之後就正常了? 爲何 第一次和第二次執行的間隔時間相加總約等於delay的倍數,這是巧合仍是必然?
回答問題:
咱們先定義一些參數,好方便如下的解釋:
fn1 爲定時器的第一次 , fn 2 爲定時器的第二次 , fn3 爲定時器 第三次和之後的無限次
關於上面的第一個問題很容易回答, setInterval 確定不是沒到一個delay就往Event Queue 推送一個執行函數 ,若是是的話如上代碼就會有三個執行函數在任務隊列裏面了,當主線程執行完畢後,去Event Queue拿函數回去執行會很是快,不可能會出現,fn2,fn3執行間隔這麼大。 其實第三個問題纔是解題的關鍵,是仔細想想,什麼狀況下才能出現這種相加爲倍數的狀況(好吧,其實怎麼想,我也說不清楚)。在試驗的過程當中,甚至出現過fn1 的執行間隔爲3950 ,fn2的執行間隔爲49的狀況,當時確實給我形成了很大的悟道,後面經過不斷的實驗,加詢問最終得出告終論
解決:出現這個事情的緣由是,Event Queue 裏面只能存在同一個定時器的一次事件,也就是說在定時器第一次被拿到主線程取走以前,第二次並不會進入Event Queue 。會依舊再Event Table 裏面等待。這個等待並非盲目的等待,在每個delay週期都看看Event Queue 裏面 上一次 的進去的定時器(fn1) 被主線程取走沒有,當取走後,就會在當前delay週期完的時候,把這一次的定時器(fn2)推入 Event Queue ,而這個時候主線程正好沒有任務正在執行,主線程就會馬上把此次的定時器放入到主線程執行,就形成了,定時器第一次執行和第二次執行的間隔時間相加總等於delay的倍數。 fn3以後的就屬於正常狀況了,當主線程沒有任務,Event Queue 中沒有定時器時,就每隔delay執行一次。
第一次寫掘金文章(也是第一次寫文章),清辯證看待,其中的一些錯別字和錯誤。若是對你有幫助,別忘了點個贊喲。
最後打個廣告,本人男,22歲,在校大四學習。座標成都,但願能找個前端的正式崗或者實習崗工做。若是有招人或者內推的大佬,能夠留言細聊喲。