DOM Event Handling
DOM event handling registers callbacks on event targets and responds as events propagate through the document. Understanding propagation, listener options, delegation, default actions, and cleanup makes handlers predictable and maintainable.
A DOM event listener registers a callback for a particular event type on an `EventTarget`, such as an element, `document`, or `window`. When an event is dispatched, the browser invokes eligible listeners with an `Event` object containing details such as the target and event type.
For events that bubble, propagation generally proceeds through capture ancestors, the target, and then bubbling ancestors. A listener can opt into the capture phase with `{ capture: true }`; otherwise, it normally runs during bubbling. Not every event bubbles, so check the event's behavior before relying on delegation.
`event.target` identifies the original event target, while `event.currentTarget` identifies the object whose listener is currently running. Delegation uses a bubbling event at a stable ancestor, then checks whether the target or one of its ancestors matches the intended control.
Use `event.preventDefault()` to cancel a cancelable default action, and use propagation-stopping methods only when controlling event flow is actually required. Listener options include `once` for one invocation, `passive` to signal that the callback will not cancel the default action, and `signal` to remove the listener when an `AbortController` is aborted.
Listeners should be cleaned up when their owner no longer needs them, especially for long-lived targets or repeated component setup. `removeEventListener()` must use the same event type, callback identity, and capture setting as registration; alternatively, abort a listener registered with an `AbortSignal`.
Key points
- Listeners are registered on `EventTarget` objects with `addEventListener()`.
- Capture, target, and bubbling describe the usual event propagation phases.
- Use `target` for the origin and `currentTarget` for the active listener owner.
- Delegation handles matching descendants through a bubbling event on an ancestor.
- Use listener options and cleanup strategies to control listener lifetime and behavior.
Interview questions
What are the phases of DOM event propagation?
Event propagation commonly has a capture phase, a target phase, and a bubbling phase. Capture travels from ancestors toward the target; bubbling travels back through ancestors when the event bubbles. A listener registered with `capture: true` participates in capture, while the default is the bubbling phase. Not all event types bubble, so the event's characteristics matter.
How do `event.target` and `event.currentTarget` differ, and why does it matter for delegation?
`target` refers to the original source of the event, while `currentTarget` refers to the object whose listener is executing now. In a delegated listener, `currentTarget` is typically the ancestor holding the listener. The target may be a nested element inside the control the handler is interested in, so delegation often uses `closest()` and verifies containment.
When would you use event delegation, and what should its handler validate?
Delegation registers one listener on a stable ancestor and uses bubbling to handle events from matching descendants. It can reduce per-element listener setup and automatically cover matching elements inserted later. The handler must check that the event came from the intended control and that the matched element is inside the delegated root. It is unsuitable when the event does not bubble unless an alternative event strategy is used.
Compare `preventDefault()`, `stopPropagation()`, and `stopImmediatePropagation()`.
`preventDefault()` asks the browser not to perform a cancelable default action, such as submitting a form in the usual way. `stopPropagation()` stops the event from continuing to other objects along the propagation path, but does not cancel that default action. `stopImmediatePropagation()` also prevents later listeners on the same target from running. These methods solve different problems and should be used only when the desired behavior requires them.
How do you prevent event listeners from outliving the component or feature that registered them?
A listener can be removed with `removeEventListener()` when the event type, callback object, and capture setting match the registration. Creating a new anonymous function for removal does not match the original callback. For lifecycle-based cleanup, register with an `AbortSignal` and abort its controller when the component is disposed. The `once` option is useful when the listener should run only once.