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 定时器那些事儿

  • 相关阅读:
    Scratch-Blockly配置过程
    Scratch www 系统搭建
    Scratch3.0——项目层次结构
    用canvas播放scratch文件
    Scratch3.0——克隆代码仓库的正确姿势
    Scratch3.0——作品截图
    Scratch GUI
    Ubuntu下Visual Studio Code的配置
    关于Ubuntu16.04下phpmyadmin出现mbstring错误的正解
    ubuntu搭建LAMP全教程及简单使用
  • 原文地址:https://www.cnblogs.com/Chen-XiaoJun/p/6230938.html
Copyright © 2011-2022 走看看