Event Binding

addEventListener and the onevent property both wire functions to events, but only one of them lets you attach multiple handlers. Knowing the difference changes how you structure event-driven code.

May 1, 20263 min read8 / 12

Every interactive piece of a web app runs because something listened for an event and responded. The DOM gives you two ways to set that up, and they are not equivalent.

The Essentials

  1. Events are everywhere: Click, double-click, change, keydown, scroll, focus, touch, pointer, drag, and many more. Any DOM element can have listeners for any applicable event.
  2. Special events live on window: DOMContentLoaded and popstate fire only on window. Listening for them on other elements will not work.
  3. Naming pattern: Event names are lowercase, no word separator, no "ed" suffix. It is click not clicked, change not changed. DOMContentLoaded is a historic exception.
  4. onevent properties (onclick, onchange): Each is a single-slot property. Assigning a second function overwrites the first.
  5. addEventListener: Accepts multiple listeners for the same event on the same element. All of them fire in sequence.

A Taxonomy of Events

The DOM API ships with a large set of built-in events, and the W3C keeps adding more as new browser capabilities are standardized.

The most common ones:

CategoryEvents
Pointerclick, dblclick, mousedown, mouseup, mousemove
Keyboardkeydown, keyup, keypress
Formchange, input, submit, focus, blur
DocumentDOMContentLoaded, load
Historypopstate
Scrollscroll
Touchtouchstart, touchend, touchmove
Dragdragstart, drag, drop, dragover

Some of these belong to newer W3C specs layered on top of the core DOM: the Pointer Events API, the Touch Events API, the Drag and Drop API. They all follow the same addEventListener pattern.

The naming convention is strict: lowercase, no hyphens, no underscores, no -ed suffix at the end. You subscribe to click, not clicked. To change, not changed. The historical outlier is DOMContentLoaded, which mixes cases because it was defined before the naming convention was established.

onevent vs addEventListener

There are two mechanisms for wiring a function to an event.

onevent properties

Every DOM element has a set of onevent properties: onclick, onchange, onkeydown, and so on. These are simple properties. You assign a function to them:

JavaScript
button.onclick = () => console.log('clicked');

The limitation: it is a property setter. Assign a second function to the same property and the first is gone:

JavaScript
button.onclick = () => console.log('first handler'); button.onclick = () => console.log('second handler'); // first one is lost

JSX uses this same mental model, but with camelCase: onClick, onChange, onSubmit. The naming is different but the one-handler-per-event structure is the same.

addEventListener

addEventListener uses the observer pattern. You register handlers, and all of them fire when the event occurs:

JavaScript
button.addEventListener('click', () => console.log('first handler')); button.addEventListener('click', () => console.log('second handler')); // Both fire when the button is clicked

You can separate concerns without worrying about overwriting another module's handler. Each part of the code subscribes independently. This is why addEventListener is the standard choice for anything beyond the simplest examples.

Note that native DOM event names go in as lowercase strings: 'click', 'change', 'keydown'. Not 'onClick' or 'onChange'. The camelCase convention is JSX-specific.

Separating Initialization Concerns

One practical advantage of addEventListener: you can call it multiple times on the same event without conflict. This lets you split initialization logic across files:

JavaScript
// In router.js window.addEventListener('DOMContentLoaded', initRouter); // In menu.js window.addEventListener('DOMContentLoaded', initMenu); // Both run when the DOM is ready, in the order they were registered

Without addEventListener, only one of these could exist. With it, each module subscribes to the events it cares about without needing to know what other modules have registered.

Further Reading and Watching

Video: