There is a traversal API that allows us to find structurally related elements of the document; it treats a document as a tree of element objects. We can access these traversal API by referring to a specific element's parent, children, and siblings properties. This traversal API ignores the text nodes; although they are part of the document.
Let's get started with these properties:
parentNode { Node }
The parent of the element.
children { HTMLCollection }
The HTML collection contains the element children of the elemen. It includes only element nodes; excludes non-element children like text nodes and comment nodes.
childElementCount { number }
The number of element children; same as children.length.
firstElementChild { Node }
Refer to the first element child.
lastElementChild { Node }
Refer to the last element child.
nextElementSibling { Node }
Refer to the sibling element immediately after the element.
previousElementSibling {Node }
Refer to the sibling element immediately before the element.
Usage
document.children[0]
document.firstElementChild.nextElementSibling
document.lastElementChild.parentNode
document.childElementCount
Ways of Traversal
There are two ways to traverse every element in the document. One is recursively doing a depth-first traversal of a document. Another one is the breath first search using the queue data structure. They both have a time complexity of O(N) and space complexity of O(N).
body
span
div
img
a
nav
Depth First Search (DFS)
/**
* @callback callbackFunc
* @property {Node}
*/
/**
* @param {Node} element
* @param {callbackFunc} callback
*/
const dfs = (element, callback) => {
// Do something with the element
callback(element);
// Traverse the children
for (const child of element.children) {
dfs(child, callback);
}
};
In the example DOM, this would run in preorder depth first search: body, div, a, span, nav, img.
Breath First Search (BFS)
const bfs = (element, callback) => {
const queue = [element];
while (queue.length) {
// Get the next element
const node = queue.shift();
// Do something with the element
callback(node);
// Find the element's children
for (const child of node.children) {
// Add the child to the queue
queue.push(child);
}
}
};
In the example DOM, this would run in breath first search: body, div, span, img, a, nav.
Different ways to traverse an element's children
for (const child of element.children) {
// Do something with the child
}
const child = element.firstElementChild;
while (child !== null) {
// Do something with the child
child = child.nextElementSibling;
}
Documents As Trees of Nodes
There is a separate set of properties defined on all Node objects that you can utilize when you don't want to ignore the Text nodes and Comment nodes while traversing a document or element.
Let's get started with these properties:
parentNode { Node }
The parent of the node.
childNodes { NodeList }
A NodeList contains all the children (not just element children).
firstChild { Node }
The first child node of the node.
lastChild { Node }
The last child node of the node.
nextSibling { Node }
The next sibling node of the node.
previousSibling { Node }
The previous sibling node of the node.
nodeValue { string }
The text content of a text or comment node.
nodeName { string }
The uppercase html tag name of the element.
NodeType { number }
A number refer to a specific kind of node.
Node.ELEMENT_NODE ( 1 )
An element node like p
or div
.
Node.ATTRIBUTE_NODE ( 2 )
An attribute of an element.
Node.TEXT_NODE ( 3 )
The text inside an element.
Node.CDATA_SECTION_NODE ( 4 )
A CDATASection like <[!CDATA[ ... ]]>
Node.PROCESSING_INSTRUCTION_NODE ( 7 )
An element node like p
or div
.
Node.COMMENT_NODE ( 8 )
A comment node like <!-- ... -->
Node.COMMENT_NODE ( 9 )
A document node.
Node.DOCUMENT_TYPE_NODE ( 10 )
A document type node, like <!DOCTYPE html>.
Node.DOCUMENT_FRAGMENT_NODE ( 11 )
A document fragment node.
Example
/**
* Get text content of a element recursively
* @param {Node} element
* @returns {string}
*/
const getText = element => {
let text = '';
// Traverse each child
for (const node of element.childNodes) {
// Check the node type
switch (node.nodeType) {
case Node.TEXT_NODE: // 3
text += node.nodeValue;
break;
case Node.ELEMENT_NODE: // 1
text += getText(element);
}
}
return text;
};
There is another way of traversing the child nodes.
let child = element.firstChild;
while (child !== null) {
// Do something with child
child = child.nextSibling;
}