首页 > 其他分享 >记录--你敢信?比 setTimeout 还快 80 倍的定时器

记录--你敢信?比 setTimeout 还快 80 倍的定时器

时间:2024-01-24 18:33:51浏览次数:34  
标签:postMessage now 定时器 -- var 80 setTimeout 100

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

起因

很多人都知道,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.

简单来说,5 层以上的定时器嵌套会导致至少 4ms 的延迟。

用如下代码做个测试:

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);

在浏览器中的打印结果大概是这样的,和规范一致,第五次执行的时候延迟来到了 4ms 以上。

 

探索

假设就需要一个「立刻执行」的定时器呢?有什么办法绕过这个 4ms 的延迟吗,在 MDN 文档的角落里有一些线索:

如果想在浏览器中实现 0ms 延时的定时器,可以参考这里所说的window.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);

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

由于postMessage的回调函数的执行时机和setTimeout类似,都属于宏任务,所以可以简单利用postMessageaddEventListener('message')的消息通知组合,来实现模拟定时器的功能。

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

再利用上面的嵌套定时器的例子来跑一下测试:

全部在 0.1 ~ 0.3 毫秒级别,而且不会随着嵌套层数的增多而增加延迟。

测试

从理论上来说,由于postMessage的实现没有被浏览器引擎限制速度,一定是比 setTimeout 要快的。

设计一个实验方法,就是分别用postMessage版定时器和传统定时器做一个递归执行计数函数的操作,看看同样计数到 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);
    }
  }
}

实验代码很简单,先通过setZeroTimeout也就是postMessage版本来递归计数到 100,然后切换成 setTimeout计数到 100。

直接放结论,这个差距不固定,在 mac 上用无痕模式排除插件等因素的干扰后,以计数到 100 为例,大概有 80 ~ 100 倍的时间差距。在硬件更好的台式机上,甚至能到 200 倍以上。

Performance 面板

只是看冷冰冰的数字还不够过瘾,打开 Performance 面板,看看更直观的可视化界面中,postMessage版的定时器和setTimeout版的定时器是如何分布的。

这张分布图非常直观的体现出了上面所说的所有现象,左边的postMessage版本的定时器分布非常密集,大概在 5ms 以内就执行完了所有的计数任务。

而右边的setTimeout版本相比较下分布的就很稀疏了,而且通过上方的时间轴可以看出,前四次的执行间隔大概在 1ms 左右,到了第五次就拉开到 4ms 以上。

作用

也许有同学会问,有什么场景需要无延迟的定时器?其实在 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);
    }
  },
};

React 把任务切分成很多片段,这样就可以通过把任务交给postMessage的回调函数,来让浏览器主线程拿回控制权,进行一些更优先的渲染任务(比如用户输入)。

为什么不用执行时机更靠前的微任务呢?关键的原因在于微任务会在渲染之前执行,这样就算浏览器有紧急的渲染任务,也得等微任务执行完才能渲染。

总结

可以了解如下几个知识点:

  1. setTimeout的 4ms 延迟历史原因,具体表现。
  2. 如何通过postMessage实现一个真正 0 延迟的定时器。
  3. postMessage定时器在 React 时间切片中的运用。
  4. 为什么时间切片需要用宏任务,而不是微任务。

本文转载于:

https://juejin.cn/post/7229520942668824633

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

标签:postMessage,now,定时器,--,var,80,setTimeout,100
From: https://www.cnblogs.com/smileZAZ/p/17985490

相关文章

  • java反射&代理面试知识
    java反射何为反射?如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。......
  • 精通 VS 调试技巧,学习与工作效率翻倍!
    ​✨✨欢迎大家来到贝蒂大讲堂✨✨​......
  • Kruise Rollout 全链路灰度实践
    作者:旦酱、十眠什么是全链路灰度?在发布应用的过程中,我们通常希望用少量特定流量来验证新版本的发布是否正常,以保障整体稳定性。这个过程被称为灰度发布。关于灰度发布,我们通过逐步增加发布的范围,来验证新版本的稳定性。如果新版本出现问题,我们也能及时发现,控制影响范围,保障整体的稳......
  • 如何降低微服务复杂度丨云栖大会微服务主题分享实录
    作者:谢吉宝本文整理自阿里云资深技术专家、中间件负责人谢吉宝在2023云栖大会《极简微服务模式,降低微服务复杂度的最佳实践》的分享2023云栖大会现场当面临复杂的挑战时,"分而治之"的方法往往能取得显著的效果。微服务架构在这方面的贡献尤为突出,它不仅为"分"与"治"这两个环节提供......
  • 查询MySQL数据库所使用的空间
    SELECTtable_schema                AS'数据库',   sum(table_rows)               AS'记录数',   sum(TRUNCATE(data_length/1024/1024,2)) AS'数据容量(MB)',   sum(TRUNCAT......
  • C语言——分支与循环
    C语言中,分支语句与循环语句是我们接触的基础语句之一,可以说是大厦之基。本篇我们先来看分支语句。分支语句包括if语句和swich语句两部分。我们先来看if语句,最简单的if语句如下if(条件){语句1;}else{语句2;}if后的圆括号里是判断条件,判断是否执行花括号里的语句;如果,判断......
  • openGauss学习笔记-206 openGauss 数据库运维-常见故障定位案例-too many clients alr
    openGauss学习笔记-206openGauss数据库运维-常见故障定位案例-toomanyclientsalready206.1高并发报错“toomanyclientsalready”或无法创建线程206.1.1问题现象高并发执行SQL,报错“sorry,toomanyclientsalready”;或报无法创建线程、无法fork进程等错误。206.1.2......
  • Python处理Excel表格的终极指南
    案例学Python(进阶篇)源代码.zip 链接:https://pan.quark.cn/s/c00aefe52fdc案例学Python(基础篇)源代码.zip 链接:https://pan.quark.cn/s/15c0b553b6b8引言Excel表格在数据处理和日常办公中扮演着不可或缺的角色。本文将详细介绍如何使用Python中的openpyxl库来处理Excel文件,......
  • 无涯教程-CSS3 - 多列布局
    CSS3可以将文本内容设计成像报纸一样的多列布局。一些最常用的多列属性,如下所示-Sr.No.Value&Remark1column-count 指定元素应该被分割的列数。2column-fill指定如何填充列3column-gap 指定列与列之间的间隙4column-rule所有column-rule-*属性的简......
  • 构建端到端可观测全景丨云栖大会可观测分享实录
    作者:周洋技术不断演进,端到端可观测需求愈发强烈随着Kubernetes、Serverless等云原生技术引领研发、运维模式变革。应用架构从单体架构逐步演进为分布式、微服务化应用。生产关系不断变化,DevOps/ 运维自动化、业务中台化让可观测不止于大促保障与日常轮值。同时,Prometheus、Gra......