JavaScript and Browser ProgrammingIntroductionJavaScript is the programming language that gives web pages behaviour. HTML defines structure, CSS defines presentation, and JavaScript coordinates interaction, computation, communication, and dynamic change. In a modern browser, JavaScript can validate forms, update the Document Object Model (DOM), communicate with servers, store user preferences, and respond to events without reloading the page. Browser programming differs from general-purpose programming because code executes within an event-driven environment. The browser provides APIs such as document, fetch, localStorage, and setTimeout; JavaScript provides the language rules used to operate them. A sound understanding of both layers is essential for developing reliable and accessible web applications. Learning ObjectivesBy the end of this chapter, students should be able to: - explain JavaScript values, variables, operators, control structures, and functions;
- use objects, arrays, classes, modules, and modern syntax appropriately;
- describe the event loop and write event-driven browser programs;
- select and modify DOM elements safely;
- use promises and the Fetch API for asynchronous work;
- apply browser storage and error-handling techniques;
- debug, test, and assess the accessibility, security, and maintainability of interactive pages.
1. The JavaScript Execution EnvironmentA browser loads an HTML document, constructs its DOM, applies CSS, and executes scripts. A script may be placed in the document with a script element, preferably using a module or the defer attribute so that parsing is not unnecessarily blocked. <script type="module" src="app.js"></script> JavaScript is single-threaded in its main execution context: one piece of JavaScript runs at a time. This does not mean that the browser cannot perform other work. Browser services such as network requests and timers operate outside the JavaScript call stack. When work is ready, a task is placed in a queue; the event loop eventually schedules it when the stack is empty. Promise reactions are handled through a microtask queue, which is generally processed before the next ordinary task. This model explains why long-running code freezes the interface. A loop that occupies the main thread prevents painting, input handling, and other callbacks from running. Expensive work should therefore be divided into smaller units, moved to Web Workers where appropriate, or redesigned using efficient algorithms. 2. Syntax, Values, and TypesJavaScript variables are declared with let, const, or, in legacy code, var. Prefer const when a binding is not reassigned and let when reassignment is required. A const object can still have its properties changed; const prevents reassignment of the variable binding, not deep mutation. const course = { title: "Web Application Development", credits: 4 };
let completed = 0;
completed += 1;JavaScript has primitive values—string, number, bigint, boolean, undefined, symbol, and null—and objects. The number type represents ordinary numeric values using floating-point representation. Consequently, decimal arithmetic may contain rounding effects. For example, 0.1 + 0.2 is not guaranteed to equal exactly 0.3; currency applications should use integer minor units or a suitable decimal library. typeof null produces "object", a historical language behaviour. A robust null check is value === null. Strict equality (===) compares value and type without implicit conversion and is normally preferable to loose equality (==). Truthy values include most objects and non-empty strings; falsy values include false, 0, -0, NaN, "", null, and undefined.
The nullish coalescing operator (??) supplies a default only when a value is null or undefined, whereas || also treats valid falsy values such as zero and the empty string as absent. const pageSize = user.pageSize ?? 20; Arrays are ordered collections and objects are key-value structures. Modern code commonly uses destructuring, spread syntax, and optional chaining. const student = { name: "Amina", programme: "CSE" };
const { name, programme } = student;
const label = student.contact?.email ?? "No email supplied";
const updated = { ...student, year: 2 };3. Control Flow and FunctionsConditional statements, loops, and exception handling express control flow. Use if for general conditions, switch for a finite set of alternatives, and array methods such as map, filter, and reduce when they make a transformation explicit. const marks = [62, 48, 75, 39];
const passes = marks.filter(mark => mark >= 40);
const average = marks.reduce((sum, mark) => sum + mark, 0) / marks.length; Functions are first-class values: they can be stored, passed, and returned. Function declarations are hoisted, while function expressions and arrow functions are evaluated when execution reaches them. Arrow functions do not create their own this, which makes them useful for callbacks but unsuitable when a method needs its own dynamic receiver. function calculateGrade(mark) {
if (mark >= 70) return "A";
if (mark >= 60) return "B";
if (mark >= 50) return "C";
return "F";
}
const addTax = (price, rate = 0.15) => price * (1 + rate);A closure occurs when a function retains access to variables from its lexical environment after the outer function has returned. Closures support data privacy and factories, but careless closures can retain large objects and increase memory use. function makeCounter() {
let count = 0;
return () => ++count;
}
const next = makeCounter();The this value depends on invocation context. In object.method(), this normally refers to object; in an arrow function it is inherited from the surrounding scope. Explicit methods such as call, apply, and bind can control a regular function's receiver. 4. Objects, Prototypes, and ModulesObjects combine state and behaviour. JavaScript inheritance is prototype-based: an object can delegate property lookup to another object. The class syntax provides a clearer notation for constructor functions and prototype methods, but it does not change the underlying prototype model. class Cart {
#items = [];
add(product) {
this.#items.push(product);
}
total() {
return this.#items.reduce((sum, item) => sum + item.price, 0);
}
}Private fields beginning with # are inaccessible outside the class. Encapsulation reduces accidental coupling and gives an object a controlled public interface. ES modules divide programs into files with explicit dependencies and exports. // format.js
export function formatCurrency(value) {
return new Intl.NumberFormat("en", { style: "currency", currency: "USD" })
.format(value);
}
// app.js
import { formatCurrency } from "./format.js";Modules operate in strict mode automatically and have their own scope. Small modules are easier to test and maintain than one large script. Avoid circular dependencies and keep modules focused on coherent responsibilities. 5. Events and the DOMThe DOM is a tree representation of the document. Nodes include elements, text, and attributes. Common operations include querySelector, querySelectorAll, createElement, append, remove, textContent, and classList. const status = document.querySelector("#status");
status.textContent = "Ready";
status.classList.add("active");Use textContent for untrusted text. Assigning untrusted data to innerHTML can create cross-site scripting (XSS) vulnerabilities because inserted markup may be interpreted as HTML. If HTML must be generated dynamically, sanitize it using a carefully maintained trusted mechanism. An event represents an occurrence such as a click, keyboard input, form submission, or network completion. The browser dispatches events through capture, target, and bubble phases. Event delegation attaches one listener to a stable ancestor and uses event.target or closest() to handle events from descendants. const list = document.querySelector("#tasks");
list.addEventListener("click", event => {
const button = event.target.closest("[data-remove]");
if (!button) return;
button.closest("li")?.remove();
});For forms, listen for submit, call event.preventDefault() when handling submission in JavaScript, and validate on both client and server. Client validation improves usability but is not a security boundary because users can bypass it. Accessible interfaces must support keyboard operation, visible focus, meaningful labels, semantic HTML, and appropriate status announcements. A clickable div is usually inferior to a native button, which already provides keyboard and accessibility behaviour. 6. Asynchronous Programming and PromisesSynchronous code completes before the next statement. Asynchronous code starts an operation and arranges for later completion. A promise represents a future result and can be pending, fulfilled, or rejected. function delay(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
async function greet() {
await delay(100);
return "Hello";
}async functions always return promises. await pauses that function until the promise settles, without blocking the browser's main thread. Use try...catch to handle rejection.
Independent requests should not be unnecessarily serialized. Promise.all waits for all promises and rejects if any rejects; Promise.allSettled reports every result; Promise.race settles with the first settled promise; and Promise.any fulfills with the first fulfilled promise. 7. Fetch API and Reliable Data ExchangeThe Fetch API performs HTTP requests and returns a promise for a Response. A resolved fetch promise does not necessarily mean the HTTP request succeeded: a 404 or 500 response still resolves. Check response.ok or response.status explicitly. async function loadCourses() {
const response = await fetch("/api/courses", {
headers: { Accept: "application/json" }
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
}
loadCourses()
.then(courses => renderCourses(courses))
.catch(error => showError(error));For a POST request, specify the method, content type, and serialized body. Never assume that network data is trustworthy. Validate its shape before rendering or using it. Abort requests that are no longer relevant, such as a search request after the user has entered a newer query. const controller = new AbortController();
fetch("/api/search?q=javascript", { signal: controller.signal });
controller.abort();Cross-origin requests are controlled by the same-origin policy and server-provided CORS headers. CORS is enforced by browsers; it is not a substitute for authentication or authorization. 8. Browser StoragelocalStorage and sessionStorage expose string key-value stores. Local storage persists across browser sessions, while session storage is associated with a tab or browsing session. Both are synchronous and should not be used for large data sets or sensitive secrets.
localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme") ?? "light";Objects require serialization with JSON.stringify and JSON.parse, with validation and error handling around parsing. IndexedDB is more suitable for larger structured data and asynchronous access. Cookies are sent with HTTP requests and can support server sessions; security-sensitive cookies should use Secure, HttpOnly, and appropriate SameSite attributes. Do not store passwords or long-lived authentication tokens in web storage merely for convenience. 9. Error Handling and DebuggingErrors may arise from syntax, runtime operations, invalid assumptions, network failures, or user input. Handle errors at a boundary where a meaningful response is possible. Do not silently swallow failures. try {
const settings = JSON.parse(rawSettings);
applySettings(settings);
} catch (error) {
console.error("Invalid settings", error);
showMessage("Your settings could not be loaded.");
}Throw Error objects with useful messages. Preserve diagnostic details in logs but avoid exposing stack traces, tokens, or personal data to users. In production, centralised error monitoring should minimise sensitive data collection. Browser developer tools support breakpoints, watch expressions, network inspection, performance profiles, storage inspection, and console tracing. Effective debugging is systematic: reproduce the fault, reduce it to a minimal case, inspect assumptions, form a hypothesis, test it, and add a regression test. Linters, formatters, static type checkers such as TypeScript, and automated tests prevent many defects before deployment. Applied Case: Accessible Course SearchBackgroundThis illustrative case concerns a university course catalogue. The original page submitted a request on every keystroke, displayed server responses with innerHTML, and left users without feedback during slow requests. Problem and AnalysisThe application suffered from race conditions: an older, slower response could replace newer results. It also exposed an XSS risk, lacked cancellation, and provided no accessible loading or error status. ApproachThe improved design uses a debounced input listener, an AbortController, response-status checking, safe DOM construction with textContent, and an aria-live status region. let controller;
let timer;
const input = document.querySelector("#search");
const results = document.querySelector("#results");
const status = document.querySelector("#status");
input.addEventListener("input", () => {
clearTimeout(timer);
timer = setTimeout(search, 300);
});
async function search() {
controller?.abort();
controller = new AbortController();
const query = input.value.trim();
if (!query) { results.replaceChildren(); return; }
status.textContent = "Searching…";
try {
const response = await fetch(`/api/courses?q=${encodeURIComponent(query)}`, {
signal: controller.signal
});
if (!response.ok) throw new Error("Search service unavailable");
const courses = await response.json();
results.replaceChildren(...courses.map(course => {
const item = document.createElement("li");
item.textContent = `${course.code}: ${course.title}`;
return item;
}));
status.textContent = `${courses.length} results found.`;
} catch (error) {
if (error.name !== "AbortError") status.textContent = "Search failed.";
}
}Outcome and Learning PointsCancellation prevents obsolete work from updating the interface; debouncing reduces request volume; safe text insertion limits injection risk; and status feedback improves usability. The server must still validate queries, enforce authorization where necessary, and apply rate limiting. The case demonstrates that correctness, performance, accessibility, and security are interconnected design concerns. Challenges, Ethics, and Contemporary PracticeJavaScript applications must address privacy, consent, accessibility, security, and reliability. Data minimisation and clear consent are preferable to indiscriminate tracking. Third-party scripts should be reviewed because they can access page data and affect performance. Content Security Policy, output encoding, dependency updates, secure transport, and server-side authorization are important defences. Progressive enhancement remains a valuable strategy: provide a meaningful HTML experience first, then add JavaScript improvements. Frameworks and build tools can improve productivity, but they do not replace knowledge of the DOM, HTTP, browser security, and accessibility. Web Components, service workers, WebSockets, and WebAssembly extend browser capabilities, while TypeScript adds static checking to large JavaScript codebases. These technologies should be selected because they solve a demonstrated problem, not merely because they are fashionable. Best Practices- Prefer
const, strict equality, small functions, and explicit module boundaries. - Keep rendering, data access, and application state sufficiently separated to enable testing.
- Check HTTP status codes and validate external data.
- Cancel obsolete asynchronous work and avoid blocking the main thread.
- Use semantic HTML, keyboard-accessible controls, labels, and status feedback.
- Treat all external input as untrusted; avoid unsafe HTML insertion.
- Store only necessary data and protect credentials through appropriate server-side mechanisms.
- Use developer tools, linting, tests, code review, and automated security checks.
Chapter SummaryJavaScript combines a flexible language with browser APIs in an event-driven execution model. Variables, strict equality, functions, closures, objects, classes, and modules provide the language foundation. The DOM and event system allow applications to respond to users and update documents. Promises, async/await, and Fetch support non-blocking network operations, but HTTP status and data validity must be checked explicitly. Browser storage offers different persistence and security properties. Robust applications handle errors, protect against XSS and unsafe data practices, support accessibility, and use systematic debugging. Professional browser programming therefore requires more than syntax: it requires sound reasoning about timing, trust, usability, and maintainability. Key TermsJavaScript: A programming language commonly used to add behaviour to web applications. DOM: A tree-structured object representation of an HTML document. Event loop: The browser mechanism that schedules queued callbacks and promise reactions. Primitive: A non-object JavaScript value such as a string, number, or boolean. Closure: A function together with access to its lexical environment. Prototype: An object used as a delegation target for property lookup. Module: A file with its own scope and explicit imports and exports. Event bubbling: Propagation of an event from a target toward ancestor elements. Event delegation: Handling descendant events through a listener on an ancestor. Promise: An object representing the eventual success or failure of asynchronous work. Fetch API: A browser interface for making HTTP requests. AbortController: An API used to cancel operations such as fetch requests. XSS: Injection of executable or harmful content into a web page. CORS: A browser security mechanism governing permitted cross-origin requests. localStorage: Persistent, origin-specific string key-value storage. IndexedDB: An asynchronous browser database for structured data. Progressive enhancement: Building a basic usable experience before adding advanced behaviour. Debouncing: Delaying execution until a burst of events has subsided. Review QuestionsShort Answer Questions- Why is
=== generally preferred to == in application code? - What is the difference between
localStorage and sessionStorage? - Why must a fetch response be checked with
response.ok? - What problem does event delegation solve?
- Why is
textContent safer than assigning untrusted data to innerHTML?
Long Answer Questions- Explain the browser event loop, including the call stack, task queue, and promise microtasks.
- Compare ordinary functions and arrow functions with respect to
this. - Describe a robust design for form validation involving both client and server responsibilities.
- Explain how promises,
async/await, Promise.all, and cancellation support reliable asynchronous applications. - Discuss how accessibility, security, performance, and maintainability influence DOM-based application design.
Analytical / Application Questions- A search interface displays results from requests issued on every keystroke. Design a solution that addresses debouncing, cancellation, stale responses, errors, and accessibility.
- An application stores a user profile in
localStorage and later crashes while parsing it. Identify the risks and propose a recovery and validation strategy. - A developer inserts server-provided course titles using
innerHTML. Analyse the security and accessibility implications and propose a safer implementation.
Multiple Choice Questions with AnswersQuestion 1Which declaration is most appropriate when a variable binding will not be reassigned? A. var B. const C. let D. static Correct Answer: B. const Question 2What does an async function return? A. A promise B. Only a string C. A DOM element D. A timer handle Correct Answer: A. A promise Question 3Which property should normally be checked to detect an HTTP error after fetch resolves? A. response.bodyUsed B. response.url C. response.ok D. response.type only Correct Answer: C. response.ok Question 4Which operation safely inserts untrusted plain text into an element? A. innerHTML B. document.write C. eval D. textContent Correct Answer: D. textContent Question 5What is the principal purpose of AbortController in the chapter's search example? A. To cancel an obsolete request B. To encrypt a response C. To parse JSON D. To create a DOM tree Correct Answer: A. To cancel an obsolete request Question 6Which statement about localStorage is correct? A. It stores JavaScript objects without conversion B. It is persistent string key-value storage C. It is automatically encrypted D. It is sent with every HTTP request Correct Answer: B. It is persistent string key-value storage Question 7What does event delegation typically use to handle events from descendants? A. A listener on an ancestor B. A separate browser tab C. A database trigger D. A CSS animation Correct Answer: A. A listener on an ancestor Question 8Which promise combinator rejects when any input promise rejects? A. Promise.all B. Promise.allSettled C. Promise.any D. Promise.race only after all settle Correct Answer: A. Promise.all Question 9Why can a long-running JavaScript loop make a page unresponsive? A. It disables HTTP permanently B. It occupies the main thread C. It deletes the DOM automatically D. It changes all strings to numbers Correct Answer: B. It occupies the main thread Question 10Which practice is a direct defence against inserting attacker-controlled markup? A. Using textContent for plain text B. Disabling strict equality C. Using more global variables D. Ignoring response status Correct Answer: A. Using textContent for plain text
|