The Document Object Model (DOM) is a programming interface for HTML and XML documents. It represents the page as a tree of nodes that JavaScript can read and modify — enabling dynamic, interactive web pages.

The DOM Tree

Every HTML element becomes a node in the tree:

  <html>
  <body>
    <h1 id="title">Hello</h1>
    <p class="text">World</p>
  </body>
</html>
  
  • document is the entry point
  • document.documentElement<html>
  • htmlbodyh1, p are child nodes
  • Text inside elements are text nodes

Node Types

Type Example
Element node <div>, <p>
Text node "Hello"
Comment node <!-- comment -->
Document node document

Selecting Elements

  // By ID (returns one element or null)
const title = document.getElementById('title');

// By class (live HTMLCollection — updates when DOM changes)
const paragraphs = document.getElementsByClassName('text');

// By tag name (live HTMLCollection)
const allP = document.getElementsByTagName('p');

// Modern selectors (recommended — static NodeList)
const el = document.querySelector('#title');
const all = document.querySelectorAll('.text');
  

querySelector returns first match; querySelectorAll returns all matches. Prefer these for flexibility and consistency.

Reading and Modifying Content

  const heading = document.querySelector('#title');

// Text content (safe — no HTML parsing)
heading.textContent = 'Hello, JavaScript!';

// Inner HTML (parses HTML — XSS risk with user input)
heading.innerHTML = '<em>Hello</em>, JavaScript!';

// Attributes
heading.setAttribute('class', 'highlight');
heading.getAttribute('id');
heading.removeAttribute('data-old');

// ClassList API
heading.classList.add('active');
heading.classList.remove('active');
heading.classList.toggle('active');
heading.classList.contains('active'); // boolean
heading.classList.replace('old', 'new');
  

Security: Always use textContent when inserting user-provided data. innerHTML executes embedded scripts if not sanitized.

Creating and Removing Elements

  const newP = document.createElement('p');
newP.textContent = 'A new paragraph';
newP.classList.add('text');

document.body.appendChild(newP);
document.body.prepend(newP);        // insert first child
newP.before(anotherElement);        // sibling before
newP.after(anotherElement);         // sibling after

const container = document.querySelector('#container');
container.insertBefore(newP, container.firstChild);

newP.remove();                      // modern
// container.removeChild(newP);     // older syntax
  

Document Fragments

Batch DOM updates for performance:

  const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    const li = document.createElement('li');
    li.textContent = `Item ${i}`;
    fragment.appendChild(li);
}
list.appendChild(fragment); // single reflow
  

Traversing the DOM

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

node.parentElement;
node.parentNode;           // includes document as parent of html
node.children;             // element children only
node.childNodes;           // all node types including text
node.nextElementSibling;
node.previousElementSibling;
node.firstElementChild;
node.lastElementChild;
node.closest('.container'); // nearest ancestor matching selector
  

DOMContentLoaded vs load

  document.addEventListener('DOMContentLoaded', () => {
    // HTML parsed, DOM ready — safe to query elements
});

window.addEventListener('load', () => {
    // All resources loaded (images, stylesheets)
});
  

Place scripts at end of <body> or use defer on <script defer src="app.js">.

Complete Example — Todo List

  <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DOM Example</title>
    <style>
        .done { text-decoration: line-through; color: gray; }
    </style>
</head>
<body>
    <h1 id="title">Todo List</h1>
    <ul id="list"></ul>
    <input id="input" type="text" placeholder="New task">
    <button id="addBtn">Add</button>

    <script>
        const list = document.querySelector('#list');
        const input = document.querySelector('#input');
        const addBtn = document.querySelector('#addBtn');

        function addTask(text) {
            const li = document.createElement('li');
            li.textContent = text;
            li.addEventListener('click', () => li.classList.toggle('done'));
            list.appendChild(li);
        }

        addBtn.addEventListener('click', () => {
            const text = input.value.trim();
            if (text) {
                addTask(text);
                input.value = '';
                input.focus();
            }
        });

        input.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') addBtn.click();
        });
    </script>
</body>
</html>
  

Best Practices

  1. Cache DOM references — don’t query inside tight loops
  2. Batch updates with DocumentFragment or display: none during bulk changes
  3. Use textContent for user data — prevent XSS
  4. Prefer classList over string manipulation of className
  5. Use event delegation for dynamic lists (see Events chapter)

Common Mistakes

Mistake Fix
Script runs before DOM exists Use DOMContentLoaded or defer
innerHTML with user input Use textContent or sanitize
Live collection surprise getElementsByClassName updates live — copy to array if iterating while modifying
Forgetting preventDefault on forms See Events chapter

Troubleshooting

querySelector returns null

  • Element doesn’t exist yet — wait for DOMContentLoaded
  • Typo in selector — verify in DevTools Console: document.querySelector('#myId')

Changes not visible

  • Modified wrong element — log element to confirm reference
  • CSS hiding element — check computed styles

Performance slow with many nodes

  • Use DocumentFragment; virtual DOM libraries (React) for complex UIs

The DOM is JavaScript’s bridge to the page — master selection, mutation, and traversal for interactive applications.