What Is Event Delegation, Capturing, and Bubbling in a Browser?
January 20, 2023
When the user interacts with the browser, various events are triggered, such as clicks and scrolls. We can handle these events through JavaScript event handlers. Event delegation is an important pattern you can use when you handle an event. Hence, it is one of the most common interview questions.
If you are not familiar with event delegation, capturing, and bubbling in the browser, make sure you learn them before going to an interview.
Event delegation
Event delegation is useful when we want to add the same event listeners and handlers to a group of child elements. When we have many identical elements with similar behaviors, instead of adding handlers to each element, we can directly add handlers to the parent layer. With this approach, we use event.target
to know which element actually has an event, and then handle the event.
Adding listeners and handlers in the parent layer, and then delegated to child elements, is what we call event delegation. The advantage of this is that we don't need to add a handler to each element, such as each button, which can reduce memory consumption; this also makes our architecture more flexible, and elements can be added or removed at any time. You can also write less code to improve readability.
For example (note: This example comes from MDN), if you want to add handler to every div
in a long list, instead of adding every child element, we can add it directly to the parent layer, even if there are hundreds or thousands of child elements.
<div id="container">
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
<div class="tile"></div>
</div>;
const container = document.querySelector("#container");
container.addEventListener(
"click",
(event) => (event.target.style.backgroundColor = bgChange())
);
Event capturing
The event delegation can happen because of the event capturing and bubbling mechanism behind it. Generally speaking, when an event is triggered, it will first enter the capturing phase, then reach the event target, and then the bubbling phase. (It is recommended that during the interview, you can simply draw this W3C event flow, it will be helpful to explain the concept!)
As can be seen from the figure above, the so-called capturing phase means that when an event is triggered, for example, the user clicks a button, the topmost Window of the DOM tree goes all the way down to pass the event down and execute it. Actually, in the code, you need to add {capturing: true}
to the event listener to enable the capturing mechanism.
Event bubbling
The bubbling stage is more commonly used. Contrary to the capturing stage, it first executes the event handler on the target, then passes it to the parent layer, then passes it to the grandparent layer, and then passes it all the way up.
<form onclick="alert('form click event triggered')">
This is a form element
<div onclick="alert('div click event triggered')">
This is a div element
<p onclick="alert('p click event fires')">This is a p element</p>
</div>
</form>
Using the above example (it is recommended to write this example simply and quickly during the interview, it can help explain the concept), when we assign an onclick
handler in the child element <p>
, and click on it, we will see alert
. It is because the onclick
of the parent <div>
is also triggered, and the onclick
of the grandparent <form>
is also triggered.
Here is a detail that needs to be noted. this
during bubbling is not necessarily equal to event.target
, but equal to event.currentTarget
. In other words, this
is the currently executing handler; and event.target
will always be the one that was actually clicked (in this case the innermost<p>
).
In practice, sometimes we don't want bubbling, for example, we only want the events of the child elements to be triggered, and we don't want the elements of the parent to be triggered, so as to avoid interference. To achieve so, you can add event.stopPropagation()
to the handler to avoid event bubbling. Nevertheless, this will still allow the handler to execute, (it just will not bubble up). If you want to stop the bubbling and prevent handlers on the current element from running, you can use event.stopImmediatePropagation()
.