首页 > 解决方案 > 在函数开头放置零延迟的 setTimeout 时更快的代码执行:为什么会发生这种情况?

问题描述

在下面的文章中use-case 1,它们改变了函数setTimeout内部调用的位置:count

https://javascript.info/event-loop#use-case-1-splitting-cpu-hungry-tasks

在第二种情况下,它的速度要快得多,他们用下面的句子来解释:

如果你运行它,很容易注意到它花费的时间明显减少。

为什么?

这很简单:正如您所记得的,对于许多嵌套的 setTimeout 调用,浏览器内的最小延迟为 4 毫秒。即使我们设置为 0,它也是 4ms(或更多)。所以我们越早安排它——它运行得越快。

对我来说,这是一个非常不清楚的解释,我根本不明白它们的意思。谁能更清楚、更详细地解释为什么这两种情况需要不同的时间?

标签: javascriptevent-loop

解决方案


setTimeout(fn, t)同步 fn安排在now + t.

然后,您的报价中的解释就很明确了:如果您在锁定计算机一段时间之前安排任务,那么它会在之前触发,而不是在之后安排。

// now = wall clock 0;
setTimeout(fn, 1000); // schedules `fn` to fire at wall clock 0 + 1000 = 1000
lockCPUFor(5000); 
// now = wall clock 5000;
setTimeout(fn, 1000); // schedules `fn` to fire at wall clock 5000 + 1000 = 6000;

在任务结束时,我们现在 = 5000 + 几毫秒。第一次超时是 4s 过去的,所以我们会立即执行它。然而,第二次超时计划在大约一秒钟后触发,所以我们将等待大约一秒钟。

const origin = performance.now();
function fn(name) {
  console.log(name, performance.now() - origin);
}
fn("begin"); // ~0.05
setTimeout(() => fn("setTimeout before"), 1000); // ~ 5010
lockCPUFor(5000);
setTimeout(() => fn("setTimeout after"), 1000);  // ~6000

function lockCPUFor(t) {
  const now = performance.now();
  while (performance.now() - now < t) {}
}

但在我们的例子中,延迟是 0 而不是 1000,所以应该没关系。

正如您的报价所述,通过0并不意味着您真的会有零超时。某些环境(例如Chrome和 node.js)总是有 1 毫秒的最小超时(尽管Chrome 正在积极尝试删除该最小超时),Firefox 也会添加一些最小超时,“在页面加载时”,(实际上更多的是他们在页面加载超时时有一个特殊的任务队列,其优先级低于任何其他任务队列)。并且每个遵循HTML 规范的
UA在第 5 层嵌套都会有 4 毫秒的超时:

let startTime;
let i = 0;
const fn = () => {
  console.log("iteration #%s took %sms", i + 1, performance.now() - startTime);
  if (++i < 6) {
    startTime = performance.now();
    setTimeout(fn, 0);
  }
}
const startTest = () => {
  startTime = performance.now();
  setTimeout(fn, 0)
}
// avoid Firefox's weird at-load task queue
onload = startTest;

因此,即使您通过0了,UA 自动添加的这几毫秒也会影响回调的调度时间,并且在长任务之前调度确实可以避免大多数这些限制(如果长任务持续时间更长)。
但请注意,如果您需要比 setTimeout 更快地挂接到事件循环,则可以使用.


推荐阅读