天天看點

javascript兩種方式實作自定義setInterval引言使用setTimeout方式實作使用requestAnimationFrame方式實作進一步改進結語

兩種方式實作setInterval(setTimeout和requestAnimationFrame)

  • 引言
  • 使用setTimeout方式實作
    • setTimeout實作方式的缺點和改進
  • 使用requestAnimationFrame方式實作
  • 進一步改進
  • 結語

引言

最近在學習基礎js時,突然想起了之前關于setInterval的問題,再有些時候并不可靠,是以有推薦使用setTimeout來實作setInterval,是以也想自己實作一個,關于setInterval的問題這裡就不多讨論,已經有很多文章再說了,這裡主要介紹自己的實作過程

使用setTimeout方式實作

這裡我主要參考了下文的實作方式

setTimeout代替setInterval的方案.

利用setTimeout加遞歸實作,主要實作代碼如下,這裡隻截圖了核心的代碼部分,如果對你有幫助請尊重原創去連結裡檢視。其優點是可以随時停止,是把定時器id綁定到了回調方法上,很多解決方法是沒有該功能的。實作interval的方式也是利用到了arguments.callee方法

javascript兩種方式實作自定義setInterval引言使用setTimeout方式實作使用requestAnimationFrame方式實作進一步改進結語

setTimeout實作方式的缺點和改進

該方法缺點也很明顯,就是其核心代碼arguments.callee在嚴格模式下無法使用,是以我嘗試用普通遞歸的方式進行替換,改進後的代碼如下

function interval({ func: fn, interval: i = 1000, isClear: cls = false }) {
	if (cls) {
        fn.timerId && clearTimeout(fn.timerId);
        return;
    }
    else {
        let self = this;
        fn.timerId = setTimeout(function f() {
            fn.timerId = setTimeout(f, i);
            fn.call(self, fn.timerId);
        }, i);
    }
}
           

使用requestAnimationFrame方式實作

後來我在查閱資料時又看到了另一種實作方式,就是利用requestAnimationFrame來實作,其原理和實作方式如下文,裡面不僅有setInterval還有setTimeout的實作,總的來說,該方法更為高效,不過該方法隻支援浏覽器環境,不支援node,因為requestAnimationFrame是window的方法

自己實作一個高性能的setTimeout和setInterval函數.

進一步改進

我後續又嘗試在此基礎上增加兩個比較常用的功能,一個是延時執行,有時候使用interval可能需要加個延時,比如5s後開始執行,每間隔1s執行一次。還有一個就是開啟interval後立即執行一次,分别在兩種實作方式上加以改進,最終代碼如下

/**
 * 實作interval(利用requestAnimationFrame)
 * @param {*} func 回調函數
 * @param {*} param1 參數 
 * interval-執行間隔   
 * isClear-是否清除   
 * delay-延時執行時間
 * immediate-是否在開始時立即執行一次(如果有delay則在delay之後),本次回調的函數内不可暫停該循環 
 */
function startIntervalByAnimation(func, { interval: i = 1000, isClear: cls = false, delay: d = 0, immediate: imm = false } = {}) {
    // 驗證回調函數是否存在
    if (func && typeof func === "function") {
        // 清除定時器
        if (cls) {
            cancelAnimationFrame(func.timerId);
        } else {
            // 延時控制
            setTimeout(function () {
                let startTime = Date.now();
                imm && func.call(this, func.timerId); // 立即執行第一次的控制
                // 開啟循環
                (function loop() {
                    func.timerId = requestAnimationFrame(loop);
                    if (Date.now() - startTime >= i) {
                        func.call(this, func.timerId);
                        startTime = Date.now();
                    }
                })();
            }, d);
        }
    } else {
        throw new Error("no callback function");
    }
}

/**
 * 實作interval(利用setTimeout)
 * @param {*} func 回調函數
 * @param {*} param1 參數 
 * interval-執行間隔   
 * isClear-是否清除   
 * delay-延時執行時間
 * immediate-是否在開始時立即執行一次(如果有delay則在delay之後),本次回調的函數内不可暫停該循環 
 */
function startIntervalByTimeout(func, { interval: i = 1000, isClear: cls = false, delay: d = 0, immediate: imm = false } = {}) {
    // 驗證回調函數是否存在
    if (func && typeof func === "function") {
        // 清除定時器
        if (cls) {
            func.timerId && clearTimeout(func.timerId);
        } else {
            let self = this;
            // 延時控制
            setTimeout(function () {
                imm && func.call(self, func.timerId); // 立即執行第一次的控制
                // 開啟循環
                func.timerId = setTimeout(function f() {
                    func.timerId = setTimeout(f, i);
                    func.call(self, func.timerId);
                }, i);
            }, d);
        }
    } else {
        throw new Error("no callback function");
    }
}


// 測試使用代碼
// 内部停止
let id1 = 0;
function test1(timerId) {
    console.log("執行代碼");
    id1++;
    if (id1 > 5) {
        console.log("停止執行");
        clearTimeout(timerId);
    }
}
startIntervalByTimeout(test1, { interval: 1000 });

// 外部停止
function test2(timerId) {
    console.log("執行代碼");
}
startIntervalByTimeout(test2, { interval: 1000 });
// 停止
setTimeout(() => {
    startIntervalByTimeout(test2, { isClear: true });
}, 5000);
           

結語

最後要再次感謝兩篇文章的作者,結合他們的實作方式最終得出了我自己想要的自定義interval方法,如果哪裡寫的有問題請大家指正,望與諸位共同進步

繼續閱讀