您的位置:首页 > 其它

浏览器定时器问题

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

  浏览器中的主要的定时器有两种,世人皆知:setTimeout和setInterval,这两个方法让程序在浏览器中的延时执行,为什么说是浏览器中而不是js中,因为定时器方法是浏览器宿主提供的,而非js本身自带,脱离了浏览器也许就不管用了。
使用实例

var timerId=setTimeout(function(){
alert("我要延时两秒来执行");
},2000);


var timerId1= setInterval(function(){
alert("我一秒钟执行一次");
},1000);


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

再看一段代码

setTimeout(function(){
alert(1);
},1000);
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,具体做法是:

function timer(fn,delay){
var s=setTimeout(function(){
fn();
timer(fn,delay);
},delay)
}


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

2.即时校准

var count=0;
var t1=(new date()).getTime();
var delay=1000;
function timer(fn,delay){
var s=setTimeout(function(){
count++;
fn();
var t2=(new date()).getTime();
var newdelay = delayer - (t2-t1-count*delay);
timer(fn,newdelay);
})
}
timer(function(){
console.log(111);
},delay);


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

好,问题算是解决了。

定时器还会涉及一些问题
1.那就是定时器的作用域里 this指向的肯定是window 除非你强制改变。
2.定时器哪怕你delay设置为0,也是会被插入到最后的队列中执行的。
3.定时器执行后会返回一个定时的索引给你,你可以通过clearTimeout 和clearInterval来定点清除

就先总结到这里了!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: