How JavaScript works: A complete guide to Events And Event Listeners in JavaScript

Lawrence Eagles
SessionStack Blog
Published in
8 min readJul 11, 2022

This is post # 67 of the series, dedicated to exploring JavaScript and its building components. In the process of identifying and describing the core elements, we also share some rules of thumb we use when building SessionStack, a JavaScript tool for developers to identify, visualize, and reproduce web app bugs through pixel-perfect session replay.

Introduction

Events are signals or actions fired or emitted by systems we interact with programmatically. And these signals are picked up and handled by event listeners — code sections that listen and respond to events.

Events are emitted to notify our programs of changes that can affect executions. And these changes can be due to user interactions such as mouse movements, button clicks, etc.

In JavaScript, an emitted event is represented by an event object that contains methods and properties or information about the event. And this event object is passed as an argument to any event listener listening to the event.

Some of the properties and methods of the event object are:

  • isTrusted
  • bubbles
  • cancelBubble
  • currentTarget
  • defaultPrevented
  • srcElement
  • target
  • timeStamp
  • type
  • stopPropagation
  • preventDefault
  • stopImmedaitePropagation
  • initEvent

There are different types of events in JavaScript, and an event can originate as a result of user interaction or programmatically from our code.

The term event interface refers to events that occur in the DOM, and these events have a name that ends in Event. Examples of these events are:

Emitting And Handling Events

In JavaScript, DOM events that are generated programmatically are called synthetic events.

And in this section, we will learn how to create and handle events programmatically.

How to create Events

Events are created by invoking the Event constructor as seen below:

The code above creates a greet event and returns an event object — with type: “greet”. But this greet event object does not do anything on its own. We need to add some code that listens for the emission of this event and responds to it.

How to Handle Events

In JavaScript, we listen to events with Event listeners or Event handlers.

So to add a listener to the greet event above, we will use the addEventListener method as seen below:

In the code above, we invoked the addEventListener method on the eventTarget. The eventTarget here refers to an object e.g the Document or the window object, as well as any parent or child element that can receive events and have listeners for them.

We will learn more about this later. So the code above listens for the emission of a greet event on the eventTarget.

However, we still need to notify all listeners when this event is fired. And to do this, we use the dispatchEvent(event) as seen below:

eventTarget.dispatchEvent(event);

The dispatchEvent(event) method takes the event object as an argument and dispatches it. And once this event is emitted the dispatchEvent(event) invokes all the associated listeners synchronously, thereby triggering a response.

Our final code should look like this:

To elaborate on this, we will use real elements as the eventTarget as seen below:

The code above is similar to our previous example. But here, we used the querySelector method to get a reference to the eventTarget — the DOM element with id=”greet”. And we stored this reference on the elem variable used throughout the code. The result is the same as the previous example, with a small addition introduced with the line:

elem.innerHTML = “Greetings from John Doe!”

And this prints “Greetings from John Doe!” on the browser once the event fires. You can see this in action here.

Note the terms event listeners and event handlers are loosely used to refer to the same thing. But in the strict sense event listeners refers to code used to add a listener to an event target:

eventTarget.addEventListener(‘click’, function() { /* do stuff here*/ }, false);

While an event handler refers to the handler function invoked once the listener is notified of an emitted event. Following this, several event listeners can use the same handlers as seen below:

In the code above, we created an event handler — handleLogDetails and used it in all the event listeners. Also, we used the CustomEvent constructor that enables us to create events with additional information. And these extra pieces of information are logged to the console.

You can see this in action here.

In addition to adding event listeners to event targets, we can also programmatically remove event listeners by using the removeEventListener method as seen below:

Event Delegation

So far, we have learned about adding event listeners to a single element. But what happens if we want to listen to events emitted from many child elements? Event delegation gives us a clean and efficient pattern that enables us to add one listener to a parent element. And this event listener will listen for and analyze bubbled events on each child element.

Event delegation utilizes event bubbling, so before we learn how to implement event delegation, let’s learn about event bubbling.

Event Bubbling And Capture

Event bubbling, target, and capture are 3 phases of an event flow in the browser. They describe how browsers handle events fired from nested elements.

During the bubbling phase, an event fired on a child element bubbles up the DOM tree. And this event can be captured and handled by any event listener on its ancestor element — outer elements.

An event only bubbles if the bubble boolean property is true.

During the bubbling phase, the browser starts looking for listeners from the immediate parent of the child element from which the event is fired. The browser then continues its search up the DOM tree.

Consider the code below:

The code above demonstrates event bubbling. And we see that when the button is clicked the event bubbles up the DOM. Consequently, the event listeners of all its ancestor elements are notified of the click event, and they log their responses to the console.

You can see this in action here.

The capture phase is the reverse of the bubble phase. In the capture phase, the browser starts searching for listeners from the outermost ancestor element — the html element and searches down the DOM tree until it reaches the direct parent of the child element that emitted the event.

Lastly, in the target phase, the browser checks to see if the event target has an event listener for the fired event. And after this, it propagates the event to the immediate parent element and continues propagating it up the DOM tree until it reaches its outermost ancestor element. Note the browser will only propagate an event up the DOM tree if the bubbles property of the emitted event is true.

We can determine which phase of the event flow is currently being executed programmatically from the eventPhase property.

By default, JavaScript events go through the capturing and target phases. But an event only enters the bubbling phase if the bubbles property is true. Also, all event listeners are by default registered in the bubbling phase. And if you want to register an event for the capturing phase, you can set the optional third property of the addEventListener method to true.

Stop Propagation

Event bubbling may not be a desirable effect in some scenarios. And in such cases, we can prevent the event from propagating by invoking the stopPropagation method on the event object. So by invoking the stopPropagation method in our previous example, we can prevent the bubbling of the current event up the DOM tree. To do this, modify the JavaScript code snippet in the example above, as seen below:

The stopPropagation method does not prevent any default behavior like reloading a form once it is submitted, and clicking on links will still work. But to prevent default behaviors, the preventDefault method.

Also, if multiple event listeners are listening for that event, invoking the stopPropagation method will not prevent the event from propagating to those listeners. But to do this, you can use the preventImmediatePropagation method.

Now with our understanding of event bubbling, we can learn how to implement event delegation.

As mentioned above, event delegation enables us to utilize event bubbling in a useful way. To see this in action, consider a webpage with the following HTML:

In the code above, the section element has five children. And each child element has a name attribute added to it using HTML 5 data-* global attributes. Our task is to log the name attribute on a child element to the console when that child element is clicked.

So rather than, adding an event listener to each child element, we can leverage event delegation and rely on event bubbling to propagate the event up the DOM tree. And by doing this, we only need to add one listener to the outermost element in the DOM tree, as seen below:

In the code above, we added the event listener to the document, so any event that is emitted from any element in the DOM tree will be captured and handled by our listener.

Thus the code above prints the name attribute of the clicked child element to the console. And you can see this in action here.

We can see from our implementation of event delegation that event delegation gives us a clean and efficient way to handle events originating from nested elements.

Conclusion

In this article, we have learned about events in JavaScript, and how to create and handle them.

We also learned about event delegation — a pattern that enables us to utilize event bubbling or event propagation in a useful way.

And in cases where event propagation is not needed, we learned how to stop the event from propagating.

JavaScript events are very important to us these days because they lay the foundation for useful patterns such as the observer pattern and the publish-subscribe pattern.

SessionStack utilizes pub/sub-services to process all of the ingested behavioral data from the browser in real-time. As the data is being ingested, SessionStack allows you to watch user sessions as videos, allowing you to see exactly what happened during their journey.

Combining this visual information with all of the tech data from the browser such as errors, stack traces, network issues, debug data, etc. you can easily understand problematic areas in your product and efficiently resolve them.

There is a free trial if you’d like to give SessionStack a try.

SessionStack replaying a session

Interested in more about JavaScript? Check out all “How JavaScript works” publications here.

--

--