DOMintermediate
Event Bubbling and Capturing
The two phases an event travels through the DOM tree — capturing down from the root, then bubbling back up from the target — and how to hook into either.
An event travels the tree in three phases: down to the target, at the target, then back up.
Event bubbling is like dropping a pebble into a pond at a specific point (the target) and watching ripples travel outward (bubble up) past every ring (ancestor element) around it — anyone stationed at any ring who's listening for ripples will feel it pass by, in order, from the center outward.
Key Concepts
1
capturing window -> document -> body -> div -> button
target button
bubbling button -> div -> body -> document -> window2
Handlers listen in the bubbling phase by default.
el.addEventListener('click', fn); // bubbling
el.addEventListener('click', fn, true); // capturing
el.addEventListener('click', fn, { capture: true });3
Which explains the ordering people are asked to predict.
outer.addEventListener('click', () => log('outer bubble'));
outer.addEventListener('click', () => log('outer capture'), true);
inner.addEventListener('click', () => log('inner'));4
// clicking inner: outer capture, inner, outer bubble5
Capture runs on the way down, so the ancestor's capture handler fires before the target's.
6
target against currentTarget.
e.target what was actually clicked
e.currentTarget the element whose handler is runningtargetcurrentTarget
7
On a handler attached to a list, target is the item clicked and currentTarget is the list. That
distinction is what makes delegation work.
targetcurrentTarget
8
Not every event bubbles.
bubble: click, input, change, keydown, submit
do not: focus, blur, load, mouseenter, mouseleave9
focusin / focusout the bubbling versions of focus / blur
mouseover / mouseout the bubbling versions of enter / leave10
Trying to delegate focus fails for this reason, and focusin is the fix.
focusfocusin
11
Stopping it.
e.stopPropagation() no further elements
e.stopImmediatePropagation() also skips other handlers
on this same element12
The useful options.
{ once: true } removes itself after firing
{ passive: true } promises not to call preventDefault,
so the browser need not wait to scroll
{ signal } remove via an AbortController13
passive: true on scroll and touch listeners is a real performance win — without it the browser must run
your handler before it can scroll, in case you cancel it.
passive: true
14
What the interviewer is probing.1. "Describe the three phases of an event." Probing: the model. Stalls: "It fires on the
element." Moves up: capture from the root down, target, then bubble back up — listeners default to
the bubble phase.
15
2. "How do you listen during capture?" Probing: the API. Stalls: "You cannot." Moves up:
pass true or { capture: true } as the third argument to addEventListener.
16
3. "Which events do not bubble?" Probing: the exceptions. Stalls: "All events bubble."
Moves up: focus, blur, load and scroll on an element, among others — focusin and focusout are the
bubbling equivalents.
17
4. "Why does the order of handlers matter?" Probing: propagation. Stalls: "It does not."
Moves up: a handler calling stopPropagation prevents later ones up the tree from running at all.