The DOM, or Document Object Model, is the browser's in-memory representation of an HTML document. It turns the page into a tree of objects that JavaScript can inspect, change, and react to.
- Access to elements, attributes, text, and structure
- Ability to change content and styles dynamically
- Event handling for interaction
- Traversal across parent, child, and sibling nodes
- Form access and validation support
- The browser parses HTML into a tree.
- Every element becomes a node.
- Nodes can be:
- element nodes
- text nodes
- comment nodes
- document nodes
- DOM manipulation usually means changing nodes, not raw HTML strings.
Selecting is the first step before modifying anything.
document.getElementById("title");
document.getElementsByClassName("card");
document.getElementsByTagName("li");
document.querySelector(".card");
document.querySelectorAll(".card");getElementById: fastest and direct for one known elementgetElementsByClassName: live HTMLCollection, useful for class groupsgetElementsByTagName: live HTMLCollection by tagquerySelector: first match using CSS selector syntaxquerySelectorAll: all matches using CSS selector syntax
querySelectorAllreturns a static NodeList.- Some older collection APIs return live collections that update automatically.
NodeListcan often be looped withforEach, but not every browser historically supported that equally well.
Once selected, elements can be changed in many ways.
element.textContent = "Hello";
element.innerText = "Hello";
element.innerHTML = "<strong>Hello</strong>";textContentis safe and fast for plain text.innerTextreflects rendered text and respects CSS visibility.innerHTMLparses HTML and should be used carefully because it can create security risks if user input is inserted directly.
element.setAttribute("type", "button");
element.getAttribute("href");
element.removeAttribute("disabled");
element.hasAttribute("aria-label");element.classList.add("active");
element.classList.remove("active");
element.classList.toggle("active");
element.classList.contains("active");element.style.color = "red";
element.style.backgroundColor = "black";- Prefer classes for larger styling changes.
- Use inline style changes for very specific dynamic updates.
const li = document.createElement("li");
li.textContent = "New item";
document.querySelector("ul").appendChild(li);Other insertion methods:
parent.append(child);
parent.prepend(child);
parent.before(node);
parent.after(node);
element.remove();oldNode.replaceWith(newNode);
const copy = node.cloneNode(true);cloneNode(true)deep clones children.cloneNode(false)clones only the node itself.
Traversal means moving around the DOM tree.
element.parentElement;
element.children;
element.firstElementChild;
element.lastElementChild;
element.childNodes;element.nextElementSibling;
element.previousElementSibling;
element.nextSibling;
element.previousSibling;- Use
childrenwhen you want element nodes only. - Use
childNodeswhen you need text nodes too. parentElementis usually more useful thanparentNodefor element work.
Event listeners let JavaScript respond to user actions and browser events.
button.addEventListener("click", function () {
console.log("Clicked");
});clickdblclickinputchangesubmitkeydownkeyupfocusblurmouseentermouseleavescrollload
element.addEventListener("click", handler, { once: true });once: trueauto-removes the listener after one run.- A listener can also be removed with
removeEventListener.
button.addEventListener("click", function (event) {
console.log(event.target);
console.log(event.currentTarget);
});targetis the actual element that triggered the event.currentTargetis the element the listener is attached to.
Events often travel through the DOM in phases.
- Capturing: from top down
- Target: at the actual element
- Bubbling: from target back up
- Most common browser events bubble upward.
- This makes parent elements able to catch events from their children.
Event delegation means putting one listener on a parent instead of many listeners on children.
document.querySelector("ul").addEventListener("click", function (event) {
if (event.target.matches("li")) {
console.log("Item clicked:", event.target.textContent);
}
});- Fewer event listeners
- Better performance for large lists
- Works well for dynamically added elements
form.addEventListener("submit", function (event) {
event.preventDefault();
});event.stopPropagation();
event.stopImmediatePropagation();stopPropagationstops bubbling.stopImmediatePropagationalso prevents other listeners on the same element.
Forms are one of the most common DOM use cases.
const value = input.value;input.value = "Hello";
checkbox.checked = true;submitinputchangereset
input.required = true;
input.minLength = 3;
input.maxLength = 20;
input.pattern = "[A-Za-z]+";
input.setCustomValidity("Please enter a valid value");- Use built-in HTML validation when possible.
- Use JavaScript for custom feedback and richer UX.
checkValidity()returns whether a field or form is valid.reportValidity()shows native validation messages.
const formData = new FormData(form);FormDatais useful for text fields and file uploads.- It integrates well with
fetch.
checkbox.checked;
radio.checked;const file = fileInput.files[0];- Select once when possible and reuse references.
- Prefer
textContentoverinnerHTMLfor plain text. - Use event delegation for lists and dynamic content.
- Keep rendering updates small and focused.
- Be careful with user-generated HTML.
Browser APIs are features provided by the browser environment beyond plain JavaScript. They let you interact with storage, timing, device capabilities, location, and more.
Both are Web Storage APIs for saving string data in the browser.
- Persists even after the browser is closed.
- Data stays until explicitly removed.
- Lives only for the current tab session.
- Data disappears when the tab is closed.
localStorage.setItem("theme", "dark");
localStorage.getItem("theme");
localStorage.removeItem("theme");
localStorage.clear();sessionStorage.setItem("draft", "hello");- Storage values are always strings.
- Use
JSON.stringify()to store objects. - Use
JSON.parse()to retrieve objects.
const user = { name: "Ava", age: 20 };
localStorage.setItem("user", JSON.stringify(user));
const savedUser = JSON.parse(localStorage.getItem("user"));- Theme preferences
- Auth tokens in simple demos
- Draft form data
- UI preferences
- Do not store sensitive secrets in localStorage.
- Any script on the page can usually access it.
Timers let you schedule code to run later or repeatedly.
Runs once after a delay.
const id = setTimeout(() => {
console.log("Runs once");
}, 1000);clearTimeout(id);Runs repeatedly at a fixed delay.
const intervalId = setInterval(() => {
console.log("Runs every second");
}, 1000);clearInterval(intervalId);setTimeoutis good for delayed actions.setIntervalis good for repeated polling or clocks.- For many repeating tasks, it is often safer to recursively schedule
setTimeoutto avoid drift.
The Geolocation API gives access to the user's location, with permission.
navigator.geolocation.getCurrentPosition(
position => {
console.log(position.coords.latitude);
console.log(position.coords.longitude);
},
error => {
console.error(error);
}
);const watchId = navigator.geolocation.watchPosition(callback, errorCallback);navigator.geolocation.clearWatch(watchId);- Permission is required.
- It works best over secure contexts.
- Results may vary in accuracy depending on device and environment.
Browser APIs often expose event-based behavior.
windowdocument- DOM elements
navigator- storage changes in some cases
resizescrollonlineofflinebeforeunloadvisibilitychangestorage
window.addEventListener("resize", () => {
console.log("Window resized");
});- Avoid adding duplicate listeners.
- Clean up listeners when components or pages are destroyed.
- Use passive listeners for scroll and touch where appropriate.
JavaScript is single-threaded, but it handles asynchronous work through the event loop, callbacks, promises, and async/await.
- Network requests take time
- Timers execute later
- File-like browser operations may complete later
- UI should stay responsive during waiting
A callback is a function passed into another function to run later.
setTimeout(() => {
console.log("Later");
}, 1000);function fetchData(callback) {
callback("Data ready");
}- Nested callbacks can become hard to read
- Error handling gets messy
- Reuse is harder
This is why promises and async/await became the preferred style for most modern async code.
A promise represents a value that may be available now, later, or never.
- pending
- fulfilled
- rejected
const p = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Done");
} else {
reject("Failed");
}
});p.then(result => {
console.log(result);
}).catch(error => {
console.error(error);
}).finally(() => {
console.log("Finished");
});fetch("/api/user")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));Promise.all: waits for all promises and fails fast if one rejectsPromise.allSettled: waits for all and reports every resultPromise.race: settles as soon as one settlesPromise.any: resolves when the first promise fulfills
Promise.all([p1, p2, p3]);- Promises flatten callback chains.
- They are the foundation for modern async/await syntax.
async/await is syntax built on top of promises.
async function loadUser() {
try {
const response = await fetch("/api/user");
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}asyncmakes a function return a promise.awaitpauses inside an async function until the promise settles.awaitcan only be used insideasyncfunctions, unless top-level await is supported in the environment.
- Easier to read
- Easier to reason about
- Cleaner error handling with
try/catch
If two async operations do not depend on each other, start them together.
const [user, posts] = await Promise.all([
fetch("/api/user").then(r => r.json()),
fetch("/api/posts").then(r => r.json())
]);Good async code always plans for failure.
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error("Request failed");
}
const data = await response.json();
} catch (error) {
console.error("Something went wrong", error);
}fetch("/api/data")
.then(response => {
if (!response.ok) throw new Error("Request failed");
return response.json();
})
.catch(error => {
console.error(error);
});- Check
response.okfor network requests - Throw meaningful errors
- Avoid swallowing errors silently
- Use
finallyfor cleanup
- Network failure
- Invalid JSON
- Permission denied
- Timeout behavior
- Unexpected response shape
AJAX means making asynchronous network requests from the browser without reloading the page.
XMLHttpRequest is the older browser API for network calls, but it is still useful to understand because many patterns were built around it.
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/users");
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(JSON.parse(xhr.responseText));
} else {
console.error("Request failed");
}
};
xhr.onerror = () => console.error("Network error");
xhr.send();open(method, url)send()onloadonerrorstatusresponseText
XMLHttpRequest has a readyState flow, which helps track request progress.
fetch() is the modern browser API for network requests.
fetch("/api/users")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));async function loadUsers() {
const response = await fetch("/api/users");
const data = await response.json();
console.log(data);
}fetch("/api/users", { method: "POST" });
fetch("/api/users/1", { method: "PUT" });
fetch("/api/users/1", { method: "PATCH" });
fetch("/api/users/1", { method: "DELETE" });fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "Ava" })
});fetchonly rejects on network errors, not on HTTP error codes.- You should check
response.okorresponse.status. response.json()also returns a promise.fetchdoes not automatically stringify objects.
response.json()response.text()response.blob()response.arrayBuffer()response.formData()
fetch("/api/users?limit=10&sort=desc");Axios is a popular HTTP client library built on promises and designed to make requests easier to work with.
- Cleaner syntax
- Automatic JSON parsing
- Better error handling ergonomics
- Request and response interceptors
- Built-in timeout support
- Request cancellation
import axios from "axios";
axios.get("/api/users")
.then(response => console.log(response.data))
.catch(error => console.error(error));import axios from "axios";
async function loadUsers() {
try {
const response = await axios.get("/api/users");
console.log(response.data);
} catch (error) {
console.error(error);
}
}axios.get(url, config)axios.post(url, data, config)axios.put(url, data, config)axios.patch(url, data, config)axios.delete(url, config)
axios.post("/api/users", {
name: "Ava",
email: "ava@example.com"
});axios.get("/api/users", {
params: { page: 2 },
headers: { Authorization: "Bearer token" },
timeout: 5000
});Interceptors let you modify requests or responses globally.
axios.interceptors.request.use(config => {
config.headers.Authorization = "Bearer token";
return config;
});- Axios rejects for HTTP status codes outside the success range.
- That makes its failure handling slightly more convenient than raw
fetch. - The error object can include:
error.responseerror.requesterror.message
fetchis built into the browser.- Axios adds more convenience features.
fetchneeds manualresponse.okchecks.- Axios automatically parses JSON in typical cases.
REST APIs use HTTP methods and resource-based URLs to communicate with servers.
GET /usersGET /users/1POST /usersPUT /users/1PATCH /users/1DELETE /users/1
- Resources are identified by URLs
- HTTP methods define action
- Responses use standard status codes
- JSON is the most common payload format
Content-TypeAcceptAuthorization
fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer token"
},
body: JSON.stringify({
name: "Ava",
role: "admin"
})
});- Check status codes
- Parse payload carefully
- Handle empty responses
- Handle validation errors from the server
- Create =
POST - Read =
GET - Update =
PUTorPATCH - Delete =
DELETE
- Use
GETfor safe reads. - Use
POSTfor creation. - Use
PUTwhen replacing a full resource. - Use
PATCHwhen updating part of a resource. - Use
DELETEfor removal.
- Requests are asynchronous because the browser must not block the UI.
- Network failures and HTTP failures are not the same thing.
- Always expect:
- latency
- retries
- partial failures
- malformed data
- Design UI states for:
- loading
- success
- empty
- error
- retry