DOM Events & Event Delegation
MedDOM events are signals fired by the browser when something happens. Events propagate through the DOM tree in three phases: capture (top-down), target, and bubble (bottom-up). Event delegation exploits bubbling to handle events efficiently by placing a single listener on a parent element.
Interactive Visualization
DOM Events
document>
body>
div>
button
Phase: Capture
Event travels DOWN from document to target
Key Points
- addEventListener(type, handler, options) attaches event listeners to DOM nodes
- Events propagate in 3 phases: capture (down), target, bubble (up)
- event.target is the element that fired; event.currentTarget is the listener element
- Event delegation: one parent listener handles events for all children via event.target
- stopPropagation() stops bubbling; preventDefault() cancels the default browser action
- Options: { once: true } auto-removes, { passive: true } improves scroll performance
Code Examples
addEventListener and Event Object
const button = document.querySelector('#myBtn'); button.addEventListener('click', function(event) { console.log('Target:', event.target); console.log('CurrentTarget:', event.currentTarget); console.log('Type:', event.type); }); // With options button.addEventListener('click', handler, { once: true, // auto-removes after first call passive: false, // allows preventDefault() });
addEventListener is the standard way to attach events. The event object contains interaction metadata.
Bubbling vs Capturing
// <div id="outer"><div id="inner"><button id="btn">Click</button></div></div> outer.addEventListener('click', () => console.log('outer capture'), true); inner.addEventListener('click', () => console.log('inner capture'), true); btn.addEventListener('click', () => console.log('btn target')); inner.addEventListener('click', () => console.log('inner bubble')); outer.addEventListener('click', () => console.log('outer bubble')); // Click btn → outer capture, inner capture, btn target, inner bubble, outer bubble
Events travel down during capture, hit the target, then bubble back up.
Event Delegation Pattern
const list = document.getElementById('todo-list'); // One listener handles ALL children list.addEventListener('click', function(event) { const target = event.target; if (target.matches('.delete-btn')) { target.closest('li').remove(); return; } if (target.matches('input[type="checkbox"]')) { target.closest('li').classList.toggle('completed'); } }); // Works even for dynamically added items!
Event delegation places one listener on a parent. Use matches() and closest() to identify the child.
preventDefault and Custom Events
// Prevent default browser behavior form.addEventListener('submit', (e) => { e.preventDefault(); console.log('Form submitted via JS'); }); // stopPropagation vs stopImmediatePropagation btn.addEventListener('click', (e) => { e.stopPropagation(); // stops bubbling }); // Custom Events const event = new CustomEvent('userLogin', { bubbles: true, detail: { username: 'alice', role: 'admin' }, }); document.querySelector('#app').dispatchEvent(event);
preventDefault stops the browser action. stopPropagation stops bubbling. CustomEvent enables app-level events.
Common Mistakes
- Using anonymous functions then trying to removeEventListener
- Calling stopPropagation when you only need preventDefault, breaking delegation
- Attaching individual listeners to hundreds of children instead of delegating
- passive: true listeners cannot call preventDefault
Interview Tips
- Draw the 3-phase propagation: capture down, target, bubble up
- Event delegation with dynamic list — one listener handles all items
- event.target (origin) vs event.currentTarget (listener)
- Passive listeners for scroll/touch performance on mobile