任务队列真的有优先级系统吗?

Is there really a prioritization system for the task-queues?

给定以下脚本:

console.log("start of hard script");
const start = performance.now();
setTimeout(() => console.log('setTimeout'),0);
document.addEventListener("DOMContentLoaded", () => {
  console.log('fired event DOMContentLoaded')
});
document.addEventListener("click" , () => {
  console.log("fired event click")
});
while(start + 1000 > performance.now());
console.log("end of hard script")

我确定我在某处读到用户交互队列的优先级高于计时器队列。

我想看看该优先级是如何定义的,但看到了 in the specs:

Let taskQueue be one of the event loop's task queues, chosen in an implementation-defined manner, with the constraint that the chosen task queue must contain at least one runnable task. If there is no such task queue, then jump to the microtasks step below.

如果 WHATWG 没有定义具体的队列顺序,我想知道实现者的标准是什么 运行?他们如何评估队列的“重要性”?最后,如果可能的话,我想看一个显示这些队列顺序的示例。

I'd like to know by what criteria implementors run? How do they evaluate the "important-ness" of queues?

这基本上是他们的决定,根据多年的经验做出设计选择,了解他们的工具是如何使用的以及应该优先考虑什么(也可能是常识的一部分)。

WHATWG 确实根本没有定义应该如何实施此任务优先级排序。他们所做的只是定义各种 task-sources(甚至 task-queues),以确保在同一源中排队的两个任务将以正确的顺序执行。

我们最接近定义某种优先级的是传入的 Prioritized Task Scheduling API 它将给我们 web-authors 优先级任务 post 的平均值,具有三个优先级:"user-blocking""user-visible""background".

要检查浏览器实际实现了什么,您必须通读它们 source-code 并彻底检查。
我自己已经在那里呆了几个小时,我能告诉你的就是,如果你想了解全貌,你最好有动力。
您可能感兴趣的几点:

  • 所有浏览器都不会公开相同的行为。
  • 在Chrome中,setTimeout()仍然有最小延迟1ms (https://crbug.com/402694)
  • 在 Firefox 中,因为 Chrome 的 1 毫秒延迟在某些 web-pages 上产生了不同的结果,他们创建了一个特殊的 very-low-priority task-queue 仅用于之前安排的计时器页面加载,之后安排的页面以正常优先级排队 task-queue。 (https://bugzil.la/1270059)
  • 至少在 Chrome 中,每个 task-queue has a "starvation" protection,通过让优先级较低的队列防止所述队列用自己的任务淹没 event-loop在 一些 时间(不确定多少)之后也执行他们的一些任务。

And in the end I'd like to see an example that shows the order of these queues, if it is possible.

如前所述,这很复杂,因为没有“一个”顺序。

你自己的例子 相当 是一个很好的测试,在我的 Chrome 浏览器中确实正确显示了 UI task-queue比定时器更高的优先级(while 循环处理我谈到的 1ms 最小延迟)。但是对于 DOMContentLoaded,我必须承认我不完全确定它显示了任何重要信息:HTML 解析器也被 while 循环阻塞,因此触发事件的任务只会得到 post在执行整个脚本后编辑。

但是鉴于此任务是 post 在 DOM Manipulation task source 上编辑的,我们可以通过强制使用此任务源的其他任务来检查它的优先级,例如 script.onerror.
因此,这是对您的代码片段的更新,其中包含更多任务源,以与我的 Chrome 的优先顺序相反的顺序调用:

const queueOnDOMManipulationTaskSource = (cb) => {
  const script = document.createElement("script");
  script.onerror = (evt) => {
    script.remove();
    cb();
  };
  script.src = "";
  document.head.append(script);
};
const queueOnTimerTaskSource = (cb) => {
  setTimeout(cb, 0);
}
const queueOnMessageTaskSource = (cb) => {
  const { port1, port2 } = new MessageChannel();
  port1.onmessage = (evt) => {
    port1.close();
    cb();
  };
  port2.postMessage("");
};
const queueOnHistoryTraversalTaskSource = (cb) => {
  history.pushState("", "", location.href);
  addEventListener("popstate", (evt) => {
    cb();
  }, { once: true });
  history.back();
}
const queueOnNetworkingTaskSource = (cb) => {
  const link = document.createElement("link");
  link.onerror = (evt) => {
    link.remove();
    cb();
  };
  link.href = ".foo";
  link.rel = "stylesheet";
  document.head.append(link);
};
const makeCB = (log) => () => console.log(log);
console.log("The page will freeze for 3 seconds, try to click on this frame to queue an UI task");
// let the message show 
setTimeout(() => {
  window.scheduler?.postTask(makeCB("queueTask background"), {
    priority: "background"
  });
  queueOnHistoryTraversalTaskSource(makeCB("History Traversal"));
  queueOnNetworkingTaskSource(makeCB("Networking"));
  queueOnTimerTaskSource(makeCB("Timer"));
  // the next three are a tie in current Chrome
  queueOnMessageTaskSource(makeCB("Message"));
  window.scheduler?.postTask(makeCB("queueTask user-visible"), {
    priority: "user-visible"
  });
  queueOnDOMManipulationTaskSource(makeCB("DOM Manipulation"));

  window.scheduler?.postTask(makeCB("queueTask user-blocking with delay"), {
    priority: "user-blocking",
    delay: 1
  });
  window.scheduler?.postTask(makeCB("queueTask user-blocking"), {
    priority: "user-blocking"
  });
  document.addEventListener("click", makeCB("UI task source"), {
    once: true
  });
  const start = performance.now();
  while (start + 3000 > performance.now());
}, 1000);