I am trying to remove an eventListener but it looks like I miss something.
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
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>
Advertisement
Answer
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.
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.
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. 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. 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.
A slightly less memoryconsuming option would be to use bind
when creating the own property, like this:
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. 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.
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. The wrapper calls the actual handler with call
, which can bind the wanted this
value to the event handler. The stored function references are used to remove events. Building such a model is not extremely complex, but it provides a bit knowledge of how the this
binding and native event model work.