浏览器定时器问题

浏览器定时器问题

  最近在做一个h5小游戏,游戏里有这样一个场景,两个倒计时同时在一分钟的时候开始倒计时,两者几乎是同时开始的,理论上,我希望的结果是他们能够几乎同时倒计时到0,但是,实际发现,在一段时间内,他们产生了很大的误差(大于一秒),于是,真正认识到js的定时器是那么的不靠谱了。为啥不靠谱。本篇文章来理理总结下。

  浏览器中的主要的定时器有两种,世人皆知:setTimeout和setInterval,这两个方法让程序在浏览器中的延时执行,为什么说是浏览器中而不是js中,因为定时器方法是浏览器宿主提供的,而非js本身自带,脱离了浏览器也许就不管用了。
使用实例
1 var timerId=setTimeout(function(){
2      alert("我要延时两秒来执行");
3 },2000);
1 var timerId1= setInterval(function(){
2    alert("我一秒钟执行一次");
3 },1000);

如果一切正常,第一段代码将在两秒后弹出弹窗,第二段代码将没一秒钟弹出一次弹窗。一切总是那么的顺畅。我们仿佛看到一个掐着秒表的调度员在调度和控制这些。

再看一段代码
1 setTimeout(function(){
2    alert(1);
3 },1000);
4 alert(2);

执行这段代码,我们发现先弹出了2,再弹出了1;也就是说我们发现定时器的一个特性,那就是“异步执行”!

  当然js是单线程的,为何能产生了异步的特性。回想下,在我们的js代码里,还有谁是异步呢,很自然,我们想到了ajax的异步。还有鼠标点击事件的异步,
这是为什么呢?难道还有其他线程在操控着这些异步。答案是肯定的。
 
回顾下几个知识点:
 
1.浏览器5个常驻线程
    js引擎分配的线程
    GUI渲染页面的线程
    浏览器事件线程
    浏览器定时器触发线程
    浏览器http请求线程
 
那么就很好理解了,js引擎的线程按照他的单线程按部就班的执行着代码。GUI负责渲染页面,但是他和js引擎的线程是互斥的,也就是只允许同时又一个线程在执行。浏览器事件线程负责处理浏览器点击等事件,浏览器定时器线程负责处理定时器的任务安排,http请求线程负责处理网络请求。
 
一切看上去很井然有序,那既然有专门的线程去安排这些事情,为何文章开篇会产生定时误差呢,
  原来浏览器的多线程其实只是处理事件的触发,至于事件的回调的执行,还是交给js的引擎线程来执行的,因为js引擎是单线程的,所以在js引擎中维护着一个队列,当这些事件触发的时候,就会把回调插入到这个位于正常js代码的队列中,队列是在正常代码块的末尾位置。正因为最终他们的回调还是单线程执行的,所以一旦上一步操作中有比较耗时间的操作,定时操作就会向后产生延误。那么就会出现上面开题所说的情况,而且这种延误会产生叠加。看下下面这张图,再理解下,为何上面的代码块先执行了alert(2)然后再执行了alert(1);
图片来源(http://hao.jser.com/archive/8414/)

 

这下似乎问题都出来了,在我的小游戏当中,维持着很多缓动定时器还有一些轮询的ajax,所以可以理解有很多耗时的任务,他们阻碍了定时器的按部就班。
  那如何修复这种情况呢,毕竟问题还是要解决的。
 
  两种解决方案
  1.尽量使用setTimeout来替代setInterval,具体做法是:
   
复制代码
1 function timer(fn,delay){
2      var s=setTimeout(function(){
3           fn();
4           timer(fn,delay);
5     },delay)      
6 }    
复制代码

这种做法的原理是,每次执行setTimeout的时候都会重新触发定时器事件,重新插入队列事件,延误会再下次被清零,避免了连续的队列延迟产生叠加

2.即时校准

复制代码
 1 var count=0;
 2 var t1=(new date()).getTime();
 3 var delay=1000;
 4 function timer(fn,delay){
 5      var s=setTimeout(function(){
 6         count++;
 7         fn();
 8         var t2=(new date()).getTime();
 9         var newdelay = delayer - (t2-t1-count*delay);
10         timer(fn,newdelay);
11     })
12 }    
13 timer(function(){
14     console.log(111);
15 },delay);
复制代码

这种方式比起第一种方式更加暴力,效果明显,保证了时间最终的基本一致,但是会有因为校准而产生的时间跳跃现象。

好,问题算是解决了。

定时器还会涉及一些问题
1.那就是定时器的作用域里 this指向的肯定是window 除非你强制改变。
2.定时器哪怕你delay设置为0,也是会被插入到最后的队列中执行的。
3.定时器执行后会返回一个定时的索引给你,你可以通过clearTimeout 和clearInterval来定点清除
 
 
就先总结到这里了!
生活总是催促我们向前!
分类:  JavaScript
4
0
« 上一篇: 一个简单的创建dom的函数
posted @  2016-07-05 14:14  陆小森 阅读( 1481) 评论( 5编辑  收藏

  
#1楼   2016-07-05 15:09  tomclock   
讲的很好呀。不过有一点,不是很清楚,为什么要尽量用setTimeout代替setInterval? 二者的本质区别是什么?
  
#2楼   2016-07-05 15:12  Jeby Sun   
就冲这较真的劲,我也要评论支持一下。
  
#3楼   2016-07-05 15:52  幻天芒   
@ tomclock
引用 讲的很好呀。不过有一点,不是很清楚,为什么要尽量用setTimeout代替setInterval? 二者的本质区别是什么?

用这个方式,能够保证每次的延迟不会太大,不像setInterval会累计延迟。
  
#4楼   2016-07-06 11:49  晴雪的夏天   
你好,那个图我怎么看不懂呢,alert(1)和alert(2)跟setInterval有什么关系呢?
  
#5楼   2016-07-06 14:57  囧月言炎   
mark。
相关文章
相关标签/搜索