setTimeout用法在實際項目中仍是會時常遇到。好比瀏覽器會聰明的等到一個函數堆棧結束後才改變DOM,若是再這個函數堆棧中把頁面背景先從白色設爲紅色,再設回白色,那麼瀏覽器會認爲DOM沒有發生任何改變而忽略這兩句話,所以咱們能夠經過setTimeout把“設回白色”函數加入下一個堆棧,那麼就能夠確保背景顏色發生過改變了(雖然速度很快可能沒法被察覺)。javascript
總之,setTimeout增長了Javascript函數調用的靈活性,爲函數執行順序的調度提供極大便利。而後,咱們從基礎的層面來看看:理解JavaScript的定時器是如何工做的是很是重要的。計時器的執行經常和咱們的直觀想象不一樣,那是由於JavaScript引擎是單線程的。咱們先來認識一下下面三個函數是如何控制計時器的。java
var id = setTimeout(fn, delay); - 初始化一個計時器,而後在指定的時間間隔後執行。該函數返回一個惟一的標誌ID(Number類型),咱們可使用它來取消計時器。 var id = setInterval(fn, delay); - 和setTimeout有些相似,但它是連續調用一個函數(時間間隔是delay參數)直到它被取消。 clearInterval(id);, clearTimeout(id); - 使用計時器ID(setTimeout 和 setInterval的返回值)來取消計時器回調的發生
有一個基本的概念你得記住了:程序員
時間延遲不能被保證。什麼意思,就是說你這樣寫setTimeout(fn, 500)並不表明fn確定在500毫秒以後立刻就執行,延遲極可能會更長。由於 JavaScript 是單線程語言,全部的異步事件(包括計時器、鼠標事件或者一個 XMLHttpRequest 完成)僅僅當程序執行期間有缺口的時候纔會執行,不是你規定了何時就何時執行,要知道程序員不是萬能的,你寫的東西最終仍是要看瀏覽器臉色的。瀏覽器
用一個很好的圖表加以說明:異步
JavaScript引擎用單線程運行也是有意義的,單線程沒必要理會線程同步這些複雜的問題,問題獲得簡化.函數
那麼單線程的JavaScript引擎是怎麼配合瀏覽器內核處理這些定時器和響應瀏覽器事件的呢?
下面結合瀏覽器內核處理方式簡單說明.spa
瀏覽器內核實現容許多個線程異步執行,這些線程在內核制控下相互配合以保持同步.假如某一瀏覽器內核的實現至少有三個常駐線 程:javascript引擎線程,界面渲染線程,瀏覽器事件觸發線程,除些之外,也有一些執行完就終止的線程,如Http請求線程,這些異步線程都會產 生不一樣的異步事件,下面經過一個圖來闡明單線程的JavaScript引擎與另外那些線程是怎樣互動通訊的.雖然每一個瀏覽器內核實現細節不一樣,但這其中的 調用原理都是大同小異.線程
由圖可看出,瀏覽器中的JavaScript引擎是基於事件驅動的,這裏的事件可看做是瀏覽器派給它的各類任務,這些任務能夠源自 JavaScript引擎當前執行的代碼塊,如調用setTimeout添加一個任務,也可來自瀏覽器內核的其它線程,如界面元素鼠標點擊事件,定時觸發 器時間到達通知,異步請求狀態變動通知等.從代碼角度看來任務實體就是各類回調函數,JavaScript引擎一直等待着任務隊列中任務的到來.因爲單線 程關係,這些任務得進行排隊,一個接着一個被引擎處理.code
IE8及其以前的IE版本更新間隔爲15.6毫秒。假設你設定的setTimeout延遲爲16.7ms,那麼它要更新兩個15.6毫秒纔會該觸發延時。這也意味着無端延遲了 15.6 x 2 - 16.7 = 14.5毫秒。blog
16.7ms
DELAY: |------------|
CLOCK: |----------|----------|
15.6ms 15.6ms
因此即便你給setTimeout設定的延時爲0ms,它也不會當即觸發。目前Chrome與IE9+瀏覽器的更新頻率都爲4ms(若是你使用的是筆記本電腦,而且在使用電池而非電源的模式下,爲了節省資源,瀏覽器會將更新頻率切換至於系統時間相同,也就意味着更新頻率更低)。
退一步說,假使timer resolution可以達到16.7ms,它還要面臨一個異步隊列的問題。由於異步的關係setTimeout中的回調函數並不是當即執行,而是須要加入等待隊列中。但問題是,若是在等待延遲觸發的過程當中,有新的同步腳本須要執行,那麼同步腳本不會排在timer的回調以後,而是當即執行
讓咱們用一個例子來闡明setTimeout和setInterval之間的區別:
setTimeout(function(){ /* Some long block of code... */ setTimeout(arguments.callee, 10); }, 10); setInterval(function(){ /* Some long block of code... */ }, 10);
這兩句代碼乍一看沒什麼差異,可是它們是不一樣的。setTimeout回調函數的執行和上一次執行之間的間隔至少有10ms(可能會更多,但不會少於10ms),而setInterval的回調函數將嘗試每隔10ms執行一次,不論上次是否執行完畢。
在這裏咱們學到了不少知識,總結一下:
JavaScript引擎是單線程的,強制全部的異步事件排隊等待執行
setTimeout 和 setInterval 在執行異步代碼的時候有着根本的不一樣
若是一個計時器被阻塞而不能當即執行,它將延遲執行直到下一次可能執行的時間點才被執行(比指望的時間間隔要長些)
若是setInterval回調函數的執行時間將足夠長(比指定的時間間隔長),它們將連續執行而且彼此之間沒有時間間隔。