JavaScript中的Timer是怎麼工做的

做爲入門者來講,瞭解JavaScript中timer的工做方式是很重要的。一般它們的表現行爲並非那麼地直觀,而這是由於它們都處在一個單一線程中。讓咱們先來看一看三個用來建立以及操做timer的函數。瀏覽器

  • var id = setTimeout(fn, delay); - 初始化一個單一的timer,這個timer將會在必定延時後去調用指定的函數。這個函數(setTimeout)將返回一個惟一的ID,咱們能夠經過這個ID來取消timer。
  • var id = setInterval(fn, delay); - 與setTimeout相似,只不過它會持續地調用指定的函數(每次都有一個延時),直到timer被取消爲止。
  • clearInterval(id);, clearTimeout(id); - 接受一個timer的ID(由上述的兩個函數返回的),而且中止timer的回調事件。

要搞明白timer在內部是怎麼工做的,咱們還須要知道一個很重要的概念:timer的延時並非每次都能如你所願的。因爲在同一個瀏覽器中全部的JavaScript都只在單一線程中執行,那些異步的事件(好比說鼠標點擊,或者timer)只在執行期出現空閒的時候纔會運行。這個用圖最能表示清楚了,請參見下圖:異步


(點擊查看大圖)

在這個示例中有不少信息能夠挖掘,可是徹底理解了以後你將會更清楚地認識到異步的JavaScript是怎麼執行的。這是個一維的圖:豎直方向上的是(掛鐘式)時間,單位爲毫秒。藍色的框表示正在執行的JavaScript片斷。舉例來講,第一塊JavaScript執行了約18ms,而鼠標點擊則執行了約11ms,以此類推。函數

因爲JavaScript向來都只能在同一時間執行一塊代碼(這是由它單線程的本質決定的),因此每個代碼塊都「阻塞」了其餘的異步事件。這意味着當異步事件發生時(好比鼠標點擊、timer觸發或者是XMLHttpRequest完成),這些事件將進入到一個隊列中等待執行(隊列的實現方法因瀏覽器而異,咱們在此只討論一個簡化的狀況)。post

剛開始,在第一個JavaScript塊中,有兩個timer被初始化了:一個10ms的setTimeout和一個是10ms的setInterval。因爲timer(這裏的timer指setTimeout中的timer,而下文中的interval則指setInvertal中的timer)開始的時間,實際上它在第一個代碼塊結束前就已經觸發了。然而請注意,它並不會立刻執行(事實上因爲單線程的存在,它也沒法作到立刻執行)。相反的,這個被延期執行的函數進入隊列中,等待在空閒的時候被執行。spa

另外,在第一個JavaScript塊中,咱們看到一個鼠標點擊事件也發生了。而與這個異步事件(咱們不知道用戶何時會去執行一個動做,所以將其認爲是一個異步動做)相關的JavaScript回調函數也沒法立馬執行,正如timer同樣,它也進行到隊列中等待被執行。線程

當第一個JavaScript塊被執行完以後,瀏覽器問了一個問題:有正在等待被執行的代碼嗎?在這個例子中,鼠標點擊事件和time事件都正在隊列中等待。因而瀏覽器選了一個(鼠標點擊事件),而後立刻執行它。而timer只能繼續等下去。code

注意當鼠標點擊事件正在執行的時候第一次的interval事件也觸發了,與timer同樣,它的事件也進入隊列等待以後執行。然而,注意,當interval再次觸發的時候(這個時候timer的事件正在執行),這一次它的事件被丟棄了。若是你在一個大的JavaScript代碼塊正在執行的時候把全部的interval回調函數都囤起來的話,其結果就是在JavaScript代碼塊執行完了以後會有一堆的interval事件被執行,而執行過程當中不會有間隔。所以,取代的做法是瀏覽器情願先等一等,以確保在一個interval進入隊列的時候隊列中沒有別的interval。隊列

事實上,咱們能夠在例子中看出:當第三個interval觸發的時候這個interval自身正在執行。這告訴咱們一個重要的事實:interval是無論當前在執行些什麼的,在任何狀況下它都會進入到隊列中去,即便這樣意味着每次回調之間的時間就不許確了。事件

最後,當第二個interval回調執行完後,咱們能夠看到隊列已經被清空,沒有什麼須要JavaScript引擎去執行的了。這代表瀏覽器如今等待一個新的異步事件發生。因而在50ms的時候咱們看到interval又觸發了。這同樣,因爲沒有什麼東西擋住了它的執行,它立刻就觸發了。ip

讓咱們來看一個例子,這個例子更好地闡釋了setTimeout和setInveral之間的區別。

  setTimeout ( function ( ) {
    /* 一個很長的代碼塊…… */
    setTimeout (arguments. callee, 10 );
  }, 10 );
 
  setInterval ( function ( ) {
    /* 一個很長的代碼塊…… */
  }, 10 );

乍看上去,這兩段代碼在功能上彷佛是相同的,可實際上並不是如此。setTimeout的代碼在前一次的回調執行完後老是至少會有10ms的延時(有可能會更多,可是絕對不會更少);而setInterval則老是在每10ms的時候嘗試執行一次回調,它無論上一次回調是何時執行的。

咱們在此學到了不少,讓咱們重述一下:

  • JavaScript引擎只有一個線程,這使得異步事件必需列隊等待執行。
  • setTimeout和setInterval在如何執行代碼上有着本質地區別。
  • 若是一個timer在將要執行的時候被阻塞,它將會等待下一個時機(比預期的延時要長)。
  • 若是interval的執行時間較長(比指定的延時長),那麼它們將連續地執行而沒有延時。

以上這些知識是至關重要的。知道JavaScript引擎的工做方式,尤爲是知道它在有不少異步事件發生的時候是怎麼工做的,爲咱們在寫進階的應用程序代碼打下了堅實的基礎。

相關文章
相關標籤/搜索