ToolPopToolPop
JavaScript · Lesson 7 of 18

The DOM, making the page actually do something

10 min readUpdated 24 Jun 2026

The DOM (Document Object Model) is the tree of HTML elements your browser shows. JavaScript can read it, change it, and listen to it. Before React and Vue, this was how every interactive site worked. Even with frameworks, you still hit the DOM eventually.

This lesson is short on purpose. The DOM API is huge, but you only need a handful of methods to do real work.

Diagram
rendering diagram...
The DOM tree mirrors your HTML. JS traverses and mutates it.

Finding elements

js
const btn = document.querySelector('#submit');
const items = document.querySelectorAll('.cart-item');
  • querySelector returns the first match using a CSS selector, or null.
  • querySelectorAll returns a NodeList of all matches. Loop it with forEach.
  • getElementById, getElementsByClassName still exist. The query selector duo replaced them in most code.

Listening to events

js
btn.addEventListener('click', (event) => {
  console.log('clicked', event.target);
});

The callback receives an event object. Useful properties: event.target (the element clicked), event.preventDefault() (stop default behaviour like form submit), event.key (for keyboard events).

To stop listening, use removeEventListener with the same function reference. Anonymous arrow functions cannot be removed, so name them if you need to.

Creating and inserting elements

js
const li = document.createElement('li');
li.textContent = 'Masala Dosa';
li.classList.add('menu-item');
document.querySelector('#menu').appendChild(li);

createElement makes a detached element. Set text, classes, attributes, then append it to a parent. appendChild, prepend, before, after and replaceWith give you all the placement you need.

textContent vs innerHTML (read this twice)

  • textContent sets the text. HTML in the string is treated as plain characters. Safe.
  • innerHTML sets HTML. Tags are parsed and inserted. Powerful, and dangerous.
js
el.textContent = '<img src=x onerror=alert(1)>'; // shows the text
el.innerHTML   = '<img src=x onerror=alert(1)>'; // runs the script

If you put user input through innerHTML without sanitising it, you have given attackers a Cross-Site Scripting (XSS) hole. Default to textContent. Use innerHTML only with content you fully control.

One leaked innerHTML with user data is enough for an attacker to steal session cookies. Take this rule seriously.

Event delegation

Adding a listener to every row of a 500-item list is wasteful. Add one listener on the parent and check event.target instead.

js
document.querySelector('#cart').addEventListener('click', (e) => {
  if (e.target.matches('.remove-btn')) {
    e.target.closest('.item').remove();
  }
});

This is event delegation. One listener handles all current and future children. Faster, simpler, less memory.

Why React exists

The above is fine for small pages. For a Zomato-scale app with thousands of dynamic pieces, manually tracking which DOM nodes need updating gets brutal. React (and Vue and Svelte) let you describe what the UI should look like for a given state, and the framework does the DOM diff for you.

Frameworks are not magic. They are just very good at the boring DOM work you would otherwise do by hand.

Quick recap

  • querySelector to find, addEventListener to react, createElement plus appendChild to build.
  • textContent is safe. innerHTML is XSS-shaped if you are not careful.
  • One delegated listener beats a hundred small ones.

Last lesson: modules and what "ship to production" actually means.

Free tools you can use while you learn

Chai0/1 done

Watching quietly. Tap me if you want a tip.

JS Playground
console
// hit run to see output

Try this (0 of 1 done)

  1. 1

    Show that textContent is safer than innerHTML by trying to inject "<script>".

    show answer
    fakeEl.textContent = '<script>alert(1)</script>';
    console.log(fakeEl.textContent);
    // renders as plain text, no script execution