欧美三级国产三级日韩三级_亚洲熟妇丰满大屁股熟妇_欧美亚洲成人一区二区三区_国产精品久久久久久模特

手把手教你寫個(gè)小程序定時(shí)器管理庫 - 新聞資訊 - 云南小程序開發(fā)|云南軟件開發(fā)|云南網(wǎng)站建設(shè)-昆明葵宇信息科技有限公司

159-8711-8523

云南網(wǎng)建設(shè)/小程序開發(fā)/軟件開發(fā)

知識(shí)

不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們?cè)谧非笃湟曈X表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營銷的便利,運(yùn)營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!

您當(dāng)前位置>首頁 » 新聞資訊 » 小程序相關(guān) >

手把手教你寫個(gè)小程序定時(shí)器管理庫

發(fā)表時(shí)間:2021-1-5

發(fā)布人:葵宇科技

瀏覽次數(shù):58

背景

凹凸曼是個(gè)小程序開發(fā)者,他要在小程序?qū)崿F(xiàn)秒殺倒計(jì)時(shí)。于是他不假思索,寫了以下代碼:

Page({
  init: function () {
    clearInterval(this.timer)
    this.timer = setInterval(() => {
      // 倒計(jì)時(shí)計(jì)算邏輯
      console.log('setInterval')
    })
  },
})

可是,凹凸曼發(fā)現(xiàn)頁面隱藏在后臺(tái)時(shí),定時(shí)器還在不斷運(yùn)行。于是凹凸曼優(yōu)化了一下,在頁面展示的時(shí)候運(yùn)行,隱藏的時(shí)候就暫停。

Page({
  onShow: function () {
    if (this.timer) {
      this.timer = setInterval(() => {
        // 倒計(jì)時(shí)計(jì)算邏輯
        console.log('setInterval')
      })
    }
  },
  onHide: function () {
    clearInterval(this.timer)
  },
  init: function () {
    clearInterval(this.timer)
    this.timer = setInterval(() => {
      // 倒計(jì)時(shí)計(jì)算邏輯
      console.log('setInterval')
    })
  },
})

問題看起來已經(jīng)解決了,就在凹凸曼開心地搓搓小手暗暗歡喜時(shí),突然發(fā)現(xiàn)小程序頁面銷毀時(shí)是不一定會(huì)調(diào)用 onHide 函數(shù)的,這樣定時(shí)器不就沒法清理了?那可是會(huì)造成內(nèi)存泄漏的。凹凸曼想了想,其實(shí)問題不難解決,在頁面 onUnload 的時(shí)候也清理一遍定時(shí)器就可以了。

Page({
  ...
  onUnload: function () {
    clearInterval(this.timer)
  },
})

這下問題都解決了,但我們可以發(fā)現(xiàn),在小程序使用定時(shí)器需要很謹(jǐn)慎,一不小心就會(huì)造成內(nèi)存泄漏。后臺(tái)的定時(shí)器積累得越多,小程序就越卡,耗電量也越大,最終導(dǎo)致程序卡死甚至崩潰。特別是團(tuán)隊(duì)開發(fā)的項(xiàng)目,很難確保每個(gè)成員都正確清理了定時(shí)器。因此,寫一個(gè)定時(shí)器管理庫來管理定時(shí)器的生命周期,將大有裨益。

思路整理

首先,我們先設(shè)計(jì)定時(shí)器的 API 規(guī)范,肯定是越接近原生 API 越好,這樣開發(fā)者可以無痛替換。

function $setTimeout(fn, timeout, ...arg) {}
function $setInterval(fn, timeout, ...arg) {}
function $clearTimeout(id) {}
function $clearInterval(id) {}

接下來我們主要解決以下兩個(gè)問題

  1. 如何實(shí)現(xiàn)定時(shí)器暫停和恢復(fù)

  2. 如何讓開發(fā)者無須在生命周期函數(shù)處理定時(shí)器

如何實(shí)現(xiàn)定時(shí)器暫停和恢復(fù)

思路如下:

  1. 將定時(shí)器函數(shù)參數(shù)保存,恢復(fù)定時(shí)器時(shí)重新創(chuàng)建

  2. 由于重新創(chuàng)建定時(shí)器,定時(shí)器 ID 會(huì)不同,因此需要自定義全局唯一 ID 來標(biāo)識(shí)定時(shí)器

  3. 隱藏時(shí)記錄定時(shí)器剩余倒計(jì)時(shí)時(shí)間,恢復(fù)時(shí)使用剩余時(shí)間重新創(chuàng)建定時(shí)器

首先我們需要定義一個(gè) Timer 類,Timer 對(duì)象會(huì)存儲(chǔ)定時(shí)器函數(shù)參數(shù),代碼如下

class Timer {
    static count = 0
    /**
     * 構(gòu)造函數(shù)
     * @param {Boolean} isInterval 是否是 setInterval
     * @param {Function} fn 回調(diào)函數(shù)
     * @param {Number} timeout 定時(shí)器執(zhí)行時(shí)間間隔
     * @param  {...any} arg 定時(shí)器其他參數(shù)
     */
    constructor (isInterval = false, fn = () => {}, timeout = 0, ...arg) {
        this.id = ++Timer.count // 定時(shí)器遞增 id
        this.fn = fn
        this.timeout = timeout
        this.restTime = timeout // 定時(shí)器剩余計(jì)時(shí)時(shí)間
        this.isInterval = isInterval
        this.arg = arg
    }
  }

  // 創(chuàng)建定時(shí)器
  function $setTimeout(fn, timeout, ...arg) {
    const timer = new Timer(false, fn, timeout, arg)
    return timer.id
  }

接下來,我們來實(shí)現(xiàn)定時(shí)器的暫停和恢復(fù),實(shí)現(xiàn)思路如下:

  1. 啟動(dòng)定時(shí)器,調(diào)用原生 API 創(chuàng)建定時(shí)器并記錄下開始計(jì)時(shí)時(shí)間戳。

  2. 暫停定時(shí)器,清除定時(shí)器并計(jì)算該周期計(jì)時(shí)剩余時(shí)間。

  3. 恢復(fù)定時(shí)器,重新記錄開始計(jì)時(shí)時(shí)間戳,并使用剩余時(shí)間創(chuàng)建定時(shí)器。

代碼如下:

class Timer {
    constructor (isInterval = false, fn = () => {}, timeout = 0, ...arg) {
        this.id = ++Timer.count // 定時(shí)器遞增 id
        this.fn = fn
        this.timeout = timeout
        this.restTime = timeout // 定時(shí)器剩余計(jì)時(shí)時(shí)間
        this.isInterval = isInterval
        this.arg = arg
    }

    /**
     * 啟動(dòng)或恢復(fù)定時(shí)器
     */
    start() {
        this.startTime = +new Date()

        if (this.isInterval) {
            /* setInterval */
            const cb = (...arg) => {
                this.fn(...arg)
                /* timerId 為空表示被 clearInterval */
                if (this.timerId) this.timerId = setTimeout(cb, this.timeout, ...this.arg)
            }
            this.timerId = setTimeout(cb, this.restTime, ...this.arg)
            return
        }
        /* setTimeout  */
        const cb = (...arg) => {
            this.fn(...arg)
        }
        this.timerId = setTimeout(cb, this.restTime, ...this.arg)
    }

    /* 暫停定時(shí)器 */
    suspend () {
        if (this.timeout > 0) {
            const now = +new Date()
            const nextRestTime = this.restTime - (now - this.startTime)
            const intervalRestTime = nextRestTime >=0 ? nextRestTime : this.timeout - (Math.abs(nextRestTime) % this.timeout)
            this.restTime = this.isInterval ? intervalRestTime : nextRestTime
        }
        clearTimeout(this.timerId)
    }
}

其中,有幾個(gè)關(guān)鍵點(diǎn)需要提示一下:

  1. 恢復(fù)定時(shí)器時(shí),實(shí)際上我們是重新創(chuàng)建了一個(gè)定時(shí)器,如果直接用 setTimeout 返回的 ID 返回給開發(fā)者,開發(fā)者要 clearTimeout,這時(shí)候是清除不了的。因此需要在創(chuàng)建 Timer 對(duì)象時(shí)內(nèi)部定義一個(gè)全局唯一 ID this.id = ++Timer.count ,將該 ID 返回給 開發(fā)者。開發(fā)者 clearTimeout 時(shí),我們?cè)俑鶕?jù)該 ID 去查找真實(shí)的定時(shí)器 ID (this.timerId)。
  2. 計(jì)時(shí)剩余時(shí)間,timeout = 0 時(shí)不必計(jì)算;timeout > 0 時(shí),需要區(qū)分是 setInterval 還是 setTimeout,setInterval 因?yàn)橛兄芷谘h(huán),因此需要對(duì)時(shí)間間隔進(jìn)行取余。

  3. setInterval 通過在回調(diào)函數(shù)末尾調(diào)用 setTimeout 實(shí)現(xiàn),清除定時(shí)器時(shí),要在定時(shí)器增加一個(gè)標(biāo)示位(this.timeId = "")表示被清除,防止死循環(huán)。

我們通過實(shí)現(xiàn) Timer 類完成了定時(shí)器的暫停和恢復(fù)功能,接下來我們需要將定時(shí)器的暫停和恢復(fù)功能跟組件或頁面的生命周期結(jié)合起來,最好是抽離成公共可復(fù)用的代碼,讓開發(fā)者無須在生命周期函數(shù)處理定時(shí)器。翻閱小程序官方文檔,發(fā)現(xiàn) Behavior 是個(gè)不錯(cuò)的選擇。

Behavior

behaviors 是用于組件間代碼共享的特性,類似于一些編程語言中的 "mixins" 或 "traits"。每個(gè) behavior 可以包含一組屬性、數(shù)據(jù)、生命周期函數(shù)和方法,組件引用它時(shí),它的屬性、數(shù)據(jù)和方法會(huì)被合并到組件中,生命周期函數(shù)也會(huì)在對(duì)應(yīng)時(shí)機(jī)被調(diào)用。每個(gè)組件可以引用多個(gè) behavior,behavior 也可以引用其他 behavior 。

// behavior.js 定義behavior
const TimerBehavior = Behavior({
  pageLifetimes: {
    show () { console.log('show') },
    hide () { console.log('hide') }
  },
  created: function () { console.log('created')},
  detached: function() { console.log('detached') }
})

export { TimerBehavior }

// component.js 使用 behavior
import { TimerBehavior } from '../behavior.js'

Component({
  behaviors: [TimerBehavior],
  created: function () {
    console.log('[my-component] created')
  },
  attached: function () {
    console.log('[my-component] attached')
  }
})

如上面的例子,組件使用 TimerBehavior 后,組件初始化過程中,會(huì)依次調(diào)用 TimerBehavior.created() => Component.created() => TimerBehavior.show() 。因此,我們只需要在 TimerBehavior 生命周期內(nèi)調(diào)用 Timer 對(duì)應(yīng)的方法,并開放定時(shí)器的創(chuàng)建銷毀 API 給開發(fā)者即可。思路如下:

  1. 組件或頁面創(chuàng)建時(shí),新建 Map 對(duì)象來存儲(chǔ)該組件或頁面的定時(shí)器。

  2. 創(chuàng)建定時(shí)器時(shí),將 Timer 對(duì)象保存在 Map 中。

  3. 定時(shí)器運(yùn)行結(jié)束或清除定時(shí)器時(shí),將 Timer 對(duì)象從 Map 移除,避免內(nèi)存泄漏。

  4. 頁面隱藏時(shí)將 Map 中的定時(shí)器暫停,頁面重新展示時(shí)恢復(fù) Map 中的定時(shí)器。

const TimerBehavior = Behavior({
  created: function () {
    this.$store = new Map()
    this.$isActive = true
  },
  detached: function() {
    this.$store.forEach(timer => timer.suspend())
    this.$isActive = false
  },
  pageLifetimes: {
    show () {
      if (this.$isActive) return

      this.$isActive = true
      this.$store.forEach(timer => timer.start(this.$store))
    },
    hide () {
      this.$store.forEach(timer => timer.suspend())
      this.$isActive = false
    }
  },
  methods: {
    $setTimeout (fn = () => {}, timeout = 0, ...arg) {
      const timer = new Timer(false, fn, timeout, ...arg)

      this.$store.set(timer.id, timer)
      this.$isActive && timer.start(this.$store)

      return timer.id
    },
    $setInterval (fn = () => {}, timeout = 0, ...arg) {
      const timer = new Timer(true, fn, timeout, ...arg)

      this.$store.set(timer.id, timer)
      this.$isActive && timer.start(this.$store)

      return timer.id
    },
    $clearInterval (id) {
      const timer = this.$store.get(id)
      if (!timer) return

      clearTimeout(timer.timerId)
      timer.timerId = ''
      this.$store.delete(id)
    },
    $clearTimeout (id) {
      const timer = this.$store.get(id)
      if (!timer) return

      clearTimeout(timer.timerId)
      timer.timerId = ''
      this.$store.delete(id)
    },
  }
})

上面的代碼有許多冗余的地方,我們可以再優(yōu)化一下,單獨(dú)定義一個(gè) TimerStore 類來管理組件或頁面定時(shí)器的添加、刪除、恢復(fù)、暫停功能。

class TimerStore {
    constructor() {
        this.store = new Map()
        this.isActive = true
    }

    addTimer(timer) {
        this.store.set(timer.id, timer)
        this.isActive && timer.start(this.store)

        return timer.id
    }

    show() {
        /* 沒有隱藏,不需要恢復(fù)定時(shí)器 */
        if (this.isActive) return

        this.isActive = true
        this.store.forEach(timer => timer.start(this.store))
    }

    hide() {
        this.store.forEach(timer => timer.suspend())
        this.isActive = false
    }

    clear(id) {
        const timer = this.store.get(id)
        if (!timer) return

        clearTimeout(timer.timerId)
        timer.timerId = ''
        this.store.delete(id)
    }
}

然后再簡(jiǎn)化一遍 TimerBehavior

const TimerBehavior = Behavior({
  created: function () { this.$timerStore = new TimerStore() },
  detached: function() { this.$timerStore.hide() },
  pageLifetimes: {
    show () { this.$timerStore.show() },
    hide () { this.$timerStore.hide() }
  },
  methods: {
    $setTimeout (fn = () => {}, timeout = 0, ...arg) {
      const timer = new Timer(false, fn, timeout, ...arg)

      return this.$timerStore.addTimer(timer)
    },
    $setInterval (fn = () => {}, timeout = 0, ...arg) {
      const timer = new Timer(true, fn, timeout, ...arg)

      return this.$timerStore.addTimer(timer)
    },
    $clearInterval (id) {
      this.$timerStore.clear(id)
    },
    $clearTimeout (id) {
      this.$timerStore.clear(id)
    },
  }
})

此外,setTimeout 創(chuàng)建的定時(shí)器運(yùn)行結(jié)束后,為了避免內(nèi)存泄漏,我們需要將定時(shí)器從 Map 中移除。稍微修改下 Timer 的 start 函數(shù),如下:

class Timer {
    // 省略若干代碼
    start(timerStore) {
        this.startTime = +new Date()

        if (this.isInterval) {
            /* setInterval */
            const cb = (...arg) => {
                this.fn(...arg)
                /* timerId 為空表示被 clearInterval */
                if (this.timerId) this.timerId = setTimeout(cb, this.timeout, ...this.arg)
            }
            this.timerId = setTimeout(cb, this.restTime, ...this.arg)
            return
        }
        /* setTimeout  */
        const cb = (...arg) => {
            this.fn(...arg)
            /* 運(yùn)行結(jié)束,移除定時(shí)器,避免內(nèi)存泄漏 */
            timerStore.delete(this.id)
        }
        this.timerId = setTimeout(cb, this.restTime, ...this.arg)
    }
}

愉快地使用

從此,把清除定時(shí)器的工作交給 TimerBehavior 管理,再也不用擔(dān)心小程序越來越卡。

import { TimerBehavior } from '../behavior.js'

// 在頁面中使用
Page({
  behaviors: [TimerBehavior],
  onReady() {
    this.$setTimeout(() => {
      console.log('setTimeout')
    })
    this.$setInterval(() => {
      console.log('setTimeout')
    })
  }
})

// 在組件中使用
Components({
  behaviors: [TimerBehavior],
  ready() {
    this.$setTimeout(() => {
      console.log('setTimeout')
    })
    this.$setInterval(() => {
      console.log('setTimeout')
    })
  }
})

npm 包支持

為了讓開發(fā)者更好地使用小程序定時(shí)器管理庫,我們整理了代碼并發(fā)布了 npm 包供開發(fā)者使用,開發(fā)者可以通過 npm install --save timer-miniprogram 安裝小程序定時(shí)器管理庫,文檔及完整代碼詳看 https://github.com/o2team/timer-miniprogram

eslint 配置

為了讓團(tuán)隊(duì)更好地遵守定時(shí)器使用規(guī)范,我們還可以配置 eslint 增加代碼提示,配置如下:

// .eslintrc.js
module.exports = {
    'rules': {
        'no-restricted-globals': ['error', {
            'name': 'setTimeout',
            'message': 'Please use TimerBehavior and this.$setTimeout instead. see the link: https://github.com/o2team/timer-miniprogram'
        }, {
            'name': 'setInterval',
            'message': 'Please use TimerBehavior and this.$setInterval instead. see the link: https://github.com/o2team/timer-miniprogram'
        }, {
            'name': 'clearInterval',
            'message': 'Please use TimerBehavior and this.$clearInterval instead. see the link: https://github.com/o2team/timer-miniprogram'
        }, {
            'name': 'clearTimout',
            'message': 'Please use TimerBehavior and this.$clearTimout  instead. see the link: https://github.com/o2team/timer-miniprogram'
        }]
    }
}

總結(jié)

千里之堤,潰于蟻穴。

管理不當(dāng)?shù)亩〞r(shí)器,將一點(diǎn)點(diǎn)榨干小程序的內(nèi)存和性能,最終讓程序崩潰。

重視定時(shí)器管理,遠(yuǎn)離定時(shí)器泄露。

相關(guān)案例查看更多