兩種方式實作setInterval(setTimeout和requestAnimationFrame)
- 引言
- 使用setTimeout方式實作
-
- setTimeout實作方式的缺點和改進
- 使用requestAnimationFrame方式實作
- 進一步改進
- 結語
引言
最近在學習基礎js時,突然想起了之前關于setInterval的問題,再有些時候并不可靠,是以有推薦使用setTimeout來實作setInterval,是以也想自己實作一個,關于setInterval的問題這裡就不多讨論,已經有很多文章再說了,這裡主要介紹自己的實作過程
使用setTimeout方式實作
這裡我主要參考了下文的實作方式
setTimeout代替setInterval的方案.
利用setTimeout加遞歸實作,主要實作代碼如下,這裡隻截圖了核心的代碼部分,如果對你有幫助請尊重原創去連結裡檢視。其優點是可以随時停止,是把定時器id綁定到了回調方法上,很多解決方法是沒有該功能的。實作interval的方式也是利用到了arguments.callee方法
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方法,如果哪裡寫的有問題請大家指正,望與諸位共同進步