手机登录/注册
X
登录
由于
js
的EventLoop
执行机制,setTimeout/setInterval
只是将事件插入了任务队列,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定会在setTimeout()
设定的延迟回调时间执行。所以此时 等待执行的实际时间=
等待执行栈为空的时间+
设定的延迟回调时间,此时时间偏差就产生了,即 时间偏差=
等待执行栈为空的时间。
可以这么去理解,
setTimeout(fn,0)
的含义是,指定某个任务在主线程最早可得空闲时间执行,也就是说,尽可能早的执行。它在任务队列的尾部添加一个事件,因此要等到同步任务和任务队列现有的事件都处理完,才会得到执行,而不是设定的回调时间0
就会立刻去执行了。
以后端返回服务器时间为准,通过定时向服务器发送请求,获取最新的时间时间偏差,以此来校准倒计时时间。但是这种方法消耗服务端资源较大,会存在并发问题;而且接口请求返回时间本身也存在时间偏差,增加了问题的不可控因素与复杂度
思路:
用递归的方法执行倒计时,在每次递归调用setTimeout
回调的时候,计算出时间偏差,在下一次执行setTimeout
时,把原设定的延迟回调时间减去时间偏差即可。
// 设定倒计时规则为每秒倒计时
const interval = 1000
// 设定总倒计时长为30s
let totalCount = 30000
// 记录递归已执行次数,以倒计时时间间隔 interval=1s 为例,那么count就相当于如果没有时间偏差情况下的理想执行时间
let count = 0
// 记录程序开始运行的时间
const startTime = new Date().getTime();
let timeoutID = setTimeout(countDownFn, interval)
// 倒计时回调函数
function countDownFn() {
// count自增,记录理想执行时间
count++
// 获取当前时间-刚开始记录的startTime-理想执行时间得到时间偏差=等待执行栈为空的时间
const offset = new Date().getTime() - startTime - count * interval
// 根据时间偏差,计算下次倒计时设定的回调时间,从而达到纠正的目的
let nextTime = interval - offset
if (nextTime < 0 ) {
nextTime = 0
}
totalCount -= interval
if (totalCount < 0) {
clearTimeout(timeoutID)
} else {
timeoutID = setTimeout(countDownStart, nextTime)
}
}