JavaScript中的定時器大家基本在平時的開發中都遇見過吧,但是又有多少人去深入的理解其中的原理呢?下面我們就來分析一下定時器的實現原理。
一、儲備知識
在我們在項目中一般會遇見過這樣的兩種定時器,第一種是setTimeOut,第二種是setInterval,這兩種定時器有如下的區別:
1、setTimeout允許設置一個超時對象,超時后執行這個對象,但是只執行一次,無周期
2、setInternval允許設置一個超時對象,超時后執行這個對象,周期等于超時對象指定的時間,周期為無限循環
舉一個簡單的例子來說明一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!DOCTYPE html> < html lang = "en" > < head > < meta charset = "UTF-8" > < title >blog案例</ title > </ head > < body > < script type = "text/javascript" > setTimeout("alert('this is test')",2000); setInterval("console.log('demo');",1000); </ script > </ body > </ html > |
這個運行后的結果是彈出了一次對話框,然后在控制臺可以看到每1秒鐘會向其中輸出demo字樣
二、定時器原理初識
那么問題來了,如下的代碼運行的時候會出現什么情況呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!DOCTYPE html> < html lang = "en" > < head > < meta charset = "UTF-8" > < title >blog案例</ title > </ head > < body > < script type = "text/javascript" > setTimeout("alert('定時器!')",0); alert("測試") </ script > </ body > </ html > |
是先執行alert("測試"),還是先執行alert("定時器")呢,那么我們就來運行一下吧!
運行后的結果是先彈出測試字樣的彈出框,然后才彈出定時器字樣的彈出框,為什么會這樣呢?不是定時器的時間為0就即可執行嗎?
答案不是這樣的,因為JS眾所周知是單線程的,所以很多人會認為在上面的例子中會先阻塞等待定時器執行完成后再執行下面的語句,但是這個也就是單線程的一個缺陷之一吧,為了解決這個問題,引入了異步機制。異步機制主要是利用一個我們平時很少去關注的一個知識點——瀏覽器的多線程。究竟什么是瀏覽器的多線程呢?
三、瀏覽器的多線程
這里我們就來講解一下,眾所周知,JS是單線程的,但是對于瀏覽器來說JS的執行只不過是在瀏覽器眾多現成中的一條,我們稱之為JS引擎線程。而瀏覽器的其他線程則是通過JS引擎線程在執行到某個特定的功能之后指定給瀏覽器的對應線程。具體的原理詳見圖示:
從這張圖我們可以知道JS引擎線程首先執行回調函數塊,然后是執行點擊事件回調,接著是執行定時器的線程,最后在執行其他的線程。
以下面的代碼我們來分析一下:
1
2
|
setTimeout( "alert('定時器!')" ,0); alert( "測試" ) |
首先JS線程讀取到setTimeout定時器,這個時候就會執行瀏覽器的線程,然后跳過定時器繼續執行,這個時候你就看到了彈出框的內容為測試,然后因為定時器的時間為0,所以一執行定時器線程就會即可將彈出框為定時器字樣的任務添加到主線程(JS引擎線程)的隊列之后,等待JS引擎的調用,這個時候我們看到的結果是先彈出測試,然后再彈出定時器
另外我們要注意在HTML5規范中規定定時器的定時時間不能小于4ms,如果是小于4ms,則默認為4ms,所以在這個例子中的0,默認的是4ms,但是這個在不通過的瀏覽器中的表現是不同的,但是這個一般在項目中是沒有什么印象的,這個只是僅做了解即可。
好的我們將上面的代碼改寫成這樣,然后我們再來看看效果:
1
2
3
4
5
6
|
<script type= "text/javascript" > console.time( "test" ); setTimeout( "for(var i=0;i<1000;i++)console.log('定時器!');" ,1000); console.log( "測試" ); console.timeEnd( "test" ); </script> |
運行后的結果如下:
這里有幾個知識點:
1、console.time和console.timeEnd這兩個方法是可以獲取在其中間執行的語句所用的時間,從圖中我們可以知道test執行的時間在1ms左右,然而定時器的定時時間是在1000ms左右所以這兩個語句只能計算當前引擎的執行時間,換句話說就是在瀏覽器中的定時器模塊的運行時間是這樣是沒法計算的
2、另外我們可以看到一個現象就是定時器在執行的時候不是一千個定時器的字樣全都一次性的打印出來,而是幾百幾百的增加,這個是為什么呢?這里就涉及到了另外的一個問題,如果是定時器的時間到了,但是定時器中的任務沒有執行完成這個時候會怎樣?
我們上面說過就是定時器的時間到了的情況下,就會向JS引擎線程添加任務,不論任務里面的語句是否執行完成,都會像JS引擎線程隊列中添加,但是剩下的未執行完成的語句怎么辦呢?
程序執行到了定時器任務的時候,就會先把已經在定時器模塊執行過的語句加載一次,然后是繼續執行定時器模塊的剩余語句。(定時器模塊向JS引擎中添加的任務相當于就是C語言中的一個指針,指向的是定時器模塊)
所以,setTimeout我們可以定義為:
在指定時間內, 將任務放入事件隊列,等待js引擎空閑后被執行.
四、setInterval的使用
setInterval最基礎的使用方法是直接當一個循環定時器使用,這里就不舉例說明
對于setInterval(fn, 100)容易產生一個誤區:并不是上一次fn執行完了之后再過100ms才開始執行下一次fn。 事實上,setInterval并不管上一次fn的執行結果,而是每隔100ms就將fn放入主線程隊列,而兩次fn之間具體間隔多久就不一定了,跟setTimeout實際延遲時間類似,和JS執行情況有關。具體的延遲效果與內存等因素有關。
五、定時器的可靠性
雖說定時器在大部分的情況下都是趨于穩定的,但是定時器在使用的時候也存在著一些誤差
如下所示:
1
2
3
4
5
6
7
|
<script type= "text/javascript" > var time1 = new Date().getTime(); setInterval( function (){ var time2 = new Date().getTime(); console.log( "setInterval執行的差值時間:" +(time2-time1)); },1000); </script> |
運行的結果如下:
從圖中我們基本可以看出定時器存在著一些小小的誤差就比如第一次的運行時間為1001ms比我們設定的時間多出了1ms,所以得出結論:定時器不是完全的可靠的,存在極小的誤差。這個還是在chrome瀏覽器上面測試的結果,換是在IE瀏覽器測試那又如何呢?
結果顯示,在IE瀏覽器下面的誤差更大
六、定時器的妙用
定時器在項目中除了可以作為定時的作用外還可以用來做耗時代碼的優化:
我們假設有這樣的一個場景,就是在某個頁面中要渲染50萬個節點,這個時候對于一般的項目中,直接渲染是不可取的,因為這個時候會占用過多的內存,導致瀏覽器出現了卡死的狀態,用戶誤以為是頁面卡死而 直接關閉瀏覽器或者殺死進程,即使是用戶不關閉頁面這樣給用戶的體驗也是不好的,這個時候我們要怎樣來解決這個問題呢,我們可以利用定時器來優化這個問題首先我們可以把50萬個節點分成多組,每組渲染 的節點數不要過多,然后通過setInterval來進行循環這個既不阻塞JS引擎線程的運行,又不可以提高渲染的消耗時間。從而達到最終的優化渲染。
七、定時器使用注意事項
如果是項目中有對個定時器的參與那么記得在一個定時器執行結束的時候記得要調用clearInterval或clearTimeout這兩個方法來清除定時器,以免定時器之間互相干擾出現一些抓摸不定的現象
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,同時也希望多多支持腳本之家!
原文鏈接:http://www.cnblogs.com/st-leslie/p/6082450.html
文章列表