【问题标题】:Remove EventListener defined in a class删除类中定义的 EventListener
【发布时间】:2021-06-20 22:37:23
【问题描述】:

我正在尝试删除一个 eventListener,但看起来我错过了一些东西。

为什么下面的代码不起作用,它没有从按钮中删除事件监听器。

我也尝试绑定 this 以传递范围,但这也不起作用

class Test {

  eventHandler(e) {
    console.log(e.target.id)
    alert()

    // no effect
    e.target.removeEventListener("click", this.eventHandler)

    // no effect either
    document.getElementById(e.target.id).removeEventListener("click", this.eventHandler)
  }
  constructor() {
    let b = document.getElementById("b")
    b.addEventListener("click", this.eventHandler)

    //b.addEventListener("click", this.eventHandler.bind(this) )
  }
}

new Test()
<button id="b">
click me
</button>

【问题讨论】:

  • 当有人想要删除事件处理程序时,总是会产生代码异味。为什么不使用标志/状态停用它?
  • @pejmankheyri "...,修复语法问题" - 什么语法问题?您的编辑删除了问题的一部分(恕我直言)...
  • b.addEventListener("click", e =&gt; this.eventHandler(e) );. 没有改变任何东西,它不起作用,你想创建一个工作示例吗?
  • @Andreas 我没有删除代码部分!这一定是一个错误!我刚刚编辑了代码上面的文字!
  • 可能是上下文问题。试试这个b.addEventListener('click', this.eventHandler.bind(this))

标签: javascript dom-events removeeventlistener


【解决方案1】:

OP 的代码不起作用有两个原因。

  • 在一种情况下,原型 eventHandler 错过了正确的 this 上下文。
  • 对于运行this.eventHandler.bind(this) 的第二种情况,创建一个新的(处理程序)函数,没有保存对它的引用。因此,removeEventHandler 永远不会引用正确的事件处理程序。

可能的解决方案...

function handleTestClickEvent(evt) {

  console.log(evt.currentTarget);
  console.log(this);
  console.log(this.eventHandler);

  // remove the instance specific (`this` context) `eventHandler`.
  evt.currentTarget.removeEventListener('click', this.eventHandler);
}

class Test {
  constructor() {
    // create own eventHandler with bound `this` context.
    this.eventHandler = handleTestClickEvent.bind(this);

    document
      .querySelector('#b')
      .addEventListener('click', this.eventHandler);
  }
}
new Test();
&lt;button id="b"&gt;click me&lt;/button&gt;

另一种可能的方法是使用基于箭头函数的,因此是特定于实例的事件处理程序。箭头函数不支持显式 this 绑定。它们总是指实现它们的上下文。

class Test {
  constructor() {
    // arrow-function based, thus instance-specific event-handler.
    this.eventHandler = evt => {

      console.log(evt.currentTarget);
      console.log(this);

      evt.currentTarget.removeEventListener('click', this.eventHandler);
    }
    document
      .querySelector('#b')
      .addEventListener('click', this.eventHandler);
  }
}
new Test();
&lt;button id="b"&gt;click me&lt;/button&gt;

尽管如此,这两种方法都表明,特定于引用的事件处理程序的原型实现不是人们应该遵循的路径。

对于 OP 提供的场景,我会选择第一个解决方案,因为它通过本地实现的 handleTestClickEvent 提供代码重用。对于特定于实例的this.eventHandler,它还具有较小的占用空间,前者是从handleTestClickEvent.bind(this) 创建的,而第二种解决方案为每个实例提供了完整的处理程序实现。

【讨论】:

    【解决方案2】:

    您的代码看起来太复杂了。如果您想禁用单击处理程序,有几种方法可以做到这一点,而无需删除处理程序。 Event delegation 也让您在这里的生活更轻松。在 sn-p button#b 中单击 3 次后禁用,使用数据属性。 button#a 可以无限点击。

    document.addEventListener("click", handle);
    
    function handle(evt) {
      if (evt.target.id === "b" && +(evt.target.dataset.nclicks || 1) < 3) {
        const nClicks = +(evt.target.dataset.nclicks || 0);
        evt.target.dataset.nclicks = nClicks + 1;
    
        if (nClicks === 2) {
          evt.target.setAttribute("disabled", "disabled");
        }
        
        return console.log(`you clicked button#b${
          nClicks === 2 ? ", no more clicks 4u" : ""}`);
      }
      if (evt.target.id === "a") {
        console.clear();
        return console.log(`you clicked button#a on ${new Date().toLocaleString()}`);
      }
    }
    <button id="a">click me a</button>
    <button id="b">click me b</button>

    【讨论】:

    • 我假设 OP 正在计划一个更大的类结构,并且他们想使用原型方法作为事件处理程序,这就是为什么对于这个相对简单的任务而言代码看起来过于复杂的原因。
    【解决方案3】:

    作为事件处理程序的原型方法有点问题,特别是当您需要绑定到实例的 this 值和对实际事件处理程序函数的引用时。

    默认情况下,事件队列在事件绑定到的元素的上下文中调用处理程序。更改上下文很容易,但这让您可以创建一个新函数,然后将其用作事件处理程序,并且该函数不再是原型中的方法。

    如果您想保持紧凑的类结构,一种方法是将事件处理程序方法定义为实例的自己的属性,它们根本无法被继承。最简单的方法是将方法定义为构造函数中的箭头函数。

    class Test {
      constructor() {
        this.eventHandler = e => {
          console.log(e.target.id);
          e.target.removeEventListener("click", this.eventHandler);
        };
        let b = document.getElementById("b");
        b.addEventListener("click", this.eventHandler);
      }
    }
    
    new Test();
    &lt;button id="b"&gt;Click me!&lt;/button&gt;

    箭头函数保留对其定义的词法环境的引用,事件队列不能覆盖上下文。现在处理函数中的this 已正确绑定到实例,this.eventHandler 引用了附加到事件的函数。

    在创建自己的属性时使用bind 是一个稍微减少内存消耗的选项,如下所示:

    class Test {
      constructor() {
        this.eventHandler = this.eventHandler.bind(this);
        let b = document.getElementById("b");
        b.addEventListener("click", this.eventHandler);
      }
      eventHandler (e) {
        console.log(e.target.id);
        e.target.removeEventListener("click", this.eventHandler);
      }
    }
    

    这里bind新建了一个函数对象,然后调用原型中的方法,方法的实际代码不重复。如果你写的话,这大致相似:

    this.eventHandler = e => Test.prototype.eventHandler.call(this, e);
    

    值得注意的是,当定义一个与底层原型属性具有相同名称的自己的属性时,原型属性不会被覆盖,它只是在实例中被隐藏,并且该类的多个实例仍将按预期工作。

    另一种选择是创建您自己的“事件模型”,它为所有事件创建一个包装函数(如上面最后一个代码示例),并存储对该函数的引用。包装器使用call 调用实际处理程序,它可以将所需的this 值绑定到事件处理程序。存储的函数引用用于删除事件。构建这样的model 并不是非常复杂,但它提供了一些关于this 绑定和本机事件模型如何工作的知识。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-14
      • 2022-11-11
      • 2023-03-18
      • 1970-01-01
      • 2014-08-10
      相关资源
      最近更新 更多