前两天接到了一个任务从服务器获取时间实现一个倒计时的功能,心想倒计时还不简单吗,不就是一个
setInterval
就搞定的事情吗,可是当我在不同的浏览器上一看发现几分钟后,倒计时就不一样了,火狐比谷歌要快,同一台设备上不同的浏览器都会存在差异,那不同的设备岂不是差别更大,于是为了解决这个问题,我开始了漫长的探索之旅,下面就讲讲我的探索之旅吧。
- 既然时间是从服务器获取的,那么势必会受到网络的影响,当服务器拿到时间的那一刻是最准的时间,但是服务器可能并不会立即返回时间给前端,另外,当经过网络传输之后,时间已经不是之前的那个服务器时间了
- 服务器的时间和客户端的时间是不一致的,并且客户端的时间用户是可以修改的,因此客户端的时间不能作为倒计时用的时间
setTimeout
并不一定会在指定的时间之后立刻执行,而是计算机的最短执行时间之后执行,如果一个操作耗时太长,那么当执行setTimeout
所指定的回调函数时,时间已经超过了预期时间setInterval
在不同处理能力的设备上,甚至是不同的浏览器上都会存在误差,因此也不可用javascript
是单线程的,当程序阻塞的时候,倒计时就会停止,比如使用alert
阻塞程序的执行,倒计时就会停止- 浏览器后台执行一定时间之后可能会自动停止
javascript
的执行,以释放内存 - 移动端程序是可以后台运行的,当程序从后台切换到前台的时候,程序是否会继续运行
- 网络传输
- 计算机执行各种操作,如执行dom操作,代码执行所消耗的时间等
- 将毫秒转换为秒所产生的误差
- 前面说到
setInterval
是存在误差的,那么我们可以采用递归的方式使用setTimeout
模拟setInterval
来实现定时器的功能 - 假设网络传输发送请求和接收请求是相同的,那么可以在发起网络请求之前获取客户端时间设为
clientTime1
, 当收到请求之后设置时间设为clientTime2
,那么从服务器发送数据开始到前台接收到请求成功这段时间设为:netError
,可以粗略的计算出netError = (clientTime2 - clientTime1) / 2
- 假设第一次执行的时间为
prevTime
,之后每次执行的时间为nextTime
,执行的次数为loopTimes
那么我们可以在每次执行的时候计算出与第一次执行时间相差errorTime = loopTimes * 1000
毫秒之后存在的误差,那么下次执行的时间就设置为1000 - errorTime
- 至于秒转换为毫秒产生的误差,我们可以让第一次的执行放在
setTimeout
里,设置时间为误差时间之后执行
切换前后台导致的问题,暂时没有太好的解决办法,但是 webwoker
可以作为参考,因为在移动端还存在问题
误差的存在是不可避免的,但是我们可以通过计算去不断地减小误差,只要能让多设备实现同步显示,基本上就问题不大了