Top
Fork me on Gitee

js -- setTimeout 实现倒计时不准确的问题

setTimeout、setInterval 属于定时触发器线程属于 macrotask,它的回调会受到GUI渲染、事件触发、http请求、等的影响。所以这两个不适合做精准的定时。最好的方法是定时矫正,用 new Date(targetDate:Date - new Date ) 格式化成你需要的时分秒即可。

  1. setTimeout、setInterval 等 timer 的执行时间点会受到js同步代码、microtask、ui rendering等的影响,导致设置的delay expired之后无法马上执行;

  2. timer有 节流(throttle) 机制,可以去mdn详细了解下。总之,不要依赖 timer 去做准确的计时计算。PS: 深入使用setTimeout等方法前建议先了解下event loop机制。

重点就是你的 setTimeout 回调函数执行的时机,比较说倒计时,每次我们的回调函数执行时,都要获取当前时间,与函数执行完毕之后的时间,它们两个时间的差就是你的函数执行时间,然后再用 1s 减去函数执行时间,就是下次 setTimeout 执行的时机;
不建议用setInterval,假如函数执行时间较长,则会跳过一次执行。建议用setTimeout递归的方式实现

start = (new Date()).getTime()
setTimeout(() => {
    b = undefined;
    for(var a = 0 ; a < 10000 ; a++) {
           b = a;
    }
    end = (new Date()).getTime()
    excuTime = end - start;
    console.log('excuTime: ', excuTime) // 输出:1003
}, 1000)

下次 setTimeout 的执行时机应该时 1000-excuTime ,通过这样不断修正,来达到尽量的精准性。

倒计时 计算时间差(服务器获取时间)

this.nowDate = Math.floor((new Date().getTime()) / 1000) * 1000 或 res.data.second * 1000 (服务器获取的10位时间戳)
handleCountTime() {
  clearTimeout(this.timer)
  const t1 = Date.now()
  let c1 = 0 // 递归次数
  const interval = 1000 // 间隔
  const sellStartTime = this.goodsDetail.sellStartTime // 售卖开始时间
  if (!sellStartTime) return null
  // let nowDate = this.systemTime
  // if(sellStartTime < nowDate) return false
  var timeDifference = parseInt(sellStartTime - this.nowDate) // 相差的总秒数

  const twentyFour = 24 * 60 * 60 * 1000
  if (timeDifference > twentyFour) return null

  this.countdown.h = Math.floor(timeDifference / 1000 / 60 / 60 % 24)
  this.countdown.m = Math.floor(timeDifference / 1000 / 60 % 60)
  this.countdown.s = Math.floor(timeDifference / 1000 % 60)
  this.countdown.h = this.countdown.h < 10 ? '0' + this.countdown.h : this.countdown.h
  this.countdown.m = this.countdown.m < 10 ? '0' + this.countdown.m : this.countdown.m
  this.countdown.s = this.countdown.s < 10 ? '0' + this.countdown.s : this.countdown.s

  if (timeDifference < 0) {
    this.showCountdown = false
    if (this.goodsDetail.sellStatus === 0) { // 改变页面状态
      this.goodsDetail.sellStatus = 1
    }
    clearTimeout(this.timer)
  } else if (timeDifference === 0) {
    this.showCountdown = true
    if (this.goodsDetail.sellStatus === 0) {
      this.goodsDetail.sellStatus = 1
    }
    this.nowDate += 1000
    // 计算误差
    const offset = Date.now() - (t1 + c1 * interval)
    const nextTime = interval - offset
    c1++
    this.timer = setTimeout(this.handleCountTime, nextTime)
  } else {
    this.showCountdown = true
    this.nowDate += 1000
    // 计算误差
    const offset = Date.now() - (t1 + c1 * interval)
    const nextTime = interval - offset
    c1++
    this.timer = setTimeout(this.handleCountTime, nextTime)
  }
}

获取本地 new Date() 的方法详见 https://www.cnblogs.com/lisaShare/p/11164541.html

posted @ 2022-08-30 16:29  lisashare  阅读(1032)  评论(0编辑  收藏  举报