发布订阅模式零延迟的"setTimeout"

537 阅读2分钟

「这是我参与11月更文挑战的第 2 天,活动详情查看:2021最后一次更文挑战」。

你不知道的 window.setTimeout

WindowOrWorkerGlobalScope 混合的 setTimeout() 方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。

我们经常在需要异步执行一段代码的时候使用它,但是还有一些值得挖掘的点。

超时延迟

定时器有可能因为当前页面(或者操作系统/浏览器本身)被其他任务占用导致延时。 需要被强调是, 直到调用 setTimeout()的主线程执行完其他任务之后,回调函数和代码段才能被执行。例如:

function foo() {
  console.log('foo has been called');
}
setTimeout(foo, 0);
console.log('After setTimeout');

会在控制台输出:

After setTimeout
foo has been called

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

有很多因素会导致setTimeout的回调函数执行比设定的预期值更久,本节将讨论最常见的原因。

最小延时 >=4ms

在浏览器中,setTimeout()/setInterval() 的每调用一次定时器的最小间隔是4ms,这通常是由于函数嵌套导致(嵌套层级达到一定深度),或者是由于已经执行的setInterval的回调函数阻塞导致的。例如:

function cb() { f(); setTimeout(cb, 0); }
setTimeout(cb, 0);
setInterval(f, 0);

一直以来,不同浏览器中出现这种最小延迟的情况有所不同(例如Firefox) - 从其他地方调用了setInterval( ),或者在嵌套函数调用setTimeout( ) 时(嵌套级别达到特定深度时),都会出现超时延迟。

实现零延迟的"setTimeout"

在这里我们可以通过 发布订阅模式来快速实现。

talk is chep, show you the code.

(function() { var timeouts = []; var messageName = "zero-timeout-message";

    // Like setTimeout, but only takes a function argument.  There's
    // no time argument (always zero) and no arguments (you have to
    // use a closure).
    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);

    // Add the one thing we want added to the window object.
    window.setZeroTimeout = setZeroTimeout;
})();