事件处理程序,在上一个点击事件的范围内执行新的点击事件

Eventhandlers, new click event executed in scope of previous click event

难以解释情况的标题,但我会分享上下文。我有一个打开的下拉菜单,打开后我设置了一个新的处理程序来检查用户是否在下拉菜单的边界之外单击。一旦发生这种互动,我们就可以关闭下拉菜单。我附上了一个非常简单的测试用例,请注意,出于演示目的,缺少实际检查下拉边界的业务逻辑。

观察到 eventlistener 的点击,在执行较早的打开下拉列表的点击事件之后添加,也立即执行。我本以为,考虑到 mousedown 事件已经执行,这个事件侦听器只会在新的点击事件上执行。

这可以进一步验证;如果我在添加事件处理程序时添加超时,它就可以正常工作。对这种方法有什么想法或聪明的想法吗?

我添加了一个显示挑战的完整测试用例。 Open 方法中的两种方法都可用。

<html>
<head><title></title></head>
<body>

<a class="trigger" activates="A">Open Dropdown</a>
<div id="A">Dropdown contents</div>

<script>

function Dropdown(domEl){
    this.trigger = domEl;
    this.Init();
}
Dropdown.prototype = {
    Init : function(){  
        console.log('initialized dropdown');
        this.trigger.addEventListener('click',function(){this.Open()}.bind(this));
    },
    Open : function(){  
        console.log('opened dropdown');
        // this does not work, as the click event is triggered immediately upon triggering the Open even set under Init
        window.addEventListener('click',function(){this.OnOpenEventHandler()}.bind(this));
        
        // this works, as the event is not triggered immediately
        window.setTimeout(function(){
            window.addEventListener('click',function(){this.OnOpenEventHandler()}.bind(this));
        }.bind(this),50);
    },
    OnOpenEventHandler : function(){
        console.log('dropdown can be closed');
        this.Close();
    },
    Close : function(){
        console.log('closed dropdown');
    }
}

var triggers = document.querySelectorAll('.trigger');
triggers.forEach((el,i) => {
    new Dropdown(el);
});


</script>

</body>
</html>

如果我理解正确你想实现什么,我认为你只需要阻止点击事件传播:

<html>

<head>
  <title></title>
</head>

<body>

  <a class="trigger" activates="A">Open Dropdown</a>
  <div id="A">Dropdown contents</div>

  <script>
    function Dropdown(domEl) {
      this.trigger = domEl;
      this.Init();
    }
    Dropdown.prototype = {
      Init: function() {
        console.log('initialized dropdown');
        this.trigger.addEventListener('click', function(e) {
          e.stopPropagation(); // will prevent the click event from propagating
          this.Open()
        }.bind(this));
      },
      Open: function() {
        console.log('opened dropdown');
        // this does not work, as the click event is triggered immediately upon triggering the Open even set under Init
        window.addEventListener('click', function() {
          this.OnOpenEventHandler()
        }.bind(this));

        // this works, as the event is not triggered immediately
        /*window.setTimeout(function(){
            window.addEventListener('click',function(){this.OnOpenEventHandler()}.bind(this));
        }.bind(this),50);*/
      },
      OnOpenEventHandler: function() {
        console.log('dropdown can be closed');
        this.Close();
      },
      Close: function() {
        console.log('closed dropdown');
      }
    }

    var triggers = document.querySelectorAll('.trigger');
    triggers.forEach((el, i) => {
      new Dropdown(el);
    });
  </script>

</body>

</html>

如果您不想使用 stopPropagation(),根据您的评论,另一种解决方案可能是检查 trigger 元素是否未触发点击事件:

<html>

<head>
  <title></title>
</head>

<body>

  <a class="trigger" activates="A">Open Dropdown</a>
  <div id="A">Dropdown contents</div>

  <script>
    function Dropdown(domEl) {
      this.trigger = domEl;
      this.Init();
    }
    Dropdown.prototype = {
      Init: function() {
        console.log('initialized dropdown');
        this.trigger.addEventListener('click', function() {
          this.Open()
        }.bind(this));
      },
      Open: function() {
        console.log('opened dropdown');

        window.addEventListener('click', function(event) {
          /* 
           * execute the code only if the click event
           * has not been fired by the 'a.trigger' element
           */
          if (event.srcElement != this.trigger) {
            this.OnOpenEventHandler()
          }
        }.bind(this));
      },
      OnOpenEventHandler: function() {
        console.log('dropdown can be closed');
        this.Close();
      },
      Close: function() {
        console.log('closed dropdown');
      }
    }

    var triggers = document.querySelectorAll('.trigger');
    triggers.forEach((el, i) => {
      new Dropdown(el);
    });
  </script>

</body>

</html>