为什么同一个 [click] 事件会被处理两次?

Why would the same [click] event be handled twice?

下面的代码是我的问题的简化。当您单击 a_element 时,将显示 b_element 并创建一个事件侦听器来侦听第二次单击,这将隐藏 b_element。问题是,使 b_element 显示的第一次点击也触发了嵌套事件侦听器,即 window.addEventListener。有没有一种方法可以编写此 window 事件侦听器,使其不会在初始点击时执行?

var a_element = document.getElementById("a");
var b_element = document.getElementById("b");

function show_block () {

  b_element.style.display = "block";
  console.log("showing block");
  a_element.removeEventListener('mouseup', show, false);
  
  window.addEventListener('mouseup', hide = function () {
    b_element.style.display = "none";
    console.log("hiding block");
    window.removeEventListener('mouseup', hide, false);
    a_element.addEventListener('mouseup', show = function () {
      show_block();
    }, false);
  }, false);

}   
 
a_element.addEventListener('mouseup', show = function () {
 show_block();
}, false);
#a {
position: absolute;
height: 50px;
width: 50px;
background-color: rgba(100,120,140,1);
}

#b {
display: none;
position: absolute;
left: 200px;
height: 50px;
width: 50px;
background-color: rgba(200,220,240,1);
}
<div id="a"></div>
<div id="b"></div>

以及简化版:

var a_block = document.getElementById("a");

function message () {
  window.addEventListener('mouseup', message = function () {
    console.log("you clicked twice");
  }, false);
}

a_block.addEventListener('mouseup', clicked = function () {
  message();
  console.log("you clicked once");
}, false);
#a {
  height: 50px;
  width: 50px;
  position: absolute;
  background-color: #000000;
}
<div id="a"></div>

hide 引用的函数在第一次“点击”时被调用的原因是因为鼠标事件首先触发 all 那些附加到任何祖先的事件监听器 ( parent、grandparent 等)作为点击“目标”的元素(正在与之交互的实际元素),它们注册了捕获标志集( addEventListener 您正在使用的,如果省略,则默认设置捕获标志)。这称为事件调度的捕获阶段——如果单击“a”元素,如果有一个事件侦听器附加到 window(所有具有事件调度的元素的祖先),第二个参数为 addEventListener 设置为 true (或根本不指定),事件侦听器将首先被调用。以此类推元素,并以调用添加到元素本身的事件侦听器结束。

在上面的“捕获阶段”之后,事件通过以目标元素开始并以 window object 结束的元素层次结构向上“冒泡”。这被称为“冒泡阶段”——它就像事件的“捕获阶段”,但顺序相反,这次只有事件侦听器添加了 addEventListner 的第二个参数被设置为 false 明确地被调用,以添加到 window object.

的事件侦听器结束

在您的情况下发生的情况是,由于您向 window object 添加了一个事件侦听器,其中 false 作为 addEventListener 的第二个参数,而事件仍然存在被调度,因为事件完成“冒泡”,当它轮到在 window object 上调用事件监听器(那些为“冒泡阶段”添加的监听器)时,你新添加的(到 window) 事件侦听器被调用!

就像我说的,我不知道为什么在你的代码中每次调用 addEventListener 都会指定 false,这就是我所说的“代码味道”——一个标志这可能是错误的。恰好是这样,是好是坏?您是否有理由在这些事件的“冒泡”期间处理事件,而不是在“捕获”期间的默认设置(没有指定 addEventListener 的第二个参数)?顺便说一句,这就是给你带来麻烦的原因。如果有疑问,请不要具体说明 - 无论如何,你犯错的机会最多是均等的,为什么要冒着额外参数的风险,这些参数会混淆 reader,并偶然导致错误行为?

实际上,您的代码还有其他问题——您附加了匿名函数,它们做同样的事情但不是同一个函数(如,相同的 object 创建一次),您 re-assign showhide 尽管它们指的是相同的 de-facto 行为——这意味着它们基本上应该是函数“显示”和“隐藏”,而没有将详细的匿名函数分配给 window 属性(没有 showhide 的声明,这使得它们被分配到 window/global object)。

但是在不知道您想要什么行为的情况下,恐怕我无法为您提供任何代码。在不知道您想要什么的情况下,我无法告诉您代码有什么问题,但显然上述所有信息仍然适用——我只是不确定它如何适用于您的情况。

事件从 window 移动到目标(捕获),然后从目标返回到 window(冒泡)。每个事件都会发生这种情况。由于 a_block 是目标,所以我将它设置为 true 或 false 并不重要(true 将在捕获阶段的最后触发它,而 false 将在冒泡阶段的最开始触发它但是因为 a_block 是运动路径的顶点,所以它本质上是同一点)。因此,事件从 window 传播到目标,然后目标在 window 处创建事件侦听器。然后,仍在目标处的事件从目标行进到 window。如果 window 事件侦听器设置为冒泡阶段,即 false,那么它将在事件到达 window 时触发。否则,如果 window 事件侦听器设置为捕获阶段,即 true,则事件将在捕获阶段触发,该阶段只能通过再次单击鼠标在该点发生。

TLDR:将嵌套侦听器更改为 true:

var a_block = document.getElementById("a");

function message () {
  window.addEventListener('mouseup', message = function () {
    console.log("you clicked twice");
  }, true);
}

a_block.addEventListener('mouseup', clicked = function () {
  message();
  console.log("you clicked once");
}, false);
#a {
  height: 50px;
  width: 50px;
  position: absolute;
  background-color: #000000;
}
<div id="a"></div>