1.前提
前端定时器(如 setTimeout 和 setInterval)可能不会准时执行,主要原因是浏览器和JavaScript运行环境的工作机制。以下是一些主要原因及其背后的原理:
-
单线程模型:JavaScript在浏览器中是单线程的,这意味着同一时间只能执行一个任务。如果某个任务占用了大量时间(例如复杂的计算或长时间运行的同步代码),它会阻塞事件循环,导致定时器回调无法按时执行。即使定时器设定的时间到了,当前正在执行的任务必须先完成,然后才能处理定时器的回调。
-
事件循环与任务队列:浏览器中的JavaScript引擎使用事件循环来管理异步操作。当设置一个定时器时,实际上是在任务队列中添加了一个待处理的任务。一旦设定的时间到达,这个任务会被加入到任务队列中等待执行。然而,只有当当前执行栈为空时,即所有同步任务都已完成,才会开始处理任务队列中的下一个任务。因此,即使定时器的时间已经到期,如果前面有其他任务在排队等待执行,定时器的回调函数也只能等待。
-
最小延迟限制:为了优化性能并减少CPU占用,浏览器对非常短的定时器设置了最小延迟限制。例如,在大多数现代浏览器中,当你尝试设置一个小于4毫秒的定时器时,实际的延迟将被调整为至少4毫秒。这是因为过于频繁的定时器调用可能会导致性能问题。
-
页面不可见时的行为变化:当标签页不在前台显示时(即处于后台状态),为了节省资源,浏览器可能会降低定时器的精度或者暂停它们。这可以防止后台页面消耗过多的系统资源。此外,移动设备上的浏览器可能会采取更严格的措施来管理后台标签页中的定时器行为,以延长电池寿命。
综上所述,尽管你可以指定定时器的确切时间间隔,但由于上述因素的影响,定时器的实际触发时间可能会比预期的要晚。了解这些原理有助于更好地设计应用,尤其是在需要精确计时的情况下,应考虑这些潜在的不准确性,并寻找合适的解决方案。
2.倒计时前端class类
这个代码实现了一个倒计时功能,封装在 Alarm 类中。它支持启动、终止倒计时,并在倒计时过程中通过回调函数更新剩余时间,倒计时结束后触发结束回调
/* eslint-disable */
const DAY_MS = 86400000;
export class Alarm {
countFunc: (leftTime:number) => void;
endFunc: () => void;
flag: null | NodeJS.Timer;
status:number;
leftTime:number;
startTime:number;
constructor(countFunc: (leftTime:number) => void, endFunc: () => void) {
'ngInject';
this.countFunc = countFunc;
this.endFunc = endFunc;
this.flag = null;
// 计时器状态 0初始化; 1倒计时中; 2 倒计时结束或取消
this.status = 0;
this.leftTime = 0;
this.startTime = new Date().getTime();
}
/**
* @method end
* @desc 终止计数器
* 结束时设置倒计时为0,因为用户退到后台,再次启动与当前时间计算可能为负,应当重置为0
*/
end() {
const self = this;
if (self.flag) {
self.leftTime = 0;
clearInterval(self.flag);
self.flag = null;
self.status = 2;
}
}
start(totalMS:number) {
// 类型转换为number,避免无效数字;
const self = this;
// if (totalMS > DAY_MS) {
// totalMS = DAY_MS;
// }
this.startTime = new Date().getTime();
this.leftTime = Math.round(totalMS - new Date().getTime() + this.startTime);
this.status = 1;
this.flag = setInterval(() => {
if (self.leftTime < 200) {
self.end();
self.endFunc();
} else {
const currentTime = new Date().getTime();
self.leftTime = Math.round(totalMS - currentTime + self.startTime);
// 剩余时间判定,如果小于0,则重置为0
self.leftTime = self.leftTime < 0 ? 0 : self.leftTime;
// 倒计时执行函数
self.countFunc(self.leftTime);
}
}, 200);
}
}
3.使用示例
if (countDown > 0) {
this.newAlarm = new Alarm((leftTime: number) => {
const day = parseInt(dayjs.duration(leftTime).format("DD"), 10),
tempHour = dayjs.duration(leftTime).format("HH"),
hour = day > 0 ? String(parseInt(tempHour, 10) + 24 * day) : tempHour;
this.setState({
timer: {
hour,
minute: dayjs.duration(leftTime).format("mm"),
second: dayjs.duration(leftTime).format("ss")
}
});
}, () => {
this.stopAlarm();
});
this.newAlarm?.start(countDown);
}
4.优势
封装性: 将倒计时逻辑封装在 Alarm 类中,易于复用。
灵活性: 通过回调函数 countFunc 和 endFunc,可以自定义倒计时过程中的行为和结束时的处理。
状态管理: 使用 status 属性明确倒计时的状态,便于调试和维护。
721

被折叠的 条评论
为什么被折叠?



