JavaScript Programming Models and Browser APIsIntroductionModern web applications are not merely collections of HTML documents. They are interactive systems that respond to user actions, communicate with remote services, preserve selected information, and update visible content without requiring a complete page reload. JavaScript provides the programming language; browser APIs provide the environment through which JavaScript interacts with the document, user, network, storage, and time. This chapter examines four complementary programming models—object-oriented, functional, event-driven, and asynchronous programming—and connects them to practical browser APIs. The emphasis is not on treating these models as mutually exclusive. Professional JavaScript applications commonly combine them: objects represent application state, functions transform data, events trigger behavior, and asynchronous operations obtain data from servers. Learning ObjectivesAfter completing this chapter, a learner should be able to: - distinguish object-oriented, functional, event-driven, and asynchronous programming;
- manipulate the Document Object Model (DOM) safely and efficiently;
- register, handle, and delegate browser events;
- use localStorage, sessionStorage, timers, and the Fetch API;
- explain promises and implement asynchronous workflows with async/await;
- handle errors, cancellation, race conditions, and untrusted data;
- integrate browser APIs into a maintainable React-oriented application;
- evaluate limitations, accessibility implications, security risks, and privacy concerns.
1. JavaScript in the BrowserA browser executes JavaScript within an environment commonly called the host environment. The language itself defines objects, functions, arrays, promises, and syntax. The browser adds APIs such as document, window, localStorage, fetch, setTimeout, and event targets. The DOM is an in-memory tree representing the current document. For example, a paragraph is represented by an element node, and its text is represented by a text node. JavaScript can query this tree, change attributes or text, create nodes, and insert or remove nodes. A browser then uses its rendering process to display the updated result. The global object in a browser is accessible through window. Modern code should avoid unnecessary global variables because global state makes dependencies difficult to identify and increases the risk of name collisions. Modules, classes, closures, and well-defined application objects provide better boundaries. A central browser concept is the event loop. JavaScript normally executes one call stack at a time. Browser APIs can perform work outside the JavaScript stack, such as waiting for a timer or receiving a network response. When that work completes, a callback or promise continuation is placed into a queue. The event loop coordinates when queued work can run. A long-running synchronous function blocks input, rendering, and other JavaScript work; therefore, expensive tasks must be divided, optimized, or moved to suitable mechanisms such as Web Workers. 2. Four Programming Models2.1 Object-oriented programmingObject-oriented programming (OOP) organizes behavior and data around objects. An object may contain state, represented by properties, and operations, represented by methods. A class is a reusable description from which objects can be created. class ShoppingCart {
constructor() {
this.items = [];
}
add(product) {
this.items.push(product);
}
total() {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
const cart = new ShoppingCart();
cart.add({ name: "Book", price: 30 });JavaScript classes use prototype-based inheritance internally. A class method is generally placed on the class prototype rather than copied separately into every instance. Inheritance can express an “is-a” relationship, but composition is often easier to maintain. Instead of creating a deep hierarchy, an application may combine small objects such as a validator, repository, and view model. Important OOP principles include encapsulation, abstraction, inheritance, and polymorphism. JavaScript supports encapsulation through module boundaries, closures, private class fields such as #items, and disciplined public interfaces. OOP is useful when an entity has a clear lifecycle, identity, and related operations. 2.2 Functional programmingFunctional programming treats computation as the evaluation and composition of functions. Functions may be passed as values, returned from other functions, and stored in variables. A pure function produces the same output for the same input and does not cause observable side effects. const prices = [10, 25, 15];
const withTax = prices.map(price => price * 1.20);
const expensive = withTax.filter(price => price >= 20);
const total = expensive.reduce((sum, price) => sum + price, 0); The example uses map, filter, and reduce. These operations create a data-processing pipeline. Functional techniques encourage immutable updates, in which new arrays or objects are created rather than modifying existing values directly. const user = { name: "Amina", role: "student" };
const updatedUser = { ...user, role: "developer" };Functional programming does not prohibit all side effects. DOM updates, network requests, logging, and storage are inherently effectful. A good design isolates effects at clear boundaries and keeps transformations pure where possible. This improves testing and reasoning. 2.3 Event-driven programmingIn event-driven programming, control flow is influenced by events rather than a single predetermined sequence. An event may be generated by a user, the browser, a timer, or a network operation. A listener subscribes to an event and executes a handler when the event occurs. const button = document.querySelector("#saveButton");
button.addEventListener("click", event => {
event.preventDefault();
console.log("Save requested");
});The event object describes the occurrence. Common properties include target, the object on which the event originated, and currentTarget, the object whose listener is currently executing. preventDefault() cancels a browser default action when permitted; stopPropagation() prevents further propagation, but it should not be used casually because it can interfere with other components. Event-driven design is particularly suitable for interfaces because users act unpredictably. It also introduces challenges: listeners may be registered more than once, event order may be difficult to follow, and shared mutable state can produce inconsistent results. 2.4 Asynchronous programmingAsynchronous programming allows an application to initiate an operation and continue other work while waiting. Network requests, timers, and some file or device operations are typical examples. In JavaScript, promises represent the eventual outcome of an operation. A promise has three conceptual states: pending, fulfilled, or rejected. Once fulfilled or rejected, it is settled and cannot transition to another state. then() handles fulfillment, catch() handles rejection, and finally() runs after settlement regardless of the outcome. fetch("/api/products")
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
})
.then(products => renderProducts(products))
.catch(error => showError(error.message));A promise rejection is not automatically equivalent to an HTTP error. The Fetch API normally resolves its promise when a response is received, including responses with status 404 or 500. The program must inspect response.ok or response.status. 3. DOM Manipulation3.1 Selecting elementsCommon selection methods include: | Method | Result | Typical use |
|---|
getElementById(id) | One element or null | Known unique identifier | querySelector(selector) | First matching element or null | CSS-style selection | querySelectorAll(selector) | Static NodeList | Multiple matching elements | closest(selector) | Nearest matching ancestor | Component or row lookup |
A robust program checks that a selection succeeded before using it. Scripts can also be loaded with defer, ensuring that parsing completes before the script runs, or placed at an appropriate point in the document. 3.2 Changing content and attributestextContent inserts text and treats it as text. It is therefore safer than innerHTML when content originates from a user or remote service. innerHTML parses a string as HTML and can create cross-site scripting (XSS) vulnerabilities if untrusted content is inserted without sanitization.
const heading = document.querySelector("h1");
heading.textContent = "Products";
heading.classList.add("page-title");
heading.setAttribute("aria-live", "polite");Use classList for CSS classes and properties such as value, checked, and disabled for form controls. Data attributes can be accessed through dataset, although sensitive information should not be placed in HTML attributes. 3.3 Creating and removing nodesfunction createProductItem(product) {
const item = document.createElement("li");
item.className = "product-item";
item.textContent = `${product.name} — $${product.price}`;
return item;
}
const list = document.querySelector("#products");
const fragment = document.createDocumentFragment();
for (const product of products) {
fragment.append(createProductItem(product));
}
list.replaceChildren(fragment);A DocumentFragment permits several nodes to be assembled before insertion. The performance benefit is context-dependent, but the technique communicates that the list is being updated as one logical operation. replaceChildren() also prevents obsolete entries from remaining in the interface. 3.4 Event delegationInstead of attaching a listener to every child, a parent can handle events that bubble from its descendants. list.addEventListener("click", event => {
const deleteButton = event.target.closest("button[data-delete-id]");
if (!deleteButton || !list.contains(deleteButton)) return;
const id = deleteButton.dataset.deleteId;
removeProduct(id);
});Delegation reduces listener management and works well for dynamically generated elements. It depends on event bubbling and requires careful filtering of the actual target. 4. Browser Events and FormsEvents may be categorized as user-interface events, keyboard events, pointer events, form events, and lifecycle events. A form submission should generally be handled through the submit event rather than only the button’s click event, because users can submit with the Enter key. form.addEventListener("submit", event => {
event.preventDefault();
const data = new FormData(form);
const email = data.get("email");
submitEmail(email);
});Accessibility requires that keyboard interaction, focus order, labels, visible focus indicators, and semantic HTML be preserved. JavaScript should enhance native controls rather than replacing them unnecessarily. A custom clickable div often creates avoidable keyboard and accessibility problems. Listeners should be removed when their owner is destroyed, especially in long-lived applications. The same function reference must be supplied to removeEventListener, or an AbortController can manage related listeners: const controller = new AbortController();
window.addEventListener("resize", handleResize, {
signal: controller.signal
});
// Later:
controller.abort();5. Storage APIsThe Web Storage API provides localStorage and sessionStorage. Both store string key–value pairs. localStorage generally persists across browser sessions for the same origin, whereas sessionStorage is associated with a particular browsing session and tab context. const settings = { theme: "dark", compact: true };
localStorage.setItem("settings", JSON.stringify(settings));
const saved = localStorage.getItem("settings");
const restored = saved ? JSON.parse(saved) : null;Storage is synchronous, has limited capacity, and should not be used for large datasets or high-frequency writes. It is not a secure secret store: any script running in the origin may access it, so passwords, long-lived tokens, and sensitive personal data should not be stored there. Cookies differ because they can be sent automatically with HTTP requests and can use security attributes such as HttpOnly and SameSite. A robust storage operation handles malformed data and unavailable storage, including privacy modes or quota errors. IndexedDB is more appropriate for larger structured client-side data and asynchronous persistence. 6. Timers and SchedulingsetTimeout(callback, delay) schedules a callback no earlier than approximately the requested delay; it does not guarantee exact execution time. The callback may be delayed by other work. setInterval repeats scheduling, but it can create overlapping work if the callback takes too long.
const timeoutId = setTimeout(() => {
console.log("One delayed action");
}, 1000);
clearTimeout(timeoutId);For repeated asynchronous work, recursive setTimeout often gives better control: async function poll() {
try {
await refreshData();
} finally {
setTimeout(poll, 5000);
}
}requestAnimationFrame is intended for visual updates and synchronizes work with the browser’s rendering cycle. It is usually preferable to timers for animation. Debouncing delays execution until activity stops, while throttling limits execution to a maximum frequency. Both are useful for search fields, scrolling, and resizing.
7. Fetch, Promises, and async/awaitFetch is a promise-based API for HTTP requests. async function loadProduct(id) {
const response = await fetch(`/api/products/${encodeURIComponent(id)}`);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return response.json();
}The await expression pauses the current asynchronous function until the promise settles; it does not block the browser’s entire JavaScript environment. Errors can be handled with try...catch. async function displayProduct(id) {
showLoading(true);
try {
const product = await loadProduct(id);
renderProduct(product);
} catch (error) {
renderError("The product could not be loaded.");
console.error(error);
} finally {
showLoading(false);
}
}Sequential and concurrent operations must be distinguished. If the second request does not depend on the first, use Promise.all: const [products, categories] = await Promise.all([
loadProducts(),
loadCategories()
]); Promise.all rejects when any input promise rejects. Promise.allSettled waits for every operation and reports each result, making it suitable when partial success is meaningful. Promise.race settles when the first promise settles; Promise.any fulfills when the first promise fulfills and rejects only if all inputs reject.
Cancellation and race conditionsFetch requests can be cancelled with AbortController: const controller = new AbortController();
try {
const response = await fetch("/api/search?q=javascript", {
signal: controller.signal
});
const results = await response.json();
} catch (error) {
if (error.name !== "AbortError") throw error;
}
// controller.abort();Search interfaces commonly face a race condition: an earlier, slower request may return after a later request and overwrite newer results. Cancellation, request sequence numbers, or checking that the response still corresponds to the current query can prevent stale updates. 8. Integrated Application PatternA maintainable browser feature can be separated into four responsibilities: - State and domain logic: data structures and pure transformations.
- Infrastructure: Fetch and storage operations.
- View rendering: conversion of state into DOM output.
- Event coordination: listeners that translate user actions into commands.
This separation resembles patterns used in React. React components usually own rendering and state coordination, while browser APIs remain available through effects and event handlers. Direct DOM manipulation should not fight React’s virtual rendering model; a component should generally update its state rather than manually changing a DOM node that React controls. Case Study: An Accessible Offline-Aware Product SearchBackgroundAn illustrative online bookstore needs a product search interface. Users enter a query, receive results from a remote API, and expect the last successful results to remain available if the network becomes unavailable. ProblemA naïve implementation sends a request for every keystroke, allows stale responses to replace newer results, writes untrusted product names with innerHTML, and stores the entire response without validation. It also provides no loading or error state. AnalysisThe feature requires event-driven input handling, asynchronous Fetch operations, cancellation, DOM rendering, and local persistence. The interface must remain responsive and must communicate status to assistive technologies. Approach and solutionconst searchInput = document.querySelector("#search");
const results = document.querySelector("#results");
const status = document.querySelector("#status");
let controller = null;
let requestNumber = 0;
function render(items) {
const fragment = document.createDocumentFragment();
for (const item of items) {
const li = document.createElement("li");
li.textContent = `${item.title} — ${item.author}`;
fragment.append(li);
}
results.replaceChildren(fragment);
}
async function search(query) {
const currentRequest = ++requestNumber;
controller?.abort();
controller = new AbortController();
status.textContent = "Searching…";
try {
const response = await fetch(`/api/books?q=${encodeURIComponent(query)}`, {
signal: controller.signal
});
if (!response.ok) throw new Error("Search request failed");
const data = await response.json();
if (currentRequest !== requestNumber) return;
render(data.items);
localStorage.setItem("lastBookResults", JSON.stringify(data.items));
status.textContent = `${data.items.length} results found.`;
} catch (error) {
if (error.name === "AbortError") return;
const cached = localStorage.getItem("lastBookResults");
if (cached) {
render(JSON.parse(cached));
status.textContent = "Network unavailable. Showing saved results.";
} else {
status.textContent = "No results are currently available.";
}
}
}
let debounceId;
searchInput.addEventListener("input", event => {
clearTimeout(debounceId);
const query = event.target.value.trim();
debounceId = setTimeout(() => {
if (query) search(query);
}, 300);
});OutcomeThe design debounces input, cancels obsolete requests, guards against stale responses, checks HTTP status, inserts remote values as text, and presents a fallback from storage. The status element can use aria-live="polite" so that changes are announced without unnecessarily interrupting users. Learning pointsThe case demonstrates that browser APIs are most effective when coordinated through explicit responsibilities. Cancellation improves efficiency, but request identity checks provide an additional correctness safeguard. Local storage improves resilience but must not be treated as authoritative or secure. Accessibility and security are part of functional quality, not optional refinements. 9. Limitations, Security, and GovernanceClient-side code runs in an environment controlled by the user. Anything sent to the browser can be inspected or modified; therefore, authorization must be enforced on the server. Client validation improves usability but does not replace server validation. Common risks include XSS through unsafe HTML insertion, cross-site request forgery in unsuitable cookie configurations, insecure handling of tokens, excessive collection of personal data, and leakage through logs or analytics. Content Security Policy can reduce the impact of some injection attacks. Same-origin policy restricts many cross-origin interactions, while CORS allows a server to declare which origins may access selected resources. CORS is not an authorization mechanism. Privacy regulations and institutional policies may require notice, consent, data minimization, retention controls, and deletion mechanisms. Storage keys should have clear lifetimes and should not silently preserve sensitive information. Error messages should help users without revealing internal infrastructure or confidential data. 10. Contemporary Developments and Best PracticesModern browser development increasingly uses modules, TypeScript, service workers, Web Workers, streams, and framework abstractions. Service workers can support caching and offline behavior, but cache invalidation and update strategies require careful design. Web Workers can move CPU-intensive computation away from the main thread, although they communicate through message passing and cannot directly manipulate the DOM. Recommended practices include: - prefer semantic HTML and progressive enhancement;
- isolate side effects from pure transformations;
- check Fetch responses and validate response data;
- use
textContent for untrusted text; - cancel obsolete asynchronous work;
- represent loading, success, empty, and error states explicitly;
- avoid synchronous storage for frequent or large operations;
- clean up listeners, timers, and subscriptions;
- test success, failure, timeout, cancellation, and malformed data;
- keep secrets out of client-side code and browser storage;
- use accessible labels, keyboard support, focus management, and status announcements;
- in React, let state drive rendering and use browser APIs within appropriate lifecycle boundaries.
Chapter SummaryJavaScript web applications combine several programming models. Object-oriented programming groups state and behavior into objects; functional programming emphasizes composable transformations and controlled side effects; event-driven programming responds to user and browser events; and asynchronous programming coordinates operations that complete later. These models are complementary rather than competing. The DOM provides a programmable representation of a document. Safe manipulation involves careful selection, semantic elements, text insertion for untrusted values, efficient updates, and event delegation where appropriate. Browser events support interactive behavior, while form handling and accessibility require attention to keyboard users and native semantics. localStorage and sessionStorage provide simple string-based persistence but are synchronous, limited, and unsuitable for secrets. Timers schedule delayed work but do not guarantee exact timing. Fetch returns promises and requires explicit HTTP-status checks. async and await improve readability, while Promise.all, cancellation, debouncing, and request identity checks support reliable concurrent applications.
Professional development also requires security, privacy, error handling, accessibility, and lifecycle management. Browser APIs should be isolated behind clear application boundaries, and React applications should allow component state to control rendering rather than competing with direct DOM manipulation. Key TermsDOM: In-memory tree representation of an HTML or XML document. Browser API: Host-provided interface through which JavaScript accesses browser capabilities. Object-oriented programming: Model that organizes state and behavior around objects. Functional programming: Model emphasizing functions, composition, immutability, and controlled side effects. Event-driven programming: Model in which events trigger handlers and influence control flow. Asynchronous programming: Model for initiating work that completes independently of the current call stack. Promise: Object representing the eventual fulfillment or rejection of an operation. Event loop: Mechanism coordinating the call stack and queued asynchronous work. Event bubbling: Propagation of an event from a target toward ancestor elements. Event delegation: Handling descendant events through a common ancestor listener. textContent: DOM property that inserts or retrieves text without parsing HTML.
Fetch API: Promise-based browser interface for HTTP requests and responses. AbortController: API used to signal cancellation to compatible operations.
localStorage: Persistent, origin-scoped string key–value storage. sessionStorage: Session-scoped string key–value storage. Debouncing: Delaying execution until a period of inactivity has elapsed. Throttling: Limiting how frequently a function may execute. XSS: Cross-site scripting, in which injected script executes in a user’s browser context. CORS: Server-controlled policy governing permitted cross-origin browser requests. DocumentFragment: Lightweight DOM container used to assemble nodes before insertion. Review QuestionsShort Answer Questions- What is the difference between
event.target and event.currentTarget? - Why should
response.ok be checked after a Fetch request? - State one important difference between
localStorage and sessionStorage. - What problem does event delegation address?
- Why is
textContent generally safer than assigning untrusted data to innerHTML?
Long Answer Questions- Explain how object-oriented and functional programming can be combined in a browser application.
- Describe the lifecycle of a promise and relate it to
async and await. - Discuss the advantages and limitations of browser storage APIs.
- Explain how event propagation, delegation, and listener cleanup affect application maintainability.
- Describe a robust architecture for a Fetch-based feature, including loading, success, empty, error, and cancellation states.
Analytical / Application Questions- A search box displays older results after a user has already entered a newer query. Analyze the race condition and propose two preventative techniques.
- Design an offline-capable notes feature. Decide what should be stored locally, identify security and privacy risks, and explain how synchronization conflicts should be handled.
- A React component directly changes a DOM element that React also renders. Analyze the likely problems and propose a design using component state and controlled side effects.
Multiple Choice Questions with AnswersQuestion 1Which statement best describes a pure function? A. It always changes a global variable. B. It produces the same output for the same input without observable side effects. C. It can run only after a browser event. D. It must be declared inside a class. Correct Answer: B. It produces the same output for the same input without observable side effects. Question 2What does Fetch normally do when the server returns HTTP 404? A. It always rejects the promise automatically. B. It resolves with a Response whose status can be inspected. C. It converts the response into JSON automatically. D. It retries until the request succeeds. Correct Answer: B. It resolves with a Response whose status can be inspected. Question 3Which property is safest for inserting untrusted plain text into an element? A. innerHTML B. outerHTML C. textContent D. document.write Correct Answer: C. textContent Question 4What is the main purpose of AbortController with fetch? A. To parse JSON responses B. To cancel a compatible asynchronous operation C. To increase localStorage capacity D. To create a DOM element Correct Answer: B. To cancel a compatible asynchronous operation Question 5Which method waits for all promises and rejects if any one rejects? A. Promise.all B. Promise.race C. Promise.any D. Promise.resolve Correct Answer: A. Promise.all Question 6Why is event delegation useful for a dynamically generated list? A. It disables event bubbling. B. It allows one ancestor listener to handle relevant descendant events. C. It prevents all default browser actions. D. It converts events into promises. Correct Answer: B. It allows one ancestor listener to handle relevant descendant events. Question 7Which statement about localStorage is correct? A. It stores JavaScript objects without conversion. B. It is automatically encrypted by the browser. C. It stores string key–value pairs and is generally persistent for the origin. D. It is always available in private browsing modes. Correct Answer: C. It stores string key–value pairs and is generally persistent for the origin. Question 8What is a likely benefit of debouncing a search input? A. It sends a request for every keystroke immediately. B. It delays work until typing pauses, reducing unnecessary requests. C. It guarantees that network requests finish in order. D. It removes the need for server validation. Correct Answer: B. It delays work until typing pauses, reducing unnecessary requests. Question 9Which API is intended primarily for scheduling visual updates with the rendering cycle? A. requestAnimationFrame B. sessionStorage C. FormData D. Promise.any Correct Answer: A. requestAnimationFrame Question 10Why should client-side validation not replace server-side validation? A. The browser cannot display validation messages. B. Users can modify or bypass client-side code and requests. C. HTML forms cannot submit validated data. D. Fetch does not support validated input. Correct Answer: B. Users can modify or bypass client-side code and requests.
|