在项目开发中经常会用到计时器,比如抽奖活动的倒计时,或者轮循去请求接口等,通常我们都是用 setInterval
这个方法去实现计时,但是这样有一个缺点,虽然能设置隔一段时间后不断执行,但是实际上只是将事件放消息队列,真正执行的时间并不确定,有可能上一个计时器任务没执行完又进来一个计时器任务,所以并不能保证能按照设定的时间去执行,如果用 setTimeout
来模拟的话可以这样
function runTimer() {
(function inner() {
let t = setTimeout(() => {
console.log('time count')
clearTimeout(t);
inner();
}, 1000);
})();
}
通过上面的方式可以在隔一秒就输出一个 time count
,通过一个立即执行方法不断调用自身来实现 setInterval
的效果,可以保证每次执行的间隔时间都是一致的。
这种方法只是相对简单的,我们在项目开发过程中可能有不止一个计时器,我们希望对所有的计时器统一进行处理,这时我们可以封装一个类来管理这些计时器,它包含以下几个内容
-
timerList
--存放计时器的数组 -
addTimer
--往timerList
添加计时器的方法,参数是一个对象,包含名称,回调方法跟时间 -
runTimer
--执行某个计时器的方法,参数是计时器的名称 -
clearTimer
--清除某个计时器的方法,参数是计时器的名称
具体实现如下:
// 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) {
// 由于删除该计时器时可能存在该计时器已经入栈,所以要先清除掉,防止添加的时候重复计时
clearTimeout(this.timerList[taskIndex].t);
this.timerList.splice(taskIndex, 1);
}
}
}
export default new timer();
这里特别说明下每个计时器的方法都是在计时器的 t
属性上定义的, 方便 clearTimer
方法中去清除掉该计时器,这是为了处理在特定场景下产生的错误,比如我们在执行一个计时器时会先判断该计时器是否存在,如果存在就删除该计时器,这里我们通过 name
值去判断,如果只是单纯把计时器从数组中移除的话,可能在删除的时候它已经进入 setTimeout
了,这时再开启一个同名的计时器的话,就会造成两个相同的计时器同时执行,所以才需要这么处理。
通过以上的方式封装计时器,我们就可以在任何页面进行引用了
import timer from './timer.js';
timer.clearTimer('xxx'); // 先清除除计时器
timer.addTimer('xxx', () => {
console.log(123)
}, 1000)
如果想要每个计时器的命名都是唯一的话,还可以使用 Symbol
去定义计时器的名称,比如可以新建一个专门存放计时器名称的文件
// 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); // 先清除除计时器
timer.addTimer(statTimer, () => {
console.log(123)
}, 1000)
这样就能保证所有的计时器都是唯一的,不用担心会有同名的计时器存在造成错误。