Chapter 14 of 28 · JavaScript for Beginners — Complete Free Tutorial

The DOM: Selecting Elements

The browser turns your HTML into a tree of objects called the DOM (Document Object Model). JavaScript does not edit your HTML file — it edits this live tree, and the browser repaints. Every interactive feature starts by selecting elements. This chapter covers the modern selection API, traversal, and reading content and attributes safely.

The DOM tree

document
└─ html
   ├─ head
   └─ body
      ├─ h1#title
      └─ ul.tasks
         ├─ li.task  (first)
         └─ li.task

Nodes: elements, text, attributes. The global document object is your entry point — everything under it is queryable.

getElementById — the fastest single lookup

const title = document.getElementById('title');
console.log(title);            // → <h1 id="title">…</h1>
console.log(title.textContent);

querySelector / querySelectorAll — the modern workhorses

const first  = document.querySelector('.card');            // first match (CSS selector)
const all    = document.querySelectorAll('.card');          // ALL matches → static NodeList
const heading = document.querySelector('main > h1');
const byId   = document.querySelector('#submit');
const input  = document.querySelector('input[type="email"]');

console.log(all.length);
all.forEach((card) => card.classList.add('ready'));
  • Any CSS selector works: tags, classes, ids, attributes, combinators (the CSS selector chapter lists them).
  • querySelector returns the first match or null — always guard if the element may not exist.
  • querySelectorAll returns a static NodeList snapshot: elements added later are not included (call it again if the list changes). forEach works on it directly.

Legacy collections (still in old code)

document.getElementsByTagName('p');    // HTMLCollection — live
document.getElementsByClassName('card'); // live, updates as the page changes

“Live” collections auto-update but are array-like (no map). In new code, prefer querySelectorAll.

Element or NodeList — null checks

const el = document.querySelector('.maybe-missing');
if (el) {
  el.textContent = 'found';
}
// modern guard with optional chaining + nullish
document.querySelector('.other')?.classList.add('ok');

Reading and writing content

const p = document.querySelector('p');

console.log(p.textContent);    // plain text of all descendants (safe)
console.log(p.innerText);      // visible text only (respects styling — slower)
console.log(p.innerHTML);      // raw HTML markup ⚠️

p.textContent = 'New <safe> text';   // special chars are escaped automatically
p.innerHTML = '<strong>Bold</strong>'; // parses HTML ⚠️ never with user input

Security rule: only use innerHTML with HTML you wrote yourself. Inserting user data via innerHTML is the classic XSS vulnerability — prefer textContent (chapter 28 revisits this).

Traversing the tree

DirectionProperties
Upel.parentElement, el.closest('.card')
Downel.querySelector('span'), el.firstElementChild, el.lastElementChild, el.children
Acrossel.nextElementSibling, el.previousElementSibling
Allel.parentNode (may be non-element), el.childNodes (includes text nodes)
const li = document.querySelector('.tasks li:nth-child(2)');
console.log(li.previousElementSibling?.textContent);
console.log(li.parentElement.parentElement);       // up two levels

// closest: walk UP from any point (great for delegated clicks)
const btn = event.target.closest('.card .btn');

Attributes and dataset

const link = document.querySelector('a');

console.log(link.getAttribute('href'));
console.log(link.hasAttribute('target'));
console.log(link.getAttribute('aria-label'));

link.setAttribute('href', '/tutorials');
link.removeAttribute('target');

const div = document.querySelector('div[data-user-id="7"]');
console.log(div.dataset.userId);       // data-user-id → camelCase
div.dataset.loading = 'true';          // sets data-loading="true"

For built-in DOM properties (href, value, disabled) prefer the property; for custom data use dataset.

Checking and class control

const el = document.querySelector('.badge');

el.classList.add('highlight');
el.classList.remove('old');
el.classList.toggle('open');        // adds if missing, removes if present
el.classList.contains('open');      // → boolean
el.classList.replace('a', 'b');

console.log(el.matches('.badge'));   // does this element match?

classList beats className = '…' strings — it never clobbers other classes. Toggle patterns pair perfectly with CSS states: .open { display: block }.

Live vs static: a timing note

Selecting happens at the moment you call it. Scripts with defer (chapter 2) run after parsing, so the DOM exists. If unsure, wrap in DOMContentLoaded — otherwise querySelector silently returns null.

Common mistakes

  • Querying too early — null + TypeError on the next line.
  • Re-querying inside loops — store the reference: const list = document.querySelector('ul').
  • innerHTML for plain text — slower, unsafe; use textContent.
  • Assuming NodeList has array methods — no map/filter directly; spread it: [...document.querySelectorAll('x')] (though forEach works natively).
  • Confusing childNodes with children — the former includes whitespace text nodes.

Practice checklist

  1. Select the third <li> in a list using querySelector CSS — then via traversal from the first item.
  2. Toggle a .hidden class on a panel and prove classList.contains flips.
  3. Read data-role from an element with dataset and log it.
  4. Find the nearest ancestor matching .card from a deeply nested button using closest.

Selection finds elements — but content must change. Next: DOM Manipulation.

Quiz: The DOM: Selecting Elements

5 questions · 70% to pass

Frequently Asked Questions

JavaScript is the programming language of the web. It adds behaviour to pages: responding to clicks and input, validating forms, fetching data from APIs, updating the DOM without reloading, and much more. It also runs on servers (Node.js), in desktop and mobile apps, and in build tooling.

They are unrelated languages that share a similar name for historical marketing reasons. Java is a statically-typed compiled language; JavaScript is a dynamically-typed interpreted language designed for the browser. Knowing one does not imply knowledge of the other.

Helpful but not mandatory — HTML gives you the structure JavaScript manipulates and CSS the styling it toggles. If you know basic tags, classes and selectors, you have enough to start; the DOM chapters assume only that much.

Use const by default and let when the value must change. Avoid var entirely in modern code: it is function-scoped instead of block-scoped, hoists differently, and can create accidental globals. This course uses const and let throughout.

The Document Object Model is the browser’s in-memory tree of objects representing your HTML. JavaScript reads and modifies that tree — selecting elements, changing text, adding nodes, attaching event listeners — and the browser re-renders the page to match.

== compares after converting types (so 5 == "5" is true), while === requires the same value AND the same type (5 === "5" is false). Loose equality has famously confusing rules — use === and !== everywhere unless you have a specific reason not to.

JavaScript is single-threaded, so long-running work (timers, fetches, events) is handled by the browser and its callbacks are queued. The event loop moves queued callbacks onto the call stack only when it is empty, with promise callbacks (microtasks) taking priority over timers. It is why asynchronous code never freezes the page.

A Promise is an object representing a future value — pending, fulfilled or rejected — that can be chained and combined (Promise.all). async/await is syntax sugar over promises that lets asynchronous code read top-to-bottom with ordinary try/catch error handling.

Yes. Frameworks are built on exactly these fundamentals — components are functions, state is your variable discipline, effects are events, rendering is DOM work. Developers who learn vanilla first understand what the framework is doing and debug far faster.

Type out the examples instead of copying, build small end-to-end projects (expense tracker, weather app, quiz), handle every UI state (loading, empty, success, error), and revisit fundamentals while reading real code. Consistency beats volume — a little most days outpaces occasional marathons.