Skip to content

07 · DOM Basics & Events

🎥 Video walkthrough

This module only applies in the browser — Node.js has no DOM. Try these snippets by pasting them into the browser console on any page, or wiring them up in an index.html + .js file pair.

Selecting elements

// Given: <div id="app"><p class="note">Hello</p></div>

const app = document.getElementById("app");
const note = document.querySelector(".note");     // first match
const allNotes = document.querySelectorAll(".note"); // NodeList of all matches

console.log(note.textContent); // Hello

Creating and modifying elements

const heading = document.createElement("h1");
heading.textContent = "Welcome";
heading.classList.add("title"); // add a CSS class

document.body.appendChild(heading); // insert into the page

note.textContent = "Updated text";
note.style.color = "blue";
note.setAttribute("data-status", "read");
console.log(note.getAttribute("data-status")); // read

Removing elements

const toRemove = document.querySelector(".note");
toRemove.remove(); // modern, direct removal

// Older pattern, still common in codebases:
// toRemove.parentNode.removeChild(toRemove);

Handling events

const button = document.createElement("button");
button.textContent = "Click me";
document.body.appendChild(button);

button.addEventListener("click", () => {
  console.log("Button was clicked!");
});

// The event object carries details about what happened
button.addEventListener("click", (event) => {
  console.log("Target:", event.target.textContent);
});

Common events

Event Fires when
click an element is clicked
input a form field's value changes as the user types
submit a form is submitted
keydown / keyup a key is pressed / released
DOMContentLoaded the HTML has finished loading (before images/styles)
load the whole page (including assets) has finished loading

A small interactive example

<!-- index.html -->
<!DOCTYPE html>
<html>
  <body>
    <input id="name-input" placeholder="Type your name" />
    <p id="greeting"></p>
    <script src="app.js"></script>
  </body>
</html>
// app.js
const input = document.getElementById("name-input");
const greeting = document.getElementById("greeting");

input.addEventListener("input", (event) => {
  const value = event.target.value;
  greeting.textContent = value ? `Hello, ${value}!` : "";
});

Event delegation

Attaching one listener to a parent, then checking event.target, scales better than attaching a listener to every child individually — especially when children are added/removed dynamically (as in the to-do app in Module 10).

const list = document.getElementById("list");

list.addEventListener("click", (event) => {
  if (event.target.matches("button.delete")) {
    event.target.closest("li").remove();
  }
});

How It Actually Works

The DOM and JavaScript are two separate worlds connected by bindings: the DOM tree itself lives in the browser's C++ rendering engine, and every element.addEventListener call registers your callback in the engine's event-dispatch tables, not in V8's memory directly. When a click happens, the browser doesn't run your handler immediately as part of the click — it constructs an Event object and schedules a task on the main thread's task queue. The event loop picks up that task, and only then does it call into V8 to run your handler synchronously to completion before picking up the next task. This is why a slow event handler blocks all rendering and other events: there is only one thread, and tasks run one at a time to completion.

Event dispatch itself follows three phases you can hook into: capture (root down to target, via the {capture: true} option), target, and bubble (target back up to root, the default). event.stopPropagation() halts this walk but doesn't cancel the browser's own default behavior (scrolling, following a link) — for that you need event.preventDefault(), which just sets a flag the browser checks after your handler returns. Because listeners are stored as references, closures you create inside addEventListener keep their entire enclosing scope alive for as long as the listener is attached — a common, hard-to-spot memory leak source when elements are removed from the DOM without calling removeEventListener first.

🔀 See this in another language

Exercise

Build a page with a text input and a button. When the button is clicked, append the input's current value as a new <li> to a <ul> on the page, then clear the input. Use event delegation so clicking any list item toggles a done CSS class on it.