The Document Object Model
The Document Object Model (DOM) is a programming interface that represents HTML documents as a tree structure of objects that JavaScript can access and manipulate. It serves as the bridge between web pages and programming languages, transforming static HTML into dynamic, interactive applications.
What the DOM Provides:
Structured Representation: HTML elements become JavaScript objects
API for Manipulation: Methods to find, create, modify, and delete elements
Event System: Mechanisms to respond to user interactions
Live Connection: Changes to the DOM immediately reflect in the browser
Platform Independence: Standard interface across all browsers

When a browser loads an HTML page, it parses the markup and creates a DOM tree in memory. Each HTML element becomes a node object with properties and methods. JavaScript can then access this tree through the global document object, which represents the entire page. Understanding that the DOM is a live representation—not the HTML source code—is crucial for effective web development.
The browser DOM APIs
The browser provides several related APIs for working with web pages:
DOM Core: Finding, accessing, and modifying elements and their content
DOM Events: Handling user interactions (clicks, keyboard input, form submissions)
DOM Style: Modifying CSS properties and classes dynamically
DOM Traversal: Navigating relationships between elements (parents, children, siblings)
DOM HTML: Specific methods and properties for HTML elements
DOM Manipulation: Creating, moving, and removing elements from the page
In essence: The HTML DOM is a standard for how to get, change, add, or delete HTML elements using JavaScript.
DOM Methods and Properties
DOM methods are actions you can perform on HTML elements (functions you can call). DOM properties are values associated with HTML elements that you can read or modify (like object properties).
html
Methods are called with parentheses and often accept arguments: getElementById("demo"). Properties are accessed without parentheses like regular object properties: element.innerHTML. Methods typically perform actions or return values, while properties store the current state of elements. Understanding this distinction helps you read documentation and write cleaner code.
Finding HTML elements
Before you can manipulate elements, you need to select them from the DOM. JavaScript provides multiple methods for finding elements, each suited to different scenarios.
Selecting by ID
getElementById() returns a single element object or null if no match is found. ID selectors are the fastest selection method because IDs must be unique. Always check if the element exists before manipulating it to avoid "cannot read property of null" errors. This method only exists on the document object, not on individual elements.
Selecting by tag name
getElementsByTagName() returns an HTMLCollection, which is live—if the DOM changes, the collection updates automatically. Access elements by index like arrays, but note that HTMLCollections lack array methods like forEach(). Convert to a true array with Array.from() to use array methods. This method can be called on any element to search within its descendants, not just on the document.
Selecting by Class name
getElementsByClassName() returns a live HTMLCollection of all elements with the specified class name. Class names are case-sensitive. When searching for multiple classes (space-separated), elements must have all specified classes to match. Like getElementsByTagName(), this method can be called on any element to narrow the search scope to that element's descendants.
Selecting with CSS Selectors (recommended)
querySelector() and querySelectorAll() are the modern, preferred methods for selecting elements. They accept any valid CSS selector, making them incredibly powerful and flexible. querySelector() returns the first match or null, while querySelectorAll() returns a NodeList of all matches (which is static, not live like HTMLCollection). NodeLists support forEach() directly, making iteration easier. These methods can use the full power of CSS selectors including pseudo-classes, attribute selectors, and combinators.
Comparison of Selection Methods
When to use each method:
getElementById(): When you have a unique ID and need maximum performance
getElementsByClassName() / getElementsByTagName(): When you need a live collection that updates automatically
querySelector(): When you need one element with flexible CSS selector matching
querySelectorAll(): Most common choice for selecting multiple elements with modern syntax
Be aware of live vs. static collections: getElementsBy* methods return live HTMLCollections that automatically update, while querySelectorAll() returns a static NodeList that captures a snapshot. Live collections can cause unexpected behavior if you're modifying the DOM while iterating
Manipulating element content
Once you've selected elements, you can read and modify their content using various properties.
innerHTML vs textContent vs innerText
Security Warning: Never use innerHTML with untrusted user input—it can execute malicious scripts (XSS attacks). If you need to insert user-provided text, use textContent instead, which treats everything as plain text. Use innerHTML only when you control the HTML content completely.
Performance: innerHTML is slower because the browser must parse HTML. textContent is faster for plain text. innerText is slowest because it considers CSS styling and triggers reflow. For most cases, prefer textContent for reading/writing plain text.
Modifying attributes
Attributes can be manipulated using getAttribute(), setAttribute(), removeAttribute(), and hasAttribute() methods. For standard HTML attributes, you can also use direct property access (element.src, element.href). Data attributes (data-*) are perfect for storing custom information and can be accessed via the dataset property, which automatically converts data-user-id to dataset.userId using camelCase. Direct property access is generally faster and more convenient for standard attributes.
Working with Classes
The classList API is the modern, safe way to manipulate CSS classes. It prevents common pitfalls like duplicate classes or spacing issues. The toggle() method is particularly useful for showing/hiding elements or changing states—it returns true if the class was added and false if removed. Never manipulate className directly with string concatenation; use classList methods instead for reliability and readability.
Modifying CSS styles
JavaScript can modify element styles through the style property or by changing classes. Each approach has specific use cases.
Inline styles with style property
The style property only accesses inline styles set via JavaScript or the HTML style attribute. It won't return styles from CSS files or <style> tags. Use window.getComputedStyle(element) to read the final computed styles from all sources.
Best Practice: Prefer adding/removing classes over setting inline styles when possible. Inline styles have high specificity and are harder to override, making CSS maintenance difficult. Use inline styles only for dynamic values that can't be predefined in CSS (like animation positions or calculated dimensions).
Class-based styling (recommended)
Using classes to control styling is the professional approach: define visual states in CSS, then use JavaScript to toggle between states by adding/removing classes. This separates concerns—CSS handles presentation, JavaScript handles behavior. Classes also enable CSS transitions and animations, which don't work well with directly manipulated inline styles. Reserve inline styles for dynamic values that must be calculated at runtime (positions, dimensions based on user input).
Creating and removing elements
The DOM isn't static—you can dynamically create new elements and remove existing ones to build interactive interfaces.
Creating new elements
Creating elements with createElement() and building them programmatically is safer than using innerHTML with string concatenation, especially with user input. When you set innerHTML, all existing content is destroyed and recreated, removing event listeners. Use insertAdjacentHTML() when you need to insert HTML strings—it's faster and doesn't destroy existing content. For complex structures, consider using template literals or document fragments for better performance.
Inserting elements at specific positions
insertAdjacentElement() and its siblings provide precise control over where new elements appear: beforebegin inserts before the element as a sibling, afterbegin inserts as the first child, beforeend inserts as the last child, and afterend inserts after the element as a sibling. These methods are more flexible than appendChild() which always adds at the end. Use insertAdjacentText() to safely insert user-provided text that should not be interpreted as HTML.
Removing elements
The modern remove() method is the simplest way to remove elements—just call it on the element you want to remove. The older removeChild() method requires a parent reference and is more verbose. When removing all children, using innerHTML = "" is fastest but doesn't properly clean up event listeners, potentially causing memory leaks. Using a loop with remove() or removeChild() is safer for elements with attached event listeners.
Cloning elements
cloneNode(true) creates a deep copy of an element including all descendants, while cloneNode(false) only copies the element itself. Clones are independent copies—changes to the clone don't affect the original. Event listeners are NOT copied, so you must reattach them. The template pattern is perfect for creating multiple similar elements: keep a hidden template in your HTML, clone it when needed, customize the clone, and add it to the page.
Handling events
Events are actions or occurrences that happen in the browser—clicks, key presses, form submissions, page loads, and more. JavaScript can listen for these events and respond accordingly.
Common event types
{% hint style="success" %} Understanding event types helps you respond to the right user actions. Mouse events handle pointer interactions, keyboard events capture typing, form events monitor user input, and window events track page state. DOMContentLoaded fires when the HTML is parsed and DOM is ready, while load waits for all resources (images, stylesheets). Use input for real-time validation and change for actions that should occur after the user finishes editing. {% endhint %}
Adding event listeners
Always use addEventListener() for attaching event handlers—it's the modern standard. Never use inline event handlers (onclick="...") in HTML as they mix structure with behavior and create security risks. The onclick property can only hold one function and will overwrite previous handlers. addEventListener() allows multiple handlers on the same element and provides better control with options like once, capture, and passive. Always use named functions if you need to remove listeners later, as anonymous functions can't be removed.
The Event Object
The event object contains valuable information about what happened. event.target is the element that triggered the event (the clicked button), while event.currentTarget is the element with the listener attached (useful in event delegation). Use preventDefault() to stop default behaviors like form submissions or link navigation. Use stopPropagation() to prevent events from bubbling up to parent elements. Keyboard events provide key (character value) and code (physical key), plus modifier key booleans for building keyboard shortcuts.
DOM Traversal
Navigating the DOM tree to find related elements is called DOM traversal.
Parent, Child, and Sibling relationships
{% hint style="warning" %} childNodes and children are different: childNodes includes all nodes (text, comments, elements) while children only includes elements. Similarly, firstChild might be a text node, while firstElementChild is always an element. Text nodes are created by whitespace in HTML, which can cause unexpected behavior. For most purposes, use the element-specific properties (children, firstElementChild, etc.) to avoid dealing with text nodes. {% endhint %}
Querying within elements
All query methods (querySelector, querySelectorAll, getElementsBy*) can be called on any element, not just document. This scopes the search to descendants of that element only. The closest() method searches up the tree for the nearest ancestor matching a selector, which is perfect for finding container elements. These scoped searches are more efficient than searching the entire document and make code more maintainable by clearly expressing intent.
Best practices
1. Cache DOM References
2. Minimize reflows and repaints
3. Use document fragments for multiple elements
4. Remove Event Listeners when not needed
5. Prefer semantic HTML and CSS over JavaScript
{% hint style="success" %} Efficient DOM manipulation requires understanding browser rendering: reading layout properties like offsetHeight triggers reflows (expensive), and multiple DOM changes cause multiple repaints. Cache element references to avoid repeated queries. Batch DOM changes together, or use Document Fragments to build complex structures off-DOM then add them in one operation. Clean up event listeners to prevent memory leaks, especially with dynamically created elements. Let CSS handle styling and animations—JavaScript should manage state (add/remove classes), not visual details. {% endhint %}
Modern DOM APIs
IntersectionObserver
MutationObserver
ResizeObserver
Modern Observer APIs provide efficient ways to monitor changes without polling. IntersectionObserver detects when elements enter/exit the viewport, perfect for lazy loading and infinite scroll. MutationObserver watches for DOM structure or attribute changes, useful for frameworks or plugins that need to react to DOM modifications. ResizeObserver tracks element size changes, better than window resize events for responsive components. These APIs are more performant than traditional polling approaches
Last updated
Was this helpful?