Glossary

DOM

DOM stands for Document Object Model, the tree of objects a browser builds from an HTML document and exposes to JavaScript so code can read, add, move and remove parts of the page. It is defined by the WHATWG DOM Standard, a living standard that describes a platform-neutral model for events, aborting activities and node trees. When you call document.querySelector, you are using it.

How it works

The browser parses HTML into nodes that form a tree. Every node implements the Node interface, and most you work with are more specific types: Document, DocumentType, DocumentFragment, Element, CharacterData (text, comments, CDATA sections and processing instructions) and Attr. The Document sits at the root and gives you the entry points such as querySelector and createElement.

Each node has a numeric nodeType. The values defined by the standard are:

  • 1 for an element (ELEMENT_NODE)
  • 2 for an attribute (ATTRIBUTE_NODE)
  • 3 for text (TEXT_NODE)
  • 8 for a comment (COMMENT_NODE)
  • 9 for the document (DOCUMENT_NODE)
  • 10 for a doctype (DOCUMENT_TYPE_NODE)
  • 11 for a document fragment (DOCUMENT_FRAGMENT_NODE)

Changing the tree changes what the browser renders. The DOM is also the event system: you attach listeners to nodes with addEventListener, and events travel through the tree in a capture phase, at the target, then a bubble phase (constants 1, 2 and 3 in the standard).

document.body.innerHTML = '<ul id=l><li>one</li><li>two</li></ul>';
const l = document.getElementById('l');
const li = document.createElement('li');
li.textContent = 'three';
l.append(li);
console.log([l.children.length, l.outerHTML, l.nodeType, l.firstChild.firstChild.nodeType, document.nodeType].join(' | '));

Output from headless Chromium 141:

3 | <ul id="l"><li>one</li><li>two</li><li>three</li></ul> | 1 | 3 | 9

The list has three children after the append. The ul is node type 1, the text inside its first li is type 3, and the document is type 9.

Is the DOM the same as the HTML source?

No. The HTML source is text, and the DOM is the live object tree built from it, so the two can differ. Scripts can change the DOM after load, so the Elements panel in developer tools may show nodes that view-source does not. The DOM is also not part of the JavaScript language: In Node.js 22, typeof document is undefined unless you add a library such as jsdom.

Common pitfalls

  • Running before the tree exists: an inline script in the head that calls document.querySelector for an element further down returned null in a test. Move the script below the element, or run it once the DOM is ready.
  • Confusing children with childNodes: children holds only elements, while childNodes also holds text and comment nodes, including whitespace between tags. After setting innerHTML to two i elements separated by a space, children.length was 2 and childNodes.length was 3.
  • Using innerHTML with untrusted text: the string is parsed as HTML. Setting innerHTML to <b>x</b> created 1 child element, while textContent with the same string created 0. Use textContent for plain text, since markup in user input otherwise becomes real elements. See XSS.
  • Holding stale references: a node removed from the tree stays alive in your variable, but it no longer renders. Re-query after replacing a container's innerHTML, since all old child nodes are gone.
  • Expecting a live list from querySelectorAll: it returns a static NodeList, while getElementsByClassName returns a live collection. After appending one more matching div, the live collection reported 2 and the querySelectorAll result still held 1.

Related terms

  • Debounce — limits how often a DOM event handler runs
  • XSS — what happens when untrusted input reaches the DOM as markup
  • Canvas — a DOM element whose pixels are drawn by script, not by the node tree
  • Markdown — a text format often converted to HTML before it becomes DOM nodes

See also