JavaScript的計時器的工做原理

最近都在看一些JavaScript原理層面的文章,恰巧看到了jQuery的做者的一篇關於JavaScript計時器原理的解析,因而坐臥不安地決定把原文翻譯成中文,一來是爲了和你們分享,二來是爲了加深本身對於JavaScript的理解。原文連接:http://ejohn.org/blog/how-javascript-timers-work/ javascript

原文翻譯:

從基礎層面來說,理解JavaScript計時器的工做原理是很重要的。因爲JavaScript是單線程的,因此不少時候計時器並非表現得和咱們的直觀想象同樣。讓咱們從下面的三個函數開始,它們可以讓咱們有機會去構造和操做計時器。 java

  • var id  =setTimeout(fn, delay); -建立了一個簡單的計時器,在通過給定的時間後,回調函數將會被執行。這個函數會返回一個惟一的ID,便於在以後某個時間能夠註銷這個計時器。
  • var id = setInterval(fn, delay); -和setTimeout相似,可是每通過一段時間(給定的延時),所傳遞的函數就會被執行一次,直到這個定時器被註銷。
  • clearInterval(id); , clearTimeout(id); -接受一個計時器ID(由以前兩種計時器返回)而且中止計時器回調函數的執行。

爲了理解計時器的內部工做原理,咱們首先須要瞭解一個很是重要的概念:計時器設定的延時是沒有保證的。由於全部在瀏覽器中執行的JavaScript單線程異步事件(好比鼠標點擊事件和計時器)都只有在它有空的時候才執行。這最好經過圖片來講明,就以下面這張圖所示: 瀏覽器

這一張圖片裏面有不少信息須要慢慢消化,可是完全地理解這張圖片將會讓你對JavaScript異步執行是如何工做的有一個更好的認識。這張圖片是從一維的角度來闡述的:在垂直方向是以毫秒計的時間,藍色的塊表明了 異步

當前正在執行的JavaScript代碼段。好比第一段JavaScript執行了大概18毫秒,鼠標點擊事件大概執行了11毫秒。 函數

因爲JavaScript每次只能執行一段代碼(基於它單線程的特性),因此全部這些代碼段都阻塞了其餘異步事件的執行。這就意味着,當一件異步事件(好比鼠標點擊,計時器觸發和一個XMLHttpRequest 請求完成)觸發的時候,這些事件的回調函數將排在執行隊列的最後去等待執行(排隊的方式因瀏覽器不一樣而不一樣,這裏只是一個簡化)。 spa

一開始,在第一段代碼段內,兩個計時器被初始化:一個10ms的 setTimeout 和一個10ms的setInterval。因爲計時器在哪兒初始化就在那兒開始計時,因此實際上計時器在第一段代碼執行完成以前就觸發了。然而,計時器的回調函數並非當即執行了(單線程限制了不能這樣作),相反的是,回調函數排在了執行隊列的最後,等到下一個有空的時間去執行。 線程

此外,在第一個代碼塊內咱們看到了一個鼠標點擊事件發生了。與之相關的javascript異步事件(咱們不可能預測用戶會在何時去採起這樣的動做,所以這個事件被視爲異步的)並不會當即執行。和計時器同樣的是,它被放到了隊列的最後去等待執行。 翻譯

在第一個代碼快執行完成的時候,瀏覽器會當即發出這樣的詢問:誰正在等待執行?這個時候,鼠標點擊處理程序和計時器回調函數都在等待執行。瀏覽器選擇了其中一個(鼠標點擊回調函數)而且當即執行它。爲了執行,計時器會等到下一個可能執行的時間。 code

咱們注意到,當鼠標點擊事件對應的處理程序正在執行的時候,第一個定時回調函數也要執行了。同定時計時器同樣,它也在隊列的後面等待執行。然而,咱們能夠注意到,當定時器再一次觸發(在計時器回調函數正在執行的時候),這一次定時器回調函數被丟棄了。若是在執行一大塊代碼塊的時候,你把全部的定時回調函數都放在隊列的最後,結果就是一大串定時回調函數將會沒有間隔的一塊兒執行,直到完成。相反,在把更多定時回調函數放到隊列以前,瀏覽器會靜靜的等待,知道隊列中的全部定時回調函數都執行完成。 blog

事實上,咱們能夠看到,當interval回調函數正在執行的時候,interval第三次被觸發。這給咱們一個很重要的信息:interval並不關心當前誰在執行,它的回調函數會不加區分地進入隊列,即便存在這個回調函數會被丟棄的可能。

最後,當第二個定時回調函數完成執行的時候,咱們能夠看到javascript引擎已經沒有什麼須要執行了。這意味着,瀏覽器如今正在等待一個新的異步事件的發生。咱們能夠看到在50ms的時候,定時回調函數再一次被觸發。然而,這一次,沒有其餘代碼阻塞他的執行了,因此他當即執行了定時回調函數。

讓咱們看一個例子來更好地闡述 setTimeout 和setInterval的區別。

1 setTimeout(function(){ 2 /* Some long block of code... */ 3 setTimeout(arguments.callee, 10); 4 }, 10); 5 6 setInterval(function(){ 7 /* Some long block of code... */ 8 }, 10);

第一眼看上去這兩段代碼在功能上是等價的,但事實上卻不是。值得注意的是,setTimeout 這段代碼會在每次回調函數執行以後至少須要延時10ms再去執行一次(多是更多,可是不會少)。可是setInterval會每隔10ms就去嘗試執行一次回調函數,無論上一個回調函數是否是還在執行。

從這裏咱們可以學到不少,讓咱們來歸納一下:

  • javascript引擎只有一個線程,迫使異步事件只能加入隊列去等待執行。
  • 在執行異步代碼的時候, setTimeout  和 setInterval  是有着本質區別的。
  • 若是計時器被正在執行的代碼阻塞了,它將會進入隊列的尾部去等待執行直到下一次可能執行的時間出現(可能超過設定的延時時間)。
  • 若是interval回調函數執行須要花很長時間的話(比指定的延時長),interval有可能沒有延遲背靠背地執行。

上述這一切對於理解js引擎是若是工做的無疑是很重要的知識,尤爲是大量的典型的異步事件發生時,對於構建一個高效的應用代碼片斷來講是一個很是有利的基礎。

相關文章
相關標籤/搜索