postMessage定时器解决setTimeout延迟问题

1. 关于setTimeout出现4ms的延迟

众所周知,setTimeout第二个参数延迟时间并不是浏览器渲染完毕后多少秒执行第一个参数回调,而是多少时间之后将回调放入执行栈。只要setTimeout形成的嵌套足够多,这样定时器线程来不及处理,执行栈就足够深,就会导致一定的延迟,延迟最稳定在4ms左右。
本文做了个实验,验证一下:

  let a = Date.now();
  setTimeout(()=>{
    let b=Date.now();
    console.log(b-a);
    setTimeout(()=>{
      let c=Date.now();
      console.log(c-b);
      setTimeout(()=>{
        let d=Date.now();
        console.log(d-c);
        setTimeout(()=>{
          let e=Date.now();
          console.log(e-d);
          setTimeout(()=>{
            let f=Date.now();
            console.log(f-e);
              setTimeout(()=>{
                let g=Date.now();
                console.log(g-f);
                setTimeout(()=>{
                let h=Date.now();
                console.log(h-g)
                },0)
              },0)
          },0)
        },0)
      },0)
    },0)
  },0)
复制代码

得出结果如下:
image.png
可见最后几次时间差都在4ms左右,有一定的缺陷。

现在有一种比较好的方案处理这种延迟,那就是使用window.postMessage()原理来实现0毫秒定时器。接下来我们先简要介绍一下postMessage()

2. postMessage()简要介绍

postMessage主要是用来处理页面中嵌套iframe跨域的问题,它的通信原理类似于TCP三次握手,不但能解决各种跨域问题,而且实时性高。
下面是一个简单的demo,a.htmlb.html使用postMessage()通信。

  //a.html
 <body style="border: 1px solid red;">
    <h1>this is A</h1>
    <iframe src="./b.html" id="myFrame"></iframe
      >
  </body>
  <script>
    var frame = document.getElementById("myFrame");
    frame.contentWindow.postMessage("A给你发消息啦", "*");

    window.addEventListener(
      "message",
      function (ev) {
        var data = ev.data;
        console.log(data)
      },
      false
    );
  </script>
复制代码
//b.html
  <body style="border: 2px solid blue;">
    <h1>this is B</h1>
  </body>
  <script>
    window.addEventListener(
      "message",
      function (ev) {
        console.log("收到A的消息啦")
        var data = ev.data;
        console.log(data)
      },
      false
    );
    parent.postMessage("B给你发消息啦", "*");
  </script>
复制代码

运行的结果:

image.png

3.使用postMessage()实现setTimeout()

代码如下:

  (function () {
  var timeouts = [];
  var messageName = '发送给自己任何消息,可以为空';
  function setZeroTimeout(fn) {
    timeouts.push(fn);
    window.postMessage(messageName, '*'); //发送
  }
  function handleMessage(event) {
      event.stopPropagation();
      if (timeouts.length > 0) {
        var fn = timeouts.shift();
        fn();
      }
  }
  window.addEventListener('message', handleMessage, true);//监听发送的消息,执行handleMessage
  window.setZeroTimeout = setZeroTimeout;
})();
复制代码

最后,我们调用自己写的setZeroTimeout()做一下类似setTimeout()嵌套的实验:

let a = Date.now();
setZeroTimeout(()=>{
  let b=Date.now();
  console.log(b-a);
  setZeroTimeout(()=>{
    let c=Date.now();
    console.log(c-b);
    setZeroTimeout(()=>{
      let d=Date.now();
      console.log(d-c);
      setZeroTimeout(()=>{
        let e=Date.now();
        console.log(e-d);
        setZeroTimeout(()=>{
          let f=Date.now();
          console.log(f-e);
          setZeroTimeout(()=>{
            let g=Date.now();
            console.log(g-f)
          })
        })
      })
    })
  })
})
复制代码

运行结果如下:

image.png
第一次的时间差6s是包括了匿名函数自执行的时间,其余时间差都是在0ms~1ms,可见postMessage()可以实现我们最初的目的,弥补了setTimeout()4ms的缺陷。

4. 总结:postMessage用在什么地方

  1. React时间切片中使用到了postMessage定时器,具体可以看这篇文章《React Scheduler 为什么使用 MessageChannel 实现》
  2. 对页面性能要求较高的地方,使用postMessage至少可以节省4ms左右的时间。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享