'click' 之类的事件是宏任务吗?

Is events like 'click' are macrotasks?

代码:

setTimeout(() => console.log(1), 10);
for (let i = 0; i < 3e9; i++) {}
console.log(0);
window.onclick = () => console.log('click');

当这个脚本是运行:

所以我有两个问题:

  1. 如果'click'是一个宏任务那么它应该在setTimeout之后进入宏任务回调队列吗?这样当同步代码执行完毕,栈为空,宏任务回调队列将console.log(0)入栈,栈执行,回调队列将console.log('click')入栈,堆栈执行它。
  2. 如果我在第 2 行的同步代码为 运行ning 时单击,为什么我在控制台中看到 'click'?我在执行器到达第 4 行之前单击了...

有多种 task-queues,它们都有不同的优先级,由 User-Agent (UA) 设置。

Event Loop's processing model 的第一步是从这些 tasks-queues 中选择一个任务,这是他们可以决定的,即使一个任务实际上已经 queued另一个,他们可以选择它,只要他们不 queued 在同一个 task-queue.

timer task source is often one of the less prioritized ones, and the UI task source 最优先的之一。

这就是您在这里遇到的情况。

请注意,有人提议 API 允许我们 web-devs 访问此优先级系统:https://github.com/WICG/main-thread-scheduling/blob/master/PrioritizedPostTask.md

setTimeout(() => console.log("timeout"), 0);

if( window.scheduler ) { // try to use the Prioritized postTask API
  scheduler.postTask(() => console.log("low priority task"), { priority: "background" });
  scheduler.postTask(() => console.log("normal priority task"), { priority: "user-visible" });
  scheduler.postTask(() => console.log("high priority task"), { priority: "user-blocking" });
}
else {
  console.log( "The Prioritized postTask API can be enabled from chrome://flags/#enable-experimental-web-platform-features" );
}

// queue on the message task-source (faster than timeout)
const { port1, port2 } = new MessageChannel();
port1.onmessage = () => console.log("message");
port2.postMessage("");

// block for 3 full seconds
const start = performance.now();
while( performance.now() - start < 3000 ) {}
console.log(0);

在 Chrome 中设置了 chrome://flags/#enable-experimental-web-platform-features 标志,这导致

0
high priority task
normal priority task
message
timeout
low priority task

老实说,任务优先级排序可能不是消息事件和“正常”任务在我的代码段中的“超时”之前触发的原因。 timer-initialisation-steps algorithm actually is itself asynchronous and asks the UAs to queue that task while in parallel(第 14 步)。所以这个任务 应该 无论如何是 queued 在其他同步 queued 的任务之后 queued。

还要注意大多数 UA (至少 Chromium 和 Firefox) 有一个饥饿系统,避免 queue 吃掉所有资源而不让另一个queues 永远执行他们的任务。


此外,为了回答问题的标题,事件不是任务,您可以很好地从同一任务同步触发事件(例如使用 EventTarget.dispatchEvent)。


对于第二个项目符号问题,

"If I click when synchronous code from line 2 is running, why I see 'click' in console at all? I clicked before executor reached the line 4..."

这仅仅是因为 UA 将继续执行 JavaScript 作业直到它结束,甚至在 排队任务 之前将事件分派给它的所有目标并最终调用所有他们的回调。所以在那个时候,你的事件处理程序已经定义好了,它的回调也是如此。