js定時器的理解

概念

  • 人們對 JavaScript的定時器存在廣泛的誤解,認爲它們是線程,其實 JavaScript 是運行於單線程的環境中的,而定時器僅僅只是計劃代碼在將來的某個時間執行。執行時機是不能保證的,由於在頁面的生命週期中,不一樣時間可能有其餘代碼在控制JavaScript進程。在頁面下載完後的代碼運行、事件處理程序、Ajax回調函數都必須使用一樣的線程來執行。實際上,瀏覽器負責進行排序,指派某段代碼在某個時間點運行 的優先級。java

  • 定時器對隊列的工做方式是,當特定時間過去後將代碼插入。注意,給隊列添加代碼並不意味着對它馬上執行,而只能表示它會盡快執行。設定一個 150ms 後執行的定時器不表明到了 150ms 代碼就馬上 執行,它表示代碼會在150ms後被加入到隊列中。若是在這個時間點上,隊列中沒有其餘東西,那麼這段代碼就會被執行,表面上看上去好像代碼就在精確指定的時間點上執行了。其餘狀況下,代碼可能明顯地等待更長時間才執行。web

setTimeout

  • 執行完一套代碼後,JavaScript進程返回一段很短的時間,這樣頁面上的其餘處理就能夠進行了。因爲JavaScript進程會阻塞其餘頁面處理,因此必須有這些小間隔來防止用戶界面被鎖定(代碼長時間 運行中還有可能出現)。這樣設置一個定時器,能夠確保在定時器代碼執行前至少有一個進程間隔。

setInterval

  • 使用 setInterval()建立的定時器確保了定時器代碼規則地插入隊列中。這個方式的問題在於,定時器代碼可能在代碼再次被添加到隊列以前尚未完成執行,結果致使定時器代碼連續運行好幾回, 而之間沒有任何停頓。幸虧,JavaScript引擎夠聰明,能避免這個問題。當使用 setInterval()時,僅當沒有該定時器的任何其餘代碼實例時,纔將定時器代碼添加到隊列中。這確保了定時器代碼加入到隊列中的最小時間間隔爲指定間隔。
  • 這種重複定時器的規則有兩個問題:

(1) 某些間隔會被跳過;瀏覽器

(2) 多個定時器的代碼執行之間的間隔可能會比預期的小。bash

爲了不 setInterval()的重複定時器的這 2 個缺點,你能夠用以下模式使用鏈式 setTimeout() 調用。函數

setTimeout(function(){ //處理中
    setTimeout(arguments.callee, interval);//次優選項 可寫個function代替
}, interval);

複製代碼

這個模式鏈式調用了setTimeout(),每次函數執行的時候都會建立一個新的定時器。第二個 setTimeout()調用使用了 arguments.callee來獲取對當前執行的函數的引用,併爲其設置另一 個定時器。這樣作的好處是,在前一個定時器代碼執行完以前,不會向隊列插入新的定時器代碼,確保不會有任何缺失的間隔。並且,它能夠保證在下一次定時器代碼執行以前,至少要等待指定的間隔,避 免了連續的運行。這個模式主要用於重複定時器,以下例所示。優化

setTimeout(function(){
        var div = document.getElementById("myDiv");
        left = parseInt(div.style.left) + 5;
        div.style.left = left + "px";
        if (left < 200){
           setTimeout(arguments.callee, 50);
        }
    }, 50);
複製代碼

requestAnimationFrame

  • 大多數電腦顯示器的刷新頻率是 60Hz,大概至關於每秒鐘重繪 60 次。大多數瀏覽器都會對 重繪操做加以限制,不超過顯示器的重繪頻率,由於即便超過那個頻率用戶體驗也不會有提高。
  • 所以,最平滑動畫的最佳循環間隔是 1000ms/60,約等於 17ms。以這個循環間隔重繪的動畫是最平 滑的,由於這個速度最接近瀏覽器的最高限速。爲了適應 17ms 的循環間隔,多重動畫可能須要加以節 制,以便不會完成得太快。
  • 雖然與使用多組setTimeout()的循環方式相比,使用setInterval()的動畫循環效率更高,但後者也不是沒有問題。不管是setInterval()仍是setTimeout()都不十分精確。爲它們傳入的第二個參數,實際上只是指定了把動畫代碼添加到瀏覽器UI線程隊列中以等待執行的時間。若是隊列前面已經加入了其餘任務,那動畫代碼就要等前面的任務完成後再執行。簡言之,以毫秒錶示的延遲時間並不表明到時候必定會執行動畫代碼,而僅表明到時候會把代碼添加到任務隊列中。若是UI線程繁忙,好比忙於處理用戶操做,那麼即便把代碼加入隊列也不會當即執行。
  • 知道何時繪製下一幀是保證動畫平滑的關鍵。然而,直至最近,開發人員都沒有辦法確保瀏覽 器按時繪製下一幀。
  • 一個很是獨特的方案。 CSS變換和動畫的優點在於瀏覽器知道動畫何時開始,所以會計算出正確的循環間隔,在恰當的時候刷新UI。而對於JavaScript動畫,瀏覽器無從知曉何時開始。所以他的方案就是創造一個新方法mozRequestAnimationFrame(),經過它告訴瀏覽器某些JavaScript代碼將要執行動畫。這樣瀏覽器能夠在運行某些代碼後進行適當的優化。
(function(){
        function draw(timestamp){
             //timestamp 它是一個時間碼(從1970 年1月1日起至今的毫秒數),表示下一次重繪的實際發生時間。
             //計算兩次重繪的時間間隔
            var drawStart = (timestamp || Date.now()),
            diff = drawStart - startTime; //使用 diff 肯定下一步的繪製時間
            //把 startTime 重寫爲這一次的繪製時間 startTime = drawStart;
            //重繪 UI
            requestAnimationFrame(draw);
        }
        var requestAnimationFrame = window.requestAnimationFrame ||
                                    window.mozRequestAnimationFrame ||
                                    window.webkitRequestAnimationFrame ||
                                    window.msRequestAnimationFrame,
            startTime = window.mozAnimationStartTime || Date.now();
 })();
複製代碼

疑問?

1.setInterval 與 setTimeout 模擬的setInterval有什麼區別?動畫

答:ui

  • setInterval以下:
setInterval(() => {
    //定時器代碼
}, 200);
複製代碼

每200ms向進程插入一次定時器代碼,在第5ms,205ms,405ms處都插入了定時器代碼,因爲這樣的一條規則:spa

當使用 setInterval()時,僅 當沒有該定時器的任何其餘代碼實例時,纔將定時器代碼添加到隊列中。線程

因此第5ms處,205ms都成功將定時器代碼插入js進程中,可是第405ms處因爲正在執行定時器代碼故插入失敗。使這個 間隔被跳過 ,同時執行完第一次定時器代碼後緊接着執行了第二次定時器代碼,使 多個定時器的代碼執行之間的間隔可能會比預期的小

  • 使用setTimeout
setTimeout(function(){ 
    //定時器代碼
    setTimeout(arguments.callee, interval);
}, interval);
複製代碼

這個模式鏈式調用了setTimeout(),每次函數執行的時候都會建立一個新的定時器。第二個setTimeout()調用使用了arguments.callee 來獲取對當前執行的函數的引用,併爲其設置另一個定時器。這樣作的好處是,在前一個定時器代碼執行完以前,不會向隊列插入新的定時器代碼,確保不會有任何缺失的間隔。並且,它能夠保證在下一次定時器代碼執行以前,至少要等待指定的間隔,避 免了連續的運行。

2.以下代碼的 clearTimeout(s)是否能夠清楚定時器。

var i = 0
    var s = setTimeout(function(){
                add()
            },300)
    function add(){
        i++
        console.log(123);
        console.log(i);
        if(i>20){
            clearTimeout(s)
        }else{
            setTimeout(function(){
                add()
            },300)
        }
    }
複製代碼

答:可

3.arguments.callee 是什麼意思?

  • arguments.callee 屬性包含當前正在執行的函數。
  • callee 是 arguments對象的一個屬性。它能夠用於引用該函數的函數體內當前正在執行的函數。這在函數的名稱是未知時頗有用,例如在沒有名稱的函數表達式 (也稱爲「匿名函數」)內。

警告:在嚴格模式下,第5版 ECMAScript (ES5) 禁止使用 arguments.callee()。當一個函數必須調用自身的時候, 避免使用 arguments.callee(), 經過要麼給函數表達式一個名字,要麼使用一個函數聲明.

相關文章
相關標籤/搜索