addEventListener accepts functions and (!) objects
- Published at
- Updated at
- Reading time
- 2min
To build interactive web interfaces, you can attach event listeners to DOM events. How does this work?
You query an element, define an event type, call addEventListener
and pair it with a callback function — and violà, you are ready to react to clicks, keypresses, scrolls and many other events.
Use the following code to react to button clicks:
document.querySelector('button')
.addEventListener('click', () => {
console.log('element clicked');
});
According to MDN, target
defines the following parameters:
target.addEventListener(type, listener [, options]);
target.addEventListener(type, listener [, useCapture]);
target.addEventListener(type, listener [, useCapture, wantsUntrusted ]); // Gecko/Mozilla only
addEventListener
accepts the event type, a listener
callback function and an options
or useCapture
parameter.
What if I told you that the listener
parameter can be a function but an object, too?
It turns out that MDN documents the listener
parameter as follows:
[listener] must be an object implementing the EventListener interface, or a JavaScript function.
The early DOM events specification (we're talking pre-HTML5 here) defined an EventListener
interface. Objects defining a handleEvent
method were valid in addEventListener
.
// a class implementing
// the `EventListener` interface
class EventHandler {
constructor() {
this.eventCount = 0;
}
handleEvent() {
this.eventCount++;
console.log(`Event triggered ${this.eventCount} time(s)`);
}
}
document.querySelector('button')
.addEventListener('click', new EventHandler());
The code above defines a JavaScript class EventHandler
. An initialized event handler object can then be passed to addEventListener
to react to specific events. And because it's an object, it can hold state.
The event handler object keeps track of the number of times a specific event occurred (check it on CodePen) by implementing an eventCount
field. All information is stored in the object itself, and the code works without outer scope variables.
I quite like this pattern, and I can see it coming in handy when dealing with sequential events.
According to MDN, the EventListener
interface is supported by all major browsers, and you can safely pass objects that implement it to addEventListener
.
When would you pass EventListener
objects to addEventListener
? I'd love to learn about more examples!
Edit: Someone shared the following snippet on Reddit.
class MyComponent {
constructor (el) {
this.el = el
this.el.addEventListener('click', this)
}
handleEvent (event) {
console.log('my component element was clicked')
}
destroy () {
this.el.removeEventListener('click', this)
}
}
const component = new MyComponent(
document.querySelector('button')
);
The MyComponent
class accepts a DOM element and automatically attaches/detaches event listeners. It also implements a handleEvent
method so you can pass this
to addEventListener
. Fancy!
Join 5.5k readers and learn something new every week with Web Weekly.