從倒計時的實現深刻探究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你不知道的事)

原文

歡迎訪問個人博客

相關文章
相關標籤/搜索