最近都在看一些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異步有了新的認識,可是可能初學者看不太懂這篇文章,因而寫了一個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');
運行結果以下:
咱們按照文中的原理來解釋一下:
鄭重聲明
本文章屬於我的原創,如需轉載,請加上原文連接:
http://segmentfault.com/a/1190000002633108
另外一樣能夠在博客園上面查看本文章:http://www.cnblogs.com/yuanzm/p/4126762.html
也歡迎Follow個人Github:https://github.com/yuanzm