DEV Community

loading...
Cover image for 💡Know The Differences Between Event Capturing, Bubbling & Delegation  in JS

💡Know The Differences Between Event Capturing, Bubbling & Delegation in JS

Rajat Gupta
Newsletter: https://buttondown.email/rajatetc
Originally published at rajatgupta.xyz ・4 min read

Do you get confused 😵 about the order in which event listeners are invoked when you click on an element? Then this article is for you.

Also, this is a hot 🔥 question in JavaScript interviews.

📝Prerequisites

  • Basic HTML/CSS
  • Familiar with DOM Manipulation in JavaScript

When we want to modify the DOM - the normal flow of things is like this:

Select an element -> addEventListener('event', callback fn) -> What to do

Things get a little complicated when you have nested elements like:

<div> -> <li> -> <p>

If you attach an event listener to every element - what will be the order of execution?

Before we answer that, let's cover what is an event object

👨‍🏫Basics

Whenever we use an event handler function, a parameter is automatically passed to it. It contains some extra information. Generally specified as event, evt or e. This is the event object.

One of the most useful properties of an event object is target.

The target property of the event object is always a reference to the element the event occurred upon

Just remember this for now. It will make more sense when we take the examples below.

Let's come back to addEventListener() for a bit. You can actually pass a third argument to it - useCapture.

It's a boolean value. By default, it is set to false meaning browsers run the bubbling phase, basically, event bubbling is used. You can set it to true for running the capturing phase.

🎈 Event Bubbling

  • In the bubbling phase, browsers run the event handler on the element first (if it has one)

  • Then it moves to the next immediate ancestor (parent) element and does the same thing, and then the next until it reaches the <html> element

Let's understand it better with the help of an example.

Demo:

Create an index.html file with three divs like this:

<!DOCTYPE html>
<html>
  <head>
    <title>Event Bubbling</title>
    <meta charset="UTF-8" />
  <style>
    div {
      min-width: 100px;
      min-height: 100px;
      padding: 30px;
      border: 1px solid black;
    }
  </style>
</head>
<body>
  <div id="grandparent">
    <div id="parent">
      <div id="child"></div>
    </div>
  </div>
  <body>
    <script src="src/index.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Select the elements and add a click event listener with a simple log function in index.js:

document.querySelector("#grandparent").addEventListener("click", () => {
  console.log("grandparent clicked");
});
document.querySelector("#parent").addEventListener("click", () => {
  console.log("parent clicked");
});
document.querySelector("#child").addEventListener("click", () => {
  console.log("child clicked");
});
Enter fullscreen mode Exit fullscreen mode

Now, click on the div with #child and check your console. It will be:

child clicked 
parent clicked 
grandparent clicked
Enter fullscreen mode Exit fullscreen mode

If you click on div with #parent:

parent clicked 
grandparent clicked
Enter fullscreen mode Exit fullscreen mode

Notice the order of execution. See how it bubbles up.

⬇️ Event Capturing

Also known as Event Trickling is essentially the opposite of Event Bubbling.

Outer-most ancestor -> next element -> selected element

In the same index.js, give the third argument as true to all three elements like this:

document.querySelector('#grandparent').addEventListener(
  'click',
  () => {
    console.log('grandparent clicked')
  },
  true
)
Enter fullscreen mode Exit fullscreen mode

Now, when you click on the child element, the console will be like this:

grandparent clicked 
parent clicked 
child clicked 
Enter fullscreen mode Exit fullscreen mode

Now, let's make it a little trickier, set the parent element to false and keep the others as true. What will be the output when you click on the child element?

grandparent clicked 
child clicked 
parent clicked
Enter fullscreen mode Exit fullscreen mode

First, we have the capturing phase. grandparent is set to true so it is logged. parent is false so it is skipped. child is logged.

Now, in the bubbling phase - parent is logged.

❓ for you: What will happen if we put the child element to false as well? Comment your answer 👇

🛑e.stopPropagation()

Now, all this bubbling/capturing is a very expensive task (in terms of performance). More on this later when we cover event delegation

Also, it gets annoying at times.

How to stop it?

Remember, the event object from before? We can call a method on it - namely:
stopPropagation

So, if we write the child element like this:

document.querySelector('#child').addEventListener(
  'click',
  (e) => {
    console.log('child clicked')
    e.stopPropagation()

  },
  false
)
Enter fullscreen mode Exit fullscreen mode

Can you guess what will happen when we click on it? Only child clicked will be logged.

✈️ Event Delegation

Suppose, you have a large application (like an e-commerce store) having a lot of events. Do you think it's a good idea to attach event listeners to every element?

It's not. It will consume a lot of memory. Not to mention the extra lines of code.

An efficient way of dealing with this problem is event delegation

Event delegation takes advantage of event bubbling. The idea is that if you want some code to run for any one of a large number of child elements, you set the event listener on the parent and have the events bubble up.

Let's understand this with the help of an example.

Demo:

Create a simple unordered list like this in index.html:

    <div>
      <ul id="category">
        <li id="oranges">oranges</li>
        <li id="apples">apples</li>
        <li id="bananas">bananas</li>
      </ul>
    </div>
Enter fullscreen mode Exit fullscreen mode

Now, in index.js - only attach an event listener to <ul> element:

document.querySelector('#category').addEventListener('click', (e) => {
  console.log(e.target)
  }
})
Enter fullscreen mode Exit fullscreen mode

Notice e.target - as covered earlier, it is a reference to the element on which event happens. Now, when you will click on the <li> with #oranges. It will log:

<li id="oranges">oranges</li>
Enter fullscreen mode Exit fullscreen mode

Finally, let's cover the pros and cons of using event delegation.

➕Pros

  • As discussed earlier, with event delegation - we have fewer event listeners and we save memory. Our applications are optimized.
  • Less code as we are not having an event handler on every child
  • It is easier to do DOM manipulation. Suppose, we are doing infinite scroll in our app. Now, we don't have to attach event listeners to every new element. As bubbling will happen and we can just have it on the parent.

➖Cons

  • Not all event gets bubbled up. Example: resize, focus, and blur.
  • Remember e.stopPropagation? If we use that anywhere in our code, bubbling won't happen from that point on.

And we are done 🏁

If you found this helpful - like, comment, and share.

📚References

Akshay Saini: https://www.youtube.com/channel/UC3N9i_KvKZYP4F84FPIzgPQ

MDN: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events

Discussion (0)