Step 02

The event object

When the browser calls your handler, it does not call it empty-handed. It passes one argument: an event object describing exactly what happened — what kind of event, where, on which element, with which keys held down, at what time. Most event-driven code is just reading this object and deciding what to do about it, so the fastest way to get fluent is to build an instrument that shows it to you.

Learning Objectives

  • Read the core properties: type, target, currentTarget, timeStamp
  • Use pointer data (clientX/clientY, button) and key data (key, code, modifier flags)
  • Cancel an event's default action with preventDefault()
  • Build a start/stop event watcher from paired addEventListener / removeEventListener calls

One object, many shapes

Every event carries the basics — type, target, timeStamp — and then a layer of properties specific to its kind. A keydown has key and code; a mouse event has coordinates and a button number; both carry the modifier flags altKey, ctrlKey, shiftKey. The demo below dumps one fixed list of properties for every event it sees:

const attrs = [ 'type','timeStamp','target','currentTarget','clientX','clientY', 'key','code','altKey','ctrlKey','shiftKey','button' ]; const fmt = (e) => { const lines = [`EVENT @ ${Math.round(e.timeStamp)}ms`]; for (const k of attrs) lines.push(`${k}: ${e[k]}`); return lines.join('\n'); };

Ask a keyboard event for clientX and you get undefined — not every property is meaningful for every event, and the watcher makes that visible rather than hiding it. For open-ended exploration, console.dir(e) inside any handler shows you everything an event actually carries.

target versus currentTarget. target is where the event originated — the thing actually clicked or typed into. currentTarget is the element whose listener is running right now. They are often the same element; the whole of step 4 lives in the cases where they are not.

Live demo

Press Start watching, then generate events: type in the textarea (try holding Alt, Shift, or Ctrl), move the mouse through the tracking area, click the button. Each event overwrites the property log on the left. Stop watching detaches every listener — the page goes quiet, which is its own lesson.

The start/stop pair works because the handlers are kept in one object, so the same function references get attached and detached:

const handlers = { keydown: (e) => out.textContent = fmt(e), mousemove: (e) => out.textContent = fmt(e), click: (e) => out.textContent = fmt(e), }; function start() { txt.addEventListener('keydown', handlers.keydown); area.addEventListener('mousemove', handlers.mousemove); btn.addEventListener('click', handlers.click); } function stop() { txt.removeEventListener('keydown', handlers.keydown); area.removeEventListener('mousemove', handlers.mousemove); btn.removeEventListener('click', handlers.click); }
Prefer keydown. The old keypress event is deprecated; keydown fires for every key, carries both the logical key (“a”, “Enter”) and the physical code (“KeyA”), and repeats while held.

Canceling the default

Many events announce something the browser is about to do: a click on a link navigates, a submit posts the form, a keydown types a character. preventDefault() is your veto. The handler still runs, the event still travels the tree — only the built-in consequence is canceled. You saw it in step 1 on a link; it returns in step 5 as the heart of form handling, where canceling the submit is what makes client-side processing possible.

Next Steps

In Step 3: Event flow, we follow one event's whole journey through the tree:

  • Trace the three phases: capture, target, bubble
  • Read eventPhase to see where a listener sits in the journey
  • Stop the journey with stopPropagation() — and see what it does not stop