JavaScript Language FundamentalsIntroductionJavaScript is the principal programming language of the modern web. It began as a scripting language for adding interaction to web pages, but it now supports browser applications, servers, mobile applications, desktop software, cloud services, and development tools. React applications depend on JavaScript fundamentals: components are functions, state is represented by values and objects, events invoke functions, and modules organize code. A strong understanding of JavaScript requires more than memorizing syntax. Programmers must understand how values are represented, how expressions are evaluated, how control flow determines execution, how scope controls access to names, and how asynchronous and modular code is organized. This chapter develops those foundations and connects them to practical software development. Learning ObjectivesBy the end of this chapter, you should be able to: - declare and update variables using appropriate binding mechanisms;
- distinguish primitive and reference-like values and explain type coercion;
- use arithmetic, comparison, logical, assignment, and conditional operators;
- construct decisions and repetition using control-flow statements;
- define, call, compose, and reason about functions;
- explain lexical scope, closures, hoisting, and the temporal dead zone;
- manipulate arrays and objects using modern ECMAScript methods;
- organize programs with ES modules;
- detect, throw, and handle errors responsibly;
- apply modern syntax such as destructuring, spread, rest, optional chaining, and nullish coalescing;
- analyze a small JavaScript solution and improve its correctness, maintainability, and security.
1. The JavaScript Execution ModelJavaScript source code is executed by an engine such as V8, SpiderMonkey, or JavaScriptCore. The engine parses source text, creates an internal representation, and executes it. In a browser, JavaScript can interact with the Document Object Model (DOM), browser APIs, network services, and user events. In Node.js, it can access server-side APIs such as files, sockets, and processes. JavaScript is dynamically typed: a variable does not have a permanent type, although each value has a type. It is also commonly described as multi-paradigm because it supports imperative, object-oriented, functional, and event-driven styles. JavaScript execution is generally single-threaded at the level of a given event loop. Long-running synchronous work blocks further JavaScript execution and can make a user interface unresponsive. Browsers and host environments provide asynchronous APIs, while the event loop schedules callbacks and promise reactions after the current call stack is able to proceed. This distinction is important: asynchronous APIs do not make CPU-intensive JavaScript automatically parallel. 2. Variables, Bindings, and Data TypesA variable is a name associated with a value. More precisely, let, const, and var create bindings, and a binding can refer to a value. Modern code normally uses const by default and let when reassignment is required. var is retained for compatibility but has function scope and behavior that can cause avoidable errors. const course = "Web Development";
let completedLessons = 2;
completedLessons += 1; const prevents reassignment of the binding; it does not make an object immutable:
const learner = { name: "Amina" };
learner.name = "Amina Khan"; // valid: the object was changed
// learner = {}; // TypeError: the binding cannot be reassignedPrimitive typesJavaScript has the following primitive types: | Type | Example | Typical use |
|---|
undefined | let result; | Missing assigned value | null | const selected = null; | Intentional absence of an object value | boolean | true | Logical conditions | number | 42, 3.14, NaN | Most ordinary numeric values | bigint | 9007199254740993n | Integers beyond safe number precision | string | "React" | Text | symbol | Symbol("id") | Unique property keys and identifiers |
Everything else is an object, including arrays, functions, dates, maps, and regular expressions. typeof is useful but has historical irregularities: typeof null is "object", while functions produce "function". number uses floating-point representation. Consequently, some decimal calculations are approximate:
console.log(0.1 + 0.2); // 0.30000000000000004 For currency or other exact decimal requirements, applications should use integer minor units, suitable decimal libraries, or a carefully designed decimal representation. NaN means “not a number” and is itself of type number; use Number.isNaN(value) to test for it. Infinity and -Infinity represent values beyond the finite numeric range. Values, identity, and coercionPrimitive values are immutable. Objects are mutable collections and are compared by identity, not by structural content: {} === {} // false
const a = { score: 10 };
const b = a;
b.score = 20;
console.log(a.score); // 20The === operator performs strict equality without the most implicit conversions and should normally be preferred to ==. The Object.is method differs from === in special cases, including NaN and signed zero. JavaScript sometimes coerces values. For example, "5" + 2 produces "52", because + concatenates when one operand is a string, while "5" - 2 produces 3 through numeric conversion. Explicit conversion is clearer: const quantity = Number("5");
const label = String(quantity);
const enabled = Boolean("yes");A falsy value is treated as false in a Boolean context. The principal falsy values are false, 0, -0, 0n, "", null, undefined, and NaN. Empty arrays and empty objects are truthy. 3. Operators and ExpressionsAn expression produces a value. Statements perform actions or control execution. A statement may contain expressions. Main operator groups- Arithmetic:
+, -, , /, %, *. - Assignment:
=, +=, -=, *=, ??=, and others. - Comparison:
===, !==, <, >, <=, >=. - Logical:
&&, ||, !. - Conditional:
condition ? valueIfTrue : valueIfFalse. - Nullish coalescing:
??, which uses the fallback only for null or undefined. - Optional chaining:
?., which safely stops when a preceding value is nullish. - Type-related:
typeof, instanceof, and in.
Logical operators return operands, not necessarily Boolean values. Thus, userName || "Guest" uses the fallback for every falsy value, whereas userName ?? "Guest" preserves valid values such as 0 and an empty string. Choose between them according to the domain meaning of “missing.” Operator precedence determines grouping, but parentheses improve readability and prevent errors: const total = (price * quantity) - discount;
const displayName = account?.profile?.name ?? "Guest"; The increment and decrement operators can be confusing in larger expressions. Prefer separate statements when clarity matters. 4. Control FlowControl flow determines which statements execute and in what order. Decisionsfunction accessMessage(role) {
if (role === "admin") {
return "Full access";
} else if (role === "editor") {
return "Content access";
}
return "Read-only access";
}A switch statement is useful when one expression is compared with several discrete cases. Each case normally needs break, return, or another intentional control transfer to avoid fall-through. A switch compares case values using strict comparison. The conditional operator is appropriate for a short value selection, not for embedding complex business logic: const label = isPublished ? "Published" : "Draft"; Repetitionfor is suitable when initialization, a condition, and an update are conceptually related. while repeats while a condition remains true. for...of iterates over values of an iterable, such as an array; for...in iterates over enumerable property names and is generally not the right choice for array values.
for (const item of items) {
console.log(item);
}break exits a loop and continue skips to the next iteration. Every loop should have a defensible termination condition. Accidental infinite loops are often caused by forgetting to update the control variable or by modifying a collection during iteration.
5. Functions and Functional ReasoningA function packages behavior. It may accept parameters and return a value. Functions are first-class values: they can be assigned to variables, stored in objects, passed as arguments, and returned from other functions. function multiply(a, b) {
return a * b;
}
const square = (n) => n * n;Function declarations are hoisted within their scope, whereas function expressions are available only after their binding has been initialized. Arrow functions have concise syntax and capture the surrounding this; they do not create their own this, arguments, or constructor behavior. Use ordinary methods or functions when dynamic this or construction is required. Parameters can have defaults, and the rest parameter gathers remaining arguments into an array: function formatTitle(title = "Untitled", ...tags) {
return `${title} [${tags.join(", ")}]`;
}A function should ideally have a clear contract: expected inputs, output, side effects, and failure behavior. Pure functions return results based only on their arguments and do not modify external state. They are easier to test and compose. Real applications also require controlled side effects such as network requests, logging, and state updates. Higher-order array operationsmap transforms every element and returns a new array. filter retains elements satisfying a predicate. find returns the first matching element or undefined. some tests whether at least one element matches, and every tests whether all elements match. reduce combines values according to an accumulator function and an initial value.
const prices = [10, 15, 20];
const discounted = prices.map(price => price * 0.9);
const expensive = prices.filter(price => price >= 15);
const total = prices.reduce((sum, price) => sum + price, 0); Providing an initial value to reduce prevents errors on empty arrays and makes the accumulator’s type explicit. 6. Scope, Closures, and the Execution ContextScope determines where a binding can be accessed. JavaScript uses lexical scope: access is determined by where code is written, not by where a function is called. - Global scope is accessible broadly and should be minimized.
- Module scope belongs to one module and is not automatically global.
- Function scope applies to parameters and variables declared with
var. - Block scope applies to
let and const within blocks such as if and for.
let and const are hoisted in a technical sense, but they remain uninitialized in the temporal dead zone until execution reaches their declaration. Accessing them earlier throws a ReferenceError. var is initialized to undefined, which can conceal mistakes.
A closure occurs when a function retains access to bindings from its lexical environment after the outer function has returned: function createCounter() {
let count = 0;
return () => {
count += 1;
return count;
};
}
const next = createCounter();
next(); // 1
next(); // 2The returned function closes over count. Closures support private state, factories, callbacks, and memoization. They also retain referenced data, so unnecessarily large closures can increase memory use. In loops, let creates a new binding for each iteration, whereas older var patterns can cause callbacks to observe one shared final value. 7. Arrays and ObjectsArrays are ordered, zero-indexed collections. Their length property is one greater than the highest assigned index, but sparse arrays can contain missing positions. Common mutating methods include push, pop, shift, unshift, splice, and sort. Common non-mutating methods include slice, concat, map, filter, and toSorted in environments that support modern ECMAScript. The default sort compares values as strings. Numeric sorting therefore requires a comparator: const scores = [8, 12, 3];
scores.sort((a, b) => a - b); reverse, sort, and splice mutate the array. In React state management, mutation can prevent reliable change detection, so create a new array when updating state:
const updated = [...tasks, newTask];
const completed = tasks.map(task =>
task.id === id ? { ...task, done: true } : task
);Objects store keyed properties. Dot notation is convenient for valid fixed identifiers; bracket notation supports computed or unusual keys: const field = "email";
const profile = { name: "Lee", [field]: "[email protected]" };Destructuring extracts values, while spread creates shallow copies: const { name, email = "unknown" } = profile;
const copy = { ...profile, verified: true };A shallow copy duplicates only the outer container. Nested objects remain shared references. Deep copying requires a suitable strategy based on data types and application requirements; structuredClone is useful for many data structures but cannot clone every value, such as functions. Object.keys, Object.values, and Object.entries support explicit object iteration. Map is often preferable when keys are not limited to strings or when frequent insertion and lookup are central. Set stores unique values and is useful for membership tests and deduplication.
8. Modules and Program OrganizationES modules provide explicit boundaries between files. An exported declaration becomes available to another module only when imported: // math.js
export function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
// app.js
import { clamp } from "./math.js";
const safe = clamp(120, 0, 100);A module may also have one default export. Named exports are often clearer because the imported name communicates intent. Modules have their own scope, are evaluated once per module graph, and support dependency analysis and bundling. Good module design groups cohesive responsibilities, avoids circular dependencies, exposes a small public interface, and keeps environment-specific code at appropriate boundaries. A React component should not normally contain unrelated database, formatting, and authentication responsibilities in one file. 9. Errors and RobustnessErrors are inevitable: users provide invalid data, networks fail, and assumptions become outdated. JavaScript exceptions can be generated by the engine or explicitly thrown. function parseAge(input) {
const age = Number(input);
if (!Number.isInteger(age) || age < 0) {
throw new Error("Age must be a non-negative integer");
}
return age;
}try...catch...finally handles synchronous exceptions:
try {
const age = parseAge(value);
console.log(age);
} catch (error) {
console.error("Invalid input:", error.message);
} finally {
clearFormState();
}Catch blocks should respond appropriately rather than silently suppressing failures. Preserve useful diagnostic context, but do not expose confidential data to end users. Custom error classes can distinguish validation, authorization, and network failures. Promises represent eventual completion or failure. async functions return promises, and await pauses that function until a promise settles without blocking the entire JavaScript thread: async function loadUser(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
}A fetch promise generally rejects for network-level failures, not automatically for HTTP statuses such as 404 or 500; checking response.ok is therefore important. Use try...catch around awaited operations and design loading, success, empty, and error states in user interfaces. 10. Modern ECMAScript Syntax and Best PracticesModern syntax improves expressiveness when used deliberately: - Template literals, using backticks, interpolate expressions and support multiline text.
- Destructuring extracts array positions and object properties.
- Spread syntax expands iterable values or enumerable object properties.
- Rest syntax collects remaining arguments or properties.
- Optional chaining avoids repeated null checks.
- Nullish coalescing distinguishes absent values from other falsy values.
class syntax provides a structured form for prototypes, although composition and plain objects are often simpler.import and export define module boundaries.
Best practice is not maximal use of new syntax. Use features that improve correctness and readability, preserve immutability where state change detection depends on identity, validate external input, avoid implicit coercion, use descriptive names, and keep functions focused. Linters, formatters, type checking, unit tests, and code review provide complementary safeguards. JavaScript can be used with TypeScript, which adds static analysis, but TypeScript types do not replace runtime validation of network or user input. Security also belongs to language practice. Never insert untrusted strings into HTML with innerHTML without appropriate sanitization; prefer text APIs or framework escaping. Do not trust client-side validation alone. Avoid evaluating strings as code, protect secrets from frontend bundles, and treat data from APIs as untrusted until validated. Applied Case: A React Task BoardBackgroundA university team is building a task board for laboratory projects. Each task has an identifier, title, status, and optional deadline. Users can filter tasks and mark tasks complete. ProblemAn early implementation mutates arrays directly, treats an empty search string as absent, assumes every API response is successful, and places all logic inside one component. These choices produce stale displays, incorrect filtering, and difficult testing. AnalysisThe domain contains several distinct responsibilities: validating API data, transforming task collections, updating state immutably, and presenting the result. Search should be case-insensitive, and an empty search should display all tasks. The update operation should create new object and array references so React can detect the change. Approach and solutionexport function visibleTasks(tasks, query, status = "all") {
const normalized = query.trim().toLowerCase();
return tasks.filter(task => {
const matchesText = task.title.toLowerCase().includes(normalized);
const matchesStatus = status === "all" || task.status === status;
return matchesText && matchesStatus;
});
}
export function completeTask(tasks, id) {
return tasks.map(task =>
task.id === id ? { ...task, status: "completed" } : task
);
}
export async function loadTasks(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Could not load tasks: ${response.status}`);
}
const data = await response.json();
if (!Array.isArray(data)) {
throw new Error("The task response must be an array");
}
return data;
}The pure functions can be tested independently. A component can hold state, call loadTasks inside an appropriate effect, display loading and error states, and call setTasks(previous => completeTask(previous, id)) when a user completes a task. The state update uses the previous state, which is safer when multiple updates may be scheduled. OutcomeFiltering is predictable, completion does not mutate existing state, HTTP failures become visible application states, and business logic is reusable outside the presentation component. The design also creates clear points for authorization checks and runtime schema validation. Learning pointsThe case demonstrates that language fundamentals directly affect React quality. Closures appear in callbacks, immutable object and array operations support state updates, modules establish boundaries, and explicit error handling produces resilient interfaces. These are not isolated syntax exercises; they are engineering decisions. Challenges and LimitationsJavaScript’s flexibility can reduce predictability. Dynamic typing allows rapid development but moves some errors to runtime. Prototype inheritance and implicit conversions can surprise inexperienced developers. Floating-point numbers are unsuitable for some exact financial calculations. Shallow copies do not provide deep immutability. Asynchronous code introduces race conditions, cancellation concerns, and stale data problems. Toolchains may transpile or bundle syntax differently, and browser support must be considered when selecting newer APIs. Frameworks do not eliminate these issues. React can encourage good one-way data flow, but developers must still understand references, closures, effects, and asynchronous failures. Automated tools detect many style and type problems, but they cannot fully determine whether a business rule is correct or whether exposing data is ethical and lawful. Chapter SummaryJavaScript programs are built from values, bindings, expressions, statements, and functions. Modern code generally uses const and let, distinguishes primitive values from object identities, and avoids accidental coercion by preferring strict equality and explicit conversion. Operators and control-flow structures express decisions and repetition. Functions are first-class values, while closures preserve access to lexical environments and support factories and private state. Arrays and objects are central data structures. Non-mutating transformations such as map, filter, and spread syntax are especially valuable in React applications because state updates depend on clear reference changes. ES modules organize dependencies and limit scope. Robust applications validate external input, check network outcomes, handle exceptions, and represent loading and failure states. Modern syntax improves clarity when applied purposefully, while security requires treating external data as untrusted and avoiding unsafe code or HTML evaluation. Key TermsVariable binding: A named association with a value. Primitive: An immutable, non-object value such as a string or Boolean. Object: A mutable collection of properties, identified by reference. Type coercion: Automatic or explicit conversion from one value type to another. Strict equality: Comparison using === without ordinary implicit conversion. Expression: Code that evaluates to a value. Statement: An instruction that performs an action or controls execution. Lexical scope: Scope determined by where code is written. Block scope: Scope created by a block for let and const bindings. Closure: A function together with retained access to its lexical environment. Hoisting: JavaScript’s processing of declarations before ordinary execution. Temporal dead zone: The period before a let or const binding is initialized. Higher-order function: A function that receives or returns another function. Destructuring: Syntax for extracting values from arrays or object properties. Spread syntax: Syntax that expands iterable or enumerable values. Module: A file-level unit with its own scope and explicit imports and exports. Promise: An object representing eventual success or failure of an asynchronous operation. Exception: An abnormal condition signaled by throwing an error. Immutability: A practice of avoiding modification of existing data structures. Event loop: The host-environment mechanism that schedules asynchronous callbacks and tasks. Review QuestionsShort Answer Questions- Why is
const not the same as deep immutability? - What is the difference between
map and filter? - Define a closure and give one practical use.
- Why should
response.ok be checked after using fetch? - What is the difference between
?? and ||?
Long Answer Questions- Explain JavaScript primitive values, objects, identity, and type coercion. Include examples of strict equality and explicit conversion.
- Discuss lexical scope, hoisting, the temporal dead zone, and closures. Explain how these concepts affect callbacks.
- Compare arrays, objects,
Map, and Set, and recommend an appropriate structure for three different application requirements. - Explain how ES modules improve maintainability and describe principles for designing module boundaries.
- Discuss error handling for synchronous and asynchronous operations, including validation, exception propagation, user feedback, and logging.
Analytical / Application Questions- A React component sorts its state array with
state.sort() and then calls the state setter with the same array. Analyze the likely problem and propose a corrected update. - Design a function that receives a list of enrollment records and returns the average score for records whose status is
"active". State how it should behave for an empty qualifying set. - An application uses
user.nickname || user.id and displays 0 as a fallback when the nickname is an empty string. Determine whether this behavior is appropriate, and revise the expression if the requirement is to fall back only when the nickname is missing.
Multiple Choice Questions with AnswersQuestion 1Which declaration is normally preferred for a binding that will not be reassigned? A. var B. const C. function D. delete Correct Answer: B. const Question 2What does Array.prototype.map return? A. The first matching element B. A Boolean indicating whether all elements match C. A new array containing transformed elements D. The original array sorted numerically Correct Answer: C. A new array containing transformed elements Question 3What is retained by a closure? A. Only the function name B. Access to bindings in its lexical environment C. A copy of every global variable D. The browser DOM automatically Correct Answer: B. Access to bindings in its lexical environment Question 4Which expression uses a fallback only when value is null or undefined? A. value || fallback B. value && fallback C. value ?? fallback D. value ? fallback : null Correct Answer: C. value ?? fallback Question 5Why does scores.sort((a, b) => a - b) sort numbers numerically? A. The comparator returns their numeric ordering B. sort always converts strings to integers C. The array becomes immutable D. The callback filters non-numbers Correct Answer: A. The comparator returns their numeric ordering Question 6Which statement about fetch is correct? A. It rejects automatically for every HTTP 404 response B. It never returns a promise C. response.ok should be checked for HTTP failure statuses D. It can only be used inside a class Correct Answer: C. response.ok should be checked for HTTP failure statuses Question 7What is the result of typeof null in JavaScript? A. "null" B. "undefined" C. "object" D. "boolean" Correct Answer: C. "object" Question 8Which syntax creates a new object with the properties of profile plus verified? A. { profile, verified: true } B. { ...profile, verified: true } C. [...profile, verified: true] D. Object.freeze(profile, verified) Correct Answer: B. { ...profile, verified: true } Question 9What is the main purpose of try...catch? A. To declare a module B. To repeat a block indefinitely C. To handle exceptions from code that may fail D. To convert an object into an array Correct Answer: C. To handle exceptions from code that may fail Question 10Which loop directly iterates over the values of an array? A. for...in B. for...of C. switch...case D. while...of Correct Answer: B. for...of
|