How it works
The explanation, with examples you can run.
The route of one event
The path is fixed when the event is dispatched: the window, the document, and every ancestor of the target. The event then runs in three phases:
- Capture: from the window down to the target's parent. Only listeners added with
{ capture: true }run here. - Target: listeners on the target itself.
- Bubble: from the target's parent back up to the window. Ordinary listeners run here.
Not every event bubbles. focus, blur, mouseenter and mouseleave don't, while focusin, focusout, click, input and keydown do. stopPropagation() ends the journey after the current element; preventDefault() is separate and only cancels the browser's default action, such as following a link or submitting a form.
Delegation
Instead of one listener per button, put one on the container and find out which child was hit. It uses less memory, and it keeps working for rows added after the listener was attached. This runs in the Try it frame, which has its own document:
Press Run to see what your code prints, in order.
Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.
closest() walks up from the element that was actually hit (here the <span>) to the nearest matching ancestor, so it doesn't matter how deep the markup inside the button goes.
Listeners and the main thread
Listeners run on the main thread as tasks of the event loop, so a slow handler delays the next frame and every other event. For touchstart, touchmove and wheel, pass { passive: true } when you never call preventDefault(): the browser can then scroll straight away instead of waiting for your code. Remove listeners you no longer need, or pass an AbortSignal with { signal } and abort it to remove a group at once.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.