zoukankan      html  css  js  c++  java
  • 解决js定时器不准的问题

    为什么会出现定时器不准呢?

    这个就得从js的执行机制说起了,在事件循环(EventLoop)执行机制中,异步事件(setInterval/setTimeout)会把回调函数放入消息队列(Event Queue)中,主线程的宏任务执行完毕后,依次执行消息队列中的微任务,等微任务执行完了再循环回来执行宏任务。由于消息队列中存在大量的任务,其他任务的执行时间就会造成定时器回调函数的延迟,如果不处理,就会一直叠加延迟,当运行时间久了之后,相差就会很大。

    因此定时器是不能完全保证的。

    解决方案

    1. 动态计算时差(仅针对循环定时器起到修正作用)

    在定时器开始前和在运行时动态获取当前时间戳,在设置下一次定时时长时,在期望值的基础上减去当前差值,以获取相对精确的定时器运行效果

    此方法仅能消除setInterval长时间运行造成的误差,或者setTimeout循环长时间运行的累计误差,无法对当个定时器消除执行的延迟

    // 每秒倒计时的实现
    let startTime, // 开始时间
      count, // 计数器
      runTime, // 当前时间
      downSecond = 1200,  // 倒计时时间
      loopTimer = null;
    
    function resetDefaultValue() {
    
      startTime = Date.now();
      count = 0;
      runTime = 0;
    }
    
    resetDefaultValue(); //每次倒计时执行前要重置一下初始值
    loop();
    function loop() {
    
      runTime = Date.now();
      let offsetTime = runTime - (startTime + count * 1000); //时间差
      count++;
      let nextTime = 1000 - offsetTime; //下一次定时器需要的时间
      nextTime = nextTime > 0 ? nextTime : 0;
      downSecond-- ;
      // 处理逻辑区域 ---- s
      console.log('时间差:'+offsetTime, ',下一次需要时间:'+ nextTime)
      if (downSecond <= 0) {
    
        // 结束定时器
        clearTimeout(loopTimer)
        loopTimer = null;
        return false;
      }
      // 处理逻辑区域 ---- e
      loopTimer = setTimeout(loop, nextTime);
    }

    var count = count2 = 0;
     var runTime,runTime2;
     var startTime,startTime2 = performance.now();//获取当前时间
     
     //普通任务-对比
     setInterval(function(){
         runTime2 = performance.now();
         ++count2;    
         console.log("普通任务",count2 + ' --- 延时:' + (runTime2 - (startTime2 + count2 * 1000)) + ' 毫秒');
     }, 1000);
     
     //动态计算时长
     function func(){
      runTime = performance.now();
         ++count;
         let time = (runTime - (startTime + count * 1000));
         console.log("优化任务",count2 + ' --- 延时:' + time +' 毫秒');
         //动态修正定时时间
         t = setTimeout(func,1000 - time);
     }
     startTime = performance.now();
     var t = setTimeout(func , 1000);
     
     //耗时任务
     setInterval(function(){
         let i = 0;
         while(++i < 100000000);
     }, 0);

     从上面看出,不管是setTimeout还是setInterval,在长时间运行中,都会存在误差,而修正就是将定时器拉会原来的轨道

    2. 使用web worker

    Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

    <html>
    <meta charset="utf-8">
    <body>
    <script type="text/javascript">
    var count = 0;
    var runTime;
    
    //performance.now()相对Date.now()精度更高,并且不会受系统程序堵塞的影响。
    //API:https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now
    var startTime = performance.now(); //获取当前时间
    
    //普通任务-对比测试
    setInterval(function(){
        runTime = performance.now();
        ++count;    
        console.log("普通任务",count + ' --- 普通任务延时:' + (runTime - (startTime + 1000))+' 毫秒');
        startTime = performance.now();
    }, 1000);
    
    //耗时任务
    setInterval(function(){
        let i = 0;
        while(i++ < 100000000);
    }, 0);
    
    // worker 解决方案
    let worker = new Worker('worker.js');
    </script>
    </body>
    </html>
    // worker.js
    var count = 0;
    var runTime;
    var startTime = performance.now();
    setInterval(function(){
       runTime = performance.now();
       ++count;
       console.log("worker任务",count + ' --- 延时:' + (runTime - (startTime + 1000))+' 毫秒');
       startTime = performance.now();
    }, 1000);

     可以看到使用worker后,延迟会非常小,基本上在3毫秒内,而且worker任务不受其他任务的干扰,即使浏览器进入后台,也没有影响worker

    使用web worker要注意以下几点

    (1)同源限制

    分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

    (2)DOM 限制

    Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document、window、parent这些对象。但是,Worker 线程可以navigator对象和location对象。

    (3)通信联系

    Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。

    (4)脚本限制

    Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。

    (5)文件限制

    Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。

     参考:

    https://johnresig.com/blog/how-javascript-timers-work/

    https://blog.csdn.net/qq_41494464/article/details/99944633

    http://www.ruanyifeng.com/blog/2018/07/web-worker.html 

  • 相关阅读:
    1654. Minimum Jumps to Reach Home
    1129. Shortest Path with Alternating Colors
    1766. Tree of Coprimes
    1368. Minimum Cost to Make at Least One Valid Path in a Grid
    LeetCode 841 钥匙与房间
    LeetCode 268 缺失数字
    LeetCode 136 只出现一次的数字
    LeetCode 461 汉明距离
    LeetCode 557 反转字符串中的单词 III
    LeetCode 392 判断子序列
  • 原文地址:https://www.cnblogs.com/shenjp/p/15774116.html
Copyright © 2011-2022 走看看