JavaScript的計時器的工做原理

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

原文翻譯:html

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

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

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

圖片描述

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

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

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

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

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

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

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

事實上,咱們能夠看到,當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引擎是若是工做的無疑是很重要的知識,尤爲是大量的典型的異步事件發生時,對於構建一個高效的應用代碼片斷來講是一個很是有利的基礎。

我的看法:

翻譯完成以後,感受對於javascript異步有了新的認識,可是可能初學者看不太懂這篇文章,因而寫了一個demo,運行在nodejs環境下(瀏覽器不容易模擬)

1 var startTime = new Date();
 2 
 3 //初始化計時器
 4 var start = setTimeout(function() {
 5     var end = new Date();
 6     console.log('10ms的計時器執行完成,距離程序開始' + (end - start) + 'ms');
 7 }, 10);
 8 
 9 //模擬鼠標點擊事件
10 function asyncReal(data, callback) {
11     process.nextTick(function() {
12         callback();      
13      });
14 }
15 var asyncStart = new Date();
16 asyncReal('yuanzm', function() {
17     var asyncEnd = new Date();
18     console.log('模擬鼠標執行事件完成,花費時間' + (asyncEnd - asyncStart) + 'ms');
19 })
20 
21 //設定定時器
22 count = 1;
23 var interval = setInterval(function() {
24     ++count;
25     if(count === 5) {
26         clearInterval(interval);
27     }
28     console.log('定時器事件');
29 },10);
30 
31 //模擬第一階段代碼執行
32 var first = [];
33 var start = new Date();
34 for(var i = 0;i < 10000000;i++){
35     first.push(i);
36 }
37 var end = new Date();
38 console.log('第一階段代碼執行完成,用時' + (end - start) + 'ms');

運行結果以下:

圖片描述

咱們按照文中的原理來解釋一下:

  1. 一開始設定的計時器並非在10ms後當即執行,而是被添加到了隊列後面,等到第一階段代碼執行完成才執行,距離開始的時間也不是設定的10ms
  2. 鼠標點擊事件一樣由於是異步事件,添加到了隊列後面,等到第一階段代碼執行完成的時候才執行。
  3. 鼠標點擊事件先於計時器事件添加到隊列後面
  4. 最後定時器才能執行

鄭重聲明
本文章屬於我的原創,如需轉載,請加上原文連接:
http://segmentfault.com/a/1190000002633108
另外一樣能夠在博客園上面查看本文章:http://www.cnblogs.com/yuanzm/p/4126762.html
也歡迎Follow個人Github:https://github.com/yuanzm

相關文章
相關標籤/搜索