循環定時器--用setTimeout代替setInterval

續前緣 
循環定時器的寫法, 很多人應該熟悉

/*
 *func 回調
 *interval 間隔時間
*/
setInterval(func, interval)

大部分人(包括之前的我)都將循環定時器理解爲: 每間隔一段時間就執行一次回調. 其實這種說法並不準確. 如果強行這麼理解, 那就要加上兩個條件: JS進程永遠處於空閒狀態; 回調函數執行時間小於間隔時間.
可以做個試驗:

setInterval(function(){
        var script = document.createElement("script")
        script.src = "http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"
        script.onload = script.onreadystatechange = function () {
            if (!script.readyState || 'loaded' === script.readyState || 'complete' === script.readyState) {
                console.log(new Date().getTime())
            }
        };
        document.querySelector("body").appendChild(script)
    }, 1000)

結果如下所示:
這裏寫圖片描述
這串數據前後相隔時間雖然在1000上下徘徊, 但都不精確.
區分兩件事情: JS進程和JS隊列時間線是並行處理的; 同一時間, 定時器在隊列中的回調函數只能有一個.
先說下間隔小於1000的情況: 假設間隔時間爲t1, 代碼執行時間爲t2, 且t2>2t1, 此時定時器代碼會跳過間隔時間且連續運行定時器代碼. 直觀呈現就是abs(time1-time2)<1000.
再說下間隔大於1000: 當t2<2t1 && t2>t1 這屬於正常情況
綜上, 循環定時器是有問題的:
1. 某些間隔會被跳過
2. 多個定時器的代碼執行時間可能會比預期的小

爲了避免這兩個問題的出現, setInterval可以採用鏈式調用setTimeout代替.

function Interval(){
    this._interval_flag = null //初始化: 定時器在隊列中的順序
}
Interval.prototype = {
    createIns: function(fun, interval){ //創建定時器
        var that = this //防止this指向發生變化
        var fouth = setTimeout(function(){
            if(typeof(fun) == "function"){
                fun()
            }else{
                return console.error("Type of the argument \"fun\" is not \"function\"")
            }
            that._interval_flag = setTimeout(arguments.callee, interval)
        }, interval) //鏈式調用setTimeout
        console.log("fouth:"+fouth)
    }
    ,clearIns: function(){ //清除定時器
        clearTimeout(this._interval_flag)
    }
}
var subInterval = new Interval() //實例化
subInterval.createIns(function(){
    var script = document.createElement("script")
    script.src = "http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"
    script.onload = script.onreadystatechange = function () {
        if (!script.readyState || 'loaded' === script.readyState || 'complete' === script.readyState) {
            console.log(new Date().getTime())
        }
    };
    document.querySelector("body").appendChild(script)
}, 1000)

如上所述, 鏈式調用setTimeout替代setInterval, 可以保證: 在前一個定時器代碼執行完之前, 不會像隊列中插入新的定時器代碼, 從而確保不會有任何確實的間隔; 此外, 還可以保證在下一次定時器代碼執行之前, 至少要等待指定時長的間隔, 避免定時器代碼連續運行.
有人可能會想, 多個定時器之間, 它們的順序是怎樣的. 其實定時器給自己開闢了一塊內存來存放索引(只存放定時器: 包括單次和循環), 索引值從1開始無上限遞增(如果定時器足夠的話), 至於順序則是按照定時器生成的時間順序.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章