1

我有以下简单的 JS 代码(https://stackblitz.com/edit/web-platform-ueq5aq?file=script.js):

const baton = document.querySelector('button');

baton.addEventListener('mousedown', (e) => {
  console.log('baton');
  baton.addEventListener('click', (e) => {
    console.log('baton click');
  });
});

当我单击一个按钮时,我将“指挥棒”和“指挥棒点击”记录到控制台。现在我的问题是这里到底发生了什么?据我了解,在执行脚本的那一刻,处理程序mousedown被添加到偶数队列中。当我实际单击按钮时,此处理程序将运行,因此它从事件队列中获取,添加到调用堆栈并被执行。当它被执行时,处理程序“click”被添加到事件队列中。

之后实际上如何onClick触发事件onMouseDown?这与事件队列有什么关系?为什么在事件发生onMouseDown之前运行处理程序?click我问是因为我有很多更复杂的代码,在不同的场景中结果是不同的。

当用户导航到包含类似脚本的 SPA 页面,然后单击“接力棒”按钮时,顺序为:

mousedown 事件 -> 处理程序 mousedown -> 处理程序单击 -> 单击事件

当用户重新加载页面时,SPA 会直接加载到该页面上,然后单击“接力棒”按钮的顺序是:

mousedown 事件 -> 单击事件 -> 处理程序 mousedown

我正在寻求答案和真相。任何帮助将不胜感激。

附言。不幸的是,我无法在示例存储库中重现此错误 - 它发生在非常复杂的 Web 应用程序中,由于显而易见的原因,我无法在此处共享生产代码。

PS2。只是为了澄清一下,因为可能没有足够清楚地说明:我不是在问“为什么在单击事件之前触发 mousedown 事件”,而是在问“为什么在单击事件之前运行 mousedown HANDLER”。这并不明显,因为处理程序不会立即运行。处理程序要运行的顺序,首先要等待调用堆栈为空,这样事件队列才能被JS引擎处理。

4

3 回答 3

0

浏览器会跟踪您单击鼠标的元素。然后它会跟踪您抬起鼠标按钮的元素。如果您解除鼠标按钮的元素是相同元素或目标元素的子元素。然后一个 click 事件被派发到你抬起鼠标的最后一个元素。然后该事件将元素链向上传播到每个父元素,除非该事件被告知停止传播。

如果您在元素 A 上单击并在元素 B 上单击鼠标。然后 A 得到鼠标按下事件,B 得到鼠标向上事件,但都没有得到单击事件。如果您将浏览器导航到鼠标向下和鼠标向上之间的另一个页面,也会发生同样的事情。

于 2021-12-06T11:18:34.357 回答
0

也许我们应该从澄清一些事情开始。

浏览器中的事件,更像是一个“嵌套层次结构”,然后是一个队列——它的工作原理被称为事件冒泡——[维基百科][1]

但是,本质上,当添加一个 EventListener 时,您正在做的事情是连接到 DOM 的一个或多个点,然后说,嘿,当 X 事件通过这里时,使用函数 Y 来处理它,然后再将它沿堆栈传递。

一旦“添加”了一个 EventListener,它就会保持活动状态,等待给定一个事件。它究竟做了什么在它的处理函数中定义。

let myYFunction = function( e ) { ... }

let myXListener = baton.addEventListern('X event', myYFunction );

// at this point, anytime 'X event' happens to baton, myYFunction will 
// be called to handle it...

现在让我们看看你的例子,让我们把事情分解一下,

const baton = document.querySelector('button');

第一行是简单地查询 DOM,以在页面中查找类型为“按钮”的第一个元素。对...这是我们要插入事件处理程序的“位置”。我们可以将它们添加到任何元素,在 DOM 中的任何位置,如果我们愿意,我们甚至可以挂钩到“body”元素。

好的,那么你有这个,

baton.addEventListener('mousedown', (e) => {
  console.log('baton');
  baton.addEventListener('click', (e) => {
    console.log('baton click');
  });
});

这是“嵌套”“click”事件侦听器的创建,但只有在“mousedown”事件被“处理”之后。没有真正的理由必须在 mousedown 处理程序的函数体中注册“click”事件。

如果我们稍微重写一下,可能会更清楚实际发生了什么。

baton.addEventListener('mousedown', (e) => {
  console.log('baton mousedown');
}

baton.addEventListener('click', (e) => {
    console.log('baton click');
});

此外,我还要指出,目前它是如何“工作”的——但它实际上隐藏了一点草率的编码......你每次触发“mousedown”事件时都会看到一个新的“click”事件监听器正在注册...所以最终您可能会得到很多很多点击处理程序来响应单个“点击”事件...查看 MDN 以了解有关 [this][2] 的更多信息


我希望这能回答你最初关于发生了什么的问题。


对于您的问题“当我单击一个按钮时,我将 '指挥棒' 和 '指挥棒点击' 记录到控制台。现在我的问题是这里到底发生了什么?” ——对我来说,它看起来像这样:

  1. 添加了“mousedown”事件监听器,但没有“执行”
  2. 'mousedown' 事件发生了,现在你的 'mousedown' 监听器执行它的函数,该函数又注销到控制台,并注册一个新的 'click' 处理程序——但同样,不执行。

继续前进,对于指挥棒看到的每个“鼠标按下”,都会重复第 1 步和第 2 步。此外,对于通过接力棒传递的任何“点击”事件——在接力棒上的每次“鼠标按下”之后都会发生:

  1. 发生“点击”事件,然后执行“点击”处理程序并注销到控制台。

SPA 事件处理策略

使用 SPA 时,显示多个“页面”,在同一个页面加载中......它可能会变得混乱,所有这些事件侦听器都在相互堆积。如果您打算在 SPA 的“页面”之间使用 eventListeners,您可能还想研究如何“删除”它们。- [MDN][3]

这样,您的 SPA 的当前“页面”只有活动监听器。

此外,考虑“概括”您的处理程序,并将它们附加到 DOM 中更高的位置......这将允许您只有少数事件侦听器将事件“路由”到它们的“逻辑”处理程序。


随机/不同的行为

通过上面概述的步骤,1、2 和 3 以及它们如何不会同时发生。您将看到似乎是随机输出到控制台的内容......尝试运行类似这样的东西,以获得正确的感觉:

let cCount = 0;
let mCount = 0;
let tCount = 0;

const baton = document.querySelector('button');

baton.addEventListener('mousedown', (e) => {
  console.log('mousedown # ' + (mCount++) + ' order:' + tCount++);
  baton.addEventListener('click', (e) => {
    console.log('click # ' + (cCount++) + ' order:' + tCount++);
  });
});

[1]: https://en.wikipedia.org/wiki/Event_bubbling#:~:text=Event%20bubbling%20is%20a%20type,Provided%20the%20handler%20is%20initialized )。[2]:https ://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener [3]:https ://developer.mozilla.org/en-US/docs/Web/API /EventTarget/removeEventListener

于 2021-12-11T02:16:32.317 回答
0

来自MDN 网络文档

当指针位于元素内部时,当同时按下和释放定点设备按钮(例如鼠标的主鼠标按钮)时,元素会接收到单击事件。

所以有一个mouseup事件,然后是click事件。


问题编辑后编辑:

“为什么 mousedown HANDLER 在点击事件之前运行?”

您已经执行mousedown的处理程序注册了click处理程序,那么处理程序应该如何click在它之前运行?

在所有click先前mousedown的处理程序中注册的所有处理程序也将在mousedownmouseup事件之后运行。

在此处输入图像描述

于 2021-12-04T14:41:41.107 回答