摘要:这么执行导致的结果是每次的时间必然会大于主线程代码执行消耗的时间,而当这个主线程代码执行消耗的时间累加起来超过时,就会出现跳一秒的情况。
拜年
新年伊始,本搬砖汪先给各位老爷们拜个晚年,祝各位技术大牛们在新的一年代码功底更进一步,家庭幸福美满!
需求下面进入正题:在翻阅segmentfault社区时看到某巨厂面试要求实现一个倒计时功能,之前也没有仔细实现过,趁年初来任务还没来得及分配,赶紧着手实现了一个。
第一版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()
结果如下:
第一版实现我使用的是递归的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)
结果如下:
这一版的结果比较接近正确答案,利用setInterval不等待执行代码完成就直接加入队列的特性(参考setInterval与setTimeout的精确度问题),再加上用Math.round方法修正js的异步方法所造成的几毫秒的误差即可。而setInterval毕竟也是浏览器的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)
结果如下:
对于同步代码执行耗时不是过大(几十毫秒到几百毫秒之间)的情况,通过实验得到结果:
setInterval > 修正时间间隔的递归setTimeout > 递归setTimeout
疑问业务场景中是否存在同步代码执行时间超过数秒的情况?
业务场景中实现倒计时的标准做法?
从服务端端获取开始时间会有时间损耗(http传输的耗时),这个耗时有没有方法规避?
依然遗留这些问题存在,还请各位不吝赐教。
参考资料JS实现活动精确倒计时
w3.org
javascript线程解释(setTimeout,setInterval你不知道的事)
欢迎访问我的博客
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/93219.html
摘要:浏览器渲染进程浏览器内核进程,内部是多线程的默认每个页面一个进程,互不影响。事件触发线程归属于浏览器而不是引擎,用来控制事件循环可以理解成引擎自己都忙不过来,需要浏览器另开线程协助。 线程和进程 进程和线程的概念可以这样理解: 进程是一个工厂,工厂有它的独立资源--工厂之间相互独立--线程是工厂中的工人,多个工人协作完成任务--工厂内有一个或多个工人--工人之间共享空间 工厂有多个工人...
摘要:而进程是多线程的,它主要包含以下主要线程渲染线程负责渲染浏览器界面,解析,,构建树和树,布局和绘制等。且加载解析执行会阻止解析器往下执行,要强调渲染和下载是不冲突的,渲染是线程在执行,下载是下载线程在执行,浏览器多线程。 了解浏览器线程基础 一个页面的呈现主要是由浏览器渲染进程实现的(render进程),主要作用为页面的渲染,脚本执行,事件处理等。而render进程是多线程的,它主要包...
阅读 2045·2023-04-26 02:23
阅读 1789·2021-09-03 10:30
阅读 1351·2019-08-30 15:43
阅读 1191·2019-08-29 16:29
阅读 530·2019-08-29 12:28
阅读 2332·2019-08-26 12:13
阅读 2169·2019-08-26 12:01
阅读 2400·2019-08-26 11:56