摘要:這么執(zhí)行導(dǎo)致的結(jié)果是每次的時間必然會大于主線程代碼執(zhí)行消耗的時間,而當(dāng)這個主線程代碼執(zhí)行消耗的時間累加起來超過時,就會出現(xiàn)跳一秒的情況。
拜年
新年伊始,本搬磚汪先給各位老爺們拜個晚年,祝各位技術(shù)大牛們在新的一年代碼功底更進一步,家庭幸福美滿!
需求下面進入正題:在翻閱segmentfault社區(qū)時看到某巨廠面試要求實現(xiàn)一個倒計時功能,之前也沒有仔細實現(xiàn)過,趁年初來任務(wù)還沒來得及分配,趕緊著手實現(xiàn)了一個。
第一版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) { // 放大主線程代碼執(zhí)行時間 j++ } console.log(h + "小時", m + "分鐘:", s + "秒(精確到毫秒)", sCeil + "秒(進一法)") } function loop() { loopInner() // 首先var j = 0 if (count === 100) { var endTime = new Date().getTime() console.log("結(jié)束時間:" + endTime) // 打印開始時間 console.log("時間差毫秒數(shù):" + Number(endTime - startTime) + "對應(yīng)秒數(shù):" + Number(endTime - startTime) / 1000) console.log("計時器計算秒數(shù):100") } else { return setTimeout(loop, interval) } } loop()
結(jié)果如下:
第一版實現(xiàn)我使用的是遞歸的setTimeout方法,原因是之前曾經(jīng)看到過遞歸的setTimeout能避免setInterval忽視代碼執(zhí)行時間,而一個事件隊列里只會有一個setInterval事件導(dǎo)致的部分setInterval事件被忽略的情況。這么執(zhí)行導(dǎo)致的結(jié)果是每次setTimeout的時間必然會大于1000ms(1000 + 主線程代碼執(zhí)行消耗的時間),而當(dāng)這個主線程代碼執(zhí)行消耗的時間累加起來超過1s時,就會出現(xiàn)跳一秒的情況。這一版實現(xiàn)方案的結(jié)果不盡如人意。
第二版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("結(jié)束時間:" + endTime) // 打印開始時間 console.log("時間差毫秒數(shù):" + Number(endTime - startTime) + "對應(yīng)秒數(shù):" + Number(endTime - startTime) / 1000) console.log("計時器計算秒數(shù):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) { // 放大主線程代碼執(zhí)行時間 j++ } console.log(h + "小時:", m + "分鐘:", s + "秒(精確到毫秒)", roundS + "秒(四舍五入)") } var Itvid = setInterval(loop, interval)
結(jié)果如下:
這一版的結(jié)果比較接近正確答案,利用setInterval不等待執(zhí)行代碼完成就直接加入隊列的特性(參考setInterval與setTimeout的精確度問題),再加上用Math.round方法修正js的異步方法所造成的幾毫秒的誤差即可。而setInterval畢竟也是瀏覽器的api,同樣是有幾毫秒的差異的。
第三版這一版是我選擇在第一種寫法的基礎(chǔ)上做改良:每次循環(huán)中基于此次代碼執(zhí)行所消耗的時間對下次循環(huán)所消耗的時間間隔做修正。
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); // 代碼執(zhí)行所消耗的時間 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 // 得到下一次循環(huán)所消耗的時間 var j = 0 while (j<100000000) { // 放大主線程代碼執(zhí)行時間 j++ } console.log("時:"+h, "分:"+m, "毫秒:"+s, "秒向上取整:"+sCeil, "代碼執(zhí)行時間:"+offset+"ms", "下次循環(huán)間隔"+currentInterval+"ms") // 打印 時 分 秒 代碼執(zhí)行時間 下次循環(huán)間隔 if (count === 100) { var endTime = new Date().getTime() console.log("結(jié)束時間:" + endTime) // 打印開始時間 console.log("時間差毫秒數(shù):" + Number(endTime - startTime) + "對應(yīng)秒數(shù):" + Number(endTime - startTime) / 1000) console.log("計時器計算秒數(shù):100") } else { setTimeout(loop, currentInterval) } } setTimeout(loop, currentInterval)
結(jié)果如下:
對于同步代碼執(zhí)行耗時不是過大(幾十毫秒到幾百毫秒之間)的情況,通過實驗得到結(jié)果:
setInterval > 修正時間間隔的遞歸setTimeout > 遞歸setTimeout
疑問業(yè)務(wù)場景中是否存在同步代碼執(zhí)行時間超過數(shù)秒的情況?
業(yè)務(wù)場景中實現(xiàn)倒計時的標準做法?
從服務(wù)端端獲取開始時間會有時間損耗(http傳輸?shù)暮臅r),這個耗時有沒有方法規(guī)避?
依然遺留這些問題存在,還請各位不吝賜教。
參考資料JS實現(xiàn)活動精確倒計時
w3.org
javascript線程解釋(setTimeout,setInterval你不知道的事)
歡迎訪問我的博客
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/93219.html
摘要:由于引擎同一時間只執(zhí)行一段代碼這是由單線程的性質(zhì)決定的,所以每個代碼塊阻塞了其它異步事件的進行。這意味著瀏覽器將等待著一個新的異步事件發(fā)生。異步的任務(wù)執(zhí)行的順序是不固定的,主要看返回的速度。 我們經(jīng)常說JS是單線程的,比如node.js研討會上大家都說JS的特色之一是單線程的,這樣使JS更簡單明了,可是大家真的理解所謂JS的單線程機制嗎?單線程時,基于事件的異步機制又該當(dāng)如何,這些知識...
摘要:瀏覽器渲染進程瀏覽器內(nèi)核進程,內(nèi)部是多線程的默認每個頁面一個進程,互不影響。事件觸發(fā)線程歸屬于瀏覽器而不是引擎,用來控制事件循環(huán)可以理解成引擎自己都忙不過來,需要瀏覽器另開線程協(xié)助。 線程和進程 進程和線程的概念可以這樣理解: 進程是一個工廠,工廠有它的獨立資源--工廠之間相互獨立--線程是工廠中的工人,多個工人協(xié)作完成任務(wù)--工廠內(nèi)有一個或多個工人--工人之間共享空間 工廠有多個工人...
摘要:而進程是多線程的,它主要包含以下主要線程渲染線程負責(zé)渲染瀏覽器界面,解析,,構(gòu)建樹和樹,布局和繪制等。且加載解析執(zhí)行會阻止解析器往下執(zhí)行,要強調(diào)渲染和下載是不沖突的,渲染是線程在執(zhí)行,下載是下載線程在執(zhí)行,瀏覽器多線程。 了解瀏覽器線程基礎(chǔ) 一個頁面的呈現(xiàn)主要是由瀏覽器渲染進程實現(xiàn)的(render進程),主要作用為頁面的渲染,腳本執(zhí)行,事件處理等。而render進程是多線程的,它主要包...
閱讀 2081·2023-04-26 02:23
閱讀 1821·2021-09-03 10:30
閱讀 1390·2019-08-30 15:43
閱讀 1221·2019-08-29 16:29
閱讀 573·2019-08-29 12:28
閱讀 2366·2019-08-26 12:13
閱讀 2254·2019-08-26 12:01
閱讀 2451·2019-08-26 11:56