JavaScript DOM Navigation

The DOM (Document Object Model) represents an HTML document as a tree of nodes. DOM navigation allows you to traverse this tree, moving between parent, child, and sibling nodes to access and manipulate elements.

parentNode and parentElement

The parentNode property returns the parent of a node. parentElement is similar but returns null if the parent is not an element node (e.g., if the parent is the document).

Navigating to Parent
// Given HTML: <div id="parent"><p id="child">Hello</p></div>

// const child = document.getElementById('child');
// const parent = child.parentNode;
// console.log(parent.id);  // 'parent'

// parentNode vs parentElement
// document.documentElement.parentNode  -> #document
// document.documentElement.parentElement -> null

// Simulating DOM navigation
const dom = {
  tag: 'div', id: 'parent',
  children: [
    { tag: 'p', id: 'child', text: 'Hello', parent: 'div#parent' }
  ]
};

console.log('Child: ' + dom.children[0].tag + '#' + dom.children[0].id);
console.log('Parent: ' + dom.children[0].parent);
console.log('parentNode returns any node type');
console.log('parentElement returns only element nodes');

childNodes and children

childNodes returns ALL child nodes (including text and comment nodes). children returns only element child nodes. Use children for most practical purposes.

Accessing Children
// HTML: <ul id="list"><li>One</li><li>Two</li><li>Three</li></ul>

// const list = document.getElementById('list');
// console.log(list.childNodes.length);   // May include text nodes
// console.log(list.children.length);      // Only element nodes

// Simulating children access
const listChildren = ['One', 'Two', 'Three'];

console.log('children.length: ' + listChildren.length);
listChildren.forEach(function(item, index) {
  console.log('Child ' + index + ': ' + item);
});

console.log('\nchildNodes includes text/comment nodes');
console.log('children includes only element nodes');

firstChild, lastChild, firstElementChild, lastElementChild

firstChild/lastChild return the first/last child node (any type). firstElementChild/lastElementChild return only element nodes, skipping text and comment nodes.

First and Last Children
// HTML: <div id="box"> <span>First</span> <span>Last</span> </div>

// const box = document.getElementById('box');
// box.firstChild       -> text node (whitespace)
// box.firstElementChild -> <span>First</span>
// box.lastChild        -> text node (whitespace)
// box.lastElementChild  -> <span>Last</span>

const elements = ['Header', 'Content', 'Footer'];

console.log('firstElementChild: ' + elements[0]);
console.log('lastElementChild: ' + elements[elements.length - 1]);
console.log('\nfirstChild may return whitespace text nodes');
console.log('firstElementChild always returns an element');

nextSibling and previousSibling

nextSibling/previousSibling return the next/previous node (any type). nextElementSibling/previousElementSibling return only element siblings.

Navigating Siblings
// HTML: <p>First</p> <p id="middle">Middle</p> <p>Last</p>

// const middle = document.getElementById('middle');
// middle.previousElementSibling  -> <p>First</p>
// middle.nextElementSibling      -> <p>Last</p>

// Simulating sibling navigation
const siblings = ['First', 'Middle', 'Last'];
const currentIndex = 1;  // 'Middle'

console.log('Current: ' + siblings[currentIndex]);
console.log('Previous sibling: ' + siblings[currentIndex - 1]);
console.log('Next sibling: ' + siblings[currentIndex + 1]);

// Walking through all siblings
console.log('\nAll siblings:');
siblings.forEach(function(s) {
  console.log('  - ' + s);
});

Node Types

Every DOM node has a nodeType property that identifies its type. Understanding node types helps when navigating and filtering nodes.

nodeTypeConstantDescription
1ELEMENT_NODEHTML element (<p>, <div>, etc.)
2ATTRIBUTE_NODEElement attribute (deprecated)
3TEXT_NODEText content
8COMMENT_NODEHTML comment
9DOCUMENT_NODEThe document itself
11DOCUMENT_FRAGMENT_NODEA lightweight document
Checking Node Types
// Checking node types
// const el = document.getElementById('test');
// console.log(el.nodeType);      // 1 (Element)
// console.log(el.nodeName);      // 'DIV'
// console.log(el.nodeValue);     // null (for elements)

// Filtering only element nodes from childNodes
// const elementChildren = [];
// for (let i = 0; i < parent.childNodes.length; i++) {
//   if (parent.childNodes[i].nodeType === 1) {
//     elementChildren.push(parent.childNodes[i]);
//   }
// }

const nodeTypes = {
  1: 'Element',
  3: 'Text',
  8: 'Comment',
  9: 'Document'
};

Object.keys(nodeTypes).forEach(function(type) {
  console.log('nodeType ' + type + ': ' + nodeTypes[type]);
});
📝 Note: When navigating the DOM, prefer the element-specific properties (children, firstElementChild, nextElementSibling) over the generic node properties (childNodes, firstChild, nextSibling) to avoid accidentally selecting whitespace text nodes. This is a common source of bugs.
Exercise:
What is the difference between childNodes and children?
Try it YourselfCtrl+Enter to run
Click Run to see the output here.