DEV Community

Andy Haskell
Andy Haskell

Posted on • Updated on

Tales of a #SurfaceGopher: Understanding JavaScript Events

Tales of a #SurfaceGopher: Understanding JavaScript Events

Events in JavaScript are how you deal with many different kinds of inputs to your web app to make it interactive. Since the 90s, some of the most familiar inputs had been things like clicks and key presses, but web apps today also can deal with inputs like the response to fetch API requests, database transactions, and for mobile web apps, things like your GPS location.

The big idea with events and why we have them is that events provide a way of letting users interact with your web app, and of letting your web app respond to things that happen. We have our web apps listen for events and run JS code responding to them in callback functions to do things like display new data or respond to changes in user preferences.

But learning JavaScript, I had trouble wrapping my head around these events. How do events “kick in” and appear in our code, and what even are they? And what’s this mysterious “event loop” people are talking about?

Experimenting with events

Let’s take a look at a small HTML page that works with an events:

<html>
  <head><title>Event handling with a sloth</title></head>
  <body>
    <h1 id="sloth" onclick="handleClick()">
      ((.(-ω-)((. Sloth is sleeping
    </h1>
    <script type="text/javascript">
      function handleClick() {
        console.log('You clicked on the <h1>!');
        let sloth = document.getElementById("sloth");
        sloth.innerHTML = '((.(⊙ω⊙)((. Sloth is awake';
      }
    </script>
  </body>
</html>

If you click on the text on the HTML page, because we registered the function handleClick as our h1’s onclick handler, if we click the h1, it opens the sloth’s eyes. Pretty straightforward so far.

Our HTML page after clicking to wake up the sloth

NOTE: On browsers that don’t support ⊙ and ω in HTML pages, replace ⊙ with ʘ and ω with ω in your HTML and JavaScript to make the sloth’s eyes and nose

Let’s experiment some with the click handler. Try having handleClick take in an argument named event and log its value:

<h1 id="sloth" onclick="handleClick(event)">
  ((.(-ω-)((. Sloth is sleeping
</h1>
<script type="text/javascript">
  function handleClick(event) {
    console.log(event);
    console.log('You clicked on the <h1>!');
    let sloth = document.getElementById("sloth");
    sloth.innerHTML = '((.(⊙ω⊙)((. Sloth is awake';
  }
</script>

Reload your page and click the sloth again, open your browser console (Command+Option+I in Mac, Ctrl+Shift+I in Windows) and you’ll see an object you can explore.

The Google Chrome browser console logging our event. Google Chrome's got a lot to say about a mouse click!

As you can see, this object is a MouseEvent. Scroll through it and you’ll see that it has properties like screenX and screenY (numbers saying the X and Y coordinates on the screen where you clicked), ctrlKey (whether or not you were holding down the Ctrl key when you were clicking the h1), and target (which HTML element the mouse event was for).

Now let’s try another experiment. At the end of the script tag, add this loop to the end of the <script> tag:

setTimeout(function() {
  for (let i = 0; i < 1000000000; i++) {
    if (i % 1000000 == 0 && i > 0) {
      console.log(i);
    }
  }
}, 100);

With this setTimeout, we wait a tenth of a second (to let our page display), then we start a loop to count to 1 billion, which should take a few seconds to complete. Now refresh this page and then try clicking the sloth again several times, then open your browser console.

The browser console counting to a billion. The mouse event doesn't log until after the counting loop completes

Interesting, even though you definitely woke up our sloth with a click event before the counter finished, we don’t actually run handleClick until after the loop ends. What’s going on? This is where the event loop comes in in JavaScript!

Introducing the event loop

The event loop is a queue that works behind the scenes to process events in your browser. When it processes an event that we register to a listener, it gives a JavaScript event handler function, such as our handleClick method, an object with details on the event. Our event handler function can then use it as one of its arguments.

But as you can see, this doesn’t happen right away. It appears that handleClick only actually gets called after that big for loop finishes. It’s after we finish counting to a billion that the event loop passes the event object into handleClick.

The event loop is kind of like a conveyor belt that dumps boxes into a machine, which is our JavaScript runtime. As we saw, the runtime can only process one piece of code at a time. So if it’s running some code, like our for loop counting to one billion, the machine gives the conveyor belt the red light so it doesn’t put any more boxes in

Diagram of the for runtime machine with a click event box on the conveyor belt. Our for loop is running, so the JS runtime machine gives the event loop the red light on giving it more events

But once the for loop completes, the runtime machine is finished running all of the code it had, so it gives the conveyor belt a green light to give it another box.

With no code to run, the event loop conveyor belt has the green light to give the runtime an event

The machine will then open the box and inside is our MouseEvent. It’s a click event targeting the h1, so the next piece of code it runs is the h1’s onclick, handleClick. Once that completes, the runtime is once again free to take another event off the event loop.

But even while our JavaScript code is running, if other events we’re listening for happen, such as another click or the window resizing, those events also go onto the event loop, waiting their turn to be processed.

Adding another event onto the event loop conveyor belt by clicking

My dog Lola the Micropanda also uses the event loop for trying to chase squirrels in the backyard. She’s not that good at it, but you’ve gotta respect that floofer!

lola.onsquirrel = function(event) {
  lola.barkWhichIsReallyIneffectiveForSneakingUpOnASquirrel();

  lola.runTo(event.squirrelLocation);
};

Lola has an onsquirrel event listener registered, so when she sees a squirrel in the yard, she runs out to try to catch the squirrel, starts barking immediately, and then runs to where the squirrel was. Even if she hears “dinner time!”, she won’t run back into the house until she finishes trying to chase the squirrel, since there’s still stuff to do in the runtime. Instead she puts the DinnerTimeEvent on the event loop so she comes in for dinner.

Lola the Micropanda lying in the grass waiting for SquirrelEvents

But really, the event loop is how we are able to have multiple things go on at the same time in a JavaScript web app. The JavaScript runtime can only do one thing at a time, but actions like streaming a video, getting GPS location updates, and mouse clicks are all outside of the runtime, so something slow like a big download won’t block our single JavaScript thread. That means putting events onto the event loop is how the outside world can communicate with the runtime so your web app can respond to events.

The other thing we get out of having an event loop is that because it gives us a way for JavaScript to only do one thing at a time, that means we don’t need to do anything special to make sure, for example, two JavaScript functions changing the same variable don’t run at the same time. In languages that do allow you to do multiple things at once, like C or Go, while running code in parallel does often make your program faster, concurrent code can be hard to debug.

What are JavaScript events?

We’ve seen the event loop, so now let’s check out events themselves. As we saw in our example code, we were able to add an event parameter to our onclick callback, and when we clicked on the sloth to wake it up, event wasn’t undefined. It was an object, and a really detailed one too, full of everything you’ve ever wanted to know about a mouse click.

The event you saw is a MouseEvent, but there are many other kinds of events out there that the event loop can send to your callback function. For example, there’s KeyboardEvent for stuff like pressing keys. And for tablets and phones there’s DeviceOrientationEvent, which tells you which direction the user is holding their device in. There’s a huge list of the many kinds of events out there in the MDN Event documentation, but don’t get overwhelmed. The main thing to know is that all these events are is data to represent “what happened” on your web app so your JS code can respond to it.

All of these event objects inherit from the Event prototype, so they all have some structure in common. The MouseEvent we logged in our handleClick function has pageX and pageY values that a KeyboardEvent wouldn’t have, but both a mouse and keyboard event have data from the Event prototype. For example, events have a type, such as “click”, “keypress”, or “keyup” that describe the event so we can listen for events of that type, and a target that tells you which object in the DOM (like an HTML element) dispatched the event.

All of these event types ultimately are just regular JavaScript objects that happen to come from outside of the runtime. In fact, if you go into your browser console and type: new MouseEvent('How do you do fellow mouse events?', {screenX: 250, screenY: 250});, you’ll see a mouse event being logged.

Our "MouseEvent" in the browser console

This is because you’re calling the MouseEvent constructor to make an object representing your mouse interacting in some way with pixel (250, 250) on your screen. This interaction didn’t actually happen, but it’s just as much of a MouseEvent object as one on the event loop caused by a real click! Amusingly, because we passed “How do you do, fellow mouse events?” as the first argument of the MouseEvent constructor, “How do you do, fellow mouse events?” is the type of our mouse event, instead of a regular MouseEvent’s type being something like click or mouseover.

One other thing to note is that not all callbacks to asynchronous actions in JavaScript work with Events. An Event object is a representation of something we’re listening for occurring, but it’s not the only way of representing what took place. For example, if in your console you run:

setTimeout(function(event){
  console.log(event);
}, 500);

Then in half a second, even though a task from the event loop goes into the runtime to start the callback, you would not get any Event logged, what’s logged is just undefined. The response to setTimeout is to just run the function you passed in, since there isn’t any data about the event to represent besides “half a second has passed”.

The JavaScript APIs you can use to build a Google Chrome extension take yet another approach; you register callbacks to listen for things like opening a tab or starting a download, and this time we do have data about what happened that goes into the callback.

chrome.downloads.onCreated.addListener(function(downloadItem) {
  alert(`Now downloading from ${downloadItem.url}`);
});

The code looks like we’re handling an event like our click event, but the object we get back in the callback is not a kind of DOM Event, it’s a Google Chrome DownloadItem, which describes the download that’s going on. In all those cases, though, our handleClick function, our setTimeout callback and our Chrome extension download callback all listen for some sort of event to happen, they just process it differently, with different data.

We’ve seen what the event loop is in JavaScript and how it’s used, and we took a closer look at these JS event objects and how they represent what happened. I hope this helped you get an understanding of what’s going on behind the scenes with events. Having seen these concepts and how they play out with callbacks, I recommend building some web apps that work with events, as well as checking out promises which are another popular way to respond to asynchronous actions.

Until next time, STAY SLOTHFUL!

Two-toed sloth sleeping

Image to Ferenc Nasztanovics CC-BY-2.0

Top comments (0)