因為自己在平時工作中,有些功能需要用到定時器,但是定時器並不像我們表邊上看到的那樣,所以這周末我看看書查查資料,深入研究了一下JavaScript中的定時器,那么廢話不多說,下面進入我們今天的正題。
大家都知道JavaScript是單線程的,所以不管是定時器還是用戶的操作都是需要在線程隊列中排隊執行的。
一、定時器在執行線程隊列里的分析
為了更好的理解我還是直接寫個測試代碼來看一下(由於有人反映例子不是很直觀不好理解,所以我做了一個新的例子,修改於2016年8月29日),這樣分析起來更直觀一些
1 <meta charset="utf-8"> 2 </head> 3 <body> 4 <button id="btn1">開始</button> 5 <script> 6 var oBtn1= document.getElementById("btn1"); 7 function createEle(num){ 8 for (i=0;i<num;i++){ 9 var oDiv = document.createElement("div"); 10 oDiv.innerHTML = i+1; 11 } 12 } 13 oBtn1.addEventListener("click",function(){ 14 var time1 = new Date().getTime(); 15 createEle(20000); 16 var time2 = new Date().getTime(); 17 console.log("普通函數1執行完的時間:"+(time2-time1)); 18 setTimeout(function(){ 19 var time3 = new Date().getTime(); 20 console.log("第一個setTimeout里面開始執行的時間:"+(time3-time1)); 21 createEle(20000); 22 var time4 = new Date().getTime(); 23 console.log("setTimeout里面的函數運行時間:"+(time4-time3)); 24 },50); 25 setInterval(function(){ 26 var time5 = new Date().getTime(); 27 console.log("setInterval開始執行的時間:"+(time5-time1)); 28 createEle(20000); 29 },50); 30 setTimeout(function(){ 31 var time6 = new Date().getTime(); 32 console.log("第二個setTimeout里面開始執行的時間:"+(time6-time1)); 33 createEle(20000); 34 },100); 35 createEle(20000); 36 var time7 = new Date().getTime(); 37 console.log("普通函數2執行完的時間:"+(time7-time1)); 38 },false); 39 40 </script> 41 </body> 42 </html>
在上面這段測試代碼里面一共分為5個部分:
1.創建dom節點
2.第一個setTimeout函數延遲50ms
3.setInterval函數每隔50ms
4.第二個setTimeout函數延遲100ms
5.創建dom節點
沒有基礎的人一般認為都是按順序執行唄,這有啥好說的,有點基礎的人會認為先執行兩個創建dom節點的函數,再延遲50ms執行第一個setTimeout里面的內容,並且每隔50ms會觸發setInterval,然后延遲100ms執行第二個setTimeout里面的內容。但是事實並是這樣的!!!
下面我貼出代碼的運行結果來逐一的分析
從運行結果可以看出,首先結果打印普通函數1運行需要花費170ms(多次刷新頁面可能這個時間會不一樣,所以我們先姑且認為這個普通函數執行需要170ms)。下面接着打印了普通函數2運行需要花費170ms,然后下面才是定時器的執行時間打印結果。到這里就可以看出我要證明的第一個結論:1.js中的定時器是在當前函數里,所有普通函數執行完成才開始執行定時器的。
下面我再具體分析一下定時器在這期間的運行過程,來揭示下面的結論。
在程序里執行到170ms的時候,第一個普通函數執行完,這時由於還有其他普通函數的存在,所以setTimeout和setInterval都進入等待隊列並且開始計時,在220ms的時候由於還有普通函數2在執行占用着線程,所以setTimeout里面的函數無法執行,並且setInterval被二次觸發,但是也因為有其他普通函數占用着線程,所以依舊在隊列中等待執行。
從第二行執行結果可以看出在350ms的時候第二個普通函數已經執行完成,至此所有定時器以外的函數全部執行完畢,從第三行執行結果可以看出在351ms的時候第一個setTimeout立刻被執行,而並沒有再等待50ms,這是因為上面已經提到了第一個setTimeout在第一個普通函數執行完的170ms的時候就開始計時了,只是因為50ms之后,第二個普通函數依然占用着線程導致setTimeout無法執行,所以在所有定時器以外的函數全部執行完畢之后,第一個定時器立刻開始執行里面的內容。這也就是我要證明的第二個結論:2.定時器即使指定了時間,也不一定就能在指定的時間執行(只能比設置的時間長,不會比設置的時間短)。
有人可能會想那么351ms的時候setInterval也應該被觸發之后每50ms觸發一次,但是在351ms的時候setInterval並沒有被執行,這是因為第一個setTimeout里面的函數還在執行,所以setInterval還是在等待隊列中等待,在514ms的時候第一個setTimeout里面的函數執行完畢,則開始觸發setInterval。這是因為設置的時間都是相同的(50ms),所以就是按定時器函數寫的順序來執行的。
如果我們把setInterval改成30ms的話,那么在第一個setTimeout之前是會先執行一次setInterval的。代碼如下:

1 <html> 2 <head> 3 <title>JavaScript定時器test</title> 4 <meta charset="utf-8"> 5 </head> 6 <body> 7 <button id="btn1">開始</button> 8 <script> 9 var oBtn1= document.getElementById("btn1"); 10 function createEle(num){ 11 for (i=0;i<num;i++){ 12 var oDiv = document.createElement("div"); 13 oDiv.innerHTML = i+1; 14 } 15 } 16 oBtn1.addEventListener("click",function(){ 17 var time1 = new Date().getTime(); 18 createEle(20000); 19 var time2 = new Date().getTime(); 20 console.log("普通函數1執行完的時間:"+(time2-time1)); 21 setTimeout(function(){ 22 var time3 = new Date().getTime(); 23 console.log("第一個setTimeout里面開始執行的時間:"+(time3-time1)); 24 createEle(20000); 25 var time4 = new Date().getTime(); 26 console.log("setTimeout里面的函數運行時間:"+(time4-time3)); 27 },50); 28 setInterval(function(){ 29 var time5 = new Date().getTime(); 30 console.log("setInterval開始執行的時間:"+(time5-time1)); 31 createEle(20000); 32 },30); 33 setTimeout(function(){ 34 var time6 = new Date().getTime(); 35 console.log("第二個setTimeout里面開始執行的時間:"+(time6-time1)); 36 createEle(20000); 37 },100); 38 createEle(20000); 39 var time7 = new Date().getTime(); 40 console.log("普通函數2執行完的時間:"+(time7-time1)); 41 },false); 42 43 </script> 44 </body> 45 </html>
修改之后的執行結果如下圖所示:
從圖上結果也是可以引出我要說的第三個結論:3.定時器如果設置的時間都相同,是按照定時器函數寫的順序來執行的,如果設置的時間不同,則是時間設置小的定時器函數會先執行,時間設置大的后執行。
接着之前的分析,在setInterval被觸發之后,由於setInterval里面的函數沒有執行完(也是需要170ms),所以第二個setTimeout函數沒法立即被執行,需要等待setInterval里面的函數執行完畢之后,這也是再一次印證了我上面的結論2,后面就是觸發第二個setTimeout函數,等第二個setTimeout函數里面的函數執行完下面依次執行setInterval。
小結:
1.js中的定時器是在當前函數里,所有普通函數執行完成才開始執行定時器的。
2.定時器即使指定了時間,也不一定就能在指定的時間執行(只能比設置的時間長,不會比設置的時間短)。
3.定時器如果設置的時間都相同,是按照定時器函數寫的順序來執行的,如果設置的時間不同,則是時間設置小的定時器函數會先執行,時間設置大的后執行。
二、setTimeout和setInterval之間的區別
還是直接上代碼來進行對比
1 setTimeout(function cb(){ 2 console.log("setTimeout"); 3 setTimeout(cb,10); 4 },10); 5 setInterval(function(){ 6 console.log("setInterval"); 7 },10);
上面的代碼看上去功能似乎是一樣的,實際上兩者是有區別的,在setTimeout里要等里面的函數執行完(也就是前一個callback)再延遲10ms(甚至更久)才可以再次執行回調函數,而setInterval是每隔10ms就會去執行函數里面的內容,而不去管上一個是否執行完。這就是兩者之間的區別。
三、定時器延遲時間的可靠性
看了我第一部分貼出的運行結果之后,細心的人可能會發現我打印的定時器延遲會有1-2ms的偏差,就是下面我要看的延遲時間可靠性的問題了
廢話不多說直接上測試代碼(為了測試效果這里我極端一點把延遲時間設置為1ms,這樣數據結果會更加明顯一點)
1 var time1 = new Date().getTime(); 2 setInterval(function(){ 3 var time2 = new Date().getTime(); 4 console.log("setInterval執行的差值時間:"+(time2-time1)); 5 },1);
常理上來講運行的結果應該是1 2 3 4 5 6 8 9 ...
但是實際的運行結果如下:
可以看到平均的延遲時間大概在5ms左右,我是拿chrome瀏覽器測試的,據說不同的系統下不同的瀏覽器這個平均的時間都不一樣,如果愛鑽牛角尖的小伙伴可以去嘗試一下,這里就不深入展開了.
所以說定時器的延遲時間不宜設置過小,因為太小的話可能根本也達不到你想要的效果(不排除一些真可以控制在1ms左右的牛逼瀏覽器),而且根據設備硬件或者瀏覽器的不同可能延遲時間也會有少量的誤差
四、定時器的小妙用
有人會說定時器能有什么妙用,無外乎就是在手機翻轉屏的時候延遲幾秒獲取設備寬度等等。
我想說的不是這些而是利用定時器來提高性能的辦法。
首先我們來模擬一個js要動態創建十萬個DOM節點的場景,這種情況瀏覽器會花費大量的時間來執行,從而阻塞了其他代碼的執行。這時如果我們使用定時器把這十萬個DOM打散分成多個部分,這樣一下就好了很多。
五、合理管理定時器
大家都知道定時器,用完之后需要清除,如果不清楚同時多個定時器在一個頁面上跑,會損耗性能讓頁面瀏覽起來有卡頓感,尤其是定時器在動畫里面的應用,想象一下,如果制作一個動畫效果里面用了許多定時器,並且多個定時器同時運行,那么有可能就會出現本來后面要執行的一個動畫效果在前面就被提前執行了,這是我們不想看到的。
所以我們就要根據情況對定時器做一個合理的管理,還是拿做動畫為例,
1.動畫肯定要保持同一時間只執行一個定時器;
2.並且自己可以靈活的控制定時器的開啟和關閉。
在網上可以找到管理定時器的示例代碼,大家可以參考一下:

1 var timers = { 2 timerID : 0, 3 timers : [], 4 add : function (fn) { 5 this.timers.push(fn); 6 }, 7 start : function (){ 8 if(this.timerID) return; 9 (function runNext(){ 10 if(timers.timers.length>0){ 11 for(var i=0;i<timers.timers.length;i++){ 12 if(timers.timers[i]() === false){ 13 timers.timers.splice(i,1); 14 i--; 15 } 16 } 17 timers.timerID = setTimeout(runNext,10); 18 } 19 })(); 20 }, 21 stop : function(){ 22 clearTimeout(this.timerID); 23 this.timerID = 0; 24 } 25 };
其實管理的核心還是我上面提到的兩條。
總結
1.js中的定時器是在當前函數里,所有普通函數執行完成才開始執行定時器的;
2.定時器即使指定了時間,也不一定就能在指定的時間執行(只能比設置的時間長,不會比設置的時間短);
3.定時器如果設置的時間都相同,是按照定時器函數寫的順序來執行的,如果設置的時間不同,則是時間設置小的定時器函數會先執行,時間設置大的后執行;
4. setTimeout和setInterval在被觸發的定義上是有很大區別的;
5.定時器的延遲時間不宜設置過小;
6.利用定時器可以分解大量操作的代碼;
7.合理管理頁面中的定時器。
感謝大家的閱讀,有什么分析的不對的地方歡迎大家批評指出,如果喜歡本文,請點擊右下角的推薦哦~