简体   繁体   English

删除 class 中定义的 EventListener

[英]Remove EventListener defined in a class

I am trying to remove an eventListener but it looks like I miss something.我正在尝试删除一个 eventListener,但看起来我错过了一些东西。

Why is the following code not working, it doesn't remove the event listener from the button.为什么以下代码不起作用,它不会从按钮中删除事件侦听器。

I also tried binding this to pass the scope, but that didn't work either我还尝试绑定它以通过 scope,但这也不起作用

 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>

The OP's code does not work for two reasons. OP 的代码不起作用有两个原因。

  • in one case the prototypal eventHandler misses the correct this context.在一种情况下,原型eventHandler错过了正确的this上下文。
  • for a second case of running this.eventHandler.bind(this) one creates a new (handler) function with no saved reference to it.对于运行this.eventHandler.bind(this)的第二种情况,会创建一个新的(处理程序)function,但没有保存对它的引用。 Thus with removeEventHandler one never refers to the correct event handler.因此,使用removeEventHandler永远不会引用正确的事件处理程序。

Possible solution...可能的解决方案...

 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();
 <button id="b">click me</button>

Another possible approach was the usage of an arrow-function based, thus instance-specific, event-handler.另一种可能的方法是使用基于箭头函数的,因此是特定于实例的事件处理程序。 Arrow-functions do not support an explicit this binding.箭头函数不支持显式this绑定。 They always refer to the context where they are implemented in.它们总是指实现它们的上下文。

 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();
 <button id="b">click me</button>

Nevertheless, both approaches show, that the prototypal implementation of a reference-specific event-handler is not the path one should follow.然而,这两种方法都表明,特定于引用的事件处理程序的原型实现不是人们应该遵循的路径。

For the scenario provided by the OP I would pick the 1st solution for it provides code reuse by the locally implemented handleTestClickEvent .对于 OP 提供的场景,我会选择第一个解决方案,因为它通过本地实现的handleTestClickEvent提供代码重用。 It also comes with a smaller footprint regarding the instance specific this.eventHandler which for the former gets created from handleTestClickEvent.bind(this) whereas the 2nd solution provides a full handler implementation to every single instance.对于特定于实例的this.eventHandler ,它还具有较小的占用空间,前者是从handleTestClickEvent.bind(this)创建的,而第二种解决方案为每个实例提供了完整的处理程序实现。

Prototype methods as event handlers are a bit problematic, specifically when you need both, the this value bound to the instance, and the reference to the actual event handler function.作为事件处理程序的原型方法有点问题,特别是当您需要绑定到实例的 this 值和对实际事件处理程序 function 的引用时。

By default, the event queue calls the handler in the context of the element the event was bound to.默认情况下,事件队列在事件绑定到的元素的上下文中调用处理程序。 It's easy to change the context, but that provides you to create a new function, which then is used as the event handler, and that function is not the method in the prototype anymore.更改上下文很容易,但这使您可以创建一个新的 function,然后将其用作事件处理程序,并且 function 不再是原型中的方法。

If you want to keep the compact class structure, one way is to define the event handler methods as own properties of the instance, they simply can't be inherited.如果要保持紧凑的 class 结构,一种方法是将事件处理程序方法定义为实例的自己的属性,它们根本无法被继承。 The simplest way would be to define the methods as arrow functions in the constructor.最简单的方法是将方法定义为构造函数中的箭头函数。

 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();
 <button id="b">Click me!</button>

The arrow function keeps the reference to the lexical environment it was defined in, and the event queue can't override the context.箭头 function 保持对它定义的词法环境的引用,并且事件队列不能覆盖上下文。 Now this in the handler function is correctly bound to the instance, and this.eventHandler refers to the function, which was attached to the event.现在处理程序 function 中的this已正确绑定到实例,并且this.eventHandler引用了附加到事件的 function。

A slightly less memoryconsuming option would be to use bind when creating the own property, like this:一个稍微少一点内存消耗的选项是在创建自己的属性时使用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);
  }
}

Here bind creates a new function object, which then calls the method in the prototype, the actual code of the method is not duplicated.这里bind新建了一个function object,然后调用原型中的方法,方法的实际代码不重复。 This is loosely similar if you wrote:如果您写了以下内容,则大致相似:

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

It's notable, that when defining an own property with the same name an underlying prototype property has, the prototype property is not overridden, it's only shadowed in the instance, and multiple instances of the class will still work as intended.值得注意的是,当定义与底层原型属性具有相同名称的自己的属性时,原型属性不会被覆盖,它仅在实例中被隐藏,并且 class 的多个实例仍将按预期工作。

Another option is to create your own "event model", which creates a wrapper function (like in the very last code example above) for all events, and stores the reference to that function.另一种选择是创建您自己的“事件模型”,它为所有事件创建一个包装器 function(如上面最后一个代码示例),并存储对该 function 的引用。 The wrapper calls the actual handler with call , which can bind the wanted this value to the event handler.包装器使用call调用实际的处理程序,它可以将想要的this值绑定到事件处理程序。 The stored function references are used to remove events.存储的 function 引用用于删除事件。 Building such a model is not extremely complex, but it provides a bit knowledge of how the this binding and native event model work.构建这样一个model并不是非常复杂,但它提供了一些关于this绑定和本机事件 model 如何工作的知识。

Your code looks way too complex.您的代码看起来太复杂了。 If you want to disable a click handler, there are several ways to do that without the necessity to remove the handler.如果您想禁用单击处理程序,有几种方法可以做到这一点,而无需删除处理程序。 Event delegation makes your life easier here too.事件委托也让您的生活更轻松。 In the snippet button#b is disabled after 3 clicks, using a data-attribute.在片段中, button#b #b 在点击 3 次后被禁用,使用数据属性。 button#a can be clicked indefinitely. 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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM