Suggest an editImprove this articleRefine the answer for “What is the DOM?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**DOM (Document Object Model)** is a live tree of objects the browser builds from HTML in memory, giving JavaScript a way to read and change page content without reloading. ```javascript const para = document.getElementById('demo'); para.textContent = 'Updated!'; // changes text, triggers repaint console.log(para.nodeName); // "P" ``` **Key point:** HTML is a static file; the DOM is the live, mutable version of it in memory.Shown above the full answer for quick recall.Answer (EN)Image**DOM (Document Object Model)** is a live tree of objects the browser builds from an HTML document in memory, letting JavaScript read, modify, add, or remove nodes without reloading the page. ## Theory ### TL;DR - The DOM is what the browser builds from your HTML and keeps in memory as a mutable object tree, not a static file - Analogy: HTML is the printed recipe; the DOM is the chef's live workspace where ingredients can be swapped or added mid-cook - Main difference: HTML is parsed once and lives on disk; the DOM lives in memory and changes constantly - Use raw DOM APIs for small scripts and extensions; switch to React or Preact when updates become frequent or complex ### Quick Example ```html <!DOCTYPE html> <html> <body> <p id="demo">Original text</p> <script> const para = document.getElementById('demo'); para.textContent = 'DOM changed this!'; // No reload needed console.log(para.nodeName); // "P" </script> </body> </html> ``` `getElementById` returns a live node object. Setting `textContent` immediately triggers a repaint in the browser's rendering pipeline. That's the whole loop. ### HTML vs the DOM HTML is a static markup file. The browser parses it once and builds the DOM tree in memory. After that, the original file is irrelevant: what the user sees is the DOM. That's why JavaScript can change page content without touching the source file. The DOM gives you standardized node objects (`ElementNode`, `TextNode`, `CommentNode`) with methods like `appendChild()`. Calling those methods tells the browser to update what's on screen automatically. ### Node Types Every item in the DOM tree is a node. Four types come up constantly: - **Element node** (`<div>`, `<p>`, etc.) - nodeType: 1 - **Text node** (actual text content inside tags) - nodeType: 3 - **Comment node** (HTML comments) - nodeType: 8 - **Document node** (the root, `document`) - nodeType: 9 This matters when iterating. `childNodes` returns all types including text nodes from whitespace between tags. `children` returns element nodes only. Mix them up and you'll hit a `Cannot set properties of undefined` error on a whitespace node. ### When to Use - Form validation or class toggling - raw DOM APIs are fine - Browser extensions or bookmarklets - no framework available anyway - Quick UI prototypes - skip the build setup entirely - Heavy or frequent updates, like rendering 1000 list items on user input - switch to a [virtual DOM library like React](/questions/what-is-react); its reconciler batches DOM changes and minimizes reflows ### How the Browser Handles DOM Mutations Chrome's Blink engine stores the DOM as C++ `Node` objects exposed to JavaScript via WebIDL bindings. When you call `querySelector()`, V8 traverses the tree depth-first in O(n) worst case. A mutation like `appendChild()` marks the affected branch as dirty, which queues a style recalculation, layout pass (reflow), paint, and compositing. Grouping mutations inside `requestAnimationFrame` collapses those passes into one and is how you stay at 60fps with DOM-heavy code. Node.js has no DOM. For testing HTML parsing in Node, use `jsdom`. ### Common Mistakes **Mistake 1: `innerHTML` with user input** ```javascript // Wrong: parses and executes markup element.innerHTML = userInput; // <script>alert('XSS')</script> runs // Correct: treats everything as plain text element.textContent = userInput; ``` In code reviews, I see `innerHTML = userInput` far more often than I'd like. It usually appears in deadline code where someone needed it to work fast and didn't think about where the input came from. Use `textContent` or `createTextNode()` for anything user-supplied. **Mistake 2: Not checking for null** ```javascript // Wrong const el = document.getElementById('nonexistent'); // null el.textContent = 'hi'; // TypeError: Cannot set property // Correct if (el) el.textContent = 'hi'; ``` `getElementById` returns `null` when nothing matches. One unchecked result crashes the whole script. **Mistake 3: Mutating a live collection during iteration** ```javascript // Wrong: getElementsByClassName returns a live HTMLCollection const items = document.getElementsByClassName('old-item'); Array.prototype.forEach.call(items, item => item.remove()); // skips every other item // Correct: convert to a static array first Array.from(document.getElementsByClassName('old-item')).forEach( item => item.remove() ); ``` `getElementsByClassName` and `getElementsByTagName` return live collections. Removing an element shifts the indices, so the loop skips the next sibling every time. `querySelectorAll` returns a static snapshot and does not have this problem. **Mistake 4: `children` vs `childNodes`** ```javascript // Works: children contains element nodes only parent.children[0].style.color = 'red'; // May crash: childNodes[0] could be a whitespace TextNode parent.childNodes[0].style.color = 'red'; // Cannot set properties of undefined ``` **Mistake 5: Forgetting to remove event listeners before removing the node** ```javascript const btn = document.createElement('button'); btn.addEventListener('click', handler); document.body.appendChild(btn); btn.remove(); // listener stays in memory // Correct btn.removeEventListener('click', handler); btn.remove(); ``` In single-page apps where elements are created and destroyed frequently, orphaned listeners add up into a real memory leak. ### Real-World Usage - **React**: `ReactDOM.render()` translates JSX into actual DOM mutations via `createElement` and `appendChild` - **jQuery** (legacy): `$(el).find()` wraps `querySelectorAll` with cross-browser compatibility - **Puppeteer**: `page.$eval()` runs JavaScript against the live DOM for end-to-end tests - **WordPress / SSR apps**: server renders HTML, then client JavaScript hydrates the DOM for interactivity ### Follow-Up Questions **Q:** What is the difference between `textContent` and `innerHTML`? **A:** `textContent` sets plain text and escapes everything. `innerHTML` parses the string as markup and will execute scripts if you're not careful. For any user-supplied data, always use `textContent`. **Q:** How does `querySelector` traverse the tree? **A:** Depth-first, pre-order, starting from the document root. It stops at the first match, which makes it faster than `querySelectorAll` when you only need one element. **Q:** What is the difference between the real DOM and the [virtual DOM](/questions/what-is-virtual-dom)? **A:** The real DOM is the actual browser tree. The virtual DOM is a plain JavaScript object that mirrors it. React diffs the old and new virtual trees, then applies only the minimal set of changes to the real DOM. **Q:** What node types exist? **A:** The main ones are Element (nodeType 1), Text (nodeType 3), Comment (nodeType 8), and Document (nodeType 9). Check with `node.nodeType`. **Q (senior level):** Walk through what Blink does when you call `appendChild()`. **A:** The node is inserted into the C++ tree. That marks the affected branch dirty, scheduling a style recalculation, then layout (reflow to compute geometry), then paint (draw pixels), then compositing. Grouping multiple mutations before the next frame using `requestAnimationFrame` merges those passes into one, which is how DOM-heavy code stays at 60fps. ## Examples ### Basic: Changing text content ```html <!DOCTYPE html> <html> <body> <p id="demo">Original text</p> <script> const para = document.getElementById('demo'); para.textContent = 'Updated by JS'; // triggers repaint console.log(para.nodeName); // "P" </script> </body> </html> ``` Grabbing a node by ID and updating its text is the most common DOM operation. No page reload, no framework needed. ### Intermediate: Building a live to-do list ```html <!DOCTYPE html> <html> <body> <ul id="todos"></ul> <input id="task" placeholder="Task name" /> <button id="add">Add</button> <script> const ul = document.getElementById('todos'); const input = document.getElementById('task'); document.getElementById('add').addEventListener('click', () => { const li = document.createElement('li'); li.textContent = input.value; // textContent keeps this XSS-safe ul.appendChild(li); input.value = ''; }); </script> </body> </html> ``` This is essentially what TodoMVC does in its vanilla JS implementation. `createElement` builds the node, `appendChild` inserts it, and `textContent` keeps it safe. React's `useState` + JSX does the same thing under the hood, just with a diff step before touching the real DOM.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.