Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

addEventListener
A call on an element that registers a listener for one kind of event.
closest
A way to find the nearest parent of an element that matches a rule you give, starting with the element itself.
closure
A function together with the variables from the place it was written, which stay available when it runs later.
default behaviour
What the browser does on its own when an event happens, such as following a link or submitting a form, before any script intervenes.
DOM
The tree of objects a browser builds from the page, which a script can read and change while the page is open.
element
One item in the page tree, such as a heading or a button, which a script can find and change.
event
Something that happens on the page, such as a click, a key press, or a form being submitted, which a script can respond to.
event bubbling
The way an event rises from the element that received it up through each of its parents.
event target
The element that actually received the action, which may be a child of the element whose listener is running.
function
A reusable block of code that can be called with values and can return an answer to whoever called it.
handler
The function you give to a listener, which the browser runs when the event it waits for happens.
listener
A function registered on an element to wait for one kind of event and run each time that event reaches it.
property
A named value stored on an object, usually read and written with a dot, such as the text of an element.
reference
A value that points at an object rather than holding a copy, so a change made through one is visible through the other.
scope
The part of a program where a name can be used, decided by where it was declared.
variable
A name that holds a value so you can use it again; a constant cannot be pointed at a different value later.

JavaScript from the ground up

Events

The page answering back: listening for clicks, reading what happened, and keeping state between events.

Part 7 of 12

Intermediate15 min readBack to Courses

Listening for something to happen

Up to now the script ran once, at load, and stopped. An event listener keeps it alive: button.addEventListener("click", handler) says "when this button is clicked, run this function" — as many times as it is clicked.

The name of the event is a string, and there are dozens: click, input while typing, submit, keydown, mouseover. The same element can listen to several at once, and several elements can listen to the same one.

The listener is registered, not run. The call to addEventListener returns immediately, and the function sits waiting until the event fires. That is the difference between a script that runs once at load and one that keeps answering for as long as the page is open.

A button that counts

const button = document.querySelector("button");
const output = document.querySelector(".output");
let count = 0;

button.addEventListener("click", () => {
  count = count + 1;
  output.textContent = `Clicked ${count} times`;
});

State lives outside the handler

The counting variable is declared out in the open, not inside the handler. A variable created inside the function would be born fresh on every click and always read 1 — the distinction between the function (which runs many times) and the value (which persists between runs).

That is what "state" means: the values a program remembers as things happen. A counter, the open item, the current filter — all of them are just variables declared where the handler can see them.

Declaring the counter outside also makes the handler reusable: the same function can be attached to two buttons and they will share the count, which may or may not be what you want. If each button needs its own count, each needs its own variable, or the count has to be stored on the element.

The event object

The handler can take a parameter: the event itself. It carries what happened — which key was pressed, the element that was clicked, the position on screen — and one method to know early: event.preventDefault(), which stops the browser’s default behaviour for that event.

That method is what a form handler always starts with, and the next chapter uses it.

Events travel through the page

A click on a button does not stop at the button. The event travels down to it and then back up through its ancestors, calling listeners on the way. That trip is called bubbling, and it is why a listener on a list can answer a click on any item inside it.

Inside the handler, event.target is the element that actually received the event, while event.currentTarget is the element whose listener is running. They are the same on a button with no children, and different the moment the click lands on a span inside it. event.target.closest("button") walks up to the thing you meant.

The handler is a value

addEventListener takes a function, not the result of calling one. button.addEventListener("click", bake) passes the function and works; button.addEventListener("click", bake()) calls it immediately and passes whatever it returned, which is usually undefined.

A listener also stays attached until it is removed or the page goes away. Adding the same function twice attaches it twice, so a counter can jump by two per click. removeEventListener needs the same reference you added, which is one good reason to give the handler a name.

A worked click, traced

Suppose the button and the output were found when the script loaded, and count starts at 0. The listener is attached and the script ends; nothing else happens until a click. On the first click the browser calls the handler, which reads count, adds one and writes Clicked 1 times into the paragraph. The function returns, but count is still 1 because it was declared outside.

A second click runs the same function again, and this time count is 1, so it becomes 2. The listener was never re-attached; it was attached once and the browser keeps calling it. That is why the state has to live where both calls can see it.

Three common slips: writing handler() instead of handler, which runs the function once at setup and attaches nothing; attaching a listener inside another listener, so it stacks on every event; and assuming event.target is the button when the click may land on a child — closest is the reliable way back to the element you mean.

Try these next

  • Log event.target.tagName and event.currentTarget.tagName from the same handler.
  • Use a named function, attach it, then remove it and confirm clicks stop counting.
  • Add a second button and let one listener on the container answer both.

A button that answers

The button does nothing. Make it count: each click should update the paragraph with how many times it has been pressed.

Live Preview
Console output will appear here...

3 left to do

  • The button listens for clicks
  • A count is kept between clicks
  • The paragraph is updated

A let count = 0; outside the handler; inside, add one and set output.textContent with a template literal.

One listener, three buttons

Attach a single click listener to the container, not to each button. When a button is clicked, read its label and write which loaf was chosen into the paragraph.

Live Preview
Console output will appear here...

3 left to do

  • The container listens for clicks
  • The clicked button is found with closest
  • The choice is written into the paragraph

Use event.target.closest("button") to find the button from whatever was clicked, then read its textContent. Ignore the click when closest finds nothing.

If a listener seems to do nothing, check that the element was found before the listener was attached. A script with a querySelector that returned null has nothing to listen on.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does button.addEventListener("click", handler) do?