做分销微商城网站,手机微网站建设,做个人网站需要哪些,wordpress 多重筛选背景凹凸曼是个小程序开发者#xff0c;他要在小程序实现秒杀倒计时。于是他不假思索#xff0c;写了以下代码#xff1a;Page({init: function () {clearInterval(this.timer)this.timer setInterval(() {// 倒计时计算逻辑console.log(setInterval)})},
})可是… 背景凹凸曼是个小程序开发者他要在小程序实现秒杀倒计时。于是他不假思索写了以下代码Page({init: function () {clearInterval(this.timer)this.timer setInterval(() {// 倒计时计算逻辑console.log(setInterval)})},
})
可是凹凸曼发现页面隐藏在后台时定时器还在不断运行。于是凹凸曼优化了一下在页面展示的时候运行隐藏的时候就暂停。Page({onShow: function () {if (this.timer) {this.timer setInterval(() {// 倒计时计算逻辑console.log(setInterval)})}},onHide: function () {clearInterval(this.timer)},init: function () {clearInterval(this.timer)this.timer setInterval(() {// 倒计时计算逻辑console.log(setInterval)})},
})
问题看起来已经解决了就在凹凸曼开心地搓搓小手暗暗欢喜时突然发现小程序页面销毁时是不一定会调用 onHide 函数的这样定时器不就没法清理了那可是会造成内存泄漏的。凹凸曼想了想其实问题不难解决在页面 onUnload 的时候也清理一遍定时器就可以了。Page({...onUnload: function () {clearInterval(this.timer)},
})
这下问题都解决了但我们可以发现在小程序使用定时器需要很谨慎一不小心就会造成内存泄漏。 后台的定时器积累得越多小程序就越卡耗电量也越大最终导致程序卡死甚至崩溃。特别是团队开发的项目很难确保每个成员都正确清理了定时器。因此写一个定时器管理库来管理定时器的生命周期将大有裨益。思路整理首先我们先设计定时器的 API 规范肯定是越接近原生 API 越好这样开发者可以无痛替换。function $setTimeout(fn, timeout, ...arg) {}
function $setInterval(fn, timeout, ...arg) {}
function $clearTimeout(id) {}
function $clearInterval(id) {}
接下来我们主要解决以下两个问题如何实现定时器暂停和恢复如何让开发者无须在生命周期函数处理定时器如何实现定时器暂停和恢复思路如下:将定时器函数参数保存恢复定时器时重新创建由于重新创建定时器定时器 ID 会不同因此需要自定义全局唯一 ID 来标识定时器隐藏时记录定时器剩余倒计时时间恢复时使用剩余时间重新创建定时器首先我们需要定义一个 Timer 类Timer 对象会存储定时器函数参数代码如下class Timer {static count 0/*** 构造函数* param {Boolean} isInterval 是否是 setInterval* param {Function} fn 回调函数* param {Number} timeout 定时器执行时间间隔* param {...any} arg 定时器其他参数*/constructor (isInterval false, fn () {}, timeout 0, ...arg) {this.id Timer.count // 定时器递增 idthis.fn fnthis.timeout timeoutthis.restTime timeout // 定时器剩余计时时间this.isInterval isIntervalthis.arg arg}}// 创建定时器function $setTimeout(fn, timeout, ...arg) {const timer new Timer(false, fn, timeout, arg)return timer.id}
接下来我们来实现定时器的暂停和恢复实现思路如下启动定时器调用原生 API 创建定时器并记录下开始计时时间戳。暂停定时器清除定时器并计算该周期计时剩余时间。恢复定时器重新记录开始计时时间戳并使用剩余时间创建定时器。代码如下class Timer {constructor (isInterval false, fn () {}, timeout 0, ...arg) {this.id Timer.count // 定时器递增 idthis.fn fnthis.timeout timeoutthis.restTime timeout // 定时器剩余计时时间this.isInterval isIntervalthis.arg arg}/*** 启动或恢复定时器*/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)}/* 暂停定时器 */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)}
}
其中有几个关键点需要提示一下恢复定时器时实际上我们是重新创建了一个定时器如果直接用 setTimeout 返回的 ID 返回给开发者开发者要 clearTimeout这时候是清除不了的。因此需要在创建 Timer 对象时内部定义一个全局唯一 ID this.id Timer.count将该 ID 返回给 开发者。开发者 clearTimeout 时我们再根据该 ID 去查找真实的定时器 ID (this.timerId)。计时剩余时间timeout 0 时不必计算timeout 0 时需要区分是 setInterval 还是 setTimeoutsetInterval 因为有周期循环因此需要对时间间隔进行取余。setInterval 通过在回调函数末尾调用 setTimeout 实现清除定时器时要在定时器增加一个标示位this.timeId 表示被清除防止死循环。我们通过实现 Timer 类完成了定时器的暂停和恢复功能接下来我们需要将定时器的暂停和恢复功能跟组件或页面的生命周期结合起来最好是抽离成公共可复用的代码让开发者无须在生命周期函数处理定时器。翻阅小程序官方文档发现 Behavior 是个不错的选择。Behaviorbehaviors 是用于组件间代码共享的特性类似于一些编程语言中的 mixins 或 traits。 每个 behavior 可以包含一组属性、数据、生命周期函数和方法组件引用它时它的属性、数据和方法会被合并到组件中生命周期函数也会在对应时机被调用。每个组件可以引用多个 behaviorbehavior 也可以引用其他 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.jsComponent({behaviors: [TimerBehavior],created: function () {console.log([my-component] created)},attached: function () {console.log([my-component] attached)}
})
如上面的例子组件使用 TimerBehavior 后组件初始化过程中会依次调用 TimerBehavior.created() Component.created() TimerBehavior.show()。 因此我们只需要在 TimerBehavior 生命周期内调用 Timer 对应的方法并开放定时器的创建销毁 API 给开发者即可。 思路如下组件或页面创建时新建 Map 对象来存储该组件或页面的定时器。创建定时器时将 Timer 对象保存在 Map 中。定时器运行结束或清除定时器时将 Timer 对象从 Map 移除避免内存泄漏。页面隐藏时将 Map 中的定时器暂停页面重新展示时恢复 Map 中的定时器。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) returnthis.$isActive truethis.$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) returnclearTimeout(timer.timerId)timer.timerId this.$store.delete(id)},$clearTimeout (id) {const timer this.$store.get(id)if (!timer) returnclearTimeout(timer.timerId)timer.timerId this.$store.delete(id)},}
})
上面的代码有许多冗余的地方我们可以再优化一下单独定义一个 TimerStore 类来管理组件或页面定时器的添加、删除、恢复、暂停功能。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() {/* 没有隐藏不需要恢复定时器 */if (this.isActive) returnthis.isActive truethis.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) returnclearTimeout(timer.timerId)timer.timerId this.store.delete(id)}
}
然后再简化一遍 TimerBehaviorconst 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 创建的定时器运行结束后为了避免内存泄漏我们需要将定时器从 Map 中移除。稍微修改下 Timer 的 start 函数如下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)/* 运行结束移除定时器避免内存泄漏 */timerStore.delete(this.id)}this.timerId setTimeout(cb, this.restTime, ...this.arg)}
}
愉快地使用从此把清除定时器的工作交给 TimerBehavior 管理再也不用担心小程序越来越卡。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 包支持为了让开发者更好地使用小程序定时器管理库我们整理了代码并发布了 npm 包供开发者使用开发者可以通过 npm install --save timer-miniprogram 安装小程序定时器管理库文档及完整代码详看 https://github.com/o2team/timer-miniprogrameslint 配置为了让团队更好地遵守定时器使用规范我们还可以配置 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}]}
}
总结千里之堤溃于蚁穴。管理不当的定时器将一点点榨干小程序的内存和性能最终让程序崩溃。重视定时器管理远离定时器泄露。参考资料[1] 小程序开发者文档: https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html推荐阅读我在阿里招前端我该怎么帮你文末有福利如何拿下阿里巴巴 P6 的前端 Offer如何准备阿里P6/P7前端面试--项目经历准备篇大厂面试官常问的亮点该如何做出如何从初级到专家(P4-P7)打破成长瓶颈和有效突破若川知乎问答2年前端经验做的项目没什么技术含量怎么办末尾你好我是若川江湖人称菜如若川历时一年只写了一个学习源码整体架构系列~(点击蓝字了解我)关注我的公众号若川视野回复pdf 领取前端优质书籍pdf我的博客地址https://lxchuan12.gitee.io 欢迎收藏觉得文章不错可以点个在看呀^_^另外欢迎留言交流小提醒若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮欢迎点击阅读