像“点击”这样的事件是宏任务吗?

Iev*_*iii 3 javascript event-loop

代码:

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

运行此脚本时:

所以我有两个问题:

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

Kai*_*ido 6

有各种任务队列,它们都有不同的优先级,由用户代理(UA)设置。

事件循环处理模型的第一步是从这些任务队列中的任何一个中选择一个任务,在那里他们可以决定即使一个任务实际上排在另一个任务之后,他们也可以选择它,只要他们不在同一个任务队列中排队。

计时器任务源往往是不太优先者之一,和UI任务源最优先的一个。

这就是你在这里所经历的。

请注意,有一个 API 提议允许我们的网络开发人员访问这个优先级系统: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);
Run Code Online (Sandbox Code Playgroud)

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

0
high priority task
normal priority task
message
timeout
low priority task
Run Code Online (Sandbox Code Playgroud)

说实话,任务优先级可能不是使消息事件和“正常”任务在我的代码片段中的“超时”之前触发的原因。的定时器初始化步骤的算法实际上是本身异步和请求UAS排队该任务,同时在平行(步骤14)。所以这个任务应该反正以后被同步排队的其他任务进行排队。

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


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


对于第二个问题,

“如果我在第 2 行的同步代码运行时单击,为什么我会在控制台中看到‘单击’?我在执行程序到达第 4 行之前单击了……”

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