新年伊始,本搬磚汪先給各位老爺們拜個晚年,祝各位技術大牛們在新的一年代碼功底更進一步,家庭幸福美滿!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
初版實現我使用的是遞歸的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
這一版的結果比較接近正確答案,利用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)
結果以下:異步
對於同步代碼執行耗時不是過大(幾十毫秒到幾百毫秒之間)的狀況,經過實驗獲得結果:oop
setInterval > 修正時間間隔的遞歸setTimeout > 遞歸setTimeout
spa
依然遺留這些問題存在,還請各位不吝賜教。
JS實現活動精確倒計時
w3.org
javascript線程解釋(setTimeout,setInterval你不知道的事)
歡迎訪問個人博客