前言
了解过js调度的应该都明白,js中定时器不是精确的延时,这个延时受到服务端传送延时,前端渲染和其他事件阻塞的影响。那么,我们应该怎样实现相对精确的计时呢?
先来看一下倒计时演示
var start = Date.now();
var count = 0;
//定时器测试
setInterval(function () {
count++;
console.log(Date.now() - (start + count * 1000));
}, 1000);
道理来说,每一次输出应该都是0;但是实际输出就反映了,我们说到的定时器不精确的问题。
结论:由于代码执行占用时间和其他事件阻塞原因,导致有些事件执行延迟了几ms,但影响很微
- 增加一段阻塞代码
var start = Date.now();
var count = 0;
//占用线程事件
setInterval(function () {
var j = 0;
while (j++ < 100000000);
}, 0);
//定时器测试
setInterval(function () {
count++;
console.log(Date.now() - (start + count * 1000));
}, 1000);
结论:由于加了很占线程的阻塞事件,导致定时器事件每次执行延迟越来越严重。
解决思路
考虑,正常思路,没有阻塞事件,下面设置好的定时器应该每隔1s执行一次
setInterval(function() {}, 1000);
如果出现阻塞事件定时器,隔1000+ms才执行一次,要想精确实现,必须获取阻塞时间
setInterval(function () {
let j = 0;
while (j++ < 100000000) {}
}, 1);
let interval = 1000,
ms = 50000, //从服务器和活动开始时间计算出的时间差,这里测试用50000ms
count = 0,
startTime = Date.now();
if (ms >= 0) {
var timeCounter = setTimeout(countDownStart, interval);
}
setInterval是一段阻塞代码。然后,我们分别定义了interval作为定时器的执行时间
function countDownStart() {
count++;
let offset = Date.now() - (startTime + count * interval);
let nextTime = interval - offset;
if (nextTime < 0) {
nextTime = 0;
}
ms -= interval;
console.log("误差:" + offset + "ms");
if (ms < 0) {
clearTimeout(timeCounter);
} else {
timeCounter = setTimeout(countDownStart, nextTime);
}
}
offset用来记录阻塞导致延误的时间是多少
nextTime代表offset和interval的差距,根据nextTime修改定时器timeCounter的执行时间
- 如果上一次执行过程中因为阻塞延误了100ms,那么下一次就提前100ms启动定时器
- 如果nextTime<0,即延误超过1s,那么将即刻启动定时器
做100%精确的倒计时很难,但做到相对比较准确是可以的。
在倒计时功能开发中,有几点总结:
要了解好js单线程工作原理;
清楚了解服务器系统时间传送到前端的流程;
了解前端渲染和线程阻塞造成的时间误差