使用setTimeout模擬setInterval

在項(xiàng)目開發(fā)中經(jīng)常會(huì)用到計(jì)時(shí)器,比如抽獎(jiǎng)活動(dòng)的倒計(jì)時(shí),或者輪循去請(qǐng)求接口等,通常我們都是用 setInterval 這個(gè)方法去實(shí)現(xiàn)計(jì)時(shí),但是這樣有一個(gè)缺點(diǎn),雖然能設(shè)置隔一段時(shí)間后不斷執(zhí)行,但是實(shí)際上只是將事件放消息隊(duì)列,真正執(zhí)行的時(shí)間并不確定,有可能上一個(gè)計(jì)時(shí)器任務(wù)沒執(zhí)行完又進(jìn)來一個(gè)計(jì)時(shí)器任務(wù),所以并不能保證能按照設(shè)定的時(shí)間去執(zhí)行,如果用 setTimeout 來模擬的話可以這樣

function runTimer() {
    (function inner() {
        let t = setTimeout(() => {
            console.log('time count')
            clearTimeout(t);
            inner();
        }, 1000);
    })();
}

通過上面的方式可以在隔一秒就輸出一個(gè) time count,通過一個(gè)立即執(zhí)行方法不斷調(diào)用自身來實(shí)現(xiàn) setInterval 的效果,可以保證每次執(zhí)行的間隔時(shí)間都是一致的。

這種方法只是相對(duì)簡(jiǎn)單的,我們?cè)陧?xiàng)目開發(fā)過程中可能有不止一個(gè)計(jì)時(shí)器,我們希望對(duì)所有的計(jì)時(shí)器統(tǒng)一進(jìn)行處理,這時(shí)我們可以封裝一個(gè)類來管理這些計(jì)時(shí)器,它包含以下幾個(gè)內(nèi)容

  • timerList--存放計(jì)時(shí)器的數(shù)組
  • addTimer--往 timerList添加計(jì)時(shí)器的方法,參數(shù)是一個(gè)對(duì)象,包含名稱,回調(diào)方法跟時(shí)間
  • runTimer--執(zhí)行某個(gè)計(jì)時(shí)器的方法,參數(shù)是計(jì)時(shí)器的名稱
  • clearTimer--清除某個(gè)計(jì)時(shí)器的方法,參數(shù)是計(jì)時(shí)器的名稱

具體實(shí)現(xiàn)如下:

// timer.js

class timer {
  timerList = [];

  addTimer(name, callback, time = 1000) {
    this.timerList.push({
      name,
      callback,
      time
    });
    this.runTimer(name);
  }

  runTimer(name) {
    const _this = this;
    (function inner() {
      const task = _this.timerList.find((item) => {
        return item.name === name;
      });
      if (!task) return;
      task.t = setTimeout(() => {
        task.callback();
        clearTimeout(task.t);
        inner();
      }, task.time);
    })();
  }

  clearTimer(name) {
    const taskIndex = this.timerList.findIndex((item) => {
      return item.name === name;
    });
    if (taskIndex !== -1) {
      // 由于刪除該計(jì)時(shí)器時(shí)可能存在該計(jì)時(shí)器已經(jīng)入棧,所以要先清除掉,防止添加的時(shí)候重復(fù)計(jì)時(shí)
      clearTimeout(this.timerList[taskIndex].t);
      this.timerList.splice(taskIndex, 1);
    }
  }
}

export default new timer();

這里特別說明下每個(gè)計(jì)時(shí)器的方法都是在計(jì)時(shí)器的 t 屬性上定義的, 方便 clearTimer 方法中去清除掉該計(jì)時(shí)器,這是為了處理在特定場(chǎng)景下產(chǎn)生的錯(cuò)誤,比如我們?cè)趫?zhí)行一個(gè)計(jì)時(shí)器時(shí)會(huì)先判斷該計(jì)時(shí)器是否存在,如果存在就刪除該計(jì)時(shí)器,這里我們通過 name 值去判斷,如果只是單純把計(jì)時(shí)器從數(shù)組中移除的話,可能在刪除的時(shí)候它已經(jīng)進(jìn)入 setTimeout 了,這時(shí)再開啟一個(gè)同名的計(jì)時(shí)器的話,就會(huì)造成兩個(gè)相同的計(jì)時(shí)器同時(shí)執(zhí)行,所以才需要這么處理。

通過以上的方式封裝計(jì)時(shí)器,我們就可以在任何頁面進(jìn)行引用了

import timer from './timer.js';

timer.clearTimer('xxx');  // 先清除除計(jì)時(shí)器
timer.addTimer('xxx', () => {
    console.log(123)
}, 1000)

如果想要每個(gè)計(jì)時(shí)器的命名都是唯一的話,還可以使用 Symbol 去定義計(jì)時(shí)器的名稱,比如可以新建一個(gè)專門存放計(jì)時(shí)器名稱的文件

// timeSymbol.js

export const statTimer = Symbol("statTimer");
export const endTimer = Symbol("endTimer");

然后通過以下方式引用

import timer from './timer.js';
import {statTimer} from './timeSymbol.js';

timer.clearTimer(statTimer);  // 先清除除計(jì)時(shí)器
timer.addTimer(statTimer, () => {
    console.log(123)
}, 1000)

這樣就能保證所有的計(jì)時(shí)器都是唯一的,不用擔(dān)心會(huì)有同名的計(jì)時(shí)器存在造成錯(cuò)誤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容