SetTimeout(延迟计时器-最小延迟)

setTimeout 是有最小延迟时间?

 

  • MDN 文档 setTimeout

    • 实际延时比设定值更久的原因:最小延迟时间

    • 在浏览器中,setTimeout()/setInterval() 的每调用一次定时器的最小间隔是 4ms,这通常是由于函数嵌套导致(嵌套层级达到一定深度)。

  • HTML Standard

    • Timers can be nested; after five such nested timers, however, the interval is forced to be at least four milliseconds.

    • (定时器可以嵌套;然而,在五个这样的嵌套定时器之后,间隔被强制为至少四毫秒。)

  • 示例测试

  •   let a = performance.now();

      setTimeout(() => {

        let b = performance.now();

        console.log(b - a);

        setTimeout(() => {

          let c = performance.now();

          console.log(c - b);

          setTimeout(() => {

            let d = performance.now();

            console.log(d - c);

            setTimeout(() => {

              let e = performance.now();

              console.log(e - d);

              setTimeout(() => {

                let f = performance.now();

                console.log(f - e);

                setTimeout(() => {

                  let g = performance.now();

                  console.log(g - f);

                }, 0);

              }, 0);

            }, 0);

          }, 0);

        }, 0);

      }, 0);

     

    // 结果

    // 1.1949999898206443

    // 1.3500000059138983

    // 1.9650000031106174

    // 2.0650000078603625

    // 5.279999983031303

    // 4.63000001036562

  • 「立刻执行」的定时器的实现

    • 想在浏览器中实现 0ms 延时的定时器,使用 window.postMessage()。

      • 由于 postMessage 的回调函数的执行时机和 setTimeout 类似,都属于宏任务,

      • 所以可以简单利用 postMessage 和 addEventListener('message') 的消息通知组合,来实现模拟定时器的功能。

      • 这样,执行时机类似,但是延迟更小的定时器就完成了。

    • 不会随着嵌套层数的增多而增加延迟;

      • 由于 postMessage 的实现没有被浏览器引擎限制速度

      // postMessage 来实现真正 0 延迟的定时器

       

        (function () {

          var timeouts = [];

          var messageName = 'zero-timeout-message';

       

          // 保持 setTimeout 的形态,只接受单个函数的参数,延迟始终为 0。

          function setZeroTimeout(fn) {

            timeouts.push(fn);

            window.postMessage(messageName, '*');

          }

       

          function handleMessage(event) {

            if (event.source == window && event.data == messageName) {

              event.stopPropagation();

              if (timeouts.length > 0) {

                var fn = timeouts.shift();

                fn();

              }

            }

          }

          window.addEventListener('message', handleMessage, true);

          window.setZeroTimeout = setZeroTimeout;  // 把 API 添加到 window 对象上

        })();

  • 100个数据

    •  

      function runtest() {

        var output = document.getElementById('output');

        var outputText = document.createTextNode('');

        output.appendChild(outputText);

        function printOutput(line) {

          outputText.data += line + '\n';

        }

        var i = 0;

        var startTime = Date.now();

        // 通过递归 setZeroTimeout 达到 100 计数

        // 达到 100 后切换成 setTimeout 来实验

        function test1() {

          if (++i == 100) {

            var endTime = Date.now();

            printOutput('100 iterations of setZeroTimeout took ' + (endTime - startTime) + ' milliseconds.');

            i = 0;

            startTime = Date.now();

            setTimeout(test2, 0);

          } else {

            setZeroTimeout(test1);

          }

        }

        setZeroTimeout(test1);

        // 通过递归 setTimeout 达到 100 计数

        function test2() {

          if (++i == 100) {

            var endTime = Date.now();

            printOutput('100 iterations of setTimeout(0) took ' + (endTime - startTime) + ' milliseconds.');

          } else {

            setTimeout(test2, 0);

          }

        }

      }

  • 100个数据测试结果;

使用场景

  • React 的源码中,做时间切片的部分就用到

  •   const channel = new MessageChannel();

      const port = channel.port2;

     

      // 每次 port.postMessage() 调用就会添加一个宏任务

      // 该宏任务为调用 scheduler.scheduleTask 方法

      channel.port1.onmessage = scheduler.scheduleTask;

     

      const scheduler = {

        scheduleTask() {

          // 挑选一个任务并执行

          const task = pickTask();

          const continuousTask = task();

     

          // 如果当前任务未完成,则在下个宏任务继续执行

          if (continuousTask) {

            port.postMessage(null);

          }

        },

      };

 

示例

参考地址

 

 

 

如何实现比 setTimeout 快 80 倍的定时器

https://mp.weixin.qq.com/s/NqzWkeOhqAU85XPkJu_wCA

 

MDN setTimeout

https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout

 

HTML Standard

https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers

 

示例

https://dbaron.org/mozilla/zero-timeout

 

博客实现

https://dbaron.org/log/20100309-faster-timeouts

 

React Scheduler 为什么使用 MessageChannel 实现

https://juejin.cn/post/6953804914715803678

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值