DOM Manipulation in JavaScript
What is the DOM?
The Document Object Model (DOM) is a programming interface for web documents. It represents the page as nodes and objects that can be manipulated with JavaScript.
Selecting DOM Elements
By ID
const element = document.getElementById('myId');
By Class Name
const elements = document.getElementsByClassName('myClass');
// Returns HTMLCollection (array-like object)
By Tag Name
const elements = document.getElementsByTagName('div');
// Returns HTMLCollection
CSS Selectors
// Select first matching element
const element = document.querySelector('.myClass');
// Select all matching elements
const elements = document.querySelectorAll('div.myClass');
// Returns NodeList
Creating and Modifying Elements
Creating Elements
// Create a new element
const div = document.createElement('div');
// Create text node
const text = document.createTextNode('Hello World');
// Append text to div
div.appendChild(text);
Modifying Element Content
// Set text content
element.textContent = 'New text content';
// Set HTML content (careful with XSS)
element.innerHTML = '<span>New HTML content</span>';
// Set HTML content safely
element.innerHTML = '';
const span = document.createElement('span');
span.textContent = 'New HTML content';
element.appendChild(span);
Modifying Attributes
// Get attribute
const value = element.getAttribute('data-id');
// Set attribute
element.setAttribute('data-id', '123');
// Remove attribute
element.removeAttribute('data-id');
// Check if attribute exists
const hasAttribute = element.hasAttribute('data-id');
// Using properties for common attributes
element.id = 'newId';
element.className = 'newClass';
element.href = 'https://example.com';
Manipulating DOM Structure
Adding Elements
// Append child (at the end)
parent.appendChild(child);
// Insert before another element
parent.insertBefore(newChild, referenceChild);
// Modern methods
parent.append(child1, child2, 'text'); // Multiple nodes and text
parent.prepend(child); // Insert at beginning
referenceElement.before(newElement); // Insert before
referenceElement.after(newElement); // Insert after
Removing Elements
// Remove child from parent
parent.removeChild(child);
// Modern method (self-removal)
element.remove();
Replacing Elements
// Replace child
parent.replaceChild(newChild, oldChild);
// Modern method
oldElement.replaceWith(newElement);
Cloning Elements
// Clone without descendants
const clone = element.cloneNode(false);
// Clone with all descendants
const deepClone = element.cloneNode(true);
Manipulating CSS
Working with Classes
// Add class
element.classList.add('active');
// Remove class
element.classList.remove('active');
// Toggle class (add if absent, remove if present)
element.classList.toggle('active');
// Check if class exists
const hasClass = element.classList.contains('active');
// Replace class
element.classList.replace('old', 'new');
Inline Styles
// Get computed style
const style = window.getComputedStyle(element);
const color = style.color;
// Set inline style
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.cssText = 'color: red; background-color: blue;';
// Remove inline style
element.style.color = '';
Event Handling
Adding Event Listeners
element.addEventListener('click', function(event) {
console.log('Element clicked!');
});
// With event object
element.addEventListener('click', function(event) {
console.log('Clicked at:', event.clientX, event.clientY);
event.preventDefault(); // Prevent default behavior
event.stopPropagation(); // Stop event bubbling
});
// With options
element.addEventListener('click', handler, {
once: true, // Run only once
capture: true, // Use capture phase
passive: true // Never call preventDefault()
});
Removing Event Listeners
function handler(event) {
console.log('Handled!');
}
// Add listener
element.addEventListener('click', handler);
// Remove listener (must use same function reference)
element.removeEventListener('click', handler);
Event Delegation
// Instead of adding listeners to each button
document.getElementById('buttonContainer').addEventListener('click', function(event) {
// Check if clicked element is a button
if (event.target.tagName === 'BUTTON') {
console.log('Button clicked:', event.target.textContent);
}
});
DOM Traversal
Parent Relationships
// Parent node
const parent = element.parentNode;
// Parent element (skips non-element nodes)
const parentElement = element.parentElement;
Child Relationships
// All child nodes (including text nodes, comments)
const childNodes = element.childNodes;
// Child elements only
const children = element.children;
// First and last child
const firstChild = element.firstChild; // May be text node
const firstElement = element.firstElementChild;
const lastChild = element.lastChild;
const lastElement = element.lastElementChild;
Sibling Relationships
// Next sibling
const nextSibling = element.nextSibling; // May be text node
const nextElement = element.nextElementSibling;
// Previous sibling
const prevSibling = element.previousSibling;
const prevElement = element.previousElementSibling;
Performance Considerations
DOM Manipulation Best Practices
Minimize DOM access: Cache DOM references
// Bad: Repeated DOM access for (let i = 0; i < 1000; i++) { document.getElementById('result').innerHTML += i + ', '; } // Good: Cache the reference const result = document.getElementById('result'); let content = ''; for (let i = 0; i < 1000; i++) { content += i + ', '; } result.innerHTML = content;
Use document fragments for batch insertions
const fragment = document.createDocumentFragment(); for (let i = 0; i < 1000; i++) { const li = document.createElement('li'); li.textContent = `Item ${i}`; fragment.appendChild(li); } document.getElementById('myList').appendChild(fragment);
Reduce reflows and repaints
// Bad: Multiple reflows const div = document.getElementById('myDiv'); div.style.width = '100px'; div.style.height = '100px'; div.style.marginTop = '20px'; // Good: Batch style changes const div = document.getElementById('myDiv'); div.style.cssText = 'width: 100px; height: 100px; margin-top: 20px;'; // Or use classes div.classList.add('my-styled-div');
Modern DOM APIs
Intersection Observer
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Element is visible in viewport');
entry.target.classList.add('visible');
}
});
}, {
threshold: 0.1 // Trigger when 10% visible
});
observer.observe(document.querySelector('.target'));
Mutation Observer
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
console.log('Child nodes changed');
} else if (mutation.type === 'attributes') {
console.log(`${mutation.attributeName} attribute changed`);
}
});
});
observer.observe(element, {
childList: true, // Watch for changes to child elements
attributes: true, // Watch for attribute changes
subtree: true // Watch all descendants
});
ResizeObserver
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log(`Element size: ${width}px × ${height}px`);
}
});
observer.observe(document.querySelector('.resizable'));
Interview Tips
- Explain the difference between
innerHTML
andtextContent
- Describe event bubbling and capturing phases
- Discuss performance implications of DOM manipulations
- Explain how to optimize DOM operations for better performance
- Demonstrate knowledge of modern DOM APIs and their use cases
- Describe how to implement event delegation and why it’s useful
- Explain the difference between HTMLCollection and NodeList
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.