zoukankan      html  css  js  c++  java
  • setTimeout和setInterval的注意事项

    精准问题

    setTimeout的问题在于它并不是精准的,例如使用setTimeout设定一个任务在10ms后执行,但是在9ms后,有一个任务占用了5ms的cpu时间片,再次轮到定时器执行时,时间已经过期了4ms,那么是不是说setInterval就是准确的呢?

    然而并不是,setInterval存在两个问题:

    1. 时间间隔可能会跳过

    2. 时间间隔可能小于定时器设定的时间

    请看以下代码:

    function click() { 
        // code block1... 
        setInterval(function() { 
            // process ... 
        }, 200); 
        // code block2 
    }

    我们假设通过一个click, 触发了setInterval以实现每隔一个时间段执行process代码

    在205ms时执行setInterval, 以此为一个时间点, 在205ms时插入process代码, process代码开始执行, 然而process代码执行的时间超过了接下来一个插入时间点405ms, 这样代码队列后又插入了一份process代码, process继续执行着, 而且超过了605ms这个插入时间点 
    下面问题来了, 由于代码队列中已经有了一份未执行的process代码(405m时插入的), 所以605ms这个插入时间点将会被跳过, 因为js引擎只允许有一份未执行的process代码

    为了避免这种情况可以使用setTimeout递归调用 
    代码如下:

    setTimeout(function(){
       // processing
       setTimeout(arguments.callee, interval);
    }, interval);

    每次函数执行的时候都会创建一个新的定时器,第二个setTimeout调用使用了arguments.callee来获取对当前执行的函数的引用,并为其设置另外一个定时器。这样做是为了在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔,也保证了在下一次定时器代码执行之前,至少要等待指定的间隔,避免了连续的运行。

    参数问题

    window.setTimeout(expression,milliseconds); window.setInterval(expression,milliseconds);

    其中,expression可以是用引号括起来的一段代码,也可以是一个函数名,到了指定的时间,系统便会自动调用该函数,当使用函数名作为调用句柄时,不能带有任何参数;而使用字符串时,则可以在其中写入要传递的参数。两个方法的第二个参数是milliseconds,表示延时或者重复执行的毫秒数。

    注意:如果第一个参数使用函数名,到了指定的时间,系统便会自动调用该函数,但函数不能带有任何参数,而且只能给出函数的名。

    如果函数给出参数,这将使sum函数立即执行,并将返回值作为调用句柄传递给setInterval函数,其结果并不是程序需要的。

    使用字符串形式可以达到想要的结果:

    但这种写法不够直观,而且有些场合必须使用函数名,下面用一个小技巧来实现带参数函数的调用:

    <script type="text/javascript"> 
      var userName="jack";
      //根据用户名显示欢迎信息
      function hello(_name){    
        alert("hello,"+_name);
      }
      //创建一个函数,用于返回一个无参数函数
      function _hello(_name){    
        return function(){       
         hello(_name);    } }
      window.setTimeout(_hello(userName),3000);
    </script>

    这里定义了一个函数_hello,用于接收一个参数,并返回一个不带参数的函数,在这个函数内部使用了外部函数的参数,从而对其调用,不需要使用参数。在window.setTimeout函数中,使用_hello(userName)来返回一个不带参数的函数句柄,从而实现了参数传递的功能。

    关于this

    在Javascript里,setTimeout和setInterval接收第一个参数是一个字符串或者一个函数,当在一个对象里面用setTimeout延时调用该对象的方法时 

    function obj() { 
      this.fn = function() { 
        alert("ok"); 
        console.log(this); 
        setTimeout(this.fn, 1000);//直接使用this引用当前对象 
      } 
    } 
    var o = new obj(); 
    o.fn(); 

    这代码的目的是想让它不断输出“OK”“obj{}”,然后我们发现上面的代码不是想要的结果,原因是setTimeout里面的this是指向window,所以要调用的函数变成 window.fn 为undefined。所以问题的关键在于得到当前对象的引用,于是有以下三种方法 

    方法一

    function obj() { 
      this.fn = function() { 
        alert("ok"); 
        console.log(this); 
        setTimeout(this.fn.bind(this), 1000);//通过Function.prototype.bind 绑定当前对象 
      } 
    } 
    var o = new obj(); 
    o.fn(); 

    这样可以得到正确的结果,可惜Function.prototype.bind方法是ES5新增的标准,测试了IE系列发现IE6-8都不支持,只有IE9+可以使用。

    关于bind的用法:https://msdn.microsoft.com/zh-cn/library/ff841995

    要想兼容就得简单的模拟下bind,看下面的代码 

    方法二

    function obj() { 
      this.fn = function() { 
        alert("ok"); 
        setTimeout((function(a,b){ 
          return function(){ 
            b.call(a); 
          } 
        })(this,this.fn), 1000);//模拟Function.prototype.bind 
      } 
    } 
    var o = new obj(); 
    o.fn(); 

    首先通过一个自执行匿名函数传当前对象和对象方法进去,也就是里面的参数a和b,再返回一个闭包,通过call方法使this指向正确。

    方法三

    function obj() { 
      this.fn = function() { 
        var that = this;//保存当前对象this 
        alert("ok"); 
        setTimeout(function(){ 
          that.fn(); 
        }, 1000);//通过闭包得到当前作用域,好访问保存好的对象that 
      } 
    } 
    var o = new obj(); 
    o.fn(); 

    两个关键点是 保存当前对象this为别名that 和 通过闭包得到当前作用域,以访问保存好的对象that;当对象方法里面多层嵌套函数或者setTimeout,setInterval等方法丢失this(也就是this不指向当前对象而是window),所以在this指向正确的作用域保存var that = this就变得很实用了

    -20170321

    // 下面代码执行之后会输出什么?
    var intervalId, timeoutId;
    
    timeoutId = setTimeout(function () {
     console.log(1);
    }, 300);
    
    setTimeout(function () {
     clearTimeout(timeoutId);
     console.log(2);
    }, 100);
    
    setTimeout('console.log("5")', 400);
    
    intervalId = setInterval(function () {
     console.log(4);
     clearInterval(intervalId);
    }, 200);
    
    // 分别输出: 2、4、5

    JS定时器的工作原理

    上图中,左侧数字代表时间,单位毫秒;左侧文字代表某一个操作完成后,浏览器去询问当前队列中存在哪些正在等待执行的操作;蓝色方块表示正在执行的代码块;右侧文字代表在代码运行过程中,出现哪些异步事件。该图大致流程如下:

    • 程序开始时,有一个JS代码块开始执行,执行时长约为18ms,在执行过程中有3个异步事件触发,其中包括一个setTimeout、鼠标点击事件、setInterval
    • 第一个setTimeout先运行,延迟时间为10ms,稍后鼠标事件出现,浏览器在事件队列中插入点击的回调函数,稍后setInterval运行,10ms到达之后,setTimeout向事件队列中插入setTimeout的回调
    • 当第一个代码块执行完成后,浏览器查看队列中有哪些事件在等待,他取出排在队列最前面的代码来执行
    • 在浏览器处理鼠标点击回调时,setInterval再次检查到到达延迟时间,他将再次向事件队列中插入一个interval的回调,以后每隔指定的延迟时间之后都会向队列中插入一个回调
    • 后面浏览器将在执行完当前队头的代码之后,将再次取出目前队头的事件来执行

    需要注意的点

    • setTimeout有最小时间间隔限制,HTML5标准为4ms,小于4ms按照4ms处理,但是每个浏览器实现的最小间隔都不同
    • 因为JS引擎只有一个线程,所以它将会强制异步事件排队执行
    • 如果setInterval的回调执行时间长于指定的延迟,setInterval将无间隔的一个接一个执行
    • this的指向问题可以通过bind函数、定义变量、箭头函数的方式来解决

    setImmediate: 在浏览器完全结束当前运行的操作之后立即执行指定的函数(仅IE10和Node 0.10+中有实现),类似setTimeout(func, 0)

    var immediateId = setImmediate(func[, param1, param2, ...]);
    var immediateId = setImmediate(func);

    requestAnimationFrame: 专门为实现高性能的帧动画而设计的API,但是不能指定延迟时间,而是根据浏览器的刷新频率而定(帧)

    var requestId = window.requestAnimationFrame(func);

    process.nextTick():Node.js是单线程的,除了系统IO之外,在它的事件轮询过程中,同一时间只会处理一个事件。你可以把事件轮询想象成一个大的队列,在每个时间点上,系统只会处理一个事件。即使你的电脑有多个CPU核心,你也无法同时并行的处理多个事件。但也就是这种特性使得node.js适合处理I/O型的应用,不适合那种CPU运算型的应用。在每个I/O型的应用中,你只需要给每一个输入输出定义一个回调函数即可,他们会自动加入到事件轮询的处理队列里。当I/O操作完成后,这个回调函数会被触发。然后系统会继续处理其他的请求。在这种处理模式下,process.nextTick()的意思就是定义出一个动作,并且让这个动作在下一个事件轮询的时间点上执行。

    参考:

    深入理解定时器:setTimeout与setInterval

    Javascript 定时器那些事儿

  • 相关阅读:
    一个好的时间函数
    Codeforces 785E. Anton and Permutation
    Codeforces 785 D. Anton and School
    Codeforces 510 E. Fox And Dinner
    Codeforces 242 E. XOR on Segment
    Codeforces 629 E. Famil Door and Roads
    Codeforces 600E. Lomsat gelral(Dsu on tree学习)
    Codeforces 438D The Child and Sequence
    Codeforces 729E Subordinates
    【ATcoder】D
  • 原文地址:https://www.cnblogs.com/Chen-XiaoJun/p/6230938.html
Copyright © 2011-2022 走看看