11 — DOM APIs — Talking to the Page from JavaScript
"The document object" was my DOM model, and it conflated the tree with the language that touches it. The idea that everything else hangs off: the DOM is a live tree of nodes the browser builds from the HTML, and JavaScript doesn't own it — it borrows a set of methods from the browser's host environment to query, change, and listen to that tree. [1]
The framing that finally landed is the host-environment split. The ECMAScript spec defines the core language (types, functions, closures). The DOM is specified separately — by the WHATWG/Web APIs — and provided by the browser. document, window, querySelector, addEventListener are not JavaScript. They're the browser's API surface, handed to the JS engine so my code can touch the page. That distinction explains why the same language in Node has no document — Node ships different host APIs.
The tree, and how I find nodes
The browser parses HTML into a tree of nodes — element nodes, text nodes, comment nodes — rooted at document. Almost everything I do starts with finding the node I want [1]:
document.querySelector("#submit"); // first match by CSS selector
document.querySelectorAll(".item"); // all matches (a NodeList)
document.getElementById("submit"); // by id (legacy, fast)
element.closest(".card"); // nearest ancestor matching a selectorquerySelector / querySelectorAll are the modern, universal tools — they take any CSS selector, so the same syntax I use for styling works for querying. The legacy methods (getElementById, getElementsByClassName) still exist and are slightly faster, but I rarely need the speed.
Changing the tree
Once I have a node, the API for reading and mutating it is large but the common moves are few [1]:
const btn = document.querySelector("#submit");
btn.textContent = "Save"; // change text
btn.setAttribute("disabled", ""); // change an attribute
btn.classList.add("loading"); // toggle a class
btn.style.color = "red"; // inline style (prefer classes)
// structure changes
const item = document.createElement("li"); // make a node
item.textContent = "New";
list.appendChild(item); // attach it
list.removeChild(oldItem); // remove one
oldItem.replaceWith(newItem); // swap oneTwo things to keep straight. First, textContent vs innerHTML — textContent sets plain text safely; innerHTML parses HTML, which is both more powerful and an XSS vector if the content comes from user input. Second, prefer CSS classes over inline style writes — classes keep styling concerns in stylesheets, where they belong.
Listening to events
Interactivity is events. The pattern is addEventListener(event, callback) — register a function to run when something happens [1]:
btn.addEventListener("click", (event) => {
console.log(event.target); // the element that was clicked
doSave();
});The callback receives an event object with details: event.target (the actual element clicked), event.currentTarget (the element the listener is attached to), event.preventDefault() (stop the default action), event.stopPropagation() (stop bubbling). Events bubble from the target up to the root, which is why event delegation works — one listener on a parent handles clicks on any of its children, useful when children are added dynamically.
Beyond the DOM: other Web APIs
The DOM is the headline, but browsers expose many more Web APIs that JavaScript can call [1]: Web Storage (localStorage, sessionStorage) for key-value persistence, Fetch for network requests, History for URL manipulation without reload, Geolocation, Canvas and WebGL for graphics, Web Workers for background threads. Each is a host API — not part of the language, lent by the browser, and absent in Node unless polyfilled.
How I use this
In a framework world (React, Vue, Svelte), I touch the DOM directly far less than I used to — the framework owns the tree and I declare state, not nodes. But the DOM APIs are still the substrate underneath, and they're essential for: reading a value the framework doesn't manage (a third-party widget, a measurement), integrating non-framework code, or building something without a framework. The rule I keep: reach for querySelector over legacy methods, prefer textContent and classes over innerHTML and inline styles, and use event delegation when children are dynamic. And remember that none of these methods are JavaScript — they're the browser's surface, and they won't exist in a Node script.
References
[1] Mozilla, "Document Object Model (DOM)," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model
[2] Mozilla, "Introduction to events," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events
[3] Mozilla, "Element.querySelector()," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
[4] Mozilla, "EventTarget.addEventListener()," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
[5] Mozilla, "Web APIs — list of interfaces," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/API
Knowledge check · Question 1 of 5
The DOM methods like querySelector and addEventListener are…
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!