JavaScript定時器原理分析

JavaScript中的定時器你們基本在平時的開發中都碰見過吧,可是又有多少人去深刻的理解其中的原理呢?下面咱們就來分析一下定時器的實現原理。javascript

1、儲備知識

在咱們在項目中通常會碰見過這樣的兩種定時器,第一種是setTimeOut,第二種是setInterval,這兩種定時器有以下的區別:html

一、setTimeout容許設置一個超時對象,超時後執行這個對象,可是隻執行一次,無週期java

二、setInternval容許設置一個超時對象,超時後執行這個對象,週期等於超時對象指定的時間,週期爲無限循環chrome

舉一個簡單的例子來講明一下:瀏覽器

<!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字樣多線程

 

2、定時器原理初識

那麼問題來了,以下的代碼運行的時候會出現什麼狀況呢?異步

<!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衆所周知是單線程的,因此不少人會認爲在上面的例子中會先阻塞等待定時器執行完成後再執行下面的語句,可是這個也就是單線程的一個缺陷之一吧,爲了解決這個問題,引入了異步機制。異步機制主要是利用一個咱們平時不多去關注的一個知識點——瀏覽器的多線程。究竟什麼是瀏覽器的多線程呢?優化

 

3、瀏覽器的多線程

 這裏咱們就來說解一下,衆所周知,JS是單線程的,可是對於瀏覽器來講JS的執行只不過是在瀏覽器衆多現成中的一條,咱們稱之爲JS引擎線程。而瀏覽器的其餘線程則是經過JS引擎線程在執行到某個特定的功能以後指定給瀏覽器的對應線程。具體的原理詳見圖示:

從這張圖咱們能夠知道JS引擎線程首先執行回調函數塊,而後是執行點擊事件回調,接着是執行定時器的線程,最後在執行其餘的線程。

如下面的代碼咱們來分析一下:

setTimeout("alert('定時器!')",0);
alert("測試")

首先JS線程讀取到setTimeout定時器,這個時候就會執行瀏覽器的線程,而後跳過定時器繼續執行,這個時候你就看到了彈出框的內容爲測試,而後由於定時器的時間爲0,因此一執行定時器線程就會便可將彈出框爲定時器字樣的任務添加到主線程(JS引擎線程)的隊列以後,等待JS引擎的調用,這個時候咱們看到的結果是先彈出測試,而後再彈出定時器

另外咱們要注意在HTML5規範中規定定時器的定時時間不能小於4ms,若是是小於4ms,則默認爲4ms,因此在這個例子中的0,默認的是4ms,可是這個在不經過的瀏覽器中的表現是不一樣的,可是這個通常在項目中是沒有什麼印象的,這個只是僅作了解便可。

 好的咱們將上面的代碼改寫成這樣,而後咱們再來看看效果:

<script type="text/javascript">
        console.time("test");
        setTimeout("for(var i=0;i<1000;i++)console.log('定時器!');",1000);

        console.log("測試");
        console.timeEnd("test");
    </script>

運行後的結果以下:

這裏有幾個知識點:

一、console.time和console.timeEnd這兩個方法是能夠獲取在其中間執行的語句所用的時間,從圖中咱們能夠知道test執行的時間在1ms左右,然而定時器的定時時間是在1000ms左右因此這兩個語句只能計算當前引擎的執行時間,換句話說就是在瀏覽器中的定時器模塊的運行時間是這樣是無法計算的

二、另外咱們能夠看到一個現象就是定時器在執行的時候不是一千個定時器的字樣全都一次性的打印出來,而是幾百幾百的增長,這個是爲何呢?這裏就涉及到了另外的一個問題,若是是定時器的時間到了,可是定時器中的任務沒有執行完成這個時候會怎樣?

咱們上面說過就是定時器的時間到了的狀況下,就會向JS引擎線程添加任務,不論任務裏面的語句是否執行完成,都會像JS引擎線程隊列中添加,可是剩下的未執行完成的語句怎麼辦呢?

程序執行到了定時器任務的時候,就會先把已經在定時器模塊執行過的語句加載一次,而後是繼續執行定時器模塊的剩餘語句。(定時器模塊向JS引擎中添加的任務至關於就是C語言中的一個指針,指向的是定時器模塊)

因此,setTimeout咱們能夠定義爲:

在指定時間內, 將任務放入事件隊列,等待js引擎空閒後被執行.

 

4、setInterval的使用

setInterval最基礎的使用方法是直接當一個循環定時器使用,這裏就不舉例說明

對於setInterval(fn, 100)容易產生一個誤區:並非上一次fn執行完了以後再過100ms纔開始執行下一次fn。 事實上,setInterval並無論上一次fn的執行結果,而是每隔100ms就將fn放入主線程隊列,而兩次fn之間具體間隔多久就不必定了,跟setTimeout實際延遲時間相似,和JS執行狀況有關。具體的延遲效果與內存等因素有關。

 

5、定時器的可靠性

雖然說定時器在大部分的狀況下都是趨於穩定的,可是定時器在使用的時候也存在着一些偏差

以下所示:

<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瀏覽器下面的偏差更大

 

6、定時器的妙用

 定時器在項目中除了能夠做爲定時的做用外還能夠用來作耗時代碼的優化:
 咱們假設有這樣的一個場景,就是在某個頁面中要渲染50萬個節點,這個時候對於通常的項目中,直接渲染是不可取的,由於這個時候會佔用過多的內存,致使瀏覽器出現了卡死的狀態,用戶誤覺得是頁面卡死而  直接關閉瀏覽器或者殺死進程,即便是用戶不關閉頁面這樣給用戶的體驗也是很差的,這個時候咱們要怎樣來解決這個問題呢,咱們能夠利用定時器來優化這個問題首先咱們能夠把50萬個節點分紅多組,每組渲染  的節點數不要過多,而後經過setInterval來進行循環這個既不阻塞JS引擎線程的運行,又不能夠提升渲染的消耗時間。從而達到最終的優化渲染。

 

7、定時器使用注意事項

 若是是項目中有對個定時器的參與那麼記得在一個定時器執行結束的時候記得要調用clearInterval或clearTimeout這兩個方法來清除定時器,以避免定時器之間互相干擾出現一些抓摸不定的現象

相關文章
相關標籤/搜索