Published at
Updated at
Reading time
2min
This post is part of my Today I learned series in which I share all my web development learnings.

Suppose you want to implement a publish/subscribe pattern in your Frontend application to react to data changes and events. First, you might be looking for an event emitter library.

I installed plenty of event emitter libraries over the years, and if I felt advanterous I wrote them from scratch as a quick coding exercise.

Today I learned that vanilla JavaScript comes with a native event emitter, which I've been indirectly using forever. There's no need for extra code!

The EventTarget interface

It's right in front of us; every time you use addEventListener on a DOM element, you subscribe to events from it. That's a classical event emitter. Can you reuse this functionality somehow?

I've always thought that addEventListener is part of the HTMLElement's prototype, but surprisingly it's not!

Where's the method coming from?

If you inspect the HTMLElement prototype chain, you'll discover it inherits from Element, Node and EventTarget.

Prototype chain of HTMLElement inheriting from Element, Node and EventTarget.

Thanks to the EventTarget interface, you can subscribe to an element's DOM events via addEventListener. MDN defines EventTarget as follows:

The EventTarget interface is implemented by objects that can receive events and may have listeners for them. In other words, any target of events implements the three methods associated with this interface.

Any object inheriting from EventTarget becomes an event emitter!

Extending EventTarget to create an event emitter

Let's initialize a new EventTarget and see what we get.

const emitter = new EventTarget()

typeof emitter.addEventListener    // "function"
typeof emitter.removeEventListener // "function" 
typeof emitter.dispatchEvent       // "function"

emitter.addEventListener('YOLO', () => {
  console.log('YOLO');
})

// invoke attached event listeners
emitter.dispatchEvent(new Event('YOLO'));

Using the bare-bones EventTarget is handy for simple logic, but if you need to do more than sending events, you probably want to pair it with custom logic.

For example, to keep your views up to date, you might want to store state in an event emitter, alter this state and rerender views whenever data changes.

To achieve this, use the JS class syntax, extend the EventTarget interface and pair it with your application logic.

Find a simple "store" event emitter below that holds data and informs listeners when its state changes.

// Extend the `EventTarget` class to get all the goodies
export class MyEventEmitter extends EventTarget {
  #list;

  constructor(list = []) {
    super();
    this.#list = list;
  }

  getItems() {
    return this.#list;
  }

  addItem(item) {
    this.#list = [...this.#list, item];
    // Dispatch a new event to notify listeners
    this.dispatchEvent(new Event("update"));
  }
}

EventTarget is pretty cool stuff! I wonder what other interface gems are hidden in our browers. 🤔

Additional resources

Sam Thorogood published an in-depth article on EventTarget, if you want to learn more about it.

Was this TIL post helpful?
Yes? Cool! You might want to check out Web Weekly for more quick learnings. The last edition went out 14 days ago.
Stefan standing in the park in front of a green background

About Stefan Judis

Frontend nerd with over ten years of experience, freelance dev, "Today I Learned" blogger, conference speaker, and Open Source maintainer.

Related Topics

Related Articles