一个事件侦听器将建立另一个事件侦听器,但两者均由第一个事件触发。为什么?

时间:2019-03-31 23:22:29

标签: javascript

我无法理解何时/如何添加事件侦听器,并试图通过这个小片段来说明我的一般性问题。

如果切换按钮具有mousedown事件,则小框在显示还是不显示之间切换。另外,如果显示了该文档,则对于文档中任何地方的mousedown事件,该框将变为不显示。

我的问题是,如果evt.stopPropagation被注释掉了,为什么不起作用?

如果.evt.stopPropagationdocument.addEventListener都被注释掉了,则该切换将按预期方式工作,但仅对按钮有效,并且不会从文档中mousedown的显示框中删除该框。

如果当文档上的事件侦听器处于活动状态并且clear被注释掉时,如果console.log消息放在evt.stopPropagation函数中,则可以看到mousedown位于该按钮还会触发文档上的mousedown。因此,单击切换按钮会同时显示并从显示中删除,并且永远不会看到该框。

我期望按钮事件在其自身的事件之后将事件侦听器添加到文档中,从而使文档无法为按钮上的第一个事件注册mousedown事件,因为该事件尚未发生宣布但似乎两个侦听器是同时设置的。

能请您解释一下吗?谢谢您考虑我的问题。

"use strict";

document.querySelector('button').addEventListener( 'mousedown', display, false );

function display( evt )
  {
    evt.stopPropagation();
  
    let e = evt.currentTarget.nextElementSibling;
    if ( e.style.display === 'block' )
      {
        e.style.display = 'none';
      }
    else
      {
        e.style.display = 'block';      
        document.addEventListener( 'mousedown', clear, false );            
      }; // end if
  } // close display
  
function clear()
  {
    document.removeEventListener( 'mousedown', clear, false );
    document.querySelector('button').nextElementSibling.style.display = 'none';    
  }
div {
 display: none;
 background-color: rgb(150,150,150);
 border: 1px solid black;
 height: 50px;
 width: 50px;
 margin-top: 10px;
}
<button>Toggle</button>

<div></div>

2 个答案:

答案 0 :(得分:0)

bubbling phase中,事件使DOM从生成事件的目标元素经过元素链的父节点链上升到document对象,然后从那里到达{{1} }:


Source 3.1 figure

在处理程序中调用window会阻止事件在DOM上继续冒泡(当然)。

现在,如果未显示切换的下一个同级,则event.stopPropagation事件处理程序将停止传播,显示下一个元素,并在文档节点上注册一个侦听器以再次隐藏该元素。

如果未调用display,则stopPropagation事件会继续使DOM冒泡,以寻找要调用的“ mousedown”侦听器。它会找到{{ 1}}到文档节点(即“清除”函数),对其进行调用,并且处理程序的执行将隐藏切换后的下一个元素。

在为mousedowndisplay调用mousedown处理程序之间,以及处理事件冒泡阻止屏幕更新之间的短暂时间内,您永远都看不到下一个元素永远不会。

答案 1 :(得分:0)

@ traktor53的答案正确地确定了会发生什么,但我担心他们的解释还不够清楚。

您在这里遇到的事情基本上是由两个事实造成的:

  • MouseEvent确实会冒泡给他们的祖先。
  • EventTarget.addEventListener是一个同步方法。

为了更好地理解,我现在将避免谈论捕获阶段。

因此,当浏览器要在目标上分配事件时,它将首先检查它必须在该目标上调用的所有处理程序,然后执行所有这些处理程序,最后将DOM冒泡到窗口中(当然,仅在发生冒泡事件的情况下。

以您的示例为例,我们可以将此冒泡阶段进行模式化:

[<button>] ->  list of handlers: [`ƒ display`]
               execute `ƒ display` // (add handler on document)
               continue with parentNode
[<body>]   ->  list of handlers: none
               continue with parentNode
[<html>]   ->  list of handlers: none
               continue with ownerDocument
[document] ->  list of handlers: [`ƒ clear`] // (added in `display`)
               execute `ƒ clear`
               continue with defaultView
[window]   ->  list of handlers: none

您可以看到,之前添加了您在document上添加的EventHandler,该算法必须检查附加到document上的EventHandler。因此,当要在document EventTarget上触发Event时,后者将为其附加此EventHandler。

为了演示它,我们甚至可以构建此梯形图代码,该代码将从第一个EventHandler内部将EventListener添加到原始目标的所有祖先:

document.querySelector('button').addEventListener('mousedown', handle, {once:true});

function handle(evt) {
  console.log('firing from', this.toString());
  const up = this.defaultView || // window for document
    this.parentNode || // elements until html
    this.ownerDocument; // html
  if(up) up.addEventListener('mousedown', handle, {once:true});
}
<button>click me</button>

这样做,您的clear函数将在display被调用后立即被调用,并立即恢复display所做的事情。