当在浏览器中触发事件时,事件处理程序在什么时候确定?

At what point do the event handlers get determined when an event has been fired in the browser?

我最近阅读了很多关于事件循环及其不同队列(job/macrotask 队列、微任务队列、渲染队列)及其优先级的文章。但是我仍然无法完全理解一件事,假设用户非常快地一个接一个地触发 2 个事件。然后两个事件处理程序都将在作业队列中排队并以正确的顺序执行。但是,如果第一个事件处理程序进行了一些繁重的计算并暂时阻塞了主线程,但它还以更改 UI 的方式操纵 DOM,以便用户单击的元素第二次被删除并替换为其他一些 dom 元素,该元素也注册了点击事件。

那么如果我理解正确的话,一旦第一个事件处理程序完成执行,所有的微任务都会被执行,微任务队列被清空。然后渲染队列被清空,浏览器重新绘制。这是我的实际问题所在,第二个事件处理程序是根据 UI 的新状态执行的,还是根据单击时存在的元素执行的?

我尝试了这个 (https://jsfiddle.net/jsx73t6c/4/),方法是让 3 个按钮都注册了一个单击事件处理程序,该处理程序会记录到控制台,表明单击了相应的按钮。我快速点击按钮 1 和按钮 2。按钮 1 的处理程序做了一个大循环,因此主线程被阻塞了一段时间,然后删除了按钮 2。由于按钮 2 被删除了,按钮 3 出现了,我最终得到了 2控制台日志指示按钮 1 和按钮 3 被单击,即使在单击我的鼠标时它位于按钮 1 和按钮 2 上。似乎事件处理程序已在队列中注册,但确定了发生单击的元素稍后需要处理单击事件并触发已注册的事件处理程序时。对我来说,这种行为比在单击时根据 DOM 的状态确定被单击的元素更有意义,并且是一种更可预测的行为。但我仍然不确定我的示例中的行为是否始终得到保证。

const button2 = getButton(2);
const button3 = getButton(3);

const removeButton2 = () => button2.parentElement.removeChild(button2);

button2.addEventListener("click", () => console.log("button 2 clicked"));

button3.addEventListener("click", () => console.log("button 3 clicked"));

button1.addEventListener("click", () => {
  console.log("button 1 clicked");

  const fragment = document.createDocumentFragment();
  for (let i = 0; i < 70_000; i++) {
    const a = document.createElement("a");
    a.innerHTML = "text " + i;
    fragment.appendChild(a);
  }

  document.documentElement.appendChild(fragment);
  removeButton2();
});
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="button-1">button 1</button>
    <button id="button-2">button 2</button>
    <button id="button-3">button 3</button>
  </body>
</html>

我知道,在浏览器的当前状态下,一切都发生得如此之快,这样的真实场景不太可能偶然发现,但希望能获得更多有关该主题的信息和资源。

这将取决于实现,即使所有现代 UA 都公开了相同的行为。例如在旧的 IE 中,它对所有事情都使用一个进程,你的点击事件在被阻止时会被忽略。

现代浏览器现在有很多辅助进程,所以他们可以处理它(浏览器进程可以保留消息,直到渲染器准备好处理它)。
所以是的,在当前的主流浏览器中,只有当第一个回调完成时,您的事件才会被渲染器处理,那是实际的 DOM 事件将被构造的时候,因此它将使用更新的 DOM 树.

您可以查看 Chromium 设计文档,其中对此进行了一些讨论:https://www.chromium.org/developers/design-documents/displaying-a-web-page-in-chrome/#life-of-a-mouse-click-message, and this one about IPC messaging in general: https://www.chromium.org/developers/design-documents/inter-process-communication/