- API to interact with a markup document, e.g. HTML, to change content, structure and styling programmatically
- language independent, but mainly used with JS
- represents document as logical structure, everything in document is a node, can visualize as tree-like structure, often refers by “DOM” to this “DOM tree”
- nodes are represented as objects, can be manipulated via their properties
- “The DOM is one of the worst APIs ever imagined.” - Douglas Crockford
- beware: minimize changes to DOM because blocks main thread, e.g. add single fragment with nodes to DOM, instead of each node individually ⚠️
document object
- represents document
- entry point to DOM, all nodes are descendants (as nodes) of it, is the main node
(“root node” is already used for<html>node) - beware: don’t confuse DOM tree of node objects with their inheritance chain, are completely seperate things
Document.dir: direction of reading, “ltr” or “rtl”Document.title: document title
Nodes
- everything in document is a node, i.e. (HTML) elements, text, comments, the doctype, and the document itself
- only element nodes, the document node, and the root document node (
<html>element) can have children, i.e. text and comment nodes are always leaf nodes - any whitespace after
<head>is a / part of a text node, usually not visible on page, dev tools also don’t show, shows between inline-block elements, can change using CSS propertywhite-space - when computing the DOM, the browser autocorrets sloppy HTML markup, e.g. adds missing
<html>,<head>,<body>,<tbody>, etc. - JS can create nodes and add them to the DOM, enables possibility to manipulate every part of web page
Node object type
- general node type, ancestor (as object) to all other node object types, inheritance chain
graph TD
A[Node] --> Text
A --> Comment
A --> B[Element]
A --> Document
A --> _1[...]
B --> SVGElement
B --> C[HTMLElement]
B --> _2[...]
C --> HTMLAnchorElement
C --> HTMLBodyElement
C --> HTMLInputElement
C --> _3[...]
- beware: don’t confuse DOM tree of node objects with their inheritance chain, are completely seperate things, e.g.
Documentinherits fromNode, but properties ofDocumentare only available to its single instancedocumentinstead of all objects that can be accessed throughdocument Node.nodeName: node name as stringNode.nodeType: node type as integerNode.nodeValue: node value as string or null
Node object types
| object type | nodeName |
nodeType |
nodeValue |
|---|---|---|---|
Element |
<tag name> | 1 | null |
Text |
#text | 3 | <content> |
Comment |
#comment | 8 | <content> |
Document |
#document | 9 | null |
DocumentType |
<doctype> | 10 | null |
DocumentFragment |
#document-fragment | 11 | null |
(this text uses abbreviation of first letter, e.g. “E” for Element, “DF” for DocumentFragment)
DocumentFragment: temporary container for transfering nodes to DOM, only its content is appended to DOM, is never part of DOM itself, its whole purpose is to transfer nodes- several mixins are implemented in node types that share functionality
ParentNodein E, D, DFNonElementParentNodein D, DFChildNodein E, C, T, DTNonDocumentTypeChildNodein E, C, T
- collection objects:
NodeList: array-like object, has.forEach()HTMLCollection: array-like object, has no.forEach(), usefor..ofto iterate
DOM tree
Example HTML file
<!-- test -->
<!DOCTYPE html>
<html>
<head></head>↵
<body>↵
␣␣<p>Hello World!</p>↵
</body>
</html>
Example DOM tree
nodeName
- #document
- #comment
- html
- HTML
- HEAD
- #text
- BODY
- #text
- P
- #text
- #text
- HEAD
nodeValue
- null
- ␣test␣
- null
- null
- null
- ↵
- null
- ↵␣␣
- null
- Hello World!
- ↵
Walking the DOM
(in following chapters for readability names only “object type” instead of correctly writing “instance of object type”, e.g. ”Node” instead of “instance of Node“)
Nodes
Node.childNodes: liveNodeListof childNodes, (Nodecan only beParentNode)Node.firstChild: first childNode, first item ofNode.childNodes, (Nodecan only beParentNode)Node.lastChild: last childNode, last item ofNode.childNodes, (Nodecan only beParentNode)Node.nextSibling: next adjacentNodeNode.previousSibling: previous adjacentNodeNode.parentNode: parentNode, (can only beParentNode)
Elements
ParentNode.children: liveHTMLCollectionof childElementsParentNode.firstElementChild: first childElement, first item ofParentNode.childrenParentNode.lastElementChild: last childElement, last item ofParentNode.childrenNonDocumentTypeChildNode.nextElementSibling: next adjacentElementNonDocumentTypeChildNode.previousElementSibling: previous adjacentElementNode.parentElement: parentElement
Special nodes
Document.doctype: the single instance ofDocumentTypeDocument.documentElement: the single instance ofHTMLHtmlElementDocument.head: the single instance ofHTMLHeadElementDocument.body: the single instance ofHTMLBodyElement
Special elements
Document.forms:HTMLCollectionof allHTMLFormElements, 🚫Document.images:HTMLCollectionof allHTMLImageElements, 🚫Document.links:HTMLCollectionof allHTMLAnchorElementandHTMLAreaElements, 🚫Document.scripts:HTMLCollectionof allHTMLScriptElements, 🚫DocumentOrShadowRoot.styleSheets:StyleSheetListof allCSSStyleSheets, 🚫
Searching the DOM
ParentNode.querySelector(): first matching descendantElement, staticParentNode.querySelectorAll():NodeListof all descendantElements, staticParentNode.getElementsByTagName(): liveHTMLCollectionof matching descendantElements, *ParentNode.getElementsByClassName(): liveHTMLCollectionof matching descendantElements, *ParentNode.getElementsByName(): liveHTMLCollectionof matching descendantElements, 🚫NonElementParentNode.getElementById(): single matchingElementElement.closest(): first matching ancestorElementincluding itselfElement.matches(): ifElementitself matches selector, boolean
(* as of Jan 2020 actually still implemented seperately in E, D, DF instead via mixin like written here)
- beware:
.getElementById()is singular “Element” without “s” and lowercase “Id” with small “d” - beware: only the
.querySelector/All()methods takes a CSS selector as string, the other ones just the name as string!
Modifying the DOM
Creating nodes
Document.createComment(): creates newCommentDocument.createDocumentFragment(): creates newDocumentFragmentDocument.createElement(): creates newHTMLElementDocument.createTextNode(): creates newText, (note the extra “Node” suffix)
Moving nodes
Node.appendChild(): adds node to end of child nodes, if node is already part of DOM just moves it, if node is DF appends only its content, (Nodecan only be validParentNode)Node.insertBefore(): inserts node before reference node in child nodes, if node is already part of DOM just moves it, if node is DF appends only its content, (Nodecan only be validParentNode)Node.cloneNode(): creates duplicate node with or without its content, with all attributes, i.e. beware of duplicate IDs, inline event listeners, etc.Node.removeChild(): removes node from child nodes, returns reference to removed node, (Nodecan only be validParentNode)Node.replaceChild(): replaces node in child nodes, returns reference to replaced node, (Nodecan only be validParentNode)
Moving nodes (new)
ParentNode.append(): likeNode.appendChild(), but newerParentNode.prepend(): likeParentNode.append(), but to beginningChildNode.before(): inserts node(s) before this node within parentChildNode.after(): inserts node(s) after this node within parentChildNode.remove(): removes this node from parentChildNode.replaceWith(): replaces this node within parent with node(s)
Modifying nodes
Attributes
Element.attributes: liveNamedNodeMapcontaining all attributes, 👎Element.hasAttribute/s(): if contains specified / any attribute, booleanElement.getAttributeNames(): attribute names, array of strings, 👍Element.getAttribute(): attribute value, string, 👍Element.setAttribute(): sets or creates attribute, returns undefined, 👍Element.removeAttribute(): removes attribute, returns undefined, 👍
Classes
Element.classList: liveDOMTokenListcontaining all class attribute values, modify using itsadd()/remove()methods, 👍Element.className: entire class attribute value, string, (should have been called “class” but it was a reserved keyword at the time it was created), 👎
Content
Element.innerHTML: serialized HTML of content, staticElement.outerHTML: serialized HTML of element and content, staticNode.textContent: concatenation of all descendantTextnodes, i.e. including text in hidden elements,<style>and<script>elements, etc.HTMLElement.innerText: only rendered text content
Special attributes
HTMLElement.dir: direction of reading, “lrt” or “rtl” or “auto”HTMLElement.hidden: ifhiddenattribute is present, booleanHTMLElement.style: inline style, returnsCSSStyleDeclaration, set/get its properties using camelCasing instead of hyphen, e.g.myelement.style.color, value must be string with unitHTMLElement.title: title attribute value
Notes on DOM methods
ParentNode.querySelector/All()is only sugar onNonElementParentNode.getElementsBy*(), less performant, also not liveNode.appendChild()forces repaint ifNodeis part of the DOM, i.e. for appending lots of nodes in a loop better append them to a DF first and then the DF once to DOM- create elements by setting HTML content of parent element with
Element.innerHTMLto template string, easier and faster than manually creating nodes, modifying their content and attributes, and appending them to element
Resources
- MDN - as usual
- The Modern Javascript Tutorial - as usual
- Ian Hickson - Live DOM Viewer