DOM Manipulation (querySelector, getElementById)
1. Element Selection (querySelector, querySelectorAll, getElementById)
| 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 (createElement, createTextNode, appendChild)
| 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 (getAttribute, setAttribute, dataset)
| 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 (classList.add, classList.remove, classList.toggle)
| 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 (parentNode, children, nextSibling)
| 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. MutationObserver 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.