The World
of DOM
An interactive deep-dive into the Document Object Model — how browsers represent HTML, and how you control it with JavaScript.
Start Learning →<head>
<title>…</title>
</head>
<body>
<header>…</header>
<main>
<p>Hello World</p>
</main>
<footer>…</footer>
</body>
</html>
Core Concept
When a browser loads an HTML file, it parses the markup and constructs the Document Object Model — a structured, tree-shaped object in memory.
Every element, attribute, and piece of text becomes a Node in this tree. JavaScript can then read and manipulate these nodes, making the page dynamic and interactive.
Key Facts
- DOM is a W3C Standard — not a JS feature
- It is live — changes to HTML reflect instantly
- Accessed via the global document object
- Nodes have types: Element, Text, Comment…
- The root is always document
- Browsers expose it via JavaScript APIs
console.log(document); console.log(document.nodeType); // 9 = Document console.log(document.nodeName); // "#document" console.log(document.URL); // current page URL console.log(document.title); // page title console.log(document.body); // <body> element
Node Types
- 1 — Element Node (e.g. <div>)
- 2 — Attribute Node
- 3 — Text Node ("hello")
- 8 — Comment Node
- 9 — Document Node
- 11 — DocumentFragment
⚡ Quick Check
Which global object gives you access to the DOM in JavaScript?
Tree Structure
The DOM forms an upside-down tree: the root (document) is at the top, branching down into <html>, then <head> and <body>, and further into nested elements.
Each node knows its parent, its children, and its siblings.
const body = document.body; // Children body.children // HTMLCollection body.childNodes // NodeList (incl. text) body.firstChild // first node body.lastChild // last node body.firstElementChild // Parent body.parentNode // <html> body.parentElement // Siblings body.previousSibling body.nextSibling body.nextElementSibling
⚡ Quick Check
What property gives you only element children (no text nodes) of a node?
// By ID — returns one element document.getElementById('hero') // By class — live HTMLCollection document.getElementsByClassName('lesson') // By tag — live HTMLCollection document.getElementsByTagName('p') // CSS selector — first match document.querySelector('.lesson h2') // CSS selector — all matches (static NodeList) document.querySelectorAll('button[data-type]')
Which to Use?
- Use getElementById for unique elements
- Use querySelector for any CSS selector
- Use querySelectorAll for multiple matches
- querySelector returns
nullif not found - Can call from any element, not just
document - Modern code prefers
querySelector*
🎮 Selector Game
Type a CSS selector below and see which elements in the sample HTML get highlighted. Try: .card, h4, #intro, [data-type]
Introduction
First paragraph
Second paragraph
Article
A span insideLast paragraph
⚡ Quick Check
Which method returns a static NodeList of all matching elements?
const el = document.querySelector('#demo'); // Text content (safe, no HTML) el.textContent = 'Hello World'; // HTML content el.innerHTML = '<strong>Bold text</strong>'; // Attributes el.setAttribute('data-id', '42'); el.getAttribute('data-id'); // '42' el.removeAttribute('data-id'); // Classes el.classList.add('active'); el.classList.remove('hidden'); el.classList.toggle('open'); el.classList.contains('active'); // true // Inline styles el.style.color = '#00e5ff'; el.style.backgroundColor = 'black';
textContent vs innerHTML
textContent sets raw text — HTML tags are treated as literal text. It's safe.
innerHTML parses the string as HTML and can inject elements. ⚠ Never use innerHTML with user-supplied data — it opens the door to XSS attacks.
Live Demo
⚡ Quick Check
Which method adds a CSS class to an element without replacing existing classes?
const btn = document.querySelector('#myBtn'); // Add a listener btn.addEventListener('click', (event) => { console.log('Clicked!', event); event.preventDefault(); // stop default action }); // Remove a listener function handler(e) { ... } btn.addEventListener('click', handler); btn.removeEventListener('click', handler); // Common events 'click' 'dblclick' 'mouseenter' 'keydown' 'keyup' 'keypress' 'submit' 'change' 'input' 'focus' 'blur' 'scroll'
The Event Object
Every listener receives an Event object with useful properties:
- event.target — element that triggered it
- event.type — event name ("click")
- event.key — for keyboard events
- event.clientX/Y — mouse coords
- event.preventDefault() — cancel default
- event.stopPropagation() — stop bubbling
Event Bubbling & Capturing
When an event fires on an element, it bubbles up through all its ancestors. A click on a <span> inside a <div> will also fire the <div>'s click listener.
Use event.stopPropagation() to prevent bubbling, or use event delegation: attach a single listener to a parent element and check event.target.
⚡ Quick Check
What does event.stopPropagation() do?
const el = document.querySelector('#myEl'); // Going UP el.parentNode el.parentElement el.closest('.container') // nearest ancestor matching selector // Going DOWN el.children // element children only el.childNodes // all nodes (incl. text) el.firstElementChild el.lastElementChild // Going SIDEWAYS el.previousElementSibling el.nextElementSibling // Useful checks el.contains(otherEl) // true if descendant el.matches('.card') // true if matches selector
Interactive Traversal
Click a node to select it. Then use the buttons to navigate to relatives.
⚡ Quick Check
Which method finds the nearest ancestor matching a CSS selector?
// Create elements const div = document.createElement('div'); const txt = document.createTextNode('Hello'); div.textContent = 'New element!'; div.className = 'my-class'; // Insert into DOM parent.appendChild(div); // at the end parent.prepend(div); // at the start parent.insertBefore(div, ref); // before ref ref.after(div); // after ref ref.before(div); // before ref // Modern: insertAdjacentHTML el.insertAdjacentHTML('beforeend', '<p>Hi</p>'); // Removing el.remove(); // remove self parent.removeChild(child); // remove child el.replaceWith(newEl); // replace // Cloning el.cloneNode(true); // deep clone
DocumentFragment
When inserting many elements, use a DocumentFragment — a lightweight container that lives outside the DOM. Build your structure in the fragment, then insert it all at once. This causes only one reflow instead of many.
const frag = document.createDocumentFragment(); for (let i = 0; i < 100; i++) { const li = document.createElement('li'); li.textContent = `Item ${i}`; frag.appendChild(li); } list.appendChild(frag); // one DOM update
Live Demo
⚡ Quick Check
Which method removes an element from the DOM without needing a reference to its parent?
🎯 Your Mission
- Read the input value when the button is clicked
- Create a new <div> with the todo text
- Add a delete button inside each todo
- Append the todo to the list container
- Bonus: toggle a "done" class on click
⚡ Final Quiz
Which approach is most performant when inserting 1000 list items into the DOM?