javascript中的定時器(How JavaScript Timers Work)

      javascript定時器工做原理是一個重要的基礎知識點。由於定時器在單線程中工做,它們表現出的行爲很直觀。咱們該如何建立和維護定時器呢?要從以下三個函數(都是定義在全局做用域,在瀏覽器中就是window的方法)提及:
javascript

  • var id=setTimeout(fn,delay);-初始化一個只執行一次的定時器,這個定時器會在指定的時間延遲delay以後調用函數fn,該setTimeout函數返回定時器的惟一id,咱們能夠經過這個id來取消定時器的執行。java

  • var id=setInvertal(fn,delay);-與setTimeout相似,只是它會以delay爲週期,反覆調用函數fn,直到咱們經過id取消該定時器。瀏覽器

  • clearInterval(id),clearTimeout(id),;-這兩個函數接受定時器的id(例如咱們上面提到的兩個函數產生的定時器id),並中止對定時器中指定函數的調用。異步

要深刻理解定時器工做原理,咱們須要探索一個重要的概念:定時器指定的延遲時間並不能獲得保證。在瀏覽器中,由於全部的javascript代碼都運行在單一線程之中,異步事件(如鼠標點擊,定時器)只有在他們被觸發的時候他們的回調纔有機會得以執行。咱們能夠用下圖說明:函數

查看大圖
動畫

       圖中包含大量的信息,吸取並理解這些信息,能幫助咱們領悟「異步的javascript代碼是如何工做的」。這個圖是一維的,垂直方向是時間,以毫秒爲單位。藍色的盒子表明正在執行的javascript代碼所佔時間片斷。例如 第一個javascript塊執行時間約18ms,第二個鼠標點擊塊執行了約11ms,其餘塊相似。spa

       由於單線程的緣故,在同一時間只能執行一條javascript代碼,每個代碼塊(藍色盒子)都會阻塞其餘異步事件的執行。這就意味着,當一個異步事件發生的時候(例如鼠標點擊,定時器觸發,一個XMLHttpRequest 請求完成),它進入了代碼的執行隊列,執行線程空閒時會依照該執行隊列中順序依次執行代碼。(如何將異步事件加入隊列,不一樣瀏覽器,他們的實現可能有所差別,因此這裏咱們將其簡單化)。線程

      開始的時候,在Javascript代碼塊(第一個盒子),初始化了兩個定時器,一個10ms延遲的setTimeout 和10ms的setInterval。這些定時器可能會在咱們第一個代碼塊執行結束以前就觸發,這取決於定時器在第一個代碼塊中啓動的位置和時間。注意,定時器雖然觸發了,可是並不會當即執行,它只是把須要延遲執行的函數加入了執行隊列,在線程的某一個可用的時間點,這個函數就可以獲得執行。code

       當第一個Javascript代碼初始化塊執行結束,瀏覽器當即提出一個問題:誰在等待着被執行? 在這個案例中鼠標點擊時間的處理程序和一個定時器(setTimeout)都在等待。瀏覽器選擇一個並執行(這裏是鼠標點擊事件的處理程序)。定時器就須要等待下一個可用時間來執行。隊列

       須要注意的是當鼠標點擊事件處理程序執行的時候,第一個interval定時器觸發了。和timeout定時器同樣,他的回調函數被加入了執行隊列,等待執行。然而,還須要注意到當interval定時器再次觸發,這個時候timeout定時器的回調函數正在執行,此時這個interval的觸發被放棄了。假想(瀏覽器不這樣作),在一個佔用時間不少的初始化定時器的代碼塊中,全部的interval觸發都把回調加入執行隊列,當初始化代碼塊結束後,執行隊列中已經累加了大量的定時器回調函數,結果就會出現大量的interval回調函數無間隔的執行,直到該執行隊列清空。因此瀏覽器在講一個interval回調加入執行隊列前,會檢查執行隊列,若是其中存在還沒有執行的interval回調那麼就等待,直到當前執行隊列中沒有相應interval的回調之後纔會繼續入隊interval回調。

       事實上,如圖,咱們看見在第一個Interval的回調執行的時候(以前進入執行隊列),第三個interval觸發了,這想咱們展現一個重要的現象:Interval不關心當前正在執行的代碼,他們會不加選擇的添加回調到執行隊列,儘管這意味着兩個interval回調函數執行的時間間隔被犧牲。這裏第一個interval回調執行結束後,緊跟着第三個interval的回調立刻獲得執行,中間沒有印象中應該有的10ms間隔。

       最終,在第三個interval的回調執行結束後,咱們看見執行隊列中沒有等待javascript引擎執行的代碼,這就意味着,瀏覽器如今等待新的異步事件的發生,在50ms的刻度處interval再次觸發,此時沒有什麼會阻塞javascript引擎,這個interval回調會當即執行。

讓咱們看一個例子來闡明,setInterval和setTimeout的不一樣,

setTimeout(function(){
    /* Some long block of code... */
    setTimeout(arguments.callee, 10);
  }, 10);
 
  setInterval(function(){
    /* Some long block of code... */
  }, 10);

看第一眼,會以爲這兩段代碼功能相同,實際上,他們是不一樣的。

       須要注意到,setTimeout的回調函數的執行老是保證了至少10ms的間隔(與上一個回調的執行相比,實際執行時,這個間隔可能變長,可是不可能更少),可是setInterval會嘗試每隔10ms執行一次回調,無論上一個回調函數時候已經執行完畢。(不少類庫的動畫都是使用的setTimeout實現)

這裏咱們學到不少,總結一下:

  • javascript引擎是單線程的,會迫使異步事件進入執行隊列,等待執行。

  • setTimeout和setInterval在執行異步代碼時從根本上是有所不一樣的。

  • 若是一個定時器事件被阻塞,使得它不能當即執行,那麼它會被延遲,直到下一個可能的時間點,才被執行(這可能比你指定的delay時間要長)

  • Interval的回調有可能‘背靠背’無間隔的執行,這種狀況是說interval的回調函數的執行時間比你指定的delay時間還要長

      這些都是構建javascript應用程序很是重要的知識。瞭解javascript Engine是如何工做的,特別存在大量的異步事件發生,爲構建高級應用程序代碼打下基礎。

相關文章
相關標籤/搜索