zoukankan      html  css  js  c++  java
  • 浏览器定时器问题

      最近在做一个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来定点清除
     
     
    就先总结到这里了!
    生活总是催促我们向前!
  • 相关阅读:
    Confluence 6 PostgreSQL 设置准备
    Confluence 6 MySQL 问题解决
    Confluence 6 MySQL 测试你的数据库连接
    Confluence 6 MySQL 输入你的数据库细节
    Confluence 6 下载和安装 MySQL 驱动
    Confluence 6 MySQL 创建数据库和数据库用户
    Confluence 6 配置 MySQL 服务器
    Confluence 6 MySQL 数据库设置准备
    Confluence 6 SQL Server 问题解决
    Confluence 6 SQL Server 数据库驱动修改
  • 原文地址:https://www.cnblogs.com/bob1314/p/5643316.html
Copyright © 2011-2022 走看看