The Document Object Model (DOM) is a programming interface for web documents. It represents the page as a tree of nodes that JavaScript can interact with, allowing you to create dynamic and interactive web pages.
// Returns an HTMLCollection of elements with the specified classconst buttons = document.getElementsByClassName('btn');// HTMLCollection is array-like but not an array// You can loop through it like this:for (let i = 0; i < buttons.length; i++) { buttons[i].style.backgroundColor = 'blue';}// Or convert to a real array:Array.from(buttons).forEach(button => { button.style.backgroundColor = 'blue';});
// Returns the first element that matches the selectorconst firstButton = document.querySelector('.btn');// Returns all elements that match the selector (as a NodeList)const allButtons = document.querySelectorAll('.btn');// NodeList is also array-like but not an array// Modern NodeList supports forEachallButtons.forEach(button => { button.classList.add('hover-effect');});
Once you have an element, you can navigate to related elements:
Copy
const parent = element.parentNode; // or parentElementconst children = element.children; // HTMLCollection of child elementsconst firstChild = element.firstElementChild;const lastChild = element.lastElementChild;const nextSibling = element.nextElementSibling;const previousSibling = element.previousElementSibling;
There’s a distinction between nodes and elements in the DOM. Nodes include elements, text, comments, etc. Elements are specifically HTML element nodes. Methods like childNodes include all nodes, while children only includes element nodes.
// Create a new elementconst newDiv = document.createElement('div');// Create a text nodeconst newText = document.createTextNode('Hello, world!');// Add the text node to the divnewDiv.appendChild(newText);// Alternative: set innerHTML or textContentnewDiv.textContent = 'Hello, world!'; // Safer, treats content as textnewDiv.innerHTML = '<span>Hello, world!</span>'; // Parses HTML, potential security risk
// Append to the end of parent's childrenparentElement.appendChild(newElement);// Insert before a specific childparentElement.insertBefore(newElement, referenceElement);// Modern insertion methodsparentElement.append(newElement); // Like appendChild, but accepts multiple nodes and textparentElement.prepend(newElement); // Insert at the beginning of parent's childrenreferenceElement.before(newElement); // Insert before reference elementreferenceElement.after(newElement); // Insert after reference element
// Set text content (safer, treats content as text)element.textContent = 'New text content';// Alternative (older, normalizes whitespace differently)element.innerText = 'New text content';
// Set HTML content (parses HTML, potential security risk with user input)element.innerHTML = '<span>New HTML content</span>';// Safer alternative using DOM methodselement.textContent = ''; // Clear existing contentconst span = document.createElement('span');span.textContent = 'New HTML content';element.appendChild(span);
Using innerHTML with user-provided content can lead to Cross-Site Scripting (XSS) vulnerabilities. Always sanitize user input or use safer alternatives like textContent and DOM methods.
const user = document.getElementById('user');// Get data attributesconst userId = user.dataset.id; // "123"const userName = user.dataset.userName; // "John" (camelCase in JS)// Set data attributesuser.dataset.status = 'active';user.dataset.lastLogin = '2023-04-01';
// Add classelement.classList.add('highlight');// Remove classelement.classList.remove('hidden');// Toggle class (add if not present, remove if present)element.classList.toggle('active');// Check if element has a classconst hasClass = element.classList.contains('highlight');// Replace one class with anotherelement.classList.replace('old-class', 'new-class');// Older method (not recommended)element.className = 'btn primary'; // Overwrites all existing classes
// Basic syntaxelement.addEventListener('click', function(event) { console.log('Element clicked!');});// With an arrow functionelement.addEventListener('click', (event) => { console.log('Element clicked!');});// With a named functionfunction handleClick(event) { console.log('Element clicked!');}element.addEventListener('click', handleClick);
// You must use the same function reference to removeelement.removeEventListener('click', handleClick);// Anonymous functions can't be removed this way// This won't work:element.addEventListener('click', function() { /* ... */ });element.removeEventListener('click', function() { /* ... */ }); // Different function reference
Capture Phase: From window down to the target element
Target Phase: The event reaches the target element
Bubbling Phase: From the target element back up to the window
Copy
// Default: Listen during bubbling phaseelement.addEventListener('click', handleClick);// Listen during capture phaseelement.addEventListener('click', handleCapture, true);// Or with options objectelement.addEventListener('click', handleCapture, { capture: true });// Stop propagationfunction handleClick(event) { event.stopPropagation(); // Prevents event from bubbling up // event.stopImmediatePropagation(); // Also prevents other listeners on same element}
Event delegation is a technique where you attach a single event listener to a parent element instead of multiple listeners on child elements. It leverages event bubbling.
Copy
// Instead of this:const buttons = document.querySelectorAll('.btn');buttons.forEach(button => { button.addEventListener('click', handleClick);});// Use event delegation:const container = document.querySelector('.button-container');container.addEventListener('click', (event) => { // Check if clicked element is a button if (event.target.classList.contains('btn')) { // Handle the button click console.log('Button clicked:', event.target); }});
// Get form by IDconst form = document.getElementById('registration-form');// Access form elements by nameconst username = form.elements.username;const email = form.elements.email;// Alternative: direct property access if name is valid JS identifierconst password = form.password;// Access by indexconst firstElement = form.elements[0];
Cache DOM references in variables instead of repeatedly querying the DOM.
Copy
// Badfor (let i = 0; i < 100; i++) { document.getElementById('result').innerHTML += i + ', ';}// Goodconst result = document.getElementById('result');let content = '';for (let i = 0; i < 100; i++) { content += i + ', ';}result.innerHTML = content;
Use Document Fragments
When adding multiple elements, use document fragments to batch DOM operations.
Copy
const fragment = document.createDocumentFragment();for (let i = 0; i < 1000; i++) { const listItem = document.createElement('li'); listItem.textContent = `Item ${i}`; fragment.appendChild(listItem);}// Only one DOM updatedocument.getElementById('myList').appendChild(fragment);
Avoid Layout Thrashing
Batch your reads and writes to prevent forcing multiple layout recalculations.
The Intersection Observer API provides a way to asynchronously observe changes in the intersection of an element with its parent or the viewport.
Copy
// Create an observerconst observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { // Element is in view console.log('Element is now visible'); entry.target.classList.add('visible'); // Stop observing if needed // observer.unobserve(entry.target); } else { // Element is out of view console.log('Element is now hidden'); entry.target.classList.remove('visible'); } });}, { root: null, // viewport rootMargin: '0px', // margin around root threshold: 0.1 // percentage of element visible});// Start observing elementsconst elements = document.querySelectorAll('.lazy-load');elements.forEach(element => { observer.observe(element);});
DOM manipulation is a fundamental skill for frontend developers. It allows you to create dynamic, interactive web applications that respond to user input and update content without page reloads.Key takeaways:
The DOM represents HTML documents as a tree of nodes that JavaScript can interact with
Modern DOM APIs provide powerful methods for selecting, creating, and modifying elements
Event handling allows you to respond to user interactions
Performance optimization is crucial for smooth user experiences
Follow best practices for maintainability and accessibility
Advanced APIs like Intersection Observer and Web Components enable more sophisticated interactions
As you build more complex applications, consider using frameworks like React, Vue, or Angular, which provide abstracted approaches to DOM manipulation with optimized rendering strategies. However, understanding the underlying DOM APIs remains valuable even when working with these frameworks.