DOM Manipulation and Selection APIs

1. Document and Element Selection Methods

Method Syntax Description Browser Support
getElementById document.getElementById(id) Returns element with specified ID. Returns null if not found. Most performant selector. All Browsers
getElementsByClassName element.getElementsByClassName(names) Returns live HTMLCollection of elements with specified class names (space-separated). Updates automatically when DOM changes. All Browsers
getElementsByTagName element.getElementsByTagName(name) Returns live HTMLCollection of elements with specified tag name. Use "*" for all elements. All Browsers
querySelector element.querySelector(selector) Returns first element matching CSS selector. Returns null if no match. Static snapshot. All Browsers
querySelectorAll element.querySelectorAll(selector) Returns static NodeList of all matching elements. Use forEach() to iterate. Non-live collection. All Browsers
getElementsByName document.getElementsByName(name) Returns live NodeList of elements with specified name attribute. Primarily for form elements. All Browsers
closest element.closest(selector) Traverses element and ancestors, returns first matching element. Returns null if no match. Modern Browsers
matches element.matches(selector) Returns boolean indicating if element matches CSS selector. Useful for event delegation. Modern Browsers

Example: Selection methods comparison

// Get by ID - fastest
const header = document.getElementById("header");

// Query selector - flexible CSS selectors
const firstButton = document.querySelector(".btn-primary");
const allButtons = document.querySelectorAll("button.active");

// Live collections - auto-update
const items = document.getElementsByClassName("item");
console.log(items.length); // 5
document.body.appendChild(newItem); // Adds .item element
console.log(items.length); // 6 - auto-updated

// Closest for ancestor lookup
const card = button.closest(".card");

// Matches for filtering
if (element.matches("[data-active='true']")) {
  // Element has attribute
}
Note: querySelector returns static NodeList while getElementsByClassName returns live HTMLCollection. Live collections automatically update when DOM changes. Use querySelector for static snapshots to avoid performance issues.

2. DOM Node Creation and Manipulation

Method Syntax Description Use Case
createElement document.createElement(tagName) Creates new element node with specified tag name. Element not in DOM until appended. Dynamic content generation
createTextNode document.createTextNode(data) Creates text node with specified string. Automatically escapes HTML entities. Safe text insertion
createDocumentFragment document.createDocumentFragment() Creates lightweight document fragment. Batch DOM operations for performance. Bulk insertions
appendChild parent.appendChild(child) Appends node as last child. Moves node if already in DOM. Returns appended node. Add to end of parent
insertBefore parent.insertBefore(new, ref) Inserts node before reference node. Use null as ref to append at end. Insert at specific position
replaceChild parent.replaceChild(new, old) Replaces old child with new node. Returns replaced node. Swap elements
removeChild parent.removeChild(child) Removes child node from parent. Returns removed node. Throws error if not child. Remove specific child
cloneNode node.cloneNode(deep) Creates copy of node. deep=true clones descendants. IDs are duplicated. Duplicate elements
append NEW parent.append(...nodes) Appends multiple nodes or strings. Accepts DOMString. No return value. Modern bulk append
prepend NEW parent.prepend(...nodes) Inserts nodes before first child. Accepts multiple arguments. Add to beginning
before NEW element.before(...nodes) Inserts nodes before element in parent's child list. Insert before sibling
after NEW element.after(...nodes) Inserts nodes after element in parent's child list. Insert after sibling
replaceWith NEW element.replaceWith(...nodes) Replaces element with nodes/strings. More convenient than replaceChild. Modern replacement
remove NEW element.remove() Removes element from DOM tree. No parent reference needed. Self-removal

Example: Creating and inserting elements

// Traditional approach
const div = document.createElement("div");
div.textContent = "Hello";
document.body.appendChild(div);

// Modern approach - multiple nodes
const container = document.querySelector("#container");
container.append("Text", document.createElement("span"), "More text");

// Document fragment for performance
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const item = document.createElement("li");
  item.textContent = `Item ${i}`;
  fragment.appendChild(item);
}
list.appendChild(fragment); // Single reflow

// Clone with deep copy
const original = document.querySelector(".template");
const copy = original.cloneNode(true);
copy.id = "new-id"; // Change duplicate ID
document.body.appendChild(copy);

// Modern insertion methods
element.before("Before"); // Insert before
element.after("After");   // Insert after
element.replaceWith("Replacement"); // Replace
element.remove(); // Remove from DOM
Warning: Using appendChild on an existing DOM node will move it, not copy it. Use cloneNode() first to duplicate. Document fragments are destroyed after insertion - create new one for each batch operation.

3. Element Attributes and Properties

Method/Property Syntax Description Use Case
getAttribute element.getAttribute(name) Returns attribute value as string. Returns null if attribute doesn't exist. Read custom attributes
setAttribute element.setAttribute(name, value) Sets attribute to specified value. Creates attribute if doesn't exist. Converts value to string. Set any attribute
removeAttribute element.removeAttribute(name) Removes attribute completely. No error if attribute doesn't exist. Remove attributes
hasAttribute element.hasAttribute(name) Returns boolean indicating if element has specified attribute. Check attribute existence
toggleAttribute NEW element.toggleAttribute(name, force) Toggles boolean attribute. Optional force parameter adds/removes explicitly. Toggle boolean attributes
attributes element.attributes Returns live NamedNodeMap of all attributes. Use .name and .value properties. Iterate all attributes
dataset element.dataset DOMStringMap of data-* attributes. Converts kebab-case to camelCase. Read/write custom data. Custom data attributes
id element.id Direct property access to element ID. Faster than getAttribute. Reflects in HTML. ID manipulation
className element.className Gets/sets class attribute as string. Space-separated for multiple classes. Bulk class replacement
innerHTML element.innerHTML Gets/sets HTML content as string. Parses HTML. XSS risk with user input. Set HTML content
outerHTML element.outerHTML Gets/sets element and its content. Setting replaces entire element. Replace element with HTML
textContent element.textContent Gets/sets text content. Strips HTML tags. Safe from XSS. Faster than innerText. Safe text manipulation
innerText element.innerText Gets/sets rendered text. Respects CSS styling. Triggers reflow. Slower than textContent. Visible text only
value input.value Gets/sets form input value. For inputs, textareas, selects. Live property. Form field values
checked input.checked Boolean for checkbox/radio state. Use property, not attribute for current state. Checkbox/radio state

Example: Attribute manipulation

// Get/set attributes
const link = document.querySelector("a");
link.getAttribute("href"); // Get
link.setAttribute("href", "/new"); // Set
link.removeAttribute("target"); // Remove

// Has attribute check
if (button.hasAttribute("disabled")) {
  console.log("Button is disabled");
}

// Toggle attribute
button.toggleAttribute("disabled");
button.toggleAttribute("hidden", true); // Force add

// Data attributes
element.setAttribute("data-user-id", "123");
console.log(element.dataset.userId); // "123"
element.dataset.userName = "John"; // Creates data-user-name

Example: Content manipulation

// innerHTML - parses HTML (XSS risk)
div.innerHTML = "<strong>Bold</strong>";

// textContent - safe, no parsing
div.textContent = "<script>alert('safe')</script>";
// Displays as literal text

// innerText vs textContent
div.innerHTML = "<span style='display:none'>Hidden</span>Text";
console.log(div.textContent); // "HiddenText"
console.log(div.innerText);   // "Text" (respects CSS)

// Form values
input.value = "New value";
checkbox.checked = true;
select.value = "option2";
Warning: Never use innerHTML with unsanitized user input - creates XSS vulnerability. Use textContent or createTextNode() for user-generated content. Properties like checked and value reflect current state, while attributes show initial HTML values.

4. CSS Classes and Styling Manipulation

Property/Method Syntax Description Use Case
classList.add element.classList.add(...tokens) Adds one or more class names. No duplicates. Does nothing if class exists. Add classes
classList.remove element.classList.remove(...tokens) Removes one or more class names. No error if class doesn't exist. Remove classes
classList.toggle element.classList.toggle(token, force) Toggles class. Returns boolean indicating if class is present after operation. Optional force parameter. Toggle states
classList.contains element.classList.contains(token) Returns boolean indicating if class exists. Case-sensitive check. Check class existence
classList.replace element.classList.replace(old, new) Replaces old class with new class. Returns boolean indicating if replacement occurred. Swap classes
classList.item element.classList.item(index) Returns class name at specified index. Returns null if out of bounds. Access by index
classList.length element.classList.length Returns number of classes. Read-only property. Count classes
style element.style.property Inline styles as CSSStyleDeclaration. Use camelCase for CSS properties. High specificity. Inline styles
style.cssText element.style.cssText Gets/sets all inline styles as string. Replaces existing inline styles when set. Bulk style setting
style.setProperty style.setProperty(prop, value, priority) Sets CSS property. Accepts kebab-case. Optional "important" priority. Set with priority
style.removeProperty style.removeProperty(property) Removes inline style property. Returns removed value. Remove inline styles
style.getPropertyValue style.getPropertyValue(property) Gets CSS property value. Accepts kebab-case property names. Read property value
getComputedStyle getComputedStyle(element, pseudo) Returns live CSSStyleDeclaration of computed styles. Includes inherited/default values. Read-only. Read final styles
attributeStyleMap NEW element.attributeStyleMap CSS Typed OM for type-safe style manipulation. Returns StylePropertyMap. Modern typed styles

Example: Class manipulation with classList

// Add single or multiple classes
element.classList.add("active");
element.classList.add("highlight", "bold", "large");

// Remove classes
element.classList.remove("inactive");

// Toggle class
const isActive = element.classList.toggle("active");
console.log(isActive); // true if added, false if removed

// Force toggle
element.classList.toggle("active", true);  // Always add
element.classList.toggle("active", false); // Always remove

// Check if class exists
if (element.classList.contains("active")) {
  console.log("Element is active");
}

// Replace class
element.classList.replace("old-theme", "new-theme");

// Iterate classes
for (let className of element.classList) {
  console.log(className);
}

Example: Style manipulation

// Direct style property (camelCase)
element.style.backgroundColor = "#ff0000";
element.style.fontSize = "16px";
element.style.marginTop = "20px";

// Set multiple styles at once
element.style.cssText = "color: blue; font-size: 14px; padding: 10px;";

// Set with priority
element.style.setProperty("color", "red", "important");

// Remove inline style
element.style.removeProperty("background-color");

// Get computed styles (read-only)
const computed = getComputedStyle(element);
console.log(computed.color); // "rgb(255, 0, 0)"
console.log(computed.fontSize); // "16px"

// Get pseudo-element styles
const beforeStyles = getComputedStyle(element, "::before");
console.log(beforeStyles.content);

// CSS Typed OM (modern)
element.attributeStyleMap.set("opacity", 0.5);
const opacity = element.attributeStyleMap.get("opacity");
Note: Prefer classList over className for class manipulation - it's more reliable and prevents accidentally removing other classes. Use CSS classes for styling instead of inline styles when possible for better separation of concerns and maintainability.

5. DOM Traversal and Navigation Methods

Property Description Returns Notes
parentNode Returns parent node of element Node or null Includes non-element nodes
parentElement Returns parent element of element Element or null Element nodes only
childNodes Live NodeList of all child nodes NodeList Includes text, comment nodes
children Live HTMLCollection of child elements HTMLCollection Element nodes only
firstChild First child node Node or null May be text/comment node
lastChild Last child node Node or null May be text/comment node
firstElementChild First child element Element or null Skips text/comment nodes
lastElementChild Last child element Element or null Skips text/comment nodes
nextSibling Next sibling node Node or null May be text/comment node
previousSibling Previous sibling node Node or null May be text/comment node
nextElementSibling Next sibling element Element or null Skips text/comment nodes
previousElementSibling Previous sibling element Element or null Skips text/comment nodes
childElementCount Number of child elements Number Equivalent to children.length
ownerDocument Document object that contains node Document Useful in iframes
nodeType Type of node Number (1-12) 1=Element, 3=Text, 8=Comment
nodeName Name of node String Uppercase for elements
nodeValue Value of node String or null null for elements

Example: DOM traversal patterns

// Parent navigation
const parent = element.parentElement;
const grandparent = element.parentElement.parentElement;

// Find closest ancestor with class
const container = element.closest(".container");

// Child navigation
const allChildren = parent.children; // HTMLCollection of elements
const firstChild = parent.firstElementChild;
const lastChild = parent.lastElementChild;

// Sibling navigation
const next = element.nextElementSibling;
const previous = element.previousElementSibling;

// Iterate all child elements
for (let child of parent.children) {
  console.log(child.tagName);
}

// Node type checking
if (node.nodeType === Node.ELEMENT_NODE) {
  console.log("Element node");
} else if (node.nodeType === Node.TEXT_NODE) {
  console.log("Text node");
}

// Walk entire tree
function walkDOM(node, callback) {
  callback(node);
  for (let child of node.children) {
    walkDOM(child, callback);
  }
}

walkDOM(document.body, (element) => {
  console.log(element.tagName);
});
Note: Use Element variants (firstElementChild, nextElementSibling) instead of node variants to skip text and comment nodes. This is especially important when HTML has whitespace between elements. children is often more useful than childNodes for the same reason.

6. Mutation Observer for DOM Changes

Feature Syntax/Property Description Use Case
MutationObserver Constructor new MutationObserver(callback) Creates new observer with callback function. Callback receives array of MutationRecord objects. Initialize observer
observe observer.observe(target, options) Starts observing target node. Options configure what changes to watch. Can observe multiple targets. Start watching changes
disconnect observer.disconnect() Stops observing all targets. Clears pending notifications. Re-observe to resume. Stop watching
takeRecords observer.takeRecords() Returns array of pending mutations not yet processed. Clears mutation queue. Get pending changes
Option Type Description Required
childList boolean Watch for addition/removal of child nodes At least one must be true
attributes boolean Watch for attribute changes on target Optional
characterData boolean Watch for text content changes in target Optional
subtree boolean Extend observation to entire subtree. Applies all options to descendants. Optional
attributeOldValue boolean Record previous attribute value. Requires attributes: true. Optional
characterDataOldValue boolean Record previous text value. Requires characterData: true. Optional
attributeFilter string[] Array of attribute names to watch. Omit to watch all attributes. Optional
MutationRecord Property Type Description
type string Type of mutation: "attributes", "characterData", or "childList"
target Node Node affected by mutation
addedNodes NodeList Nodes added (for childList mutations)
removedNodes NodeList Nodes removed (for childList mutations)
previousSibling Node Previous sibling of added/removed nodes
nextSibling Node Next sibling of added/removed nodes
attributeName string Name of changed attribute
attributeNamespace string Namespace of changed attribute
oldValue string Previous value (if oldValue option enabled)

Example: Basic mutation observer usage

// Create observer
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    console.log("Mutation type:", mutation.type);
    console.log("Target:", mutation.target);
    
    if (mutation.type === "childList") {
      console.log("Added:", mutation.addedNodes);
      console.log("Removed:", mutation.removedNodes);
    }
    
    if (mutation.type === "attributes") {
      console.log("Attribute:", mutation.attributeName);
      console.log("Old value:", mutation.oldValue);
    }
  });
});

// Configure and start observing
const config = {
  "childList": true,
  "attributes": true,
  "attributeOldValue": true,
  "subtree": true
};

observer.observe(document.body, config);

// Later: stop observing
observer.disconnect();

Example: Practical use cases

// Watch for specific attribute changes
const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.attributeName === "data-status") {
      console.log("Status changed to:", mutation.target.dataset.status);
    }
  });
});

observer.observe(element, {
  "attributes": true,
  "attributeFilter": ["data-status", "class"]
});

// Detect when element is added to DOM
function whenElementAdded(selector, callback) {
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      mutation.addedNodes.forEach((node) => {
        if (node.matches && node.matches(selector)) {
          callback(node);
        }
      });
    });
  });
  
  observer.observe(document.body, {
    "childList": true,
    "subtree": true
  });
  
  return observer;
}

// Usage
const obs = whenElementAdded(".dynamic-content", (element) => {
  console.log("Dynamic content appeared:", element);
  obs.disconnect(); // Stop after first match
});

// React to DOM changes with debouncing
let timeoutId;
const debouncedObserver = new MutationObserver(() => {
  clearTimeout(timeoutId);
  timeoutId = setTimeout(() => {
    console.log("DOM stable after changes");
    updateUI();
  }, 300);
});

debouncedObserver.observe(container, {
  "childList": true,
  "subtree": true
});
Note: MutationObserver is asynchronous and batches mutations for performance. The callback receives all mutations since last callback. Always disconnect observers when no longer needed to prevent memory leaks. Use attributeFilter to limit observations to specific attributes for better performance.
Warning: Be careful not to modify the DOM inside the observer callback in ways that trigger the same observer - this creates infinite loops. Use disconnect() before making changes and observe() after, or use a flag to prevent recursion.