作為入門者來說。了解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的回調事件。要弄明確這個定時器內部是怎么工作的,有一個非常重要的概念須要被提出來:
1 定時器延遲是不準確的(guaranteed).由于全部的javascript 瀏覽器代碼僅僅有一個單線程運行,而且那些異步事件(如鼠標點擊事件,和定時器)僅僅會在出現線程空暇的時候去會運行。這有一個圖表演示。例如以下:
這里有非常多信息在這個圖中須要去理解。可是全然理解它之后。你會對javascript中異步機制有一個清楚的認識。
這個圖是一維的:
垂直方向我們用毫秒單位來標記這個時間。這個藍色的方塊代表這個正在被運行的javascript代碼。比如,這第一個被運行的javascript代碼大約花了18毫秒,這個鼠標事件塊大約花費了11毫秒,等等。
由于javascript 引擎永遠僅僅會執行一個片段的代碼在同一個時間(由于這個單線程機制),那么這時每個代碼塊將會堵塞(blocking)別的異步事件的執行。
這意味著當一個異步事件被調用(比如當一個鼠標點擊,一個定時器觸發firing,或者一個xmlhttprequest 過程完畢),它將會被增加到隊里。并延遲運行(至于詳細怎樣被入到隊列中,不同的瀏覽器有不同的實現。我們這里僅僅考慮簡單的情況)
從一開始,在第一個javasript中,有兩個定時器被初始化了: 一個10 毫秒的 setTimeout時間和一個10 毫秒的setInterval事件(這里注意,只不過初始化,亦或叫作定義)。
由于這個定時器開始的時間和位置。導致它們在第一個javascript塊完畢前就已經真正被調用(這里的調用,并不是直接運行,這里須要注意,能夠理解為僅僅是準備調用,把該回調方法增加到隊列)了。
注意,不管怎么樣(however)。定時器都不會立馬運行(由于線程沒有空暇的原因,它沒辦法直接運行)。
相反。這個被延遲的方法會被增加到隊列中,在某個能夠運行的時刻(線程空暇的時刻)運行。
另外一點,在第一個javascript塊中。我們能夠看到另一個鼠標事件被觸發了。這個javascript 回調方法被關聯到一個異步事件 (沒人知道用戶什么時候做這個動作。所以它被覺得是異步的),這個異步事件也不會立馬運行,和上面的定時器一樣。也會被增加到隊列中。
在第一個javascript 塊運行結束之后,javascript 引擎就會立馬問一個問題: 還有什么在等待被運行的代碼么? 那么這個時間,有一個鼠標事件回調和定時器回調同一時候在等待。這個瀏覽器立即挑選一個(從圖中看。是鼠標事件回調)立馬運行。這個定時器繼續等待,直到下一個可能的時刻。
注意一點:在這個這個鼠標事件處理函數正在被運行的同一時候,第一個interval 回調函數也會調用。
正如前面提到的定時器一樣,它的回調方法會被增加到隊列中。
然而。注意當這個interval再一次被調用(這個時候這個定時器的回調方法正在被運行)。那么這個時候。這個interval 的回調方法將會被刪除(drop)。
假設因為主線程須要運行非常長時間的代碼塊,導致你在隊列中增加了非常多個回調方法,那么當這個主線程結束之后,一連串的回調函數連續運行沒有間隔,直到結束。比較好的做法,是臨時讓瀏覽器歇息等待一會。讓隊列中沒有Interval回調。
我們在看到一些情況:在第三個interval 回調方法觸發的時候。inteval自身正在運行(這里應該是下在運行第二個interval沒有結束)。這里給我們展示了一個重要的信息:
interval 不會去關心當前的線程如今運行什么,它們會把自己的回調方法增加到隊列中在不論什么情況下,即使它會讓兩個間隔的回調方法之間的時間降低。
最后,在第二個interval(圖中應該是第三個,這里應為中間有一個被drop掉了)被 運行完之后,javasript引擎中已經沒有東西能夠用來運行了。
這也就是說。瀏覽器如今正在等一個新的異步事件須要去觸發(occur)。在第50毫秒的時候,再一次觸發了inteval回調。
這個時候。已經沒有東西去堵塞它的運行。所以它增加到隊列中之后就立馬運行了。
接下來,讓我們看一個樣例更好的理解setTimeout與setInterval的差別:
setTimeout(function(){/* Some long block of code... */setTimeout(arguments.callee, 10);}, 10);setInterval(function(){/* Some long block of code... */}, 10);
這兩段代碼可能在功能的實現上很的相似。不經意一看,他們是全然一樣的。
尤其是這個setTimeout代碼會在上一個回調函數運行之后至少隔10毫秒再運行一次回調方法(它可能會超過10毫秒,但不會少于10毫秒)。可是setInteval 卻會嘗試10毫秒就運行一個回調函數,不會去管上一個回調是什么時候運行的。
These two pieces of code may appear to be functionally equivalent, at first glance, but they are not. Notably the setTimeout code will always have at least a 10ms delay after the previous callback execution (it may end up being more, but never less) whereas the setInterval will attempt to execute a callback every 10ms regardless of when the last callback was executed.
這里有一些東西是我們從這里學到的,做一個總結:
1 javascript引擎只唯獨一個單線程,正在運行的異步事件會增加到隊列等待。
2 setTimeout與setInterval 是運行異步回調方法從根本上不一樣的。
3 假設一個須要馬上運行的定時器被堵塞了。它將被延遲運行。知道下一次線程空暇(那么被延遲的時間會超過定時器定義的時間)
4 interval 可能會沒有延遲的連續運行回調方法,假設主線程了運行一個足夠長的代碼(比定時的延遲長)
全部的這些都是很重要的知識對于了解javascript引擎是怎樣工作的。特別是對于大數量的回調事件發生的時候,為我們建立更好的應用代碼建立好的基礎。
----------------------------------------------------------------------------------------------------
原文出自jQuery的作者John Resig。
地址:http://ejohn.org/blog/how-javascript-timers-work/#postcomment