从倒计时的实现深刻探究setTimout与setInterval

拜年

新年伊始,本搬砖汪先给各位老爷们拜个晚年,祝各位技术大牛们在新的一年代码功底更进一步,家庭幸福美满!javascript

需求

下面进入正题:在翻阅segmentfault社区时看到某巨厂面试要求实现一个倒计时功能,以前也没有仔细实现过,趁年初来任务还没来得及分配,赶忙着手实现了一个。html

初版

var period = 60*1000*60*2
var end = new Date().getTime() + period
var date = new Date(end)
var interval = 1000
var count = 0
var startTime = new Date().getTime()

console.log('开始时间:' + startTime)

function loopInner() {
  count++

  var diff = end - new Date().getTime()
  var h = Math.floor(diff / (60*1000*60))
  var hdiff = diff % (60*1000*60)
  var m = Math.floor(hdiff / (60*1000))
  var mdiff = hdiff % (60*1000)
  var s = mdiff / 1000
  var sCeil = Math.ceil(s)

  var j = 0
  while (j<100000000) { // 放大主线程代码执行时间
    j++
  }

  console.log(h + '小时', m + '分钟:', s + '秒(精确到毫秒)', sCeil + '秒(进一法)')
}

function loop() {
  loopInner() // 首先var j = 0

  if (count === 100) {
    var endTime = new Date().getTime()
    console.log('结束时间:' + endTime) // 打印开始时间
    console.log('时间差毫秒数:' + Number(endTime - startTime) + '对应秒数:' + Number(endTime - startTime) / 1000)
    console.log('计时器计算秒数:100')
  } else {
    return setTimeout(loop, interval)
  }
}

loop()

结果以下:java

code_1_1
code_1_2

初版实现我使用的是递归的setTimeout方法,缘由是以前曾经看到过递归的setTimeout能避免setInterval忽视代码执行时间,而一个事件队列里只会有一个setInterval事件致使的部分setInterval事件被忽略的状况。这么执行致使的结果是每次setTimeout的时间必然会大于1000ms(1000 + 主线程代码执行消耗的时间),而当这个主线程代码执行消耗的时间累加起来超过1s时,就会出现跳一秒的状况。这一版实现方案的结果不尽如人意。面试

第二版

var period = 60 * 1000 * 60 * 2
var end
var date = new Date(end)
var interval = 1000
var count = 0
var startTime = new Date().getTime()

console.log('开始时间:' + startTime)

var loop = function () {
  count++
  if (count === 100) {
    var endTime = new Date().getTime()
    console.log('结束时间:' + endTime) // 打印开始时间
    console.log('时间差毫秒数:' + Number(endTime - startTime) + '对应秒数:' + Number(endTime - startTime) / 1000)
    console.log('计时器计算秒数:100')
    return clearInterval(Itvid)
  }

  if (!end) { end = new Date().getTime() + period }
  var diff = end - new Date().getTime()
  var h = Math.floor(diff / (60 * 1000 * 60))
  var hdiff = diff % (60 * 1000 * 60)
  var m = Math.floor(hdiff / (60 * 1000))
  var mdiff = hdiff % (60 * 1000)
  var s = mdiff / (1000)
  var roundS = Math.round(s)

  var j = 0
  while (j<100000000) { // 放大主线程代码执行时间
    j++
  }

  console.log(h + '小时:', m + '分钟:', s + '秒(精确到毫秒)', roundS + '秒(四舍五入)')
}
var Itvid = setInterval(loop, interval)

结果以下:segmentfault

code_2_1
code_2_2

这一版的结果比较接近正确答案,利用setInterval不等待执行代码完成就直接加入队列的特性(参考setInterval与setTimeout的精确度问题),再加上用Math.round方法修正js的异步方法所形成的几毫秒的偏差便可。而setInterval毕竟也是浏览器的api,一样是有几毫秒的差别的。api

第三版

这一版是我选择在第一种写法的基础上作改良:每次循环中基于这次代码执行所消耗的时间对下次循环所消耗的时间间隔作修正。浏览器

var period = 60 * 1000 * 60 * 2
var startTime = new Date().getTime();
var count = 0
var end = new Date().getTime() + period
var interval = 1000
var currentInterval = interval

console.log('开始时间:' + startTime) // 打印开始时间

function loop() {
  count++
  var offset = new Date().getTime() - (startTime + count * interval); // 代码执行所消耗的时间
  var diff = end - new Date().getTime()
  var h = Math.floor(diff / (60 * 1000 * 60))
  var hdiff = diff % (60 * 1000 * 60)
  var m = Math.floor(hdiff / (60 * 1000))
  var mdiff = hdiff % (60 * 1000)
  var s = mdiff / (1000)
  var sCeil = Math.ceil(s)
  var sFloor = Math.floor(s)
  currentInterval = interval - offset // 获得下一次循环所消耗的时间

  var j = 0
  while (j<100000000) { // 放大主线程代码执行时间
    j++
  }

  console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执行时间:'+offset+'ms', '下次循环间隔'+currentInterval+'ms') // 打印 时 分 秒 代码执行时间 下次循环间隔
  if (count === 100) {
    var endTime = new Date().getTime()
    console.log('结束时间:' + endTime) // 打印开始时间
    console.log('时间差毫秒数:' + Number(endTime - startTime) + '对应秒数:' + Number(endTime - startTime) / 1000)
    console.log('计时器计算秒数:100')
  } else {
    setTimeout(loop, currentInterval)
  }
}

setTimeout(loop, currentInterval)

结果以下:异步

code_3_1
code_3_2

暂时性结论

对于同步代码执行耗时不是过大(几十毫秒到几百毫秒之间)的状况,经过实验获得结果:oop

setInterval > 修正时间间隔的递归setTimeout > 递归setTimeoutspa

疑问

  1. 业务场景中是否存在同步代码执行时间超过数秒的状况?
  2. 业务场景中实现倒计时的标准作法?
  3. 从服务端端获取开始时间会有时间损耗(http传输的耗时),这个耗时有没有方法规避?

依然遗留这些问题存在,还请各位不吝赐教。

参考资料

JS实现活动精确倒计时
w3.org
javascript线程解释(setTimeout,setInterval你不知道的事)

原文

欢迎访问个人博客

相关文章
相关标签/搜索