1. JavaScript Syntax and Language Fundamentals

1.1 JavaScript Syntax Structure and Statements

Statement Type Syntax Description Example
Expression Statement expression; Any valid expression followed by semicolon (ASI applies) x = 5; 2 + 2;
Declaration Statement var/let/const name; Declares variables with specific scope and mutability let x; const y = 10;
Block Statement { statements } Groups multiple statements; creates block scope for let/const { let x = 1; }
Empty Statement ; Does nothing; useful in loop bodies or as placeholder while(condition);
Labeled Statement label: statement Adds identifier to statement for break/continue targeting loop1: for(...) {...}
Function Declaration function name() {} Hoisted function definition, available before declaration function add(a,b) { return a+b; }
Class Declaration class Name {} Declares class; not hoisted like functions class User { constructor() {} }
Import Statement import x from 'mod'; Loads module exports; hoisted and executed first import { func } from './module';
Export Statement export default x; Exposes values/functions from module export { func, var };

Example: Statement types in action

// Expression statements
x = 5;
y = x + 10;

// Block statement with local scope
{
    let blockScoped = 'local';
    const PI = 3.14159;
}

// Labeled statement for loop control
outer: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) break outer;
    }
}
ASI (Automatic Semicolon Insertion): JavaScript automatically inserts semicolons in certain cases, but relying on it can cause bugs. Always use explicit semicolons for clarity.

1.2 Variable Declarations (var, let, const)

Declaration Scope Hoisting Reassignment Redeclaration TDZ
var LEGACY Function scope Yes (undefined) ✓ Allowed ✓ Allowed ✗ No
let ES6 Block scope Yes (uninitialized) ✓ Allowed ✗ SyntaxError ✓ Yes
const ES6 Block scope Yes (uninitialized) ✗ TypeError ✗ SyntaxError ✓ Yes
Feature var let const
Global Object Property ✓ Creates property on window/global ✗ Does not create property ✗ Does not create property
Loop Binding Single binding (shared) New binding per iteration New binding per iteration
Initialization Required ✗ Optional ✗ Optional ✓ Required at declaration
Object/Array Mutation Allowed Allowed Allowed (reference is const)

Example: Variable declaration differences

// var: function-scoped, hoisted
console.log(x); // undefined (hoisted)
var x = 5;
if (true) {
    var x = 10; // Same variable
}
console.log(x); // 10

// let: block-scoped, TDZ
// console.log(y); // ReferenceError: Cannot access before initialization
let y = 5;
if (true) {
    let y = 10; // Different variable
    console.log(y); // 10
}
console.log(y); // 5

// const: block-scoped, immutable binding
const z = 5;
// z = 10; // TypeError: Assignment to constant
const obj = { a: 1 };
obj.a = 2; // ✓ OK: mutation allowed
// obj = {}; // ✗ TypeError: reassignment not allowed
Warning: const creates an immutable binding, not an immutable value. Object properties can still be modified.

1.3 Identifier Rules and Naming Conventions

Rule Description Valid Examples Invalid Examples
First Character Letter (a-z, A-Z), underscore (_), or dollar sign ($) _var, $elem, myVar 1var, -name, @value
Subsequent Characters Letters, digits (0-9), underscore, dollar sign var1, _temp2, $el3 my-var, my.var, my var
Case Sensitivity JavaScript is case-sensitive; myVar ≠ myvar myVar, MyVar, MYVAR All are different variables
Unicode Support Unicode letters allowed (international characters) μ, café, 変数 Avoid for portability
Reserved Words Cannot use JavaScript keywords as identifiers myClass, _return class, return, if
Convention Usage Example Purpose
camelCase Variables, functions, methods myVariable, getUserData() Standard for most identifiers
PascalCase Classes, constructors, components UserProfile, DataService Distinguishes classes from functions
UPPER_SNAKE_CASE Constants, configuration values MAX_SIZE, API_URL Indicates immutable values
_privateConvention Private/internal members (convention) _internalCache, _helper() Signals internal use only
#privateFields ES2022 True private class fields #privateValue Language-level privacy

Example: Naming conventions in practice

// Constants
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = 'https://api.example.com';

// Variables and functions (camelCase)
let userName = 'John';
function calculateTotal(items) {
    return items.reduce((sum, item) => sum + item.price, 0);
}

// Classes (PascalCase)
class UserProfile {
    #privateData; // True private field
    
    constructor(name) {
        this._internalId = Math.random(); // Convention-based private
        this.#privateData = name;
    }
}

// Boolean variables (descriptive prefix)
const isActive = true;
const hasPermission = false;
const canEdit = user.role === 'admin';

1.4 Comments and Documentation Syntax

Comment Type Syntax Usage Example
Single-line // comment Brief explanations, inline notes // Calculate sum
Multi-line /* comment */ Longer explanations, block comments /* This is a multi-line comment */
JSDoc Block /** @tag */ API documentation, type annotations /** @param {string} name */
Hashbang #!/usr/bin/env node Shebang for executable scripts (line 1 only) Node.js CLI scripts
HTML-style LEGACY <!-- comment --> Legacy browser compatibility (avoid) Only in embedded scripts
JSDoc Tag Purpose Syntax Example
@param Function parameter documentation @param {type} name description @param {number} age User's age
@returns Return value description @returns {type} description @returns {boolean} True if valid
@typedef Custom type definition @typedef {Object} TypeName @typedef {Object} User
@type Variable type annotation @type {type} @type {string[]}
@deprecated Mark as deprecated @deprecated Use newFunc instead Signals obsolete code
@example Usage examples @example functionCall() Provides code samples
@throws Documents exceptions @throws {Error} description @throws {TypeError}

Example: JSDoc documentation

/**
 * Calculates the total price with tax
 * @param {number} price - Base price of item
 * @param {number} taxRate - Tax rate as decimal (e.g., 0.08)
 * @returns {number} Total price including tax
 * @throws {TypeError} If parameters are not numbers
 * @example
 * calculateTotal(100, 0.08); // Returns 108
 */
function calculateTotal(price, taxRate) {
    if (typeof price !== 'number' || typeof taxRate !== 'number') {
        throw new TypeError('Parameters must be numbers');
    }
    return price * (1 + taxRate);
}

/**
 * @typedef {Object} User
 * @property {string} name - User's full name
 * @property {number} age - User's age
 * @property {string[]} roles - User roles
 */

/** @type {User} */
const user = {
    name: 'John Doe',
    age: 30,
    roles: ['admin', 'user']
};

1.5 Strict Mode and Its Effects

Aspect Non-Strict Mode Strict Mode Impact
Activation Default behavior 'use strict'; at file/function start Enables stricter parsing and error handling
Implicit Globals Creates global variable ReferenceError Prevents accidental globals from typos
Assignment to Non-writable Silently fails TypeError Throws error on read-only property assignment
Delete Variables Returns false SyntaxError Cannot delete variables, functions, or arguments
Duplicate Parameters Allowed (last wins) SyntaxError Prevents function param name conflicts
Octal Literals 0123 allowed SyntaxError Use 0o123 instead
this in Functions Global object (window) undefined Prevents accidental global modifications
with Statement Allowed SyntaxError Banned due to scope ambiguity
eval Scope Creates variables in surrounding scope Own scope only Prevents eval from polluting scope
Reserved Words Some allowed as identifiers Stricter: implements, interface, let, package, private, protected, public, static, yield Future-proofs code for new keywords

Example: Strict mode effects

'use strict';

// 1. Prevents implicit globals
// mistypedVariable = 17; // ReferenceError (without strict: creates global)

// 2. Prevents duplicate parameters
// function sum(a, a, c) { } // SyntaxError (without strict: allowed)

// 3. Makes 'this' undefined in functions
function showThis() {
    console.log(this); // undefined (without strict: window/global)
}
showThis();

// 4. Prevents assignment to non-writable properties
const obj = {};
Object.defineProperty(obj, 'x', { value: 42, writable: false });
// obj.x = 9; // TypeError (without strict: silently fails)

// 5. Prevents deleting variables
let x = 10;
// delete x; // SyntaxError (without strict: returns false)

// 6. Safer eval
eval('var y = 2;');
// console.log(y); // ReferenceError (without strict: y accessible)

// 7. Octal literals banned
// const octal = 0123; // SyntaxError
const octalCorrect = 0o123; // ✓ Use 0o prefix
Note: ES6 modules and classes automatically use strict mode. No need to add 'use strict'; explicitly.

1.6 JavaScript Reserved Words and Keywords

Category Keywords Description
Control Flow if, else, switch, case, default, break, continue, return Conditional execution and flow control
Loops for, while, do, in, of Iteration constructs
Declarations var, let, const, function, class Variable and function definitions
Exception Handling try, catch, finally, throw Error handling constructs
Object-Oriented new, this, super, extends, static OOP keywords
Type/Value null, undefined, true, false, NaN, Infinity Primitive values and special values
Operators typeof, instanceof, void, delete, in Special operators
Module System import, export, from, as, default ES6 module syntax
Async async, await, yield Asynchronous programming keywords
Other debugger, with DEPRECATED Debugging and legacy features
Future Reserved (Strict Mode) Status Notes
implements, interface, package Reserved Reserved for potential future use in strict mode
private, protected, public Reserved Access modifiers reserved for future features
enum Reserved (all modes) Reserved for enumeration type (not yet implemented)
arguments, eval Restricted in strict mode Cannot be used as identifiers or assigned to

Cannot Use as Identifiers:

// ✗ Invalid
let break = 5;
function return() {}
const if = true;
let class = 'myClass';
var new = 'value';

Workarounds:

// ✓ Valid alternatives
let breakPoint = 5;
function returnValue() {}
const condition = true;
let className = 'myClass';
var newValue = 'value';
Warning: While some keywords like undefined and NaN are not technically reserved, they reference global properties and should not be used as variable names to avoid confusion.

Section 1 Summary

  • JavaScript uses various statement types for different purposes (expressions, declarations, blocks)
  • Use let/const over var for block scoping and better error detection
  • Follow naming conventions: camelCase for variables/functions, PascalCase for classes, UPPER_SNAKE_CASE for constants
  • JSDoc comments provide type information and documentation for better IDE support
  • Strict mode catches common mistakes and prevents unsafe actions (auto-enabled in modules/classes)
  • Avoid using reserved keywords as identifiers; they're reserved for language features

2. Data Types and Type System

2.1 Primitive Types (string, number, boolean, null, undefined, symbol, bigint)

Type Description typeof Result Default Value Wrapper Object
string Immutable sequence of Unicode characters "string" "" (empty string) String
number 64-bit IEEE 754 floating point (±1.8×10308) "number" 0 Number
boolean Logical true or false value "boolean" false Boolean
null Intentional absence of value (assignment) "object" BUG N/A None
undefined Variable declared but not assigned "undefined" undefined None
symbol ES6 Unique, immutable identifier for object properties "symbol" N/A Symbol
bigint ES2020 Arbitrary precision integers (beyond 253-1) "bigint" 0n BigInt
Type Literal Syntax Constructor Special Values
string 'text', "text", `template` String("value") Empty string ""
number 42, 3.14, 1e5, 0xFF, 0o77, 0b1010 Number("123") NaN, Infinity, -Infinity
boolean true, false Boolean(value) Only true and false
null null N/A Only null
undefined undefined undefined or void 0 Only undefined
symbol N/A (must use function) Symbol("desc") Each symbol is unique
bigint 42n, 0xFFn, 0o77n, 0b1010n BigInt("123") No decimal values

Example: Primitive type characteristics

// Immutability: primitives are immutable
let str = "hello";
str[0] = "H"; // No effect, strings are immutable
console.log(str); // "hello"

// Primitive vs Wrapper auto-boxing
let num = 42;
console.log(num.toString()); // "42" - temporary wrapper created

// Special number values
console.log(1 / 0);        // Infinity
console.log(-1 / 0);       // -Infinity
console.log(0 / 0);        // NaN
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991

// Symbols are unique
const sym1 = Symbol("desc");
const sym2 = Symbol("desc");
console.log(sym1 === sym2); // false (each is unique)

// BigInt for large integers
const bigNum = 9007199254740991n + 1n;
console.log(bigNum); // 9007199254740992n
Warning: typeof null returns "object" due to a legacy bug. Use value === null for null checking.

2.2 Reference Types (object, array, function)

Type Description typeof Result Mutable Stored By
Object Collection of key-value pairs (properties) "object" ✓ Yes Reference
Array Ordered collection with numeric indices (subtype of Object) "object" ✓ Yes Reference
Function Callable object with executable code "function" ✓ Yes (properties) Reference
Date Represents date and time (milliseconds since epoch) "object" ✓ Yes Reference
RegExp Regular expression pattern matcher "object" ✓ Yes Reference
Map ES6 Key-value pairs with any key type "object" ✓ Yes Reference
Set ES6 Collection of unique values "object" ✓ Yes Reference
Behavior Primitives Reference Types
Assignment Copies the value Copies the reference (both point to same object)
Comparison (===) Compares actual values Compares references (same object?)
Mutability Immutable (cannot change) Mutable (can modify properties)
Storage Stored directly in variable (stack) Variable holds reference pointer (heap)
Passing to Functions Pass by value (copy) Pass by reference (can mutate original)

Example: Primitive vs Reference behavior

// Primitives: value copied
let a = 5;
let b = a; // Copy value
b = 10;
console.log(a); // 5 (unchanged)

// References: pointer copied
let obj1 = { x: 5 };
let obj2 = obj1; // Copy reference
obj2.x = 10;
console.log(obj1.x); // 10 (both point to same object)

// Comparison
let str1 = "hello";
let str2 = "hello";
console.log(str1 === str2); // true (value comparison)

let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false (different references)
console.log(arr1 === arr1); // true (same reference)

// Function parameter behavior
function modifyPrimitive(x) {
    x = 100; // Local copy modified
}
let num = 5;
modifyPrimitive(num);
console.log(num); // 5 (original unchanged)

function modifyObject(obj) {
    obj.value = 100; // Original object modified
}
let data = { value: 5 };
modifyObject(data);
console.log(data.value); // 100 (original changed)

2.3 Type Conversion and Coercion Rules

Target Type From String From Number From Boolean From Object
String Unchanged "5" "true"/"false" toString() or valueOf()
Number 123 or NaN Unchanged 1 / 0 valueOf() then toString()
Boolean "" → false, else true 0, NaN → false, else true Unchanged Always true
Conversion Method Syntax Use Case Example
Explicit String String(x), x.toString(), x + "" Force string conversion String(123) → "123"
Explicit Number Number(x), +x, parseInt(), parseFloat() Force numeric conversion Number("123") → 123
Explicit Boolean Boolean(x), !!x Force boolean conversion Boolean(0) → false
Implicit Coercion Operators: +, -, *, /, == Automatic conversion by operators "5" * 2 → 10
Value to String to Number to Boolean
undefined "undefined" NaN false
null "null" 0 false
true "true" 1 true
false "false" 0 false
"" "" 0 false
"123" "123" 123 true
"abc" "abc" NaN true
0, -0 "0" 0 false
NaN "NaN" NaN false
Infinity "Infinity" Infinity true
{}, [] "[object Object]", "" NaN, 0 true

Example: Type coercion in action

// String coercion (+ with string)
console.log("5" + 3);      // "53" (number to string)
console.log("5" + true);   // "5true"

// Numeric coercion (other operators)
console.log("5" - 3);      // 2 (string to number)
console.log("5" * "2");    // 10
console.log(true + 1);     // 2 (true → 1)
console.log(false + 1);    // 1 (false → 0)

// Boolean coercion
console.log(Boolean(""));    // false
console.log(Boolean(0));     // false
console.log(Boolean(NaN));   // false
console.log(Boolean(null));  // false
console.log(Boolean(undefined)); // false
console.log(Boolean([]));    // true (object)
console.log(Boolean({}));    // true (object)

// Explicit conversions
console.log(Number("123"));     // 123
console.log(Number("12.5"));    // 12.5
console.log(Number("abc"));     // NaN
console.log(parseInt("123px")); // 123 (parses until non-digit)
console.log(String(123));       // "123"
console.log(+"42");             // 42 (unary +)

// Falsy values (6 total)
if (!undefined || !null || !0 || !NaN || !"" || !false) {
    console.log("All falsy values");
}
Falsy Values (6 total): false, 0, "", null, undefined, NaN. Everything else is truthy (including [], {}, "0", "false").

2.4 Type Checking (typeof, instanceof, Array.isArray)

Operator/Method Syntax Returns Use Case Limitations
typeof typeof value String type name Check primitive types typeof null === "object" (bug); arrays return "object"
instanceof obj instanceof Constructor Boolean Check prototype chain Doesn't work across frames/windows; primitives always false
Array.isArray() Array.isArray(value) Boolean Reliable array detection Only for arrays
Object.prototype.toString Object.prototype.toString.call(value) "[object Type]" Most reliable type checking Verbose syntax
constructor value.constructor === Type Boolean Check constructor Can be overridden; fails for null/undefined
Value typeof instanceof Best Detection Method
42 "number" N/A (primitive) typeof x === "number"
"text" "string" N/A (primitive) typeof x === "string"
true "boolean" N/A (primitive) typeof x === "boolean"
null "object" ⚠️ false x === null
undefined "undefined" false x === undefined or typeof x === "undefined"
[] "object" true (Array) Array.isArray(x)
{} "object" true (Object) typeof x === "object" && x !== null
function(){} "function" true (Function) typeof x === "function"
Symbol() "symbol" N/A (primitive) typeof x === "symbol"
42n "bigint" N/A (primitive) typeof x === "bigint"

Example: Type checking techniques

// typeof: basic primitive checking
console.log(typeof 42);           // "number"
console.log(typeof "hello");      // "string"
console.log(typeof true);         // "boolean"
console.log(typeof undefined);    // "undefined"
console.log(typeof Symbol());     // "symbol"
console.log(typeof 42n);          // "bigint"
console.log(typeof null);         // "object" ⚠️ (legacy bug)
console.log(typeof []);           // "object"
console.log(typeof {});           // "object"
console.log(typeof function(){}); // "function"

// instanceof: prototype chain checking
console.log([] instanceof Array);       // true
console.log({} instanceof Object);      // true
console.log(new Date() instanceof Date); // true
console.log(42 instanceof Number);      // false (primitive)

// Array.isArray: reliable array detection
console.log(Array.isArray([]));         // true
console.log(Array.isArray({}));         // false
console.log(Array.isArray("array"));    // false

// Object.prototype.toString: most reliable
const toString = Object.prototype.toString;
console.log(toString.call([]));         // "[object Array]"
console.log(toString.call({}));         // "[object Object]"
console.log(toString.call(null));       // "[object Null]"
console.log(toString.call(undefined));  // "[object Undefined]"
console.log(toString.call(new Date())); // "[object Date]"
console.log(toString.call(/regex/));    // "[object RegExp]"

// Utility type checking function
function getType(value) {
    if (value === null) return 'null';
    if (Array.isArray(value)) return 'array';
    return typeof value;
}
console.log(getType(null));  // "null"
console.log(getType([]));    // "array"
console.log(getType(42));    // "number"

2.5 Symbol Type and Well-known Symbols

Feature Description Syntax Use Case
Symbol Creation Creates unique identifier Symbol("description") Unique object property keys
Symbol Registry Global shared symbols Symbol.for("key") Cross-realm symbol sharing
Symbol.keyFor() Get key from registry symbol Symbol.keyFor(sym) Retrieve symbol key name
Symbol Properties Hidden from enumeration obj[sym] = value Private-like properties (not truly private)
Well-known Symbol Purpose Usage
Symbol.iterator Defines default iterator Used by for...of loops, spread operator
Symbol.asyncIterator Defines async iterator Used by for await...of loops
Symbol.toStringTag Customizes toString() output Changes Object.prototype.toString.call() result
Symbol.toPrimitive Defines type coercion behavior Controls conversion to primitive types
Symbol.hasInstance Customizes instanceof Overrides instanceof behavior
Symbol.species Constructor for derived objects Used in Array/Promise methods
Symbol.match, matchAll String matching behavior Used by String.prototype.match()
Symbol.replace, search, split String manipulation behavior Used by respective string methods

Example: Symbol usage and well-known symbols

// Basic symbol creation (each is unique)
const sym1 = Symbol("id");
const sym2 = Symbol("id");
console.log(sym1 === sym2); // false (unique)

// Symbols as object properties
const id = Symbol("id");
const user = {
    name: "John",
    [id]: 123 // Symbol as computed property
};
console.log(user[id]); // 123
console.log(Object.keys(user)); // ["name"] (symbol hidden)

// Symbol registry (shared across realm)
const globalSym1 = Symbol.for("app.id");
const globalSym2 = Symbol.for("app.id");
console.log(globalSym1 === globalSym2); // true (same symbol)
console.log(Symbol.keyFor(globalSym1)); // "app.id"

// Well-known symbols: Symbol.iterator
const iterable = {
    data: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => ({
                value: this.data[index],
                done: index++ >= this.data.length
            })
        };
    }
};
console.log([...iterable]); // [1, 2, 3]

// Symbol.toStringTag
class CustomClass {
    get [Symbol.toStringTag]() {
        return "CustomClass";
    }
}
const obj = new CustomClass();
console.log(Object.prototype.toString.call(obj)); // "[object CustomClass]"

// Symbol.toPrimitive
const obj2 = {
    [Symbol.toPrimitive](hint) {
        if (hint === "number") return 42;
        if (hint === "string") return "hello";
        return true;
    }
};
console.log(+obj2);     // 42 (number hint)
console.log(`${obj2}`); // "hello" (string hint)
console.log(obj2 + ""); // "true" (default hint)

2.6 BigInt for Large Integer Operations

Feature Syntax Description Example
BigInt Literal value + "n" Append 'n' to integer literal 123n, 0xFFn, 0o77n, 0b1010n
BigInt Constructor BigInt(value) Convert number/string to BigInt BigInt("9007199254740991")
Range Arbitrary precision No upper/lower limit (memory-limited) Can represent integers beyond 253-1
Type typeof x Returns "bigint" typeof 42n === "bigint"
Operation BigInt Support Example Notes
Arithmetic +, -, *, /, %, ** 10n + 20n = 30n Both operands must be BigInt
Comparison <, >, <=, >=, ==, != 10n < 20n, 10n == 10 == coerces; === is strict
Bitwise &, |, ^, ~, <<, >> 5n & 3n = 1n >>> not supported (unsigned)
Division / (integer division) 7n / 2n = 3n Truncates decimal (rounds toward zero)
Unary Minus -x -42n = -42n Negation supported
Unary Plus ❌ Not supported +42n ❌ TypeError Use Number() to convert
Limitation Description Workaround
No Mixed Operations Cannot mix BigInt with Number Explicitly convert: 10n + BigInt(5) or Number(10n) + 5
No Decimal Values BigInt only for integers Use Number for decimal/floating-point
Math Object Math.* methods don't support BigInt Implement custom logic or convert to Number
JSON Serialization JSON.stringify() throws TypeError Custom replacer: JSON.stringify(obj, (k,v) => typeof v === 'bigint' ? v.toString() : v)
Implicit Coercion No automatic type coercion Always explicit conversion required

Example: BigInt operations and limitations

// Creating BigInts
const big1 = 9007199254740991n;
const big2 = BigInt("9007199254740991");
const big3 = BigInt(Number.MAX_SAFE_INTEGER);

// Arithmetic operations
console.log(100n + 50n);   // 150n
console.log(100n - 50n);   // 50n
console.log(100n * 2n);    // 200n
console.log(100n / 3n);    // 33n (integer division, truncates)
console.log(100n % 3n);    // 1n
console.log(2n ** 100n);   // Very large number (2^100)

// Comparison
console.log(10n === 10);   // false (strict equality)
console.log(10n == 10);    // true (loose equality with coercion)
console.log(10n < 20n);    // true
console.log(5n > 3);       // true (coercion in comparison)

// Limitations
// console.log(10n + 5);   // ❌ TypeError: Cannot mix BigInt and Number
console.log(10n + BigInt(5)); // ✓ 15n (explicit conversion)
console.log(Number(10n) + 5); // ✓ 15 (convert to Number)

// console.log(+10n);      // ❌ TypeError: Cannot convert BigInt
console.log(Number(10n));  // ✓ 10 (explicit conversion)

// Bitwise operations
console.log(5n & 3n);     // 1n (binary AND)
console.log(5n | 3n);     // 7n (binary OR)
console.log(5n ^ 3n);     // 6n (binary XOR)
console.log(5n << 2n);    // 20n (left shift)

// Use case: Precise large integer calculations
const largeNumber = 9007199254740992n; // Beyond Number.MAX_SAFE_INTEGER
console.log(largeNumber + 1n); // 9007199254740993n (exact)
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992 (precision lost)
Warning: BigInt cannot be mixed with regular numbers in arithmetic operations. Always explicitly convert using BigInt() or Number().

Section 2 Summary

  • JavaScript has 7 primitive types: string, number, boolean, null, undefined, symbol, bigint
  • Primitives are immutable and passed by value; reference types are mutable and passed by reference
  • Type coercion happens implicitly with operators; 6 falsy values exist: false, 0, "", null, undefined, NaN
  • Use typeof for primitives, Array.isArray() for arrays, and instanceof for prototype checking
  • Symbols provide unique identifiers for object properties; well-known symbols customize language behavior
  • BigInt enables arbitrary-precision integer math beyond Number.MAX_SAFE_INTEGER (253-1)

3. Variables, Scope, and Closure

3.1 Variable Hoisting and Temporal Dead Zone

Declaration Hoisting Behavior Initialization TDZ Access Before Declaration
var Hoisted to function/global scope Initialized to undefined ❌ No TDZ Returns undefined
let Hoisted to block scope Not initialized ✓ Has TDZ ReferenceError
const Hoisted to block scope Not initialized ✓ Has TDZ ReferenceError
function (declaration) Fully hoisted with definition Fully initialized ❌ No TDZ Callable before declaration
function (expression) Variable hoisted only Depends on var/let/const Depends on var/let/const Not callable (undefined or TDZ)
class Hoisted to block scope Not initialized ✓ Has TDZ ReferenceError
Concept Description Key Points
Hoisting JavaScript moves declarations to top of their scope during compilation Only declarations hoisted, not initializations
Temporal Dead Zone (TDZ) Time between entering scope and variable initialization where access causes error Exists for let, const, and class
TDZ Start Beginning of enclosing block scope Not visible in code; conceptual time period
TDZ End Point where variable is declared and initialized Variable becomes usable after this point

Example: Hoisting and TDZ behavior

// var hoisting: declaration hoisted, initialized to undefined
console.log(x); // undefined (no error)
var x = 5;
console.log(x); // 5

// let/const TDZ: declaration hoisted but not initialized
// console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
console.log(y); // 10

// Function declaration: fully hoisted
greet(); // "Hello" (works before declaration)
function greet() {
    console.log("Hello");
}

// Function expression: only variable hoisted
// sayHi(); // TypeError: sayHi is not a function
var sayHi = function() {
    console.log("Hi");
};
sayHi(); // "Hi"

// TDZ example with block scope
{
    // TDZ starts here for 'temp'
    // console.log(temp); // ReferenceError
    // const result = doSomething(temp); // ReferenceError
    
    let temp = 5; // TDZ ends here
    console.log(temp); // 5 (now accessible)
}

// Class TDZ
// const p = new Person(); // ReferenceError
class Person {
    constructor(name) { this.name = name; }
}
const p = new Person("John"); // Works after declaration
Note: TDZ prevents using variables before initialization, catching potential bugs. Always declare variables at the top of their scope for clarity.

3.2 Scope Types (Global, Function, Block, Module)

Scope Type Created By Variables Lifetime Access
Global Scope Outside all functions/blocks var, let, const, functions Entire program execution Accessible everywhere
Function Scope Function declarations/expressions var, parameters, inner functions Function execution Within function and nested functions
Block Scope { } braces, loops, if/switch let, const, class Block execution Within block only
Module Scope ES6 modules (.js files with import/export) All declarations in module file Module lifetime Isolated; explicit exports required
Catch Block Scope catch(error) { } Error parameter, let, const Catch block execution Within catch block only
Declaration Global Scope Function Scope Block Scope
var ✓ Function or global ✓ Function-scoped ❌ Ignores blocks
let ✓ Global (no window property) ✓ Function-scoped ✓ Block-scoped
const ✓ Global (no window property) ✓ Function-scoped ✓ Block-scoped
function ✓ Global ✓ Function-scoped ❌ Block-scoped in strict mode only

Example: Different scope types

// Global scope
var globalVar = 'global var';
let globalLet = 'global let';
const globalConst = 'global const';

function demoScopes() {
    // Function scope
    var functionVar = 'function scoped';
    let functionLet = 'function scoped';
    
    console.log(globalVar); // Accessible
    
    if (true) {
        // Block scope
        var blockVar = 'not block scoped (var)';
        let blockLet = 'block scoped';
        const blockConst = 'block scoped';
        
        console.log(functionVar); // Accessible from outer function
        console.log(blockLet);    // Accessible within block
    }
    
    console.log(blockVar);  // Accessible (var ignores blocks)
    // console.log(blockLet); // ReferenceError (block-scoped)
    
    // Loop scope
    for (let i = 0; i < 3; i++) {
        // 'i' is block-scoped to this loop
        setTimeout(() => console.log(i), 100); // Prints 0, 1, 2
    }
    
    for (var j = 0; j < 3; j++) {
        // 'j' is function-scoped (shared)
        setTimeout(() => console.log(j), 100); // Prints 3, 3, 3
    }
}

// Module scope (in ES6 modules)
// Variables not automatically global
// export const moduleVar = 'exported';
// const privateVar = 'not exported';

// Catch block scope
try {
    throw new Error('test');
} catch (error) {
    // 'error' is scoped to catch block
    let catchVar = 'catch scoped';
    console.log(error.message);
}
// console.log(error); // ReferenceError
// console.log(catchVar); // ReferenceError
Warning: Global variables create properties on the global object (window in browsers) with var, but not with let/const. Avoid global variables to prevent naming conflicts.

3.3 Lexical Scope and Scope Chain

Concept Description Behavior
Lexical Scope Scope determined by code location (where functions are written) Inner functions access outer function variables
Scope Chain Hierarchy of nested scopes searched for variable resolution Searches from inner → outer until found or reaches global
Static Scoping Another name for lexical scoping (opposite of dynamic scoping) Scope determined at write-time, not runtime
Outer Environment Reference Link from execution context to parent scope Created when function is defined, not when called
Variable Lookup Process Step Action
Step 1 Current Scope Check if variable exists in current local scope
Step 2 Parent Scope If not found, move to enclosing (parent) scope
Step 3 Repeat Continue up the scope chain through ancestors
Step 4 Global Scope Check global scope as last resort
Step 5 Not Found ReferenceError if not found anywhere in chain

Example: Lexical scope and scope chain

// Scope chain demonstration
const globalVar = 'global';

function outer() {
    const outerVar = 'outer';
    
    function middle() {
        const middleVar = 'middle';
        
        function inner() {
            const innerVar = 'inner';
            
            // Scope chain: inner → middle → outer → global
            console.log(innerVar);   // Found in inner scope
            console.log(middleVar);  // Found in middle scope (parent)
            console.log(outerVar);   // Found in outer scope (grandparent)
            console.log(globalVar);  // Found in global scope
            
            // Variable lookup order:
            // 1. Check inner scope
            // 2. Check middle scope
            // 3. Check outer scope
            // 4. Check global scope
            // 5. ReferenceError if not found
        }
        
        inner();
        // console.log(innerVar); // ReferenceError (not in scope chain)
    }
    
    middle();
}

outer();

// Lexical scope is determined by code structure
function makeCounter() {
    let count = 0; // In outer function scope
    
    return function() {
        // Lexically scoped to access 'count' from parent
        return ++count;
    };
}

const counter1 = makeCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2

const counter2 = makeCounter();
console.log(counter2()); // 1 (separate scope chain)

// Scope determined at definition, not invocation
let x = 'global x';

function showX() {
    console.log(x); // Lexically bound to global 'x'
}

function demo() {
    let x = 'local x';
    showX(); // Prints "global x" (not "local x")
             // Scope determined where showX was defined
}

demo();
Note: Lexical scope is determined by where the function is written, not where it's called. This enables closures and predictable variable access.

3.4 Variable Shadowing and Name Resolution

Concept Description Behavior Recommendation
Variable Shadowing Inner variable hides outer variable with same name Inner scope variable takes precedence Avoid shadowing; use different names
Name Resolution Process of finding which variable identifier refers to Searches scope chain from inner to outer Use descriptive, unique names
Parameter Shadowing Function parameter shadows outer variable Parameter accessible, outer variable hidden Common pattern; usually intentional
Block Shadowing Block-scoped variable shadows outer scope Only within block; outer restored after Use for temporary local variables
Shadowing Type Outer Inner Allowed? Behavior
var shadows var var var ✓ Yes Redeclaration in same scope or shadowing in nested
let/const shadows var var let/const ✓ Yes Inner hides outer; outer unchanged
var shadows let/const let/const var Depends Error if same function scope; OK if nested function
let/const shadows let/const let/const let/const ✓ In different block ✗ SyntaxError in same block
Parameter shadowing Any Parameter ✓ Yes Parameter has priority in function

Example: Variable shadowing scenarios

// Basic shadowing
let x = 'outer';

function demo() {
    let x = 'inner'; // Shadows outer 'x'
    console.log(x);  // "inner"
}

demo();
console.log(x); // "outer" (outer unchanged)

// Parameter shadowing
let value = 100;

function processValue(value) { // Parameter shadows outer 'value'
    console.log(value); // Uses parameter, not outer variable
    value = 200;        // Modifies parameter only
}

processValue(50);  // Prints 50
console.log(value); // 100 (outer unchanged)

// Block shadowing
let count = 10;

if (true) {
    let count = 20; // Shadows outer 'count' in block
    console.log(count); // 20
}

console.log(count); // 10 (outer restored after block)

// Nested function shadowing
function outer() {
    let name = 'outer';
    
    function inner() {
        let name = 'inner'; // Shadows parent function's 'name'
        console.log(name);  // "inner"
    }
    
    inner();
    console.log(name); // "outer"
}

outer();

// Loop variable shadowing
for (let i = 0; i < 3; i++) {
    console.log(i); // Loop 'i'
    
    for (let i = 0; i < 2; i++) {
        console.log('  ' + i); // Inner loop 'i' shadows outer
    }
}

// Problematic shadowing to avoid
function calculateTotal() {
    let total = 0;
    
    if (true) {
        let total = 100; // Shadowing can be confusing
        // Which 'total' is which?
    }
    
    return total; // Returns 0, not 100
}
Warning: While shadowing is allowed, excessive use can make code confusing. Use distinct variable names for clarity unless shadowing is intentional (e.g., function parameters).

3.5 Closure Patterns and Memory Management

Concept Definition Key Characteristics
Closure Function bundled with its lexical environment (surrounding state) Retains access to outer scope variables even after outer function returns
Closure Creation Created every time a function is created Inner function "closes over" variables from outer scope
Closure Use Cases Data privacy, factory functions, callbacks, event handlers Maintains state between function calls
Memory Retention Closed-over variables remain in memory Can cause memory leaks if not managed properly
Closure Pattern Use Case Example Structure
Private Variables Encapsulation, data hiding Function returns methods that access private variables
Factory Functions Create objects with private state Function returns object with methods (closures)
Module Pattern Namespace management, public/private API IIFE returns object with public methods
Callbacks/Event Handlers Preserve context for async operations Function captures variables for later execution
Currying/Partial Application Function transformation, configuration Function returns function with captured arguments
Memoization Performance optimization, caching Closure maintains cache of computed values

Example: Common closure patterns

// 1. Private variables pattern
function createCounter() {
    let count = 0; // Private variable
    
    return {
        increment: () => ++count,
        decrement: () => --count,
        getCount: () => count
    };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount());  // 2
// console.log(counter.count); // undefined (private)

// 2. Factory function pattern
function createUser(name) {
    let _name = name; // Private
    let _sessions = 0;
    
    return {
        getName: () => _name,
        login: () => ++_sessions,
        getSessions: () => _sessions
    };
}

const user1 = createUser("Alice");
const user2 = createUser("Bob");
user1.login();
console.log(user1.getSessions()); // 1
console.log(user2.getSessions()); // 0

// 3. Module pattern with IIFE
const calculator = (function() {
    let history = []; // Private state
    
    return {
        add: (a, b) => {
            const result = a + b;
            history.push(`${a} + ${b} = ${result}`);
            return result;
        },
        getHistory: () => [...history] // Return copy
    };
})();

calculator.add(5, 3);
console.log(calculator.getHistory()); // ["5 + 3 = 8"]

// 4. Callback closure preserving context
function fetchDataWithRetry(url, retries) {
    let attempts = 0; // Captured by callback
    
    function attemptFetch() {
        fetch(url)
            .catch(error => {
                if (++attempts < retries) {
                    console.log(`Retry ${attempts}`);
                    attemptFetch(); // Closure accesses 'attempts'
                }
            });
    }
    
    attemptFetch();
}

// 5. Currying with closures
function multiply(a) {
    return function(b) {
        return function(c) {
            return a * b * c;
        };
    };
}

const multiplyBy2 = multiply(2);
const multiplyBy2And3 = multiplyBy2(3);
console.log(multiplyBy2And3(4)); // 24

// 6. Memoization pattern
function memoize(fn) {
    const cache = {}; // Closure maintains cache
    
    return function(...args) {
        const key = JSON.stringify(args);
        if (key in cache) {
            return cache[key];
        }
        const result = fn(...args);
        cache[key] = result;
        return result;
    };
}

const factorial = memoize(n => n <= 1 ? 1 : n * factorial(n - 1));
console.log(factorial(5)); // Computed: 120
console.log(factorial(5)); // Cached: 120
Memory Consideration Issue Solution
Retained Variables Closed-over variables can't be garbage collected Clear references when done (obj = null)
Event Listeners Closure in listener prevents GC of entire scope Remove listeners: removeEventListener
Timers/Intervals Callback closures keep references alive Clear timers: clearTimeout/clearInterval
Large Data Structures Closure inadvertently captures large objects Extract only needed data before creating closure
Note: Closures are powerful but retain references to their outer scope. Be mindful of memory usage, especially with long-lived closures or large captured variables.

3.6 Module Scope and Variable Isolation

Module Feature Description Benefit
Module Scope Each ES6 module has its own scope (file-level) Variables don't pollute global scope by default
Variable Isolation Module variables are private unless explicitly exported Prevents naming conflicts between modules
Strict Mode Modules automatically run in strict mode No need for 'use strict'; directive
Top-level this this is undefined at module top level Not bound to global object (safer)
Static Imports Import declarations hoisted and executed first Enables static analysis and tree-shaking
Module Pattern Syntax Use Case
Named Export export const x = 1; Export multiple items from module
Default Export export default function() {} Main export from module (one per module)
Named Import import { x, y } from './mod'; Import specific exports
Default Import import mod from './mod'; Import default export
Namespace Import import * as mod from './mod'; Import all exports as object
Re-export export { x } from './mod'; Export from another module (barrel pattern)
Dynamic Import import('./mod').then(...) Load modules conditionally/lazily

Example: Module scope and isolation

// ===== math.js (Module A) =====
// Private variables (not exported)
const PI = 3.14159;
let calculationCount = 0;

// Private helper function
function log(operation) {
    calculationCount++;
    console.log(`Operation: ${operation}, Count: ${calculationCount}`);
}

// Named exports (public API)
export function add(a, b) {
    log('add');
    return a + b;
}

export function multiply(a, b) {
    log('multiply');
    return a * b;
}

export const TAU = 2 * PI; // Export constant

// Default export
export default function calculate(expr) {
    return eval(expr); // Example only
}

// ===== user.js (Module B) =====
// Module variables are isolated
const userCount = 0; // Won't conflict with other modules

class User {
    constructor(name) {
        this.name = name;
    }
}

export { User, userCount };

// ===== app.js (Main module) =====
// Import from math.js
import calculate, { add, multiply, TAU } from './math.js';
// Import from user.js
import { User, userCount } from './user.js';

// Module-scoped variables (private to this module)
const appName = 'MyApp';
let config = { debug: true };

console.log(add(5, 3));        // 8
console.log(multiply(2, 4));   // 8
console.log(TAU);              // 6.28318

// Cannot access private variables from math.js
// console.log(PI); // ReferenceError
// console.log(calculationCount); // ReferenceError

const user = new User('Alice');

// Top-level 'this' is undefined in modules
console.log(this); // undefined (not window)

// ===== Dynamic imports =====
async function loadModule() {
    if (config.debug) {
        const debugModule = await import('./debug.js');
        debugModule.log('Debug mode enabled');
    }
}

// ===== Barrel export pattern (index.js) =====
// Re-export from multiple modules
export { add, multiply } from './math.js';
export { User } from './user.js';
export { default as calculate } from './math.js';

Module Benefits:

  • Encapsulation: private implementation details
  • Explicit dependencies: clear import statements
  • No global pollution: isolated scope
  • Reusability: modular, composable code
  • Static analysis: enables tree-shaking

Before Modules (Legacy):

// IIFE pattern for isolation
(function() {
    var private = 'hidden';
    
    window.MyModule = {
        public: function() {
            return private;
        }
    };
})();
Note: ES6 modules provide true encapsulation with file-level scope. Variables are private by default, eliminating need for IIFE patterns and reducing global scope pollution.

Section 3 Summary

  • Hoisting moves declarations to top of scope; let/const/class have TDZ (ReferenceError before initialization)
  • 4 scope types: Global (everywhere), Function (var), Block (let/const), Module (file-level isolation)
  • Lexical scope determined by code structure; scope chain searches inner → outer → global for variables
  • Variable shadowing occurs when inner scope variable hides outer one; use distinct names for clarity
  • Closures retain access to outer scope variables; enable private state but require memory management
  • ES6 modules provide true isolation with file-level scope; variables private unless exported

4. Operators and Expressions Reference

4.1 Arithmetic Operators and Math Operations

Operator Name Description Example Result
+ Addition / Unary Plus Adds numbers or concatenates strings; converts to number 5 + 3, +"42" 8, 42
- Subtraction / Unary Minus Subtracts numbers; negates value 5 - 3, -5 2, -5
* Multiplication Multiplies numbers 5 * 3 15
/ Division Divides numbers (floating-point result) 10 / 3 3.3333...
% Remainder / Modulo Returns remainder of division 10 % 3 1
** Exponentiation ES2016 Raises to power 2 ** 3 8
++ Increment Increases by 1 (prefix or postfix) ++x, x++ Pre: return new; Post: return old
-- Decrement Decreases by 1 (prefix or postfix) --x, x-- Pre: return new; Post: return old
Operation Type Behavior Special Cases
Number + Number Numeric addition 5 + 3 = 8
String + Any String concatenation "5" + 3 = "53", "a" + true = "atrue"
Division by Zero Returns Infinity 5 / 0 = Infinity, -5 / 0 = -Infinity
0 / 0 Returns NaN Indeterminate result
NaN in Operations Propagates NaN NaN + 5 = NaN, NaN * 2 = NaN
Infinity Operations Mathematical infinity rules Infinity + 1 = Infinity, Infinity * -1 = -Infinity

Example: Arithmetic operators and edge cases

// Basic arithmetic
console.log(10 + 5);   // 15
console.log(10 - 5);   // 5
console.log(10 * 5);   // 50
console.log(10 / 5);   // 2
console.log(10 % 3);   // 1 (remainder)
console.log(2 ** 3);   // 8 (exponentiation)

// Increment/Decrement
let x = 5;
console.log(x++);      // 5 (returns old value, then increments)
console.log(x);        // 6
console.log(++x);      // 7 (increments, then returns new value)
console.log(x--);      // 7 (returns old value, then decrements)
console.log(--x);      // 5 (decrements, then returns new value)

// String concatenation with +
console.log("Hello" + " " + "World");  // "Hello World"
console.log("5" + 3);    // "53" (string concatenation)
console.log(5 + "3");    // "53"
console.log("5" + 3 + 2); // "532"
console.log(5 + 3 + "2"); // "82" (left to right: 5+3=8, then 8+"2"="82")

// Type coercion with other operators
console.log("10" - 5);   // 5 (string to number)
console.log("10" * "2"); // 20
console.log("10" / "2"); // 5
console.log("10" % 3);   // 1

// Unary operators
console.log(+"42");      // 42 (string to number)
console.log(+"abc");     // NaN
console.log(-"5");       // -5

// Special values
console.log(5 / 0);      // Infinity
console.log(-5 / 0);     // -Infinity
console.log(0 / 0);      // NaN
console.log(NaN + 10);   // NaN (NaN propagates)
console.log(Infinity - 1); // Infinity

4.2 Assignment Operators and Compound Assignment

Operator Name Equivalent Example Result
= Simple Assignment N/A x = 5 x is 5
+= Addition Assignment x = x + y x += 3 Adds and assigns
-= Subtraction Assignment x = x - y x -= 3 Subtracts and assigns
*= Multiplication Assignment x = x * y x *= 3 Multiplies and assigns
/= Division Assignment x = x / y x /= 3 Divides and assigns
%= Remainder Assignment x = x % y x %= 3 Remainder and assigns
**= Exponentiation Assignment x = x ** y x **= 3 Exponentiates and assigns
<<= Left Shift Assignment x = x << y x <<= 2 Left shift and assigns
>>= Right Shift Assignment x = x >> y x >>= 2 Right shift and assigns
>>>= Unsigned Right Shift Assignment x = x >>> y x >>>= 2 Unsigned shift and assigns
&= Bitwise AND Assignment x = x & y x &= 3 AND and assigns
|= Bitwise OR Assignment x = x | y x |= 3 OR and assigns
^= Bitwise XOR Assignment x = x ^ y x |= 3 XOR and assigns
Feature Description Example
Destructuring Assignment Unpack values from arrays/objects [a, b] = [1, 2], {x, y} = obj
Multiple Assignment Chain assignments (right-to-left) a = b = c = 5 (all get 5)
Return Value Assignment returns assigned value y = (x = 5) (both are 5)
Compound Benefits Shorter, clearer, evaluates left side once arr[i++] += 5 increments i once

Example: Assignment operators

// Simple assignment
let x = 10;

// Compound assignments
x += 5;   // x = x + 5;  x is now 15
x -= 3;   // x = x - 3;  x is now 12
x *= 2;   // x = x * 2;  x is now 24
x /= 4;   // x = x / 4;  x is now 6
x %= 5;   // x = x % 5;  x is now 1
x **= 3;  // x = x ** 3; x is now 1

// String concatenation with +=
let str = "Hello";
str += " World";  // str is "Hello World"

// Multiple assignment (right-to-left)
let a, b, c;
a = b = c = 5;  // All are 5
console.log(a, b, c);  // 5 5 5

// Assignment returns value
let y = (x = 10);  // x is 10, y is 10
console.log(x, y);  // 10 10

// Destructuring assignment
let [p, q] = [1, 2];
console.log(p, q);  // 1 2

let {name, age} = {name: "John", age: 30};
console.log(name, age);  // "John" 30

// Compound assignment with side effects
let arr = [10, 20, 30];
let i = 0;
arr[i++] += 5;  // arr[0] becomes 15, then i increments
console.log(arr[0], i);  // 15 1

// Swapping with destructuring
let m = 1, n = 2;
[m, n] = [n, m];  // Swap
console.log(m, n);  // 2 1

4.3 Comparison Operators and Equality Rules

Operator Name Description Type Coercion Example
== Equality (loose) Checks value equality with type coercion ✓ Yes 5 == "5"true
=== Strict Equality Checks value and type equality ✗ No 5 === "5"false
!= Inequality (loose) Checks value inequality with coercion ✓ Yes 5 != "6"true
!== Strict Inequality Checks value or type inequality ✗ No 5 !== "5"true
< Less Than Checks if left < right ✓ Yes 5 < 10true
> Greater Than Checks if left > right ✓ Yes 10 > 5true
<= Less Than or Equal Checks if left ≤ right ✓ Yes 5 <= 5true
>= Greater Than or Equal Checks if left ≥ right ✓ Yes 10 >= 10true
Comparison == (loose) === (strict) Reason
5 vs "5" true false String coerced to number
0 vs false true false Boolean coerced to number
null vs undefined true false Special case: only equal to each other
"" vs 0 true false Empty string coerced to 0
NaN vs NaN false false NaN not equal to itself (use isNaN())
[] vs [] false false Different object references
0 vs -0 true true Considered equal (use Object.is() to distinguish)

Example: Equality and comparison operators

// Strict equality (===) - recommended
console.log(5 === 5);        // true
console.log(5 === "5");      // false (different types)
console.log(null === null);  // true
console.log(undefined === undefined); // true

// Loose equality (==) - with type coercion
console.log(5 == "5");       // true (string coerced to number)
console.log(0 == false);     // true (boolean to number)
console.log("" == 0);        // true (string to number)
console.log(null == undefined); // true (special case)

// Inequality
console.log(5 !== "5");      // true (strict)
console.log(5 != "6");       // true (loose)

// Relational operators
console.log(5 < 10);         // true
console.log(10 > 5);         // true
console.log(5 <= 5);         // true
console.log(10 >= 10);       // true

// String comparison (lexicographic)
console.log("apple" < "banana"); // true (a < b)
console.log("10" < "9");         // true (string comparison: "1" < "9")
console.log(10 < "9");           // false (number comparison: 10 > 9)

// Special cases
console.log(NaN === NaN);     // false (use Number.isNaN())
console.log(NaN == NaN);      // false
console.log(Number.isNaN(NaN)); // true (correct way)

console.log(0 === -0);        // true
console.log(Object.is(0, -0)); // false (distinguishes +0 and -0)

// Object comparison (reference)
const obj1 = {a: 1};
const obj2 = {a: 1};
const obj3 = obj1;
console.log(obj1 === obj2);  // false (different references)
console.log(obj1 === obj3);  // true (same reference)

// Array comparison
console.log([1,2] == [1,2]); // false (different references)
console.log([1,2] === [1,2]); // false

// Tricky comparisons to avoid
console.log([] == ![]);      // true (![] is false, [] coerced to "")
console.log([] == false);    // true
console.log("" == 0);        // true
console.log(" " == 0);       // true
console.log("0" == 0);       // true
console.log("0" == false);   // true
Warning: Always use === and !== (strict equality) to avoid unexpected type coercion bugs. Use == only when you specifically need type coercion.

4.4 Logical Operators and Short-circuit Evaluation

Operator Name Returns Short-circuits Use Case
&& Logical AND First falsy value or last value ✓ If left is falsy Both conditions must be true
|| Logical OR First truthy value or last value ✓ If left is truthy At least one condition must be true
! Logical NOT Boolean (inverted) ✗ No Inverts truthiness
!! Double NOT Boolean conversion ✗ No Convert to boolean explicitly
Expression Result Explanation
true && true true Both truthy → returns last value
true && false false Right is falsy → returns false
false && true false Short-circuits at false (left)
5 && "hello" "hello" Both truthy → returns last
0 && "hello" 0 First falsy → returns 0
true || false true Short-circuits at first truthy
false || true true Returns first truthy value
false || false false Both falsy → returns last
5 || "hello" 5 First truthy → short-circuits
0 || "hello" "hello" First falsy, returns second
!true false Inverts boolean
!0 true 0 is falsy, inverted to true
!!"hello" true Convert to boolean: truthy

Example: Logical operators and short-circuit evaluation

// Logical AND (&&) - returns first falsy or last value
console.log(true && true);        // true
console.log(true && false);       // false
console.log(5 && 10);             // 10 (both truthy, returns last)
console.log(0 && 10);             // 0 (first falsy)
console.log(null && "hello");     // null (first falsy)

// Logical OR (||) - returns first truthy or last value
console.log(false || true);       // true
console.log(false || false);      // false (last value)
console.log(5 || 10);             // 5 (first truthy)
console.log(0 || 10);             // 10 (first falsy, returns second)
console.log(null || "default");   // "default" (first falsy)

// Logical NOT (!)
console.log(!true);               // false
console.log(!false);              // true
console.log(!0);                  // true (0 is falsy)
console.log(!"");                 // true ("" is falsy)
console.log(!"hello");            // false ("hello" is truthy)

// Double NOT (!!) - convert to boolean
console.log(!!"hello");           // true
console.log(!!0);                 // false
console.log(!!"");                // false
console.log(!!{});                // true (objects are truthy)

// Short-circuit evaluation - practical uses

// 1. Default values (before ?? was added)
let username = input || "Guest";  // If input is falsy, use "Guest"

// 2. Conditional execution
isLoggedIn && showDashboard();    // Execute if truthy
hasError || showSuccessMessage(); // Execute if falsy

// 3. Guard clauses
function processUser(user) {
    user && user.profile && console.log(user.profile.name);
    // Only accesses nested property if all are truthy
}

// 4. Avoiding function calls
let result = expensiveCheck() || cachedValue;
// If expensiveCheck() is truthy, cachedValue never evaluated

// Short-circuit prevents errors
let obj = null;
let value = obj && obj.property;  // undefined (doesn't throw)
// Without short-circuit: obj.property would throw error

// Combining operators
let access = isAdmin || (isPremium && hasPermission);
console.log(true || (false && error())); // true, error() not called

// Falsy values: false, 0, "", null, undefined, NaN
console.log(false || 0 || "" || null || undefined || NaN || "found");
// "found" (first truthy)
Note: Logical operators return the actual value (not just boolean). Short-circuit evaluation stops at first definitive result, useful for default values and conditional execution.

4.5 Bitwise Operators and Binary Operations

Operator Name Description Example Binary Result
& AND 1 if both bits are 1 5 & 3 0101 & 0011 1 (0001)
| OR 1 if at least one bit is 1 5 | 3 0101 | 0011 7 (0111)
^ XOR 1 if bits are different 5 ^ 3 0101 ^ 0011 6 (0110)
~ NOT Inverts all bits ~5 ~0101 -6 (two's complement)
<< Left Shift Shifts bits left, fills 0s 5 << 1 0101 → 1010 10
>> Sign-propagating Right Shift Shifts bits right, preserves sign 5 >> 1 0101 → 0010 2
>>> Zero-fill Right Shift Shifts bits right, fills 0s -5 >>> 1 Fills with 0s Large positive number
Use Case Operation Example Purpose
Check if even n & 1 5 & 1 = 1 (odd), 4 & 1 = 0 (even) Fast even/odd check
Multiply by 2n n << x 5 << 2 = 20 (5 × 4) Fast multiplication
Divide by 2n n >> x 20 >> 2 = 5 (20 ÷ 4) Fast division
Toggle bit n ^ (1 << i) Toggles i-th bit Flags/permissions
Set bit n | (1 << i) Sets i-th bit to 1 Enable flag
Clear bit n & ~(1 << i) Sets i-th bit to 0 Disable flag
Check bit (n >> i) & 1 Gets value of i-th bit Test flag
Swap variables a ^= b; b ^= a; a ^= b; Swap without temp variable XOR swap trick

Example: Bitwise operations

// Basic bitwise operations
console.log(5 & 3);    // 1  (0101 & 0011 = 0001)
console.log(5 | 3);    // 7  (0101 | 0011 = 0111)
console.log(5 ^ 3);    // 6  (0101 ^ 0011 = 0110)
console.log(~5);       // -6 (inverts bits, two's complement)

// Bit shifts
console.log(5 << 1);   // 10  (0101 → 1010) multiply by 2
console.log(5 << 2);   // 20  (0101 → 10100) multiply by 4
console.log(10 >> 1);  // 5   (1010 → 0101) divide by 2
console.log(20 >> 2);  // 5   (10100 → 0101) divide by 4

// Practical use cases

// 1. Check if number is even or odd
function isEven(n) {
    return (n & 1) === 0;
}
console.log(isEven(4));  // true
console.log(isEven(5));  // false

// 2. Flags and permissions
const READ = 1;      // 001
const WRITE = 2;     // 010
const EXECUTE = 4;   // 100

let permissions = 0;
permissions |= READ;       // Add READ permission
permissions |= WRITE;      // Add WRITE permission

console.log(permissions & READ);    // Check if READ (non-zero = true)
console.log(permissions & EXECUTE); // Check if EXECUTE (0 = false)

permissions &= ~WRITE;     // Remove WRITE permission
console.log(permissions);  // 1 (only READ)

// 3. Fast integer conversion
console.log(~~3.14);       // 3 (double NOT truncates decimals)
console.log(5.7 | 0);      // 5 (OR with 0 truncates)
console.log(5.7 >> 0);     // 5 (right shift by 0 truncates)

// 4. XOR swap (without temp variable)
let a = 5, b = 10;
a ^= b;  // a = 5 ^ 10
b ^= a;  // b = 10 ^ (5 ^ 10) = 5
a ^= b;  // a = (5 ^ 10) ^ 5 = 10
console.log(a, b);  // 10 5

// 5. Toggle boolean
let flag = true;
flag ^= 1;  // false (1 ^ 1 = 0)
flag ^= 1;  // true (0 ^ 1 = 1)

// 6. Set specific bit
let n = 0b0000;  // Binary literal
n |= (1 << 2);   // Set bit 2: 0b0100
console.log(n.toString(2));  // "100"

// 7. Clear specific bit
n &= ~(1 << 2);  // Clear bit 2: 0b0000
console.log(n.toString(2));  // "0"
Note: Bitwise operators work on 32-bit integers. Common uses include flags, permissions, fast math operations, and low-level data manipulation.

4.6 Optional Chaining (?.) and Nullish Coalescing (??)

Operator Syntax Returns Use Case
?. ES2020 obj?.prop undefined if obj is null/undefined, else obj.prop Safe property access
?.[] obj?.[expr] undefined if obj is null/undefined Safe dynamic property access
?.() func?.(args) undefined if func is null/undefined Safe function call
?? ES2020 a ?? b a if not null/undefined, else b Default values for null/undefined only
Feature || (OR) ?? (Nullish Coalescing)
Falsy values treated as missing Yes (false, 0, "", null, undefined, NaN) No (only null/undefined)
0 || 100 100 0
"" || "default" "default" ""
false || true true false
null ?? 100 100 100
undefined ?? 100 100 100
Use case When any falsy should use default When only null/undefined should use default

Example: Optional chaining and nullish coalescing

// Optional Chaining (?.)

// Without optional chaining (verbose and error-prone)
let user = null;
// let name = user.profile.name; // TypeError: Cannot read property 'profile' of null
let name = user && user.profile && user.profile.name; // undefined

// With optional chaining (concise and safe)
name = user?.profile?.name; // undefined (no error)

const user2 = {
    profile: {
        name: "Alice",
        address: {
            city: "NYC"
        }
    }
};

console.log(user2?.profile?.name);          // "Alice"
console.log(user2?.profile?.address?.city); // "NYC"
console.log(user2?.profile?.phone);         // undefined (no error)
console.log(user2?.account?.balance);       // undefined

// Optional chaining with arrays
const arr = null;
console.log(arr?.[0]);        // undefined (safe)
console.log(user2?.tags?.[0]); // undefined

// Optional chaining with function calls
const obj = {
    method: function() { return "called"; }
};

console.log(obj.method?.());      // "called"
console.log(obj.nonExistent?.());  // undefined (no error)
console.log(user?.getProfile?.()); // undefined

// Nullish Coalescing (??)

// Problem with || operator
let count = 0;
let value1 = count || 10;  // 10 (0 is falsy, uses default)
let value2 = count ?? 10;  // 0 (0 is not null/undefined)

let text = "";
let msg1 = text || "default";  // "default" ("" is falsy)
let msg2 = text ?? "default";  // "" (empty string is valid)

// Use ?? for actual null/undefined checks
let config = {
    timeout: 0,      // 0 is valid
    enabled: false,  // false is valid
    name: ""         // empty string is valid
};

let timeout = config.timeout ?? 3000;    // 0 (not null/undefined)
let enabled = config.enabled ?? true;    // false (not null/undefined)
let name = config.name ?? "Unnamed";     // "" (not null/undefined)
let missing = config.missing ?? "N/A";   // "N/A" (undefined)

// Combining ?. and ??
const data = {
    user: null,
    settings: { theme: "dark" }
};

let theme = data?.settings?.theme ?? "light";  // "dark"
let userName = data?.user?.name ?? "Guest";    // "Guest"

// Real-world example: API response
function getUserCity(response) {
    return response?.data?.user?.address?.city ?? "Unknown";
}

console.log(getUserCity(null));                    // "Unknown"
console.log(getUserCity({ data: null }));          // "Unknown"
console.log(getUserCity({
    data: { user: { address: { city: "NYC" } } }
})); // "NYC"

// Short-circuit evaluation
let expensiveFn = () => { console.log("Called"); return 42; };
let result = null?.property?.method?.();  // undefined (expensiveFn not called)
console.log(result);  // undefined
Warning: Don't overuse ?. as it can hide bugs. Use it for genuinely optional properties, not to mask errors in required data.

4.7 Logical Assignment Operators (||=, &&=, ??=)

Operator Name Equivalent Assigns When
||= ES2021 Logical OR Assignment x || (x = y) x is falsy
&&= ES2021 Logical AND Assignment x && (x = y) x is truthy
??= ES2021 Nullish Assignment x ?? (x = y) x is null or undefined
Operator Behavior Use Case Example
||= Assigns if current value is falsy Set default for any falsy value x ||= 10 (assigns if x is falsy)
&&= Assigns if current value is truthy Update existing truthy value x &&= 10 (assigns if x is truthy)
??= Assigns only if null/undefined Set default only for null/undefined x ??= 10 (assigns if x is null/undefined)

Before ES2021:

// Logical OR pattern
x = x || defaultValue;
if (!x) x = defaultValue;

// Logical AND pattern
x = x && newValue;
if (x) x = newValue;

// Nullish pattern
x = x ?? defaultValue;
if (x == null) x = defaultValue;

With ES2021:

// Logical OR assignment
x ||= defaultValue;


// Logical AND assignment
x &&= newValue;


// Nullish assignment
x ??= defaultValue;

Example: Logical assignment operators

// ||= Logical OR Assignment
// Assigns if left side is falsy
let a = 0;
a ||= 10;        // a is 10 (0 is falsy)

let b = 5;
b ||= 10;        // b is still 5 (5 is truthy)

let c = "";
c ||= "default"; // c is "default" ("" is falsy)

// &&= Logical AND Assignment
// Assigns if left side is truthy
let x = 5;
x &&= 10;        // x is 10 (5 is truthy)

let y = 0;
y &&= 10;        // y is still 0 (0 is falsy, no assignment)

let z = null;
z &&= 10;        // z is still null (null is falsy)

// ??= Nullish Assignment
// Assigns only if null or undefined
let p = 0;
p ??= 10;        // p is still 0 (0 is not null/undefined)

let q = null;
q ??= 10;        // q is 10 (null)

let r = undefined;
r ??= 10;        // r is 10 (undefined)

let s = false;
s ??= true;      // s is still false (false is not null/undefined)

// Practical use cases

// 1. Object property defaults with ??=
const config = {
    timeout: 0,      // 0 is valid
    retry: null      // null should be replaced
};

config.timeout ??= 3000;  // Still 0 (not null/undefined)
config.retry ??= 3;       // Now 3 (was null)
config.newProp ??= "default"; // "default" (was undefined)

console.log(config);
// { timeout: 0, retry: 3, newProp: "default" }

// 2. Accumulation with &&=
let total = 100;
let applyDiscount = true;

total &&= total * 0.9;  // Apply 10% discount if total exists
console.log(total);        // 90

let nullTotal = null;
nullTotal &&= nullTotal * 0.9;  // No operation (nullTotal is falsy)
console.log(nullTotal);    // null (unchanged)

// 3. Cache pattern with ||=
let cache = {};

function getData(key) {
    // Set cache if not already set
    cache[key] ||= expensiveOperation(key);
    return cache[key];
}

function expensiveOperation(key) {
    console.log("Computing...");
    return key * 2;
}

console.log(getData(5));   // "Computing..." then 10
console.log(getData(5));   // 10 (from cache, no computing)

// 4. Updating objects conditionally
const user = {
    name: "Alice",
    age: null,
    status: undefined
};

user.name ||= "Anonymous";    // Still "Alice" (truthy)
user.age ??= 18;              // Now 18 (was null)
user.status ??= "active";     // Now "active" (was undefined)

// 5. Short-circuit: right side not evaluated if condition not met
let count = 0;
let getValue = () => { count++; return 100; };

let x1 = 5;
x1 ||= getValue();  // getValue() NOT called (x1 is truthy)
console.log(count); // 0

let x2 = 0;
x2 ||= getValue();  // getValue() IS called (x2 is falsy)
console.log(count); // 1
Note: Logical assignment operators combine logical operations with assignment, providing cleaner syntax and short-circuit evaluation (right side only evaluated when assignment occurs).

Section 4 Summary

  • Arithmetic operators include +, -, *, /, %, ** (exponentiation); + concatenates strings if either operand is string
  • Assignment operators support compound forms (+=, -=, *=, etc.) for concise update operations
  • Use strict equality (===, !==) to avoid type coercion bugs; loose equality (==) performs type conversion
  • Logical operators (&&, ||, !) short-circuit and return actual values, not just booleans; useful for defaults and guards
  • Bitwise operators work on 32-bit integers; useful for flags, permissions, and fast math operations
  • Optional chaining (?.) safely accesses nested properties; nullish coalescing (??) provides defaults only for null/undefined
  • Logical assignment (||=, &&=, ??=) combines logic with assignment; short-circuits when assignment unnecessary

5. Control Flow and Loop Constructs

5.1 Conditional Statements (if, switch, ternary)

Statement Syntax Use Case Notes
if if (condition) { } Execute block if condition is truthy Most common conditional
if...else if (cond) { } else { } Execute one of two blocks Binary choice
if...else if...else if (c1) { } else if (c2) { } else { } Multiple conditions, evaluated in order First truthy wins
switch switch(expr) { case val: break; } Multiple discrete values, strict equality (===) Don't forget break
Ternary condition ? trueVal : falseVal Inline conditional expression Returns value, concise
Nested Ternary c1 ? v1 : c2 ? v2 : v3 Multiple conditions inline Can be hard to read
Feature if/else switch Ternary
Returns Value No (statements) No (statements) Yes (expression)
Multiple Conditions ✓ Any expression Single expression, multiple cases One condition per ternary
Comparison Type Any truthy/falsy Strict equality (===) Any truthy/falsy
Fall-through N/A ✓ Without break N/A
Best For Complex conditions, ranges Many discrete values Simple inline conditions

Example: Conditional statements

// if statement
let age = 20;
if (age >= 18) {
    console.log("Adult");
}

// if...else
if (age >= 18) {
    console.log("Adult");
} else {
    console.log("Minor");
}

// if...else if...else
let score = 85;
if (score >= 90) {
    console.log("A");
} else if (score >= 80) {
    console.log("B");
} else if (score >= 70) {
    console.log("C");
} else {
    console.log("F");
}

// switch statement
let day = "Monday";
switch (day) {
    case "Monday":
    case "Tuesday":
    case "Wednesday":
    case "Thursday":
    case "Friday":
        console.log("Weekday");
        break;  // Important!
    case "Saturday":
    case "Sunday":
        console.log("Weekend");
        break;
    default:
        console.log("Invalid day");
}

// switch with expressions (modern pattern)
let result = switch(status) {  // Not yet in JS, but coming
    case 200: "OK";
    case 404: "Not Found";
    default: "Unknown";
};

// Ternary operator
let message = age >= 18 ? "Adult" : "Minor";
console.log(message);

// Nested ternary
let grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F";

// Ternary with side effects (avoid for readability)
let value = condition ? (count++, doSomething()) : doSomethingElse();

// Multiple ternaries (can be hard to read)
let type = age < 13 ? "child" 
         : age < 20 ? "teen"
         : age < 65 ? "adult"
         : "senior";

// Guard clauses (early return pattern)
function processUser(user) {
    if (!user) return;              // Guard clause
    if (!user.isActive) return;     // Guard clause
    if (!user.hasPermission) return; // Guard clause
    
    // Main logic here
    console.log("Processing user...");
}
Warning: Always use break in switch cases to prevent fall-through (unless intentional). Nested ternaries can reduce readability; use if/else for complex logic.

5.2 Loop Statements (for, while, do-while)

Loop Type Syntax When to Use Characteristics
for for (init; cond; update) { } Known iteration count Initialization, condition, update in header
while while (condition) { } Unknown iteration count, condition first Tests condition before each iteration
do-while do { } while (condition); At least one iteration guaranteed Tests condition after each iteration
Feature for while do-while
Minimum Iterations 0 (can skip entirely) 0 (can skip entirely) 1 (always executes once)
Condition Check Before each iteration Before each iteration After each iteration
Loop Variable Scope Block-scoped if using let/const Declared outside loop Declared outside loop
Best For Counter-based iteration Condition-based iteration Execute-then-check pattern

Example: Classic loop statements

// for loop - most common for arrays/counters
for (let i = 0; i < 5; i++) {
    console.log(i);  // 0, 1, 2, 3, 4
}

// Multiple variables in for loop
for (let i = 0, j = 10; i < 5; i++, j--) {
    console.log(i, j);  // (0,10), (1,9), (2,8), (3,7), (4,6)
}

// Infinite loop (condition always true)
// for (;;) {
//     console.log("Forever");
//     break; // Need break to exit
// }

// while loop - condition-based
let count = 0;
while (count < 5) {
    console.log(count);
    count++;
}

// while with complex condition
let items = [1, 2, 3, 4, 5];
let i = 0;
while (i < items.length && items[i] !== 3) {
    console.log(items[i]);
    i++;
}

// Reading input until valid
let input;
while (!input || input.trim() === "") {
    // input = prompt("Enter value:");  // Browser example
    input = "test";  // Simulated
}

// do-while - at least one iteration
let num = 0;
do {
    console.log(num);  // Executes once even though num < 1 is false
    num++;
} while (num < 1);

// Menu loop pattern
let choice;
do {
    // Display menu
    console.log("1. Option 1");
    console.log("2. Option 2");
    console.log("3. Exit");
    // choice = getUserInput();
    choice = 3;  // Simulated
} while (choice !== 3);

// Empty for loop (all work in condition)
let arr = [1, 2, 3];
for (let i = 0; i < arr.length && arr[i]++ < 10;);
console.log(arr);  // [2, 3, 4]

// Nested loops
for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        console.log(`${i}, ${j}`);
    }
}

// Loop with multiple updates
for (let i = 0; i < 10; i += 2) {
    console.log(i);  // 0, 2, 4, 6, 8
}

// Reverse loop
for (let i = 5; i >= 0; i--) {
    console.log(i);  // 5, 4, 3, 2, 1, 0
}
Note: Use for for counter-based iteration, while for condition-based, and do-while when you need at least one iteration before checking the condition.

5.3 Enhanced Loops (for...in, for...of, for await...of)

Loop Type Syntax Iterates Over Returns Use For
for...in for (key in obj) { } Enumerable properties Property names (strings) Object properties
for...of ES6 for (val of iterable) { } Iterable objects Values Arrays, strings, Maps, Sets
for await...of ES2018 for await (val of asyncIterable) { } Async iterables Resolved promises Async generators, streams
Feature for...in for...of
Works with Objects ✓ Yes (enumerable properties) ✗ No (unless iterable)
Works with Arrays ✓ Yes (indices as strings) ✓ Yes (values directly)
Works with Strings ✓ Yes (indices) ✓ Yes (characters)
Works with Map/Set ✗ No ✓ Yes
Includes Prototype Chain ✓ Yes (inherited properties) ✗ No
Recommended For Object properties (use with hasOwnProperty) Iterables (arrays, strings, collections)

Example: Enhanced loops

// for...in - iterates over object properties
const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
    console.log(key, obj[key]);  // "a" 1, "b" 2, "c" 3
}

// for...in with arrays (NOT recommended - use for...of)
const arr = [10, 20, 30];
for (let index in arr) {
    console.log(index, arr[index]);  // "0" 10, "1" 20, "2" 30
    console.log(typeof index);       // "string" (not number!)
}

// for...in includes inherited properties
Object.prototype.inheritedProp = "inherited";
const obj2 = { own: "value" };
for (let key in obj2) {
    console.log(key);  // "own", "inheritedProp"
}

// Use hasOwnProperty to filter
for (let key in obj2) {
    if (obj2.hasOwnProperty(key)) {
        console.log(key);  // "own" only
    }
}

// for...of - iterates over iterable values (ES6)
const arr2 = [10, 20, 30];
for (let value of arr2) {
    console.log(value);  // 10, 20, 30 (values, not indices)
}

// for...of with strings
const str = "hello";
for (let char of str) {
    console.log(char);  // "h", "e", "l", "l", "o"
}

// for...of with Map
const map = new Map([['a', 1], ['b', 2]]);
for (let [key, value] of map) {
    console.log(key, value);  // "a" 1, "b" 2
}

// for...of with Set
const set = new Set([1, 2, 3]);
for (let value of set) {
    console.log(value);  // 1, 2, 3
}

// for...of with array methods
const arr3 = [1, 2, 3, 4, 5];
for (let value of arr3.entries()) {
    console.log(value);  // [0, 1], [1, 2], [2, 3]...
}

for (let [index, value] of arr3.entries()) {
    console.log(index, value);  // 0 1, 1 2, 2 3...
}

// for await...of - async iteration (ES2018)
async function fetchSequentially() {
    const urls = ['url1', 'url2', 'url3'];
    
    // Create async iterable
    async function* fetchUrls() {
        for (let url of urls) {
            yield fetch(url);  // Simulated
        }
    }
    
    // Iterate and wait for each promise
    for await (let response of fetchUrls()) {
        console.log(await response.text());
    }
}

// for await...of with Promise array
async function processPromises() {
    const promises = [
        Promise.resolve(1),
        Promise.resolve(2),
        Promise.resolve(3)
    ];
    
    for await (let value of promises) {
        console.log(value);  // 1, 2, 3 (waits for each)
    }
}

// Custom iterable with for...of
const customIterable = {
    data: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => ({
                value: this.data[index],
                done: index++ >= this.data.length
            })
        };
    }
};

for (let value of customIterable) {
    console.log(value);  // 1, 2, 3
}
Warning: Use for...of for arrays (not for...in). With for...in on objects, always use hasOwnProperty() to avoid inherited properties.

5.4 Jump Statements (break, continue, return)

Statement Syntax Effect Use In
break break; Exits loop or switch immediately Loops, switch
break label break labelName; Exits named loop/block Nested loops
continue continue; Skips to next iteration Loops only
continue label continue labelName; Continues named loop Nested loops
return return value; Exits function with value Functions only
return (void) return; Exits function, returns undefined Functions only
Statement break continue return
In for loop ✓ Exits loop ✓ Next iteration ✓ Exits function (and loop)
In while loop ✓ Exits loop ✓ Next iteration ✓ Exits function (and loop)
In switch ✓ Exits switch ✗ Syntax error ✓ Exits function (and switch)
In function ✗ Must be in loop/switch ✗ Must be in loop ✓ Exits function
With labels ✓ Can target outer loop ✓ Can target outer loop ✗ Cannot use labels

Example: Jump statements

// break - exit loop immediately
for (let i = 0; i < 10; i++) {
    if (i === 5) break;  // Exit loop when i is 5
    console.log(i);      // 0, 1, 2, 3, 4
}

// break in nested loop (only exits inner loop)
for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (j === 1) break;  // Exits inner loop only
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  1,0  2,0

// continue - skip to next iteration
for (let i = 0; i < 5; i++) {
    if (i === 2) continue;  // Skip when i is 2
    console.log(i);         // 0, 1, 3, 4
}

// continue with condition
const arr = [1, 2, 3, 4, 5];
for (let num of arr) {
    if (num % 2 === 0) continue;  // Skip even numbers
    console.log(num);             // 1, 3, 5
}

// return - exit function
function findValue(arr, target) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] === target) {
            return i;  // Exit function, return index
        }
    }
    return -1;  // Not found
}

console.log(findValue([1, 2, 3], 2));  // 1
console.log(findValue([1, 2, 3], 5));  // -1

// return without value (returns undefined)
function logMessage(msg) {
    if (!msg) return;  // Early exit
    console.log(msg);
}

// Multiple return points (guard clauses)
function validateUser(user) {
    if (!user) return { valid: false, error: "No user" };
    if (!user.name) return { valid: false, error: "No name" };
    if (!user.email) return { valid: false, error: "No email" };
    return { valid: true };
}

// break in switch
function getDayType(day) {
    switch (day) {
        case 'Mon':
        case 'Tue':
        case 'Wed':
        case 'Thu':
        case 'Fri':
            return 'Weekday';
            // No break needed after return
        case 'Sat':
        case 'Sun':
            return 'Weekend';
        default:
            return 'Invalid';
    }
}

// Finding first match
const users = [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 30 },
    { name: 'Charlie', age: 35 }
];

let found;
for (let user of users) {
    if (user.age > 28) {
        found = user;
        break;  // Stop after finding first match
    }
}
console.log(found);  // Bob

// Skip invalid items
const data = [1, null, 2, undefined, 3, NaN, 4];
for (let item of data) {
    if (item == null || Number.isNaN(item)) continue;
    console.log(item);  // 1, 2, 3, 4
}
Note: Use break to exit loops early, continue to skip iterations, and return to exit functions. For nested loops, consider labeled statements for more control.

5.5 Exception Handling (try, catch, finally, throw)

Keyword Purpose Required Description
try Execute code that may throw ✓ Yes Contains code to be tested for errors
catch Handle exceptions ✓ Yes (or finally) Executes if error thrown in try block
finally Cleanup code ✗ Optional Always executes (even if error/return)
throw Throw exception N/A Creates and throws error object
Error Type When Thrown Example
Error Generic error (base class) new Error("message")
SyntaxError Invalid JavaScript syntax eval("{")
ReferenceError Invalid reference/undeclared variable console.log(undeclaredVar)
TypeError Value not of expected type null.property
RangeError Value not in allowed range new Array(-1)
URIError Invalid URI encoding/decoding decodeURI("%")

Example: Exception handling

// Basic try-catch
try {
    let result = riskyOperation();
    console.log(result);
} catch (error) {
    console.error("Error:", error.message);
}

// Accessing error properties
try {
    throw new Error("Something went wrong");
} catch (error) {
    console.log(error.name);     // "Error"
    console.log(error.message);  // "Something went wrong"
    console.log(error.stack);    // Stack trace
}

// try-catch-finally
try {
    console.log("Try block");
    // throw new Error("Test");
} catch (error) {
    console.log("Catch block:", error.message);
} finally {
    console.log("Finally always runs");
}

// finally runs even with return
function testFinally() {
    try {
        return "from try";
    } catch (error) {
        return "from catch";
    } finally {
        console.log("Finally executed");  // Runs before return
    }
}
console.log(testFinally());  // "Finally executed", then "from try"

// Throwing errors
function divide(a, b) {
    if (b === 0) {
        throw new Error("Division by zero");
    }
    return a / b;
}

try {
    console.log(divide(10, 0));
} catch (error) {
    console.log("Caught:", error.message);
}

// Throwing custom errors
class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}

function validateAge(age) {
    if (age < 0) {
        throw new ValidationError("Age cannot be negative");
    }
    if (age > 150) {
        throw new ValidationError("Age too high");
    }
    return true;
}

try {
    validateAge(-5);
} catch (error) {
    if (error instanceof ValidationError) {
        console.log("Validation error:", error.message);
    } else {
        console.log("Other error:", error);
    }
}

// Nested try-catch
try {
    try {
        throw new Error("Inner error");
    } catch (innerError) {
        console.log("Inner catch:", innerError.message);
        throw new Error("Outer error");  // Re-throw or new error
    }
} catch (outerError) {
    console.log("Outer catch:", outerError.message);
}

// Catching specific error types
try {
    // Some operation
    JSON.parse("invalid json");
} catch (error) {
    if (error instanceof SyntaxError) {
        console.log("JSON syntax error");
    } else if (error instanceof TypeError) {
        console.log("Type error");
    } else {
        console.log("Unknown error");
    }
}

// Async error handling
async function fetchData() {
    try {
        const response = await fetch('/api/data');
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Fetch error:", error);
        throw error;  // Re-throw for caller to handle
    } finally {
        console.log("Cleanup operations");
    }
}

// Error without catch (just finally)
try {
    console.log("Executing...");
} finally {
    console.log("Cleanup");  // Valid: try-finally without catch
}

// Throwing non-Error objects (possible but not recommended)
try {
    throw "String error";  // Works but not best practice
    throw { code: 500 };   // Works but no stack trace
    throw 42;              // Works but no context
} catch (error) {
    console.log(typeof error);  // Could be anything
}
Warning: Always catch errors for operations that may fail (network, parsing, etc.). Use finally for cleanup that must run regardless of success/failure.

5.6 Labels and Labeled Statements

Feature Syntax Use With Purpose
Label Definition labelName: statement Any statement/block Names a statement for reference
Labeled break break labelName; Loops, blocks Exits named loop/block
Labeled continue continue labelName; Loops only Continues named loop
Use Case Without Labels With Labels
Break from nested loop Only breaks inner loop Can break outer loop directly
Continue outer loop Requires flag variable Direct continue to outer loop
Exit multiple levels Multiple break statements Single labeled break
Code clarity Complex control flow Explicit jump targets

Example: Labeled statements

// Labeled break - exit outer loop
outer: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) {
            break outer;  // Breaks out of outer loop
        }
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  0,1  0,2  1,0

// Without label (only breaks inner loop)
for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) {
            break;  // Only breaks inner loop
        }
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  0,1  0,2  1,0  2,0  2,1  2,2

// Labeled continue - continue outer loop
outer: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (j === 1) {
            continue outer;  // Continues outer loop, skips rest of inner
        }
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  1,0  2,0

// Finding element in 2D array
const matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

let target = 5;
let found = false;

search: for (let i = 0; i < matrix.length; i++) {
    for (let j = 0; j < matrix[i].length; j++) {
        if (matrix[i][j] === target) {
            console.log(`Found at [${i}][${j}]`);
            found = true;
            break search;  // Exit both loops
        }
    }
}

// Multiple nested loops with labels
outer: for (let i = 0; i < 2; i++) {
    middle: for (let j = 0; j < 2; j++) {
        inner: for (let k = 0; k < 2; k++) {
            if (i === 1) break outer;      // Exit all loops
            if (j === 1) break middle;     // Exit middle and inner
            if (k === 1) break inner;      // Exit only inner
            console.log(`${i},${j},${k}`);
        }
    }
}

// Labeled block (not loop)
blockLabel: {
    console.log("Start");
    if (someCondition) {
        break blockLabel;  // Exit block early
    }
    console.log("This may not execute");
}
console.log("After block");

// Complex search with labeled break
const data = [
    { id: 1, items: [10, 20, 30] },
    { id: 2, items: [40, 50, 60] },
    { id: 3, items: [70, 80, 90] }
];

let searchValue = 50;
let result = null;

dataSearch: for (let group of data) {
    for (let item of group.items) {
        if (item === searchValue) {
            result = { groupId: group.id, value: item };
            break dataSearch;  // Found, exit all loops
        }
    }
}

console.log(result);  // { groupId: 2, value: 50 }

// Alternative to labeled break (using function return)
function findInMatrix(matrix, target) {
    for (let i = 0; i < matrix.length; i++) {
        for (let j = 0; j < matrix[i].length; j++) {
            if (matrix[i][j] === target) {
                return [i, j];  // Return exits function (simpler)
            }
        }
    }
    return null;
}

// Labeled continue with condition
outer: for (let i = 0; i < 5; i++) {
    for (let j = 0; j < 5; j++) {
        if (i + j > 5) {
            continue outer;  // Skip to next outer iteration
        }
        console.log(`Sum: ${i + j}`);
    }
}
Note: Labeled statements are useful for complex nested loops but can reduce readability. Consider refactoring into separate functions with return as a cleaner alternative.

Section 5 Summary

  • Conditionals: Use if/else for complex logic, switch for discrete values, ternary for inline expressions
  • Loops: for (known count), while (condition-based), do-while (at least once)
  • Enhanced loops: for...of for iterables (arrays, strings), for...in for object properties, for await...of for async
  • Jump statements: break exits loops/switch, continue skips to next iteration, return exits functions
  • Exception handling: try-catch-finally for error handling; throw to raise errors; finally always executes
  • Labels: Enable break/continue to target outer loops; useful for nested structures but consider function refactoring

6. Functions and Functional Programming

6.1 Function Declaration and Expression Syntax

Function Type Syntax Hoisting Use Case
Function Declaration function name(params) { body } ✓ Fully hoisted (can call before declaration) Top-level functions, utility functions requiring hoisting
Function Expression const name = function(params) { body }; ✗ Variable hoisted but undefined (TDZ for let/const) Conditional function creation, callbacks, function as data
Named Function Expression const f = function name(params) { body }; ✗ Not hoisted; name only visible inside function Recursion in expressions, better stack traces
Arrow Function ES6 const name = (params) => expression ✗ Variable hoisting behavior Short callbacks, lexical this binding, concise syntax
Method Definition ES6 { methodName(params) { body } } ✗ Part of object/class definition Object methods, class methods with concise syntax
Generator Function ES6 function* name(params) { yield value } ✓ Hoisted like regular functions Iterators, lazy evaluation, pausable execution
Async Function ES8 async function name(params) { await promise } ✓ Hoisted (declaration) / ✗ (expression) Asynchronous operations with cleaner syntax

Example: Function declaration vs expression

// Function Declaration - hoisted, can call before definition
console.log(add(2, 3));  // Works: 5
function add(a, b) {
    return a + b;
}

// Function Expression - not hoisted
// console.log(subtract(5, 2));  // Error: Cannot access before initialization
const subtract = function(a, b) {
    return a - b;
};

// Named Function Expression - name visible only inside
const factorial = function fact(n) {
    return n <= 1 ? 1 : n * fact(n - 1);  // 'fact' accessible here
};
console.log(factorial(5));  // 120
// console.log(fact(5));  // Error: fact is not defined

// Arrow function - concise syntax
const multiply = (a, b) => a * b;
const square = x => x * x;  // Single param, no parens needed
const greet = () => 'Hello';  // No params
Best Practice: Use function declarations for top-level utility functions that need hoisting. Use arrow functions for callbacks and methods that don't need their own this. Use function expressions when functions are conditional or need to be treated as values.

6.2 Arrow Functions and Lexical this Binding

Feature Arrow Function Regular Function Impact
this Binding Lexical (from enclosing scope) Dynamic (based on call) No .bind() needed for callbacks
arguments Object ✗ Not available (use rest params) ✓ Available Use ...args for arrow functions
new Operator ✗ Cannot be constructor ✓ Can be constructor Arrow functions have no [[Construct]]
prototype Property ✗ No prototype ✓ Has prototype Lighter memory footprint for arrows
super Keyword ✗ Cannot use super ✓ Can use in methods Use regular functions for class methods needing super
yield Keyword ✗ Cannot be generator ✓ Can yield Use function* for generators
Syntax Form Example When to Use
No Parameters () => expression Functions with no inputs
Single Parameter x => expression Unary functions (map, filter callbacks)
Multiple Parameters (x, y) => expression Binary/multi-parameter functions
Expression Body x => x * 2 Single expression, implicit return
Block Body x => { return x * 2; } Multiple statements, explicit return
Object Literal Return x => ({ value: x }) Returning object (wrap in parentheses)

Example: Lexical this binding in practice

// Problem with regular functions - this binding changes
function Timer() {
    this.seconds = 0;
    
    // Regular function - 'this' is window/undefined in strict mode
    setInterval(function() {
        this.seconds++;  // ✗ Wrong 'this'
        console.log(this.seconds);  // NaN
    }, 1000);
}

// Solution 1: Arrow function - lexical 'this'
function Timer() {
    this.seconds = 0;
    
    setInterval(() => {
        this.seconds++;  // ✓ Correct 'this' from Timer
        console.log(this.seconds);  // 1, 2, 3...
    }, 1000);
}

// Solution 2: Old approach with .bind() (before ES6)
function Timer() {
    this.seconds = 0;
    setInterval(function() {
        this.seconds++;
    }.bind(this), 1000);  // Explicitly bind 'this'
}

// Class methods with arrow functions
class Counter {
    count = 0;
    
    // Arrow function as class field - 'this' always bound to instance
    increment = () => {
        this.count++;
    }
    
    // Regular method - 'this' depends on how it's called
    decrement() {
        this.count--;
    }
}

const counter = new Counter();
const inc = counter.increment;
const dec = counter.decrement;

inc();  // ✓ Works - arrow function has lexical 'this'
dec();  // ✗ Error - 'this' is undefined (not bound)

Example: Arrow function syntax variations

// Expression body - implicit return
const double = x => x * 2;
const sum = (a, b) => a + b;

// Block body - explicit return needed
const greet = name => {
    const message = `Hello, ${name}!`;
    return message.toUpperCase();
};

// Returning object literal - wrap in parentheses
const makePerson = (name, age) => ({ name, age });
const point = (x, y) => ({ x: x, y: y });

// Array methods with arrows
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const total = numbers.reduce((sum, n) => sum + n, 0);

// Chaining with concise arrows
const result = [1, 2, 3, 4, 5]
    .filter(x => x % 2 === 0)
    .map(x => x * x)
    .reduce((sum, x) => sum + x, 0);  // 20
Warning: Don't use arrow functions as object methods or event handlers where you need dynamic this. Arrow functions cannot be used as constructors with new. They don't have arguments object - use rest parameters instead.

6.3 Function Parameters (default, rest, destructuring)

Parameter Type Syntax Description Example
Required Parameters function f(a, b) Traditional positional parameters; undefined if not provided f(1, 2)
Default Parameters ES6 function f(a = 0, b = 1) Provides default value if undefined (not for null) f() // a=0, b=1
Rest Parameters ES6 function f(...args) Collects remaining arguments into array; must be last f(1,2,3) // args=[1,2,3]
Destructuring (Array) ES6 function f([a, b]) Extracts values from array argument by position f([10, 20])
Destructuring (Object) ES6 function f({x, y}) Extracts properties from object argument by name f({x:1, y:2})
Nested Destructuring ES6 function f({a: {b}}) Extracts nested properties from deep object structure f({a: {b: 5}})
Default + Destructuring ES6 function f({x = 0} = {}) Combines destructuring with defaults for object properties f() // x=0
Pattern Example Behavior
Default with Expression f(a = computeDefault()) Expression evaluated only if parameter is undefined
Using Previous Params f(a, b = a * 2) Default can reference earlier parameters
Rest with Destructuring f([first, ...rest]) Combine array destructuring with rest collection
Rename in Destructuring f({x: width, y: height}) Extract and rename properties simultaneously

Example: Default parameters

// Basic default parameters
function greet(name = 'Guest', greeting = 'Hello') {
    return `${greeting}, ${name}!`;
}
console.log(greet());  // "Hello, Guest!"
console.log(greet('Alice'));  // "Hello, Alice!"
console.log(greet('Bob', 'Hi'));  // "Hi, Bob!"

// Default only applies to undefined, not null
console.log(greet(null));  // "Hello, null!" (null !== undefined)
console.log(greet(undefined, 'Hey'));  // "Hey, Guest!" (undefined triggers default)

// Default expressions evaluated at call time
let counter = 0;
function f(x = counter++) {
    return x;
}
console.log(f());  // 0 (counter becomes 1)
console.log(f());  // 1 (counter becomes 2)
console.log(f(10));  // 10 (default not used, counter stays 2)

// Using previous parameters in defaults
function createRect(width, height = width) {
    return { width, height };  // height defaults to width (square)
}
console.log(createRect(5));  // { width: 5, height: 5 }
console.log(createRect(5, 10));  // { width: 5, height: 10 }

Example: Rest parameters

// Rest parameters collect remaining arguments into array
function sum(...numbers) {
    return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4));  // 10
console.log(sum());  // 0 (empty array)

// Combine regular params with rest
function multiply(multiplier, ...numbers) {
    return numbers.map(n => n * multiplier);
}
console.log(multiply(2, 1, 2, 3));  // [2, 4, 6]

// Rest must be last parameter
// function invalid(...args, last) {}  // ✗ SyntaxError

// Rest vs arguments object
function oldWay() {
    // arguments is array-like, not real array
    const args = Array.from(arguments);
    return args.reduce((sum, n) => sum + n, 0);
}

function newWay(...args) {
    // args is real array with all array methods
    return args.reduce((sum, n) => sum + n, 0);
}

// Arrow functions don't have arguments, use rest
const arrowSum = (...nums) => nums.reduce((s, n) => s + n, 0);

Example: Destructuring parameters

// Object destructuring in parameters
function createUser({ name, age, email = 'none@example.com' }) {
    return { name, age, email };
}
createUser({ name: 'Alice', age: 30 });
// { name: 'Alice', age: 30, email: 'none@example.com' }

// Array destructuring in parameters
function sumFirstTwo([a, b]) {
    return a + b;
}
console.log(sumFirstTwo([10, 20, 30]));  // 30

// Nested destructuring
function displayPoint({ coords: { x, y }, label = 'Point' }) {
    return `${label}: (${x}, ${y})`;
}
displayPoint({ coords: { x: 10, y: 20 } });  // "Point: (10, 20)"

// Destructuring with rest
function getFirstAndRest([first, ...rest]) {
    return { first, rest };
}
console.log(getFirstAndRest([1, 2, 3, 4]));
// { first: 1, rest: [2, 3, 4] }

// Rename while destructuring
function printDimensions({ width: w, height: h }) {
    console.log(`Width: ${w}, Height: ${h}`);
}
printDimensions({ width: 100, height: 200 });

// Default for entire destructured object
function config({ host = 'localhost', port = 8080 } = {}) {
    return `${host}:${port}`;
}
console.log(config());  // "localhost:8080"
console.log(config({ host: 'example.com' }));  // "example.com:8080"

6.4 Higher-Order Functions and Callbacks

Concept Definition Common Use Cases
Higher-Order Function (HOF) Function that takes function(s) as argument(s) and/or returns a function map, filter, reduce, addEventListener, middleware
Callback Function Function passed as argument to be executed later (synchronous or asynchronous) Event handlers, async operations, array methods
Function Returning Function HOF that creates and returns new function (function factory) Configuration, currying, closures, partial application
Function Composition Combining functions where output of one is input to another Data transformation pipelines, functional programming
Built-in HOF Purpose Callback Signature Returns
Array.map() Transform each element (element, index, array) => newValue New array (same length)
Array.filter() Select elements by condition (element, index, array) => boolean New array (≤ original length)
Array.reduce() Aggregate to single value (accumulator, element, index, array) => newAcc Single accumulated value
Array.forEach() Execute side effects (element, index, array) => void undefined (no return value)
Array.find() Find first matching element (element, index, array) => boolean Element or undefined
Array.some() Test if any element passes (element, index, array) => boolean boolean
Array.every() Test if all elements pass (element, index, array) => boolean boolean

Example: Higher-order functions

// Function that takes function as argument
function repeat(n, action) {
    for (let i = 0; i < n; i++) {
        action(i);
    }
}
repeat(3, console.log);  // Logs: 0, 1, 2

// Function that returns function (function factory)
function multiplier(factor) {
    return function(number) {
        return number * factor;
    };
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5));  // 10
console.log(triple(5));  // 15

// Function composition
function compose(f, g) {
    return function(x) {
        return f(g(x));
    };
}
const addOne = x => x + 1;
const square = x => x * x;
const addOneThenSquare = compose(square, addOne);
console.log(addOneThenSquare(4));  // 25 (5²)

// Practical HOF: logger wrapper
function withLogging(fn) {
    return function(...args) {
        console.log(`Calling ${fn.name} with`, args);
        const result = fn(...args);
        console.log(`Result:`, result);
        return result;
    };
}
const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(2, 3);  // Logs call info, returns 5

Example: Array HOF methods in action

const users = [
    { id: 1, name: 'Alice', age: 25, active: true },
    { id: 2, name: 'Bob', age: 30, active: false },
    { id: 3, name: 'Charlie', age: 35, active: true }
];

// map - transform data
const names = users.map(user => user.name);
// ['Alice', 'Bob', 'Charlie']

// filter - select subset
const activeUsers = users.filter(user => user.active);
// [Alice, Charlie]

// reduce - aggregate
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
// 90

// Chaining HOFs
const result = users
    .filter(user => user.active)
    .map(user => user.name)
    .map(name => name.toUpperCase());
// ['ALICE', 'CHARLIE']

// find - first match
const user = users.find(u => u.age > 25);
// { id: 2, name: 'Bob', age: 30, active: false }

// some - any match
const hasInactive = users.some(u => !u.active);  // true

// every - all match
const allActive = users.every(u => u.active);  // false

// Complex reduce example
const grouped = users.reduce((groups, user) => {
    const key = user.active ? 'active' : 'inactive';
    groups[key] = groups[key] || [];
    groups[key].push(user);
    return groups;
}, {});
// { active: [Alice, Charlie], inactive: [Bob] }
Best Practice: Prefer declarative HOFs (map, filter, reduce) over imperative loops for data transformation. Chain operations for readability. Use arrow functions for concise callbacks. Remember that HOFs enable code reuse and abstraction.

6.5 Closures and Function Scope Management

Concept Definition Key Characteristics
Closure Function that retains access to variables from its outer (enclosing) scope even after outer function has returned Lexical scoping, persistent state, data privacy
Lexical Environment Internal structure holding variable bindings and reference to outer environment Created at function execution, forms scope chain
Scope Chain Chain of lexical environments searched for variable resolution Inner to outer traversal, stops at first match
Private Variables Variables accessible only through closures, not directly from outside Encapsulation, data hiding, module pattern
Closure Pattern Use Case Example Pattern
Function Factory Create specialized functions with preset parameters function makeAdder(x) { return y => x + y; }
Private State Encapsulate data with controlled access function counter() { let n=0; return ()=>++n; }
Module Pattern Create modules with public/private members IIFE returning object with methods accessing private vars
Event Handlers Preserve context in async callbacks Handler functions accessing outer scope variables
Memoization Cache function results using closure-stored cache Closure over cache object for recursive functions

Example: Basic closure mechanics

// Simple closure - inner function accesses outer variable
function outer() {
    let count = 0;  // Variable in outer scope
    
    function inner() {
        count++;  // Accesses count from outer scope
        return count;
    }
    
    return inner;
}

const counter = outer();
console.log(counter());  // 1
console.log(counter());  // 2
console.log(counter());  // 3
// 'count' persists between calls through closure

// Multiple closures over same variable
function makeCounter() {
    let count = 0;
    return {
        increment: () => ++count,
        decrement: () => --count,
        value: () => count
    };
}

const c1 = makeCounter();
const c2 = makeCounter();  // Separate closure, separate count
c1.increment();
c1.increment();
console.log(c1.value());  // 2
console.log(c2.value());  // 0 (independent)

Example: Practical closure patterns

// Function factory with closure
function multiplier(factor) {
    return function(number) {
        return number * factor;  // 'factor' remembered via closure
    };
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5));  // 10
console.log(triple(5));  // 15

// Private variables pattern
function createBankAccount(initialBalance) {
    let balance = initialBalance;  // Private variable
    
    return {
        deposit(amount) {
            if (amount > 0) balance += amount;
            return balance;
        },
        withdraw(amount) {
            if (amount > 0 && amount <= balance) balance -= amount;
            return balance;
        },
        getBalance() {
            return balance;
        }
    };
    // No direct access to 'balance' from outside
}

const account = createBankAccount(100);
account.deposit(50);  // 150
account.withdraw(30);  // 120
console.log(account.balance);  // undefined (private)
console.log(account.getBalance());  // 120

// Memoization with closure
function memoize(fn) {
    const cache = {};  // Closure over cache
    return function(...args) {
        const key = JSON.stringify(args);
        if (key in cache) {
            console.log('Cached');
            return cache[key];
        }
        const result = fn(...args);
        cache[key] = result;
        return result;
    };
}

const slowFunction = (n) => {
    console.log('Computing...');
    return n * 2;
};
const fast = memoize(slowFunction);
fast(5);  // Computing... 10
fast(5);  // Cached 10
Common Pitfall: Closures in loops. Using var creates single binding shared across iterations. Solution: use let (creates new binding per iteration) or IIFE to capture value.
// ✗ Problem with var
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// Logs: 3, 3, 3 (all closures see same 'i')

// ✓ Solution 1: Use let
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// Logs: 0, 1, 2 (each closure gets own 'i')

// ✓ Solution 2: IIFE (old approach)
for (var i = 0; i < 3; i++) {
    (function(j) {
        setTimeout(() => console.log(j), 100);
    })(i);
}
// Logs: 0, 1, 2

6.6 IIFE Patterns and Module Creation

Pattern Syntax Purpose Use Case
Basic IIFE (function() { })(); Execute function immediately, create private scope Avoid global pollution, one-time initialization
Alternative Syntax (function() { }()); Same as basic IIFE (different grouping) Style preference, same behavior
Arrow IIFE ES6 (() => { })(); Concise IIFE with lexical this Modern alternative, shorter syntax
Named IIFE (function name() { })(); Self-reference for recursion, better debugging Recursive IIFEs, stack traces
IIFE with Parameters (function(x) { })(value); Pass values into IIFE scope Dependency injection, value capture
IIFE Returning Value const x = (function() { return val; })(); Initialize variable with complex logic Complex initialization, module pattern
Module Pattern (IIFE) const mod = (function() { return {...}; })(); Create module with public API, private state Pre-ES6 modules, encapsulation

Example: IIFE basics and variations

// Basic IIFE - executes immediately
(function() {
    const message = 'Hello from IIFE';
    console.log(message);
})();  // Logs: Hello from IIFE
// 'message' not accessible here (private scope)

// IIFE with parameters
(function(name) {
    console.log(`Hello, ${name}!`);
})('Alice');  // Hello, Alice!

// IIFE returning value
const config = (function() {
    const privateKey = 'secret123';
    return {
        apiUrl: 'https://api.example.com',
        timeout: 5000,
        getKey: () => privateKey  // Controlled access
    };
})();
console.log(config.apiUrl);  // 'https://api.example.com'
console.log(config.privateKey);  // undefined (private)

// Arrow function IIFE (ES6+)
(() => {
    const temp = 'temporary variable';
    console.log(temp);
})();

// Named IIFE for recursion
(function factorial(n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
})(5);  // 120

// IIFE for variable capture in loops (pre-let solution)
for (var i = 0; i < 3; i++) {
    (function(index) {
        setTimeout(() => console.log(index), 100);
    })(i);  // Capture current value of i
}

Example: Module pattern with IIFE

// Classic Module Pattern
const Calculator = (function() {
    // Private variables and functions
    let history = [];
    
    function log(operation, result) {
        history.push({ operation, result, timestamp: Date.now() });
    }
    
    // Public API
    return {
        add(a, b) {
            const result = a + b;
            log('add', result);
            return result;
        },
        subtract(a, b) {
            const result = a - b;
            log('subtract', result);
            return result;
        },
        getHistory() {
            return [...history];  // Return copy, not reference
        },
        clearHistory() {
            history = [];
        }
    };
})();

Calculator.add(5, 3);  // 8
Calculator.subtract(10, 4);  // 6
console.log(Calculator.getHistory());  // Array of operations
console.log(Calculator.history);  // undefined (private)

// Revealing Module Pattern
const Counter = (function() {
    let count = 0;
    
    function increment() { return ++count; }
    function decrement() { return --count; }
    function getValue() { return count; }
    function reset() { count = 0; }
    
    // Reveal selected functions
    return {
        increment,
        decrement,
        getValue,
        reset
    };
})();

Counter.increment();  // 1
Counter.increment();  // 2
console.log(Counter.getValue());  // 2

// Module with dependencies
const UserManager = (function($, _) {
    // Use jQuery ($) and lodash (_) passed as parameters
    function getUsers() {
        return _.map(users, user => user.name);
    }
    
    return { getUsers };
})(jQuery, lodash);  // Inject dependencies
Modern Alternative: ES6 modules (import/export) have largely replaced IIFE-based modules. However, IIFEs remain useful for: creating private scope, avoiding global pollution, one-time initialization code, and browser compatibility when modules aren't available.

6.7 Function Methods (call, apply, bind)

Method Syntax Arguments Returns When Called
call() fn.call(thisArg, arg1, arg2, ...) Individual arguments Function result Immediately
apply() fn.apply(thisArg, [args]) Array of arguments Function result Immediately
bind() fn.bind(thisArg, arg1, ...) Individual arguments (partial) New bound function Later (returned fn)
Use Case Best Method Why
Set 'this' and call now call() Known number of arguments, immediate execution
Set 'this' with array args apply() Arguments already in array, or use Math.max/min with arrays
Set 'this' for later use bind() Event handlers, callbacks, partial application
Partial application bind() Pre-fill some arguments, returns reusable function
Borrowing methods call() or apply() Use array methods on array-like objects

Example: call() and apply()

const person = {
    name: 'Alice',
    greet(greeting, punctuation) {
        return `${greeting}, I'm ${this.name}${punctuation}`;
    }
};

console.log(person.greet('Hello', '!'));  // "Hello, I'm Alice!"

// call() - set 'this' and pass individual arguments
const otherPerson = { name: 'Bob' };
console.log(person.greet.call(otherPerson, 'Hi', '.'));
// "Hi, I'm Bob."

// apply() - set 'this' and pass arguments as array
console.log(person.greet.apply(otherPerson, ['Hey', '...']));
// "Hey, I'm Bob..."

// Practical: borrowing array methods for array-like objects
function sumAll() {
    // 'arguments' is array-like, not real array
    // Borrow reduce() from Array
    return Array.prototype.reduce.call(arguments, (sum, n) => sum + n, 0);
}
console.log(sumAll(1, 2, 3, 4));  // 10

// Math.max with apply (pre-spread operator)
const numbers = [5, 2, 9, 1, 7];
const max = Math.max.apply(null, numbers);  // 9
// Modern: Math.max(...numbers)

// Invoking function with specific context
function introduce() {
    return `My name is ${this.name} and I'm ${this.age}`;
}
const user = { name: 'Charlie', age: 30 };
console.log(introduce.call(user));
// "My name is Charlie and I'm 30"

Example: bind() for permanent binding

const obj = {
    value: 42,
    getValue() {
        return this.value;
    }
};

// Problem: 'this' lost when method assigned to variable
const getValue = obj.getValue;
console.log(getValue());  // undefined ('this' is window/undefined)

// Solution: bind() creates new function with fixed 'this'
const boundGetValue = obj.getValue.bind(obj);
console.log(boundGetValue());  // 42

// Practical: event handlers
class Button {
    constructor(label) {
        this.label = label;
        this.clickCount = 0;
    }
    
    handleClick() {
        this.clickCount++;
        console.log(`${this.label} clicked ${this.clickCount} times`);
    }
}

const btn = new Button('Submit');
// Without bind, 'this' would be the button element, not our object
document.querySelector('button').addEventListener('click', 
    btn.handleClick.bind(btn)
);

// Partial application with bind()
function multiply(a, b) {
    return a * b;
}
const double = multiply.bind(null, 2);  // Pre-fill first argument
const triple = multiply.bind(null, 3);
console.log(double(5));  // 10
console.log(triple(5));  // 15

// Binding with setTimeout
const timer = {
    seconds: 0,
    start() {
        // bind() ensures 'this' refers to timer object
        setInterval(function() {
            this.seconds++;
            console.log(this.seconds);
        }.bind(this), 1000);
        
        // Modern alternative: arrow function (lexical 'this')
        // setInterval(() => { this.seconds++; }, 1000);
    }
};

// Multiple bind() calls - first one wins
function log() { console.log(this.value); }
const obj1 = { value: 1 };
const obj2 = { value: 2 };
const bound1 = log.bind(obj1);
const bound2 = bound1.bind(obj2);  // Has no effect
bound2();  // 1 (not 2 - can't rebind)
Modern Context: Arrow functions (=>) with lexical this have reduced the need for bind() in many cases. However, call(), apply(), and bind() remain essential for: method borrowing, explicit context control, partial application, and working with legacy code.

6.8 Recursion and Tail Call Optimization

Concept Definition Characteristics
Recursion Function calling itself to solve smaller subproblems Base case + recursive case; elegant for tree/graph problems
Base Case Condition that stops recursion without further calls Prevents infinite recursion; returns direct result
Recursive Case Logic that breaks problem down and calls function recursively Progress toward base case; must modify parameters
Call Stack Stack storing execution contexts for function calls Each recursive call adds frame; limited size (stack overflow risk)
Tail Call Function call that's the last operation (nothing after return) Eligible for optimization; no pending operations
Tail Call Optimization (TCO) LIMITED Reuse stack frame for tail calls instead of creating new ones ES6 spec, but poor browser support; prevents stack overflow
Pattern Stack Usage TCO Eligible When to Use
Standard Recursion O(n) stack frames ✗ No (pending operations after call) Simple problems, small input size
Tail Recursion O(1) with TCO, O(n) without ✓ Yes (return is direct recursive call) Large inputs if TCO available
Accumulator Pattern O(1) with TCO ✓ Yes (pass result through parameters) Convert standard recursion to tail recursion
Iteration (Loop) O(1) stack N/A (no recursion) Production code, guaranteed stack safety

Example: Basic recursion patterns

// Standard recursion - NOT tail recursive
function factorial(n) {
    // Base case
    if (n <= 1) return 1;
    
    // Recursive case - operation AFTER recursive call
    return n * factorial(n - 1);  // ✗ Not tail call (multiplication pending)
}
console.log(factorial(5));  // 120
// Stack: factorial(5) waits for factorial(4)
//        factorial(4) waits for factorial(3), etc.

// Tail recursive version - uses accumulator
function factorialTail(n, acc = 1) {
    // Base case
    if (n <= 1) return acc;
    
    // Recursive case - nothing after recursive call
    return factorialTail(n - 1, n * acc);  // ✓ Tail call (direct return)
}
console.log(factorialTail(5));  // 120
// With TCO: reuses same stack frame

// Fibonacci - standard recursion (exponential time!)
function fib(n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);  // Two recursive calls
}
// Very slow for large n: fib(40) takes seconds

// Fibonacci - tail recursive with two accumulators
function fibTail(n, a = 0, b = 1) {
    if (n === 0) return a;
    return fibTail(n - 1, b, a + b);  // Tail call
}
console.log(fibTail(40));  // Fast even for large n

// Array sum - standard recursion
function sumArray(arr) {
    if (arr.length === 0) return 0;
    return arr[0] + sumArray(arr.slice(1));  // ✗ Not tail call
}

// Array sum - tail recursive
function sumArrayTail(arr, acc = 0) {
    if (arr.length === 0) return acc;
    return sumArrayTail(arr.slice(1), acc + arr[0]);  // ✓ Tail call
}

Example: Converting to tail recursion

// Problem: countdown - NOT tail recursive
function countdown(n) {
    if (n < 0) return;
    console.log(n);
    countdown(n - 1);
    console.log('Done', n);  // ✗ Operation after recursive call
}

// Solution: tail recursive countdown
function countdownTail(n) {
    if (n < 0) return;
    console.log(n);
    return countdownTail(n - 1);  // ✓ Tail call
}

// Problem: power - NOT tail recursive
function power(base, exp) {
    if (exp === 0) return 1;
    return base * power(base, exp - 1);  // ✗ Multiplication pending
}

// Solution: tail recursive with accumulator
function powerTail(base, exp, acc = 1) {
    if (exp === 0) return acc;
    return powerTail(base, exp - 1, acc * base);  // ✓ Tail call
}

// Tree traversal - naturally recursive
function sumTree(node) {
    if (!node) return 0;
    return node.value 
        + sumTree(node.left) 
        + sumTree(node.right);
    // Not tail recursive but appropriate for trees
}

// Tail recursive tree traversal (using continuation)
function sumTreeTail(node, cont = x => x) {
    if (!node) return cont(0);
    return sumTreeTail(node.left, leftSum =>
        sumTreeTail(node.right, rightSum =>
            cont(node.value + leftSum + rightSum)
        )
    );
}
Important Limitations:
  • TCO Support: Proper tail call optimization is in ES6 spec but only Safari implements it. Chrome/Firefox/Edge don't support TCO.
  • Stack Overflow Risk: Without TCO, even tail-recursive functions will overflow stack with large inputs (typically 10,000-15,000 calls).
  • Production Code: For critical code, use iteration (loops) instead of recursion to guarantee stack safety.
  • Trampoline: Workaround pattern to simulate TCO, but adds complexity.

Example: When to use recursion vs iteration

// ✓ Good use of recursion - tree/graph structures
function findNode(tree, value) {
    if (!tree) return null;
    if (tree.value === value) return tree;
    return findNode(tree.left, value) || findNode(tree.right, value);
}

// ✓ Good use of recursion - divide and conquer
function binarySearch(arr, target, left = 0, right = arr.length - 1) {
    if (left > right) return -1;
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] === target) return mid;
    if (arr[mid] > target) return binarySearch(arr, target, left, mid - 1);
    return binarySearch(arr, target, mid + 1, right);
}

// ✗ Bad use of recursion - simple iteration better
function sumToN(n) {
    if (n === 0) return 0;
    return n + sumToN(n - 1);  // Just use n * (n + 1) / 2
}

// ✓ Better: iterative solution
function sumToNIterative(n) {
    let sum = 0;
    for (let i = 1; i <= n; i++) {
        sum += i;
    }
    return sum;
    // Or: return n * (n + 1) / 2;
}

// Trampoline pattern (TCO workaround)
function trampoline(fn) {
    while (typeof fn === 'function') {
        fn = fn();  // Keep calling until not a function
    }
    return fn;
}

function factorialTrampoline(n, acc = 1) {
    if (n <= 1) return acc;
    return () => factorialTrampoline(n - 1, n * acc);  // Return thunk
}

console.log(trampoline(factorialTrampoline(100000)));  // No stack overflow

Section 6 Summary

  • Function types: Declarations (hoisted), expressions (not hoisted), arrows (lexical this), generators (yield), async (await)
  • Arrow functions: Concise syntax, lexical this, no arguments/new/prototype, ideal for callbacks
  • Parameters: Default values, rest (...args), destructuring (array/object), combine for flexible APIs
  • Higher-order functions: Take/return functions; enables map/filter/reduce, composition, abstraction
  • Closures: Functions retaining outer scope access; enables private state, factories, memoization
  • IIFE: Immediately-invoked functions; creates private scope, module pattern (pre-ES6 modules)
  • call/apply/bind: Control this context; call/apply execute now, bind returns new function
  • Recursion: Self-calling functions; elegant for trees/graphs; tail recursion + TCO prevents stack overflow (limited support)

7. Objects and Property Management

7.1 Object Literal Syntax and Computed Properties

Feature Syntax ES Version Description
Basic Object Literal { key: value } ES3 Traditional property definition with static keys
Property Shorthand ES6 { name } ES6 Equivalent to { name: name } when variable name matches key
Method Shorthand ES6 { method() {} } ES6 Concise method definition, equivalent to { method: function() {} }
Computed Property ES6 { [expression]: value } ES6 Property name computed at runtime from expression
Computed Method ES6 { [expr]() {} } ES6 Method name computed dynamically
Spread Properties ES2018 { ...obj } ES2018 Copy enumerable properties from source object
Getters/Setters { get prop() {}, set prop(v) {} } ES5 Define accessor properties with custom logic

Example: Modern object literal syntax

// ES6+ object literal features
const name = 'Alice';
const age = 30;
const propKey = 'dynamicKey';

const user = {
    // Property shorthand (ES6)
    name,  // Same as name: name
    age,   // Same as age: age
    
    // Method shorthand (ES6)
    greet() {
        return `Hello, I'm ${this.name}`;
    },
    
    // Computed property name (ES6)
    [propKey]: 'dynamic value',
    [`computed_${name}`]: true,
    
    // Computed method name (ES6)
    [`get${name}Info`]() {
        return `${this.name} is ${this.age}`;
    },
    
    // Traditional syntax still works
    email: 'alice@example.com',
    
    // Arrow function as property (not a method)
    arrowFunc: () => 'arrow',
    
    // Getter and setter
    get fullInfo() {
        return `${this.name}, ${this.age} years old`;
    },
    set fullInfo(info) {
        [this.name, this.age] = info.split(', ');
    }
};

console.log(user.name);  // 'Alice'
console.log(user.greet());  // 'Hello, I'm Alice'
console.log(user.dynamicKey);  // 'dynamic value'
console.log(user.computed_Alice);  // true
console.log(user.getAliceInfo());  // 'Alice is 30'
console.log(user.fullInfo);  // 'Alice, 30 years old'

Example: Computed properties in practice

// Dynamic property names
const fields = ['id', 'name', 'email'];
const values = [1, 'Bob', 'bob@example.com'];

// Build object with computed properties
const user = fields.reduce((obj, field, index) => {
    obj[field] = values[index];
    return obj;
}, {});
// { id: 1, name: 'Bob', email: 'bob@example.com' }

// Using computed properties in literal
const status = 'active';
const config = {
    [`is${status.charAt(0).toUpperCase() + status.slice(1)}`]: true
};
// { isActive: true }

// Symbol as computed property
const SECRET = Symbol('secret');
const obj = {
    public: 'visible',
    [SECRET]: 'hidden'  // Property name is a symbol
};
console.log(obj.public);  // 'visible'
console.log(obj[SECRET]);  // 'hidden'
console.log(Object.keys(obj));  // ['public'] - symbols not enumerable

// Spread properties (ES2018)
const defaults = { theme: 'dark', lang: 'en' };
const userPrefs = { lang: 'fr', fontSize: 14 };
const settings = { ...defaults, ...userPrefs };
// { theme: 'dark', lang: 'fr', fontSize: 14 }
// userPrefs.lang overwrites defaults.lang

7.2 Property Descriptors and Object.defineProperty

Descriptor Attribute Type Default (defineProperty) Default (Literal) Description
value any undefined property value The property's value (data descriptor)
writable boolean false true Can property value be changed?
enumerable boolean false true Shows in for...in, Object.keys()?
configurable boolean false true Can descriptor be changed? Can property be deleted?
get function undefined N/A Getter function (accessor descriptor)
set function undefined N/A Setter function (accessor descriptor)
Method Syntax Description
Object.defineProperty() Object.defineProperty(obj, prop, descriptor) Define or modify single property with full control over attributes
Object.defineProperties() Object.defineProperties(obj, descriptors) Define or modify multiple properties at once
Object.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor(obj, prop) Get descriptor object for single property
Object.getOwnPropertyDescriptors() ES2017 Object.getOwnPropertyDescriptors(obj) Get descriptors for all own properties

Example: Property descriptors in action

const obj = {};

// Define property with custom descriptor
Object.defineProperty(obj, 'name', {
    value: 'Alice',
    writable: false,     // Cannot change value
    enumerable: true,    // Shows in for...in, Object.keys()
    configurable: false  // Cannot delete or reconfigure
});

obj.name = 'Bob';  // Fails silently (strict mode: TypeError)
console.log(obj.name);  // 'Alice' (unchanged)

delete obj.name;  // Fails silently (configurable: false)
console.log(obj.name);  // 'Alice' (still exists)

// Check property descriptor
const desc = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(desc);
// { value: 'Alice', writable: false, enumerable: true, configurable: false }

// Compare with literal property (all attributes true)
const obj2 = { name: 'Charlie' };
const desc2 = Object.getOwnPropertyDescriptor(obj2, 'name');
console.log(desc2);
// { value: 'Charlie', writable: true, enumerable: true, configurable: true }

Example: Accessor properties with get/set

const person = {
    firstName: 'John',
    lastName: 'Doe'
};

// Define computed property with getter/setter
Object.defineProperty(person, 'fullName', {
    get() {
        return `${this.firstName} ${this.lastName}`;
    },
    set(value) {
        [this.firstName, this.lastName] = value.split(' ');
    },
    enumerable: true,
    configurable: true
});

console.log(person.fullName);  // 'John Doe'
person.fullName = 'Jane Smith';
console.log(person.firstName);  // 'Jane'
console.log(person.lastName);  // 'Smith'

// Define multiple properties at once
const account = {};
Object.defineProperties(account, {
    _balance: {
        value: 0,
        writable: true,
        enumerable: false  // Private-like (convention)
    },
    balance: {
        get() { return this._balance; },
        set(val) {
            if (val < 0) throw new Error('Balance cannot be negative');
            this._balance = val;
        },
        enumerable: true
    },
    currency: {
        value: 'USD',
        writable: false,
        enumerable: true
    }
});

console.log(account.balance);  // 0
account.balance = 100;
console.log(account.balance);  // 100
// account.balance = -50;  // Error: Balance cannot be negative

Example: Non-enumerable and non-configurable properties

const obj = { a: 1, b: 2 };

// Add non-enumerable property
Object.defineProperty(obj, 'hidden', {
    value: 'secret',
    enumerable: false  // Won't show in iteration
});

console.log(obj.hidden);  // 'secret' (accessible directly)
console.log(Object.keys(obj));  // ['a', 'b'] (hidden not listed)
for (let key in obj) {
    console.log(key);  // Only logs 'a' and 'b'
}

// Non-configurable property
Object.defineProperty(obj, 'permanent', {
    value: 'cannot change',
    configurable: false
});

// Cannot reconfigure
try {
    Object.defineProperty(obj, 'permanent', {
        value: 'new value'  // TypeError: cannot redefine
    });
} catch (e) {
    console.log('Cannot reconfigure');
}

// Cannot delete
delete obj.permanent;  // Fails silently
console.log(obj.permanent);  // Still exists

// Get all descriptors (ES2017)
const allDescriptors = Object.getOwnPropertyDescriptors(obj);
console.log(allDescriptors.hidden);
// { value: 'secret', writable: false, enumerable: false, configurable: false }
Important: Data descriptors (value/writable) and accessor descriptors (get/set) are mutually exclusive. A property can have value OR get/set, not both. Non-configurable properties cannot be deleted or have their descriptor changed (except writable can go from true to false).

7.3 Object Methods (Object.keys, Object.values, Object.entries)

Method Returns ES Version Description
Object.keys(obj) Array of strings ES5 Own enumerable property names (keys)
Object.values(obj) ES2017 Array of values ES2017 Own enumerable property values
Object.entries(obj) ES2017 Array of [key, value] pairs ES2017 Own enumerable properties as key-value pairs
Object.fromEntries(entries) ES2019 Object ES2019 Create object from array of [key, value] pairs (inverse of entries)
Object.getOwnPropertyNames(obj) Array of strings ES5 All own property names (including non-enumerable)
Object.getOwnPropertySymbols(obj) ES6 Array of symbols ES6 All own symbol properties
Object.hasOwn(obj, prop) ES2022 boolean ES2022 Safe alternative to hasOwnProperty (recommended)
obj.hasOwnProperty(prop) boolean ES3 Check if property is own (not inherited); use Object.hasOwn instead
Comparison for...in Object.keys() Object.getOwnPropertyNames()
Own Properties ✓ Yes ✓ Yes ✓ Yes
Inherited Properties ✓ Yes (from prototype chain) ✗ No ✗ No
Non-enumerable ✗ No ✗ No ✓ Yes
Symbol Properties ✗ No ✗ No ✗ No (use getOwnPropertySymbols)

Example: Object.keys, values, entries

const user = {
    id: 1,
    name: 'Alice',
    email: 'alice@example.com',
    active: true
};

// Get all keys
const keys = Object.keys(user);
console.log(keys);  // ['id', 'name', 'email', 'active']

// Get all values (ES2017)
const values = Object.values(user);
console.log(values);  // [1, 'Alice', 'alice@example.com', true]

// Get key-value pairs (ES2017)
const entries = Object.entries(user);
console.log(entries);
// [['id', 1], ['name', 'Alice'], ['email', 'alice@example.com'], ['active', true]]

// Iterate over entries
Object.entries(user).forEach(([key, value]) => {
    console.log(`${key}: ${value}`);
});

// Transform object using entries
const doubled = Object.fromEntries(
    Object.entries({ a: 1, b: 2, c: 3 })
        .map(([key, value]) => [key, value * 2])
);
console.log(doubled);  // { a: 2, b: 4, c: 6 }

// Filter object properties
const activeUsers = Object.fromEntries(
    Object.entries({ u1: { active: true }, u2: { active: false } })
        .filter(([key, val]) => val.active)
);
// { u1: { active: true } }

Example: Enumerable vs non-enumerable properties

const obj = { a: 1, b: 2 };

// Add non-enumerable property
Object.defineProperty(obj, 'hidden', {
    value: 'secret',
    enumerable: false
});

// Add symbol property
const sym = Symbol('symbol');
obj[sym] = 'symbol value';

console.log(Object.keys(obj));  // ['a', 'b'] (only enumerable string keys)
console.log(Object.getOwnPropertyNames(obj));  // ['a', 'b', 'hidden'] (includes non-enumerable)
console.log(Object.getOwnPropertySymbols(obj));  // [Symbol(symbol)]

// for...in includes inherited enumerable properties
function Parent() {}
Parent.prototype.inherited = 'from parent';

const child = Object.create(new Parent());
child.own = 'own property';

for (let key in child) {
    console.log(key);  // Logs: 'own', 'inherited'
}

// Filter to own properties only
for (let key in child) {
    if (Object.hasOwn(child, key)) {  // ES2022
        console.log(key);  // Logs: 'own' only
    }
}

// Alternative: hasOwnProperty (older)
for (let key in child) {
    if (child.hasOwnProperty(key)) {
        console.log(key);  // Logs: 'own' only
    }
}

Example: Practical use cases

// Count object properties
const obj = { a: 1, b: 2, c: 3 };
const count = Object.keys(obj).length;  // 3

// Check if object is empty
const isEmpty = Object.keys(obj).length === 0;

// Merge objects by converting to entries
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = Object.fromEntries([
    ...Object.entries(obj1),
    ...Object.entries(obj2)
]);
// { a: 1, b: 3, c: 4 } (obj2 overwrites obj1)

// Convert object to Map
const map = new Map(Object.entries(obj));

// Convert Map to object
const objFromMap = Object.fromEntries(map);

// Swap keys and values
const original = { a: 1, b: 2, c: 3 };
const swapped = Object.fromEntries(
    Object.entries(original).map(([k, v]) => [v, k])
);
// { 1: 'a', 2: 'b', 3: 'c' }

// Pick specific properties
function pick(obj, keys) {
    return Object.fromEntries(
        keys.filter(key => key in obj)
            .map(key => [key, obj[key]])
    );
}
const user = { id: 1, name: 'Alice', email: 'a@example.com', age: 30 };
const userPreview = pick(user, ['id', 'name']);
// { id: 1, name: 'Alice' }

7.4 Object Creation Patterns (Object.create, constructors)

Pattern Syntax Prototype Use Case
Object Literal const obj = { } Object.prototype Simple data objects, quick creation
Object() Constructor const obj = new Object() Object.prototype Rarely used (literal preferred)
Object.create() ES5 Object.create(proto, descriptors?) Specified proto Prototypal inheritance, null prototype objects
Constructor Function function Ctor() { } + new Ctor() Ctor.prototype Pre-ES6 classes, instance creation with shared methods
Factory Function function create() { return { }; } Object.prototype (or custom) Object creation without 'new', private state
Class Syntax ES6 class C { } + new C() C.prototype Modern OOP, cleaner syntax over constructors
Method Description Returns
Object.create(proto) Create new object with specified prototype New object inheriting from proto
Object.create(proto, descriptors) Create object with prototype and property descriptors New object with specified properties
Object.create(null) Create object with no prototype (truly empty) Object without any inherited properties
Object.setPrototypeOf(obj, proto) Change prototype of existing object (slow, avoid) The modified object
Object.getPrototypeOf(obj) Get prototype of object Prototype object or null

Example: Object.create() patterns

// Create object with specific prototype
const personProto = {
    greet() {
        return `Hello, I'm ${this.name}`;
    }
};

const person = Object.create(personProto);
person.name = 'Alice';
console.log(person.greet());  // "Hello, I'm Alice"
console.log(Object.getPrototypeOf(person) === personProto);  // true

// Create with property descriptors
const user = Object.create(personProto, {
    name: {
        value: 'Bob',
        writable: true,
        enumerable: true
    },
    age: {
        value: 30,
        writable: true,
        enumerable: true
    }
});
console.log(user.name);  // 'Bob'
console.log(user.greet());  // "Hello, I'm Bob"

// Create object with null prototype (no inherited properties)
const dict = Object.create(null);
dict.key = 'value';
console.log(dict.toString);  // undefined (no inherited methods)
console.log(dict.hasOwnProperty);  // undefined
// Useful for pure data storage, no prototype pollution

// Compare with literal
const literal = {};
console.log(literal.toString);  // [Function: toString] (inherited)
console.log(Object.getPrototypeOf(literal) === Object.prototype);  // true

Example: Constructor functions

// Constructor function (pre-ES6 classes)
function Person(name, age) {
    // Instance properties (unique per instance)
    this.name = name;
    this.age = age;
}

// Shared methods on prototype (memory efficient)
Person.prototype.greet = function() {
    return `Hello, I'm ${this.name}`;
};

Person.prototype.getAge = function() {
    return this.age;
};

// Create instances with 'new'
const alice = new Person('Alice', 30);
const bob = new Person('Bob', 25);

console.log(alice.greet());  // "Hello, I'm Alice"
console.log(bob.greet());  // "Hello, I'm Bob"
console.log(alice.greet === bob.greet);  // true (shared method)

// Verify prototype
console.log(Object.getPrototypeOf(alice) === Person.prototype);  // true
console.log(alice instanceof Person);  // true

// Forgetting 'new' - common mistake
function SafeConstructor(name) {
    // Guard against missing 'new'
    if (!(this instanceof SafeConstructor)) {
        return new SafeConstructor(name);
    }
    this.name = name;
}

const instance1 = new SafeConstructor('Alice');
const instance2 = SafeConstructor('Bob');  // Works without 'new'
console.log(instance2 instanceof SafeConstructor);  // true

Example: Factory functions

// Factory function - no 'new' needed
function createPerson(name, age) {
    // Private variables (closure)
    let privateData = 'secret';
    
    // Return object with public interface
    return {
        name,
        age,
        greet() {
            return `Hello, I'm ${this.name}`;
        },
        getPrivate() {
            return privateData;  // Access private via closure
        }
    };
}

const person1 = createPerson('Alice', 30);  // No 'new'
const person2 = createPerson('Bob', 25);

console.log(person1.greet());  // "Hello, I'm Alice"
console.log(person1.getPrivate());  // 'secret'
console.log(person1.privateData);  // undefined (truly private)

// Factory with prototypal inheritance
function createUser(name) {
    const proto = {
        greet() { return `Hello ${this.name}`; }
    };
    
    const user = Object.create(proto);
    user.name = name;
    return user;
}

const user = createUser('Charlie');
console.log(user.greet());  // "Hello Charlie"

// Modern: ES6 class (preferred over constructors)
class PersonClass {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    greet() {
        return `Hello, I'm ${this.name}`;
    }
}

const personInstance = new PersonClass('Dave', 35);
console.log(personInstance.greet());  // "Hello, I'm Dave"
Best Practice: Modern JavaScript prefers ES6 classes over constructor functions for clarity. Use factory functions when you need private state via closures or want to avoid new. Use Object.create(null) for dictionaries/maps to avoid prototype pollution. Avoid Object.setPrototypeOf() as it's very slow.

7.5 Object Cloning and Merging (Object.assign, spread)

Method Syntax Copy Type Nested Objects
Object.assign() ES6 Object.assign(target, ...sources) Shallow copy Copies references (not deep)
Spread Operator ES2018 { ...obj } Shallow copy Copies references (not deep)
JSON.parse/stringify JSON.parse(JSON.stringify(obj)) Deep copy ✓ Clones nested, but loses functions/symbols/dates
structuredClone() NEW structuredClone(obj) Deep copy ✓ Clones nested, preserves types (not functions)
Manual Deep Clone Recursive function Deep copy ✓ Full control, handle all types
Feature Object.assign() Spread {...} Differences
Copy enumerable props ✓ Yes ✓ Yes Both copy own enumerable properties
Trigger setters ✓ Yes ✗ No assign() invokes setters, spread defines new props
Copy getters/setters ✗ No (executes getter) ✗ No (executes getter) Both execute getters, copy resulting values
Mutate target ✓ Yes (modifies first arg) ✗ No (creates new object) assign() mutates, spread creates new
Syntax Function call Operator (concise) Spread is more concise and readable

Example: Shallow copying with Object.assign and spread

const original = {
    name: 'Alice',
    age: 30,
    address: { city: 'NYC', zip: '10001' }
};

// Object.assign - shallow copy
const copy1 = Object.assign({}, original);
copy1.name = 'Bob';
copy1.address.city = 'LA';  // Modifies nested object

console.log(original.name);  // 'Alice' (primitive copied)
console.log(original.address.city);  // 'LA' (nested reference shared!)

// Spread operator - shallow copy (ES2018)
const copy2 = { ...original };
copy2.age = 25;
copy2.address.zip = '90001';  // Also modifies original!

console.log(original.age);  // 30 (primitive copied)
console.log(original.address.zip);  // '90001' (nested reference shared!)

// Merge multiple objects
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const obj3 = { c: 5, d: 6 };

// Using Object.assign
const merged1 = Object.assign({}, obj1, obj2, obj3);
// { a: 1, b: 3, c: 5, d: 6 } (later values overwrite)

// Using spread (more common)
const merged2 = { ...obj1, ...obj2, ...obj3 };
// { a: 1, b: 3, c: 5, d: 6 } (same result)

// Add/override properties while spreading
const enhanced = {
    ...original,
    age: 35,  // Override
    email: 'alice@example.com'  // Add new
};
// { name: 'Alice', age: 35, address: {...}, email: '...' }

Example: Deep cloning techniques

const original = {
    name: 'Alice',
    age: 30,
    hobbies: ['reading', 'coding'],
    address: { city: 'NYC', coords: { lat: 40, lng: -74 } },
    createdAt: new Date('2024-01-01')
};

// Method 1: JSON (quick but lossy)
const jsonClone = JSON.parse(JSON.stringify(original));
jsonClone.hobbies.push('gaming');
jsonClone.address.city = 'LA';

console.log(original.hobbies);  // ['reading', 'coding'] (not affected)
console.log(original.address.city);  // 'NYC' (not affected)
console.log(jsonClone.createdAt);  // String, not Date! (limitation)

// Method 2: structuredClone() (modern, recommended)
const structClone = structuredClone(original);
structClone.address.coords.lat = 35;

console.log(original.address.coords.lat);  // 40 (not affected)
console.log(structClone.createdAt instanceof Date);  // true (preserved!)

// Limitations: cannot clone functions
const withFunction = { 
    data: 'value', 
    method() { return this.data; } 
};
// structuredClone(withFunction);  // Error: functions not cloneable

// Method 3: Manual deep clone (full control)
function deepClone(obj, seen = new WeakMap()) {
    // Handle primitives and null
    if (obj === null || typeof obj !== 'object') return obj;
    
    // Handle circular references
    if (seen.has(obj)) return seen.get(obj);
    
    // Handle Date
    if (obj instanceof Date) return new Date(obj);
    
    // Handle Array
    if (Array.isArray(obj)) {
        const arrCopy = [];
        seen.set(obj, arrCopy);
        obj.forEach((item, i) => {
            arrCopy[i] = deepClone(item, seen);
        });
        return arrCopy;
    }
    
    // Handle Object
    const objCopy = {};
    seen.set(obj, objCopy);
    Object.keys(obj).forEach(key => {
        objCopy[key] = deepClone(obj[key], seen);
    });
    return objCopy;
}

const manualClone = deepClone(original);
manualClone.address.coords.lng = -118;
console.log(original.address.coords.lng);  // -74 (not affected)

Example: Object.assign vs spread differences

// Difference 1: Mutating vs creating
const target = { a: 1 };
const source = { b: 2 };

Object.assign(target, source);  // Mutates target
console.log(target);  // { a: 1, b: 2 } (modified)

const newObj = { ...target, ...source };  // Creates new object
// target unchanged

// Difference 2: Setters
const obj = {
    _value: 0,
    set value(v) {
        console.log('Setter called:', v);
        this._value = v;
    }
};

// Object.assign triggers setter
Object.assign(obj, { value: 10 });  // Logs: "Setter called: 10"

// Spread defines new property (doesn't trigger setter)
const copy = { ...obj, value: 20 };  // No setter call
console.log(copy.value);  // 20 (simple property, not setter)

// Use case: Default options
function configure(options = {}) {
    const defaults = {
        timeout: 5000,
        retries: 3,
        cache: true
    };
    
    // Merge with user options (spread preferred)
    return { ...defaults, ...options };
}

const config1 = configure({ timeout: 10000 });
// { timeout: 10000, retries: 3, cache: true }

const config2 = configure({ retries: 5, debug: true });
// { timeout: 5000, retries: 5, cache: true, debug: true }
Warning: Both Object.assign() and spread create shallow copies. Nested objects are copied by reference, not value. Modifying nested properties affects the original. For true deep copies, use structuredClone() (modern) or implement custom deep clone logic.

7.6 Property Getters and Setters

Syntax Form Example When to Use
Object Literal Getter { get prop() { return value; } } Computed properties, validation on read
Object Literal Setter { set prop(val) { this._prop = val; } } Validation/transformation on write
defineProperty Getter Object.defineProperty(obj, 'prop', { get() {} }) Add getter to existing object
defineProperty Setter Object.defineProperty(obj, 'prop', { set(v) {} }) Add setter to existing object
Class Getter class C { get prop() { } } ES6 class computed properties
Class Setter class C { set prop(v) { } } ES6 class property validation
Use Case Pattern Example
Computed Property Getter derives value from other properties get fullName() { return this.first + ' ' + this.last; }
Validation Setter validates before assignment set age(v) { if (v < 0) throw Error; this._age = v; }
Lazy Initialization Getter computes value on first access get data() { return this._data || (this._data = load()); }
Side Effects Setter triggers additional actions set value(v) { this._value = v; this.notify(); }
Read-only Property Getter without setter get id() { return this._id; } (no setter)
Private Backing Field Store value in _property, expose via getter/setter get name() { return this._name; }

Example: Getters and setters in object literals

const person = {
    firstName: 'John',
    lastName: 'Doe',
    _age: 30,  // Backing field (convention: underscore = private-like)
    
    // Getter: computed property
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    },
    
    // Setter: parse and set multiple properties
    set fullName(name) {
        const parts = name.split(' ');
        this.firstName = parts[0];
        this.lastName = parts[1];
    },
    
    // Getter with validation/transformation
    get age() {
        return this._age;
    },
    
    // Setter with validation
    set age(value) {
        if (typeof value !== 'number' || value < 0 || value > 150) {
            throw new Error('Invalid age');
        }
        this._age = value;
    },
    
    // Read-only property (getter only)
    get birthYear() {
        return new Date().getFullYear() - this._age;
    }
};

// Using getters and setters
console.log(person.fullName);  // "John Doe" (getter called)
person.fullName = 'Jane Smith';  // Setter called
console.log(person.firstName);  // 'Jane'
console.log(person.lastName);  // 'Smith'

console.log(person.age);  // 30 (getter)
person.age = 25;  // Setter with validation
// person.age = -5;  // Error: Invalid age

console.log(person.birthYear);  // Computed from age
// person.birthYear = 1990;  // TypeError: no setter (read-only)

Example: Getters/setters in classes

class Rectangle {
    constructor(width, height) {
        this._width = width;
        this._height = height;
    }
    
    // Getter for computed property
    get area() {
        return this._width * this._height;
    }
    
    get perimeter() {
        return 2 * (this._width + this._height);
    }
    
    // Getter/setter with validation
    get width() {
        return this._width;
    }
    
    set width(value) {
        if (value <= 0) {
            throw new Error('Width must be positive');
        }
        this._width = value;
    }
    
    get height() {
        return this._height;
    }
    
    set height(value) {
        if (value <= 0) {
            throw new Error('Height must be positive');
        }
        this._height = value;
    }
}

const rect = new Rectangle(10, 5);
console.log(rect.area);  // 50 (computed)
console.log(rect.perimeter);  // 30 (computed)

rect.width = 20;  // Setter with validation
console.log(rect.area);  // 100 (auto-updated)

// rect.width = -5;  // Error: Width must be positive
// rect.area = 200;  // TypeError: no setter (read-only)

Example: Advanced getter/setter patterns

// Lazy initialization with getter
class DataLoader {
    constructor(url) {
        this.url = url;
        this._data = null;
    }
    
    get data() {
        // Load data on first access, cache for subsequent access
        if (this._data === null) {
            console.log('Loading data...');
            this._data = this.loadData();  // Expensive operation
        }
        return this._data;
    }
    
    loadData() {
        // Simulate data loading
        return { loaded: true, url: this.url };
    }
}

const loader = new DataLoader('/api/data');
console.log(loader.data);  // Logs: "Loading data...", returns data
console.log(loader.data);  // No log, returns cached data

// Observable pattern with setters
class Observable {
    constructor() {
        this._observers = [];
        this._value = null;
    }
    
    get value() {
        return this._value;
    }
    
    set value(newValue) {
        const oldValue = this._value;
        this._value = newValue;
        this.notify(oldValue, newValue);
    }
    
    subscribe(observer) {
        this._observers.push(observer);
    }
    
    notify(oldValue, newValue) {
        this._observers.forEach(fn => fn(newValue, oldValue));
    }
}

const obs = new Observable();
obs.subscribe((newVal, oldVal) => {
    console.log(`Changed from ${oldVal} to ${newVal}`);
});
obs.value = 10;  // Logs: "Changed from null to 10"
obs.value = 20;  // Logs: "Changed from 10 to 20"

// Type conversion in setter
const config = {
    _port: 8080,
    
    get port() {
        return this._port;
    },
    
    set port(value) {
        // Always store as number
        this._port = Number(value);
    }
};

config.port = '3000';  // String input
console.log(config.port);  // 3000 (number)
console.log(typeof config.port);  // 'number'
Best Practice: Use getters for computed properties and derived state. Use setters for validation, type conversion, and triggering side effects. Follow naming convention: use underscore prefix for backing fields (e.g., _value). For true privacy in modern JS, use private fields (#field) instead of convention.

7.7 Object Freezing and Sealing (freeze, seal, preventExtensions)

Method Add Props Delete Props Modify Values Change Config
Normal Object ✓ Yes ✓ Yes ✓ Yes ✓ Yes
Object.preventExtensions() ✗ No ✓ Yes ✓ Yes ✓ Yes
Object.seal() ✗ No ✗ No ✓ Yes ✗ No
Object.freeze() ✗ No ✗ No ✗ No ✗ No
Method Effect Check Method Use Case
Object.preventExtensions(obj) Cannot add new properties Object.isExtensible(obj) Prevent accidental property additions
Object.seal(obj) Cannot add/delete, can modify existing Object.isSealed(obj) Fixed property set, mutable values
Object.freeze(obj) Completely immutable (shallow) Object.isFrozen(obj) Constants, immutable config

Example: Object.preventExtensions()

const obj = { a: 1, b: 2 };

// Prevent adding new properties
Object.preventExtensions(obj);

// Existing properties work normally
obj.a = 10;  // ✓ Works (can modify)
delete obj.b;  // ✓ Works (can delete)
console.log(obj);  // { a: 10 }

// Cannot add new properties
obj.c = 3;  // ✗ Fails silently (strict mode: TypeError)
console.log(obj.c);  // undefined

// Check if extensible
console.log(Object.isExtensible(obj));  // false

// Cannot make extensible again (one-way operation)
// No Object.makeExtensible() method

Example: Object.seal()

const user = { name: 'Alice', age: 30 };

// Seal object - fixed property set
Object.seal(user);

// Can modify existing properties
user.name = 'Bob';  // ✓ Works
user.age = 35;  // ✓ Works
console.log(user);  // { name: 'Bob', age: 35 }

// Cannot add new properties
user.email = 'test@example.com';  // ✗ Fails silently
console.log(user.email);  // undefined

// Cannot delete properties
delete user.age;  // ✗ Fails silently
console.log(user.age);  // 35 (still exists)

// Cannot reconfigure property descriptors
// Object.defineProperty(user, 'name', { enumerable: false });  // ✗ TypeError

// Check if sealed
console.log(Object.isSealed(user));  // true

// Sealed implies non-extensible
console.log(Object.isExtensible(user));  // false

Example: Object.freeze()

const config = {
    API_URL: 'https://api.example.com',
    TIMEOUT: 5000,
    MAX_RETRIES: 3
};

// Freeze object - completely immutable
Object.freeze(config);

// Cannot modify properties
config.TIMEOUT = 10000;  // ✗ Fails silently (strict mode: TypeError)
console.log(config.TIMEOUT);  // 5000 (unchanged)

// Cannot add properties
config.DEBUG = true;  // ✗ Fails silently
console.log(config.DEBUG);  // undefined

// Cannot delete properties
delete config.MAX_RETRIES;  // ✗ Fails silently
console.log(config.MAX_RETRIES);  // 3 (still exists)

// Check if frozen
console.log(Object.isFrozen(config));  // true

// Frozen implies sealed and non-extensible
console.log(Object.isSealed(config));  // true
console.log(Object.isExtensible(config));  // false

// Common use: constants
const CONSTANTS = Object.freeze({
    PI: 3.14159,
    E: 2.71828,
    MAX_INT: 2147483647
});
// CONSTANTS.PI = 3.14;  // ✗ Cannot modify

Example: Shallow vs deep freeze

// Object.freeze is SHALLOW - nested objects not frozen
const user = {
    name: 'Alice',
    settings: {
        theme: 'dark',
        notifications: true
    }
};

Object.freeze(user);

// Top-level frozen
user.name = 'Bob';  // ✗ Fails
console.log(user.name);  // 'Alice' (unchanged)

// Nested object NOT frozen
user.settings.theme = 'light';  // ✓ Works!
user.settings.notifications = false;  // ✓ Works!
console.log(user.settings.theme);  // 'light' (modified)

// Deep freeze implementation
function deepFreeze(obj) {
    // Freeze object itself
    Object.freeze(obj);
    
    // Recursively freeze all object properties
    Object.getOwnPropertyNames(obj).forEach(prop => {
        const value = obj[prop];
        if (value && typeof value === 'object' && !Object.isFrozen(value)) {
            deepFreeze(value);
        }
    });
    
    return obj;
}

const deepUser = {
    name: 'Charlie',
    settings: { theme: 'dark', notifications: true }
};

deepFreeze(deepUser);

// Now nested is also frozen
deepUser.settings.theme = 'light';  // ✗ Fails
console.log(deepUser.settings.theme);  // 'dark' (unchanged)

Example: Practical use cases

// Use case 1: Enum-like constants
const Status = Object.freeze({
    PENDING: 'pending',
    APPROVED: 'approved',
    REJECTED: 'rejected'
});
// Status.PENDING = 'other';  // ✗ Cannot modify

// Use case 2: Configuration objects
const appConfig = Object.freeze({
    version: '1.0.0',
    apiEndpoint: '/api/v1',
    features: {
        auth: true,
        analytics: true
    }
});

// Use case 3: Prevent accidental mutations in function
function processUser(user) {
    // Prevent function from modifying passed object
    const immutableUser = Object.freeze({ ...user });
    
    // immutableUser.name = 'Modified';  // ✗ Fails
    
    // Return new object instead
    return { ...immutableUser, processed: true };
}

// Use case 4: Sealed object with mutable values
const counters = Object.seal({
    clicks: 0,
    views: 0,
    purchases: 0
});

// Can update counters
counters.clicks++;
counters.views += 10;

// But cannot add new counters
// counters.downloads = 0;  // ✗ Fails

// Comparison matrix
const obj1 = { a: 1 };
Object.preventExtensions(obj1);
console.log({
    canAdd: !Object.isExtensible(obj1),      // true (cannot add)
    canDelete: !Object.isSealed(obj1),        // true (can delete)
    canModify: !Object.isFrozen(obj1)         // true (can modify)
});

const obj2 = { a: 1 };
Object.seal(obj2);
console.log({
    canAdd: !Object.isExtensible(obj2),      // true (cannot add)
    canDelete: !Object.isSealed(obj2),        // false (cannot delete)
    canModify: !Object.isFrozen(obj2)         // true (can modify)
});

const obj3 = { a: 1 };
Object.freeze(obj3);
console.log({
    canAdd: !Object.isExtensible(obj3),      // true (cannot add)
    canDelete: !Object.isSealed(obj3),        // false (cannot delete)
    canModify: !Object.isFrozen(obj3)         // false (cannot modify)
});
Important Limitations:
  • Shallow only: All three methods (preventExtensions, seal, freeze) only affect the immediate object, not nested objects
  • Irreversible: Cannot undo these operations - once frozen/sealed, always frozen/sealed
  • Silent failures: In non-strict mode, violations fail silently (no error thrown)
  • Performance: Frozen objects may have optimization benefits but are rarely the bottleneck

Section 7 Summary

  • Modern literals: Property/method shorthand, computed properties, spread syntax, getters/setters for clean object creation
  • Property descriptors: Control writable/enumerable/configurable attributes; defineProperty for fine-grained control
  • Object methods: keys/values/entries for iteration; fromEntries for transformation; hasOwn for safe property checks
  • Creation patterns: Object.create for prototypal inheritance; constructors (legacy) vs classes (modern); factory functions for flexibility
  • Cloning: Spread/assign for shallow copy; structuredClone for deep copy; JSON for simple deep copy (lossy)
  • Getters/setters: Computed properties, validation, lazy initialization; use _prefix convention for backing fields
  • Immutability: preventExtensions (no add), seal (no add/delete), freeze (no changes); all shallow, implement deepFreeze for nested

8. Prototypes and Inheritance System

8.1 Prototype Chain and Property Lookup

Concept Description Key Points
Prototype Internal reference ([[Prototype]]) that links object to another object Forms inheritance chain, enables property sharing
Prototype Chain Series of prototype links from object to Object.prototype to null Property lookup traverses chain until found or reaches null
__proto__ Accessor property to get/set [[Prototype]] (legacy, avoid) Non-standard but widely supported; use Object.getPrototypeOf() instead
Object.prototype Top of prototype chain for normal objects; contains toString, hasOwnProperty, etc. Inherits from null; all objects inherit from it unless explicitly set
Function.prototype Prototype for all functions; contains call, apply, bind, etc. Functions inherit from Function.prototype which inherits from Object.prototype
.prototype Property Property on constructor functions; used as prototype for instances created with new Only constructor functions have .prototype; instances have [[Prototype]]
Property Lookup Step Location Action
1. Own Properties Object itself Check if property exists directly on object; return if found
2. Prototype Object's [[Prototype]] If not found, check prototype; return if found
3. Prototype Chain Prototype's prototype, etc. Continue up chain until property found
4. Object.prototype Top of chain Check Object.prototype; return if found
5. null End of chain Return undefined if not found anywhere

Example: Understanding the prototype chain

// Every object has internal [[Prototype]] link
const obj = { a: 1 };

// Access prototype using Object.getPrototypeOf()
console.log(Object.getPrototypeOf(obj) === Object.prototype);  // true

// Object.prototype's prototype is null (end of chain)
console.log(Object.getPrototypeOf(Object.prototype));  // null

// Property lookup demonstration
const animal = {
    eats: true,
    walk() {
        console.log('Animal walks');
    }
};

const rabbit = {
    jumps: true
};

// Set rabbit's prototype to animal
Object.setPrototypeOf(rabbit, animal);

// rabbit can access both own and inherited properties
console.log(rabbit.jumps);  // true (own property)
console.log(rabbit.eats);   // true (inherited from animal)
rabbit.walk();  // "Animal walks" (inherited method)

// Property lookup order
console.log(rabbit.toString);  // [Function: toString] (from Object.prototype)

// Chain: rabbit -> animal -> Object.prototype -> null
console.log(Object.getPrototypeOf(rabbit) === animal);  // true
console.log(Object.getPrototypeOf(animal) === Object.prototype);  // true
console.log(Object.getPrototypeOf(Object.prototype));  // null

Example: Property shadowing and prototype lookup

const parent = {
    name: 'Parent',
    greet() {
        return `Hello from ${this.name}`;
    }
};

const child = Object.create(parent);
child.age = 10;

// Access inherited property
console.log(child.name);  // 'Parent' (from prototype)
console.log(child.greet());  // "Hello from Parent"

// Own property shadows prototype property
child.name = 'Child';
console.log(child.name);  // 'Child' (own property, shadows prototype)

// Check property ownership
console.log(child.hasOwnProperty('name'));  // true (own)
console.log(child.hasOwnProperty('greet'));  // false (inherited)
console.log('greet' in child);  // true (checks own + prototype)

// Delete own property - prototype property revealed
delete child.name;
console.log(child.name);  // 'Parent' (prototype property no longer shadowed)

// Writing to inherited property creates own property
const obj = Object.create({ x: 1 });
console.log(obj.x);  // 1 (inherited)
obj.x = 2;  // Creates own property (doesn't modify prototype)
console.log(obj.x);  // 2 (own property)
console.log(Object.getPrototypeOf(obj).x);  // 1 (prototype unchanged)

Example: Working with prototypes

// Get prototype of object
const obj = {};
const proto = Object.getPrototypeOf(obj);
console.log(proto === Object.prototype);  // true

// Set prototype (avoid in production - slow!)
const animal = { type: 'animal' };
const dog = { breed: 'Labrador' };
Object.setPrototypeOf(dog, animal);
console.log(dog.type);  // 'animal' (inherited)

// Create object with specific prototype (preferred)
const cat = Object.create(animal);
cat.breed = 'Persian';
console.log(cat.type);  // 'animal' (inherited)

// Check if object is in prototype chain
console.log(animal.isPrototypeOf(dog));  // true
console.log(animal.isPrototypeOf(cat));  // true
console.log(Object.prototype.isPrototypeOf(dog));  // true (all objects)

// Create object with null prototype (no inheritance)
const noProto = Object.create(null);
console.log(noProto.toString);  // undefined (no Object.prototype)
console.log(Object.getPrototypeOf(noProto));  // null

// Prototype chain visualization
function showChain(obj, label = 'obj') {
    console.log(`${label}:`, obj);
    const proto = Object.getPrototypeOf(obj);
    if (proto) {
        showChain(proto, `${label}.__proto__`);
    } else {
        console.log('End of chain: null');
    }
}

const myObj = { x: 1 };
showChain(myObj);
// myObj: { x: 1 }
// myObj.__proto__: Object.prototype
// End of chain: null
Important: Property lookup is read-only up the chain. Writing to a property always creates/modifies own property (unless setter exists in prototype). Use Object.getPrototypeOf() and Object.create() instead of deprecated __proto__. Avoid Object.setPrototypeOf() for performance reasons.

8.2 Constructor Functions and new Operator

Concept Description Behavior
Constructor Function Regular function used with new to create instances Conventionally capitalized; sets up instance properties
new Operator Creates instance, sets prototype, binds this, returns object 4-step process: create, link, execute, return
Constructor.prototype Object that becomes prototype of instances created with new Constructor() Shared methods placed here for memory efficiency
constructor Property Property on prototype pointing back to constructor function Allows instances to identify their constructor
Instance Properties Properties unique to each instance (set in constructor) Defined using this.property = value
Prototype Methods Methods shared across all instances (on Constructor.prototype) Memory efficient - one copy shared by all instances
Step new Operator Process Effect
1. Create New empty object created const obj = {}
2. Link Prototype Object's [[Prototype]] set to Constructor.prototype Object.setPrototypeOf(obj, Constructor.prototype)
3. Execute Constructor Constructor called with this bound to new object Constructor.call(obj, ...args)
4. Return Return object (or constructor's return if object) Returns this unless constructor explicitly returns object

Example: Constructor functions and instances

// Constructor function (capitalized by convention)
function Person(name, age) {
    // Instance properties (unique per instance)
    this.name = name;
    this.age = age;
    
    // ✗ Avoid methods here (creates new function per instance)
    // this.greet = function() { return `Hi, I'm ${this.name}`; };
}

// Shared methods on prototype (memory efficient)
Person.prototype.greet = function() {
    return `Hello, I'm ${this.name}`;
};

Person.prototype.getAge = function() {
    return this.age;
};

// Create instances with 'new'
const alice = new Person('Alice', 30);
const bob = new Person('Bob', 25);

// Each instance has own properties
console.log(alice.name);  // 'Alice'
console.log(bob.name);  // 'Bob'

// Shared methods from prototype
console.log(alice.greet());  // "Hello, I'm Alice"
console.log(bob.greet());  // "Hello, I'm Bob"

// Methods are shared (same function object)
console.log(alice.greet === bob.greet);  // true (memory efficient)

// Verify prototype relationship
console.log(Object.getPrototypeOf(alice) === Person.prototype);  // true
console.log(alice.constructor === Person);  // true
console.log(alice instanceof Person);  // true

// Prototype chain: alice -> Person.prototype -> Object.prototype -> null
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype);  // true

Example: Manual simulation of 'new' operator

// Understanding what 'new' does
function myNew(Constructor, ...args) {
    // 1. Create new object
    const obj = {};
    
    // 2. Link to prototype
    Object.setPrototypeOf(obj, Constructor.prototype);
    // Or: obj.__proto__ = Constructor.prototype;
    
    // 3. Execute constructor with 'this' bound to new object
    const result = Constructor.apply(obj, args);
    
    // 4. Return object (or constructor's return if it's an object)
    return (typeof result === 'object' && result !== null) ? result : obj;
}

function Dog(name) {
    this.name = name;
}

Dog.prototype.bark = function() {
    return `${this.name} says woof!`;
};

// Using custom 'new'
const dog1 = myNew(Dog, 'Rex');
console.log(dog1.name);  // 'Rex'
console.log(dog1.bark());  // "Rex says woof!"
console.log(dog1 instanceof Dog);  // true

// Compare with real 'new'
const dog2 = new Dog('Max');
console.log(dog2.name);  // 'Max'
console.log(dog2.bark());  // "Max says woof!"

// Constructor returning object overrides default behavior
function Special() {
    this.x = 1;
    return { y: 2 };  // Explicit object return
}

const s = new Special();
console.log(s.x);  // undefined (returned object used instead)
console.log(s.y);  // 2

// Constructor returning primitive doesn't override
function Normal() {
    this.x = 1;
    return 42;  // Primitive ignored
}

const n = new Normal();
console.log(n.x);  // 1 (this returned as usual)

Example: Constructor patterns and best practices

// Pattern 1: Instance properties + prototype methods (recommended)
function Car(make, model) {
    this.make = make;
    this.model = model;
}

Car.prototype.getDescription = function() {
    return `${this.make} ${this.model}`;
};

// Pattern 2: Static methods (on constructor itself)
Car.fromString = function(str) {
    const [make, model] = str.split(' ');
    return new Car(make, model);
};

const car1 = new Car('Toyota', 'Camry');
const car2 = Car.fromString('Honda Civic');

// Pattern 3: Guard against missing 'new'
function SafeConstructor(value) {
    // Check if called with 'new'
    if (!(this instanceof SafeConstructor)) {
        return new SafeConstructor(value);
    }
    this.value = value;
}

const obj1 = new SafeConstructor(10);  // With 'new'
const obj2 = SafeConstructor(20);  // Without 'new' - still works
console.log(obj1 instanceof SafeConstructor);  // true
console.log(obj2 instanceof SafeConstructor);  // true

// Pattern 4: Setting prototype after constructor definition
function Animal(name) {
    this.name = name;
}

// Replace entire prototype (need to restore constructor)
Animal.prototype = {
    constructor: Animal,  // Restore constructor reference
    speak() {
        return `${this.name} makes a sound`;
    },
    eat() {
        return `${this.name} is eating`;
    }
};

const animal = new Animal('Lion');
console.log(animal.speak());  // "Lion makes a sound"
console.log(animal.constructor === Animal);  // true (restored)

// ✗ Common mistake: adding methods as instance properties
function Inefficient(name) {
    this.name = name;
    // ✗ Bad: creates new function for each instance
    this.greet = function() {
        return `Hi, I'm ${this.name}`;
    };
}

const i1 = new Inefficient('A');
const i2 = new Inefficient('B');
console.log(i1.greet === i2.greet);  // false (different functions - wasteful)
Common Pitfalls:
  • Forgetting 'new': Constructor called as regular function; this refers to global object (or undefined in strict mode)
  • Methods as instance properties: Creating functions inside constructor wastes memory; use prototype instead
  • Overwriting prototype: Breaks constructor reference; always restore .constructor property
  • Modifying built-in prototypes: Never modify Object.prototype, Array.prototype, etc. (causes conflicts)

8.3 Object.create and Prototypal Inheritance

Method Syntax Creates Use Case
Object.create(proto) Object.create(protoObj) New object with protoObj as prototype Prototypal inheritance, object delegation
Object.create(proto, descriptors) Object.create(proto, { props }) Object with prototype + property descriptors Fine-grained property control
Object.create(null) Object.create(null) Object with no prototype (null) Pure data storage, avoid prototype pollution
Inheritance Pattern Approach Pros Cons
Constructor Pattern function + new + prototype Familiar to class-based developers Verbose, easy to forget 'new'
Object.create Pattern Object.create(proto) Simple, pure prototypal, no 'new' Less familiar, no constructor function
ES6 Class class syntax Clean syntax, modern, standard Syntactic sugar over prototypes
Factory Function function returning object Flexible, private state via closure No shared prototype methods (unless manually added)

Example: Object.create for prototypal inheritance

// Parent object (prototype)
const animal = {
    type: 'animal',
    eat() {
        return `${this.name} is eating`;
    },
    sleep() {
        return `${this.name} is sleeping`;
    }
};

// Create child objects inheriting from parent
const dog = Object.create(animal);
dog.name = 'Rex';
dog.breed = 'Labrador';
dog.bark = function() {
    return `${this.name} says woof!`;
};

const cat = Object.create(animal);
cat.name = 'Whiskers';
cat.breed = 'Persian';
cat.meow = function() {
    return `${this.name} says meow!`;
};

// Access own properties
console.log(dog.name);  // 'Rex' (own)
console.log(dog.breed);  // 'Labrador' (own)

// Access inherited properties/methods
console.log(dog.type);  // 'animal' (inherited)
console.log(dog.eat());  // "Rex is eating" (inherited method)
console.log(dog.bark());  // "Rex says woof!" (own method)

// Verify prototype chain
console.log(Object.getPrototypeOf(dog) === animal);  // true
console.log(Object.getPrototypeOf(cat) === animal);  // true

// Shared prototype
console.log(dog.eat === cat.eat);  // true (same method from prototype)

// Modify prototype affects all children
animal.move = function() {
    return `${this.name} is moving`;
};
console.log(dog.move());  // "Rex is moving" (newly added)
console.log(cat.move());  // "Whiskers is moving"

Example: Object.create with property descriptors

const parent = {
    greet() {
        return `Hello from ${this.name}`;
    }
};

// Create object with prototype and property descriptors
const child = Object.create(parent, {
    name: {
        value: 'Child',
        writable: true,
        enumerable: true,
        configurable: true
    },
    age: {
        value: 10,
        writable: true,
        enumerable: true,
        configurable: true
    },
    // Accessor property
    description: {
        get() {
            return `${this.name}, age ${this.age}`;
        },
        enumerable: true,
        configurable: true
    }
});

console.log(child.name);  // 'Child'
console.log(child.age);  // 10
console.log(child.greet());  // "Hello from Child" (inherited)
console.log(child.description);  // "Child, age 10" (computed)

// Object with null prototype (no inheritance)
const dict = Object.create(null);
dict.key1 = 'value1';
dict.key2 = 'value2';

console.log(dict.toString);  // undefined (no Object.prototype)
console.log(dict.hasOwnProperty);  // undefined
console.log(Object.getPrototypeOf(dict));  // null

// Useful for pure data storage
console.log('toString' in dict);  // false (safe from prototype pollution)

Example: Implementing classical inheritance with Object.create

// Parent constructor
function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    return `${this.name} is eating`;
};

Animal.prototype.sleep = function() {
    return `${this.name} is sleeping`;
};

// Child constructor
function Dog(name, breed) {
    // Call parent constructor
    Animal.call(this, name);  // Inherit instance properties
    this.breed = breed;
}

// Set up prototype chain (critical step)
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;  // Restore constructor reference

// Add child-specific methods
Dog.prototype.bark = function() {
    return `${this.name} says woof!`;
};

// Create instance
const dog = new Dog('Rex', 'Labrador');

console.log(dog.name);  // 'Rex' (from Animal)
console.log(dog.breed);  // 'Labrador' (from Dog)
console.log(dog.eat());  // "Rex is eating" (inherited from Animal)
console.log(dog.bark());  // "Rex says woof!" (from Dog)

// Verify prototype chain
console.log(dog instanceof Dog);  // true
console.log(dog instanceof Animal);  // true
console.log(dog.constructor === Dog);  // true

// Chain: dog -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
console.log(Object.getPrototypeOf(dog) === Dog.prototype);  // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype);  // true

// ✗ Wrong way (don't do this)
// Dog.prototype = Animal.prototype;  // Shares prototype (modifications affect both)
// Dog.prototype = new Animal();  // Creates unnecessary instance

// ✓ Correct way (ES6+ alternative)
class AnimalClass {
    constructor(name) {
        this.name = name;
    }
    eat() {
        return `${this.name} is eating`;
    }
}

class DogClass extends AnimalClass {
    constructor(name, breed) {
        super(name);
        this.breed = breed;
    }
    bark() {
        return `${this.name} says woof!`;
    }
}

const modernDog = new DogClass('Max', 'Beagle');
console.log(modernDog.eat());  // "Max is eating"
console.log(modernDog.bark());  // "Max says woof!"
Best Practice: For inheritance, use ES6 classes (cleaner syntax). If using constructor functions, always use Object.create(Parent.prototype) to set up prototype chain, not new Parent() or direct assignment. Always restore constructor property. Use Object.create(null) for dictionary objects to avoid prototype pollution.

8.4 Prototype Methods and Property Override

Concept Description Behavior
Method Inheritance Child inherits methods from parent's prototype Shared method executed with child instance as this
Method Override Child defines method with same name as parent Child method shadows parent method in prototype chain
super Pattern Call parent method from overridden child method Use Parent.prototype.method.call(this) or ES6 super
Property Shadowing Own property hides prototype property with same name Lookup stops at first match (own properties checked first)
Dynamic Dispatch this binding determined at call time, not definition Enables polymorphism - same method name, different behavior
Pattern Implementation Use Case
Simple Override Define method on child with same name Completely replace parent behavior
Extend Parent Method Call parent method + add child logic Augment parent behavior
Delegate to Parent Forward some calls to parent method Conditional behavior based on state
Mixin Pattern Copy methods from multiple sources Multiple inheritance simulation

Example: Method inheritance and override

// Parent with methods
const animal = {
    speak() {
        return `${this.name} makes a sound`;
    },
    eat() {
        return `${this.name} is eating`;
    }
};

// Child inherits and overrides
const dog = Object.create(animal);
dog.name = 'Rex';

// Inherited method (no override)
console.log(dog.eat());  // "Rex is eating" (from animal)

// Override speak method
dog.speak = function() {
    return `${this.name} barks loudly`;
};

console.log(dog.speak());  // "Rex barks loudly" (overridden)

// Access parent method explicitly
console.log(animal.speak.call(dog));  // "Rex makes a sound" (parent method)

// Another child with different override
const cat = Object.create(animal);
cat.name = 'Whiskers';
cat.speak = function() {
    return `${this.name} meows softly`;
};

console.log(cat.speak());  // "Whiskers meows softly" (different override)

// Polymorphism in action
const animals = [dog, cat];
animals.forEach(a => console.log(a.speak()));
// "Rex barks loudly"
// "Whiskers meows softly"

Example: Calling parent methods (super pattern)

// Constructor-based inheritance
function Animal(name) {
    this.name = name;
}

Animal.prototype.describe = function() {
    return `This is ${this.name}`;
};

Animal.prototype.speak = function() {
    return `${this.name} makes a sound`;
};

function Dog(name, breed) {
    Animal.call(this, name);  // Call parent constructor
    this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// Override with extension - call parent method
Dog.prototype.describe = function() {
    // Call parent method
    const parentDesc = Animal.prototype.describe.call(this);
    // Add child-specific info
    return `${parentDesc}, a ${this.breed}`;
};

Dog.prototype.speak = function() {
    // Completely override (no parent call)
    return `${this.name} barks`;
};

const dog = new Dog('Rex', 'Labrador');
console.log(dog.describe());  // "This is Rex, a Labrador" (extended)
console.log(dog.speak());  // "Rex barks" (overridden)

// ES6 class with super keyword (cleaner)
class AnimalClass {
    constructor(name) {
        this.name = name;
    }
    
    describe() {
        return `This is ${this.name}`;
    }
    
    speak() {
        return `${this.name} makes a sound`;
    }
}

class DogClass extends AnimalClass {
    constructor(name, breed) {
        super(name);  // Call parent constructor
        this.breed = breed;
    }
    
    describe() {
        // Call parent method with super
        return `${super.describe()}, a ${this.breed}`;
    }
    
    speak() {
        // Override without calling parent
        return `${this.name} barks`;
    }
}

const modernDog = new DogClass('Max', 'Beagle');
console.log(modernDog.describe());  // "This is Max, a Beagle"
console.log(modernDog.speak());  // "Max barks"

Example: Property shadowing and resolution

const parent = {
    name: 'Parent',
    value: 10,
    getValue() {
        return this.value;
    }
};

const child = Object.create(parent);
console.log(child.name);  // 'Parent' (inherited)
console.log(child.value);  // 10 (inherited)

// Shadow property with own value
child.name = 'Child';
child.value = 20;

console.log(child.name);  // 'Child' (own property shadows parent)
console.log(child.value);  // 20 (own)
console.log(parent.name);  // 'Parent' (parent unchanged)

// Method uses 'this.value' - gets child's own value
console.log(child.getValue());  // 20 (inherited method, child's value)

// Delete own property - parent property revealed
delete child.name;
console.log(child.name);  // 'Parent' (parent property no longer shadowed)

// hasOwnProperty distinguishes own vs inherited
console.log(child.hasOwnProperty('value'));  // true (own)
console.log(child.hasOwnProperty('getValue'));  // false (inherited)

// Writing to inherited property creates own property
const obj = Object.create({ x: 1, y: 2 });
obj.x = 10;  // Creates own property
console.log(obj.x);  // 10 (own)
console.log(Object.getPrototypeOf(obj).x);  // 1 (prototype unchanged)

// Arrays inherit push, pop, etc. from Array.prototype
const arr = [1, 2, 3];
console.log(arr.hasOwnProperty('push'));  // false (inherited)
console.log(arr.hasOwnProperty('length'));  // true (own property)
console.log(arr.hasOwnProperty(0));  // true (own property)

Example: Mixin pattern for method sharing

// Mixin: reusable method collections
const canEat = {
    eat(food) {
        return `${this.name} is eating ${food}`;
    }
};

const canWalk = {
    walk() {
        return `${this.name} is walking`;
    }
};

const canSwim = {
    swim() {
        return `${this.name} is swimming`;
    }
};

// Mixin helper function
function mixin(target, ...sources) {
    Object.assign(target, ...sources);
    return target;
}

// Create object with multiple mixins
function Animal(name) {
    this.name = name;
}

// Add mixins to prototype
mixin(Animal.prototype, canEat, canWalk);

const dog = new Animal('Rex');
console.log(dog.eat('bone'));  // "Rex is eating bone"
console.log(dog.walk());  // "Rex is walking"
// console.log(dog.swim());  // Error: swim not available

// Different mix of capabilities
function Fish(name) {
    this.name = name;
}

mixin(Fish.prototype, canEat, canSwim);

const fish = new Fish('Nemo');
console.log(fish.eat('algae'));  // "Nemo is eating algae"
console.log(fish.swim());  // "Nemo is swimming"
// console.log(fish.walk());  // Error: walk not available

// ES6 class with mixins
const Flyable = {
    fly() {
        return `${this.name} is flying`;
    }
};

class Bird {
    constructor(name) {
        this.name = name;
    }
}

// Apply mixin to class
Object.assign(Bird.prototype, canEat, Flyable);

const bird = new Bird('Tweety');
console.log(bird.eat('seeds'));  // "Tweety is eating seeds"
console.log(bird.fly());  // "Tweety is flying"
Best Practice: Use method override for polymorphism. Call parent methods with super (ES6 classes) or Parent.prototype.method.call(this) (constructor functions). Use mixins for horizontal code reuse. Remember: methods use this binding, so inherited methods access instance properties correctly.

8.5 instanceof and Prototype Testing

Method/Operator Syntax Returns What It Checks
instanceof obj instanceof Constructor boolean Is Constructor.prototype in obj's prototype chain?
isPrototypeOf() proto.isPrototypeOf(obj) boolean Is proto in obj's prototype chain?
Object.getPrototypeOf() Object.getPrototypeOf(obj) object or null Returns obj's direct prototype
hasOwnProperty() obj.hasOwnProperty(prop) boolean Is prop an own property (not inherited)?
Object.hasOwn() ES2022 Object.hasOwn(obj, prop) boolean Safer alternative to hasOwnProperty
in operator 'prop' in obj boolean Is prop in obj or its prototype chain?
Test Own Property Inherited Property Nonexistent
obj.prop value value undefined
'prop' in obj true true false
obj.hasOwnProperty('prop') true false false
Object.hasOwn(obj, 'prop') true false false

Example: instanceof operator

// Constructor-based type checking
function Animal(name) {
    this.name = name;
}

function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

const dog = new Dog('Rex', 'Labrador');

// instanceof checks prototype chain
console.log(dog instanceof Dog);  // true
console.log(dog instanceof Animal);  // true
console.log(dog instanceof Object);  // true (all objects)

// Arrays
const arr = [1, 2, 3];
console.log(arr instanceof Array);  // true
console.log(arr instanceof Object);  // true

// Primitives
console.log('hello' instanceof String);  // false (primitive, not object)
console.log(new String('hello') instanceof String);  // true (wrapper object)
console.log(42 instanceof Number);  // false

// instanceof can be fooled
function Fake() {}
Fake.prototype = dog;  // Set prototype to instance
const fake = new Fake();
console.log(fake instanceof Dog);  // true! (dog in prototype chain)

// Object.create
const parent = { type: 'parent' };
const child = Object.create(parent);
// No constructor function, instanceof doesn't work well
// console.log(child instanceof ???);  // What to check?

Example: isPrototypeOf() method

const animal = {
    eats: true
};

const rabbit = Object.create(animal);
rabbit.jumps = true;

const longEaredRabbit = Object.create(rabbit);
longEaredRabbit.earLength = 10;

// Check prototype relationships
console.log(animal.isPrototypeOf(rabbit));  // true
console.log(animal.isPrototypeOf(longEaredRabbit));  // true (anywhere in chain)
console.log(rabbit.isPrototypeOf(longEaredRabbit));  // true

console.log(rabbit.isPrototypeOf(animal));  // false (wrong direction)

// Works with any object, not just constructors
console.log(Object.prototype.isPrototypeOf(rabbit));  // true (all objects)
console.log(Object.prototype.isPrototypeOf({}));  // true

// Compare with instanceof (requires constructor)
function Animal() {}
const dog = new Animal();
console.log(dog instanceof Animal);  // true
console.log(Animal.prototype.isPrototypeOf(dog));  // true (equivalent)

// null prototype
const noProto = Object.create(null);
console.log(Object.prototype.isPrototypeOf(noProto));  // false

Example: Property ownership testing

const parent = {
    inherited: 'from parent',
    sharedMethod() {
        return 'shared';
    }
};

const child = Object.create(parent);
child.own = 'own property';

// 'in' operator - checks own and inherited
console.log('own' in child);  // true (own)
console.log('inherited' in child);  // true (inherited)
console.log('sharedMethod' in child);  // true (inherited)
console.log('nonexistent' in child);  // false

// hasOwnProperty - checks only own properties
console.log(child.hasOwnProperty('own'));  // true
console.log(child.hasOwnProperty('inherited'));  // false (inherited)
console.log(child.hasOwnProperty('sharedMethod'));  // false

// Object.hasOwn (ES2022) - safer alternative
console.log(Object.hasOwn(child, 'own'));  // true
console.log(Object.hasOwn(child, 'inherited'));  // false

// Why Object.hasOwn is safer
const obj = Object.create(null);  // No prototype
obj.prop = 'value';
// obj.hasOwnProperty('prop');  // Error: hasOwnProperty not available
console.log(Object.hasOwn(obj, 'prop'));  // true (works!)

// Distinguish own vs inherited in iteration
for (let key in child) {
    if (child.hasOwnProperty(key)) {
        console.log(`Own: ${key}`);
    } else {
        console.log(`Inherited: ${key}`);
    }
}
// Own: own
// Inherited: inherited
// Inherited: sharedMethod

// Object.keys() only returns own enumerable properties
console.log(Object.keys(child));  // ['own']

// Get all properties including inherited (manual)
function getAllProperties(obj) {
    const props = new Set();
    let current = obj;
    while (current) {
        Object.getOwnPropertyNames(current).forEach(p => props.add(p));
        current = Object.getPrototypeOf(current);
    }
    return Array.from(props);
}

console.log(getAllProperties(child));
// Includes: own, inherited, sharedMethod, toString, hasOwnProperty, etc.

Example: Type checking best practices

// Checking built-in types
function checkType(value) {
    // Arrays - use Array.isArray (most reliable)
    if (Array.isArray(value)) {
        return 'array';
    }
    
    // null (special case - typeof null === 'object')
    if (value === null) {
        return 'null';
    }
    
    // Primitives - use typeof
    if (typeof value !== 'object') {
        return typeof value;  // 'string', 'number', 'boolean', etc.
    }
    
    // Objects - check constructor
    if (value instanceof Date) return 'date';
    if (value instanceof RegExp) return 'regexp';
    if (value instanceof Map) return 'map';
    if (value instanceof Set) return 'set';
    
    return 'object';
}

console.log(checkType([1, 2, 3]));  // 'array'
console.log(checkType('hello'));  // 'string'
console.log(checkType(42));  // 'number'
console.log(checkType(null));  // 'null'
console.log(checkType(new Date()));  // 'date'
console.log(checkType({ x: 1 }));  // 'object'

// Duck typing - check for expected methods
function isIterable(obj) {
    return obj != null && typeof obj[Symbol.iterator] === 'function';
}

console.log(isIterable([1, 2, 3]));  // true
console.log(isIterable('hello'));  // true
console.log(isIterable({ x: 1 }));  // false
console.log(isIterable(new Map()));  // true

// Custom type guard (TypeScript-like pattern in JS)
function isDog(animal) {
    return animal 
        && typeof animal.bark === 'function'
        && typeof animal.breed === 'string';
}

const dog = { name: 'Rex', breed: 'Lab', bark() {} };
const cat = { name: 'Fluffy', meow() {} };

console.log(isDog(dog));  // true
console.log(isDog(cat));  // false

// Symbol.toStringTag for custom types
class CustomType {
    get [Symbol.toStringTag]() {
        return 'CustomType';
    }
}

const custom = new CustomType();
console.log(Object.prototype.toString.call(custom));  // '[object CustomType]'
console.log(Object.prototype.toString.call([]));  // '[object Array]'
console.log(Object.prototype.toString.call(new Date()));  // '[object Date]'
Common Pitfalls:
  • instanceof with primitives: Returns false for primitives like 'string' even though they can use String methods
  • instanceof across frames: Different window/frame has different built-in constructors; use Array.isArray() for arrays
  • hasOwnProperty safety: Objects with null prototype don't have hasOwnProperty; use Object.hasOwn() instead
  • typeof null: Returns 'object' (JavaScript quirk); check === null explicitly

8.6 Object Delegation Patterns

Pattern Description Key Concept
Behavior Delegation (OLOO) Objects delegate to other objects (not classes) Objects Linked to Other Objects - pure prototypal
Classical Inheritance Parent-child hierarchy with constructors Constructor functions + prototype chain
Concatenative Inheritance Mix properties from multiple sources Object.assign() / spread for composition
Functional Inheritance Factory functions with closures for privacy Functions return objects with private state
Aspect Classical (Constructor) Delegation (OLOO)
Syntax new Constructor() Object.create(proto)
Focus Parent-child classes Peer objects delegating to each other
Initialization Constructor function Init method on prototype
Complexity More verbose, mimics classes Simpler, embraces prototypes
Mental Model "This IS-A that" (inheritance) "This HAS-A reference to that" (delegation)

Example: OLOO pattern (Objects Linked to Other Objects)

// Behavior delegation style (OLOO)
const Vehicle = {
    init(make, model) {
        this.make = make;
        this.model = model;
        return this;
    },
    
    describe() {
        return `${this.make} ${this.model}`;
    }
};

const Car = Object.create(Vehicle);

Car.init = function(make, model, doors) {
    // Delegate to Vehicle.init
    Vehicle.init.call(this, make, model);
    this.doors = doors;
    return this;
};

Car.drive = function() {
    return `Driving ${this.describe()}`;
};

// Create instances
const myCar = Object.create(Car).init('Toyota', 'Camry', 4);
const yourCar = Object.create(Car).init('Honda', 'Civic', 4);

console.log(myCar.describe());  // "Toyota Camry"
console.log(myCar.drive());  // "Driving Toyota Camry"
console.log(yourCar.drive());  // "Driving Honda Civic"

// No constructors, no 'new', just objects delegating
console.log(Object.getPrototypeOf(myCar) === Car);  // true
console.log(Object.getPrototypeOf(Car) === Vehicle);  // true

// Compare with constructor pattern
function VehicleConstructor(make, model) {
    this.make = make;
    this.model = model;
}

VehicleConstructor.prototype.describe = function() {
    return `${this.make} ${this.model}`;
};

function CarConstructor(make, model, doors) {
    VehicleConstructor.call(this, make, model);
    this.doors = doors;
}

CarConstructor.prototype = Object.create(VehicleConstructor.prototype);
CarConstructor.prototype.constructor = CarConstructor;

CarConstructor.prototype.drive = function() {
    return `Driving ${this.describe()}`;
};

const constructorCar = new CarConstructor('Ford', 'Focus', 4);
console.log(constructorCar.drive());  // Same result, more complex setup

Example: Concatenative inheritance (composition)

// Reusable behavior objects
const canEat = {
    eat(food) {
        return `${this.name} eats ${food}`;
    }
};

const canWalk = {
    walk() {
        return `${this.name} walks`;
    }
};

const canSwim = {
    swim() {
        return `${this.name} swims`;
    }
};

// Compose objects by combining behaviors
function createDog(name) {
    return {
        name,
        ...canEat,
        ...canWalk,
        bark() {
            return `${this.name} barks`;
        }
    };
}

function createFish(name) {
    return {
        name,
        ...canEat,
        ...canSwim
    };
}

function createDuck(name) {
    return {
        name,
        ...canEat,
        ...canWalk,
        ...canSwim,
        quack() {
            return `${this.name} quacks`;
        }
    };
}

const dog = createDog('Rex');
console.log(dog.eat('bone'));  // "Rex eats bone"
console.log(dog.walk());  // "Rex walks"
console.log(dog.bark());  // "Rex barks"
// console.log(dog.swim());  // undefined (no swim capability)

const duck = createDuck('Donald');
console.log(duck.eat('bread'));  // "Donald eats bread"
console.log(duck.walk());  // "Donald walks"
console.log(duck.swim());  // "Donald swims"
console.log(duck.quack());  // "Donald quacks"

// Advanced: functional mixin
const withLogging = (obj) => {
    const originalMethods = {};
    
    Object.keys(obj).forEach(key => {
        if (typeof obj[key] === 'function') {
            originalMethods[key] = obj[key];
            obj[key] = function(...args) {
                console.log(`Calling ${key} with`, args);
                return originalMethods[key].apply(this, args);
            };
        }
    });
    
    return obj;
};

const loggedDog = withLogging(createDog('Max'));
loggedDog.eat('treat');  // Logs: "Calling eat with ['treat']"
                         // Returns: "Max eats treat"

Example: Functional inheritance with closures

// Factory function with private state
function createBankAccount(initialBalance) {
    // Private variables (closure)
    let balance = initialBalance;
    let transactionHistory = [];
    
    // Private function
    function recordTransaction(type, amount) {
        transactionHistory.push({
            type,
            amount,
            timestamp: Date.now(),
            balance
        });
    }
    
    // Public interface
    return {
        deposit(amount) {
            if (amount > 0) {
                balance += amount;
                recordTransaction('deposit', amount);
            }
            return balance;
        },
        
        withdraw(amount) {
            if (amount > 0 && amount <= balance) {
                balance -= amount;
                recordTransaction('withdraw', amount);
            }
            return balance;
        },
        
        getBalance() {
            return balance;
        },
        
        getHistory() {
            // Return copy to prevent external modification
            return [...transactionHistory];
        }
    };
}

const account = createBankAccount(1000);
account.deposit(500);   // 1500
account.withdraw(200);  // 1300

console.log(account.getBalance());  // 1300
console.log(account.balance);  // undefined (private!)
console.log(account.getHistory().length);  // 2 transactions

// Inheritance with functional pattern
function createSavingsAccount(initialBalance, interestRate) {
    // Get base account
    const account = createBankAccount(initialBalance);
    
    // Add new functionality
    account.applyInterest = function() {
        const interest = account.getBalance() * interestRate;
        account.deposit(interest);
        return account.getBalance();
    };
    
    return account;
}

const savings = createSavingsAccount(1000, 0.05);
savings.deposit(500);  // 1500
savings.applyInterest();  // 1575 (5% interest)
console.log(savings.getBalance());  // 1575

Example: Practical delegation patterns

// Plugin system using delegation
const BaseApp = {
    init(name) {
        this.name = name;
        this.plugins = [];
        return this;
    },
    
    use(plugin) {
        this.plugins.push(plugin);
        if (plugin.install) {
            plugin.install(this);
        }
        return this;
    },
    
    run() {
        console.log(`${this.name} is running`);
        this.plugins.forEach(p => p.onRun && p.onRun(this));
    }
};

// Specific app type
const WebApp = Object.create(BaseApp);
WebApp.init = function(name, port) {
    BaseApp.init.call(this, name);
    this.port = port;
    return this;
};

WebApp.start = function() {
    console.log(`Server started on port ${this.port}`);
    this.run();
};

// Plugins
const LoggerPlugin = {
    install(app) {
        console.log(`Logger installed in ${app.name}`);
    },
    onRun(app) {
        console.log(`[LOG] ${app.name} executed`);
    }
};

const CachePlugin = {
    install(app) {
        app.cache = new Map();
        console.log(`Cache installed in ${app.name}`);
    },
    onRun(app) {
        console.log(`[CACHE] Size: ${app.cache.size}`);
    }
};

// Create app with plugins
const app = Object.create(WebApp)
    .init('MyApp', 3000)
    .use(LoggerPlugin)
    .use(CachePlugin);

app.start();
// Logger installed in MyApp
// Cache installed in MyApp
// Server started on port 3000
// MyApp is running
// [LOG] MyApp executed
// [CACHE] Size: 0

Section 8 Summary

  • Prototype chain: Property lookup traverses [[Prototype]] links from object to Object.prototype to null; use getPrototypeOf(), not __proto__
  • Constructor pattern: Function with new creates instances; 4 steps: create, link prototype, execute, return; methods on prototype for efficiency
  • Object.create: Direct prototypal inheritance without constructors; clean delegation; use for pure prototypal patterns
  • Method override: Child methods shadow parent; call parent with super or Parent.prototype.method.call(this); enables polymorphism
  • Type testing: instanceof checks constructor chain; isPrototypeOf() checks object chain; Object.hasOwn() for property ownership (safer than hasOwnProperty)
  • Delegation patterns: OLOO for pure delegation; concatenative for composition; functional for privacy; choose based on needs

9. Arrays and Array Methods Reference

9.1 Array Creation and Initialization Patterns

Pattern Syntax Description Example
Array Literal [el1, el2, ...] Most common creation method; supports mixed types and sparse arrays const arr = [1, 2, 3];
Array Constructor new Array(length) Creates array with specified length (holes); avoid for single numeric argument new Array(3) // [empty × 3]
Array.of() Array.of(...items) Creates array from arguments; fixes Array() single-number issue Array.of(3) // [3]
Array.from() Array.from(iterable, mapFn) Creates array from iterable/array-like; optional mapper function Array.from('abc') // ['a','b','c']
Spread Syntax [...iterable] Shallow copy or convert iterable to array; modern and concise [...set] // array from Set
Array.fill() Array(n).fill(value) Create pre-filled array; value is shared reference for objects Array(3).fill(0) // [0,0,0]
Array.keys() Array.from(Array(n).keys()) Generate sequential number array from 0 to n-1 [...Array(3).keys()] // [0,1,2]

Example: Various array creation patterns

// Literal - most common
const fruits = ['apple', 'banana', 'orange'];

// Array.of - safe with single number
const single = Array.of(5); // [5] not [empty × 5]

// Array.from with mapper
const squares = Array.from({length: 5}, (_, i) => i ** 2);
// [0, 1, 4, 9, 16]

// From string
const chars = Array.from('hello'); // ['h','e','l','l','o']

// From Set
const unique = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]

// Pre-fill with value
const zeros = Array(10).fill(0);

// Sequential numbers
const range = [...Array(5).keys()]; // [0, 1, 2, 3, 4]
const rangeFrom1 = Array.from({length: 5}, (_, i) => i + 1);
// [1, 2, 3, 4, 5]

// 2D array (careful with fill!)
const matrix = Array(3).fill(null).map(() => Array(3).fill(0));
Note: Avoid new Array(n).fill([]) for 2D arrays as all rows share same reference. Use .map(() => []) instead.

9.2 Array Mutating Methods (push, pop, splice, sort)

Method Syntax Description Returns
push() arr.push(el1, ...) Adds elements to end; modifies original array; O(1) time New length
pop() arr.pop() Removes and returns last element; O(1) time Removed element
unshift() arr.unshift(el1, ...) Adds elements to beginning; shifts indices; O(n) time New length
shift() arr.shift() Removes and returns first element; shifts indices; O(n) time Removed element
splice() arr.splice(start, deleteCount, ...items) Add/remove elements at any position; powerful but complex Array of deleted elements
sort() arr.sort(compareFn) Sorts in-place; default: string comparison; provide compareFn for numbers Sorted array (same reference)
reverse() arr.reverse() Reverses array in-place; O(n) time Reversed array (same reference)
fill() arr.fill(value, start, end) Fills with static value; optional range; O(n) time Modified array
copyWithin() arr.copyWithin(target, start, end) Shallow copies part of array to another location in same array Modified array

Example: Mutating array methods

const arr = [1, 2, 3];

// Stack operations (end)
arr.push(4, 5); // [1,2,3,4,5] returns 5
arr.pop(); // [1,2,3,4] returns 5

// Queue operations (beginning)
arr.unshift(0); // [0,1,2,3,4] returns 5
arr.shift(); // [1,2,3,4] returns 0

// splice - remove
const items = [1, 2, 3, 4, 5];
items.splice(2, 2); // [1,2,5] returns [3,4]

// splice - insert
items.splice(1, 0, 'a', 'b'); // [1,'a','b',2,5]

// splice - replace
items.splice(0, 2, 'x'); // ['x','b',2,5]

// sort - numbers (must provide compareFn!)
const nums = [10, 2, 5, 1];
nums.sort(); // [1, 10, 2, 5] ❌ string sort!
nums.sort((a, b) => a - b); // [1, 2, 5, 10] ✓

// sort - objects
const users = [{age: 30}, {age: 20}, {age: 25}];
users.sort((a, b) => a.age - b.age);

// reverse
[1, 2, 3].reverse(); // [3, 2, 1]

// fill
Array(5).fill(0); // [0,0,0,0,0]
[1,2,3,4,5].fill(0, 2, 4); // [1,2,0,0,5]

// copyWithin
[1,2,3,4,5].copyWithin(0, 3); // [4,5,3,4,5]
Warning: Default sort() converts to strings! Always provide compare function for numbers: arr.sort((a,b) => a - b)

9.3 Array Non-mutating Methods (slice, concat, join)

Method Syntax Description Returns
slice() arr.slice(start, end) Shallow copy of portion; negative indices from end; doesn't modify original New array
concat() arr.concat(arr2, ...) Merges arrays/values; shallow copy; doesn't modify originals New array
join() arr.join(separator) Converts to string with separator; default comma; doesn't modify String
toString() arr.toString() Converts to comma-separated string; same as join(',') String
toLocaleString() arr.toLocaleString() Locale-aware string conversion; respects number/date formatting String
flat() ES2019 arr.flat(depth) Flattens nested arrays; default depth 1; Infinity for full flatten New flattened array
flatMap() ES2019 arr.flatMap(fn) Maps then flattens (depth 1); more efficient than map().flat() New array
with() ES2023 arr.with(index, value) Returns new array with element replaced; immutable alternative to arr[i]=val New array
toReversed() ES2023 arr.toReversed() Non-mutating reverse; returns new reversed array New reversed array
toSorted() ES2023 arr.toSorted(compareFn) Non-mutating sort; returns new sorted array New sorted array
toSpliced() ES2023 arr.toSpliced(start, deleteCount, ...items) Non-mutating splice; returns new array with changes New array

Example: Non-mutating array methods

const arr = [1, 2, 3, 4, 5];

// slice - extract portion
arr.slice(1, 3); // [2, 3]
arr.slice(-2); // [4, 5] (last 2)
arr.slice(); // [1,2,3,4,5] (shallow copy)

// concat - merge
[1, 2].concat([3, 4]); // [1, 2, 3, 4]
[1].concat(2, [3, 4], 5); // [1, 2, 3, 4, 5]

// join - to string
['a', 'b', 'c'].join(); // "a,b,c"
['a', 'b', 'c'].join(''); // "abc"
['a', 'b', 'c'].join(' - '); // "a - b - c"

// flat - nested arrays
[1, [2, 3], [4, [5]]].flat(); // [1, 2, 3, 4, [5]]
[1, [2, [3, [4]]]].flat(2); // [1, 2, 3, [4]]
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]

// flatMap - map then flatten
['hello', 'world'].flatMap(s => s.split(''));
// ['h','e','l','l','o','w','o','r','l','d']

[1, 2, 3].flatMap(x => [x, x * 2]);
// [1, 2, 2, 4, 3, 6]

// ES2023 immutable methods
const original = [3, 1, 2];

original.toSorted(); // [1, 2, 3]
console.log(original); // [3, 1, 2] unchanged

original.toReversed(); // [2, 1, 3]

original.with(1, 99); // [3, 99, 2]

original.toSpliced(1, 1, 'a', 'b'); // [3, 'a', 'b', 2]
Note: Use spread [...arr] or slice() for shallow copy. ES2023 adds toSorted(), toReversed(), toSpliced(), with() for immutable operations.

9.4 Array Iteration Methods (forEach, map, filter, reduce)

Method Syntax Description Returns
forEach() arr.forEach((el, i, arr) => {}) Execute function for each element; no return value; can't break/continue undefined
map() arr.map((el, i, arr) => {}) Transform each element; returns new array with results New array
filter() arr.filter((el, i, arr) => {}) Keep elements that pass test; returns new array with matches New array
reduce() arr.reduce((acc, el, i, arr) => {}, init) Reduce to single value; accumulator pattern; init value recommended Any value
reduceRight() arr.reduceRight((acc, el) => {}, init) Like reduce but right-to-left; useful for right-associative operations Any value
flatMap() arr.flatMap((el, i, arr) => {}) Map then flatten depth 1; efficient for map + flat combination New flattened array
entries() arr.entries() Returns iterator of [index, value] pairs Iterator
keys() arr.keys() Returns iterator of indices Iterator
values() arr.values() Returns iterator of values Iterator

Example: Array iteration methods

const nums = [1, 2, 3, 4, 5];

// forEach - side effects only
nums.forEach((n, i) => console.log(`${i}: ${n}`));

// map - transform
const doubled = nums.map(n => n * 2); // [2,4,6,8,10]
const objects = nums.map(n => ({value: n}));

// filter - select
const evens = nums.filter(n => n % 2 === 0); // [2,4]
const large = nums.filter(n => n > 3); // [4,5]

// reduce - accumulate
const sum = nums.reduce((acc, n) => acc + n, 0); // 15
const product = nums.reduce((acc, n) => acc * n, 1); // 120

// reduce - group by
const items = ['a', 'b', 'c', 'a', 'b'];
const counts = items.reduce((acc, item) => {
    acc[item] = (acc[item] || 0) + 1;
    return acc;
}, {}); // {a: 2, b: 2, c: 1}

// reduce - flatten
[[1, 2], [3, 4], [5]].reduce((acc, arr) => acc.concat(arr), []);
// [1, 2, 3, 4, 5] (use flat() instead!)

// Method chaining
nums
    .filter(n => n % 2 === 0)
    .map(n => n * 2)
    .reduce((acc, n) => acc + n, 0); // 12

// flatMap - map and flatten
['hello', 'world'].flatMap(w => w.split(''));
// ['h','e','l','l','o','w','o','r','l','d']

// Iterators
for (const [i, val] of nums.entries()) {
    console.log(i, val); // 0 1, 1 2, ...
}

for (const i of nums.keys()) {
    console.log(i); // 0, 1, 2, 3, 4
}
Note: Always provide initial value to reduce() to avoid errors on empty arrays. Use for...of if you need to break/continue; forEach() can't be stopped early.

9.5 Array Search Methods (find, includes, indexOf)

Method Syntax Description Returns
find() arr.find((el, i, arr) => {}) Returns first element that passes test; stops on first match Element or undefined
findLast() ES2023 arr.findLast((el, i, arr) => {}) Like find() but searches right-to-left; returns last match Element or undefined
findIndex() arr.findIndex((el, i, arr) => {}) Returns index of first element that passes test Index or -1
findLastIndex() ES2023 arr.findLastIndex((el, i, arr) => {}) Like findIndex() but searches right-to-left Index or -1
includes() arr.includes(value, fromIndex) Checks if array contains value; uses SameValueZero (handles NaN) Boolean
indexOf() arr.indexOf(value, fromIndex) Returns first index of value; uses strict equality (===); -1 if not found Index or -1
lastIndexOf() arr.lastIndexOf(value, fromIndex) Returns last index of value; searches backwards Index or -1
at() ES2022 arr.at(index) Returns element at index; negative indices from end; undefined if out of bounds Element or undefined

Example: Array search methods

const users = [
    {id: 1, name: 'Alice', age: 25},
    {id: 2, name: 'Bob', age: 30},
    {id: 3, name: 'Charlie', age: 25}
];

// find - first match
const bob = users.find(u => u.name === 'Bob');
// {id: 2, name: 'Bob', age: 30}

const young = users.find(u => u.age < 30);
// {id: 1, name: 'Alice', age: 25}

// findLast - last match
const lastYoung = users.findLast(u => u.age === 25);
// {id: 3, name: 'Charlie', age: 25}

// findIndex
const bobIndex = users.findIndex(u => u.name === 'Bob'); // 1
const notFound = users.findIndex(u => u.name === 'Dave'); // -1

// includes - value check
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, NaN].includes(NaN); // true ✓ (indexOf fails here)

// indexOf - strict equality
['a', 'b', 'c'].indexOf('b'); // 1
['a', 'b', 'c'].indexOf('d'); // -1
[1, 2, 3, 2].indexOf(2); // 1 (first occurrence)

// lastIndexOf - search backwards
[1, 2, 3, 2, 1].lastIndexOf(2); // 3
[1, 2, 3].lastIndexOf(4); // -1

// at - negative indexing
const arr = ['a', 'b', 'c', 'd'];
arr.at(0); // 'a'
arr.at(-1); // 'd' (last)
arr.at(-2); // 'c' (second to last)
arr.at(10); // undefined

// Comparison: includes vs indexOf
[1, 2, NaN].includes(NaN); // true
[1, 2, NaN].indexOf(NaN); // -1 ❌

// Check existence
if (users.find(u => u.id === 2)) {
    // Found
}

// Get index and value
const index = users.findIndex(u => u.age === 25);
if (index !== -1) {
    const user = users[index];
}
Note: Use includes() for existence check (handles NaN); find() for objects with conditions; at() for negative indexing (cleaner than arr[arr.length - 1]).

9.6 Array Testing Methods (every, some)

Method Syntax Description Returns
every() arr.every((el, i, arr) => {}) Tests if ALL elements pass test; short-circuits on first false Boolean
some() arr.some((el, i, arr) => {}) Tests if ANY element passes test; short-circuits on first true Boolean

Example: Array testing methods

const nums = [2, 4, 6, 8, 10];
const mixed = [1, 2, 3, 4, 5];

// every - all must pass
nums.every(n => n % 2 === 0); // true (all even)
mixed.every(n => n % 2 === 0); // false

nums.every(n => n > 0); // true (all positive)
nums.every(n => n > 5); // false

// some - at least one must pass
mixed.some(n => n % 2 === 0); // true (has even)
mixed.some(n => n > 10); // false

[1, 3, 5].some(n => n % 2 === 0); // false (no even)

// Validation use cases
const users = [
    {name: 'Alice', age: 25},
    {name: 'Bob', age: 30},
    {name: 'Charlie', age: 35}
];

// All adults?
const allAdults = users.every(u => u.age >= 18); // true

// Any seniors?
const hasSeniors = users.some(u => u.age >= 65); // false

// All have names?
const allNamed = users.every(u => u.name && u.name.length > 0);

// Any in 30s?
const hasThirties = users.some(u => u.age >= 30 && u.age < 40);

// Empty array edge cases
[].every(n => n > 100); // true (vacuous truth)
[].some(n => n > 100); // false

// Short-circuit behavior
const expensive = [1, 2, 3, 4, 5];
expensive.every(n => {
    console.log('checking', n);
    return n < 3;
}); // logs: checking 1, checking 2, checking 3
    // stops at 3 (first failure)

expensive.some(n => {
    console.log('checking', n);
    return n > 3;
}); // logs: checking 1, 2, 3, 4
    // stops at 4 (first success)

// Combine with other methods
const valid = users
    .filter(u => u.age > 25)
    .every(u => u.name.length > 0);
Note: every() returns true for empty arrays (vacuous truth); some() returns false. Both short-circuit, stopping iteration early for performance.

9.7 Array Destructuring and Spread Operations

Pattern Syntax Description Example
Basic Destructuring const [a, b] = arr Extract elements into variables by position const [x, y] = [1, 2]
Skip Elements const [a, , c] = arr Omit elements with empty comma slots const [, , z] = [1, 2, 3]
Rest Pattern const [a, ...rest] = arr Collect remaining elements; must be last const [h, ...t] = [1,2,3]
Default Values const [a = 0] = arr Provide fallback if undefined; not for null const [x = 10] = []
Nested Destructuring const [[a], [b]] = arr Destructure nested arrays recursively const [[x]] = [[1]]
Swap Variables [a, b] = [b, a] Elegant variable swap without temp [x, y] = [y, x]
Spread in Array [...arr] Shallow copy or expand iterable [...arr1, ...arr2]
Spread in Call fn(...arr) Pass array elements as separate arguments Math.max(...nums)
Rest in Function fn(...args) Collect function arguments into array function sum(...n)

Example: Destructuring and spread patterns

// Basic destructuring
const [a, b, c] = [1, 2, 3];
// a=1, b=2, c=3

// Skip elements
const [first, , third] = [1, 2, 3];
// first=1, third=3

// Rest pattern
const [head, ...tail] = [1, 2, 3, 4];
// head=1, tail=[2,3,4]

const [x, y, ...rest] = [1];
// x=1, y=undefined, rest=[]

// Default values
const [p = 0, q = 0] = [1];
// p=1, q=0

const [m = 'default'] = [undefined];
// m='default' (undefined triggers default)

const [n = 'default'] = [null];
// n=null (null doesn't trigger default!)

// Nested destructuring
const [[a1, a2], [b1, b2]] = [[1, 2], [3, 4]];
// a1=1, a2=2, b1=3, b2=4

// Swap variables
let i = 1, j = 2;
[i, j] = [j, i]; // i=2, j=1

// Function parameters
function sum([a, b]) {
    return a + b;
}
sum([3, 4]); // 7

function coords({x, y, z = 0}) {
    return [x, y, z];
}

// Spread - shallow copy
const original = [1, 2, 3];
const copy = [...original]; // [1, 2, 3]

// Spread - merge arrays
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1,2,3,4]

const combined = [0, ...arr1, 2.5, ...arr2, 5];
// [0, 1, 2, 2.5, 3, 4, 5]

// Spread - function arguments
const nums = [5, 3, 8, 1];
Math.max(...nums); // 8 (instead of apply)

// Rest parameters
function sum(...numbers) {
    return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10

function first(a, b, ...rest) {
    console.log(rest); // array of remaining args
}

// Convert iterable
const set = new Set([1, 2, 3]);
const arr = [...set]; // [1, 2, 3]

const str = 'hello';
const chars = [...str]; // ['h','e','l','l','o']

// Shallow copy warning
const nested = [[1, 2], [3, 4]];
const shallowCopy = [...nested];
shallowCopy[0][0] = 99;
console.log(nested[0][0]); // 99 ❌ (shared reference!)
Warning: Spread creates shallow copy. Nested objects/arrays share references. Use structuredClone() for deep copy.

9.8 Typed Arrays and Buffer Management

Type Bytes/Element Range Description
Int8Array 1 -128 to 127 Signed 8-bit integer
Uint8Array 1 0 to 255 Unsigned 8-bit integer
Uint8ClampedArray 1 0 to 255 (clamped) Clamped unsigned 8-bit (for Canvas)
Int16Array 2 -32,768 to 32,767 Signed 16-bit integer
Uint16Array 2 0 to 65,535 Unsigned 16-bit integer
Int32Array 4 -2³¹ to 2³¹-1 Signed 32-bit integer
Uint32Array 4 0 to 2³²-1 Unsigned 32-bit integer
Float32Array 4 ±1.2×10⁻³⁸ to ±3.4×10³⁸ 32-bit floating point
Float64Array 8 ±5.0×10⁻³²⁴ to ±1.8×10³⁰⁸ 64-bit floating point
BigInt64Array 8 -2⁶³ to 2⁶³-1 Signed 64-bit BigInt
BigUint64Array 8 0 to 2⁶⁴-1 Unsigned 64-bit BigInt
Buffer API Syntax Description Use Case
ArrayBuffer new ArrayBuffer(byteLength) Fixed-length raw binary data buffer; not directly accessible Binary data storage
DataView new DataView(buffer, offset, length) Low-level interface to read/write multiple types in buffer Mixed-type binary data
SharedArrayBuffer new SharedArrayBuffer(length) Shared memory buffer for Web Workers; requires COOP/COEP headers Multi-threaded data sharing

Example: Typed arrays and buffer operations

// Create typed arrays
const uint8 = new Uint8Array(4); // [0, 0, 0, 0]
const int32 = new Int32Array([1, 2, 3]); // [1, 2, 3]

// From ArrayBuffer
const buffer = new ArrayBuffer(16); // 16 bytes
const view32 = new Int32Array(buffer); // 4 elements (16/4)
const view8 = new Uint8Array(buffer); // 16 elements

// Shared buffer - multiple views
view32[0] = 0x12345678;
console.log(view8[0]); // 0x78 (little-endian)

// Array-like operations (limited)
const arr = new Uint8Array([1, 2, 3, 4]);
arr.length; // 4
arr[0]; // 1
arr.slice(1, 3); // Uint8Array[2, 3]
arr.map(x => x * 2); // Uint8Array[2, 4, 6, 8]

// No push/pop/splice - fixed length!
// arr.push(5); ❌ TypeError

// Overflow behavior
const u8 = new Uint8Array(1);
u8[0] = 256; // wraps to 0
u8[0] = -1; // wraps to 255

const clamped = new Uint8ClampedArray(1);
clamped[0] = 256; // clamps to 255
clamped[0] = -1; // clamps to 0

// DataView - mixed types
const buf = new ArrayBuffer(8);
const view = new DataView(buf);

view.setInt8(0, 127); // byte 0: 127
view.setInt16(1, 1000); // bytes 1-2: 1000
view.setFloat32(4, 3.14); // bytes 4-7: 3.14

view.getInt8(0); // 127
view.getInt16(1); // 1000
view.getFloat32(4); // 3.14...

// Endianness control
view.setInt32(0, 0x12345678, true); // little-endian
view.setInt32(0, 0x12345678, false); // big-endian

// Convert regular array
const regular = [1, 2, 3, 4, 5];
const typed = new Int32Array(regular);

// Convert typed to regular
const back = Array.from(typed);
// or [...typed]

// Use cases
// 1. Binary file I/O
fetch('data.bin')
    .then(r => r.arrayBuffer())
    .then(buf => new Uint8Array(buf));

// 2. WebGL graphics
const vertices = new Float32Array([
    -1.0, -1.0,
     1.0, -1.0,
     0.0,  1.0
]);

// 3. Image processing
const imageData = ctx.getImageData(0, 0, w, h);
const pixels = imageData.data; // Uint8ClampedArray
// RGBA: [r, g, b, a, r, g, b, a, ...]

// 4. Network protocols
const header = new Uint8Array([0x01, 0x02, 0x03, 0x04]);

// SharedArrayBuffer (requires headers)
const shared = new SharedArrayBuffer(1024);
const worker1View = new Int32Array(shared);
const worker2View = new Int32Array(shared);
// Both workers see same memory
Note: Typed arrays are fixed-length, array-like objects for binary data. Use for performance-critical operations (WebGL, image processing, binary I/O). Regular arrays support more methods and dynamic sizing.

Section 9 Summary

  • Creation: Use literals [] for general use; Array.from() for iterables with mapper; Array.of() to avoid single-number trap; spread for copying
  • Mutating: push/pop (end O(1)), shift/unshift (start O(n)), splice (any position); sort needs compare function for numbers; these modify original
  • Non-mutating: slice (copy), concat (merge), flat/flatMap (flatten); ES2023 adds toSorted/toReversed/toSpliced/with for immutability
  • Iteration: map (transform), filter (select), reduce (accumulate), forEach (side effects); always provide initial value to reduce
  • Search: find/findIndex (with predicate), includes (existence, handles NaN), indexOf (position); at() for negative indexing
  • Testing: every (all pass), some (any pass); both short-circuit; every([]) === true, some([]) === false
  • Destructuring: [a, b] = arr, rest [...rest], skip with ,, defaults; spread ...arr for copy/merge (shallow!)
  • Typed arrays: Fixed-length, type-specific, backed by ArrayBuffer; use for binary data, WebGL, performance; limited methods vs regular arrays

10. Strings and Template Literals

10.1 String Creation and String Literals

Type Syntax Description Example
Single Quotes 'string' Basic string literal; escape ' as \' 'Hello world'
Double Quotes "string" Identical to single quotes; escape " as \" "Hello world"
Template Literal `string` Backticks; supports interpolation, multiline, and tag functions `Hello ${name}`
String Constructor String(value) Convert to string; returns primitive (without new) String(123) // "123"
String Object new String(value) Creates String object (wrapper); avoid - use primitives new String("hi")
Escape Sequences \n \t \\ \' \" Special characters: newline, tab, backslash, quotes 'Line 1\nLine 2'
Unicode Escape \uXXXX 4-digit hex Unicode code point (BMP only) '\u0041' // "A"
Unicode Code Point \u{X...} 1-6 hex digits; supports full Unicode range (including astral) '\u{1F600}' // "😀"
Hex Escape \xXX 2-digit hex byte value (0x00-0xFF) '\x41' // "A"
Raw String ES2015 String.raw`...` Template literal without escape processing String.raw`\n` // "\\n"

Example: String creation patterns

// Literal syntax (preferred)
const single = 'Hello';
const double = "World";
const template = `Hello ${name}`;

// String constructor (type conversion)
String(123); // "123"
String(true); // "true"
String(null); // "null"
String({a: 1}); // "[object Object]"

// Avoid String object (wrapper)
const bad = new String("text"); // typeof bad === "object" ❌
const good = "text"; // typeof good === "string" ✓

// Escape sequences
'Line 1\nLine 2'; // newline
'Tab\tseparated'; // tab
'She said "hi"'; // double quotes in single
"It's okay"; // single quote in double
'Can\'t escape'; // escaped single quote

// More escape sequences
'C:\\Users\\name'; // backslashes
'First\x20Second'; // space using hex
'\u00A9 2025'; // © 2025
'\u{1F4A9}'; // 💩 (requires code point syntax)

// Multiline strings
const multi = `Line 1
Line 2
Line 3`; // preserves newlines

const legacy = 'Line 1\n' +
               'Line 2\n' +
               'Line 3'; // old way

// Raw strings (no escape processing)
String.raw`C:\Users\name`; // "C:\\Users\\name"
String.raw`\n\t`; // "\\n\\t" (literal backslashes)

// Empty and whitespace
const empty = '';
const space = ' ';
const whitespace = '   \t\n   ';

// Immutability
const str = 'hello';
str[0] = 'H'; // No effect - strings are immutable
str.toUpperCase(); // Returns "HELLO", str unchanged
Note: Strings are immutable primitives. Use single/double quotes for simple strings; template literals for interpolation/multiline. Avoid new String() wrapper objects.

10.2 String Methods and String Manipulation

Method Syntax Description Returns
length str.length Number of UTF-16 code units (not characters!); astral chars count as 2 Number
charAt() str.charAt(index) Character at index; returns empty string if out of bounds String
charCodeAt() str.charCodeAt(index) UTF-16 code unit (0-65535); NaN if out of bounds Number
codePointAt() str.codePointAt(index) Full Unicode code point; handles astral characters correctly Number
at() ES2022 str.at(index) Character at index; negative indices from end; undefined if out of bounds String | undefined
concat() str.concat(str2, ...) Join strings; prefer + or template literals for readability String
slice() str.slice(start, end) Extract substring; negative indices from end; preferred method String
substring() str.substring(start, end) Like slice but swaps args if start > end; no negative indices String
substr() DEPRECATED str.substr(start, length) Extract by length; deprecated - use slice() instead String
toUpperCase() str.toUpperCase() Convert to uppercase; locale-independent String
toLowerCase() str.toLowerCase() Convert to lowercase; locale-independent String
toLocaleUpperCase() str.toLocaleUpperCase(locale) Locale-aware uppercase (e.g., Turkish İ/I) String
toLocaleLowerCase() str.toLocaleLowerCase(locale) Locale-aware lowercase String
trim() str.trim() Remove whitespace from both ends String
trimStart() / trimLeft() str.trimStart() Remove whitespace from start only String
trimEnd() / trimRight() str.trimEnd() Remove whitespace from end only String
padStart() ES2017 str.padStart(length, padStr) Pad to length from start; default space String
padEnd() ES2017 str.padEnd(length, padStr) Pad to length from end; default space String
repeat() str.repeat(count) Repeat string count times; count must be non-negative integer String

Example: String manipulation methods

const str = 'Hello World';

// Access characters
str.charAt(0); // "H"
str[0]; // "H" (bracket notation)
str.at(0); // "H"
str.at(-1); // "d" (last character)
str.at(-2); // "l" (second to last)

// Character codes
str.charCodeAt(0); // 72 (UTF-16 code unit)
str.codePointAt(0); // 72 (Unicode code point)

// Emoji handling
const emoji = '😀';
emoji.length; // 2 ❌ (surrogate pair)
emoji.codePointAt(0); // 128512 ✓
emoji.charCodeAt(0); // 55357 ❌ (high surrogate only)

// Substrings
str.slice(0, 5); // "Hello"
str.slice(6); // "World"
str.slice(-5); // "World" (last 5)
str.slice(-5, -1); // "Worl"

str.substring(0, 5); // "Hello" (same as slice)
str.substring(5, 0); // "Hello" (swaps args!)

// Case conversion
'JavaScript'.toUpperCase(); // "JAVASCRIPT"
'JavaScript'.toLowerCase(); // "javascript"

// Turkish locale example
'i'.toLocaleUpperCase('tr'); // "İ" (dotted capital I)
'I'.toLocaleLowerCase('tr'); // "ı" (dotless small i)

// Whitespace removal
'  hello  '.trim(); // "hello"
'  hello  '.trimStart(); // "hello  "
'  hello  '.trimEnd(); // "  hello"

// Padding
'5'.padStart(3, '0'); // "005"
'123'.padStart(5, '0'); // "00123"
'hello'.padEnd(10, '.'); // "hello....."

const cardNum = '1234';
cardNum.padStart(16, '*'); // "************1234"

// Repeat
'*'.repeat(10); // "**********"
'abc'.repeat(3); // "abcabcabc"

// Concatenation
'Hello'.concat(' ', 'World'); // "Hello World"
'a' + 'b' + 'c'; // "abc" (preferred)

// Chaining
const result = '  HELLO  '
    .trim()
    .toLowerCase()
    .repeat(2)
    .padStart(15, '.');
// "...hellohello"
Note: Use slice() over substring() for consistency with arrays. Avoid deprecated substr(). Remember length counts UTF-16 code units, not characters.

10.3 Template Literals and Tag Functions

Feature Syntax Description Example
Template Literal `text` Backticks; supports interpolation and multiline `Hello ${name}`
Interpolation ${expression} Embed any expression; converted to string via ToString `Sum: ${a + b}`
Multiline `line1\nline2` Preserves actual newlines and indentation `Line 1\nLine 2`
Tagged Template tag`template` Function processes template; receives strings and values separately html`<div>${text}</div>`
String.raw String.raw`text` Built-in tag that returns raw string (no escape processing) String.raw`\n` // "\\n"
Tag Function Signature Receives strings array and interpolated values
Parameters function tag(strings, ...values)
strings Array of string literals; has .raw property for unprocessed strings
...values Rest parameter with all interpolated expression results

Example: Template literals and tag functions

// Basic interpolation
const name = 'Alice';
const age = 25;
const msg = `Hello ${name}, you are ${age} years old`;
// "Hello Alice, you are 25 years old"

// Expressions
const a = 10, b = 20;
`Sum: ${a + b}`; // "Sum: 30"
`Comparison: ${a > b ? 'a' : 'b'}`; // "Comparison: b"
`Array: ${[1, 2, 3].join(', ')}`; // "Array: 1, 2, 3"

// Multiline
const html = `
  <div class="card">
    <h2>${name}</h2>
    <p>Age: ${age}</p>
  </div>
`; // Preserves indentation and newlines

// Nesting
`Outer ${`Inner ${1 + 1}`} text`; // "Outer Inner 2 text"

// String.raw - no escape processing
String.raw`C:\Users\name`; // "C:\\Users\\name"
String.raw`Line 1\nLine 2`; // "Line 1\\nLine 2"

// Custom tag function - basic
function upper(strings, ...values) {
    let result = '';
    strings.forEach((str, i) => {
        result += str;
        if (i < values.length) {
            result += String(values[i]).toUpperCase();
        }
    });
    return result;
}

upper`Hello ${name}, age ${age}`;
// "Hello ALICE, age 25"

// Tag function - HTML escaping
function html(strings, ...values) {
    return strings.reduce((result, str, i) => {
        const value = values[i] || '';
        const escaped = String(value)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;');
        return result + str + escaped;
    }, '');
}

const userInput = '<script>alert("xss")</script>';
html`<div>${userInput}</div>`;
// "<div>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</div>"

// Tag function - SQL (parameterized queries)
function sql(strings, ...values) {
    return {
        query: strings.join('?'),
        params: values
    };
}

const userId = 123;
const query = sql`SELECT * FROM users WHERE id = ${userId}`;
// {query: "SELECT * FROM users WHERE id = ?", params: [123]}

// Tag function - i18n translation
function t(strings, ...values) {
    const key = strings.join('{}');
    // Look up translation for key
    return translate(key, values);
}

t`Welcome ${name}!`; // Translates "Welcome {}!" with name

// Access raw strings
function logger(strings, ...values) {
    console.log('Processed:', strings);
    console.log('Raw:', strings.raw);
    console.log('Values:', values);
}

logger`Line 1\nLine 2`;
// Processed: ["Line 1
// Line 2"]
// Raw: ["Line 1\\nLine 2"]
// Values: []

// Empty interpolations
`a${null}b`; // "anullb"
`a${undefined}b`; // "aundefinedb"
`a${''}b`; // "ab"
Note: Tagged templates enable custom string processing (HTML escaping, i18n, SQL, styling). The strings.raw property provides unprocessed strings. All interpolated values are converted to strings.

10.4 String Interpolation and Expression Embedding

Pattern Syntax Description Example
Variable ${variable} Insert variable value; converts to string `Hello ${name}`
Expression ${expr} Any valid expression; evaluated and converted to string `Sum: ${a + b}`
Function Call ${fn()} Call function and insert return value `Time: ${getTime()}`
Ternary ${cond ? a : b} Conditional expressions inline `${online ? '🟢' : '🔴'}`
Logical AND ${cond && value} Short-circuit conditional rendering; false becomes "false" string `${hasError && errorMsg}`
Logical OR ${val || default} Fallback value; beware of 0, '', false `${name || 'Guest'}`
Nullish Coalescing ${val ?? default} Fallback only for null/undefined; preserves 0, '', false `${count ?? 0}`
Array Method ${arr.method()} Array operations inline `Items: ${arr.join(', ')}`
Object Property ${obj.prop} Access nested properties `Name: ${user.name}`
Nested Template ${`inner ${val}`} Template literals can be nested `Outer ${`inner ${x}`}`

Example: String interpolation patterns

const user = {name: 'Alice', age: 25, online: true};
const items = ['apple', 'banana', 'orange'];
const count = 0;

// Basic variable
`Username: ${user.name}`; // "Username: Alice"

// Arithmetic expressions
`Total: ${(99.99 * 1.1).toFixed(2)}`; // "Total: $109.99"
`Progress: ${(completed / total * 100).toFixed(1)}%`;

// Function calls
`Generated at ${new Date().toISOString()}`;
`Random: ${Math.random().toFixed(4)}`;

// Ternary operator
`Status: ${user.online ? '🟢 Online' : '🔴 Offline'}`;
`You have ${count === 0 ? 'no' : count} items`;

// Logical AND (conditional content)
`${user.admin && 'Admin Panel'}`;
// If admin: "Admin Panel", else: "false" ❌

// Better: use ternary for boolean
`${user.admin ? 'Admin Panel' : ''}`;
// If admin: "Admin Panel", else: ""

// Logical OR (fallback)
`Hello ${user.nickname || user.name}`;
`Count: ${count || 'N/A'}`; // ❌ 0 becomes 'N/A'!

// Nullish coalescing (better fallback)
`Count: ${count ?? 'N/A'}`; // ✓ 0 stays 0
`Name: ${user.name ?? 'Anonymous'}`;

// Array methods
`Items: ${items.join(', ')}`; // "Items: apple, banana, orange"
`Count: ${items.length}`;
`First: ${items[0]}`;

// Array rendering with map
`<ul>${items.map(item => `<li>${item}</li>`).join('')}</ul>`;
// "<ul><li>apple</li><li>banana</li><li>orange</li></ul>"

// Object methods
`User: ${JSON.stringify(user)}`;
`Keys: ${Object.keys(user).join(', ')}`;

// Nested templates
const className = `btn ${user.online ? 'btn-success' : 'btn-danger'}`;
`<button class="${className}">${user.name}</button>`;

// Complex expressions
const price = 99.99;
const discount = 0.15;
const tax = 0.08;
`Final: ${(price * (1 - discount) * (1 + tax)).toFixed(2)}`;

// Method chaining
`Result: ${str.trim().toLowerCase().split(' ').join('-')}`;

// Type coercion
`Number: ${123}`; // "Number: 123"
`Boolean: ${true}`; // "Boolean: true"
`Null: ${null}`; // "Null: null"
`Undefined: ${undefined}`; // "Undefined: undefined"
`Object: ${{}}`; // "Object: [object Object]"
`Array: ${[1, 2]}`; // "Array: 1,2"

// Multi-line with interpolation
const html = `
  <div class="user-card ${user.online ? 'online' : 'offline'}">
    <h2>${user.name}</h2>
    <p>Age: ${user.age}</p>
    <p>Status: ${user.online ? '🟢' : '🔴'}</p>
  </div>
`;

// Escaping interpolation
const literal = String.raw`Not interpolated: \${variable}`;
// "Not interpolated: ${variable}"
Warning: Logical AND ${cond && val} converts false to "false" string. Use ternary ${cond ? val : ''} for conditional rendering. Prefer ?? over || for fallbacks.

10.5 Unicode Handling and Character Encoding

Method/Feature Syntax Description Example
length Property str.length UTF-16 code units, not characters; astral symbols count as 2 '😀'.length // 2
Code Point Escape \u{codepoint} 1-6 hex digits; supports full Unicode (0-10FFFF) '\u{1F600}' // 😀
BMP Escape \uXXXX 4 hex digits; Basic Multilingual Plane only (0-FFFF) '\u00A9' // ©
String.fromCharCode() String.fromCharCode(code, ...) Create from UTF-16 code units; astral chars need two codes (surrogate pair) String.fromCharCode(65) // "A"
String.fromCodePoint() String.fromCodePoint(point, ...) Create from Unicode code points; handles astral chars correctly String.fromCodePoint(0x1F600) // "😀"
codePointAt() str.codePointAt(index) Get full Unicode code point at position '😀'.codePointAt(0) // 128512
charCodeAt() str.charCodeAt(index) Get UTF-16 code unit; only half of astral char (high surrogate) '😀'.charCodeAt(0) // 55357
normalize() str.normalize(form) Unicode normalization: NFC, NFD, NFKC, NFKD; for comparison 'café'.normalize('NFC')
localeCompare() str.localeCompare(str2, locale) Locale-aware string comparison; handles accents, case correctly 'ä'.localeCompare('z', 'de')
Spread/Array.from [...str] Iterate by code points, not code units; handles emoji correctly [...'😀🎉'].length // 2
for...of Loop for (const char of str) Iterates by Unicode code points; emoji-safe for (const c of '😀')

Example: Unicode handling

// BMP characters (single code unit)
const ascii = 'A';
ascii.length; // 1
ascii.charCodeAt(0); // 65
ascii.codePointAt(0); // 65

const copyright = '\u00A9'; // ©
copyright.length; // 1

// Astral plane (emoji, historic scripts)
const emoji = '😀';
emoji.length; // 2 ❌ (surrogate pair!)
emoji.charCodeAt(0); // 55357 (high surrogate)
emoji.charCodeAt(1); // 56832 (low surrogate)
emoji.codePointAt(0); // 128512 ✓ (full code point)

// Creating from code points
String.fromCharCode(65); // "A"
String.fromCharCode(0x00A9); // "©"

// Emoji requires fromCodePoint
String.fromCodePoint(0x1F600); // "😀"
String.fromCharCode(0x1F600); // ❌ Wrong!

// Surrogate pair manually (don't do this!)
String.fromCharCode(0xD83D, 0xDE00); // "😀" (works but use fromCodePoint)

// Code point escape syntax
'\u{1F600}'; // "😀"
'\u{1F4A9}'; // "💩"
'\u{1F680}'; // "🚀"

// Length issues with emoji
'hello'.length; // 5 ✓
'😀'.length; // 2 ❌
'👨‍👩‍👧‍👦'.length; // 11 ❌ (family emoji with ZWJ)

// Count actual characters
[...'hello'].length; // 5
[...'😀'].length; // 1 ✓
[...'😀🎉'].length; // 2 ✓

// However, combining characters still tricky
[...'👨‍👩‍👧‍👦'].length; // 7 (4 people + 3 ZWJ)

// Iterate correctly
for (const char of '😀🎉') {
    console.log(char); // "😀", "🎉" (correct!)
}

// Split fails with emoji
'😀🎉'.split(''); // ['�','�','�','�'] ❌
[...'😀🎉']; // ['😀','🎉'] ✓

// Unicode normalization
const e1 = '\u00E9'; // é (single code point)
const e2 = '\u0065\u0301'; // é (e + combining acute accent)

e1 === e2; // false ❌
e1.length; // 1
e2.length; // 2

e1.normalize() === e2.normalize(); // true ✓

// Forms: NFC (composed), NFD (decomposed)
'café'.normalize('NFC').length; // 4
'café'.normalize('NFD').length; // 5 (e + combining accent)

// Locale-aware comparison
const strings = ['ä', 'z', 'a'];
strings.sort(); // ['a', 'z', 'ä'] ❌
strings.sort((a, b) => a.localeCompare(b, 'de'));
// ['a', 'ä', 'z'] ✓ (German collation)

'ä'.localeCompare('z', 'de'); // -1 (ä comes before z)
'ä'.localeCompare('z', 'sv'); // 1 (ä comes after z in Swedish)

// Case comparison with locale
'i'.toLocaleUpperCase('en'); // "I"
'i'.toLocaleUpperCase('tr'); // "İ" (dotted capital I in Turkish)

// Real character count (approximate)
function graphemeLength(str) {
    return [...new Intl.Segmenter().segment(str)].length;
}

graphemeLength('😀'); // 1
graphemeLength('👨‍👩‍👧‍👦'); // 1 (treats family as one grapheme)
Warning: length counts UTF-16 code units, not characters. Use [...str].length for code point count. For grapheme clusters (with ZWJ, combining marks), use Intl.Segmenter.

10.6 String Regular Expression Integration

Method Syntax Description Returns
match() str.match(regexp) Find matches; with /g returns all matches, without returns match details Array | null
matchAll() ES2020 str.matchAll(regexp) Iterator of all matches with capture groups; regexp must have /g flag Iterator
search() str.search(regexp) Find index of first match; ignores /g flag Index | -1
replace() str.replace(regexp|str, newStr|fn) Replace matches; without /g replaces first only String
replaceAll() ES2021 str.replaceAll(str|regexp, newStr|fn) Replace all occurrences; regexp must have /g or use string String
split() str.split(separator, limit) Split by string or regexp; optional limit on array size Array
startsWith() str.startsWith(search, pos) Check if starts with string; optional position; no regexp Boolean
endsWith() str.endsWith(search, length) Check if ends with string; optional length limit; no regexp Boolean
includes() str.includes(search, pos) Check if contains string; optional start position; no regexp Boolean
indexOf() str.indexOf(search, pos) Find first occurrence; case-sensitive; no regexp Index | -1
lastIndexOf() str.lastIndexOf(search, pos) Find last occurrence; searches backwards; no regexp Index | -1

Example: String RegExp integration

const str = 'The quick brown fox jumps over the lazy dog';

// match - without /g (first match with details)
str.match(/quick/);
// ["quick", index: 4, input: "...", groups: undefined]

str.match(/(\w+) (\w+)/);
// ["The quick", "The", "quick", index: 0, ...]

// match - with /g (all matches, no details)
str.match(/\w+/g);
// ["The", "quick", "brown", "fox", ...]

str.match(/o/g); // ["o", "o", "o", "o"]

// matchAll - all matches with details
const regex = /t(\w+)/gi;
const matches = [...str.matchAll(regex)];
matches[0]; // ["The", "he", index: 0, ...]
matches[1]; // ["the", "he", index: 31, ...]

// matchAll with named groups
const pattern = /(?<word>\w+) (?<num>\d+)/g;
const text = 'item 1, thing 2';
for (const match of text.matchAll(pattern)) {
    console.log(match.groups.word, match.groups.num);
    // "item" "1", "thing" "2"
}

// search - find position
str.search(/brown/); // 10
str.search(/cat/); // -1
str.search(/THE/i); // 0 (case-insensitive)

// replace - string or regexp
str.replace('dog', 'cat');
// "The quick brown fox jumps over the lazy cat"

str.replace(/the/gi, 'a');
// "a quick brown fox jumps over a lazy dog"

// replace with function
str.replace(/\b\w{4}\b/g, match => match.toUpperCase());
// "The QUICK brown fox JUMPS OVER the LAZY dog"

// replace with capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// replace with named groups
'2025-12-17'.replace(
    /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
    '
const str = 'The quick brown fox jumps over the lazy dog';

// match - without /g (first match with details)
str.match(/quick/);
// ["quick", index: 4, input: "...", groups: undefined]

str.match(/(\w+) (\w+)/);
// ["The quick", "The", "quick", index: 0, ...]

// match - with /g (all matches, no details)
str.match(/\w+/g);
// ["The", "quick", "brown", "fox", ...]

str.match(/o/g); // ["o", "o", "o", "o"]

// matchAll - all matches with details
const regex = /t(\w+)/gi;
const matches = [...str.matchAll(regex)];
matches[0]; // ["The", "he", index: 0, ...]
matches[1]; // ["the", "he", index: 31, ...]

// matchAll with named groups
const pattern = /(?<word>\w+) (?<num>\d+)/g;
const text = 'item 1, thing 2';
for (const match of text.matchAll(pattern)) {
    console.log(match.groups.word, match.groups.num);
    // "item" "1", "thing" "2"
}

// search - find position
str.search(/brown/); // 10
str.search(/cat/); // -1
str.search(/THE/i); // 0 (case-insensitive)

// replace - string or regexp
str.replace('dog', 'cat');
// "The quick brown fox jumps over the lazy cat"

str.replace(/the/gi, 'a');
// "a quick brown fox jumps over a lazy dog"

// replace with function
str.replace(/\b\w{4}\b/g, match => match.toUpperCase());
// "The QUICK brown fox JUMPS OVER the LAZY dog"

// replace with capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// replace with named groups
'2025-12-17'.replace(
    /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// replaceAll - all occurrences
'aaa'.replace('a', 'b'); // "baa"
'aaa'.replaceAll('a', 'b'); // "bbb"

'a-b-c'.replaceAll('-', '_'); // "a_b_c"

// split by regexp
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split with limit
'a-b-c-d'.split('-', 2); // ["a", "b"]

// includes, startsWith, endsWith (no regexp!)
str.includes('fox'); // true
str.includes('cat'); // false

str.startsWith('The'); // true
str.startsWith('the'); // false (case-sensitive)

str.endsWith('dog'); // true
str.endsWith('cat'); // false

// indexOf, lastIndexOf
str.indexOf('o'); // 12 (first 'o')
str.lastIndexOf('o'); // 41 (last 'o')

str.indexOf('the'); // -1 (case-sensitive)
str.toLowerCase().indexOf('the'); // 0

// Check from position
str.startsWith('quick', 4); // true (at index 4)
str.includes('fox', 20); // false (search starts at 20)

// Case-insensitive search
const lower = str.toLowerCase();
lower.includes('THE'.toLowerCase()); // true

// Or use regexp with /i
/THE/i.test(str); // true
#x3C;month>/
const str = 'The quick brown fox jumps over the lazy dog';

// match - without /g (first match with details)
str.match(/quick/);
// ["quick", index: 4, input: "...", groups: undefined]

str.match(/(\w+) (\w+)/);
// ["The quick", "The", "quick", index: 0, ...]

// match - with /g (all matches, no details)
str.match(/\w+/g);
// ["The", "quick", "brown", "fox", ...]

str.match(/o/g); // ["o", "o", "o", "o"]

// matchAll - all matches with details
const regex = /t(\w+)/gi;
const matches = [...str.matchAll(regex)];
matches[0]; // ["The", "he", index: 0, ...]
matches[1]; // ["the", "he", index: 31, ...]

// matchAll with named groups
const pattern = /(?<word>\w+) (?<num>\d+)/g;
const text = 'item 1, thing 2';
for (const match of text.matchAll(pattern)) {
    console.log(match.groups.word, match.groups.num);
    // "item" "1", "thing" "2"
}

// search - find position
str.search(/brown/); // 10
str.search(/cat/); // -1
str.search(/THE/i); // 0 (case-insensitive)

// replace - string or regexp
str.replace('dog', 'cat');
// "The quick brown fox jumps over the lazy cat"

str.replace(/the/gi, 'a');
// "a quick brown fox jumps over a lazy dog"

// replace with function
str.replace(/\b\w{4}\b/g, match => match.toUpperCase());
// "The QUICK brown fox JUMPS OVER the LAZY dog"

// replace with capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// replace with named groups
'2025-12-17'.replace(
    /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// replaceAll - all occurrences
'aaa'.replace('a', 'b'); // "baa"
'aaa'.replaceAll('a', 'b'); // "bbb"

'a-b-c'.replaceAll('-', '_'); // "a_b_c"

// split by regexp
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split with limit
'a-b-c-d'.split('-', 2); // ["a", "b"]

// includes, startsWith, endsWith (no regexp!)
str.includes('fox'); // true
str.includes('cat'); // false

str.startsWith('The'); // true
str.startsWith('the'); // false (case-sensitive)

str.endsWith('dog'); // true
str.endsWith('cat'); // false

// indexOf, lastIndexOf
str.indexOf('o'); // 12 (first 'o')
str.lastIndexOf('o'); // 41 (last 'o')

str.indexOf('the'); // -1 (case-sensitive)
str.toLowerCase().indexOf('the'); // 0

// Check from position
str.startsWith('quick', 4); // true (at index 4)
str.includes('fox', 20); // false (search starts at 20)

// Case-insensitive search
const lower = str.toLowerCase();
lower.includes('THE'.toLowerCase()); // true

// Or use regexp with /i
/THE/i.test(str); // true
#x3C;day>/
const str = 'The quick brown fox jumps over the lazy dog';

// match - without /g (first match with details)
str.match(/quick/);
// ["quick", index: 4, input: "...", groups: undefined]

str.match(/(\w+) (\w+)/);
// ["The quick", "The", "quick", index: 0, ...]

// match - with /g (all matches, no details)
str.match(/\w+/g);
// ["The", "quick", "brown", "fox", ...]

str.match(/o/g); // ["o", "o", "o", "o"]

// matchAll - all matches with details
const regex = /t(\w+)/gi;
const matches = [...str.matchAll(regex)];
matches[0]; // ["The", "he", index: 0, ...]
matches[1]; // ["the", "he", index: 31, ...]

// matchAll with named groups
const pattern = /(?<word>\w+) (?<num>\d+)/g;
const text = 'item 1, thing 2';
for (const match of text.matchAll(pattern)) {
    console.log(match.groups.word, match.groups.num);
    // "item" "1", "thing" "2"
}

// search - find position
str.search(/brown/); // 10
str.search(/cat/); // -1
str.search(/THE/i); // 0 (case-insensitive)

// replace - string or regexp
str.replace('dog', 'cat');
// "The quick brown fox jumps over the lazy cat"

str.replace(/the/gi, 'a');
// "a quick brown fox jumps over a lazy dog"

// replace with function
str.replace(/\b\w{4}\b/g, match => match.toUpperCase());
// "The QUICK brown fox JUMPS OVER the LAZY dog"

// replace with capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// replace with named groups
'2025-12-17'.replace(
    /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// replaceAll - all occurrences
'aaa'.replace('a', 'b'); // "baa"
'aaa'.replaceAll('a', 'b'); // "bbb"

'a-b-c'.replaceAll('-', '_'); // "a_b_c"

// split by regexp
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split with limit
'a-b-c-d'.split('-', 2); // ["a", "b"]

// includes, startsWith, endsWith (no regexp!)
str.includes('fox'); // true
str.includes('cat'); // false

str.startsWith('The'); // true
str.startsWith('the'); // false (case-sensitive)

str.endsWith('dog'); // true
str.endsWith('cat'); // false

// indexOf, lastIndexOf
str.indexOf('o'); // 12 (first 'o')
str.lastIndexOf('o'); // 41 (last 'o')

str.indexOf('the'); // -1 (case-sensitive)
str.toLowerCase().indexOf('the'); // 0

// Check from position
str.startsWith('quick', 4); // true (at index 4)
str.includes('fox', 20); // false (search starts at 20)

// Case-insensitive search
const lower = str.toLowerCase();
lower.includes('THE'.toLowerCase()); // true

// Or use regexp with /i
/THE/i.test(str); // true
#x3C;year>'
); // "12/17/2025" // replaceAll - all occurrences 'aaa'.replace('a', 'b'); // "baa" 'aaa'.replaceAll('a', 'b'); // "bbb" 'a-b-c'.replaceAll('-', '_'); // "a_b_c" // split by regexp 'a1b2c3'.split(/\d/); // ["a", "b", "c", ""] 'one,two;three:four'.split(/[,;:]/); // ["one", "two", "three", "four"] // split with limit 'a-b-c-d'.split('-', 2); // ["a", "b"] // includes, startsWith, endsWith (no regexp!) str.includes('fox'); // true str.includes('cat'); // false str.startsWith('The'); // true str.startsWith('the'); // false (case-sensitive) str.endsWith('dog'); // true str.endsWith('cat'); // false // indexOf, lastIndexOf str.indexOf('o'); // 12 (first 'o') str.lastIndexOf('o'); // 41 (last 'o') str.indexOf('the'); // -1 (case-sensitive) str.toLowerCase().indexOf('the'); // 0 // Check from position str.startsWith('quick', 4); // true (at index 4) str.includes('fox', 20); // false (search starts at 20) // Case-insensitive search const lower = str.toLowerCase(); lower.includes('THE'.toLowerCase()); // true // Or use regexp with /i /THE/i.test(str); // true
Note: Use match() for simple searches; matchAll() for all matches with details; replace() with functions for complex transformations. includes/startsWith/endsWith don't support regexp.

Section 10 Summary

  • Creation: Use single/double quotes for simple strings; template literals `...` for interpolation/multiline; escape sequences \n \t; Unicode \u{...} for full range
  • Methods: slice() for substrings (supports negative indices); trim/padStart/padEnd for formatting; toUpperCase/toLowerCase for case; all return new strings (immutable)
  • Template literals: `${expr}` interpolation; multiline preserves formatting; tagged templates tag`...` for custom processing (HTML escape, i18n, SQL)
  • Interpolation: Any expression; use ternary ? : not && for conditionals; ?? for null/undefined fallback; beware || with 0/false
  • Unicode: length counts UTF-16 units not characters; [...str] for code points; codePointAt/fromCodePoint for emoji; normalize() for comparison
  • RegExp: match/matchAll find patterns; replace/replaceAll modify; split divides; includes/startsWith/endsWith check (no regexp); search finds index

11. Regular Expressions and Pattern Matching

11.1 RegExp Literal and Constructor Syntax

Syntax Form Description Example
Literal Syntax /pattern/flags Most common; compiled at parse time; cannot use variables in pattern /hello/i
Constructor new RegExp(pattern, flags) Dynamic patterns; compiled at runtime; pattern is string (escape backslashes!) new RegExp('\\d+', 'g')
Constructor (no new) RegExp(pattern, flags) Same as with new; returns RegExp instance RegExp('test')
From RegExp new RegExp(regexp, flags) Clone with optional new flags; ES2015+ new RegExp(/test/i, 'g')
Property Type Description Example
source String Pattern text (without delimiters and flags) /abc/i.source // "abc"
flags String All flags as string; alphabetically sorted /a/gi.flags // "gi"
global Boolean Has g flag /a/g.global // true
ignoreCase Boolean Has i flag /a/i.ignoreCase // true
multiline Boolean Has m flag /a/m.multiline // true
dotAll Boolean Has s flag (. matches newlines) /a/s.dotAll // true
unicode Boolean Has u flag /a/u.unicode // true
sticky Boolean Has y flag /a/y.sticky // true
lastIndex Number Start position for next match; used with g or y flags regex.lastIndex = 0

Example: RegExp creation and properties

// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;

// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally

// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d

// Or use String.raw
const regex = new RegExp(String.raw`\d+`);

// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")

// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false

// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)

// Reset lastIndex
globalRe.lastIndex = 0;

// Create from string (user input)
function createRegex(userPattern, options = {}) {
    const flags = 
        (options.global ? 'g' : '') +
        (options.ignoreCase ? 'i' : '') +
        (options.multiline ? 'm' : '');
    
    try {
        return new RegExp(userPattern, flags);
    } catch (e) {
        console.error('Invalid regex:', e.message);
        return null;
    }
}

// Escape special characters for literal matching
function escapeRegex(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\
// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;

// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally

// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d

// Or use String.raw
const regex = new RegExp(String.raw`\d+`);

// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")

// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false

// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)

// Reset lastIndex
globalRe.lastIndex = 0;

// Create from string (user input)
function createRegex(userPattern, options = {}) {
    const flags = 
        (options.global ? 'g' : '') +
        (options.ignoreCase ? 'i' : '') +
        (options.multiline ? 'm' : '');
    
    try {
        return new RegExp(userPattern, flags);
    } catch (e) {
        console.error('Invalid regex:', e.message);
        return null;
    }
}

// Escape special characters for literal matching
function escapeRegex(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

const userInput = 'hello.world';
const escaped = escapeRegex(userInput); // "hello\\.world"
const safe = new RegExp(escaped); // matches literal "hello.world"
#x26;"
);
} const userInput = 'hello.world'; const escaped = escapeRegex(userInput); // "hello\\.world" const safe = new RegExp(escaped); // matches literal "hello.world"
Note: Use literals /pattern/flags for static patterns (better performance); constructor for dynamic patterns. Remember to double-escape backslashes in strings: '\\d' not '\d'.

11.2 RegExp Flags and Global Modifiers

Flag Name Description Effect
g Global Find all matches, not just first; affects exec(), test(), match(), replace() Multiple matches
i Ignore Case Case-insensitive matching; [a-z] matches both cases /a/i matches 'A' and 'a'
m Multiline ^ and $ match line boundaries, not just string start/end ^word matches after \n
s ES2018 DotAll . (dot) matches newlines; normally dot excludes \n \r \u2028 \u2029 /.+/s matches across lines
u ES2015 Unicode Full Unicode support; astral characters, Unicode escapes, properties \u{1F600} and \p{...}
y ES2015 Sticky Match must start at lastIndex; no skipping ahead For tokenizers/parsers
d ES2022 Indices Capture group indices in .indices property of match result match.indices[0]

Example: Flag behaviors

const str = 'Hello World\nHello Again';

// g - global (all matches)
str.match(/hello/i); // ["Hello"] (first only)
str.match(/hello/gi); // ["Hello", "Hello"] (all)

/\d+/g.test('123'); // true
/\d+/g.test('123'); // false (lastIndex moved!)

// i - ignore case
/hello/.test('HELLO'); // false
/hello/i.test('HELLO'); // true

// m - multiline (^ $ match line boundaries)
const multi = 'line1\nline2\nline3';
multi.match(/^line/g); // ["line"] (only first)
multi.match(/^line/gm); // ["line", "line", "line"] (each line)

/world$/.test('hello\nworld'); // true (end of string)
/world$/m.test('world\nhello'); // true (end of line)

// s - dotAll (. matches newlines)
/.+/.test('hello\nworld'); // false (. stops at \n)
/.+/s.test('hello\nworld'); // true (. matches \n)

'a\nb'.match(/a.b/); // null
'a\nb'.match(/a.b/s); // ["a\nb"]

// u - unicode (proper astral character handling)
'😀'.match(/./); // ["�"] ❌ (high surrogate only)
'😀'.match(/./u); // ["😀"] ✓

// Unicode property escapes (requires u flag)
/\p{Emoji}/u.test('😀'); // true
/\p{Script=Greek}/u.test('α'); // true
/\p{Letter}/u.test('A'); // true

// Count code points, not code units
'😀'.length; // 2
'😀'.match(/./gu); // ["😀"] (1 match)
'😀'.match(/./g); // ["�","�"] (2 matches)

// y - sticky (match at exact position)
const sticky = /\d+/y;
sticky.lastIndex = 0;
sticky.exec('123 456'); // ["123"]
sticky.lastIndex; // 3

sticky.exec('123 456'); // null (position 3 is space)
sticky.lastIndex; // 0 (reset on failure)

// Without y, would skip ahead and match 456
const notSticky = /\d+/g;
notSticky.lastIndex = 3;
notSticky.exec('123 456'); // ["456"]

// d - indices (capture group positions)
const indicesRe = /(\d+)-(\d+)/d;
const match = indicesRe.exec('id: 123-456');

match[1]; // "123"
match[2]; // "456"
match.indices[1]; // [4, 7] (position of "123")
match.indices[2]; // [8, 11] (position of "456")

// Combine flags
const combined = /pattern/gimsu;
combined.flags; // "gimsu" (alphabetically sorted)
Warning: Global g flag makes RegExp stateful (lastIndex). Reset with regex.lastIndex = 0 or use string methods. Always use u flag for Unicode text!

11.3 Pattern Matching Methods (test, exec, match)

Method Syntax Description Returns
test() regex.test(str) Check if pattern matches; fastest for existence check Boolean
exec() regex.exec(str) Get match details with capture groups; use in loop with /g flag Array | null
match() str.match(regex) Without /g: like exec(); with /g: array of all matches (no details) Array | null
matchAll() ES2020 str.matchAll(regex) Iterator of all matches with full details; regex must have /g Iterator

Example: Pattern matching methods

const str = 'The year 2025 and 2026';

// test() - simple existence check
/\d+/.test(str); // true
/cat/.test(str); // false

// Use for validation
function isEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// exec() - detailed match info
const yearRe = /(\d{4})/;
const match = yearRe.exec(str);
match[0]; // "2025" (full match)
match[1]; // "2025" (capture group 1)
match.index; // 9 (position)
match.input; // original string

// exec() with /g flag (iterate)
const yearReGlobal = /(\d{4})/g;
let m;
while ((m = yearReGlobal.exec(str)) !== null) {
    console.log(m[1], 'at', m.index);
    // "2025" at 9
    // "2026" at 18
}

// match() without /g (same as exec)
str.match(/(\d{4})/);
// ["2025", "2025", index: 9, input: "...", groups: undefined]

// match() with /g (all matches, no details)
str.match(/\d{4}/g); // ["2025", "2026"]
str.match(/\d+/g); // ["2025", "2026"]

// matchAll() - best of both (ES2020+)
const years = str.matchAll(/(\d{4})/g);
for (const match of years) {
    console.log(match[1], 'at', match.index);
    // "2025" at 9
    // "2026" at 18
}

// Convert to array
const allMatches = [...str.matchAll(/(\d{4})/g)];

// Named capture groups
const phoneRe = /(?<area>\d{3})-(?<exchange>\d{3})-(?<number>\d{4})/;
const phone = '555-123-4567';
const phoneMatch = phone.match(phoneRe);

phoneMatch[1]; // "555"
phoneMatch.groups.area; // "555"
phoneMatch.groups.exchange; // "123"
phoneMatch.groups.number; // "4567"

// Multiple patterns
const email = 'user@example.com';
const emailRe = /^(?<user>[^@]+)@(?<domain>[^@]+)$/;
const emailMatch = email.match(emailRe);
emailMatch.groups.user; // "user"
emailMatch.groups.domain; // "example.com"

// No match returns null
'abc'.match(/\d+/); // null
/\d+/.test('abc'); // false
/\d+/.exec('abc'); // null

// Global test() gotcha (stateful!)
const re = /test/g;
re.test('test test'); // true (first call)
re.test('test test'); // true (second call)
re.test('test test'); // false! (lastIndex past end)
re.lastIndex; // 0 (reset)

// Solution: don't reuse global regex for test()
function hasDigit(str) {
    return /\d/.test(str); // no /g, always works
}

// Or reset lastIndex
function hasPattern(str, regex) {
    regex.lastIndex = 0;
    return regex.test(str);
}
Note: Use test() for boolean checks; match() for simple extraction; matchAll() for all matches with details. Avoid test() with global regex in loops (stateful).
Method Syntax Description Returns
replace() str.replace(regex, newStr|fn) Replace first match (or all with /g); newStr can use $1, $2, etc. String
replaceAll() ES2021 str.replaceAll(str|regex, newStr|fn) Replace all occurrences; regex must have /g flag String
split() str.split(regex, limit) Split string by pattern; optional limit; capture groups included in result Array
search() str.search(regex) Find index of first match; ignores /g flag; like indexOf for regex Index | -1
Replacement Patterns (in replace newStr argument)
Pattern Inserts Example Result
$$ Literal $ '$10'.replace(/\d+/, '$$&') "$&10"
$& Entire match 'cat'.replace(/\w+/, '[$&]') "[cat]"
$` Text before match 'a-b'.replace(/-/, '$`') "aab"
$' Text after match 'a-b'.replace(/-/, "$'") "abb"
$n Capture group n (1-99) 'John Doe'.replace(/(\w+) (\w+)/, '$2, $1') "Doe, John"
$<name> Named capture group '2025-12-17'.replace(/(?<y>\d+)-(?<m>\d+)-(?<d>\d+)/, '$<m>/$<d>/$<y>') "12/17/2025"

Example: String RegExp methods

const str = 'Hello World, hello again';

// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"

// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"

// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"

// Replacement patterns
'test'.replace(/test/, '[
const str = 'Hello World, hello again';

// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"

// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"

// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"

// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"

// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// Named groups
const date = '2025-12-17';
date.replace(
    /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"

// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
    return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"

// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
    return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"

// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]

// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]

// split - whitespace
'  hello   world  '.split(/\s+/);
// ["", "hello", "world", ""]

'  hello   world  '.trim().split(/\s+/);
// ["hello", "world"]

// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1

// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')

// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4

// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
    return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"

// 2. Sanitize HTML
function escapeHtml(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}

// 3. Slugify
function slugify(str) {
    return str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"

// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x26;]'
); // "[test]"
'a-b'.replace(/-/, '(

1. JavaScript Syntax and Language Fundamentals

1.1 JavaScript Syntax Structure and Statements

Statement Type Syntax Description Example
Expression Statement expression; Any valid expression followed by semicolon (ASI applies) x = 5; 2 + 2;
Declaration Statement var/let/const name; Declares variables with specific scope and mutability let x; const y = 10;
Block Statement { statements } Groups multiple statements; creates block scope for let/const { let x = 1; }
Empty Statement ; Does nothing; useful in loop bodies or as placeholder while(condition);
Labeled Statement label: statement Adds identifier to statement for break/continue targeting loop1: for(...) {...}
Function Declaration function name() {} Hoisted function definition, available before declaration function add(a,b) { return a+b; }
Class Declaration class Name {} Declares class; not hoisted like functions class User { constructor() {} }
Import Statement import x from 'mod'; Loads module exports; hoisted and executed first import { func } from './module';
Export Statement export default x; Exposes values/functions from module export { func, var };

Example: Statement types in action

// Expression statements
x = 5;
y = x + 10;

// Block statement with local scope
{
    let blockScoped = 'local';
    const PI = 3.14159;
}

// Labeled statement for loop control
outer: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) break outer;
    }
}
ASI (Automatic Semicolon Insertion): JavaScript automatically inserts semicolons in certain cases, but relying on it can cause bugs. Always use explicit semicolons for clarity.

1.2 Variable Declarations (var, let, const)

Declaration Scope Hoisting Reassignment Redeclaration TDZ
var LEGACY Function scope Yes (undefined) ✓ Allowed ✓ Allowed ✗ No
let ES6 Block scope Yes (uninitialized) ✓ Allowed ✗ SyntaxError ✓ Yes
const ES6 Block scope Yes (uninitialized) ✗ TypeError ✗ SyntaxError ✓ Yes
Feature var let const
Global Object Property ✓ Creates property on window/global ✗ Does not create property ✗ Does not create property
Loop Binding Single binding (shared) New binding per iteration New binding per iteration
Initialization Required ✗ Optional ✗ Optional ✓ Required at declaration
Object/Array Mutation Allowed Allowed Allowed (reference is const)

Example: Variable declaration differences

// var: function-scoped, hoisted
console.log(x); // undefined (hoisted)
var x = 5;
if (true) {
    var x = 10; // Same variable
}
console.log(x); // 10

// let: block-scoped, TDZ
// console.log(y); // ReferenceError: Cannot access before initialization
let y = 5;
if (true) {
    let y = 10; // Different variable
    console.log(y); // 10
}
console.log(y); // 5

// const: block-scoped, immutable binding
const z = 5;
// z = 10; // TypeError: Assignment to constant
const obj = { a: 1 };
obj.a = 2; // ✓ OK: mutation allowed
// obj = {}; // ✗ TypeError: reassignment not allowed
Warning: const creates an immutable binding, not an immutable value. Object properties can still be modified.

1.3 Identifier Rules and Naming Conventions

Rule Description Valid Examples Invalid Examples
First Character Letter (a-z, A-Z), underscore (_), or dollar sign ($) _var, $elem, myVar 1var, -name, @value
Subsequent Characters Letters, digits (0-9), underscore, dollar sign var1, _temp2, $el3 my-var, my.var, my var
Case Sensitivity JavaScript is case-sensitive; myVar ≠ myvar myVar, MyVar, MYVAR All are different variables
Unicode Support Unicode letters allowed (international characters) μ, café, 変数 Avoid for portability
Reserved Words Cannot use JavaScript keywords as identifiers myClass, _return class, return, if
Convention Usage Example Purpose
camelCase Variables, functions, methods myVariable, getUserData() Standard for most identifiers
PascalCase Classes, constructors, components UserProfile, DataService Distinguishes classes from functions
UPPER_SNAKE_CASE Constants, configuration values MAX_SIZE, API_URL Indicates immutable values
_privateConvention Private/internal members (convention) _internalCache, _helper() Signals internal use only
#privateFields ES2022 True private class fields #privateValue Language-level privacy

Example: Naming conventions in practice

// Constants
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = 'https://api.example.com';

// Variables and functions (camelCase)
let userName = 'John';
function calculateTotal(items) {
    return items.reduce((sum, item) => sum + item.price, 0);
}

// Classes (PascalCase)
class UserProfile {
    #privateData; // True private field
    
    constructor(name) {
        this._internalId = Math.random(); // Convention-based private
        this.#privateData = name;
    }
}

// Boolean variables (descriptive prefix)
const isActive = true;
const hasPermission = false;
const canEdit = user.role === 'admin';

1.4 Comments and Documentation Syntax

Comment Type Syntax Usage Example
Single-line // comment Brief explanations, inline notes // Calculate sum
Multi-line /* comment */ Longer explanations, block comments /* This is a multi-line comment */
JSDoc Block /** @tag */ API documentation, type annotations /** @param {string} name */
Hashbang #!/usr/bin/env node Shebang for executable scripts (line 1 only) Node.js CLI scripts
HTML-style LEGACY <!-- comment --> Legacy browser compatibility (avoid) Only in embedded scripts
JSDoc Tag Purpose Syntax Example
@param Function parameter documentation @param {type} name description @param {number} age User's age
@returns Return value description @returns {type} description @returns {boolean} True if valid
@typedef Custom type definition @typedef {Object} TypeName @typedef {Object} User
@type Variable type annotation @type {type} @type {string[]}
@deprecated Mark as deprecated @deprecated Use newFunc instead Signals obsolete code
@example Usage examples @example functionCall() Provides code samples
@throws Documents exceptions @throws {Error} description @throws {TypeError}

Example: JSDoc documentation

/**
 * Calculates the total price with tax
 * @param {number} price - Base price of item
 * @param {number} taxRate - Tax rate as decimal (e.g., 0.08)
 * @returns {number} Total price including tax
 * @throws {TypeError} If parameters are not numbers
 * @example
 * calculateTotal(100, 0.08); // Returns 108
 */
function calculateTotal(price, taxRate) {
    if (typeof price !== 'number' || typeof taxRate !== 'number') {
        throw new TypeError('Parameters must be numbers');
    }
    return price * (1 + taxRate);
}

/**
 * @typedef {Object} User
 * @property {string} name - User's full name
 * @property {number} age - User's age
 * @property {string[]} roles - User roles
 */

/** @type {User} */
const user = {
    name: 'John Doe',
    age: 30,
    roles: ['admin', 'user']
};

1.5 Strict Mode and Its Effects

Aspect Non-Strict Mode Strict Mode Impact
Activation Default behavior 'use strict'; at file/function start Enables stricter parsing and error handling
Implicit Globals Creates global variable ReferenceError Prevents accidental globals from typos
Assignment to Non-writable Silently fails TypeError Throws error on read-only property assignment
Delete Variables Returns false SyntaxError Cannot delete variables, functions, or arguments
Duplicate Parameters Allowed (last wins) SyntaxError Prevents function param name conflicts
Octal Literals 0123 allowed SyntaxError Use 0o123 instead
this in Functions Global object (window) undefined Prevents accidental global modifications
with Statement Allowed SyntaxError Banned due to scope ambiguity
eval Scope Creates variables in surrounding scope Own scope only Prevents eval from polluting scope
Reserved Words Some allowed as identifiers Stricter: implements, interface, let, package, private, protected, public, static, yield Future-proofs code for new keywords

Example: Strict mode effects

'use strict';

// 1. Prevents implicit globals
// mistypedVariable = 17; // ReferenceError (without strict: creates global)

// 2. Prevents duplicate parameters
// function sum(a, a, c) { } // SyntaxError (without strict: allowed)

// 3. Makes 'this' undefined in functions
function showThis() {
    console.log(this); // undefined (without strict: window/global)
}
showThis();

// 4. Prevents assignment to non-writable properties
const obj = {};
Object.defineProperty(obj, 'x', { value: 42, writable: false });
// obj.x = 9; // TypeError (without strict: silently fails)

// 5. Prevents deleting variables
let x = 10;
// delete x; // SyntaxError (without strict: returns false)

// 6. Safer eval
eval('var y = 2;');
// console.log(y); // ReferenceError (without strict: y accessible)

// 7. Octal literals banned
// const octal = 0123; // SyntaxError
const octalCorrect = 0o123; // ✓ Use 0o prefix
Note: ES6 modules and classes automatically use strict mode. No need to add 'use strict'; explicitly.

1.6 JavaScript Reserved Words and Keywords

Category Keywords Description
Control Flow if, else, switch, case, default, break, continue, return Conditional execution and flow control
Loops for, while, do, in, of Iteration constructs
Declarations var, let, const, function, class Variable and function definitions
Exception Handling try, catch, finally, throw Error handling constructs
Object-Oriented new, this, super, extends, static OOP keywords
Type/Value null, undefined, true, false, NaN, Infinity Primitive values and special values
Operators typeof, instanceof, void, delete, in Special operators
Module System import, export, from, as, default ES6 module syntax
Async async, await, yield Asynchronous programming keywords
Other debugger, with DEPRECATED Debugging and legacy features
Future Reserved (Strict Mode) Status Notes
implements, interface, package Reserved Reserved for potential future use in strict mode
private, protected, public Reserved Access modifiers reserved for future features
enum Reserved (all modes) Reserved for enumeration type (not yet implemented)
arguments, eval Restricted in strict mode Cannot be used as identifiers or assigned to

Cannot Use as Identifiers:

// ✗ Invalid
let break = 5;
function return() {}
const if = true;
let class = 'myClass';
var new = 'value';

Workarounds:

// ✓ Valid alternatives
let breakPoint = 5;
function returnValue() {}
const condition = true;
let className = 'myClass';
var newValue = 'value';
Warning: While some keywords like undefined and NaN are not technically reserved, they reference global properties and should not be used as variable names to avoid confusion.

Section 1 Summary

  • JavaScript uses various statement types for different purposes (expressions, declarations, blocks)
  • Use let/const over var for block scoping and better error detection
  • Follow naming conventions: camelCase for variables/functions, PascalCase for classes, UPPER_SNAKE_CASE for constants
  • JSDoc comments provide type information and documentation for better IDE support
  • Strict mode catches common mistakes and prevents unsafe actions (auto-enabled in modules/classes)
  • Avoid using reserved keywords as identifiers; they're reserved for language features

2. Data Types and Type System

2.1 Primitive Types (string, number, boolean, null, undefined, symbol, bigint)

Type Description typeof Result Default Value Wrapper Object
string Immutable sequence of Unicode characters "string" "" (empty string) String
number 64-bit IEEE 754 floating point (±1.8×10308) "number" 0 Number
boolean Logical true or false value "boolean" false Boolean
null Intentional absence of value (assignment) "object" BUG N/A None
undefined Variable declared but not assigned "undefined" undefined None
symbol ES6 Unique, immutable identifier for object properties "symbol" N/A Symbol
bigint ES2020 Arbitrary precision integers (beyond 253-1) "bigint" 0n BigInt
Type Literal Syntax Constructor Special Values
string 'text', "text", `template` String("value") Empty string ""
number 42, 3.14, 1e5, 0xFF, 0o77, 0b1010 Number("123") NaN, Infinity, -Infinity
boolean true, false Boolean(value) Only true and false
null null N/A Only null
undefined undefined undefined or void 0 Only undefined
symbol N/A (must use function) Symbol("desc") Each symbol is unique
bigint 42n, 0xFFn, 0o77n, 0b1010n BigInt("123") No decimal values

Example: Primitive type characteristics

// Immutability: primitives are immutable
let str = "hello";
str[0] = "H"; // No effect, strings are immutable
console.log(str); // "hello"

// Primitive vs Wrapper auto-boxing
let num = 42;
console.log(num.toString()); // "42" - temporary wrapper created

// Special number values
console.log(1 / 0);        // Infinity
console.log(-1 / 0);       // -Infinity
console.log(0 / 0);        // NaN
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991

// Symbols are unique
const sym1 = Symbol("desc");
const sym2 = Symbol("desc");
console.log(sym1 === sym2); // false (each is unique)

// BigInt for large integers
const bigNum = 9007199254740991n + 1n;
console.log(bigNum); // 9007199254740992n
Warning: typeof null returns "object" due to a legacy bug. Use value === null for null checking.

2.2 Reference Types (object, array, function)

Type Description typeof Result Mutable Stored By
Object Collection of key-value pairs (properties) "object" ✓ Yes Reference
Array Ordered collection with numeric indices (subtype of Object) "object" ✓ Yes Reference
Function Callable object with executable code "function" ✓ Yes (properties) Reference
Date Represents date and time (milliseconds since epoch) "object" ✓ Yes Reference
RegExp Regular expression pattern matcher "object" ✓ Yes Reference
Map ES6 Key-value pairs with any key type "object" ✓ Yes Reference
Set ES6 Collection of unique values "object" ✓ Yes Reference
Behavior Primitives Reference Types
Assignment Copies the value Copies the reference (both point to same object)
Comparison (===) Compares actual values Compares references (same object?)
Mutability Immutable (cannot change) Mutable (can modify properties)
Storage Stored directly in variable (stack) Variable holds reference pointer (heap)
Passing to Functions Pass by value (copy) Pass by reference (can mutate original)

Example: Primitive vs Reference behavior

// Primitives: value copied
let a = 5;
let b = a; // Copy value
b = 10;
console.log(a); // 5 (unchanged)

// References: pointer copied
let obj1 = { x: 5 };
let obj2 = obj1; // Copy reference
obj2.x = 10;
console.log(obj1.x); // 10 (both point to same object)

// Comparison
let str1 = "hello";
let str2 = "hello";
console.log(str1 === str2); // true (value comparison)

let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false (different references)
console.log(arr1 === arr1); // true (same reference)

// Function parameter behavior
function modifyPrimitive(x) {
    x = 100; // Local copy modified
}
let num = 5;
modifyPrimitive(num);
console.log(num); // 5 (original unchanged)

function modifyObject(obj) {
    obj.value = 100; // Original object modified
}
let data = { value: 5 };
modifyObject(data);
console.log(data.value); // 100 (original changed)

2.3 Type Conversion and Coercion Rules

Target Type From String From Number From Boolean From Object
String Unchanged "5" "true"/"false" toString() or valueOf()
Number 123 or NaN Unchanged 1 / 0 valueOf() then toString()
Boolean "" → false, else true 0, NaN → false, else true Unchanged Always true
Conversion Method Syntax Use Case Example
Explicit String String(x), x.toString(), x + "" Force string conversion String(123) → "123"
Explicit Number Number(x), +x, parseInt(), parseFloat() Force numeric conversion Number("123") → 123
Explicit Boolean Boolean(x), !!x Force boolean conversion Boolean(0) → false
Implicit Coercion Operators: +, -, *, /, == Automatic conversion by operators "5" * 2 → 10
Value to String to Number to Boolean
undefined "undefined" NaN false
null "null" 0 false
true "true" 1 true
false "false" 0 false
"" "" 0 false
"123" "123" 123 true
"abc" "abc" NaN true
0, -0 "0" 0 false
NaN "NaN" NaN false
Infinity "Infinity" Infinity true
{}, [] "[object Object]", "" NaN, 0 true

Example: Type coercion in action

// String coercion (+ with string)
console.log("5" + 3);      // "53" (number to string)
console.log("5" + true);   // "5true"

// Numeric coercion (other operators)
console.log("5" - 3);      // 2 (string to number)
console.log("5" * "2");    // 10
console.log(true + 1);     // 2 (true → 1)
console.log(false + 1);    // 1 (false → 0)

// Boolean coercion
console.log(Boolean(""));    // false
console.log(Boolean(0));     // false
console.log(Boolean(NaN));   // false
console.log(Boolean(null));  // false
console.log(Boolean(undefined)); // false
console.log(Boolean([]));    // true (object)
console.log(Boolean({}));    // true (object)

// Explicit conversions
console.log(Number("123"));     // 123
console.log(Number("12.5"));    // 12.5
console.log(Number("abc"));     // NaN
console.log(parseInt("123px")); // 123 (parses until non-digit)
console.log(String(123));       // "123"
console.log(+"42");             // 42 (unary +)

// Falsy values (6 total)
if (!undefined || !null || !0 || !NaN || !"" || !false) {
    console.log("All falsy values");
}
Falsy Values (6 total): false, 0, "", null, undefined, NaN. Everything else is truthy (including [], {}, "0", "false").

2.4 Type Checking (typeof, instanceof, Array.isArray)

Operator/Method Syntax Returns Use Case Limitations
typeof typeof value String type name Check primitive types typeof null === "object" (bug); arrays return "object"
instanceof obj instanceof Constructor Boolean Check prototype chain Doesn't work across frames/windows; primitives always false
Array.isArray() Array.isArray(value) Boolean Reliable array detection Only for arrays
Object.prototype.toString Object.prototype.toString.call(value) "[object Type]" Most reliable type checking Verbose syntax
constructor value.constructor === Type Boolean Check constructor Can be overridden; fails for null/undefined
Value typeof instanceof Best Detection Method
42 "number" N/A (primitive) typeof x === "number"
"text" "string" N/A (primitive) typeof x === "string"
true "boolean" N/A (primitive) typeof x === "boolean"
null "object" ⚠️ false x === null
undefined "undefined" false x === undefined or typeof x === "undefined"
[] "object" true (Array) Array.isArray(x)
{} "object" true (Object) typeof x === "object" && x !== null
function(){} "function" true (Function) typeof x === "function"
Symbol() "symbol" N/A (primitive) typeof x === "symbol"
42n "bigint" N/A (primitive) typeof x === "bigint"

Example: Type checking techniques

// typeof: basic primitive checking
console.log(typeof 42);           // "number"
console.log(typeof "hello");      // "string"
console.log(typeof true);         // "boolean"
console.log(typeof undefined);    // "undefined"
console.log(typeof Symbol());     // "symbol"
console.log(typeof 42n);          // "bigint"
console.log(typeof null);         // "object" ⚠️ (legacy bug)
console.log(typeof []);           // "object"
console.log(typeof {});           // "object"
console.log(typeof function(){}); // "function"

// instanceof: prototype chain checking
console.log([] instanceof Array);       // true
console.log({} instanceof Object);      // true
console.log(new Date() instanceof Date); // true
console.log(42 instanceof Number);      // false (primitive)

// Array.isArray: reliable array detection
console.log(Array.isArray([]));         // true
console.log(Array.isArray({}));         // false
console.log(Array.isArray("array"));    // false

// Object.prototype.toString: most reliable
const toString = Object.prototype.toString;
console.log(toString.call([]));         // "[object Array]"
console.log(toString.call({}));         // "[object Object]"
console.log(toString.call(null));       // "[object Null]"
console.log(toString.call(undefined));  // "[object Undefined]"
console.log(toString.call(new Date())); // "[object Date]"
console.log(toString.call(/regex/));    // "[object RegExp]"

// Utility type checking function
function getType(value) {
    if (value === null) return 'null';
    if (Array.isArray(value)) return 'array';
    return typeof value;
}
console.log(getType(null));  // "null"
console.log(getType([]));    // "array"
console.log(getType(42));    // "number"

2.5 Symbol Type and Well-known Symbols

Feature Description Syntax Use Case
Symbol Creation Creates unique identifier Symbol("description") Unique object property keys
Symbol Registry Global shared symbols Symbol.for("key") Cross-realm symbol sharing
Symbol.keyFor() Get key from registry symbol Symbol.keyFor(sym) Retrieve symbol key name
Symbol Properties Hidden from enumeration obj[sym] = value Private-like properties (not truly private)
Well-known Symbol Purpose Usage
Symbol.iterator Defines default iterator Used by for...of loops, spread operator
Symbol.asyncIterator Defines async iterator Used by for await...of loops
Symbol.toStringTag Customizes toString() output Changes Object.prototype.toString.call() result
Symbol.toPrimitive Defines type coercion behavior Controls conversion to primitive types
Symbol.hasInstance Customizes instanceof Overrides instanceof behavior
Symbol.species Constructor for derived objects Used in Array/Promise methods
Symbol.match, matchAll String matching behavior Used by String.prototype.match()
Symbol.replace, search, split String manipulation behavior Used by respective string methods

Example: Symbol usage and well-known symbols

// Basic symbol creation (each is unique)
const sym1 = Symbol("id");
const sym2 = Symbol("id");
console.log(sym1 === sym2); // false (unique)

// Symbols as object properties
const id = Symbol("id");
const user = {
    name: "John",
    [id]: 123 // Symbol as computed property
};
console.log(user[id]); // 123
console.log(Object.keys(user)); // ["name"] (symbol hidden)

// Symbol registry (shared across realm)
const globalSym1 = Symbol.for("app.id");
const globalSym2 = Symbol.for("app.id");
console.log(globalSym1 === globalSym2); // true (same symbol)
console.log(Symbol.keyFor(globalSym1)); // "app.id"

// Well-known symbols: Symbol.iterator
const iterable = {
    data: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => ({
                value: this.data[index],
                done: index++ >= this.data.length
            })
        };
    }
};
console.log([...iterable]); // [1, 2, 3]

// Symbol.toStringTag
class CustomClass {
    get [Symbol.toStringTag]() {
        return "CustomClass";
    }
}
const obj = new CustomClass();
console.log(Object.prototype.toString.call(obj)); // "[object CustomClass]"

// Symbol.toPrimitive
const obj2 = {
    [Symbol.toPrimitive](hint) {
        if (hint === "number") return 42;
        if (hint === "string") return "hello";
        return true;
    }
};
console.log(+obj2);     // 42 (number hint)
console.log(`${obj2}`); // "hello" (string hint)
console.log(obj2 + ""); // "true" (default hint)

2.6 BigInt for Large Integer Operations

Feature Syntax Description Example
BigInt Literal value + "n" Append 'n' to integer literal 123n, 0xFFn, 0o77n, 0b1010n
BigInt Constructor BigInt(value) Convert number/string to BigInt BigInt("9007199254740991")
Range Arbitrary precision No upper/lower limit (memory-limited) Can represent integers beyond 253-1
Type typeof x Returns "bigint" typeof 42n === "bigint"
Operation BigInt Support Example Notes
Arithmetic +, -, *, /, %, ** 10n + 20n = 30n Both operands must be BigInt
Comparison <, >, <=, >=, ==, != 10n < 20n, 10n == 10 == coerces; === is strict
Bitwise &, |, ^, ~, <<, >> 5n & 3n = 1n >>> not supported (unsigned)
Division / (integer division) 7n / 2n = 3n Truncates decimal (rounds toward zero)
Unary Minus -x -42n = -42n Negation supported
Unary Plus ❌ Not supported +42n ❌ TypeError Use Number() to convert
Limitation Description Workaround
No Mixed Operations Cannot mix BigInt with Number Explicitly convert: 10n + BigInt(5) or Number(10n) + 5
No Decimal Values BigInt only for integers Use Number for decimal/floating-point
Math Object Math.* methods don't support BigInt Implement custom logic or convert to Number
JSON Serialization JSON.stringify() throws TypeError Custom replacer: JSON.stringify(obj, (k,v) => typeof v === 'bigint' ? v.toString() : v)
Implicit Coercion No automatic type coercion Always explicit conversion required

Example: BigInt operations and limitations

// Creating BigInts
const big1 = 9007199254740991n;
const big2 = BigInt("9007199254740991");
const big3 = BigInt(Number.MAX_SAFE_INTEGER);

// Arithmetic operations
console.log(100n + 50n);   // 150n
console.log(100n - 50n);   // 50n
console.log(100n * 2n);    // 200n
console.log(100n / 3n);    // 33n (integer division, truncates)
console.log(100n % 3n);    // 1n
console.log(2n ** 100n);   // Very large number (2^100)

// Comparison
console.log(10n === 10);   // false (strict equality)
console.log(10n == 10);    // true (loose equality with coercion)
console.log(10n < 20n);    // true
console.log(5n > 3);       // true (coercion in comparison)

// Limitations
// console.log(10n + 5);   // ❌ TypeError: Cannot mix BigInt and Number
console.log(10n + BigInt(5)); // ✓ 15n (explicit conversion)
console.log(Number(10n) + 5); // ✓ 15 (convert to Number)

// console.log(+10n);      // ❌ TypeError: Cannot convert BigInt
console.log(Number(10n));  // ✓ 10 (explicit conversion)

// Bitwise operations
console.log(5n & 3n);     // 1n (binary AND)
console.log(5n | 3n);     // 7n (binary OR)
console.log(5n ^ 3n);     // 6n (binary XOR)
console.log(5n << 2n);    // 20n (left shift)

// Use case: Precise large integer calculations
const largeNumber = 9007199254740992n; // Beyond Number.MAX_SAFE_INTEGER
console.log(largeNumber + 1n); // 9007199254740993n (exact)
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992 (precision lost)
Warning: BigInt cannot be mixed with regular numbers in arithmetic operations. Always explicitly convert using BigInt() or Number().

Section 2 Summary

  • JavaScript has 7 primitive types: string, number, boolean, null, undefined, symbol, bigint
  • Primitives are immutable and passed by value; reference types are mutable and passed by reference
  • Type coercion happens implicitly with operators; 6 falsy values exist: false, 0, "", null, undefined, NaN
  • Use typeof for primitives, Array.isArray() for arrays, and instanceof for prototype checking
  • Symbols provide unique identifiers for object properties; well-known symbols customize language behavior
  • BigInt enables arbitrary-precision integer math beyond Number.MAX_SAFE_INTEGER (253-1)

3. Variables, Scope, and Closure

3.1 Variable Hoisting and Temporal Dead Zone

Declaration Hoisting Behavior Initialization TDZ Access Before Declaration
var Hoisted to function/global scope Initialized to undefined ❌ No TDZ Returns undefined
let Hoisted to block scope Not initialized ✓ Has TDZ ReferenceError
const Hoisted to block scope Not initialized ✓ Has TDZ ReferenceError
function (declaration) Fully hoisted with definition Fully initialized ❌ No TDZ Callable before declaration
function (expression) Variable hoisted only Depends on var/let/const Depends on var/let/const Not callable (undefined or TDZ)
class Hoisted to block scope Not initialized ✓ Has TDZ ReferenceError
Concept Description Key Points
Hoisting JavaScript moves declarations to top of their scope during compilation Only declarations hoisted, not initializations
Temporal Dead Zone (TDZ) Time between entering scope and variable initialization where access causes error Exists for let, const, and class
TDZ Start Beginning of enclosing block scope Not visible in code; conceptual time period
TDZ End Point where variable is declared and initialized Variable becomes usable after this point

Example: Hoisting and TDZ behavior

// var hoisting: declaration hoisted, initialized to undefined
console.log(x); // undefined (no error)
var x = 5;
console.log(x); // 5

// let/const TDZ: declaration hoisted but not initialized
// console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
console.log(y); // 10

// Function declaration: fully hoisted
greet(); // "Hello" (works before declaration)
function greet() {
    console.log("Hello");
}

// Function expression: only variable hoisted
// sayHi(); // TypeError: sayHi is not a function
var sayHi = function() {
    console.log("Hi");
};
sayHi(); // "Hi"

// TDZ example with block scope
{
    // TDZ starts here for 'temp'
    // console.log(temp); // ReferenceError
    // const result = doSomething(temp); // ReferenceError
    
    let temp = 5; // TDZ ends here
    console.log(temp); // 5 (now accessible)
}

// Class TDZ
// const p = new Person(); // ReferenceError
class Person {
    constructor(name) { this.name = name; }
}
const p = new Person("John"); // Works after declaration
Note: TDZ prevents using variables before initialization, catching potential bugs. Always declare variables at the top of their scope for clarity.

3.2 Scope Types (Global, Function, Block, Module)

Scope Type Created By Variables Lifetime Access
Global Scope Outside all functions/blocks var, let, const, functions Entire program execution Accessible everywhere
Function Scope Function declarations/expressions var, parameters, inner functions Function execution Within function and nested functions
Block Scope { } braces, loops, if/switch let, const, class Block execution Within block only
Module Scope ES6 modules (.js files with import/export) All declarations in module file Module lifetime Isolated; explicit exports required
Catch Block Scope catch(error) { } Error parameter, let, const Catch block execution Within catch block only
Declaration Global Scope Function Scope Block Scope
var ✓ Function or global ✓ Function-scoped ❌ Ignores blocks
let ✓ Global (no window property) ✓ Function-scoped ✓ Block-scoped
const ✓ Global (no window property) ✓ Function-scoped ✓ Block-scoped
function ✓ Global ✓ Function-scoped ❌ Block-scoped in strict mode only

Example: Different scope types

// Global scope
var globalVar = 'global var';
let globalLet = 'global let';
const globalConst = 'global const';

function demoScopes() {
    // Function scope
    var functionVar = 'function scoped';
    let functionLet = 'function scoped';
    
    console.log(globalVar); // Accessible
    
    if (true) {
        // Block scope
        var blockVar = 'not block scoped (var)';
        let blockLet = 'block scoped';
        const blockConst = 'block scoped';
        
        console.log(functionVar); // Accessible from outer function
        console.log(blockLet);    // Accessible within block
    }
    
    console.log(blockVar);  // Accessible (var ignores blocks)
    // console.log(blockLet); // ReferenceError (block-scoped)
    
    // Loop scope
    for (let i = 0; i < 3; i++) {
        // 'i' is block-scoped to this loop
        setTimeout(() => console.log(i), 100); // Prints 0, 1, 2
    }
    
    for (var j = 0; j < 3; j++) {
        // 'j' is function-scoped (shared)
        setTimeout(() => console.log(j), 100); // Prints 3, 3, 3
    }
}

// Module scope (in ES6 modules)
// Variables not automatically global
// export const moduleVar = 'exported';
// const privateVar = 'not exported';

// Catch block scope
try {
    throw new Error('test');
} catch (error) {
    // 'error' is scoped to catch block
    let catchVar = 'catch scoped';
    console.log(error.message);
}
// console.log(error); // ReferenceError
// console.log(catchVar); // ReferenceError
Warning: Global variables create properties on the global object (window in browsers) with var, but not with let/const. Avoid global variables to prevent naming conflicts.

3.3 Lexical Scope and Scope Chain

Concept Description Behavior
Lexical Scope Scope determined by code location (where functions are written) Inner functions access outer function variables
Scope Chain Hierarchy of nested scopes searched for variable resolution Searches from inner → outer until found or reaches global
Static Scoping Another name for lexical scoping (opposite of dynamic scoping) Scope determined at write-time, not runtime
Outer Environment Reference Link from execution context to parent scope Created when function is defined, not when called
Variable Lookup Process Step Action
Step 1 Current Scope Check if variable exists in current local scope
Step 2 Parent Scope If not found, move to enclosing (parent) scope
Step 3 Repeat Continue up the scope chain through ancestors
Step 4 Global Scope Check global scope as last resort
Step 5 Not Found ReferenceError if not found anywhere in chain

Example: Lexical scope and scope chain

// Scope chain demonstration
const globalVar = 'global';

function outer() {
    const outerVar = 'outer';
    
    function middle() {
        const middleVar = 'middle';
        
        function inner() {
            const innerVar = 'inner';
            
            // Scope chain: inner → middle → outer → global
            console.log(innerVar);   // Found in inner scope
            console.log(middleVar);  // Found in middle scope (parent)
            console.log(outerVar);   // Found in outer scope (grandparent)
            console.log(globalVar);  // Found in global scope
            
            // Variable lookup order:
            // 1. Check inner scope
            // 2. Check middle scope
            // 3. Check outer scope
            // 4. Check global scope
            // 5. ReferenceError if not found
        }
        
        inner();
        // console.log(innerVar); // ReferenceError (not in scope chain)
    }
    
    middle();
}

outer();

// Lexical scope is determined by code structure
function makeCounter() {
    let count = 0; // In outer function scope
    
    return function() {
        // Lexically scoped to access 'count' from parent
        return ++count;
    };
}

const counter1 = makeCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2

const counter2 = makeCounter();
console.log(counter2()); // 1 (separate scope chain)

// Scope determined at definition, not invocation
let x = 'global x';

function showX() {
    console.log(x); // Lexically bound to global 'x'
}

function demo() {
    let x = 'local x';
    showX(); // Prints "global x" (not "local x")
             // Scope determined where showX was defined
}

demo();
Note: Lexical scope is determined by where the function is written, not where it's called. This enables closures and predictable variable access.

3.4 Variable Shadowing and Name Resolution

Concept Description Behavior Recommendation
Variable Shadowing Inner variable hides outer variable with same name Inner scope variable takes precedence Avoid shadowing; use different names
Name Resolution Process of finding which variable identifier refers to Searches scope chain from inner to outer Use descriptive, unique names
Parameter Shadowing Function parameter shadows outer variable Parameter accessible, outer variable hidden Common pattern; usually intentional
Block Shadowing Block-scoped variable shadows outer scope Only within block; outer restored after Use for temporary local variables
Shadowing Type Outer Inner Allowed? Behavior
var shadows var var var ✓ Yes Redeclaration in same scope or shadowing in nested
let/const shadows var var let/const ✓ Yes Inner hides outer; outer unchanged
var shadows let/const let/const var Depends Error if same function scope; OK if nested function
let/const shadows let/const let/const let/const ✓ In different block ✗ SyntaxError in same block
Parameter shadowing Any Parameter ✓ Yes Parameter has priority in function

Example: Variable shadowing scenarios

// Basic shadowing
let x = 'outer';

function demo() {
    let x = 'inner'; // Shadows outer 'x'
    console.log(x);  // "inner"
}

demo();
console.log(x); // "outer" (outer unchanged)

// Parameter shadowing
let value = 100;

function processValue(value) { // Parameter shadows outer 'value'
    console.log(value); // Uses parameter, not outer variable
    value = 200;        // Modifies parameter only
}

processValue(50);  // Prints 50
console.log(value); // 100 (outer unchanged)

// Block shadowing
let count = 10;

if (true) {
    let count = 20; // Shadows outer 'count' in block
    console.log(count); // 20
}

console.log(count); // 10 (outer restored after block)

// Nested function shadowing
function outer() {
    let name = 'outer';
    
    function inner() {
        let name = 'inner'; // Shadows parent function's 'name'
        console.log(name);  // "inner"
    }
    
    inner();
    console.log(name); // "outer"
}

outer();

// Loop variable shadowing
for (let i = 0; i < 3; i++) {
    console.log(i); // Loop 'i'
    
    for (let i = 0; i < 2; i++) {
        console.log('  ' + i); // Inner loop 'i' shadows outer
    }
}

// Problematic shadowing to avoid
function calculateTotal() {
    let total = 0;
    
    if (true) {
        let total = 100; // Shadowing can be confusing
        // Which 'total' is which?
    }
    
    return total; // Returns 0, not 100
}
Warning: While shadowing is allowed, excessive use can make code confusing. Use distinct variable names for clarity unless shadowing is intentional (e.g., function parameters).

3.5 Closure Patterns and Memory Management

Concept Definition Key Characteristics
Closure Function bundled with its lexical environment (surrounding state) Retains access to outer scope variables even after outer function returns
Closure Creation Created every time a function is created Inner function "closes over" variables from outer scope
Closure Use Cases Data privacy, factory functions, callbacks, event handlers Maintains state between function calls
Memory Retention Closed-over variables remain in memory Can cause memory leaks if not managed properly
Closure Pattern Use Case Example Structure
Private Variables Encapsulation, data hiding Function returns methods that access private variables
Factory Functions Create objects with private state Function returns object with methods (closures)
Module Pattern Namespace management, public/private API IIFE returns object with public methods
Callbacks/Event Handlers Preserve context for async operations Function captures variables for later execution
Currying/Partial Application Function transformation, configuration Function returns function with captured arguments
Memoization Performance optimization, caching Closure maintains cache of computed values

Example: Common closure patterns

// 1. Private variables pattern
function createCounter() {
    let count = 0; // Private variable
    
    return {
        increment: () => ++count,
        decrement: () => --count,
        getCount: () => count
    };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount());  // 2
// console.log(counter.count); // undefined (private)

// 2. Factory function pattern
function createUser(name) {
    let _name = name; // Private
    let _sessions = 0;
    
    return {
        getName: () => _name,
        login: () => ++_sessions,
        getSessions: () => _sessions
    };
}

const user1 = createUser("Alice");
const user2 = createUser("Bob");
user1.login();
console.log(user1.getSessions()); // 1
console.log(user2.getSessions()); // 0

// 3. Module pattern with IIFE
const calculator = (function() {
    let history = []; // Private state
    
    return {
        add: (a, b) => {
            const result = a + b;
            history.push(`${a} + ${b} = ${result}`);
            return result;
        },
        getHistory: () => [...history] // Return copy
    };
})();

calculator.add(5, 3);
console.log(calculator.getHistory()); // ["5 + 3 = 8"]

// 4. Callback closure preserving context
function fetchDataWithRetry(url, retries) {
    let attempts = 0; // Captured by callback
    
    function attemptFetch() {
        fetch(url)
            .catch(error => {
                if (++attempts < retries) {
                    console.log(`Retry ${attempts}`);
                    attemptFetch(); // Closure accesses 'attempts'
                }
            });
    }
    
    attemptFetch();
}

// 5. Currying with closures
function multiply(a) {
    return function(b) {
        return function(c) {
            return a * b * c;
        };
    };
}

const multiplyBy2 = multiply(2);
const multiplyBy2And3 = multiplyBy2(3);
console.log(multiplyBy2And3(4)); // 24

// 6. Memoization pattern
function memoize(fn) {
    const cache = {}; // Closure maintains cache
    
    return function(...args) {
        const key = JSON.stringify(args);
        if (key in cache) {
            return cache[key];
        }
        const result = fn(...args);
        cache[key] = result;
        return result;
    };
}

const factorial = memoize(n => n <= 1 ? 1 : n * factorial(n - 1));
console.log(factorial(5)); // Computed: 120
console.log(factorial(5)); // Cached: 120
Memory Consideration Issue Solution
Retained Variables Closed-over variables can't be garbage collected Clear references when done (obj = null)
Event Listeners Closure in listener prevents GC of entire scope Remove listeners: removeEventListener
Timers/Intervals Callback closures keep references alive Clear timers: clearTimeout/clearInterval
Large Data Structures Closure inadvertently captures large objects Extract only needed data before creating closure
Note: Closures are powerful but retain references to their outer scope. Be mindful of memory usage, especially with long-lived closures or large captured variables.

3.6 Module Scope and Variable Isolation

Module Feature Description Benefit
Module Scope Each ES6 module has its own scope (file-level) Variables don't pollute global scope by default
Variable Isolation Module variables are private unless explicitly exported Prevents naming conflicts between modules
Strict Mode Modules automatically run in strict mode No need for 'use strict'; directive
Top-level this this is undefined at module top level Not bound to global object (safer)
Static Imports Import declarations hoisted and executed first Enables static analysis and tree-shaking
Module Pattern Syntax Use Case
Named Export export const x = 1; Export multiple items from module
Default Export export default function() {} Main export from module (one per module)
Named Import import { x, y } from './mod'; Import specific exports
Default Import import mod from './mod'; Import default export
Namespace Import import * as mod from './mod'; Import all exports as object
Re-export export { x } from './mod'; Export from another module (barrel pattern)
Dynamic Import import('./mod').then(...) Load modules conditionally/lazily

Example: Module scope and isolation

// ===== math.js (Module A) =====
// Private variables (not exported)
const PI = 3.14159;
let calculationCount = 0;

// Private helper function
function log(operation) {
    calculationCount++;
    console.log(`Operation: ${operation}, Count: ${calculationCount}`);
}

// Named exports (public API)
export function add(a, b) {
    log('add');
    return a + b;
}

export function multiply(a, b) {
    log('multiply');
    return a * b;
}

export const TAU = 2 * PI; // Export constant

// Default export
export default function calculate(expr) {
    return eval(expr); // Example only
}

// ===== user.js (Module B) =====
// Module variables are isolated
const userCount = 0; // Won't conflict with other modules

class User {
    constructor(name) {
        this.name = name;
    }
}

export { User, userCount };

// ===== app.js (Main module) =====
// Import from math.js
import calculate, { add, multiply, TAU } from './math.js';
// Import from user.js
import { User, userCount } from './user.js';

// Module-scoped variables (private to this module)
const appName = 'MyApp';
let config = { debug: true };

console.log(add(5, 3));        // 8
console.log(multiply(2, 4));   // 8
console.log(TAU);              // 6.28318

// Cannot access private variables from math.js
// console.log(PI); // ReferenceError
// console.log(calculationCount); // ReferenceError

const user = new User('Alice');

// Top-level 'this' is undefined in modules
console.log(this); // undefined (not window)

// ===== Dynamic imports =====
async function loadModule() {
    if (config.debug) {
        const debugModule = await import('./debug.js');
        debugModule.log('Debug mode enabled');
    }
}

// ===== Barrel export pattern (index.js) =====
// Re-export from multiple modules
export { add, multiply } from './math.js';
export { User } from './user.js';
export { default as calculate } from './math.js';

Module Benefits:

  • Encapsulation: private implementation details
  • Explicit dependencies: clear import statements
  • No global pollution: isolated scope
  • Reusability: modular, composable code
  • Static analysis: enables tree-shaking

Before Modules (Legacy):

// IIFE pattern for isolation
(function() {
    var private = 'hidden';
    
    window.MyModule = {
        public: function() {
            return private;
        }
    };
})();
Note: ES6 modules provide true encapsulation with file-level scope. Variables are private by default, eliminating need for IIFE patterns and reducing global scope pollution.

Section 3 Summary

  • Hoisting moves declarations to top of scope; let/const/class have TDZ (ReferenceError before initialization)
  • 4 scope types: Global (everywhere), Function (var), Block (let/const), Module (file-level isolation)
  • Lexical scope determined by code structure; scope chain searches inner → outer → global for variables
  • Variable shadowing occurs when inner scope variable hides outer one; use distinct names for clarity
  • Closures retain access to outer scope variables; enable private state but require memory management
  • ES6 modules provide true isolation with file-level scope; variables private unless exported

4. Operators and Expressions Reference

4.1 Arithmetic Operators and Math Operations

Operator Name Description Example Result
+ Addition / Unary Plus Adds numbers or concatenates strings; converts to number 5 + 3, +"42" 8, 42
- Subtraction / Unary Minus Subtracts numbers; negates value 5 - 3, -5 2, -5
* Multiplication Multiplies numbers 5 * 3 15
/ Division Divides numbers (floating-point result) 10 / 3 3.3333...
% Remainder / Modulo Returns remainder of division 10 % 3 1
** Exponentiation ES2016 Raises to power 2 ** 3 8
++ Increment Increases by 1 (prefix or postfix) ++x, x++ Pre: return new; Post: return old
-- Decrement Decreases by 1 (prefix or postfix) --x, x-- Pre: return new; Post: return old
Operation Type Behavior Special Cases
Number + Number Numeric addition 5 + 3 = 8
String + Any String concatenation "5" + 3 = "53", "a" + true = "atrue"
Division by Zero Returns Infinity 5 / 0 = Infinity, -5 / 0 = -Infinity
0 / 0 Returns NaN Indeterminate result
NaN in Operations Propagates NaN NaN + 5 = NaN, NaN * 2 = NaN
Infinity Operations Mathematical infinity rules Infinity + 1 = Infinity, Infinity * -1 = -Infinity

Example: Arithmetic operators and edge cases

// Basic arithmetic
console.log(10 + 5);   // 15
console.log(10 - 5);   // 5
console.log(10 * 5);   // 50
console.log(10 / 5);   // 2
console.log(10 % 3);   // 1 (remainder)
console.log(2 ** 3);   // 8 (exponentiation)

// Increment/Decrement
let x = 5;
console.log(x++);      // 5 (returns old value, then increments)
console.log(x);        // 6
console.log(++x);      // 7 (increments, then returns new value)
console.log(x--);      // 7 (returns old value, then decrements)
console.log(--x);      // 5 (decrements, then returns new value)

// String concatenation with +
console.log("Hello" + " " + "World");  // "Hello World"
console.log("5" + 3);    // "53" (string concatenation)
console.log(5 + "3");    // "53"
console.log("5" + 3 + 2); // "532"
console.log(5 + 3 + "2"); // "82" (left to right: 5+3=8, then 8+"2"="82")

// Type coercion with other operators
console.log("10" - 5);   // 5 (string to number)
console.log("10" * "2"); // 20
console.log("10" / "2"); // 5
console.log("10" % 3);   // 1

// Unary operators
console.log(+"42");      // 42 (string to number)
console.log(+"abc");     // NaN
console.log(-"5");       // -5

// Special values
console.log(5 / 0);      // Infinity
console.log(-5 / 0);     // -Infinity
console.log(0 / 0);      // NaN
console.log(NaN + 10);   // NaN (NaN propagates)
console.log(Infinity - 1); // Infinity

4.2 Assignment Operators and Compound Assignment

Operator Name Equivalent Example Result
= Simple Assignment N/A x = 5 x is 5
+= Addition Assignment x = x + y x += 3 Adds and assigns
-= Subtraction Assignment x = x - y x -= 3 Subtracts and assigns
*= Multiplication Assignment x = x * y x *= 3 Multiplies and assigns
/= Division Assignment x = x / y x /= 3 Divides and assigns
%= Remainder Assignment x = x % y x %= 3 Remainder and assigns
**= Exponentiation Assignment x = x ** y x **= 3 Exponentiates and assigns
<<= Left Shift Assignment x = x << y x <<= 2 Left shift and assigns
>>= Right Shift Assignment x = x >> y x >>= 2 Right shift and assigns
>>>= Unsigned Right Shift Assignment x = x >>> y x >>>= 2 Unsigned shift and assigns
&= Bitwise AND Assignment x = x & y x &= 3 AND and assigns
|= Bitwise OR Assignment x = x | y x |= 3 OR and assigns
^= Bitwise XOR Assignment x = x ^ y x |= 3 XOR and assigns
Feature Description Example
Destructuring Assignment Unpack values from arrays/objects [a, b] = [1, 2], {x, y} = obj
Multiple Assignment Chain assignments (right-to-left) a = b = c = 5 (all get 5)
Return Value Assignment returns assigned value y = (x = 5) (both are 5)
Compound Benefits Shorter, clearer, evaluates left side once arr[i++] += 5 increments i once

Example: Assignment operators

// Simple assignment
let x = 10;

// Compound assignments
x += 5;   // x = x + 5;  x is now 15
x -= 3;   // x = x - 3;  x is now 12
x *= 2;   // x = x * 2;  x is now 24
x /= 4;   // x = x / 4;  x is now 6
x %= 5;   // x = x % 5;  x is now 1
x **= 3;  // x = x ** 3; x is now 1

// String concatenation with +=
let str = "Hello";
str += " World";  // str is "Hello World"

// Multiple assignment (right-to-left)
let a, b, c;
a = b = c = 5;  // All are 5
console.log(a, b, c);  // 5 5 5

// Assignment returns value
let y = (x = 10);  // x is 10, y is 10
console.log(x, y);  // 10 10

// Destructuring assignment
let [p, q] = [1, 2];
console.log(p, q);  // 1 2

let {name, age} = {name: "John", age: 30};
console.log(name, age);  // "John" 30

// Compound assignment with side effects
let arr = [10, 20, 30];
let i = 0;
arr[i++] += 5;  // arr[0] becomes 15, then i increments
console.log(arr[0], i);  // 15 1

// Swapping with destructuring
let m = 1, n = 2;
[m, n] = [n, m];  // Swap
console.log(m, n);  // 2 1

4.3 Comparison Operators and Equality Rules

Operator Name Description Type Coercion Example
== Equality (loose) Checks value equality with type coercion ✓ Yes 5 == "5"true
=== Strict Equality Checks value and type equality ✗ No 5 === "5"false
!= Inequality (loose) Checks value inequality with coercion ✓ Yes 5 != "6"true
!== Strict Inequality Checks value or type inequality ✗ No 5 !== "5"true
< Less Than Checks if left < right ✓ Yes 5 < 10true
> Greater Than Checks if left > right ✓ Yes 10 > 5true
<= Less Than or Equal Checks if left ≤ right ✓ Yes 5 <= 5true
>= Greater Than or Equal Checks if left ≥ right ✓ Yes 10 >= 10true
Comparison == (loose) === (strict) Reason
5 vs "5" true false String coerced to number
0 vs false true false Boolean coerced to number
null vs undefined true false Special case: only equal to each other
"" vs 0 true false Empty string coerced to 0
NaN vs NaN false false NaN not equal to itself (use isNaN())
[] vs [] false false Different object references
0 vs -0 true true Considered equal (use Object.is() to distinguish)

Example: Equality and comparison operators

// Strict equality (===) - recommended
console.log(5 === 5);        // true
console.log(5 === "5");      // false (different types)
console.log(null === null);  // true
console.log(undefined === undefined); // true

// Loose equality (==) - with type coercion
console.log(5 == "5");       // true (string coerced to number)
console.log(0 == false);     // true (boolean to number)
console.log("" == 0);        // true (string to number)
console.log(null == undefined); // true (special case)

// Inequality
console.log(5 !== "5");      // true (strict)
console.log(5 != "6");       // true (loose)

// Relational operators
console.log(5 < 10);         // true
console.log(10 > 5);         // true
console.log(5 <= 5);         // true
console.log(10 >= 10);       // true

// String comparison (lexicographic)
console.log("apple" < "banana"); // true (a < b)
console.log("10" < "9");         // true (string comparison: "1" < "9")
console.log(10 < "9");           // false (number comparison: 10 > 9)

// Special cases
console.log(NaN === NaN);     // false (use Number.isNaN())
console.log(NaN == NaN);      // false
console.log(Number.isNaN(NaN)); // true (correct way)

console.log(0 === -0);        // true
console.log(Object.is(0, -0)); // false (distinguishes +0 and -0)

// Object comparison (reference)
const obj1 = {a: 1};
const obj2 = {a: 1};
const obj3 = obj1;
console.log(obj1 === obj2);  // false (different references)
console.log(obj1 === obj3);  // true (same reference)

// Array comparison
console.log([1,2] == [1,2]); // false (different references)
console.log([1,2] === [1,2]); // false

// Tricky comparisons to avoid
console.log([] == ![]);      // true (![] is false, [] coerced to "")
console.log([] == false);    // true
console.log("" == 0);        // true
console.log(" " == 0);       // true
console.log("0" == 0);       // true
console.log("0" == false);   // true
Warning: Always use === and !== (strict equality) to avoid unexpected type coercion bugs. Use == only when you specifically need type coercion.

4.4 Logical Operators and Short-circuit Evaluation

Operator Name Returns Short-circuits Use Case
&& Logical AND First falsy value or last value ✓ If left is falsy Both conditions must be true
|| Logical OR First truthy value or last value ✓ If left is truthy At least one condition must be true
! Logical NOT Boolean (inverted) ✗ No Inverts truthiness
!! Double NOT Boolean conversion ✗ No Convert to boolean explicitly
Expression Result Explanation
true && true true Both truthy → returns last value
true && false false Right is falsy → returns false
false && true false Short-circuits at false (left)
5 && "hello" "hello" Both truthy → returns last
0 && "hello" 0 First falsy → returns 0
true || false true Short-circuits at first truthy
false || true true Returns first truthy value
false || false false Both falsy → returns last
5 || "hello" 5 First truthy → short-circuits
0 || "hello" "hello" First falsy, returns second
!true false Inverts boolean
!0 true 0 is falsy, inverted to true
!!"hello" true Convert to boolean: truthy

Example: Logical operators and short-circuit evaluation

// Logical AND (&&) - returns first falsy or last value
console.log(true && true);        // true
console.log(true && false);       // false
console.log(5 && 10);             // 10 (both truthy, returns last)
console.log(0 && 10);             // 0 (first falsy)
console.log(null && "hello");     // null (first falsy)

// Logical OR (||) - returns first truthy or last value
console.log(false || true);       // true
console.log(false || false);      // false (last value)
console.log(5 || 10);             // 5 (first truthy)
console.log(0 || 10);             // 10 (first falsy, returns second)
console.log(null || "default");   // "default" (first falsy)

// Logical NOT (!)
console.log(!true);               // false
console.log(!false);              // true
console.log(!0);                  // true (0 is falsy)
console.log(!"");                 // true ("" is falsy)
console.log(!"hello");            // false ("hello" is truthy)

// Double NOT (!!) - convert to boolean
console.log(!!"hello");           // true
console.log(!!0);                 // false
console.log(!!"");                // false
console.log(!!{});                // true (objects are truthy)

// Short-circuit evaluation - practical uses

// 1. Default values (before ?? was added)
let username = input || "Guest";  // If input is falsy, use "Guest"

// 2. Conditional execution
isLoggedIn && showDashboard();    // Execute if truthy
hasError || showSuccessMessage(); // Execute if falsy

// 3. Guard clauses
function processUser(user) {
    user && user.profile && console.log(user.profile.name);
    // Only accesses nested property if all are truthy
}

// 4. Avoiding function calls
let result = expensiveCheck() || cachedValue;
// If expensiveCheck() is truthy, cachedValue never evaluated

// Short-circuit prevents errors
let obj = null;
let value = obj && obj.property;  // undefined (doesn't throw)
// Without short-circuit: obj.property would throw error

// Combining operators
let access = isAdmin || (isPremium && hasPermission);
console.log(true || (false && error())); // true, error() not called

// Falsy values: false, 0, "", null, undefined, NaN
console.log(false || 0 || "" || null || undefined || NaN || "found");
// "found" (first truthy)
Note: Logical operators return the actual value (not just boolean). Short-circuit evaluation stops at first definitive result, useful for default values and conditional execution.

4.5 Bitwise Operators and Binary Operations

Operator Name Description Example Binary Result
& AND 1 if both bits are 1 5 & 3 0101 & 0011 1 (0001)
| OR 1 if at least one bit is 1 5 | 3 0101 | 0011 7 (0111)
^ XOR 1 if bits are different 5 ^ 3 0101 ^ 0011 6 (0110)
~ NOT Inverts all bits ~5 ~0101 -6 (two's complement)
<< Left Shift Shifts bits left, fills 0s 5 << 1 0101 → 1010 10
>> Sign-propagating Right Shift Shifts bits right, preserves sign 5 >> 1 0101 → 0010 2
>>> Zero-fill Right Shift Shifts bits right, fills 0s -5 >>> 1 Fills with 0s Large positive number
Use Case Operation Example Purpose
Check if even n & 1 5 & 1 = 1 (odd), 4 & 1 = 0 (even) Fast even/odd check
Multiply by 2n n << x 5 << 2 = 20 (5 × 4) Fast multiplication
Divide by 2n n >> x 20 >> 2 = 5 (20 ÷ 4) Fast division
Toggle bit n ^ (1 << i) Toggles i-th bit Flags/permissions
Set bit n | (1 << i) Sets i-th bit to 1 Enable flag
Clear bit n & ~(1 << i) Sets i-th bit to 0 Disable flag
Check bit (n >> i) & 1 Gets value of i-th bit Test flag
Swap variables a ^= b; b ^= a; a ^= b; Swap without temp variable XOR swap trick

Example: Bitwise operations

// Basic bitwise operations
console.log(5 & 3);    // 1  (0101 & 0011 = 0001)
console.log(5 | 3);    // 7  (0101 | 0011 = 0111)
console.log(5 ^ 3);    // 6  (0101 ^ 0011 = 0110)
console.log(~5);       // -6 (inverts bits, two's complement)

// Bit shifts
console.log(5 << 1);   // 10  (0101 → 1010) multiply by 2
console.log(5 << 2);   // 20  (0101 → 10100) multiply by 4
console.log(10 >> 1);  // 5   (1010 → 0101) divide by 2
console.log(20 >> 2);  // 5   (10100 → 0101) divide by 4

// Practical use cases

// 1. Check if number is even or odd
function isEven(n) {
    return (n & 1) === 0;
}
console.log(isEven(4));  // true
console.log(isEven(5));  // false

// 2. Flags and permissions
const READ = 1;      // 001
const WRITE = 2;     // 010
const EXECUTE = 4;   // 100

let permissions = 0;
permissions |= READ;       // Add READ permission
permissions |= WRITE;      // Add WRITE permission

console.log(permissions & READ);    // Check if READ (non-zero = true)
console.log(permissions & EXECUTE); // Check if EXECUTE (0 = false)

permissions &= ~WRITE;     // Remove WRITE permission
console.log(permissions);  // 1 (only READ)

// 3. Fast integer conversion
console.log(~~3.14);       // 3 (double NOT truncates decimals)
console.log(5.7 | 0);      // 5 (OR with 0 truncates)
console.log(5.7 >> 0);     // 5 (right shift by 0 truncates)

// 4. XOR swap (without temp variable)
let a = 5, b = 10;
a ^= b;  // a = 5 ^ 10
b ^= a;  // b = 10 ^ (5 ^ 10) = 5
a ^= b;  // a = (5 ^ 10) ^ 5 = 10
console.log(a, b);  // 10 5

// 5. Toggle boolean
let flag = true;
flag ^= 1;  // false (1 ^ 1 = 0)
flag ^= 1;  // true (0 ^ 1 = 1)

// 6. Set specific bit
let n = 0b0000;  // Binary literal
n |= (1 << 2);   // Set bit 2: 0b0100
console.log(n.toString(2));  // "100"

// 7. Clear specific bit
n &= ~(1 << 2);  // Clear bit 2: 0b0000
console.log(n.toString(2));  // "0"
Note: Bitwise operators work on 32-bit integers. Common uses include flags, permissions, fast math operations, and low-level data manipulation.

4.6 Optional Chaining (?.) and Nullish Coalescing (??)

Operator Syntax Returns Use Case
?. ES2020 obj?.prop undefined if obj is null/undefined, else obj.prop Safe property access
?.[] obj?.[expr] undefined if obj is null/undefined Safe dynamic property access
?.() func?.(args) undefined if func is null/undefined Safe function call
?? ES2020 a ?? b a if not null/undefined, else b Default values for null/undefined only
Feature || (OR) ?? (Nullish Coalescing)
Falsy values treated as missing Yes (false, 0, "", null, undefined, NaN) No (only null/undefined)
0 || 100 100 0
"" || "default" "default" ""
false || true true false
null ?? 100 100 100
undefined ?? 100 100 100
Use case When any falsy should use default When only null/undefined should use default

Example: Optional chaining and nullish coalescing

// Optional Chaining (?.)

// Without optional chaining (verbose and error-prone)
let user = null;
// let name = user.profile.name; // TypeError: Cannot read property 'profile' of null
let name = user && user.profile && user.profile.name; // undefined

// With optional chaining (concise and safe)
name = user?.profile?.name; // undefined (no error)

const user2 = {
    profile: {
        name: "Alice",
        address: {
            city: "NYC"
        }
    }
};

console.log(user2?.profile?.name);          // "Alice"
console.log(user2?.profile?.address?.city); // "NYC"
console.log(user2?.profile?.phone);         // undefined (no error)
console.log(user2?.account?.balance);       // undefined

// Optional chaining with arrays
const arr = null;
console.log(arr?.[0]);        // undefined (safe)
console.log(user2?.tags?.[0]); // undefined

// Optional chaining with function calls
const obj = {
    method: function() { return "called"; }
};

console.log(obj.method?.());      // "called"
console.log(obj.nonExistent?.());  // undefined (no error)
console.log(user?.getProfile?.()); // undefined

// Nullish Coalescing (??)

// Problem with || operator
let count = 0;
let value1 = count || 10;  // 10 (0 is falsy, uses default)
let value2 = count ?? 10;  // 0 (0 is not null/undefined)

let text = "";
let msg1 = text || "default";  // "default" ("" is falsy)
let msg2 = text ?? "default";  // "" (empty string is valid)

// Use ?? for actual null/undefined checks
let config = {
    timeout: 0,      // 0 is valid
    enabled: false,  // false is valid
    name: ""         // empty string is valid
};

let timeout = config.timeout ?? 3000;    // 0 (not null/undefined)
let enabled = config.enabled ?? true;    // false (not null/undefined)
let name = config.name ?? "Unnamed";     // "" (not null/undefined)
let missing = config.missing ?? "N/A";   // "N/A" (undefined)

// Combining ?. and ??
const data = {
    user: null,
    settings: { theme: "dark" }
};

let theme = data?.settings?.theme ?? "light";  // "dark"
let userName = data?.user?.name ?? "Guest";    // "Guest"

// Real-world example: API response
function getUserCity(response) {
    return response?.data?.user?.address?.city ?? "Unknown";
}

console.log(getUserCity(null));                    // "Unknown"
console.log(getUserCity({ data: null }));          // "Unknown"
console.log(getUserCity({
    data: { user: { address: { city: "NYC" } } }
})); // "NYC"

// Short-circuit evaluation
let expensiveFn = () => { console.log("Called"); return 42; };
let result = null?.property?.method?.();  // undefined (expensiveFn not called)
console.log(result);  // undefined
Warning: Don't overuse ?. as it can hide bugs. Use it for genuinely optional properties, not to mask errors in required data.

4.7 Logical Assignment Operators (||=, &&=, ??=)

Operator Name Equivalent Assigns When
||= ES2021 Logical OR Assignment x || (x = y) x is falsy
&&= ES2021 Logical AND Assignment x && (x = y) x is truthy
??= ES2021 Nullish Assignment x ?? (x = y) x is null or undefined
Operator Behavior Use Case Example
||= Assigns if current value is falsy Set default for any falsy value x ||= 10 (assigns if x is falsy)
&&= Assigns if current value is truthy Update existing truthy value x &&= 10 (assigns if x is truthy)
??= Assigns only if null/undefined Set default only for null/undefined x ??= 10 (assigns if x is null/undefined)

Before ES2021:

// Logical OR pattern
x = x || defaultValue;
if (!x) x = defaultValue;

// Logical AND pattern
x = x && newValue;
if (x) x = newValue;

// Nullish pattern
x = x ?? defaultValue;
if (x == null) x = defaultValue;

With ES2021:

// Logical OR assignment
x ||= defaultValue;


// Logical AND assignment
x &&= newValue;


// Nullish assignment
x ??= defaultValue;

Example: Logical assignment operators

// ||= Logical OR Assignment
// Assigns if left side is falsy
let a = 0;
a ||= 10;        // a is 10 (0 is falsy)

let b = 5;
b ||= 10;        // b is still 5 (5 is truthy)

let c = "";
c ||= "default"; // c is "default" ("" is falsy)

// &&= Logical AND Assignment
// Assigns if left side is truthy
let x = 5;
x &&= 10;        // x is 10 (5 is truthy)

let y = 0;
y &&= 10;        // y is still 0 (0 is falsy, no assignment)

let z = null;
z &&= 10;        // z is still null (null is falsy)

// ??= Nullish Assignment
// Assigns only if null or undefined
let p = 0;
p ??= 10;        // p is still 0 (0 is not null/undefined)

let q = null;
q ??= 10;        // q is 10 (null)

let r = undefined;
r ??= 10;        // r is 10 (undefined)

let s = false;
s ??= true;      // s is still false (false is not null/undefined)

// Practical use cases

// 1. Object property defaults with ??=
const config = {
    timeout: 0,      // 0 is valid
    retry: null      // null should be replaced
};

config.timeout ??= 3000;  // Still 0 (not null/undefined)
config.retry ??= 3;       // Now 3 (was null)
config.newProp ??= "default"; // "default" (was undefined)

console.log(config);
// { timeout: 0, retry: 3, newProp: "default" }

// 2. Accumulation with &&=
let total = 100;
let applyDiscount = true;

total &&= total * 0.9;  // Apply 10% discount if total exists
console.log(total);        // 90

let nullTotal = null;
nullTotal &&= nullTotal * 0.9;  // No operation (nullTotal is falsy)
console.log(nullTotal);    // null (unchanged)

// 3. Cache pattern with ||=
let cache = {};

function getData(key) {
    // Set cache if not already set
    cache[key] ||= expensiveOperation(key);
    return cache[key];
}

function expensiveOperation(key) {
    console.log("Computing...");
    return key * 2;
}

console.log(getData(5));   // "Computing..." then 10
console.log(getData(5));   // 10 (from cache, no computing)

// 4. Updating objects conditionally
const user = {
    name: "Alice",
    age: null,
    status: undefined
};

user.name ||= "Anonymous";    // Still "Alice" (truthy)
user.age ??= 18;              // Now 18 (was null)
user.status ??= "active";     // Now "active" (was undefined)

// 5. Short-circuit: right side not evaluated if condition not met
let count = 0;
let getValue = () => { count++; return 100; };

let x1 = 5;
x1 ||= getValue();  // getValue() NOT called (x1 is truthy)
console.log(count); // 0

let x2 = 0;
x2 ||= getValue();  // getValue() IS called (x2 is falsy)
console.log(count); // 1
Note: Logical assignment operators combine logical operations with assignment, providing cleaner syntax and short-circuit evaluation (right side only evaluated when assignment occurs).

Section 4 Summary

  • Arithmetic operators include +, -, *, /, %, ** (exponentiation); + concatenates strings if either operand is string
  • Assignment operators support compound forms (+=, -=, *=, etc.) for concise update operations
  • Use strict equality (===, !==) to avoid type coercion bugs; loose equality (==) performs type conversion
  • Logical operators (&&, ||, !) short-circuit and return actual values, not just booleans; useful for defaults and guards
  • Bitwise operators work on 32-bit integers; useful for flags, permissions, and fast math operations
  • Optional chaining (?.) safely accesses nested properties; nullish coalescing (??) provides defaults only for null/undefined
  • Logical assignment (||=, &&=, ??=) combines logic with assignment; short-circuits when assignment unnecessary

5. Control Flow and Loop Constructs

5.1 Conditional Statements (if, switch, ternary)

Statement Syntax Use Case Notes
if if (condition) { } Execute block if condition is truthy Most common conditional
if...else if (cond) { } else { } Execute one of two blocks Binary choice
if...else if...else if (c1) { } else if (c2) { } else { } Multiple conditions, evaluated in order First truthy wins
switch switch(expr) { case val: break; } Multiple discrete values, strict equality (===) Don't forget break
Ternary condition ? trueVal : falseVal Inline conditional expression Returns value, concise
Nested Ternary c1 ? v1 : c2 ? v2 : v3 Multiple conditions inline Can be hard to read
Feature if/else switch Ternary
Returns Value No (statements) No (statements) Yes (expression)
Multiple Conditions ✓ Any expression Single expression, multiple cases One condition per ternary
Comparison Type Any truthy/falsy Strict equality (===) Any truthy/falsy
Fall-through N/A ✓ Without break N/A
Best For Complex conditions, ranges Many discrete values Simple inline conditions

Example: Conditional statements

// if statement
let age = 20;
if (age >= 18) {
    console.log("Adult");
}

// if...else
if (age >= 18) {
    console.log("Adult");
} else {
    console.log("Minor");
}

// if...else if...else
let score = 85;
if (score >= 90) {
    console.log("A");
} else if (score >= 80) {
    console.log("B");
} else if (score >= 70) {
    console.log("C");
} else {
    console.log("F");
}

// switch statement
let day = "Monday";
switch (day) {
    case "Monday":
    case "Tuesday":
    case "Wednesday":
    case "Thursday":
    case "Friday":
        console.log("Weekday");
        break;  // Important!
    case "Saturday":
    case "Sunday":
        console.log("Weekend");
        break;
    default:
        console.log("Invalid day");
}

// switch with expressions (modern pattern)
let result = switch(status) {  // Not yet in JS, but coming
    case 200: "OK";
    case 404: "Not Found";
    default: "Unknown";
};

// Ternary operator
let message = age >= 18 ? "Adult" : "Minor";
console.log(message);

// Nested ternary
let grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F";

// Ternary with side effects (avoid for readability)
let value = condition ? (count++, doSomething()) : doSomethingElse();

// Multiple ternaries (can be hard to read)
let type = age < 13 ? "child" 
         : age < 20 ? "teen"
         : age < 65 ? "adult"
         : "senior";

// Guard clauses (early return pattern)
function processUser(user) {
    if (!user) return;              // Guard clause
    if (!user.isActive) return;     // Guard clause
    if (!user.hasPermission) return; // Guard clause
    
    // Main logic here
    console.log("Processing user...");
}
Warning: Always use break in switch cases to prevent fall-through (unless intentional). Nested ternaries can reduce readability; use if/else for complex logic.

5.2 Loop Statements (for, while, do-while)

Loop Type Syntax When to Use Characteristics
for for (init; cond; update) { } Known iteration count Initialization, condition, update in header
while while (condition) { } Unknown iteration count, condition first Tests condition before each iteration
do-while do { } while (condition); At least one iteration guaranteed Tests condition after each iteration
Feature for while do-while
Minimum Iterations 0 (can skip entirely) 0 (can skip entirely) 1 (always executes once)
Condition Check Before each iteration Before each iteration After each iteration
Loop Variable Scope Block-scoped if using let/const Declared outside loop Declared outside loop
Best For Counter-based iteration Condition-based iteration Execute-then-check pattern

Example: Classic loop statements

// for loop - most common for arrays/counters
for (let i = 0; i < 5; i++) {
    console.log(i);  // 0, 1, 2, 3, 4
}

// Multiple variables in for loop
for (let i = 0, j = 10; i < 5; i++, j--) {
    console.log(i, j);  // (0,10), (1,9), (2,8), (3,7), (4,6)
}

// Infinite loop (condition always true)
// for (;;) {
//     console.log("Forever");
//     break; // Need break to exit
// }

// while loop - condition-based
let count = 0;
while (count < 5) {
    console.log(count);
    count++;
}

// while with complex condition
let items = [1, 2, 3, 4, 5];
let i = 0;
while (i < items.length && items[i] !== 3) {
    console.log(items[i]);
    i++;
}

// Reading input until valid
let input;
while (!input || input.trim() === "") {
    // input = prompt("Enter value:");  // Browser example
    input = "test";  // Simulated
}

// do-while - at least one iteration
let num = 0;
do {
    console.log(num);  // Executes once even though num < 1 is false
    num++;
} while (num < 1);

// Menu loop pattern
let choice;
do {
    // Display menu
    console.log("1. Option 1");
    console.log("2. Option 2");
    console.log("3. Exit");
    // choice = getUserInput();
    choice = 3;  // Simulated
} while (choice !== 3);

// Empty for loop (all work in condition)
let arr = [1, 2, 3];
for (let i = 0; i < arr.length && arr[i]++ < 10;);
console.log(arr);  // [2, 3, 4]

// Nested loops
for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        console.log(`${i}, ${j}`);
    }
}

// Loop with multiple updates
for (let i = 0; i < 10; i += 2) {
    console.log(i);  // 0, 2, 4, 6, 8
}

// Reverse loop
for (let i = 5; i >= 0; i--) {
    console.log(i);  // 5, 4, 3, 2, 1, 0
}
Note: Use for for counter-based iteration, while for condition-based, and do-while when you need at least one iteration before checking the condition.

5.3 Enhanced Loops (for...in, for...of, for await...of)

Loop Type Syntax Iterates Over Returns Use For
for...in for (key in obj) { } Enumerable properties Property names (strings) Object properties
for...of ES6 for (val of iterable) { } Iterable objects Values Arrays, strings, Maps, Sets
for await...of ES2018 for await (val of asyncIterable) { } Async iterables Resolved promises Async generators, streams
Feature for...in for...of
Works with Objects ✓ Yes (enumerable properties) ✗ No (unless iterable)
Works with Arrays ✓ Yes (indices as strings) ✓ Yes (values directly)
Works with Strings ✓ Yes (indices) ✓ Yes (characters)
Works with Map/Set ✗ No ✓ Yes
Includes Prototype Chain ✓ Yes (inherited properties) ✗ No
Recommended For Object properties (use with hasOwnProperty) Iterables (arrays, strings, collections)

Example: Enhanced loops

// for...in - iterates over object properties
const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
    console.log(key, obj[key]);  // "a" 1, "b" 2, "c" 3
}

// for...in with arrays (NOT recommended - use for...of)
const arr = [10, 20, 30];
for (let index in arr) {
    console.log(index, arr[index]);  // "0" 10, "1" 20, "2" 30
    console.log(typeof index);       // "string" (not number!)
}

// for...in includes inherited properties
Object.prototype.inheritedProp = "inherited";
const obj2 = { own: "value" };
for (let key in obj2) {
    console.log(key);  // "own", "inheritedProp"
}

// Use hasOwnProperty to filter
for (let key in obj2) {
    if (obj2.hasOwnProperty(key)) {
        console.log(key);  // "own" only
    }
}

// for...of - iterates over iterable values (ES6)
const arr2 = [10, 20, 30];
for (let value of arr2) {
    console.log(value);  // 10, 20, 30 (values, not indices)
}

// for...of with strings
const str = "hello";
for (let char of str) {
    console.log(char);  // "h", "e", "l", "l", "o"
}

// for...of with Map
const map = new Map([['a', 1], ['b', 2]]);
for (let [key, value] of map) {
    console.log(key, value);  // "a" 1, "b" 2
}

// for...of with Set
const set = new Set([1, 2, 3]);
for (let value of set) {
    console.log(value);  // 1, 2, 3
}

// for...of with array methods
const arr3 = [1, 2, 3, 4, 5];
for (let value of arr3.entries()) {
    console.log(value);  // [0, 1], [1, 2], [2, 3]...
}

for (let [index, value] of arr3.entries()) {
    console.log(index, value);  // 0 1, 1 2, 2 3...
}

// for await...of - async iteration (ES2018)
async function fetchSequentially() {
    const urls = ['url1', 'url2', 'url3'];
    
    // Create async iterable
    async function* fetchUrls() {
        for (let url of urls) {
            yield fetch(url);  // Simulated
        }
    }
    
    // Iterate and wait for each promise
    for await (let response of fetchUrls()) {
        console.log(await response.text());
    }
}

// for await...of with Promise array
async function processPromises() {
    const promises = [
        Promise.resolve(1),
        Promise.resolve(2),
        Promise.resolve(3)
    ];
    
    for await (let value of promises) {
        console.log(value);  // 1, 2, 3 (waits for each)
    }
}

// Custom iterable with for...of
const customIterable = {
    data: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => ({
                value: this.data[index],
                done: index++ >= this.data.length
            })
        };
    }
};

for (let value of customIterable) {
    console.log(value);  // 1, 2, 3
}
Warning: Use for...of for arrays (not for...in). With for...in on objects, always use hasOwnProperty() to avoid inherited properties.

5.4 Jump Statements (break, continue, return)

Statement Syntax Effect Use In
break break; Exits loop or switch immediately Loops, switch
break label break labelName; Exits named loop/block Nested loops
continue continue; Skips to next iteration Loops only
continue label continue labelName; Continues named loop Nested loops
return return value; Exits function with value Functions only
return (void) return; Exits function, returns undefined Functions only
Statement break continue return
In for loop ✓ Exits loop ✓ Next iteration ✓ Exits function (and loop)
In while loop ✓ Exits loop ✓ Next iteration ✓ Exits function (and loop)
In switch ✓ Exits switch ✗ Syntax error ✓ Exits function (and switch)
In function ✗ Must be in loop/switch ✗ Must be in loop ✓ Exits function
With labels ✓ Can target outer loop ✓ Can target outer loop ✗ Cannot use labels

Example: Jump statements

// break - exit loop immediately
for (let i = 0; i < 10; i++) {
    if (i === 5) break;  // Exit loop when i is 5
    console.log(i);      // 0, 1, 2, 3, 4
}

// break in nested loop (only exits inner loop)
for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (j === 1) break;  // Exits inner loop only
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  1,0  2,0

// continue - skip to next iteration
for (let i = 0; i < 5; i++) {
    if (i === 2) continue;  // Skip when i is 2
    console.log(i);         // 0, 1, 3, 4
}

// continue with condition
const arr = [1, 2, 3, 4, 5];
for (let num of arr) {
    if (num % 2 === 0) continue;  // Skip even numbers
    console.log(num);             // 1, 3, 5
}

// return - exit function
function findValue(arr, target) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] === target) {
            return i;  // Exit function, return index
        }
    }
    return -1;  // Not found
}

console.log(findValue([1, 2, 3], 2));  // 1
console.log(findValue([1, 2, 3], 5));  // -1

// return without value (returns undefined)
function logMessage(msg) {
    if (!msg) return;  // Early exit
    console.log(msg);
}

// Multiple return points (guard clauses)
function validateUser(user) {
    if (!user) return { valid: false, error: "No user" };
    if (!user.name) return { valid: false, error: "No name" };
    if (!user.email) return { valid: false, error: "No email" };
    return { valid: true };
}

// break in switch
function getDayType(day) {
    switch (day) {
        case 'Mon':
        case 'Tue':
        case 'Wed':
        case 'Thu':
        case 'Fri':
            return 'Weekday';
            // No break needed after return
        case 'Sat':
        case 'Sun':
            return 'Weekend';
        default:
            return 'Invalid';
    }
}

// Finding first match
const users = [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 30 },
    { name: 'Charlie', age: 35 }
];

let found;
for (let user of users) {
    if (user.age > 28) {
        found = user;
        break;  // Stop after finding first match
    }
}
console.log(found);  // Bob

// Skip invalid items
const data = [1, null, 2, undefined, 3, NaN, 4];
for (let item of data) {
    if (item == null || Number.isNaN(item)) continue;
    console.log(item);  // 1, 2, 3, 4
}
Note: Use break to exit loops early, continue to skip iterations, and return to exit functions. For nested loops, consider labeled statements for more control.

5.5 Exception Handling (try, catch, finally, throw)

Keyword Purpose Required Description
try Execute code that may throw ✓ Yes Contains code to be tested for errors
catch Handle exceptions ✓ Yes (or finally) Executes if error thrown in try block
finally Cleanup code ✗ Optional Always executes (even if error/return)
throw Throw exception N/A Creates and throws error object
Error Type When Thrown Example
Error Generic error (base class) new Error("message")
SyntaxError Invalid JavaScript syntax eval("{")
ReferenceError Invalid reference/undeclared variable console.log(undeclaredVar)
TypeError Value not of expected type null.property
RangeError Value not in allowed range new Array(-1)
URIError Invalid URI encoding/decoding decodeURI("%")

Example: Exception handling

// Basic try-catch
try {
    let result = riskyOperation();
    console.log(result);
} catch (error) {
    console.error("Error:", error.message);
}

// Accessing error properties
try {
    throw new Error("Something went wrong");
} catch (error) {
    console.log(error.name);     // "Error"
    console.log(error.message);  // "Something went wrong"
    console.log(error.stack);    // Stack trace
}

// try-catch-finally
try {
    console.log("Try block");
    // throw new Error("Test");
} catch (error) {
    console.log("Catch block:", error.message);
} finally {
    console.log("Finally always runs");
}

// finally runs even with return
function testFinally() {
    try {
        return "from try";
    } catch (error) {
        return "from catch";
    } finally {
        console.log("Finally executed");  // Runs before return
    }
}
console.log(testFinally());  // "Finally executed", then "from try"

// Throwing errors
function divide(a, b) {
    if (b === 0) {
        throw new Error("Division by zero");
    }
    return a / b;
}

try {
    console.log(divide(10, 0));
} catch (error) {
    console.log("Caught:", error.message);
}

// Throwing custom errors
class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}

function validateAge(age) {
    if (age < 0) {
        throw new ValidationError("Age cannot be negative");
    }
    if (age > 150) {
        throw new ValidationError("Age too high");
    }
    return true;
}

try {
    validateAge(-5);
} catch (error) {
    if (error instanceof ValidationError) {
        console.log("Validation error:", error.message);
    } else {
        console.log("Other error:", error);
    }
}

// Nested try-catch
try {
    try {
        throw new Error("Inner error");
    } catch (innerError) {
        console.log("Inner catch:", innerError.message);
        throw new Error("Outer error");  // Re-throw or new error
    }
} catch (outerError) {
    console.log("Outer catch:", outerError.message);
}

// Catching specific error types
try {
    // Some operation
    JSON.parse("invalid json");
} catch (error) {
    if (error instanceof SyntaxError) {
        console.log("JSON syntax error");
    } else if (error instanceof TypeError) {
        console.log("Type error");
    } else {
        console.log("Unknown error");
    }
}

// Async error handling
async function fetchData() {
    try {
        const response = await fetch('/api/data');
        const data = await response.json();
        return data;
    } catch (error) {
        console.error("Fetch error:", error);
        throw error;  // Re-throw for caller to handle
    } finally {
        console.log("Cleanup operations");
    }
}

// Error without catch (just finally)
try {
    console.log("Executing...");
} finally {
    console.log("Cleanup");  // Valid: try-finally without catch
}

// Throwing non-Error objects (possible but not recommended)
try {
    throw "String error";  // Works but not best practice
    throw { code: 500 };   // Works but no stack trace
    throw 42;              // Works but no context
} catch (error) {
    console.log(typeof error);  // Could be anything
}
Warning: Always catch errors for operations that may fail (network, parsing, etc.). Use finally for cleanup that must run regardless of success/failure.

5.6 Labels and Labeled Statements

Feature Syntax Use With Purpose
Label Definition labelName: statement Any statement/block Names a statement for reference
Labeled break break labelName; Loops, blocks Exits named loop/block
Labeled continue continue labelName; Loops only Continues named loop
Use Case Without Labels With Labels
Break from nested loop Only breaks inner loop Can break outer loop directly
Continue outer loop Requires flag variable Direct continue to outer loop
Exit multiple levels Multiple break statements Single labeled break
Code clarity Complex control flow Explicit jump targets

Example: Labeled statements

// Labeled break - exit outer loop
outer: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) {
            break outer;  // Breaks out of outer loop
        }
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  0,1  0,2  1,0

// Without label (only breaks inner loop)
for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) {
            break;  // Only breaks inner loop
        }
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  0,1  0,2  1,0  2,0  2,1  2,2

// Labeled continue - continue outer loop
outer: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (j === 1) {
            continue outer;  // Continues outer loop, skips rest of inner
        }
        console.log(`${i},${j}`);
    }
}
// Output: 0,0  1,0  2,0

// Finding element in 2D array
const matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

let target = 5;
let found = false;

search: for (let i = 0; i < matrix.length; i++) {
    for (let j = 0; j < matrix[i].length; j++) {
        if (matrix[i][j] === target) {
            console.log(`Found at [${i}][${j}]`);
            found = true;
            break search;  // Exit both loops
        }
    }
}

// Multiple nested loops with labels
outer: for (let i = 0; i < 2; i++) {
    middle: for (let j = 0; j < 2; j++) {
        inner: for (let k = 0; k < 2; k++) {
            if (i === 1) break outer;      // Exit all loops
            if (j === 1) break middle;     // Exit middle and inner
            if (k === 1) break inner;      // Exit only inner
            console.log(`${i},${j},${k}`);
        }
    }
}

// Labeled block (not loop)
blockLabel: {
    console.log("Start");
    if (someCondition) {
        break blockLabel;  // Exit block early
    }
    console.log("This may not execute");
}
console.log("After block");

// Complex search with labeled break
const data = [
    { id: 1, items: [10, 20, 30] },
    { id: 2, items: [40, 50, 60] },
    { id: 3, items: [70, 80, 90] }
];

let searchValue = 50;
let result = null;

dataSearch: for (let group of data) {
    for (let item of group.items) {
        if (item === searchValue) {
            result = { groupId: group.id, value: item };
            break dataSearch;  // Found, exit all loops
        }
    }
}

console.log(result);  // { groupId: 2, value: 50 }

// Alternative to labeled break (using function return)
function findInMatrix(matrix, target) {
    for (let i = 0; i < matrix.length; i++) {
        for (let j = 0; j < matrix[i].length; j++) {
            if (matrix[i][j] === target) {
                return [i, j];  // Return exits function (simpler)
            }
        }
    }
    return null;
}

// Labeled continue with condition
outer: for (let i = 0; i < 5; i++) {
    for (let j = 0; j < 5; j++) {
        if (i + j > 5) {
            continue outer;  // Skip to next outer iteration
        }
        console.log(`Sum: ${i + j}`);
    }
}
Note: Labeled statements are useful for complex nested loops but can reduce readability. Consider refactoring into separate functions with return as a cleaner alternative.

Section 5 Summary

  • Conditionals: Use if/else for complex logic, switch for discrete values, ternary for inline expressions
  • Loops: for (known count), while (condition-based), do-while (at least once)
  • Enhanced loops: for...of for iterables (arrays, strings), for...in for object properties, for await...of for async
  • Jump statements: break exits loops/switch, continue skips to next iteration, return exits functions
  • Exception handling: try-catch-finally for error handling; throw to raise errors; finally always executes
  • Labels: Enable break/continue to target outer loops; useful for nested structures but consider function refactoring

6. Functions and Functional Programming

6.1 Function Declaration and Expression Syntax

Function Type Syntax Hoisting Use Case
Function Declaration function name(params) { body } ✓ Fully hoisted (can call before declaration) Top-level functions, utility functions requiring hoisting
Function Expression const name = function(params) { body }; ✗ Variable hoisted but undefined (TDZ for let/const) Conditional function creation, callbacks, function as data
Named Function Expression const f = function name(params) { body }; ✗ Not hoisted; name only visible inside function Recursion in expressions, better stack traces
Arrow Function ES6 const name = (params) => expression ✗ Variable hoisting behavior Short callbacks, lexical this binding, concise syntax
Method Definition ES6 { methodName(params) { body } } ✗ Part of object/class definition Object methods, class methods with concise syntax
Generator Function ES6 function* name(params) { yield value } ✓ Hoisted like regular functions Iterators, lazy evaluation, pausable execution
Async Function ES8 async function name(params) { await promise } ✓ Hoisted (declaration) / ✗ (expression) Asynchronous operations with cleaner syntax

Example: Function declaration vs expression

// Function Declaration - hoisted, can call before definition
console.log(add(2, 3));  // Works: 5
function add(a, b) {
    return a + b;
}

// Function Expression - not hoisted
// console.log(subtract(5, 2));  // Error: Cannot access before initialization
const subtract = function(a, b) {
    return a - b;
};

// Named Function Expression - name visible only inside
const factorial = function fact(n) {
    return n <= 1 ? 1 : n * fact(n - 1);  // 'fact' accessible here
};
console.log(factorial(5));  // 120
// console.log(fact(5));  // Error: fact is not defined

// Arrow function - concise syntax
const multiply = (a, b) => a * b;
const square = x => x * x;  // Single param, no parens needed
const greet = () => 'Hello';  // No params
Best Practice: Use function declarations for top-level utility functions that need hoisting. Use arrow functions for callbacks and methods that don't need their own this. Use function expressions when functions are conditional or need to be treated as values.

6.2 Arrow Functions and Lexical this Binding

Feature Arrow Function Regular Function Impact
this Binding Lexical (from enclosing scope) Dynamic (based on call) No .bind() needed for callbacks
arguments Object ✗ Not available (use rest params) ✓ Available Use ...args for arrow functions
new Operator ✗ Cannot be constructor ✓ Can be constructor Arrow functions have no [[Construct]]
prototype Property ✗ No prototype ✓ Has prototype Lighter memory footprint for arrows
super Keyword ✗ Cannot use super ✓ Can use in methods Use regular functions for class methods needing super
yield Keyword ✗ Cannot be generator ✓ Can yield Use function* for generators
Syntax Form Example When to Use
No Parameters () => expression Functions with no inputs
Single Parameter x => expression Unary functions (map, filter callbacks)
Multiple Parameters (x, y) => expression Binary/multi-parameter functions
Expression Body x => x * 2 Single expression, implicit return
Block Body x => { return x * 2; } Multiple statements, explicit return
Object Literal Return x => ({ value: x }) Returning object (wrap in parentheses)

Example: Lexical this binding in practice

// Problem with regular functions - this binding changes
function Timer() {
    this.seconds = 0;
    
    // Regular function - 'this' is window/undefined in strict mode
    setInterval(function() {
        this.seconds++;  // ✗ Wrong 'this'
        console.log(this.seconds);  // NaN
    }, 1000);
}

// Solution 1: Arrow function - lexical 'this'
function Timer() {
    this.seconds = 0;
    
    setInterval(() => {
        this.seconds++;  // ✓ Correct 'this' from Timer
        console.log(this.seconds);  // 1, 2, 3...
    }, 1000);
}

// Solution 2: Old approach with .bind() (before ES6)
function Timer() {
    this.seconds = 0;
    setInterval(function() {
        this.seconds++;
    }.bind(this), 1000);  // Explicitly bind 'this'
}

// Class methods with arrow functions
class Counter {
    count = 0;
    
    // Arrow function as class field - 'this' always bound to instance
    increment = () => {
        this.count++;
    }
    
    // Regular method - 'this' depends on how it's called
    decrement() {
        this.count--;
    }
}

const counter = new Counter();
const inc = counter.increment;
const dec = counter.decrement;

inc();  // ✓ Works - arrow function has lexical 'this'
dec();  // ✗ Error - 'this' is undefined (not bound)

Example: Arrow function syntax variations

// Expression body - implicit return
const double = x => x * 2;
const sum = (a, b) => a + b;

// Block body - explicit return needed
const greet = name => {
    const message = `Hello, ${name}!`;
    return message.toUpperCase();
};

// Returning object literal - wrap in parentheses
const makePerson = (name, age) => ({ name, age });
const point = (x, y) => ({ x: x, y: y });

// Array methods with arrows
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const total = numbers.reduce((sum, n) => sum + n, 0);

// Chaining with concise arrows
const result = [1, 2, 3, 4, 5]
    .filter(x => x % 2 === 0)
    .map(x => x * x)
    .reduce((sum, x) => sum + x, 0);  // 20
Warning: Don't use arrow functions as object methods or event handlers where you need dynamic this. Arrow functions cannot be used as constructors with new. They don't have arguments object - use rest parameters instead.

6.3 Function Parameters (default, rest, destructuring)

Parameter Type Syntax Description Example
Required Parameters function f(a, b) Traditional positional parameters; undefined if not provided f(1, 2)
Default Parameters ES6 function f(a = 0, b = 1) Provides default value if undefined (not for null) f() // a=0, b=1
Rest Parameters ES6 function f(...args) Collects remaining arguments into array; must be last f(1,2,3) // args=[1,2,3]
Destructuring (Array) ES6 function f([a, b]) Extracts values from array argument by position f([10, 20])
Destructuring (Object) ES6 function f({x, y}) Extracts properties from object argument by name f({x:1, y:2})
Nested Destructuring ES6 function f({a: {b}}) Extracts nested properties from deep object structure f({a: {b: 5}})
Default + Destructuring ES6 function f({x = 0} = {}) Combines destructuring with defaults for object properties f() // x=0
Pattern Example Behavior
Default with Expression f(a = computeDefault()) Expression evaluated only if parameter is undefined
Using Previous Params f(a, b = a * 2) Default can reference earlier parameters
Rest with Destructuring f([first, ...rest]) Combine array destructuring with rest collection
Rename in Destructuring f({x: width, y: height}) Extract and rename properties simultaneously

Example: Default parameters

// Basic default parameters
function greet(name = 'Guest', greeting = 'Hello') {
    return `${greeting}, ${name}!`;
}
console.log(greet());  // "Hello, Guest!"
console.log(greet('Alice'));  // "Hello, Alice!"
console.log(greet('Bob', 'Hi'));  // "Hi, Bob!"

// Default only applies to undefined, not null
console.log(greet(null));  // "Hello, null!" (null !== undefined)
console.log(greet(undefined, 'Hey'));  // "Hey, Guest!" (undefined triggers default)

// Default expressions evaluated at call time
let counter = 0;
function f(x = counter++) {
    return x;
}
console.log(f());  // 0 (counter becomes 1)
console.log(f());  // 1 (counter becomes 2)
console.log(f(10));  // 10 (default not used, counter stays 2)

// Using previous parameters in defaults
function createRect(width, height = width) {
    return { width, height };  // height defaults to width (square)
}
console.log(createRect(5));  // { width: 5, height: 5 }
console.log(createRect(5, 10));  // { width: 5, height: 10 }

Example: Rest parameters

// Rest parameters collect remaining arguments into array
function sum(...numbers) {
    return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4));  // 10
console.log(sum());  // 0 (empty array)

// Combine regular params with rest
function multiply(multiplier, ...numbers) {
    return numbers.map(n => n * multiplier);
}
console.log(multiply(2, 1, 2, 3));  // [2, 4, 6]

// Rest must be last parameter
// function invalid(...args, last) {}  // ✗ SyntaxError

// Rest vs arguments object
function oldWay() {
    // arguments is array-like, not real array
    const args = Array.from(arguments);
    return args.reduce((sum, n) => sum + n, 0);
}

function newWay(...args) {
    // args is real array with all array methods
    return args.reduce((sum, n) => sum + n, 0);
}

// Arrow functions don't have arguments, use rest
const arrowSum = (...nums) => nums.reduce((s, n) => s + n, 0);

Example: Destructuring parameters

// Object destructuring in parameters
function createUser({ name, age, email = 'none@example.com' }) {
    return { name, age, email };
}
createUser({ name: 'Alice', age: 30 });
// { name: 'Alice', age: 30, email: 'none@example.com' }

// Array destructuring in parameters
function sumFirstTwo([a, b]) {
    return a + b;
}
console.log(sumFirstTwo([10, 20, 30]));  // 30

// Nested destructuring
function displayPoint({ coords: { x, y }, label = 'Point' }) {
    return `${label}: (${x}, ${y})`;
}
displayPoint({ coords: { x: 10, y: 20 } });  // "Point: (10, 20)"

// Destructuring with rest
function getFirstAndRest([first, ...rest]) {
    return { first, rest };
}
console.log(getFirstAndRest([1, 2, 3, 4]));
// { first: 1, rest: [2, 3, 4] }

// Rename while destructuring
function printDimensions({ width: w, height: h }) {
    console.log(`Width: ${w}, Height: ${h}`);
}
printDimensions({ width: 100, height: 200 });

// Default for entire destructured object
function config({ host = 'localhost', port = 8080 } = {}) {
    return `${host}:${port}`;
}
console.log(config());  // "localhost:8080"
console.log(config({ host: 'example.com' }));  // "example.com:8080"

6.4 Higher-Order Functions and Callbacks

Concept Definition Common Use Cases
Higher-Order Function (HOF) Function that takes function(s) as argument(s) and/or returns a function map, filter, reduce, addEventListener, middleware
Callback Function Function passed as argument to be executed later (synchronous or asynchronous) Event handlers, async operations, array methods
Function Returning Function HOF that creates and returns new function (function factory) Configuration, currying, closures, partial application
Function Composition Combining functions where output of one is input to another Data transformation pipelines, functional programming
Built-in HOF Purpose Callback Signature Returns
Array.map() Transform each element (element, index, array) => newValue New array (same length)
Array.filter() Select elements by condition (element, index, array) => boolean New array (≤ original length)
Array.reduce() Aggregate to single value (accumulator, element, index, array) => newAcc Single accumulated value
Array.forEach() Execute side effects (element, index, array) => void undefined (no return value)
Array.find() Find first matching element (element, index, array) => boolean Element or undefined
Array.some() Test if any element passes (element, index, array) => boolean boolean
Array.every() Test if all elements pass (element, index, array) => boolean boolean

Example: Higher-order functions

// Function that takes function as argument
function repeat(n, action) {
    for (let i = 0; i < n; i++) {
        action(i);
    }
}
repeat(3, console.log);  // Logs: 0, 1, 2

// Function that returns function (function factory)
function multiplier(factor) {
    return function(number) {
        return number * factor;
    };
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5));  // 10
console.log(triple(5));  // 15

// Function composition
function compose(f, g) {
    return function(x) {
        return f(g(x));
    };
}
const addOne = x => x + 1;
const square = x => x * x;
const addOneThenSquare = compose(square, addOne);
console.log(addOneThenSquare(4));  // 25 (5²)

// Practical HOF: logger wrapper
function withLogging(fn) {
    return function(...args) {
        console.log(`Calling ${fn.name} with`, args);
        const result = fn(...args);
        console.log(`Result:`, result);
        return result;
    };
}
const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(2, 3);  // Logs call info, returns 5

Example: Array HOF methods in action

const users = [
    { id: 1, name: 'Alice', age: 25, active: true },
    { id: 2, name: 'Bob', age: 30, active: false },
    { id: 3, name: 'Charlie', age: 35, active: true }
];

// map - transform data
const names = users.map(user => user.name);
// ['Alice', 'Bob', 'Charlie']

// filter - select subset
const activeUsers = users.filter(user => user.active);
// [Alice, Charlie]

// reduce - aggregate
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
// 90

// Chaining HOFs
const result = users
    .filter(user => user.active)
    .map(user => user.name)
    .map(name => name.toUpperCase());
// ['ALICE', 'CHARLIE']

// find - first match
const user = users.find(u => u.age > 25);
// { id: 2, name: 'Bob', age: 30, active: false }

// some - any match
const hasInactive = users.some(u => !u.active);  // true

// every - all match
const allActive = users.every(u => u.active);  // false

// Complex reduce example
const grouped = users.reduce((groups, user) => {
    const key = user.active ? 'active' : 'inactive';
    groups[key] = groups[key] || [];
    groups[key].push(user);
    return groups;
}, {});
// { active: [Alice, Charlie], inactive: [Bob] }
Best Practice: Prefer declarative HOFs (map, filter, reduce) over imperative loops for data transformation. Chain operations for readability. Use arrow functions for concise callbacks. Remember that HOFs enable code reuse and abstraction.

6.5 Closures and Function Scope Management

Concept Definition Key Characteristics
Closure Function that retains access to variables from its outer (enclosing) scope even after outer function has returned Lexical scoping, persistent state, data privacy
Lexical Environment Internal structure holding variable bindings and reference to outer environment Created at function execution, forms scope chain
Scope Chain Chain of lexical environments searched for variable resolution Inner to outer traversal, stops at first match
Private Variables Variables accessible only through closures, not directly from outside Encapsulation, data hiding, module pattern
Closure Pattern Use Case Example Pattern
Function Factory Create specialized functions with preset parameters function makeAdder(x) { return y => x + y; }
Private State Encapsulate data with controlled access function counter() { let n=0; return ()=>++n; }
Module Pattern Create modules with public/private members IIFE returning object with methods accessing private vars
Event Handlers Preserve context in async callbacks Handler functions accessing outer scope variables
Memoization Cache function results using closure-stored cache Closure over cache object for recursive functions

Example: Basic closure mechanics

// Simple closure - inner function accesses outer variable
function outer() {
    let count = 0;  // Variable in outer scope
    
    function inner() {
        count++;  // Accesses count from outer scope
        return count;
    }
    
    return inner;
}

const counter = outer();
console.log(counter());  // 1
console.log(counter());  // 2
console.log(counter());  // 3
// 'count' persists between calls through closure

// Multiple closures over same variable
function makeCounter() {
    let count = 0;
    return {
        increment: () => ++count,
        decrement: () => --count,
        value: () => count
    };
}

const c1 = makeCounter();
const c2 = makeCounter();  // Separate closure, separate count
c1.increment();
c1.increment();
console.log(c1.value());  // 2
console.log(c2.value());  // 0 (independent)

Example: Practical closure patterns

// Function factory with closure
function multiplier(factor) {
    return function(number) {
        return number * factor;  // 'factor' remembered via closure
    };
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5));  // 10
console.log(triple(5));  // 15

// Private variables pattern
function createBankAccount(initialBalance) {
    let balance = initialBalance;  // Private variable
    
    return {
        deposit(amount) {
            if (amount > 0) balance += amount;
            return balance;
        },
        withdraw(amount) {
            if (amount > 0 && amount <= balance) balance -= amount;
            return balance;
        },
        getBalance() {
            return balance;
        }
    };
    // No direct access to 'balance' from outside
}

const account = createBankAccount(100);
account.deposit(50);  // 150
account.withdraw(30);  // 120
console.log(account.balance);  // undefined (private)
console.log(account.getBalance());  // 120

// Memoization with closure
function memoize(fn) {
    const cache = {};  // Closure over cache
    return function(...args) {
        const key = JSON.stringify(args);
        if (key in cache) {
            console.log('Cached');
            return cache[key];
        }
        const result = fn(...args);
        cache[key] = result;
        return result;
    };
}

const slowFunction = (n) => {
    console.log('Computing...');
    return n * 2;
};
const fast = memoize(slowFunction);
fast(5);  // Computing... 10
fast(5);  // Cached 10
Common Pitfall: Closures in loops. Using var creates single binding shared across iterations. Solution: use let (creates new binding per iteration) or IIFE to capture value.
// ✗ Problem with var
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// Logs: 3, 3, 3 (all closures see same 'i')

// ✓ Solution 1: Use let
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// Logs: 0, 1, 2 (each closure gets own 'i')

// ✓ Solution 2: IIFE (old approach)
for (var i = 0; i < 3; i++) {
    (function(j) {
        setTimeout(() => console.log(j), 100);
    })(i);
}
// Logs: 0, 1, 2

6.6 IIFE Patterns and Module Creation

Pattern Syntax Purpose Use Case
Basic IIFE (function() { })(); Execute function immediately, create private scope Avoid global pollution, one-time initialization
Alternative Syntax (function() { }()); Same as basic IIFE (different grouping) Style preference, same behavior
Arrow IIFE ES6 (() => { })(); Concise IIFE with lexical this Modern alternative, shorter syntax
Named IIFE (function name() { })(); Self-reference for recursion, better debugging Recursive IIFEs, stack traces
IIFE with Parameters (function(x) { })(value); Pass values into IIFE scope Dependency injection, value capture
IIFE Returning Value const x = (function() { return val; })(); Initialize variable with complex logic Complex initialization, module pattern
Module Pattern (IIFE) const mod = (function() { return {...}; })(); Create module with public API, private state Pre-ES6 modules, encapsulation

Example: IIFE basics and variations

// Basic IIFE - executes immediately
(function() {
    const message = 'Hello from IIFE';
    console.log(message);
})();  // Logs: Hello from IIFE
// 'message' not accessible here (private scope)

// IIFE with parameters
(function(name) {
    console.log(`Hello, ${name}!`);
})('Alice');  // Hello, Alice!

// IIFE returning value
const config = (function() {
    const privateKey = 'secret123';
    return {
        apiUrl: 'https://api.example.com',
        timeout: 5000,
        getKey: () => privateKey  // Controlled access
    };
})();
console.log(config.apiUrl);  // 'https://api.example.com'
console.log(config.privateKey);  // undefined (private)

// Arrow function IIFE (ES6+)
(() => {
    const temp = 'temporary variable';
    console.log(temp);
})();

// Named IIFE for recursion
(function factorial(n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
})(5);  // 120

// IIFE for variable capture in loops (pre-let solution)
for (var i = 0; i < 3; i++) {
    (function(index) {
        setTimeout(() => console.log(index), 100);
    })(i);  // Capture current value of i
}

Example: Module pattern with IIFE

// Classic Module Pattern
const Calculator = (function() {
    // Private variables and functions
    let history = [];
    
    function log(operation, result) {
        history.push({ operation, result, timestamp: Date.now() });
    }
    
    // Public API
    return {
        add(a, b) {
            const result = a + b;
            log('add', result);
            return result;
        },
        subtract(a, b) {
            const result = a - b;
            log('subtract', result);
            return result;
        },
        getHistory() {
            return [...history];  // Return copy, not reference
        },
        clearHistory() {
            history = [];
        }
    };
})();

Calculator.add(5, 3);  // 8
Calculator.subtract(10, 4);  // 6
console.log(Calculator.getHistory());  // Array of operations
console.log(Calculator.history);  // undefined (private)

// Revealing Module Pattern
const Counter = (function() {
    let count = 0;
    
    function increment() { return ++count; }
    function decrement() { return --count; }
    function getValue() { return count; }
    function reset() { count = 0; }
    
    // Reveal selected functions
    return {
        increment,
        decrement,
        getValue,
        reset
    };
})();

Counter.increment();  // 1
Counter.increment();  // 2
console.log(Counter.getValue());  // 2

// Module with dependencies
const UserManager = (function($, _) {
    // Use jQuery ($) and lodash (_) passed as parameters
    function getUsers() {
        return _.map(users, user => user.name);
    }
    
    return { getUsers };
})(jQuery, lodash);  // Inject dependencies
Modern Alternative: ES6 modules (import/export) have largely replaced IIFE-based modules. However, IIFEs remain useful for: creating private scope, avoiding global pollution, one-time initialization code, and browser compatibility when modules aren't available.

6.7 Function Methods (call, apply, bind)

Method Syntax Arguments Returns When Called
call() fn.call(thisArg, arg1, arg2, ...) Individual arguments Function result Immediately
apply() fn.apply(thisArg, [args]) Array of arguments Function result Immediately
bind() fn.bind(thisArg, arg1, ...) Individual arguments (partial) New bound function Later (returned fn)
Use Case Best Method Why
Set 'this' and call now call() Known number of arguments, immediate execution
Set 'this' with array args apply() Arguments already in array, or use Math.max/min with arrays
Set 'this' for later use bind() Event handlers, callbacks, partial application
Partial application bind() Pre-fill some arguments, returns reusable function
Borrowing methods call() or apply() Use array methods on array-like objects

Example: call() and apply()

const person = {
    name: 'Alice',
    greet(greeting, punctuation) {
        return `${greeting}, I'm ${this.name}${punctuation}`;
    }
};

console.log(person.greet('Hello', '!'));  // "Hello, I'm Alice!"

// call() - set 'this' and pass individual arguments
const otherPerson = { name: 'Bob' };
console.log(person.greet.call(otherPerson, 'Hi', '.'));
// "Hi, I'm Bob."

// apply() - set 'this' and pass arguments as array
console.log(person.greet.apply(otherPerson, ['Hey', '...']));
// "Hey, I'm Bob..."

// Practical: borrowing array methods for array-like objects
function sumAll() {
    // 'arguments' is array-like, not real array
    // Borrow reduce() from Array
    return Array.prototype.reduce.call(arguments, (sum, n) => sum + n, 0);
}
console.log(sumAll(1, 2, 3, 4));  // 10

// Math.max with apply (pre-spread operator)
const numbers = [5, 2, 9, 1, 7];
const max = Math.max.apply(null, numbers);  // 9
// Modern: Math.max(...numbers)

// Invoking function with specific context
function introduce() {
    return `My name is ${this.name} and I'm ${this.age}`;
}
const user = { name: 'Charlie', age: 30 };
console.log(introduce.call(user));
// "My name is Charlie and I'm 30"

Example: bind() for permanent binding

const obj = {
    value: 42,
    getValue() {
        return this.value;
    }
};

// Problem: 'this' lost when method assigned to variable
const getValue = obj.getValue;
console.log(getValue());  // undefined ('this' is window/undefined)

// Solution: bind() creates new function with fixed 'this'
const boundGetValue = obj.getValue.bind(obj);
console.log(boundGetValue());  // 42

// Practical: event handlers
class Button {
    constructor(label) {
        this.label = label;
        this.clickCount = 0;
    }
    
    handleClick() {
        this.clickCount++;
        console.log(`${this.label} clicked ${this.clickCount} times`);
    }
}

const btn = new Button('Submit');
// Without bind, 'this' would be the button element, not our object
document.querySelector('button').addEventListener('click', 
    btn.handleClick.bind(btn)
);

// Partial application with bind()
function multiply(a, b) {
    return a * b;
}
const double = multiply.bind(null, 2);  // Pre-fill first argument
const triple = multiply.bind(null, 3);
console.log(double(5));  // 10
console.log(triple(5));  // 15

// Binding with setTimeout
const timer = {
    seconds: 0,
    start() {
        // bind() ensures 'this' refers to timer object
        setInterval(function() {
            this.seconds++;
            console.log(this.seconds);
        }.bind(this), 1000);
        
        // Modern alternative: arrow function (lexical 'this')
        // setInterval(() => { this.seconds++; }, 1000);
    }
};

// Multiple bind() calls - first one wins
function log() { console.log(this.value); }
const obj1 = { value: 1 };
const obj2 = { value: 2 };
const bound1 = log.bind(obj1);
const bound2 = bound1.bind(obj2);  // Has no effect
bound2();  // 1 (not 2 - can't rebind)
Modern Context: Arrow functions (=>) with lexical this have reduced the need for bind() in many cases. However, call(), apply(), and bind() remain essential for: method borrowing, explicit context control, partial application, and working with legacy code.

6.8 Recursion and Tail Call Optimization

Concept Definition Characteristics
Recursion Function calling itself to solve smaller subproblems Base case + recursive case; elegant for tree/graph problems
Base Case Condition that stops recursion without further calls Prevents infinite recursion; returns direct result
Recursive Case Logic that breaks problem down and calls function recursively Progress toward base case; must modify parameters
Call Stack Stack storing execution contexts for function calls Each recursive call adds frame; limited size (stack overflow risk)
Tail Call Function call that's the last operation (nothing after return) Eligible for optimization; no pending operations
Tail Call Optimization (TCO) LIMITED Reuse stack frame for tail calls instead of creating new ones ES6 spec, but poor browser support; prevents stack overflow
Pattern Stack Usage TCO Eligible When to Use
Standard Recursion O(n) stack frames ✗ No (pending operations after call) Simple problems, small input size
Tail Recursion O(1) with TCO, O(n) without ✓ Yes (return is direct recursive call) Large inputs if TCO available
Accumulator Pattern O(1) with TCO ✓ Yes (pass result through parameters) Convert standard recursion to tail recursion
Iteration (Loop) O(1) stack N/A (no recursion) Production code, guaranteed stack safety

Example: Basic recursion patterns

// Standard recursion - NOT tail recursive
function factorial(n) {
    // Base case
    if (n <= 1) return 1;
    
    // Recursive case - operation AFTER recursive call
    return n * factorial(n - 1);  // ✗ Not tail call (multiplication pending)
}
console.log(factorial(5));  // 120
// Stack: factorial(5) waits for factorial(4)
//        factorial(4) waits for factorial(3), etc.

// Tail recursive version - uses accumulator
function factorialTail(n, acc = 1) {
    // Base case
    if (n <= 1) return acc;
    
    // Recursive case - nothing after recursive call
    return factorialTail(n - 1, n * acc);  // ✓ Tail call (direct return)
}
console.log(factorialTail(5));  // 120
// With TCO: reuses same stack frame

// Fibonacci - standard recursion (exponential time!)
function fib(n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);  // Two recursive calls
}
// Very slow for large n: fib(40) takes seconds

// Fibonacci - tail recursive with two accumulators
function fibTail(n, a = 0, b = 1) {
    if (n === 0) return a;
    return fibTail(n - 1, b, a + b);  // Tail call
}
console.log(fibTail(40));  // Fast even for large n

// Array sum - standard recursion
function sumArray(arr) {
    if (arr.length === 0) return 0;
    return arr[0] + sumArray(arr.slice(1));  // ✗ Not tail call
}

// Array sum - tail recursive
function sumArrayTail(arr, acc = 0) {
    if (arr.length === 0) return acc;
    return sumArrayTail(arr.slice(1), acc + arr[0]);  // ✓ Tail call
}

Example: Converting to tail recursion

// Problem: countdown - NOT tail recursive
function countdown(n) {
    if (n < 0) return;
    console.log(n);
    countdown(n - 1);
    console.log('Done', n);  // ✗ Operation after recursive call
}

// Solution: tail recursive countdown
function countdownTail(n) {
    if (n < 0) return;
    console.log(n);
    return countdownTail(n - 1);  // ✓ Tail call
}

// Problem: power - NOT tail recursive
function power(base, exp) {
    if (exp === 0) return 1;
    return base * power(base, exp - 1);  // ✗ Multiplication pending
}

// Solution: tail recursive with accumulator
function powerTail(base, exp, acc = 1) {
    if (exp === 0) return acc;
    return powerTail(base, exp - 1, acc * base);  // ✓ Tail call
}

// Tree traversal - naturally recursive
function sumTree(node) {
    if (!node) return 0;
    return node.value 
        + sumTree(node.left) 
        + sumTree(node.right);
    // Not tail recursive but appropriate for trees
}

// Tail recursive tree traversal (using continuation)
function sumTreeTail(node, cont = x => x) {
    if (!node) return cont(0);
    return sumTreeTail(node.left, leftSum =>
        sumTreeTail(node.right, rightSum =>
            cont(node.value + leftSum + rightSum)
        )
    );
}
Important Limitations:
  • TCO Support: Proper tail call optimization is in ES6 spec but only Safari implements it. Chrome/Firefox/Edge don't support TCO.
  • Stack Overflow Risk: Without TCO, even tail-recursive functions will overflow stack with large inputs (typically 10,000-15,000 calls).
  • Production Code: For critical code, use iteration (loops) instead of recursion to guarantee stack safety.
  • Trampoline: Workaround pattern to simulate TCO, but adds complexity.

Example: When to use recursion vs iteration

// ✓ Good use of recursion - tree/graph structures
function findNode(tree, value) {
    if (!tree) return null;
    if (tree.value === value) return tree;
    return findNode(tree.left, value) || findNode(tree.right, value);
}

// ✓ Good use of recursion - divide and conquer
function binarySearch(arr, target, left = 0, right = arr.length - 1) {
    if (left > right) return -1;
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] === target) return mid;
    if (arr[mid] > target) return binarySearch(arr, target, left, mid - 1);
    return binarySearch(arr, target, mid + 1, right);
}

// ✗ Bad use of recursion - simple iteration better
function sumToN(n) {
    if (n === 0) return 0;
    return n + sumToN(n - 1);  // Just use n * (n + 1) / 2
}

// ✓ Better: iterative solution
function sumToNIterative(n) {
    let sum = 0;
    for (let i = 1; i <= n; i++) {
        sum += i;
    }
    return sum;
    // Or: return n * (n + 1) / 2;
}

// Trampoline pattern (TCO workaround)
function trampoline(fn) {
    while (typeof fn === 'function') {
        fn = fn();  // Keep calling until not a function
    }
    return fn;
}

function factorialTrampoline(n, acc = 1) {
    if (n <= 1) return acc;
    return () => factorialTrampoline(n - 1, n * acc);  // Return thunk
}

console.log(trampoline(factorialTrampoline(100000)));  // No stack overflow

Section 6 Summary

  • Function types: Declarations (hoisted), expressions (not hoisted), arrows (lexical this), generators (yield), async (await)
  • Arrow functions: Concise syntax, lexical this, no arguments/new/prototype, ideal for callbacks
  • Parameters: Default values, rest (...args), destructuring (array/object), combine for flexible APIs
  • Higher-order functions: Take/return functions; enables map/filter/reduce, composition, abstraction
  • Closures: Functions retaining outer scope access; enables private state, factories, memoization
  • IIFE: Immediately-invoked functions; creates private scope, module pattern (pre-ES6 modules)
  • call/apply/bind: Control this context; call/apply execute now, bind returns new function
  • Recursion: Self-calling functions; elegant for trees/graphs; tail recursion + TCO prevents stack overflow (limited support)

7. Objects and Property Management

7.1 Object Literal Syntax and Computed Properties

Feature Syntax ES Version Description
Basic Object Literal { key: value } ES3 Traditional property definition with static keys
Property Shorthand ES6 { name } ES6 Equivalent to { name: name } when variable name matches key
Method Shorthand ES6 { method() {} } ES6 Concise method definition, equivalent to { method: function() {} }
Computed Property ES6 { [expression]: value } ES6 Property name computed at runtime from expression
Computed Method ES6 { [expr]() {} } ES6 Method name computed dynamically
Spread Properties ES2018 { ...obj } ES2018 Copy enumerable properties from source object
Getters/Setters { get prop() {}, set prop(v) {} } ES5 Define accessor properties with custom logic

Example: Modern object literal syntax

// ES6+ object literal features
const name = 'Alice';
const age = 30;
const propKey = 'dynamicKey';

const user = {
    // Property shorthand (ES6)
    name,  // Same as name: name
    age,   // Same as age: age
    
    // Method shorthand (ES6)
    greet() {
        return `Hello, I'm ${this.name}`;
    },
    
    // Computed property name (ES6)
    [propKey]: 'dynamic value',
    [`computed_${name}`]: true,
    
    // Computed method name (ES6)
    [`get${name}Info`]() {
        return `${this.name} is ${this.age}`;
    },
    
    // Traditional syntax still works
    email: 'alice@example.com',
    
    // Arrow function as property (not a method)
    arrowFunc: () => 'arrow',
    
    // Getter and setter
    get fullInfo() {
        return `${this.name}, ${this.age} years old`;
    },
    set fullInfo(info) {
        [this.name, this.age] = info.split(', ');
    }
};

console.log(user.name);  // 'Alice'
console.log(user.greet());  // 'Hello, I'm Alice'
console.log(user.dynamicKey);  // 'dynamic value'
console.log(user.computed_Alice);  // true
console.log(user.getAliceInfo());  // 'Alice is 30'
console.log(user.fullInfo);  // 'Alice, 30 years old'

Example: Computed properties in practice

// Dynamic property names
const fields = ['id', 'name', 'email'];
const values = [1, 'Bob', 'bob@example.com'];

// Build object with computed properties
const user = fields.reduce((obj, field, index) => {
    obj[field] = values[index];
    return obj;
}, {});
// { id: 1, name: 'Bob', email: 'bob@example.com' }

// Using computed properties in literal
const status = 'active';
const config = {
    [`is${status.charAt(0).toUpperCase() + status.slice(1)}`]: true
};
// { isActive: true }

// Symbol as computed property
const SECRET = Symbol('secret');
const obj = {
    public: 'visible',
    [SECRET]: 'hidden'  // Property name is a symbol
};
console.log(obj.public);  // 'visible'
console.log(obj[SECRET]);  // 'hidden'
console.log(Object.keys(obj));  // ['public'] - symbols not enumerable

// Spread properties (ES2018)
const defaults = { theme: 'dark', lang: 'en' };
const userPrefs = { lang: 'fr', fontSize: 14 };
const settings = { ...defaults, ...userPrefs };
// { theme: 'dark', lang: 'fr', fontSize: 14 }
// userPrefs.lang overwrites defaults.lang

7.2 Property Descriptors and Object.defineProperty

Descriptor Attribute Type Default (defineProperty) Default (Literal) Description
value any undefined property value The property's value (data descriptor)
writable boolean false true Can property value be changed?
enumerable boolean false true Shows in for...in, Object.keys()?
configurable boolean false true Can descriptor be changed? Can property be deleted?
get function undefined N/A Getter function (accessor descriptor)
set function undefined N/A Setter function (accessor descriptor)
Method Syntax Description
Object.defineProperty() Object.defineProperty(obj, prop, descriptor) Define or modify single property with full control over attributes
Object.defineProperties() Object.defineProperties(obj, descriptors) Define or modify multiple properties at once
Object.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor(obj, prop) Get descriptor object for single property
Object.getOwnPropertyDescriptors() ES2017 Object.getOwnPropertyDescriptors(obj) Get descriptors for all own properties

Example: Property descriptors in action

const obj = {};

// Define property with custom descriptor
Object.defineProperty(obj, 'name', {
    value: 'Alice',
    writable: false,     // Cannot change value
    enumerable: true,    // Shows in for...in, Object.keys()
    configurable: false  // Cannot delete or reconfigure
});

obj.name = 'Bob';  // Fails silently (strict mode: TypeError)
console.log(obj.name);  // 'Alice' (unchanged)

delete obj.name;  // Fails silently (configurable: false)
console.log(obj.name);  // 'Alice' (still exists)

// Check property descriptor
const desc = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(desc);
// { value: 'Alice', writable: false, enumerable: true, configurable: false }

// Compare with literal property (all attributes true)
const obj2 = { name: 'Charlie' };
const desc2 = Object.getOwnPropertyDescriptor(obj2, 'name');
console.log(desc2);
// { value: 'Charlie', writable: true, enumerable: true, configurable: true }

Example: Accessor properties with get/set

const person = {
    firstName: 'John',
    lastName: 'Doe'
};

// Define computed property with getter/setter
Object.defineProperty(person, 'fullName', {
    get() {
        return `${this.firstName} ${this.lastName}`;
    },
    set(value) {
        [this.firstName, this.lastName] = value.split(' ');
    },
    enumerable: true,
    configurable: true
});

console.log(person.fullName);  // 'John Doe'
person.fullName = 'Jane Smith';
console.log(person.firstName);  // 'Jane'
console.log(person.lastName);  // 'Smith'

// Define multiple properties at once
const account = {};
Object.defineProperties(account, {
    _balance: {
        value: 0,
        writable: true,
        enumerable: false  // Private-like (convention)
    },
    balance: {
        get() { return this._balance; },
        set(val) {
            if (val < 0) throw new Error('Balance cannot be negative');
            this._balance = val;
        },
        enumerable: true
    },
    currency: {
        value: 'USD',
        writable: false,
        enumerable: true
    }
});

console.log(account.balance);  // 0
account.balance = 100;
console.log(account.balance);  // 100
// account.balance = -50;  // Error: Balance cannot be negative

Example: Non-enumerable and non-configurable properties

const obj = { a: 1, b: 2 };

// Add non-enumerable property
Object.defineProperty(obj, 'hidden', {
    value: 'secret',
    enumerable: false  // Won't show in iteration
});

console.log(obj.hidden);  // 'secret' (accessible directly)
console.log(Object.keys(obj));  // ['a', 'b'] (hidden not listed)
for (let key in obj) {
    console.log(key);  // Only logs 'a' and 'b'
}

// Non-configurable property
Object.defineProperty(obj, 'permanent', {
    value: 'cannot change',
    configurable: false
});

// Cannot reconfigure
try {
    Object.defineProperty(obj, 'permanent', {
        value: 'new value'  // TypeError: cannot redefine
    });
} catch (e) {
    console.log('Cannot reconfigure');
}

// Cannot delete
delete obj.permanent;  // Fails silently
console.log(obj.permanent);  // Still exists

// Get all descriptors (ES2017)
const allDescriptors = Object.getOwnPropertyDescriptors(obj);
console.log(allDescriptors.hidden);
// { value: 'secret', writable: false, enumerable: false, configurable: false }
Important: Data descriptors (value/writable) and accessor descriptors (get/set) are mutually exclusive. A property can have value OR get/set, not both. Non-configurable properties cannot be deleted or have their descriptor changed (except writable can go from true to false).

7.3 Object Methods (Object.keys, Object.values, Object.entries)

Method Returns ES Version Description
Object.keys(obj) Array of strings ES5 Own enumerable property names (keys)
Object.values(obj) ES2017 Array of values ES2017 Own enumerable property values
Object.entries(obj) ES2017 Array of [key, value] pairs ES2017 Own enumerable properties as key-value pairs
Object.fromEntries(entries) ES2019 Object ES2019 Create object from array of [key, value] pairs (inverse of entries)
Object.getOwnPropertyNames(obj) Array of strings ES5 All own property names (including non-enumerable)
Object.getOwnPropertySymbols(obj) ES6 Array of symbols ES6 All own symbol properties
Object.hasOwn(obj, prop) ES2022 boolean ES2022 Safe alternative to hasOwnProperty (recommended)
obj.hasOwnProperty(prop) boolean ES3 Check if property is own (not inherited); use Object.hasOwn instead
Comparison for...in Object.keys() Object.getOwnPropertyNames()
Own Properties ✓ Yes ✓ Yes ✓ Yes
Inherited Properties ✓ Yes (from prototype chain) ✗ No ✗ No
Non-enumerable ✗ No ✗ No ✓ Yes
Symbol Properties ✗ No ✗ No ✗ No (use getOwnPropertySymbols)

Example: Object.keys, values, entries

const user = {
    id: 1,
    name: 'Alice',
    email: 'alice@example.com',
    active: true
};

// Get all keys
const keys = Object.keys(user);
console.log(keys);  // ['id', 'name', 'email', 'active']

// Get all values (ES2017)
const values = Object.values(user);
console.log(values);  // [1, 'Alice', 'alice@example.com', true]

// Get key-value pairs (ES2017)
const entries = Object.entries(user);
console.log(entries);
// [['id', 1], ['name', 'Alice'], ['email', 'alice@example.com'], ['active', true]]

// Iterate over entries
Object.entries(user).forEach(([key, value]) => {
    console.log(`${key}: ${value}`);
});

// Transform object using entries
const doubled = Object.fromEntries(
    Object.entries({ a: 1, b: 2, c: 3 })
        .map(([key, value]) => [key, value * 2])
);
console.log(doubled);  // { a: 2, b: 4, c: 6 }

// Filter object properties
const activeUsers = Object.fromEntries(
    Object.entries({ u1: { active: true }, u2: { active: false } })
        .filter(([key, val]) => val.active)
);
// { u1: { active: true } }

Example: Enumerable vs non-enumerable properties

const obj = { a: 1, b: 2 };

// Add non-enumerable property
Object.defineProperty(obj, 'hidden', {
    value: 'secret',
    enumerable: false
});

// Add symbol property
const sym = Symbol('symbol');
obj[sym] = 'symbol value';

console.log(Object.keys(obj));  // ['a', 'b'] (only enumerable string keys)
console.log(Object.getOwnPropertyNames(obj));  // ['a', 'b', 'hidden'] (includes non-enumerable)
console.log(Object.getOwnPropertySymbols(obj));  // [Symbol(symbol)]

// for...in includes inherited enumerable properties
function Parent() {}
Parent.prototype.inherited = 'from parent';

const child = Object.create(new Parent());
child.own = 'own property';

for (let key in child) {
    console.log(key);  // Logs: 'own', 'inherited'
}

// Filter to own properties only
for (let key in child) {
    if (Object.hasOwn(child, key)) {  // ES2022
        console.log(key);  // Logs: 'own' only
    }
}

// Alternative: hasOwnProperty (older)
for (let key in child) {
    if (child.hasOwnProperty(key)) {
        console.log(key);  // Logs: 'own' only
    }
}

Example: Practical use cases

// Count object properties
const obj = { a: 1, b: 2, c: 3 };
const count = Object.keys(obj).length;  // 3

// Check if object is empty
const isEmpty = Object.keys(obj).length === 0;

// Merge objects by converting to entries
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = Object.fromEntries([
    ...Object.entries(obj1),
    ...Object.entries(obj2)
]);
// { a: 1, b: 3, c: 4 } (obj2 overwrites obj1)

// Convert object to Map
const map = new Map(Object.entries(obj));

// Convert Map to object
const objFromMap = Object.fromEntries(map);

// Swap keys and values
const original = { a: 1, b: 2, c: 3 };
const swapped = Object.fromEntries(
    Object.entries(original).map(([k, v]) => [v, k])
);
// { 1: 'a', 2: 'b', 3: 'c' }

// Pick specific properties
function pick(obj, keys) {
    return Object.fromEntries(
        keys.filter(key => key in obj)
            .map(key => [key, obj[key]])
    );
}
const user = { id: 1, name: 'Alice', email: 'a@example.com', age: 30 };
const userPreview = pick(user, ['id', 'name']);
// { id: 1, name: 'Alice' }

7.4 Object Creation Patterns (Object.create, constructors)

Pattern Syntax Prototype Use Case
Object Literal const obj = { } Object.prototype Simple data objects, quick creation
Object() Constructor const obj = new Object() Object.prototype Rarely used (literal preferred)
Object.create() ES5 Object.create(proto, descriptors?) Specified proto Prototypal inheritance, null prototype objects
Constructor Function function Ctor() { } + new Ctor() Ctor.prototype Pre-ES6 classes, instance creation with shared methods
Factory Function function create() { return { }; } Object.prototype (or custom) Object creation without 'new', private state
Class Syntax ES6 class C { } + new C() C.prototype Modern OOP, cleaner syntax over constructors
Method Description Returns
Object.create(proto) Create new object with specified prototype New object inheriting from proto
Object.create(proto, descriptors) Create object with prototype and property descriptors New object with specified properties
Object.create(null) Create object with no prototype (truly empty) Object without any inherited properties
Object.setPrototypeOf(obj, proto) Change prototype of existing object (slow, avoid) The modified object
Object.getPrototypeOf(obj) Get prototype of object Prototype object or null

Example: Object.create() patterns

// Create object with specific prototype
const personProto = {
    greet() {
        return `Hello, I'm ${this.name}`;
    }
};

const person = Object.create(personProto);
person.name = 'Alice';
console.log(person.greet());  // "Hello, I'm Alice"
console.log(Object.getPrototypeOf(person) === personProto);  // true

// Create with property descriptors
const user = Object.create(personProto, {
    name: {
        value: 'Bob',
        writable: true,
        enumerable: true
    },
    age: {
        value: 30,
        writable: true,
        enumerable: true
    }
});
console.log(user.name);  // 'Bob'
console.log(user.greet());  // "Hello, I'm Bob"

// Create object with null prototype (no inherited properties)
const dict = Object.create(null);
dict.key = 'value';
console.log(dict.toString);  // undefined (no inherited methods)
console.log(dict.hasOwnProperty);  // undefined
// Useful for pure data storage, no prototype pollution

// Compare with literal
const literal = {};
console.log(literal.toString);  // [Function: toString] (inherited)
console.log(Object.getPrototypeOf(literal) === Object.prototype);  // true

Example: Constructor functions

// Constructor function (pre-ES6 classes)
function Person(name, age) {
    // Instance properties (unique per instance)
    this.name = name;
    this.age = age;
}

// Shared methods on prototype (memory efficient)
Person.prototype.greet = function() {
    return `Hello, I'm ${this.name}`;
};

Person.prototype.getAge = function() {
    return this.age;
};

// Create instances with 'new'
const alice = new Person('Alice', 30);
const bob = new Person('Bob', 25);

console.log(alice.greet());  // "Hello, I'm Alice"
console.log(bob.greet());  // "Hello, I'm Bob"
console.log(alice.greet === bob.greet);  // true (shared method)

// Verify prototype
console.log(Object.getPrototypeOf(alice) === Person.prototype);  // true
console.log(alice instanceof Person);  // true

// Forgetting 'new' - common mistake
function SafeConstructor(name) {
    // Guard against missing 'new'
    if (!(this instanceof SafeConstructor)) {
        return new SafeConstructor(name);
    }
    this.name = name;
}

const instance1 = new SafeConstructor('Alice');
const instance2 = SafeConstructor('Bob');  // Works without 'new'
console.log(instance2 instanceof SafeConstructor);  // true

Example: Factory functions

// Factory function - no 'new' needed
function createPerson(name, age) {
    // Private variables (closure)
    let privateData = 'secret';
    
    // Return object with public interface
    return {
        name,
        age,
        greet() {
            return `Hello, I'm ${this.name}`;
        },
        getPrivate() {
            return privateData;  // Access private via closure
        }
    };
}

const person1 = createPerson('Alice', 30);  // No 'new'
const person2 = createPerson('Bob', 25);

console.log(person1.greet());  // "Hello, I'm Alice"
console.log(person1.getPrivate());  // 'secret'
console.log(person1.privateData);  // undefined (truly private)

// Factory with prototypal inheritance
function createUser(name) {
    const proto = {
        greet() { return `Hello ${this.name}`; }
    };
    
    const user = Object.create(proto);
    user.name = name;
    return user;
}

const user = createUser('Charlie');
console.log(user.greet());  // "Hello Charlie"

// Modern: ES6 class (preferred over constructors)
class PersonClass {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    greet() {
        return `Hello, I'm ${this.name}`;
    }
}

const personInstance = new PersonClass('Dave', 35);
console.log(personInstance.greet());  // "Hello, I'm Dave"
Best Practice: Modern JavaScript prefers ES6 classes over constructor functions for clarity. Use factory functions when you need private state via closures or want to avoid new. Use Object.create(null) for dictionaries/maps to avoid prototype pollution. Avoid Object.setPrototypeOf() as it's very slow.

7.5 Object Cloning and Merging (Object.assign, spread)

Method Syntax Copy Type Nested Objects
Object.assign() ES6 Object.assign(target, ...sources) Shallow copy Copies references (not deep)
Spread Operator ES2018 { ...obj } Shallow copy Copies references (not deep)
JSON.parse/stringify JSON.parse(JSON.stringify(obj)) Deep copy ✓ Clones nested, but loses functions/symbols/dates
structuredClone() NEW structuredClone(obj) Deep copy ✓ Clones nested, preserves types (not functions)
Manual Deep Clone Recursive function Deep copy ✓ Full control, handle all types
Feature Object.assign() Spread {...} Differences
Copy enumerable props ✓ Yes ✓ Yes Both copy own enumerable properties
Trigger setters ✓ Yes ✗ No assign() invokes setters, spread defines new props
Copy getters/setters ✗ No (executes getter) ✗ No (executes getter) Both execute getters, copy resulting values
Mutate target ✓ Yes (modifies first arg) ✗ No (creates new object) assign() mutates, spread creates new
Syntax Function call Operator (concise) Spread is more concise and readable

Example: Shallow copying with Object.assign and spread

const original = {
    name: 'Alice',
    age: 30,
    address: { city: 'NYC', zip: '10001' }
};

// Object.assign - shallow copy
const copy1 = Object.assign({}, original);
copy1.name = 'Bob';
copy1.address.city = 'LA';  // Modifies nested object

console.log(original.name);  // 'Alice' (primitive copied)
console.log(original.address.city);  // 'LA' (nested reference shared!)

// Spread operator - shallow copy (ES2018)
const copy2 = { ...original };
copy2.age = 25;
copy2.address.zip = '90001';  // Also modifies original!

console.log(original.age);  // 30 (primitive copied)
console.log(original.address.zip);  // '90001' (nested reference shared!)

// Merge multiple objects
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const obj3 = { c: 5, d: 6 };

// Using Object.assign
const merged1 = Object.assign({}, obj1, obj2, obj3);
// { a: 1, b: 3, c: 5, d: 6 } (later values overwrite)

// Using spread (more common)
const merged2 = { ...obj1, ...obj2, ...obj3 };
// { a: 1, b: 3, c: 5, d: 6 } (same result)

// Add/override properties while spreading
const enhanced = {
    ...original,
    age: 35,  // Override
    email: 'alice@example.com'  // Add new
};
// { name: 'Alice', age: 35, address: {...}, email: '...' }

Example: Deep cloning techniques

const original = {
    name: 'Alice',
    age: 30,
    hobbies: ['reading', 'coding'],
    address: { city: 'NYC', coords: { lat: 40, lng: -74 } },
    createdAt: new Date('2024-01-01')
};

// Method 1: JSON (quick but lossy)
const jsonClone = JSON.parse(JSON.stringify(original));
jsonClone.hobbies.push('gaming');
jsonClone.address.city = 'LA';

console.log(original.hobbies);  // ['reading', 'coding'] (not affected)
console.log(original.address.city);  // 'NYC' (not affected)
console.log(jsonClone.createdAt);  // String, not Date! (limitation)

// Method 2: structuredClone() (modern, recommended)
const structClone = structuredClone(original);
structClone.address.coords.lat = 35;

console.log(original.address.coords.lat);  // 40 (not affected)
console.log(structClone.createdAt instanceof Date);  // true (preserved!)

// Limitations: cannot clone functions
const withFunction = { 
    data: 'value', 
    method() { return this.data; } 
};
// structuredClone(withFunction);  // Error: functions not cloneable

// Method 3: Manual deep clone (full control)
function deepClone(obj, seen = new WeakMap()) {
    // Handle primitives and null
    if (obj === null || typeof obj !== 'object') return obj;
    
    // Handle circular references
    if (seen.has(obj)) return seen.get(obj);
    
    // Handle Date
    if (obj instanceof Date) return new Date(obj);
    
    // Handle Array
    if (Array.isArray(obj)) {
        const arrCopy = [];
        seen.set(obj, arrCopy);
        obj.forEach((item, i) => {
            arrCopy[i] = deepClone(item, seen);
        });
        return arrCopy;
    }
    
    // Handle Object
    const objCopy = {};
    seen.set(obj, objCopy);
    Object.keys(obj).forEach(key => {
        objCopy[key] = deepClone(obj[key], seen);
    });
    return objCopy;
}

const manualClone = deepClone(original);
manualClone.address.coords.lng = -118;
console.log(original.address.coords.lng);  // -74 (not affected)

Example: Object.assign vs spread differences

// Difference 1: Mutating vs creating
const target = { a: 1 };
const source = { b: 2 };

Object.assign(target, source);  // Mutates target
console.log(target);  // { a: 1, b: 2 } (modified)

const newObj = { ...target, ...source };  // Creates new object
// target unchanged

// Difference 2: Setters
const obj = {
    _value: 0,
    set value(v) {
        console.log('Setter called:', v);
        this._value = v;
    }
};

// Object.assign triggers setter
Object.assign(obj, { value: 10 });  // Logs: "Setter called: 10"

// Spread defines new property (doesn't trigger setter)
const copy = { ...obj, value: 20 };  // No setter call
console.log(copy.value);  // 20 (simple property, not setter)

// Use case: Default options
function configure(options = {}) {
    const defaults = {
        timeout: 5000,
        retries: 3,
        cache: true
    };
    
    // Merge with user options (spread preferred)
    return { ...defaults, ...options };
}

const config1 = configure({ timeout: 10000 });
// { timeout: 10000, retries: 3, cache: true }

const config2 = configure({ retries: 5, debug: true });
// { timeout: 5000, retries: 5, cache: true, debug: true }
Warning: Both Object.assign() and spread create shallow copies. Nested objects are copied by reference, not value. Modifying nested properties affects the original. For true deep copies, use structuredClone() (modern) or implement custom deep clone logic.

7.6 Property Getters and Setters

Syntax Form Example When to Use
Object Literal Getter { get prop() { return value; } } Computed properties, validation on read
Object Literal Setter { set prop(val) { this._prop = val; } } Validation/transformation on write
defineProperty Getter Object.defineProperty(obj, 'prop', { get() {} }) Add getter to existing object
defineProperty Setter Object.defineProperty(obj, 'prop', { set(v) {} }) Add setter to existing object
Class Getter class C { get prop() { } } ES6 class computed properties
Class Setter class C { set prop(v) { } } ES6 class property validation
Use Case Pattern Example
Computed Property Getter derives value from other properties get fullName() { return this.first + ' ' + this.last; }
Validation Setter validates before assignment set age(v) { if (v < 0) throw Error; this._age = v; }
Lazy Initialization Getter computes value on first access get data() { return this._data || (this._data = load()); }
Side Effects Setter triggers additional actions set value(v) { this._value = v; this.notify(); }
Read-only Property Getter without setter get id() { return this._id; } (no setter)
Private Backing Field Store value in _property, expose via getter/setter get name() { return this._name; }

Example: Getters and setters in object literals

const person = {
    firstName: 'John',
    lastName: 'Doe',
    _age: 30,  // Backing field (convention: underscore = private-like)
    
    // Getter: computed property
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    },
    
    // Setter: parse and set multiple properties
    set fullName(name) {
        const parts = name.split(' ');
        this.firstName = parts[0];
        this.lastName = parts[1];
    },
    
    // Getter with validation/transformation
    get age() {
        return this._age;
    },
    
    // Setter with validation
    set age(value) {
        if (typeof value !== 'number' || value < 0 || value > 150) {
            throw new Error('Invalid age');
        }
        this._age = value;
    },
    
    // Read-only property (getter only)
    get birthYear() {
        return new Date().getFullYear() - this._age;
    }
};

// Using getters and setters
console.log(person.fullName);  // "John Doe" (getter called)
person.fullName = 'Jane Smith';  // Setter called
console.log(person.firstName);  // 'Jane'
console.log(person.lastName);  // 'Smith'

console.log(person.age);  // 30 (getter)
person.age = 25;  // Setter with validation
// person.age = -5;  // Error: Invalid age

console.log(person.birthYear);  // Computed from age
// person.birthYear = 1990;  // TypeError: no setter (read-only)

Example: Getters/setters in classes

class Rectangle {
    constructor(width, height) {
        this._width = width;
        this._height = height;
    }
    
    // Getter for computed property
    get area() {
        return this._width * this._height;
    }
    
    get perimeter() {
        return 2 * (this._width + this._height);
    }
    
    // Getter/setter with validation
    get width() {
        return this._width;
    }
    
    set width(value) {
        if (value <= 0) {
            throw new Error('Width must be positive');
        }
        this._width = value;
    }
    
    get height() {
        return this._height;
    }
    
    set height(value) {
        if (value <= 0) {
            throw new Error('Height must be positive');
        }
        this._height = value;
    }
}

const rect = new Rectangle(10, 5);
console.log(rect.area);  // 50 (computed)
console.log(rect.perimeter);  // 30 (computed)

rect.width = 20;  // Setter with validation
console.log(rect.area);  // 100 (auto-updated)

// rect.width = -5;  // Error: Width must be positive
// rect.area = 200;  // TypeError: no setter (read-only)

Example: Advanced getter/setter patterns

// Lazy initialization with getter
class DataLoader {
    constructor(url) {
        this.url = url;
        this._data = null;
    }
    
    get data() {
        // Load data on first access, cache for subsequent access
        if (this._data === null) {
            console.log('Loading data...');
            this._data = this.loadData();  // Expensive operation
        }
        return this._data;
    }
    
    loadData() {
        // Simulate data loading
        return { loaded: true, url: this.url };
    }
}

const loader = new DataLoader('/api/data');
console.log(loader.data);  // Logs: "Loading data...", returns data
console.log(loader.data);  // No log, returns cached data

// Observable pattern with setters
class Observable {
    constructor() {
        this._observers = [];
        this._value = null;
    }
    
    get value() {
        return this._value;
    }
    
    set value(newValue) {
        const oldValue = this._value;
        this._value = newValue;
        this.notify(oldValue, newValue);
    }
    
    subscribe(observer) {
        this._observers.push(observer);
    }
    
    notify(oldValue, newValue) {
        this._observers.forEach(fn => fn(newValue, oldValue));
    }
}

const obs = new Observable();
obs.subscribe((newVal, oldVal) => {
    console.log(`Changed from ${oldVal} to ${newVal}`);
});
obs.value = 10;  // Logs: "Changed from null to 10"
obs.value = 20;  // Logs: "Changed from 10 to 20"

// Type conversion in setter
const config = {
    _port: 8080,
    
    get port() {
        return this._port;
    },
    
    set port(value) {
        // Always store as number
        this._port = Number(value);
    }
};

config.port = '3000';  // String input
console.log(config.port);  // 3000 (number)
console.log(typeof config.port);  // 'number'
Best Practice: Use getters for computed properties and derived state. Use setters for validation, type conversion, and triggering side effects. Follow naming convention: use underscore prefix for backing fields (e.g., _value). For true privacy in modern JS, use private fields (#field) instead of convention.

7.7 Object Freezing and Sealing (freeze, seal, preventExtensions)

Method Add Props Delete Props Modify Values Change Config
Normal Object ✓ Yes ✓ Yes ✓ Yes ✓ Yes
Object.preventExtensions() ✗ No ✓ Yes ✓ Yes ✓ Yes
Object.seal() ✗ No ✗ No ✓ Yes ✗ No
Object.freeze() ✗ No ✗ No ✗ No ✗ No
Method Effect Check Method Use Case
Object.preventExtensions(obj) Cannot add new properties Object.isExtensible(obj) Prevent accidental property additions
Object.seal(obj) Cannot add/delete, can modify existing Object.isSealed(obj) Fixed property set, mutable values
Object.freeze(obj) Completely immutable (shallow) Object.isFrozen(obj) Constants, immutable config

Example: Object.preventExtensions()

const obj = { a: 1, b: 2 };

// Prevent adding new properties
Object.preventExtensions(obj);

// Existing properties work normally
obj.a = 10;  // ✓ Works (can modify)
delete obj.b;  // ✓ Works (can delete)
console.log(obj);  // { a: 10 }

// Cannot add new properties
obj.c = 3;  // ✗ Fails silently (strict mode: TypeError)
console.log(obj.c);  // undefined

// Check if extensible
console.log(Object.isExtensible(obj));  // false

// Cannot make extensible again (one-way operation)
// No Object.makeExtensible() method

Example: Object.seal()

const user = { name: 'Alice', age: 30 };

// Seal object - fixed property set
Object.seal(user);

// Can modify existing properties
user.name = 'Bob';  // ✓ Works
user.age = 35;  // ✓ Works
console.log(user);  // { name: 'Bob', age: 35 }

// Cannot add new properties
user.email = 'test@example.com';  // ✗ Fails silently
console.log(user.email);  // undefined

// Cannot delete properties
delete user.age;  // ✗ Fails silently
console.log(user.age);  // 35 (still exists)

// Cannot reconfigure property descriptors
// Object.defineProperty(user, 'name', { enumerable: false });  // ✗ TypeError

// Check if sealed
console.log(Object.isSealed(user));  // true

// Sealed implies non-extensible
console.log(Object.isExtensible(user));  // false

Example: Object.freeze()

const config = {
    API_URL: 'https://api.example.com',
    TIMEOUT: 5000,
    MAX_RETRIES: 3
};

// Freeze object - completely immutable
Object.freeze(config);

// Cannot modify properties
config.TIMEOUT = 10000;  // ✗ Fails silently (strict mode: TypeError)
console.log(config.TIMEOUT);  // 5000 (unchanged)

// Cannot add properties
config.DEBUG = true;  // ✗ Fails silently
console.log(config.DEBUG);  // undefined

// Cannot delete properties
delete config.MAX_RETRIES;  // ✗ Fails silently
console.log(config.MAX_RETRIES);  // 3 (still exists)

// Check if frozen
console.log(Object.isFrozen(config));  // true

// Frozen implies sealed and non-extensible
console.log(Object.isSealed(config));  // true
console.log(Object.isExtensible(config));  // false

// Common use: constants
const CONSTANTS = Object.freeze({
    PI: 3.14159,
    E: 2.71828,
    MAX_INT: 2147483647
});
// CONSTANTS.PI = 3.14;  // ✗ Cannot modify

Example: Shallow vs deep freeze

// Object.freeze is SHALLOW - nested objects not frozen
const user = {
    name: 'Alice',
    settings: {
        theme: 'dark',
        notifications: true
    }
};

Object.freeze(user);

// Top-level frozen
user.name = 'Bob';  // ✗ Fails
console.log(user.name);  // 'Alice' (unchanged)

// Nested object NOT frozen
user.settings.theme = 'light';  // ✓ Works!
user.settings.notifications = false;  // ✓ Works!
console.log(user.settings.theme);  // 'light' (modified)

// Deep freeze implementation
function deepFreeze(obj) {
    // Freeze object itself
    Object.freeze(obj);
    
    // Recursively freeze all object properties
    Object.getOwnPropertyNames(obj).forEach(prop => {
        const value = obj[prop];
        if (value && typeof value === 'object' && !Object.isFrozen(value)) {
            deepFreeze(value);
        }
    });
    
    return obj;
}

const deepUser = {
    name: 'Charlie',
    settings: { theme: 'dark', notifications: true }
};

deepFreeze(deepUser);

// Now nested is also frozen
deepUser.settings.theme = 'light';  // ✗ Fails
console.log(deepUser.settings.theme);  // 'dark' (unchanged)

Example: Practical use cases

// Use case 1: Enum-like constants
const Status = Object.freeze({
    PENDING: 'pending',
    APPROVED: 'approved',
    REJECTED: 'rejected'
});
// Status.PENDING = 'other';  // ✗ Cannot modify

// Use case 2: Configuration objects
const appConfig = Object.freeze({
    version: '1.0.0',
    apiEndpoint: '/api/v1',
    features: {
        auth: true,
        analytics: true
    }
});

// Use case 3: Prevent accidental mutations in function
function processUser(user) {
    // Prevent function from modifying passed object
    const immutableUser = Object.freeze({ ...user });
    
    // immutableUser.name = 'Modified';  // ✗ Fails
    
    // Return new object instead
    return { ...immutableUser, processed: true };
}

// Use case 4: Sealed object with mutable values
const counters = Object.seal({
    clicks: 0,
    views: 0,
    purchases: 0
});

// Can update counters
counters.clicks++;
counters.views += 10;

// But cannot add new counters
// counters.downloads = 0;  // ✗ Fails

// Comparison matrix
const obj1 = { a: 1 };
Object.preventExtensions(obj1);
console.log({
    canAdd: !Object.isExtensible(obj1),      // true (cannot add)
    canDelete: !Object.isSealed(obj1),        // true (can delete)
    canModify: !Object.isFrozen(obj1)         // true (can modify)
});

const obj2 = { a: 1 };
Object.seal(obj2);
console.log({
    canAdd: !Object.isExtensible(obj2),      // true (cannot add)
    canDelete: !Object.isSealed(obj2),        // false (cannot delete)
    canModify: !Object.isFrozen(obj2)         // true (can modify)
});

const obj3 = { a: 1 };
Object.freeze(obj3);
console.log({
    canAdd: !Object.isExtensible(obj3),      // true (cannot add)
    canDelete: !Object.isSealed(obj3),        // false (cannot delete)
    canModify: !Object.isFrozen(obj3)         // false (cannot modify)
});
Important Limitations:
  • Shallow only: All three methods (preventExtensions, seal, freeze) only affect the immediate object, not nested objects
  • Irreversible: Cannot undo these operations - once frozen/sealed, always frozen/sealed
  • Silent failures: In non-strict mode, violations fail silently (no error thrown)
  • Performance: Frozen objects may have optimization benefits but are rarely the bottleneck

Section 7 Summary

  • Modern literals: Property/method shorthand, computed properties, spread syntax, getters/setters for clean object creation
  • Property descriptors: Control writable/enumerable/configurable attributes; defineProperty for fine-grained control
  • Object methods: keys/values/entries for iteration; fromEntries for transformation; hasOwn for safe property checks
  • Creation patterns: Object.create for prototypal inheritance; constructors (legacy) vs classes (modern); factory functions for flexibility
  • Cloning: Spread/assign for shallow copy; structuredClone for deep copy; JSON for simple deep copy (lossy)
  • Getters/setters: Computed properties, validation, lazy initialization; use _prefix convention for backing fields
  • Immutability: preventExtensions (no add), seal (no add/delete), freeze (no changes); all shallow, implement deepFreeze for nested

8. Prototypes and Inheritance System

8.1 Prototype Chain and Property Lookup

Concept Description Key Points
Prototype Internal reference ([[Prototype]]) that links object to another object Forms inheritance chain, enables property sharing
Prototype Chain Series of prototype links from object to Object.prototype to null Property lookup traverses chain until found or reaches null
__proto__ Accessor property to get/set [[Prototype]] (legacy, avoid) Non-standard but widely supported; use Object.getPrototypeOf() instead
Object.prototype Top of prototype chain for normal objects; contains toString, hasOwnProperty, etc. Inherits from null; all objects inherit from it unless explicitly set
Function.prototype Prototype for all functions; contains call, apply, bind, etc. Functions inherit from Function.prototype which inherits from Object.prototype
.prototype Property Property on constructor functions; used as prototype for instances created with new Only constructor functions have .prototype; instances have [[Prototype]]
Property Lookup Step Location Action
1. Own Properties Object itself Check if property exists directly on object; return if found
2. Prototype Object's [[Prototype]] If not found, check prototype; return if found
3. Prototype Chain Prototype's prototype, etc. Continue up chain until property found
4. Object.prototype Top of chain Check Object.prototype; return if found
5. null End of chain Return undefined if not found anywhere

Example: Understanding the prototype chain

// Every object has internal [[Prototype]] link
const obj = { a: 1 };

// Access prototype using Object.getPrototypeOf()
console.log(Object.getPrototypeOf(obj) === Object.prototype);  // true

// Object.prototype's prototype is null (end of chain)
console.log(Object.getPrototypeOf(Object.prototype));  // null

// Property lookup demonstration
const animal = {
    eats: true,
    walk() {
        console.log('Animal walks');
    }
};

const rabbit = {
    jumps: true
};

// Set rabbit's prototype to animal
Object.setPrototypeOf(rabbit, animal);

// rabbit can access both own and inherited properties
console.log(rabbit.jumps);  // true (own property)
console.log(rabbit.eats);   // true (inherited from animal)
rabbit.walk();  // "Animal walks" (inherited method)

// Property lookup order
console.log(rabbit.toString);  // [Function: toString] (from Object.prototype)

// Chain: rabbit -> animal -> Object.prototype -> null
console.log(Object.getPrototypeOf(rabbit) === animal);  // true
console.log(Object.getPrototypeOf(animal) === Object.prototype);  // true
console.log(Object.getPrototypeOf(Object.prototype));  // null

Example: Property shadowing and prototype lookup

const parent = {
    name: 'Parent',
    greet() {
        return `Hello from ${this.name}`;
    }
};

const child = Object.create(parent);
child.age = 10;

// Access inherited property
console.log(child.name);  // 'Parent' (from prototype)
console.log(child.greet());  // "Hello from Parent"

// Own property shadows prototype property
child.name = 'Child';
console.log(child.name);  // 'Child' (own property, shadows prototype)

// Check property ownership
console.log(child.hasOwnProperty('name'));  // true (own)
console.log(child.hasOwnProperty('greet'));  // false (inherited)
console.log('greet' in child);  // true (checks own + prototype)

// Delete own property - prototype property revealed
delete child.name;
console.log(child.name);  // 'Parent' (prototype property no longer shadowed)

// Writing to inherited property creates own property
const obj = Object.create({ x: 1 });
console.log(obj.x);  // 1 (inherited)
obj.x = 2;  // Creates own property (doesn't modify prototype)
console.log(obj.x);  // 2 (own property)
console.log(Object.getPrototypeOf(obj).x);  // 1 (prototype unchanged)

Example: Working with prototypes

// Get prototype of object
const obj = {};
const proto = Object.getPrototypeOf(obj);
console.log(proto === Object.prototype);  // true

// Set prototype (avoid in production - slow!)
const animal = { type: 'animal' };
const dog = { breed: 'Labrador' };
Object.setPrototypeOf(dog, animal);
console.log(dog.type);  // 'animal' (inherited)

// Create object with specific prototype (preferred)
const cat = Object.create(animal);
cat.breed = 'Persian';
console.log(cat.type);  // 'animal' (inherited)

// Check if object is in prototype chain
console.log(animal.isPrototypeOf(dog));  // true
console.log(animal.isPrototypeOf(cat));  // true
console.log(Object.prototype.isPrototypeOf(dog));  // true (all objects)

// Create object with null prototype (no inheritance)
const noProto = Object.create(null);
console.log(noProto.toString);  // undefined (no Object.prototype)
console.log(Object.getPrototypeOf(noProto));  // null

// Prototype chain visualization
function showChain(obj, label = 'obj') {
    console.log(`${label}:`, obj);
    const proto = Object.getPrototypeOf(obj);
    if (proto) {
        showChain(proto, `${label}.__proto__`);
    } else {
        console.log('End of chain: null');
    }
}

const myObj = { x: 1 };
showChain(myObj);
// myObj: { x: 1 }
// myObj.__proto__: Object.prototype
// End of chain: null
Important: Property lookup is read-only up the chain. Writing to a property always creates/modifies own property (unless setter exists in prototype). Use Object.getPrototypeOf() and Object.create() instead of deprecated __proto__. Avoid Object.setPrototypeOf() for performance reasons.

8.2 Constructor Functions and new Operator

Concept Description Behavior
Constructor Function Regular function used with new to create instances Conventionally capitalized; sets up instance properties
new Operator Creates instance, sets prototype, binds this, returns object 4-step process: create, link, execute, return
Constructor.prototype Object that becomes prototype of instances created with new Constructor() Shared methods placed here for memory efficiency
constructor Property Property on prototype pointing back to constructor function Allows instances to identify their constructor
Instance Properties Properties unique to each instance (set in constructor) Defined using this.property = value
Prototype Methods Methods shared across all instances (on Constructor.prototype) Memory efficient - one copy shared by all instances
Step new Operator Process Effect
1. Create New empty object created const obj = {}
2. Link Prototype Object's [[Prototype]] set to Constructor.prototype Object.setPrototypeOf(obj, Constructor.prototype)
3. Execute Constructor Constructor called with this bound to new object Constructor.call(obj, ...args)
4. Return Return object (or constructor's return if object) Returns this unless constructor explicitly returns object

Example: Constructor functions and instances

// Constructor function (capitalized by convention)
function Person(name, age) {
    // Instance properties (unique per instance)
    this.name = name;
    this.age = age;
    
    // ✗ Avoid methods here (creates new function per instance)
    // this.greet = function() { return `Hi, I'm ${this.name}`; };
}

// Shared methods on prototype (memory efficient)
Person.prototype.greet = function() {
    return `Hello, I'm ${this.name}`;
};

Person.prototype.getAge = function() {
    return this.age;
};

// Create instances with 'new'
const alice = new Person('Alice', 30);
const bob = new Person('Bob', 25);

// Each instance has own properties
console.log(alice.name);  // 'Alice'
console.log(bob.name);  // 'Bob'

// Shared methods from prototype
console.log(alice.greet());  // "Hello, I'm Alice"
console.log(bob.greet());  // "Hello, I'm Bob"

// Methods are shared (same function object)
console.log(alice.greet === bob.greet);  // true (memory efficient)

// Verify prototype relationship
console.log(Object.getPrototypeOf(alice) === Person.prototype);  // true
console.log(alice.constructor === Person);  // true
console.log(alice instanceof Person);  // true

// Prototype chain: alice -> Person.prototype -> Object.prototype -> null
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype);  // true

Example: Manual simulation of 'new' operator

// Understanding what 'new' does
function myNew(Constructor, ...args) {
    // 1. Create new object
    const obj = {};
    
    // 2. Link to prototype
    Object.setPrototypeOf(obj, Constructor.prototype);
    // Or: obj.__proto__ = Constructor.prototype;
    
    // 3. Execute constructor with 'this' bound to new object
    const result = Constructor.apply(obj, args);
    
    // 4. Return object (or constructor's return if it's an object)
    return (typeof result === 'object' && result !== null) ? result : obj;
}

function Dog(name) {
    this.name = name;
}

Dog.prototype.bark = function() {
    return `${this.name} says woof!`;
};

// Using custom 'new'
const dog1 = myNew(Dog, 'Rex');
console.log(dog1.name);  // 'Rex'
console.log(dog1.bark());  // "Rex says woof!"
console.log(dog1 instanceof Dog);  // true

// Compare with real 'new'
const dog2 = new Dog('Max');
console.log(dog2.name);  // 'Max'
console.log(dog2.bark());  // "Max says woof!"

// Constructor returning object overrides default behavior
function Special() {
    this.x = 1;
    return { y: 2 };  // Explicit object return
}

const s = new Special();
console.log(s.x);  // undefined (returned object used instead)
console.log(s.y);  // 2

// Constructor returning primitive doesn't override
function Normal() {
    this.x = 1;
    return 42;  // Primitive ignored
}

const n = new Normal();
console.log(n.x);  // 1 (this returned as usual)

Example: Constructor patterns and best practices

// Pattern 1: Instance properties + prototype methods (recommended)
function Car(make, model) {
    this.make = make;
    this.model = model;
}

Car.prototype.getDescription = function() {
    return `${this.make} ${this.model}`;
};

// Pattern 2: Static methods (on constructor itself)
Car.fromString = function(str) {
    const [make, model] = str.split(' ');
    return new Car(make, model);
};

const car1 = new Car('Toyota', 'Camry');
const car2 = Car.fromString('Honda Civic');

// Pattern 3: Guard against missing 'new'
function SafeConstructor(value) {
    // Check if called with 'new'
    if (!(this instanceof SafeConstructor)) {
        return new SafeConstructor(value);
    }
    this.value = value;
}

const obj1 = new SafeConstructor(10);  // With 'new'
const obj2 = SafeConstructor(20);  // Without 'new' - still works
console.log(obj1 instanceof SafeConstructor);  // true
console.log(obj2 instanceof SafeConstructor);  // true

// Pattern 4: Setting prototype after constructor definition
function Animal(name) {
    this.name = name;
}

// Replace entire prototype (need to restore constructor)
Animal.prototype = {
    constructor: Animal,  // Restore constructor reference
    speak() {
        return `${this.name} makes a sound`;
    },
    eat() {
        return `${this.name} is eating`;
    }
};

const animal = new Animal('Lion');
console.log(animal.speak());  // "Lion makes a sound"
console.log(animal.constructor === Animal);  // true (restored)

// ✗ Common mistake: adding methods as instance properties
function Inefficient(name) {
    this.name = name;
    // ✗ Bad: creates new function for each instance
    this.greet = function() {
        return `Hi, I'm ${this.name}`;
    };
}

const i1 = new Inefficient('A');
const i2 = new Inefficient('B');
console.log(i1.greet === i2.greet);  // false (different functions - wasteful)
Common Pitfalls:
  • Forgetting 'new': Constructor called as regular function; this refers to global object (or undefined in strict mode)
  • Methods as instance properties: Creating functions inside constructor wastes memory; use prototype instead
  • Overwriting prototype: Breaks constructor reference; always restore .constructor property
  • Modifying built-in prototypes: Never modify Object.prototype, Array.prototype, etc. (causes conflicts)

8.3 Object.create and Prototypal Inheritance

Method Syntax Creates Use Case
Object.create(proto) Object.create(protoObj) New object with protoObj as prototype Prototypal inheritance, object delegation
Object.create(proto, descriptors) Object.create(proto, { props }) Object with prototype + property descriptors Fine-grained property control
Object.create(null) Object.create(null) Object with no prototype (null) Pure data storage, avoid prototype pollution
Inheritance Pattern Approach Pros Cons
Constructor Pattern function + new + prototype Familiar to class-based developers Verbose, easy to forget 'new'
Object.create Pattern Object.create(proto) Simple, pure prototypal, no 'new' Less familiar, no constructor function
ES6 Class class syntax Clean syntax, modern, standard Syntactic sugar over prototypes
Factory Function function returning object Flexible, private state via closure No shared prototype methods (unless manually added)

Example: Object.create for prototypal inheritance

// Parent object (prototype)
const animal = {
    type: 'animal',
    eat() {
        return `${this.name} is eating`;
    },
    sleep() {
        return `${this.name} is sleeping`;
    }
};

// Create child objects inheriting from parent
const dog = Object.create(animal);
dog.name = 'Rex';
dog.breed = 'Labrador';
dog.bark = function() {
    return `${this.name} says woof!`;
};

const cat = Object.create(animal);
cat.name = 'Whiskers';
cat.breed = 'Persian';
cat.meow = function() {
    return `${this.name} says meow!`;
};

// Access own properties
console.log(dog.name);  // 'Rex' (own)
console.log(dog.breed);  // 'Labrador' (own)

// Access inherited properties/methods
console.log(dog.type);  // 'animal' (inherited)
console.log(dog.eat());  // "Rex is eating" (inherited method)
console.log(dog.bark());  // "Rex says woof!" (own method)

// Verify prototype chain
console.log(Object.getPrototypeOf(dog) === animal);  // true
console.log(Object.getPrototypeOf(cat) === animal);  // true

// Shared prototype
console.log(dog.eat === cat.eat);  // true (same method from prototype)

// Modify prototype affects all children
animal.move = function() {
    return `${this.name} is moving`;
};
console.log(dog.move());  // "Rex is moving" (newly added)
console.log(cat.move());  // "Whiskers is moving"

Example: Object.create with property descriptors

const parent = {
    greet() {
        return `Hello from ${this.name}`;
    }
};

// Create object with prototype and property descriptors
const child = Object.create(parent, {
    name: {
        value: 'Child',
        writable: true,
        enumerable: true,
        configurable: true
    },
    age: {
        value: 10,
        writable: true,
        enumerable: true,
        configurable: true
    },
    // Accessor property
    description: {
        get() {
            return `${this.name}, age ${this.age}`;
        },
        enumerable: true,
        configurable: true
    }
});

console.log(child.name);  // 'Child'
console.log(child.age);  // 10
console.log(child.greet());  // "Hello from Child" (inherited)
console.log(child.description);  // "Child, age 10" (computed)

// Object with null prototype (no inheritance)
const dict = Object.create(null);
dict.key1 = 'value1';
dict.key2 = 'value2';

console.log(dict.toString);  // undefined (no Object.prototype)
console.log(dict.hasOwnProperty);  // undefined
console.log(Object.getPrototypeOf(dict));  // null

// Useful for pure data storage
console.log('toString' in dict);  // false (safe from prototype pollution)

Example: Implementing classical inheritance with Object.create

// Parent constructor
function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    return `${this.name} is eating`;
};

Animal.prototype.sleep = function() {
    return `${this.name} is sleeping`;
};

// Child constructor
function Dog(name, breed) {
    // Call parent constructor
    Animal.call(this, name);  // Inherit instance properties
    this.breed = breed;
}

// Set up prototype chain (critical step)
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;  // Restore constructor reference

// Add child-specific methods
Dog.prototype.bark = function() {
    return `${this.name} says woof!`;
};

// Create instance
const dog = new Dog('Rex', 'Labrador');

console.log(dog.name);  // 'Rex' (from Animal)
console.log(dog.breed);  // 'Labrador' (from Dog)
console.log(dog.eat());  // "Rex is eating" (inherited from Animal)
console.log(dog.bark());  // "Rex says woof!" (from Dog)

// Verify prototype chain
console.log(dog instanceof Dog);  // true
console.log(dog instanceof Animal);  // true
console.log(dog.constructor === Dog);  // true

// Chain: dog -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
console.log(Object.getPrototypeOf(dog) === Dog.prototype);  // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype);  // true

// ✗ Wrong way (don't do this)
// Dog.prototype = Animal.prototype;  // Shares prototype (modifications affect both)
// Dog.prototype = new Animal();  // Creates unnecessary instance

// ✓ Correct way (ES6+ alternative)
class AnimalClass {
    constructor(name) {
        this.name = name;
    }
    eat() {
        return `${this.name} is eating`;
    }
}

class DogClass extends AnimalClass {
    constructor(name, breed) {
        super(name);
        this.breed = breed;
    }
    bark() {
        return `${this.name} says woof!`;
    }
}

const modernDog = new DogClass('Max', 'Beagle');
console.log(modernDog.eat());  // "Max is eating"
console.log(modernDog.bark());  // "Max says woof!"
Best Practice: For inheritance, use ES6 classes (cleaner syntax). If using constructor functions, always use Object.create(Parent.prototype) to set up prototype chain, not new Parent() or direct assignment. Always restore constructor property. Use Object.create(null) for dictionary objects to avoid prototype pollution.

8.4 Prototype Methods and Property Override

Concept Description Behavior
Method Inheritance Child inherits methods from parent's prototype Shared method executed with child instance as this
Method Override Child defines method with same name as parent Child method shadows parent method in prototype chain
super Pattern Call parent method from overridden child method Use Parent.prototype.method.call(this) or ES6 super
Property Shadowing Own property hides prototype property with same name Lookup stops at first match (own properties checked first)
Dynamic Dispatch this binding determined at call time, not definition Enables polymorphism - same method name, different behavior
Pattern Implementation Use Case
Simple Override Define method on child with same name Completely replace parent behavior
Extend Parent Method Call parent method + add child logic Augment parent behavior
Delegate to Parent Forward some calls to parent method Conditional behavior based on state
Mixin Pattern Copy methods from multiple sources Multiple inheritance simulation

Example: Method inheritance and override

// Parent with methods
const animal = {
    speak() {
        return `${this.name} makes a sound`;
    },
    eat() {
        return `${this.name} is eating`;
    }
};

// Child inherits and overrides
const dog = Object.create(animal);
dog.name = 'Rex';

// Inherited method (no override)
console.log(dog.eat());  // "Rex is eating" (from animal)

// Override speak method
dog.speak = function() {
    return `${this.name} barks loudly`;
};

console.log(dog.speak());  // "Rex barks loudly" (overridden)

// Access parent method explicitly
console.log(animal.speak.call(dog));  // "Rex makes a sound" (parent method)

// Another child with different override
const cat = Object.create(animal);
cat.name = 'Whiskers';
cat.speak = function() {
    return `${this.name} meows softly`;
};

console.log(cat.speak());  // "Whiskers meows softly" (different override)

// Polymorphism in action
const animals = [dog, cat];
animals.forEach(a => console.log(a.speak()));
// "Rex barks loudly"
// "Whiskers meows softly"

Example: Calling parent methods (super pattern)

// Constructor-based inheritance
function Animal(name) {
    this.name = name;
}

Animal.prototype.describe = function() {
    return `This is ${this.name}`;
};

Animal.prototype.speak = function() {
    return `${this.name} makes a sound`;
};

function Dog(name, breed) {
    Animal.call(this, name);  // Call parent constructor
    this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// Override with extension - call parent method
Dog.prototype.describe = function() {
    // Call parent method
    const parentDesc = Animal.prototype.describe.call(this);
    // Add child-specific info
    return `${parentDesc}, a ${this.breed}`;
};

Dog.prototype.speak = function() {
    // Completely override (no parent call)
    return `${this.name} barks`;
};

const dog = new Dog('Rex', 'Labrador');
console.log(dog.describe());  // "This is Rex, a Labrador" (extended)
console.log(dog.speak());  // "Rex barks" (overridden)

// ES6 class with super keyword (cleaner)
class AnimalClass {
    constructor(name) {
        this.name = name;
    }
    
    describe() {
        return `This is ${this.name}`;
    }
    
    speak() {
        return `${this.name} makes a sound`;
    }
}

class DogClass extends AnimalClass {
    constructor(name, breed) {
        super(name);  // Call parent constructor
        this.breed = breed;
    }
    
    describe() {
        // Call parent method with super
        return `${super.describe()}, a ${this.breed}`;
    }
    
    speak() {
        // Override without calling parent
        return `${this.name} barks`;
    }
}

const modernDog = new DogClass('Max', 'Beagle');
console.log(modernDog.describe());  // "This is Max, a Beagle"
console.log(modernDog.speak());  // "Max barks"

Example: Property shadowing and resolution

const parent = {
    name: 'Parent',
    value: 10,
    getValue() {
        return this.value;
    }
};

const child = Object.create(parent);
console.log(child.name);  // 'Parent' (inherited)
console.log(child.value);  // 10 (inherited)

// Shadow property with own value
child.name = 'Child';
child.value = 20;

console.log(child.name);  // 'Child' (own property shadows parent)
console.log(child.value);  // 20 (own)
console.log(parent.name);  // 'Parent' (parent unchanged)

// Method uses 'this.value' - gets child's own value
console.log(child.getValue());  // 20 (inherited method, child's value)

// Delete own property - parent property revealed
delete child.name;
console.log(child.name);  // 'Parent' (parent property no longer shadowed)

// hasOwnProperty distinguishes own vs inherited
console.log(child.hasOwnProperty('value'));  // true (own)
console.log(child.hasOwnProperty('getValue'));  // false (inherited)

// Writing to inherited property creates own property
const obj = Object.create({ x: 1, y: 2 });
obj.x = 10;  // Creates own property
console.log(obj.x);  // 10 (own)
console.log(Object.getPrototypeOf(obj).x);  // 1 (prototype unchanged)

// Arrays inherit push, pop, etc. from Array.prototype
const arr = [1, 2, 3];
console.log(arr.hasOwnProperty('push'));  // false (inherited)
console.log(arr.hasOwnProperty('length'));  // true (own property)
console.log(arr.hasOwnProperty(0));  // true (own property)

Example: Mixin pattern for method sharing

// Mixin: reusable method collections
const canEat = {
    eat(food) {
        return `${this.name} is eating ${food}`;
    }
};

const canWalk = {
    walk() {
        return `${this.name} is walking`;
    }
};

const canSwim = {
    swim() {
        return `${this.name} is swimming`;
    }
};

// Mixin helper function
function mixin(target, ...sources) {
    Object.assign(target, ...sources);
    return target;
}

// Create object with multiple mixins
function Animal(name) {
    this.name = name;
}

// Add mixins to prototype
mixin(Animal.prototype, canEat, canWalk);

const dog = new Animal('Rex');
console.log(dog.eat('bone'));  // "Rex is eating bone"
console.log(dog.walk());  // "Rex is walking"
// console.log(dog.swim());  // Error: swim not available

// Different mix of capabilities
function Fish(name) {
    this.name = name;
}

mixin(Fish.prototype, canEat, canSwim);

const fish = new Fish('Nemo');
console.log(fish.eat('algae'));  // "Nemo is eating algae"
console.log(fish.swim());  // "Nemo is swimming"
// console.log(fish.walk());  // Error: walk not available

// ES6 class with mixins
const Flyable = {
    fly() {
        return `${this.name} is flying`;
    }
};

class Bird {
    constructor(name) {
        this.name = name;
    }
}

// Apply mixin to class
Object.assign(Bird.prototype, canEat, Flyable);

const bird = new Bird('Tweety');
console.log(bird.eat('seeds'));  // "Tweety is eating seeds"
console.log(bird.fly());  // "Tweety is flying"
Best Practice: Use method override for polymorphism. Call parent methods with super (ES6 classes) or Parent.prototype.method.call(this) (constructor functions). Use mixins for horizontal code reuse. Remember: methods use this binding, so inherited methods access instance properties correctly.

8.5 instanceof and Prototype Testing

Method/Operator Syntax Returns What It Checks
instanceof obj instanceof Constructor boolean Is Constructor.prototype in obj's prototype chain?
isPrototypeOf() proto.isPrototypeOf(obj) boolean Is proto in obj's prototype chain?
Object.getPrototypeOf() Object.getPrototypeOf(obj) object or null Returns obj's direct prototype
hasOwnProperty() obj.hasOwnProperty(prop) boolean Is prop an own property (not inherited)?
Object.hasOwn() ES2022 Object.hasOwn(obj, prop) boolean Safer alternative to hasOwnProperty
in operator 'prop' in obj boolean Is prop in obj or its prototype chain?
Test Own Property Inherited Property Nonexistent
obj.prop value value undefined
'prop' in obj true true false
obj.hasOwnProperty('prop') true false false
Object.hasOwn(obj, 'prop') true false false

Example: instanceof operator

// Constructor-based type checking
function Animal(name) {
    this.name = name;
}

function Dog(name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

const dog = new Dog('Rex', 'Labrador');

// instanceof checks prototype chain
console.log(dog instanceof Dog);  // true
console.log(dog instanceof Animal);  // true
console.log(dog instanceof Object);  // true (all objects)

// Arrays
const arr = [1, 2, 3];
console.log(arr instanceof Array);  // true
console.log(arr instanceof Object);  // true

// Primitives
console.log('hello' instanceof String);  // false (primitive, not object)
console.log(new String('hello') instanceof String);  // true (wrapper object)
console.log(42 instanceof Number);  // false

// instanceof can be fooled
function Fake() {}
Fake.prototype = dog;  // Set prototype to instance
const fake = new Fake();
console.log(fake instanceof Dog);  // true! (dog in prototype chain)

// Object.create
const parent = { type: 'parent' };
const child = Object.create(parent);
// No constructor function, instanceof doesn't work well
// console.log(child instanceof ???);  // What to check?

Example: isPrototypeOf() method

const animal = {
    eats: true
};

const rabbit = Object.create(animal);
rabbit.jumps = true;

const longEaredRabbit = Object.create(rabbit);
longEaredRabbit.earLength = 10;

// Check prototype relationships
console.log(animal.isPrototypeOf(rabbit));  // true
console.log(animal.isPrototypeOf(longEaredRabbit));  // true (anywhere in chain)
console.log(rabbit.isPrototypeOf(longEaredRabbit));  // true

console.log(rabbit.isPrototypeOf(animal));  // false (wrong direction)

// Works with any object, not just constructors
console.log(Object.prototype.isPrototypeOf(rabbit));  // true (all objects)
console.log(Object.prototype.isPrototypeOf({}));  // true

// Compare with instanceof (requires constructor)
function Animal() {}
const dog = new Animal();
console.log(dog instanceof Animal);  // true
console.log(Animal.prototype.isPrototypeOf(dog));  // true (equivalent)

// null prototype
const noProto = Object.create(null);
console.log(Object.prototype.isPrototypeOf(noProto));  // false

Example: Property ownership testing

const parent = {
    inherited: 'from parent',
    sharedMethod() {
        return 'shared';
    }
};

const child = Object.create(parent);
child.own = 'own property';

// 'in' operator - checks own and inherited
console.log('own' in child);  // true (own)
console.log('inherited' in child);  // true (inherited)
console.log('sharedMethod' in child);  // true (inherited)
console.log('nonexistent' in child);  // false

// hasOwnProperty - checks only own properties
console.log(child.hasOwnProperty('own'));  // true
console.log(child.hasOwnProperty('inherited'));  // false (inherited)
console.log(child.hasOwnProperty('sharedMethod'));  // false

// Object.hasOwn (ES2022) - safer alternative
console.log(Object.hasOwn(child, 'own'));  // true
console.log(Object.hasOwn(child, 'inherited'));  // false

// Why Object.hasOwn is safer
const obj = Object.create(null);  // No prototype
obj.prop = 'value';
// obj.hasOwnProperty('prop');  // Error: hasOwnProperty not available
console.log(Object.hasOwn(obj, 'prop'));  // true (works!)

// Distinguish own vs inherited in iteration
for (let key in child) {
    if (child.hasOwnProperty(key)) {
        console.log(`Own: ${key}`);
    } else {
        console.log(`Inherited: ${key}`);
    }
}
// Own: own
// Inherited: inherited
// Inherited: sharedMethod

// Object.keys() only returns own enumerable properties
console.log(Object.keys(child));  // ['own']

// Get all properties including inherited (manual)
function getAllProperties(obj) {
    const props = new Set();
    let current = obj;
    while (current) {
        Object.getOwnPropertyNames(current).forEach(p => props.add(p));
        current = Object.getPrototypeOf(current);
    }
    return Array.from(props);
}

console.log(getAllProperties(child));
// Includes: own, inherited, sharedMethod, toString, hasOwnProperty, etc.

Example: Type checking best practices

// Checking built-in types
function checkType(value) {
    // Arrays - use Array.isArray (most reliable)
    if (Array.isArray(value)) {
        return 'array';
    }
    
    // null (special case - typeof null === 'object')
    if (value === null) {
        return 'null';
    }
    
    // Primitives - use typeof
    if (typeof value !== 'object') {
        return typeof value;  // 'string', 'number', 'boolean', etc.
    }
    
    // Objects - check constructor
    if (value instanceof Date) return 'date';
    if (value instanceof RegExp) return 'regexp';
    if (value instanceof Map) return 'map';
    if (value instanceof Set) return 'set';
    
    return 'object';
}

console.log(checkType([1, 2, 3]));  // 'array'
console.log(checkType('hello'));  // 'string'
console.log(checkType(42));  // 'number'
console.log(checkType(null));  // 'null'
console.log(checkType(new Date()));  // 'date'
console.log(checkType({ x: 1 }));  // 'object'

// Duck typing - check for expected methods
function isIterable(obj) {
    return obj != null && typeof obj[Symbol.iterator] === 'function';
}

console.log(isIterable([1, 2, 3]));  // true
console.log(isIterable('hello'));  // true
console.log(isIterable({ x: 1 }));  // false
console.log(isIterable(new Map()));  // true

// Custom type guard (TypeScript-like pattern in JS)
function isDog(animal) {
    return animal 
        && typeof animal.bark === 'function'
        && typeof animal.breed === 'string';
}

const dog = { name: 'Rex', breed: 'Lab', bark() {} };
const cat = { name: 'Fluffy', meow() {} };

console.log(isDog(dog));  // true
console.log(isDog(cat));  // false

// Symbol.toStringTag for custom types
class CustomType {
    get [Symbol.toStringTag]() {
        return 'CustomType';
    }
}

const custom = new CustomType();
console.log(Object.prototype.toString.call(custom));  // '[object CustomType]'
console.log(Object.prototype.toString.call([]));  // '[object Array]'
console.log(Object.prototype.toString.call(new Date()));  // '[object Date]'
Common Pitfalls:
  • instanceof with primitives: Returns false for primitives like 'string' even though they can use String methods
  • instanceof across frames: Different window/frame has different built-in constructors; use Array.isArray() for arrays
  • hasOwnProperty safety: Objects with null prototype don't have hasOwnProperty; use Object.hasOwn() instead
  • typeof null: Returns 'object' (JavaScript quirk); check === null explicitly

8.6 Object Delegation Patterns

Pattern Description Key Concept
Behavior Delegation (OLOO) Objects delegate to other objects (not classes) Objects Linked to Other Objects - pure prototypal
Classical Inheritance Parent-child hierarchy with constructors Constructor functions + prototype chain
Concatenative Inheritance Mix properties from multiple sources Object.assign() / spread for composition
Functional Inheritance Factory functions with closures for privacy Functions return objects with private state
Aspect Classical (Constructor) Delegation (OLOO)
Syntax new Constructor() Object.create(proto)
Focus Parent-child classes Peer objects delegating to each other
Initialization Constructor function Init method on prototype
Complexity More verbose, mimics classes Simpler, embraces prototypes
Mental Model "This IS-A that" (inheritance) "This HAS-A reference to that" (delegation)

Example: OLOO pattern (Objects Linked to Other Objects)

// Behavior delegation style (OLOO)
const Vehicle = {
    init(make, model) {
        this.make = make;
        this.model = model;
        return this;
    },
    
    describe() {
        return `${this.make} ${this.model}`;
    }
};

const Car = Object.create(Vehicle);

Car.init = function(make, model, doors) {
    // Delegate to Vehicle.init
    Vehicle.init.call(this, make, model);
    this.doors = doors;
    return this;
};

Car.drive = function() {
    return `Driving ${this.describe()}`;
};

// Create instances
const myCar = Object.create(Car).init('Toyota', 'Camry', 4);
const yourCar = Object.create(Car).init('Honda', 'Civic', 4);

console.log(myCar.describe());  // "Toyota Camry"
console.log(myCar.drive());  // "Driving Toyota Camry"
console.log(yourCar.drive());  // "Driving Honda Civic"

// No constructors, no 'new', just objects delegating
console.log(Object.getPrototypeOf(myCar) === Car);  // true
console.log(Object.getPrototypeOf(Car) === Vehicle);  // true

// Compare with constructor pattern
function VehicleConstructor(make, model) {
    this.make = make;
    this.model = model;
}

VehicleConstructor.prototype.describe = function() {
    return `${this.make} ${this.model}`;
};

function CarConstructor(make, model, doors) {
    VehicleConstructor.call(this, make, model);
    this.doors = doors;
}

CarConstructor.prototype = Object.create(VehicleConstructor.prototype);
CarConstructor.prototype.constructor = CarConstructor;

CarConstructor.prototype.drive = function() {
    return `Driving ${this.describe()}`;
};

const constructorCar = new CarConstructor('Ford', 'Focus', 4);
console.log(constructorCar.drive());  // Same result, more complex setup

Example: Concatenative inheritance (composition)

// Reusable behavior objects
const canEat = {
    eat(food) {
        return `${this.name} eats ${food}`;
    }
};

const canWalk = {
    walk() {
        return `${this.name} walks`;
    }
};

const canSwim = {
    swim() {
        return `${this.name} swims`;
    }
};

// Compose objects by combining behaviors
function createDog(name) {
    return {
        name,
        ...canEat,
        ...canWalk,
        bark() {
            return `${this.name} barks`;
        }
    };
}

function createFish(name) {
    return {
        name,
        ...canEat,
        ...canSwim
    };
}

function createDuck(name) {
    return {
        name,
        ...canEat,
        ...canWalk,
        ...canSwim,
        quack() {
            return `${this.name} quacks`;
        }
    };
}

const dog = createDog('Rex');
console.log(dog.eat('bone'));  // "Rex eats bone"
console.log(dog.walk());  // "Rex walks"
console.log(dog.bark());  // "Rex barks"
// console.log(dog.swim());  // undefined (no swim capability)

const duck = createDuck('Donald');
console.log(duck.eat('bread'));  // "Donald eats bread"
console.log(duck.walk());  // "Donald walks"
console.log(duck.swim());  // "Donald swims"
console.log(duck.quack());  // "Donald quacks"

// Advanced: functional mixin
const withLogging = (obj) => {
    const originalMethods = {};
    
    Object.keys(obj).forEach(key => {
        if (typeof obj[key] === 'function') {
            originalMethods[key] = obj[key];
            obj[key] = function(...args) {
                console.log(`Calling ${key} with`, args);
                return originalMethods[key].apply(this, args);
            };
        }
    });
    
    return obj;
};

const loggedDog = withLogging(createDog('Max'));
loggedDog.eat('treat');  // Logs: "Calling eat with ['treat']"
                         // Returns: "Max eats treat"

Example: Functional inheritance with closures

// Factory function with private state
function createBankAccount(initialBalance) {
    // Private variables (closure)
    let balance = initialBalance;
    let transactionHistory = [];
    
    // Private function
    function recordTransaction(type, amount) {
        transactionHistory.push({
            type,
            amount,
            timestamp: Date.now(),
            balance
        });
    }
    
    // Public interface
    return {
        deposit(amount) {
            if (amount > 0) {
                balance += amount;
                recordTransaction('deposit', amount);
            }
            return balance;
        },
        
        withdraw(amount) {
            if (amount > 0 && amount <= balance) {
                balance -= amount;
                recordTransaction('withdraw', amount);
            }
            return balance;
        },
        
        getBalance() {
            return balance;
        },
        
        getHistory() {
            // Return copy to prevent external modification
            return [...transactionHistory];
        }
    };
}

const account = createBankAccount(1000);
account.deposit(500);   // 1500
account.withdraw(200);  // 1300

console.log(account.getBalance());  // 1300
console.log(account.balance);  // undefined (private!)
console.log(account.getHistory().length);  // 2 transactions

// Inheritance with functional pattern
function createSavingsAccount(initialBalance, interestRate) {
    // Get base account
    const account = createBankAccount(initialBalance);
    
    // Add new functionality
    account.applyInterest = function() {
        const interest = account.getBalance() * interestRate;
        account.deposit(interest);
        return account.getBalance();
    };
    
    return account;
}

const savings = createSavingsAccount(1000, 0.05);
savings.deposit(500);  // 1500
savings.applyInterest();  // 1575 (5% interest)
console.log(savings.getBalance());  // 1575

Example: Practical delegation patterns

// Plugin system using delegation
const BaseApp = {
    init(name) {
        this.name = name;
        this.plugins = [];
        return this;
    },
    
    use(plugin) {
        this.plugins.push(plugin);
        if (plugin.install) {
            plugin.install(this);
        }
        return this;
    },
    
    run() {
        console.log(`${this.name} is running`);
        this.plugins.forEach(p => p.onRun && p.onRun(this));
    }
};

// Specific app type
const WebApp = Object.create(BaseApp);
WebApp.init = function(name, port) {
    BaseApp.init.call(this, name);
    this.port = port;
    return this;
};

WebApp.start = function() {
    console.log(`Server started on port ${this.port}`);
    this.run();
};

// Plugins
const LoggerPlugin = {
    install(app) {
        console.log(`Logger installed in ${app.name}`);
    },
    onRun(app) {
        console.log(`[LOG] ${app.name} executed`);
    }
};

const CachePlugin = {
    install(app) {
        app.cache = new Map();
        console.log(`Cache installed in ${app.name}`);
    },
    onRun(app) {
        console.log(`[CACHE] Size: ${app.cache.size}`);
    }
};

// Create app with plugins
const app = Object.create(WebApp)
    .init('MyApp', 3000)
    .use(LoggerPlugin)
    .use(CachePlugin);

app.start();
// Logger installed in MyApp
// Cache installed in MyApp
// Server started on port 3000
// MyApp is running
// [LOG] MyApp executed
// [CACHE] Size: 0

Section 8 Summary

  • Prototype chain: Property lookup traverses [[Prototype]] links from object to Object.prototype to null; use getPrototypeOf(), not __proto__
  • Constructor pattern: Function with new creates instances; 4 steps: create, link prototype, execute, return; methods on prototype for efficiency
  • Object.create: Direct prototypal inheritance without constructors; clean delegation; use for pure prototypal patterns
  • Method override: Child methods shadow parent; call parent with super or Parent.prototype.method.call(this); enables polymorphism
  • Type testing: instanceof checks constructor chain; isPrototypeOf() checks object chain; Object.hasOwn() for property ownership (safer than hasOwnProperty)
  • Delegation patterns: OLOO for pure delegation; concatenative for composition; functional for privacy; choose based on needs

9. Arrays and Array Methods Reference

9.1 Array Creation and Initialization Patterns

Pattern Syntax Description Example
Array Literal [el1, el2, ...] Most common creation method; supports mixed types and sparse arrays const arr = [1, 2, 3];
Array Constructor new Array(length) Creates array with specified length (holes); avoid for single numeric argument new Array(3) // [empty × 3]
Array.of() Array.of(...items) Creates array from arguments; fixes Array() single-number issue Array.of(3) // [3]
Array.from() Array.from(iterable, mapFn) Creates array from iterable/array-like; optional mapper function Array.from('abc') // ['a','b','c']
Spread Syntax [...iterable] Shallow copy or convert iterable to array; modern and concise [...set] // array from Set
Array.fill() Array(n).fill(value) Create pre-filled array; value is shared reference for objects Array(3).fill(0) // [0,0,0]
Array.keys() Array.from(Array(n).keys()) Generate sequential number array from 0 to n-1 [...Array(3).keys()] // [0,1,2]

Example: Various array creation patterns

// Literal - most common
const fruits = ['apple', 'banana', 'orange'];

// Array.of - safe with single number
const single = Array.of(5); // [5] not [empty × 5]

// Array.from with mapper
const squares = Array.from({length: 5}, (_, i) => i ** 2);
// [0, 1, 4, 9, 16]

// From string
const chars = Array.from('hello'); // ['h','e','l','l','o']

// From Set
const unique = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]

// Pre-fill with value
const zeros = Array(10).fill(0);

// Sequential numbers
const range = [...Array(5).keys()]; // [0, 1, 2, 3, 4]
const rangeFrom1 = Array.from({length: 5}, (_, i) => i + 1);
// [1, 2, 3, 4, 5]

// 2D array (careful with fill!)
const matrix = Array(3).fill(null).map(() => Array(3).fill(0));
Note: Avoid new Array(n).fill([]) for 2D arrays as all rows share same reference. Use .map(() => []) instead.

9.2 Array Mutating Methods (push, pop, splice, sort)

Method Syntax Description Returns
push() arr.push(el1, ...) Adds elements to end; modifies original array; O(1) time New length
pop() arr.pop() Removes and returns last element; O(1) time Removed element
unshift() arr.unshift(el1, ...) Adds elements to beginning; shifts indices; O(n) time New length
shift() arr.shift() Removes and returns first element; shifts indices; O(n) time Removed element
splice() arr.splice(start, deleteCount, ...items) Add/remove elements at any position; powerful but complex Array of deleted elements
sort() arr.sort(compareFn) Sorts in-place; default: string comparison; provide compareFn for numbers Sorted array (same reference)
reverse() arr.reverse() Reverses array in-place; O(n) time Reversed array (same reference)
fill() arr.fill(value, start, end) Fills with static value; optional range; O(n) time Modified array
copyWithin() arr.copyWithin(target, start, end) Shallow copies part of array to another location in same array Modified array

Example: Mutating array methods

const arr = [1, 2, 3];

// Stack operations (end)
arr.push(4, 5); // [1,2,3,4,5] returns 5
arr.pop(); // [1,2,3,4] returns 5

// Queue operations (beginning)
arr.unshift(0); // [0,1,2,3,4] returns 5
arr.shift(); // [1,2,3,4] returns 0

// splice - remove
const items = [1, 2, 3, 4, 5];
items.splice(2, 2); // [1,2,5] returns [3,4]

// splice - insert
items.splice(1, 0, 'a', 'b'); // [1,'a','b',2,5]

// splice - replace
items.splice(0, 2, 'x'); // ['x','b',2,5]

// sort - numbers (must provide compareFn!)
const nums = [10, 2, 5, 1];
nums.sort(); // [1, 10, 2, 5] ❌ string sort!
nums.sort((a, b) => a - b); // [1, 2, 5, 10] ✓

// sort - objects
const users = [{age: 30}, {age: 20}, {age: 25}];
users.sort((a, b) => a.age - b.age);

// reverse
[1, 2, 3].reverse(); // [3, 2, 1]

// fill
Array(5).fill(0); // [0,0,0,0,0]
[1,2,3,4,5].fill(0, 2, 4); // [1,2,0,0,5]

// copyWithin
[1,2,3,4,5].copyWithin(0, 3); // [4,5,3,4,5]
Warning: Default sort() converts to strings! Always provide compare function for numbers: arr.sort((a,b) => a - b)

9.3 Array Non-mutating Methods (slice, concat, join)

Method Syntax Description Returns
slice() arr.slice(start, end) Shallow copy of portion; negative indices from end; doesn't modify original New array
concat() arr.concat(arr2, ...) Merges arrays/values; shallow copy; doesn't modify originals New array
join() arr.join(separator) Converts to string with separator; default comma; doesn't modify String
toString() arr.toString() Converts to comma-separated string; same as join(',') String
toLocaleString() arr.toLocaleString() Locale-aware string conversion; respects number/date formatting String
flat() ES2019 arr.flat(depth) Flattens nested arrays; default depth 1; Infinity for full flatten New flattened array
flatMap() ES2019 arr.flatMap(fn) Maps then flattens (depth 1); more efficient than map().flat() New array
with() ES2023 arr.with(index, value) Returns new array with element replaced; immutable alternative to arr[i]=val New array
toReversed() ES2023 arr.toReversed() Non-mutating reverse; returns new reversed array New reversed array
toSorted() ES2023 arr.toSorted(compareFn) Non-mutating sort; returns new sorted array New sorted array
toSpliced() ES2023 arr.toSpliced(start, deleteCount, ...items) Non-mutating splice; returns new array with changes New array

Example: Non-mutating array methods

const arr = [1, 2, 3, 4, 5];

// slice - extract portion
arr.slice(1, 3); // [2, 3]
arr.slice(-2); // [4, 5] (last 2)
arr.slice(); // [1,2,3,4,5] (shallow copy)

// concat - merge
[1, 2].concat([3, 4]); // [1, 2, 3, 4]
[1].concat(2, [3, 4], 5); // [1, 2, 3, 4, 5]

// join - to string
['a', 'b', 'c'].join(); // "a,b,c"
['a', 'b', 'c'].join(''); // "abc"
['a', 'b', 'c'].join(' - '); // "a - b - c"

// flat - nested arrays
[1, [2, 3], [4, [5]]].flat(); // [1, 2, 3, 4, [5]]
[1, [2, [3, [4]]]].flat(2); // [1, 2, 3, [4]]
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]

// flatMap - map then flatten
['hello', 'world'].flatMap(s => s.split(''));
// ['h','e','l','l','o','w','o','r','l','d']

[1, 2, 3].flatMap(x => [x, x * 2]);
// [1, 2, 2, 4, 3, 6]

// ES2023 immutable methods
const original = [3, 1, 2];

original.toSorted(); // [1, 2, 3]
console.log(original); // [3, 1, 2] unchanged

original.toReversed(); // [2, 1, 3]

original.with(1, 99); // [3, 99, 2]

original.toSpliced(1, 1, 'a', 'b'); // [3, 'a', 'b', 2]
Note: Use spread [...arr] or slice() for shallow copy. ES2023 adds toSorted(), toReversed(), toSpliced(), with() for immutable operations.

9.4 Array Iteration Methods (forEach, map, filter, reduce)

Method Syntax Description Returns
forEach() arr.forEach((el, i, arr) => {}) Execute function for each element; no return value; can't break/continue undefined
map() arr.map((el, i, arr) => {}) Transform each element; returns new array with results New array
filter() arr.filter((el, i, arr) => {}) Keep elements that pass test; returns new array with matches New array
reduce() arr.reduce((acc, el, i, arr) => {}, init) Reduce to single value; accumulator pattern; init value recommended Any value
reduceRight() arr.reduceRight((acc, el) => {}, init) Like reduce but right-to-left; useful for right-associative operations Any value
flatMap() arr.flatMap((el, i, arr) => {}) Map then flatten depth 1; efficient for map + flat combination New flattened array
entries() arr.entries() Returns iterator of [index, value] pairs Iterator
keys() arr.keys() Returns iterator of indices Iterator
values() arr.values() Returns iterator of values Iterator

Example: Array iteration methods

const nums = [1, 2, 3, 4, 5];

// forEach - side effects only
nums.forEach((n, i) => console.log(`${i}: ${n}`));

// map - transform
const doubled = nums.map(n => n * 2); // [2,4,6,8,10]
const objects = nums.map(n => ({value: n}));

// filter - select
const evens = nums.filter(n => n % 2 === 0); // [2,4]
const large = nums.filter(n => n > 3); // [4,5]

// reduce - accumulate
const sum = nums.reduce((acc, n) => acc + n, 0); // 15
const product = nums.reduce((acc, n) => acc * n, 1); // 120

// reduce - group by
const items = ['a', 'b', 'c', 'a', 'b'];
const counts = items.reduce((acc, item) => {
    acc[item] = (acc[item] || 0) + 1;
    return acc;
}, {}); // {a: 2, b: 2, c: 1}

// reduce - flatten
[[1, 2], [3, 4], [5]].reduce((acc, arr) => acc.concat(arr), []);
// [1, 2, 3, 4, 5] (use flat() instead!)

// Method chaining
nums
    .filter(n => n % 2 === 0)
    .map(n => n * 2)
    .reduce((acc, n) => acc + n, 0); // 12

// flatMap - map and flatten
['hello', 'world'].flatMap(w => w.split(''));
// ['h','e','l','l','o','w','o','r','l','d']

// Iterators
for (const [i, val] of nums.entries()) {
    console.log(i, val); // 0 1, 1 2, ...
}

for (const i of nums.keys()) {
    console.log(i); // 0, 1, 2, 3, 4
}
Note: Always provide initial value to reduce() to avoid errors on empty arrays. Use for...of if you need to break/continue; forEach() can't be stopped early.

9.5 Array Search Methods (find, includes, indexOf)

Method Syntax Description Returns
find() arr.find((el, i, arr) => {}) Returns first element that passes test; stops on first match Element or undefined
findLast() ES2023 arr.findLast((el, i, arr) => {}) Like find() but searches right-to-left; returns last match Element or undefined
findIndex() arr.findIndex((el, i, arr) => {}) Returns index of first element that passes test Index or -1
findLastIndex() ES2023 arr.findLastIndex((el, i, arr) => {}) Like findIndex() but searches right-to-left Index or -1
includes() arr.includes(value, fromIndex) Checks if array contains value; uses SameValueZero (handles NaN) Boolean
indexOf() arr.indexOf(value, fromIndex) Returns first index of value; uses strict equality (===); -1 if not found Index or -1
lastIndexOf() arr.lastIndexOf(value, fromIndex) Returns last index of value; searches backwards Index or -1
at() ES2022 arr.at(index) Returns element at index; negative indices from end; undefined if out of bounds Element or undefined

Example: Array search methods

const users = [
    {id: 1, name: 'Alice', age: 25},
    {id: 2, name: 'Bob', age: 30},
    {id: 3, name: 'Charlie', age: 25}
];

// find - first match
const bob = users.find(u => u.name === 'Bob');
// {id: 2, name: 'Bob', age: 30}

const young = users.find(u => u.age < 30);
// {id: 1, name: 'Alice', age: 25}

// findLast - last match
const lastYoung = users.findLast(u => u.age === 25);
// {id: 3, name: 'Charlie', age: 25}

// findIndex
const bobIndex = users.findIndex(u => u.name === 'Bob'); // 1
const notFound = users.findIndex(u => u.name === 'Dave'); // -1

// includes - value check
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, NaN].includes(NaN); // true ✓ (indexOf fails here)

// indexOf - strict equality
['a', 'b', 'c'].indexOf('b'); // 1
['a', 'b', 'c'].indexOf('d'); // -1
[1, 2, 3, 2].indexOf(2); // 1 (first occurrence)

// lastIndexOf - search backwards
[1, 2, 3, 2, 1].lastIndexOf(2); // 3
[1, 2, 3].lastIndexOf(4); // -1

// at - negative indexing
const arr = ['a', 'b', 'c', 'd'];
arr.at(0); // 'a'
arr.at(-1); // 'd' (last)
arr.at(-2); // 'c' (second to last)
arr.at(10); // undefined

// Comparison: includes vs indexOf
[1, 2, NaN].includes(NaN); // true
[1, 2, NaN].indexOf(NaN); // -1 ❌

// Check existence
if (users.find(u => u.id === 2)) {
    // Found
}

// Get index and value
const index = users.findIndex(u => u.age === 25);
if (index !== -1) {
    const user = users[index];
}
Note: Use includes() for existence check (handles NaN); find() for objects with conditions; at() for negative indexing (cleaner than arr[arr.length - 1]).

9.6 Array Testing Methods (every, some)

Method Syntax Description Returns
every() arr.every((el, i, arr) => {}) Tests if ALL elements pass test; short-circuits on first false Boolean
some() arr.some((el, i, arr) => {}) Tests if ANY element passes test; short-circuits on first true Boolean

Example: Array testing methods

const nums = [2, 4, 6, 8, 10];
const mixed = [1, 2, 3, 4, 5];

// every - all must pass
nums.every(n => n % 2 === 0); // true (all even)
mixed.every(n => n % 2 === 0); // false

nums.every(n => n > 0); // true (all positive)
nums.every(n => n > 5); // false

// some - at least one must pass
mixed.some(n => n % 2 === 0); // true (has even)
mixed.some(n => n > 10); // false

[1, 3, 5].some(n => n % 2 === 0); // false (no even)

// Validation use cases
const users = [
    {name: 'Alice', age: 25},
    {name: 'Bob', age: 30},
    {name: 'Charlie', age: 35}
];

// All adults?
const allAdults = users.every(u => u.age >= 18); // true

// Any seniors?
const hasSeniors = users.some(u => u.age >= 65); // false

// All have names?
const allNamed = users.every(u => u.name && u.name.length > 0);

// Any in 30s?
const hasThirties = users.some(u => u.age >= 30 && u.age < 40);

// Empty array edge cases
[].every(n => n > 100); // true (vacuous truth)
[].some(n => n > 100); // false

// Short-circuit behavior
const expensive = [1, 2, 3, 4, 5];
expensive.every(n => {
    console.log('checking', n);
    return n < 3;
}); // logs: checking 1, checking 2, checking 3
    // stops at 3 (first failure)

expensive.some(n => {
    console.log('checking', n);
    return n > 3;
}); // logs: checking 1, 2, 3, 4
    // stops at 4 (first success)

// Combine with other methods
const valid = users
    .filter(u => u.age > 25)
    .every(u => u.name.length > 0);
Note: every() returns true for empty arrays (vacuous truth); some() returns false. Both short-circuit, stopping iteration early for performance.

9.7 Array Destructuring and Spread Operations

Pattern Syntax Description Example
Basic Destructuring const [a, b] = arr Extract elements into variables by position const [x, y] = [1, 2]
Skip Elements const [a, , c] = arr Omit elements with empty comma slots const [, , z] = [1, 2, 3]
Rest Pattern const [a, ...rest] = arr Collect remaining elements; must be last const [h, ...t] = [1,2,3]
Default Values const [a = 0] = arr Provide fallback if undefined; not for null const [x = 10] = []
Nested Destructuring const [[a], [b]] = arr Destructure nested arrays recursively const [[x]] = [[1]]
Swap Variables [a, b] = [b, a] Elegant variable swap without temp [x, y] = [y, x]
Spread in Array [...arr] Shallow copy or expand iterable [...arr1, ...arr2]
Spread in Call fn(...arr) Pass array elements as separate arguments Math.max(...nums)
Rest in Function fn(...args) Collect function arguments into array function sum(...n)

Example: Destructuring and spread patterns

// Basic destructuring
const [a, b, c] = [1, 2, 3];
// a=1, b=2, c=3

// Skip elements
const [first, , third] = [1, 2, 3];
// first=1, third=3

// Rest pattern
const [head, ...tail] = [1, 2, 3, 4];
// head=1, tail=[2,3,4]

const [x, y, ...rest] = [1];
// x=1, y=undefined, rest=[]

// Default values
const [p = 0, q = 0] = [1];
// p=1, q=0

const [m = 'default'] = [undefined];
// m='default' (undefined triggers default)

const [n = 'default'] = [null];
// n=null (null doesn't trigger default!)

// Nested destructuring
const [[a1, a2], [b1, b2]] = [[1, 2], [3, 4]];
// a1=1, a2=2, b1=3, b2=4

// Swap variables
let i = 1, j = 2;
[i, j] = [j, i]; // i=2, j=1

// Function parameters
function sum([a, b]) {
    return a + b;
}
sum([3, 4]); // 7

function coords({x, y, z = 0}) {
    return [x, y, z];
}

// Spread - shallow copy
const original = [1, 2, 3];
const copy = [...original]; // [1, 2, 3]

// Spread - merge arrays
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1,2,3,4]

const combined = [0, ...arr1, 2.5, ...arr2, 5];
// [0, 1, 2, 2.5, 3, 4, 5]

// Spread - function arguments
const nums = [5, 3, 8, 1];
Math.max(...nums); // 8 (instead of apply)

// Rest parameters
function sum(...numbers) {
    return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10

function first(a, b, ...rest) {
    console.log(rest); // array of remaining args
}

// Convert iterable
const set = new Set([1, 2, 3]);
const arr = [...set]; // [1, 2, 3]

const str = 'hello';
const chars = [...str]; // ['h','e','l','l','o']

// Shallow copy warning
const nested = [[1, 2], [3, 4]];
const shallowCopy = [...nested];
shallowCopy[0][0] = 99;
console.log(nested[0][0]); // 99 ❌ (shared reference!)
Warning: Spread creates shallow copy. Nested objects/arrays share references. Use structuredClone() for deep copy.

9.8 Typed Arrays and Buffer Management

Type Bytes/Element Range Description
Int8Array 1 -128 to 127 Signed 8-bit integer
Uint8Array 1 0 to 255 Unsigned 8-bit integer
Uint8ClampedArray 1 0 to 255 (clamped) Clamped unsigned 8-bit (for Canvas)
Int16Array 2 -32,768 to 32,767 Signed 16-bit integer
Uint16Array 2 0 to 65,535 Unsigned 16-bit integer
Int32Array 4 -2³¹ to 2³¹-1 Signed 32-bit integer
Uint32Array 4 0 to 2³²-1 Unsigned 32-bit integer
Float32Array 4 ±1.2×10⁻³⁸ to ±3.4×10³⁸ 32-bit floating point
Float64Array 8 ±5.0×10⁻³²⁴ to ±1.8×10³⁰⁸ 64-bit floating point
BigInt64Array 8 -2⁶³ to 2⁶³-1 Signed 64-bit BigInt
BigUint64Array 8 0 to 2⁶⁴-1 Unsigned 64-bit BigInt
Buffer API Syntax Description Use Case
ArrayBuffer new ArrayBuffer(byteLength) Fixed-length raw binary data buffer; not directly accessible Binary data storage
DataView new DataView(buffer, offset, length) Low-level interface to read/write multiple types in buffer Mixed-type binary data
SharedArrayBuffer new SharedArrayBuffer(length) Shared memory buffer for Web Workers; requires COOP/COEP headers Multi-threaded data sharing

Example: Typed arrays and buffer operations

// Create typed arrays
const uint8 = new Uint8Array(4); // [0, 0, 0, 0]
const int32 = new Int32Array([1, 2, 3]); // [1, 2, 3]

// From ArrayBuffer
const buffer = new ArrayBuffer(16); // 16 bytes
const view32 = new Int32Array(buffer); // 4 elements (16/4)
const view8 = new Uint8Array(buffer); // 16 elements

// Shared buffer - multiple views
view32[0] = 0x12345678;
console.log(view8[0]); // 0x78 (little-endian)

// Array-like operations (limited)
const arr = new Uint8Array([1, 2, 3, 4]);
arr.length; // 4
arr[0]; // 1
arr.slice(1, 3); // Uint8Array[2, 3]
arr.map(x => x * 2); // Uint8Array[2, 4, 6, 8]

// No push/pop/splice - fixed length!
// arr.push(5); ❌ TypeError

// Overflow behavior
const u8 = new Uint8Array(1);
u8[0] = 256; // wraps to 0
u8[0] = -1; // wraps to 255

const clamped = new Uint8ClampedArray(1);
clamped[0] = 256; // clamps to 255
clamped[0] = -1; // clamps to 0

// DataView - mixed types
const buf = new ArrayBuffer(8);
const view = new DataView(buf);

view.setInt8(0, 127); // byte 0: 127
view.setInt16(1, 1000); // bytes 1-2: 1000
view.setFloat32(4, 3.14); // bytes 4-7: 3.14

view.getInt8(0); // 127
view.getInt16(1); // 1000
view.getFloat32(4); // 3.14...

// Endianness control
view.setInt32(0, 0x12345678, true); // little-endian
view.setInt32(0, 0x12345678, false); // big-endian

// Convert regular array
const regular = [1, 2, 3, 4, 5];
const typed = new Int32Array(regular);

// Convert typed to regular
const back = Array.from(typed);
// or [...typed]

// Use cases
// 1. Binary file I/O
fetch('data.bin')
    .then(r => r.arrayBuffer())
    .then(buf => new Uint8Array(buf));

// 2. WebGL graphics
const vertices = new Float32Array([
    -1.0, -1.0,
     1.0, -1.0,
     0.0,  1.0
]);

// 3. Image processing
const imageData = ctx.getImageData(0, 0, w, h);
const pixels = imageData.data; // Uint8ClampedArray
// RGBA: [r, g, b, a, r, g, b, a, ...]

// 4. Network protocols
const header = new Uint8Array([0x01, 0x02, 0x03, 0x04]);

// SharedArrayBuffer (requires headers)
const shared = new SharedArrayBuffer(1024);
const worker1View = new Int32Array(shared);
const worker2View = new Int32Array(shared);
// Both workers see same memory
Note: Typed arrays are fixed-length, array-like objects for binary data. Use for performance-critical operations (WebGL, image processing, binary I/O). Regular arrays support more methods and dynamic sizing.

Section 9 Summary

  • Creation: Use literals [] for general use; Array.from() for iterables with mapper; Array.of() to avoid single-number trap; spread for copying
  • Mutating: push/pop (end O(1)), shift/unshift (start O(n)), splice (any position); sort needs compare function for numbers; these modify original
  • Non-mutating: slice (copy), concat (merge), flat/flatMap (flatten); ES2023 adds toSorted/toReversed/toSpliced/with for immutability
  • Iteration: map (transform), filter (select), reduce (accumulate), forEach (side effects); always provide initial value to reduce
  • Search: find/findIndex (with predicate), includes (existence, handles NaN), indexOf (position); at() for negative indexing
  • Testing: every (all pass), some (any pass); both short-circuit; every([]) === true, some([]) === false
  • Destructuring: [a, b] = arr, rest [...rest], skip with ,, defaults; spread ...arr for copy/merge (shallow!)
  • Typed arrays: Fixed-length, type-specific, backed by ArrayBuffer; use for binary data, WebGL, performance; limited methods vs regular arrays

10. Strings and Template Literals

10.1 String Creation and String Literals

Type Syntax Description Example
Single Quotes 'string' Basic string literal; escape ' as \' 'Hello world'
Double Quotes "string" Identical to single quotes; escape " as \" "Hello world"
Template Literal `string` Backticks; supports interpolation, multiline, and tag functions `Hello ${name}`
String Constructor String(value) Convert to string; returns primitive (without new) String(123) // "123"
String Object new String(value) Creates String object (wrapper); avoid - use primitives new String("hi")
Escape Sequences \n \t \\ \' \" Special characters: newline, tab, backslash, quotes 'Line 1\nLine 2'
Unicode Escape \uXXXX 4-digit hex Unicode code point (BMP only) '\u0041' // "A"
Unicode Code Point \u{X...} 1-6 hex digits; supports full Unicode range (including astral) '\u{1F600}' // "😀"
Hex Escape \xXX 2-digit hex byte value (0x00-0xFF) '\x41' // "A"
Raw String ES2015 String.raw`...` Template literal without escape processing String.raw`\n` // "\\n"

Example: String creation patterns

// Literal syntax (preferred)
const single = 'Hello';
const double = "World";
const template = `Hello ${name}`;

// String constructor (type conversion)
String(123); // "123"
String(true); // "true"
String(null); // "null"
String({a: 1}); // "[object Object]"

// Avoid String object (wrapper)
const bad = new String("text"); // typeof bad === "object" ❌
const good = "text"; // typeof good === "string" ✓

// Escape sequences
'Line 1\nLine 2'; // newline
'Tab\tseparated'; // tab
'She said "hi"'; // double quotes in single
"It's okay"; // single quote in double
'Can\'t escape'; // escaped single quote

// More escape sequences
'C:\\Users\\name'; // backslashes
'First\x20Second'; // space using hex
'\u00A9 2025'; // © 2025
'\u{1F4A9}'; // 💩 (requires code point syntax)

// Multiline strings
const multi = `Line 1
Line 2
Line 3`; // preserves newlines

const legacy = 'Line 1\n' +
               'Line 2\n' +
               'Line 3'; // old way

// Raw strings (no escape processing)
String.raw`C:\Users\name`; // "C:\\Users\\name"
String.raw`\n\t`; // "\\n\\t" (literal backslashes)

// Empty and whitespace
const empty = '';
const space = ' ';
const whitespace = '   \t\n   ';

// Immutability
const str = 'hello';
str[0] = 'H'; // No effect - strings are immutable
str.toUpperCase(); // Returns "HELLO", str unchanged
Note: Strings are immutable primitives. Use single/double quotes for simple strings; template literals for interpolation/multiline. Avoid new String() wrapper objects.

10.2 String Methods and String Manipulation

Method Syntax Description Returns
length str.length Number of UTF-16 code units (not characters!); astral chars count as 2 Number
charAt() str.charAt(index) Character at index; returns empty string if out of bounds String
charCodeAt() str.charCodeAt(index) UTF-16 code unit (0-65535); NaN if out of bounds Number
codePointAt() str.codePointAt(index) Full Unicode code point; handles astral characters correctly Number
at() ES2022 str.at(index) Character at index; negative indices from end; undefined if out of bounds String | undefined
concat() str.concat(str2, ...) Join strings; prefer + or template literals for readability String
slice() str.slice(start, end) Extract substring; negative indices from end; preferred method String
substring() str.substring(start, end) Like slice but swaps args if start > end; no negative indices String
substr() DEPRECATED str.substr(start, length) Extract by length; deprecated - use slice() instead String
toUpperCase() str.toUpperCase() Convert to uppercase; locale-independent String
toLowerCase() str.toLowerCase() Convert to lowercase; locale-independent String
toLocaleUpperCase() str.toLocaleUpperCase(locale) Locale-aware uppercase (e.g., Turkish İ/I) String
toLocaleLowerCase() str.toLocaleLowerCase(locale) Locale-aware lowercase String
trim() str.trim() Remove whitespace from both ends String
trimStart() / trimLeft() str.trimStart() Remove whitespace from start only String
trimEnd() / trimRight() str.trimEnd() Remove whitespace from end only String
padStart() ES2017 str.padStart(length, padStr) Pad to length from start; default space String
padEnd() ES2017 str.padEnd(length, padStr) Pad to length from end; default space String
repeat() str.repeat(count) Repeat string count times; count must be non-negative integer String

Example: String manipulation methods

const str = 'Hello World';

// Access characters
str.charAt(0); // "H"
str[0]; // "H" (bracket notation)
str.at(0); // "H"
str.at(-1); // "d" (last character)
str.at(-2); // "l" (second to last)

// Character codes
str.charCodeAt(0); // 72 (UTF-16 code unit)
str.codePointAt(0); // 72 (Unicode code point)

// Emoji handling
const emoji = '😀';
emoji.length; // 2 ❌ (surrogate pair)
emoji.codePointAt(0); // 128512 ✓
emoji.charCodeAt(0); // 55357 ❌ (high surrogate only)

// Substrings
str.slice(0, 5); // "Hello"
str.slice(6); // "World"
str.slice(-5); // "World" (last 5)
str.slice(-5, -1); // "Worl"

str.substring(0, 5); // "Hello" (same as slice)
str.substring(5, 0); // "Hello" (swaps args!)

// Case conversion
'JavaScript'.toUpperCase(); // "JAVASCRIPT"
'JavaScript'.toLowerCase(); // "javascript"

// Turkish locale example
'i'.toLocaleUpperCase('tr'); // "İ" (dotted capital I)
'I'.toLocaleLowerCase('tr'); // "ı" (dotless small i)

// Whitespace removal
'  hello  '.trim(); // "hello"
'  hello  '.trimStart(); // "hello  "
'  hello  '.trimEnd(); // "  hello"

// Padding
'5'.padStart(3, '0'); // "005"
'123'.padStart(5, '0'); // "00123"
'hello'.padEnd(10, '.'); // "hello....."

const cardNum = '1234';
cardNum.padStart(16, '*'); // "************1234"

// Repeat
'*'.repeat(10); // "**********"
'abc'.repeat(3); // "abcabcabc"

// Concatenation
'Hello'.concat(' ', 'World'); // "Hello World"
'a' + 'b' + 'c'; // "abc" (preferred)

// Chaining
const result = '  HELLO  '
    .trim()
    .toLowerCase()
    .repeat(2)
    .padStart(15, '.');
// "...hellohello"
Note: Use slice() over substring() for consistency with arrays. Avoid deprecated substr(). Remember length counts UTF-16 code units, not characters.

10.3 Template Literals and Tag Functions

Feature Syntax Description Example
Template Literal `text` Backticks; supports interpolation and multiline `Hello ${name}`
Interpolation ${expression} Embed any expression; converted to string via ToString `Sum: ${a + b}`
Multiline `line1\nline2` Preserves actual newlines and indentation `Line 1\nLine 2`
Tagged Template tag`template` Function processes template; receives strings and values separately html`<div>${text}</div>`
String.raw String.raw`text` Built-in tag that returns raw string (no escape processing) String.raw`\n` // "\\n"
Tag Function Signature Receives strings array and interpolated values
Parameters function tag(strings, ...values)
strings Array of string literals; has .raw property for unprocessed strings
...values Rest parameter with all interpolated expression results

Example: Template literals and tag functions

// Basic interpolation
const name = 'Alice';
const age = 25;
const msg = `Hello ${name}, you are ${age} years old`;
// "Hello Alice, you are 25 years old"

// Expressions
const a = 10, b = 20;
`Sum: ${a + b}`; // "Sum: 30"
`Comparison: ${a > b ? 'a' : 'b'}`; // "Comparison: b"
`Array: ${[1, 2, 3].join(', ')}`; // "Array: 1, 2, 3"

// Multiline
const html = `
  <div class="card">
    <h2>${name}</h2>
    <p>Age: ${age}</p>
  </div>
`; // Preserves indentation and newlines

// Nesting
`Outer ${`Inner ${1 + 1}`} text`; // "Outer Inner 2 text"

// String.raw - no escape processing
String.raw`C:\Users\name`; // "C:\\Users\\name"
String.raw`Line 1\nLine 2`; // "Line 1\\nLine 2"

// Custom tag function - basic
function upper(strings, ...values) {
    let result = '';
    strings.forEach((str, i) => {
        result += str;
        if (i < values.length) {
            result += String(values[i]).toUpperCase();
        }
    });
    return result;
}

upper`Hello ${name}, age ${age}`;
// "Hello ALICE, age 25"

// Tag function - HTML escaping
function html(strings, ...values) {
    return strings.reduce((result, str, i) => {
        const value = values[i] || '';
        const escaped = String(value)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;');
        return result + str + escaped;
    }, '');
}

const userInput = '<script>alert("xss")</script>';
html`<div>${userInput}</div>`;
// "<div>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</div>"

// Tag function - SQL (parameterized queries)
function sql(strings, ...values) {
    return {
        query: strings.join('?'),
        params: values
    };
}

const userId = 123;
const query = sql`SELECT * FROM users WHERE id = ${userId}`;
// {query: "SELECT * FROM users WHERE id = ?", params: [123]}

// Tag function - i18n translation
function t(strings, ...values) {
    const key = strings.join('{}');
    // Look up translation for key
    return translate(key, values);
}

t`Welcome ${name}!`; // Translates "Welcome {}!" with name

// Access raw strings
function logger(strings, ...values) {
    console.log('Processed:', strings);
    console.log('Raw:', strings.raw);
    console.log('Values:', values);
}

logger`Line 1\nLine 2`;
// Processed: ["Line 1
// Line 2"]
// Raw: ["Line 1\\nLine 2"]
// Values: []

// Empty interpolations
`a${null}b`; // "anullb"
`a${undefined}b`; // "aundefinedb"
`a${''}b`; // "ab"
Note: Tagged templates enable custom string processing (HTML escaping, i18n, SQL, styling). The strings.raw property provides unprocessed strings. All interpolated values are converted to strings.

10.4 String Interpolation and Expression Embedding

Pattern Syntax Description Example
Variable ${variable} Insert variable value; converts to string `Hello ${name}`
Expression ${expr} Any valid expression; evaluated and converted to string `Sum: ${a + b}`
Function Call ${fn()} Call function and insert return value `Time: ${getTime()}`
Ternary ${cond ? a : b} Conditional expressions inline `${online ? '🟢' : '🔴'}`
Logical AND ${cond && value} Short-circuit conditional rendering; false becomes "false" string `${hasError && errorMsg}`
Logical OR ${val || default} Fallback value; beware of 0, '', false `${name || 'Guest'}`
Nullish Coalescing ${val ?? default} Fallback only for null/undefined; preserves 0, '', false `${count ?? 0}`
Array Method ${arr.method()} Array operations inline `Items: ${arr.join(', ')}`
Object Property ${obj.prop} Access nested properties `Name: ${user.name}`
Nested Template ${`inner ${val}`} Template literals can be nested `Outer ${`inner ${x}`}`

Example: String interpolation patterns

const user = {name: 'Alice', age: 25, online: true};
const items = ['apple', 'banana', 'orange'];
const count = 0;

// Basic variable
`Username: ${user.name}`; // "Username: Alice"

// Arithmetic expressions
`Total: ${(99.99 * 1.1).toFixed(2)}`; // "Total: $109.99"
`Progress: ${(completed / total * 100).toFixed(1)}%`;

// Function calls
`Generated at ${new Date().toISOString()}`;
`Random: ${Math.random().toFixed(4)}`;

// Ternary operator
`Status: ${user.online ? '🟢 Online' : '🔴 Offline'}`;
`You have ${count === 0 ? 'no' : count} items`;

// Logical AND (conditional content)
`${user.admin && 'Admin Panel'}`;
// If admin: "Admin Panel", else: "false" ❌

// Better: use ternary for boolean
`${user.admin ? 'Admin Panel' : ''}`;
// If admin: "Admin Panel", else: ""

// Logical OR (fallback)
`Hello ${user.nickname || user.name}`;
`Count: ${count || 'N/A'}`; // ❌ 0 becomes 'N/A'!

// Nullish coalescing (better fallback)
`Count: ${count ?? 'N/A'}`; // ✓ 0 stays 0
`Name: ${user.name ?? 'Anonymous'}`;

// Array methods
`Items: ${items.join(', ')}`; // "Items: apple, banana, orange"
`Count: ${items.length}`;
`First: ${items[0]}`;

// Array rendering with map
`<ul>${items.map(item => `<li>${item}</li>`).join('')}</ul>`;
// "<ul><li>apple</li><li>banana</li><li>orange</li></ul>"

// Object methods
`User: ${JSON.stringify(user)}`;
`Keys: ${Object.keys(user).join(', ')}`;

// Nested templates
const className = `btn ${user.online ? 'btn-success' : 'btn-danger'}`;
`<button class="${className}">${user.name}</button>`;

// Complex expressions
const price = 99.99;
const discount = 0.15;
const tax = 0.08;
`Final: ${(price * (1 - discount) * (1 + tax)).toFixed(2)}`;

// Method chaining
`Result: ${str.trim().toLowerCase().split(' ').join('-')}`;

// Type coercion
`Number: ${123}`; // "Number: 123"
`Boolean: ${true}`; // "Boolean: true"
`Null: ${null}`; // "Null: null"
`Undefined: ${undefined}`; // "Undefined: undefined"
`Object: ${{}}`; // "Object: [object Object]"
`Array: ${[1, 2]}`; // "Array: 1,2"

// Multi-line with interpolation
const html = `
  <div class="user-card ${user.online ? 'online' : 'offline'}">
    <h2>${user.name}</h2>
    <p>Age: ${user.age}</p>
    <p>Status: ${user.online ? '🟢' : '🔴'}</p>
  </div>
`;

// Escaping interpolation
const literal = String.raw`Not interpolated: \${variable}`;
// "Not interpolated: ${variable}"
Warning: Logical AND ${cond && val} converts false to "false" string. Use ternary ${cond ? val : ''} for conditional rendering. Prefer ?? over || for fallbacks.

10.5 Unicode Handling and Character Encoding

Method/Feature Syntax Description Example
length Property str.length UTF-16 code units, not characters; astral symbols count as 2 '😀'.length // 2
Code Point Escape \u{codepoint} 1-6 hex digits; supports full Unicode (0-10FFFF) '\u{1F600}' // 😀
BMP Escape \uXXXX 4 hex digits; Basic Multilingual Plane only (0-FFFF) '\u00A9' // ©
String.fromCharCode() String.fromCharCode(code, ...) Create from UTF-16 code units; astral chars need two codes (surrogate pair) String.fromCharCode(65) // "A"
String.fromCodePoint() String.fromCodePoint(point, ...) Create from Unicode code points; handles astral chars correctly String.fromCodePoint(0x1F600) // "😀"
codePointAt() str.codePointAt(index) Get full Unicode code point at position '😀'.codePointAt(0) // 128512
charCodeAt() str.charCodeAt(index) Get UTF-16 code unit; only half of astral char (high surrogate) '😀'.charCodeAt(0) // 55357
normalize() str.normalize(form) Unicode normalization: NFC, NFD, NFKC, NFKD; for comparison 'café'.normalize('NFC')
localeCompare() str.localeCompare(str2, locale) Locale-aware string comparison; handles accents, case correctly 'ä'.localeCompare('z', 'de')
Spread/Array.from [...str] Iterate by code points, not code units; handles emoji correctly [...'😀🎉'].length // 2
for...of Loop for (const char of str) Iterates by Unicode code points; emoji-safe for (const c of '😀')

Example: Unicode handling

// BMP characters (single code unit)
const ascii = 'A';
ascii.length; // 1
ascii.charCodeAt(0); // 65
ascii.codePointAt(0); // 65

const copyright = '\u00A9'; // ©
copyright.length; // 1

// Astral plane (emoji, historic scripts)
const emoji = '😀';
emoji.length; // 2 ❌ (surrogate pair!)
emoji.charCodeAt(0); // 55357 (high surrogate)
emoji.charCodeAt(1); // 56832 (low surrogate)
emoji.codePointAt(0); // 128512 ✓ (full code point)

// Creating from code points
String.fromCharCode(65); // "A"
String.fromCharCode(0x00A9); // "©"

// Emoji requires fromCodePoint
String.fromCodePoint(0x1F600); // "😀"
String.fromCharCode(0x1F600); // ❌ Wrong!

// Surrogate pair manually (don't do this!)
String.fromCharCode(0xD83D, 0xDE00); // "😀" (works but use fromCodePoint)

// Code point escape syntax
'\u{1F600}'; // "😀"
'\u{1F4A9}'; // "💩"
'\u{1F680}'; // "🚀"

// Length issues with emoji
'hello'.length; // 5 ✓
'😀'.length; // 2 ❌
'👨‍👩‍👧‍👦'.length; // 11 ❌ (family emoji with ZWJ)

// Count actual characters
[...'hello'].length; // 5
[...'😀'].length; // 1 ✓
[...'😀🎉'].length; // 2 ✓

// However, combining characters still tricky
[...'👨‍👩‍👧‍👦'].length; // 7 (4 people + 3 ZWJ)

// Iterate correctly
for (const char of '😀🎉') {
    console.log(char); // "😀", "🎉" (correct!)
}

// Split fails with emoji
'😀🎉'.split(''); // ['�','�','�','�'] ❌
[...'😀🎉']; // ['😀','🎉'] ✓

// Unicode normalization
const e1 = '\u00E9'; // é (single code point)
const e2 = '\u0065\u0301'; // é (e + combining acute accent)

e1 === e2; // false ❌
e1.length; // 1
e2.length; // 2

e1.normalize() === e2.normalize(); // true ✓

// Forms: NFC (composed), NFD (decomposed)
'café'.normalize('NFC').length; // 4
'café'.normalize('NFD').length; // 5 (e + combining accent)

// Locale-aware comparison
const strings = ['ä', 'z', 'a'];
strings.sort(); // ['a', 'z', 'ä'] ❌
strings.sort((a, b) => a.localeCompare(b, 'de'));
// ['a', 'ä', 'z'] ✓ (German collation)

'ä'.localeCompare('z', 'de'); // -1 (ä comes before z)
'ä'.localeCompare('z', 'sv'); // 1 (ä comes after z in Swedish)

// Case comparison with locale
'i'.toLocaleUpperCase('en'); // "I"
'i'.toLocaleUpperCase('tr'); // "İ" (dotted capital I in Turkish)

// Real character count (approximate)
function graphemeLength(str) {
    return [...new Intl.Segmenter().segment(str)].length;
}

graphemeLength('😀'); // 1
graphemeLength('👨‍👩‍👧‍👦'); // 1 (treats family as one grapheme)
Warning: length counts UTF-16 code units, not characters. Use [...str].length for code point count. For grapheme clusters (with ZWJ, combining marks), use Intl.Segmenter.

10.6 String Regular Expression Integration

Method Syntax Description Returns
match() str.match(regexp) Find matches; with /g returns all matches, without returns match details Array | null
matchAll() ES2020 str.matchAll(regexp) Iterator of all matches with capture groups; regexp must have /g flag Iterator
search() str.search(regexp) Find index of first match; ignores /g flag Index | -1
replace() str.replace(regexp|str, newStr|fn) Replace matches; without /g replaces first only String
replaceAll() ES2021 str.replaceAll(str|regexp, newStr|fn) Replace all occurrences; regexp must have /g or use string String
split() str.split(separator, limit) Split by string or regexp; optional limit on array size Array
startsWith() str.startsWith(search, pos) Check if starts with string; optional position; no regexp Boolean
endsWith() str.endsWith(search, length) Check if ends with string; optional length limit; no regexp Boolean
includes() str.includes(search, pos) Check if contains string; optional start position; no regexp Boolean
indexOf() str.indexOf(search, pos) Find first occurrence; case-sensitive; no regexp Index | -1
lastIndexOf() str.lastIndexOf(search, pos) Find last occurrence; searches backwards; no regexp Index | -1

Example: String RegExp integration

const str = 'The quick brown fox jumps over the lazy dog';

// match - without /g (first match with details)
str.match(/quick/);
// ["quick", index: 4, input: "...", groups: undefined]

str.match(/(\w+) (\w+)/);
// ["The quick", "The", "quick", index: 0, ...]

// match - with /g (all matches, no details)
str.match(/\w+/g);
// ["The", "quick", "brown", "fox", ...]

str.match(/o/g); // ["o", "o", "o", "o"]

// matchAll - all matches with details
const regex = /t(\w+)/gi;
const matches = [...str.matchAll(regex)];
matches[0]; // ["The", "he", index: 0, ...]
matches[1]; // ["the", "he", index: 31, ...]

// matchAll with named groups
const pattern = /(?<word>\w+) (?<num>\d+)/g;
const text = 'item 1, thing 2';
for (const match of text.matchAll(pattern)) {
    console.log(match.groups.word, match.groups.num);
    // "item" "1", "thing" "2"
}

// search - find position
str.search(/brown/); // 10
str.search(/cat/); // -1
str.search(/THE/i); // 0 (case-insensitive)

// replace - string or regexp
str.replace('dog', 'cat');
// "The quick brown fox jumps over the lazy cat"

str.replace(/the/gi, 'a');
// "a quick brown fox jumps over a lazy dog"

// replace with function
str.replace(/\b\w{4}\b/g, match => match.toUpperCase());
// "The QUICK brown fox JUMPS OVER the LAZY dog"

// replace with capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// replace with named groups
'2025-12-17'.replace(
    /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
    '
const str = 'The quick brown fox jumps over the lazy dog';

// match - without /g (first match with details)
str.match(/quick/);
// ["quick", index: 4, input: "...", groups: undefined]

str.match(/(\w+) (\w+)/);
// ["The quick", "The", "quick", index: 0, ...]

// match - with /g (all matches, no details)
str.match(/\w+/g);
// ["The", "quick", "brown", "fox", ...]

str.match(/o/g); // ["o", "o", "o", "o"]

// matchAll - all matches with details
const regex = /t(\w+)/gi;
const matches = [...str.matchAll(regex)];
matches[0]; // ["The", "he", index: 0, ...]
matches[1]; // ["the", "he", index: 31, ...]

// matchAll with named groups
const pattern = /(?<word>\w+) (?<num>\d+)/g;
const text = 'item 1, thing 2';
for (const match of text.matchAll(pattern)) {
    console.log(match.groups.word, match.groups.num);
    // "item" "1", "thing" "2"
}

// search - find position
str.search(/brown/); // 10
str.search(/cat/); // -1
str.search(/THE/i); // 0 (case-insensitive)

// replace - string or regexp
str.replace('dog', 'cat');
// "The quick brown fox jumps over the lazy cat"

str.replace(/the/gi, 'a');
// "a quick brown fox jumps over a lazy dog"

// replace with function
str.replace(/\b\w{4}\b/g, match => match.toUpperCase());
// "The QUICK brown fox JUMPS OVER the LAZY dog"

// replace with capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// replace with named groups
'2025-12-17'.replace(
    /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// replaceAll - all occurrences
'aaa'.replace('a', 'b'); // "baa"
'aaa'.replaceAll('a', 'b'); // "bbb"

'a-b-c'.replaceAll('-', '_'); // "a_b_c"

// split by regexp
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split with limit
'a-b-c-d'.split('-', 2); // ["a", "b"]

// includes, startsWith, endsWith (no regexp!)
str.includes('fox'); // true
str.includes('cat'); // false

str.startsWith('The'); // true
str.startsWith('the'); // false (case-sensitive)

str.endsWith('dog'); // true
str.endsWith('cat'); // false

// indexOf, lastIndexOf
str.indexOf('o'); // 12 (first 'o')
str.lastIndexOf('o'); // 41 (last 'o')

str.indexOf('the'); // -1 (case-sensitive)
str.toLowerCase().indexOf('the'); // 0

// Check from position
str.startsWith('quick', 4); // true (at index 4)
str.includes('fox', 20); // false (search starts at 20)

// Case-insensitive search
const lower = str.toLowerCase();
lower.includes('THE'.toLowerCase()); // true

// Or use regexp with /i
/THE/i.test(str); // true
#x3C;month>/
const str = 'The quick brown fox jumps over the lazy dog';

// match - without /g (first match with details)
str.match(/quick/);
// ["quick", index: 4, input: "...", groups: undefined]

str.match(/(\w+) (\w+)/);
// ["The quick", "The", "quick", index: 0, ...]

// match - with /g (all matches, no details)
str.match(/\w+/g);
// ["The", "quick", "brown", "fox", ...]

str.match(/o/g); // ["o", "o", "o", "o"]

// matchAll - all matches with details
const regex = /t(\w+)/gi;
const matches = [...str.matchAll(regex)];
matches[0]; // ["The", "he", index: 0, ...]
matches[1]; // ["the", "he", index: 31, ...]

// matchAll with named groups
const pattern = /(?<word>\w+) (?<num>\d+)/g;
const text = 'item 1, thing 2';
for (const match of text.matchAll(pattern)) {
    console.log(match.groups.word, match.groups.num);
    // "item" "1", "thing" "2"
}

// search - find position
str.search(/brown/); // 10
str.search(/cat/); // -1
str.search(/THE/i); // 0 (case-insensitive)

// replace - string or regexp
str.replace('dog', 'cat');
// "The quick brown fox jumps over the lazy cat"

str.replace(/the/gi, 'a');
// "a quick brown fox jumps over a lazy dog"

// replace with function
str.replace(/\b\w{4}\b/g, match => match.toUpperCase());
// "The QUICK brown fox JUMPS OVER the LAZY dog"

// replace with capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// replace with named groups
'2025-12-17'.replace(
    /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// replaceAll - all occurrences
'aaa'.replace('a', 'b'); // "baa"
'aaa'.replaceAll('a', 'b'); // "bbb"

'a-b-c'.replaceAll('-', '_'); // "a_b_c"

// split by regexp
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split with limit
'a-b-c-d'.split('-', 2); // ["a", "b"]

// includes, startsWith, endsWith (no regexp!)
str.includes('fox'); // true
str.includes('cat'); // false

str.startsWith('The'); // true
str.startsWith('the'); // false (case-sensitive)

str.endsWith('dog'); // true
str.endsWith('cat'); // false

// indexOf, lastIndexOf
str.indexOf('o'); // 12 (first 'o')
str.lastIndexOf('o'); // 41 (last 'o')

str.indexOf('the'); // -1 (case-sensitive)
str.toLowerCase().indexOf('the'); // 0

// Check from position
str.startsWith('quick', 4); // true (at index 4)
str.includes('fox', 20); // false (search starts at 20)

// Case-insensitive search
const lower = str.toLowerCase();
lower.includes('THE'.toLowerCase()); // true

// Or use regexp with /i
/THE/i.test(str); // true
#x3C;day>/
const str = 'The quick brown fox jumps over the lazy dog';

// match - without /g (first match with details)
str.match(/quick/);
// ["quick", index: 4, input: "...", groups: undefined]

str.match(/(\w+) (\w+)/);
// ["The quick", "The", "quick", index: 0, ...]

// match - with /g (all matches, no details)
str.match(/\w+/g);
// ["The", "quick", "brown", "fox", ...]

str.match(/o/g); // ["o", "o", "o", "o"]

// matchAll - all matches with details
const regex = /t(\w+)/gi;
const matches = [...str.matchAll(regex)];
matches[0]; // ["The", "he", index: 0, ...]
matches[1]; // ["the", "he", index: 31, ...]

// matchAll with named groups
const pattern = /(?<word>\w+) (?<num>\d+)/g;
const text = 'item 1, thing 2';
for (const match of text.matchAll(pattern)) {
    console.log(match.groups.word, match.groups.num);
    // "item" "1", "thing" "2"
}

// search - find position
str.search(/brown/); // 10
str.search(/cat/); // -1
str.search(/THE/i); // 0 (case-insensitive)

// replace - string or regexp
str.replace('dog', 'cat');
// "The quick brown fox jumps over the lazy cat"

str.replace(/the/gi, 'a');
// "a quick brown fox jumps over a lazy dog"

// replace with function
str.replace(/\b\w{4}\b/g, match => match.toUpperCase());
// "The QUICK brown fox JUMPS OVER the LAZY dog"

// replace with capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// replace with named groups
'2025-12-17'.replace(
    /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// replaceAll - all occurrences
'aaa'.replace('a', 'b'); // "baa"
'aaa'.replaceAll('a', 'b'); // "bbb"

'a-b-c'.replaceAll('-', '_'); // "a_b_c"

// split by regexp
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split with limit
'a-b-c-d'.split('-', 2); // ["a", "b"]

// includes, startsWith, endsWith (no regexp!)
str.includes('fox'); // true
str.includes('cat'); // false

str.startsWith('The'); // true
str.startsWith('the'); // false (case-sensitive)

str.endsWith('dog'); // true
str.endsWith('cat'); // false

// indexOf, lastIndexOf
str.indexOf('o'); // 12 (first 'o')
str.lastIndexOf('o'); // 41 (last 'o')

str.indexOf('the'); // -1 (case-sensitive)
str.toLowerCase().indexOf('the'); // 0

// Check from position
str.startsWith('quick', 4); // true (at index 4)
str.includes('fox', 20); // false (search starts at 20)

// Case-insensitive search
const lower = str.toLowerCase();
lower.includes('THE'.toLowerCase()); // true

// Or use regexp with /i
/THE/i.test(str); // true
#x3C;year>'
); // "12/17/2025" // replaceAll - all occurrences 'aaa'.replace('a', 'b'); // "baa" 'aaa'.replaceAll('a', 'b'); // "bbb" 'a-b-c'.replaceAll('-', '_'); // "a_b_c" // split by regexp 'a1b2c3'.split(/\d/); // ["a", "b", "c", ""] 'one,two;three:four'.split(/[,;:]/); // ["one", "two", "three", "four"] // split with limit 'a-b-c-d'.split('-', 2); // ["a", "b"] // includes, startsWith, endsWith (no regexp!) str.includes('fox'); // true str.includes('cat'); // false str.startsWith('The'); // true str.startsWith('the'); // false (case-sensitive) str.endsWith('dog'); // true str.endsWith('cat'); // false // indexOf, lastIndexOf str.indexOf('o'); // 12 (first 'o') str.lastIndexOf('o'); // 41 (last 'o') str.indexOf('the'); // -1 (case-sensitive) str.toLowerCase().indexOf('the'); // 0 // Check from position str.startsWith('quick', 4); // true (at index 4) str.includes('fox', 20); // false (search starts at 20) // Case-insensitive search const lower = str.toLowerCase(); lower.includes('THE'.toLowerCase()); // true // Or use regexp with /i /THE/i.test(str); // true
Note: Use match() for simple searches; matchAll() for all matches with details; replace() with functions for complex transformations. includes/startsWith/endsWith don't support regexp.

Section 10 Summary

  • Creation: Use single/double quotes for simple strings; template literals `...` for interpolation/multiline; escape sequences \n \t; Unicode \u{...} for full range
  • Methods: slice() for substrings (supports negative indices); trim/padStart/padEnd for formatting; toUpperCase/toLowerCase for case; all return new strings (immutable)
  • Template literals: `${expr}` interpolation; multiline preserves formatting; tagged templates tag`...` for custom processing (HTML escape, i18n, SQL)
  • Interpolation: Any expression; use ternary ? : not && for conditionals; ?? for null/undefined fallback; beware || with 0/false
  • Unicode: length counts UTF-16 units not characters; [...str] for code points; codePointAt/fromCodePoint for emoji; normalize() for comparison
  • RegExp: match/matchAll find patterns; replace/replaceAll modify; split divides; includes/startsWith/endsWith check (no regexp); search finds index

11. Regular Expressions and Pattern Matching

11.1 RegExp Literal and Constructor Syntax

Syntax Form Description Example
Literal Syntax /pattern/flags Most common; compiled at parse time; cannot use variables in pattern /hello/i
Constructor new RegExp(pattern, flags) Dynamic patterns; compiled at runtime; pattern is string (escape backslashes!) new RegExp('\\d+', 'g')
Constructor (no new) RegExp(pattern, flags) Same as with new; returns RegExp instance RegExp('test')
From RegExp new RegExp(regexp, flags) Clone with optional new flags; ES2015+ new RegExp(/test/i, 'g')
Property Type Description Example
source String Pattern text (without delimiters and flags) /abc/i.source // "abc"
flags String All flags as string; alphabetically sorted /a/gi.flags // "gi"
global Boolean Has g flag /a/g.global // true
ignoreCase Boolean Has i flag /a/i.ignoreCase // true
multiline Boolean Has m flag /a/m.multiline // true
dotAll Boolean Has s flag (. matches newlines) /a/s.dotAll // true
unicode Boolean Has u flag /a/u.unicode // true
sticky Boolean Has y flag /a/y.sticky // true
lastIndex Number Start position for next match; used with g or y flags regex.lastIndex = 0

Example: RegExp creation and properties

// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;

// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally

// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d

// Or use String.raw
const regex = new RegExp(String.raw`\d+`);

// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")

// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false

// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)

// Reset lastIndex
globalRe.lastIndex = 0;

// Create from string (user input)
function createRegex(userPattern, options = {}) {
    const flags = 
        (options.global ? 'g' : '') +
        (options.ignoreCase ? 'i' : '') +
        (options.multiline ? 'm' : '');
    
    try {
        return new RegExp(userPattern, flags);
    } catch (e) {
        console.error('Invalid regex:', e.message);
        return null;
    }
}

// Escape special characters for literal matching
function escapeRegex(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\
// Literal syntax (preferred)
const literal = /hello/i;
const pattern = /\d{3}-\d{4}/;

// Constructor - dynamic patterns
const word = 'test';
const dynamic = new RegExp(word, 'gi');
// Matches 'test' case-insensitively, globally

// Constructor - escape backslashes!
const wrong = new RegExp('\d+'); // ❌ \d becomes 'd'
const correct = new RegExp('\\d+'); // ✓ \\d becomes \d

// Or use String.raw
const regex = new RegExp(String.raw`\d+`);

// Clone with new flags
const original = /test/i;
const clone = new RegExp(original, 'g');
// Pattern: "test", flags: "g" (not "gi")

// Properties
const re = /hello/gi;
re.source; // "hello"
re.flags; // "gi"
re.global; // true
re.ignoreCase; // true
re.multiline; // false

// lastIndex (stateful with g or y flag)
const globalRe = /test/g;
globalRe.lastIndex; // 0 (initial)
globalRe.exec('test test');
globalRe.lastIndex; // 4 (after first match)

// Reset lastIndex
globalRe.lastIndex = 0;

// Create from string (user input)
function createRegex(userPattern, options = {}) {
    const flags = 
        (options.global ? 'g' : '') +
        (options.ignoreCase ? 'i' : '') +
        (options.multiline ? 'm' : '');
    
    try {
        return new RegExp(userPattern, flags);
    } catch (e) {
        console.error('Invalid regex:', e.message);
        return null;
    }
}

// Escape special characters for literal matching
function escapeRegex(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

const userInput = 'hello.world';
const escaped = escapeRegex(userInput); // "hello\\.world"
const safe = new RegExp(escaped); // matches literal "hello.world"
#x26;"
);
} const userInput = 'hello.world'; const escaped = escapeRegex(userInput); // "hello\\.world" const safe = new RegExp(escaped); // matches literal "hello.world"
Note: Use literals /pattern/flags for static patterns (better performance); constructor for dynamic patterns. Remember to double-escape backslashes in strings: '\\d' not '\d'.

11.2 RegExp Flags and Global Modifiers

Flag Name Description Effect
g Global Find all matches, not just first; affects exec(), test(), match(), replace() Multiple matches
i Ignore Case Case-insensitive matching; [a-z] matches both cases /a/i matches 'A' and 'a'
m Multiline ^ and $ match line boundaries, not just string start/end ^word matches after \n
s ES2018 DotAll . (dot) matches newlines; normally dot excludes \n \r \u2028 \u2029 /.+/s matches across lines
u ES2015 Unicode Full Unicode support; astral characters, Unicode escapes, properties \u{1F600} and \p{...}
y ES2015 Sticky Match must start at lastIndex; no skipping ahead For tokenizers/parsers
d ES2022 Indices Capture group indices in .indices property of match result match.indices[0]

Example: Flag behaviors

const str = 'Hello World\nHello Again';

// g - global (all matches)
str.match(/hello/i); // ["Hello"] (first only)
str.match(/hello/gi); // ["Hello", "Hello"] (all)

/\d+/g.test('123'); // true
/\d+/g.test('123'); // false (lastIndex moved!)

// i - ignore case
/hello/.test('HELLO'); // false
/hello/i.test('HELLO'); // true

// m - multiline (^ $ match line boundaries)
const multi = 'line1\nline2\nline3';
multi.match(/^line/g); // ["line"] (only first)
multi.match(/^line/gm); // ["line", "line", "line"] (each line)

/world$/.test('hello\nworld'); // true (end of string)
/world$/m.test('world\nhello'); // true (end of line)

// s - dotAll (. matches newlines)
/.+/.test('hello\nworld'); // false (. stops at \n)
/.+/s.test('hello\nworld'); // true (. matches \n)

'a\nb'.match(/a.b/); // null
'a\nb'.match(/a.b/s); // ["a\nb"]

// u - unicode (proper astral character handling)
'😀'.match(/./); // ["�"] ❌ (high surrogate only)
'😀'.match(/./u); // ["😀"] ✓

// Unicode property escapes (requires u flag)
/\p{Emoji}/u.test('😀'); // true
/\p{Script=Greek}/u.test('α'); // true
/\p{Letter}/u.test('A'); // true

// Count code points, not code units
'😀'.length; // 2
'😀'.match(/./gu); // ["😀"] (1 match)
'😀'.match(/./g); // ["�","�"] (2 matches)

// y - sticky (match at exact position)
const sticky = /\d+/y;
sticky.lastIndex = 0;
sticky.exec('123 456'); // ["123"]
sticky.lastIndex; // 3

sticky.exec('123 456'); // null (position 3 is space)
sticky.lastIndex; // 0 (reset on failure)

// Without y, would skip ahead and match 456
const notSticky = /\d+/g;
notSticky.lastIndex = 3;
notSticky.exec('123 456'); // ["456"]

// d - indices (capture group positions)
const indicesRe = /(\d+)-(\d+)/d;
const match = indicesRe.exec('id: 123-456');

match[1]; // "123"
match[2]; // "456"
match.indices[1]; // [4, 7] (position of "123")
match.indices[2]; // [8, 11] (position of "456")

// Combine flags
const combined = /pattern/gimsu;
combined.flags; // "gimsu" (alphabetically sorted)
Warning: Global g flag makes RegExp stateful (lastIndex). Reset with regex.lastIndex = 0 or use string methods. Always use u flag for Unicode text!

11.3 Pattern Matching Methods (test, exec, match)

Method Syntax Description Returns
test() regex.test(str) Check if pattern matches; fastest for existence check Boolean
exec() regex.exec(str) Get match details with capture groups; use in loop with /g flag Array | null
match() str.match(regex) Without /g: like exec(); with /g: array of all matches (no details) Array | null
matchAll() ES2020 str.matchAll(regex) Iterator of all matches with full details; regex must have /g Iterator

Example: Pattern matching methods

const str = 'The year 2025 and 2026';

// test() - simple existence check
/\d+/.test(str); // true
/cat/.test(str); // false

// Use for validation
function isEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// exec() - detailed match info
const yearRe = /(\d{4})/;
const match = yearRe.exec(str);
match[0]; // "2025" (full match)
match[1]; // "2025" (capture group 1)
match.index; // 9 (position)
match.input; // original string

// exec() with /g flag (iterate)
const yearReGlobal = /(\d{4})/g;
let m;
while ((m = yearReGlobal.exec(str)) !== null) {
    console.log(m[1], 'at', m.index);
    // "2025" at 9
    // "2026" at 18
}

// match() without /g (same as exec)
str.match(/(\d{4})/);
// ["2025", "2025", index: 9, input: "...", groups: undefined]

// match() with /g (all matches, no details)
str.match(/\d{4}/g); // ["2025", "2026"]
str.match(/\d+/g); // ["2025", "2026"]

// matchAll() - best of both (ES2020+)
const years = str.matchAll(/(\d{4})/g);
for (const match of years) {
    console.log(match[1], 'at', match.index);
    // "2025" at 9
    // "2026" at 18
}

// Convert to array
const allMatches = [...str.matchAll(/(\d{4})/g)];

// Named capture groups
const phoneRe = /(?<area>\d{3})-(?<exchange>\d{3})-(?<number>\d{4})/;
const phone = '555-123-4567';
const phoneMatch = phone.match(phoneRe);

phoneMatch[1]; // "555"
phoneMatch.groups.area; // "555"
phoneMatch.groups.exchange; // "123"
phoneMatch.groups.number; // "4567"

// Multiple patterns
const email = 'user@example.com';
const emailRe = /^(?<user>[^@]+)@(?<domain>[^@]+)$/;
const emailMatch = email.match(emailRe);
emailMatch.groups.user; // "user"
emailMatch.groups.domain; // "example.com"

// No match returns null
'abc'.match(/\d+/); // null
/\d+/.test('abc'); // false
/\d+/.exec('abc'); // null

// Global test() gotcha (stateful!)
const re = /test/g;
re.test('test test'); // true (first call)
re.test('test test'); // true (second call)
re.test('test test'); // false! (lastIndex past end)
re.lastIndex; // 0 (reset)

// Solution: don't reuse global regex for test()
function hasDigit(str) {
    return /\d/.test(str); // no /g, always works
}

// Or reset lastIndex
function hasPattern(str, regex) {
    regex.lastIndex = 0;
    return regex.test(str);
}
Note: Use test() for boolean checks; match() for simple extraction; matchAll() for all matches with details. Avoid test() with global regex in loops (stateful).
Method Syntax Description Returns
replace() str.replace(regex, newStr|fn) Replace first match (or all with /g); newStr can use $1, $2, etc. String
replaceAll() ES2021 str.replaceAll(str|regex, newStr|fn) Replace all occurrences; regex must have /g flag String
split() str.split(regex, limit) Split string by pattern; optional limit; capture groups included in result Array
search() str.search(regex) Find index of first match; ignores /g flag; like indexOf for regex Index | -1
Replacement Patterns (in replace newStr argument)
Pattern Inserts Example Result
$$ Literal $ '$10'.replace(/\d+/, '$$&') "$&10"
$& Entire match 'cat'.replace(/\w+/, '[$&]') "[cat]"
$` Text before match 'a-b'.replace(/-/, '$`') "aab"
$' Text after match 'a-b'.replace(/-/, "$'") "abb"
$n Capture group n (1-99) 'John Doe'.replace(/(\w+) (\w+)/, '$2, $1') "Doe, John"
$<name> Named capture group '2025-12-17'.replace(/(?<y>\d+)-(?<m>\d+)-(?<d>\d+)/, '$<m>/$<d>/$<y>') "12/17/2025"

Example: String RegExp methods

|$\')'); // "a(a|b)b" // Capture groups 'John Smith'.replace(/(\w+) (\w+)/, '$2, $1'); // "Smith, John" // Named groups const date = '2025-12-17'; date.replace( /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/, '
const str = 'Hello World, hello again';

// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"

// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"

// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"

// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"

// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// Named groups
const date = '2025-12-17';
date.replace(
    /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"

// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
    return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"

// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
    return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"

// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]

// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]

// split - whitespace
'  hello   world  '.split(/\s+/);
// ["", "hello", "world", ""]

'  hello   world  '.trim().split(/\s+/);
// ["hello", "world"]

// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1

// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')

// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4

// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
    return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"

// 2. Sanitize HTML
function escapeHtml(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}

// 3. Slugify
function slugify(str) {
    return str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"

// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x3C;month>/
const str = 'Hello World, hello again';

// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"

// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"

// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"

// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"

// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// Named groups
const date = '2025-12-17';
date.replace(
    /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"

// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
    return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"

// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
    return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"

// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]

// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]

// split - whitespace
'  hello   world  '.split(/\s+/);
// ["", "hello", "world", ""]

'  hello   world  '.trim().split(/\s+/);
// ["hello", "world"]

// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1

// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')

// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4

// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
    return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"

// 2. Sanitize HTML
function escapeHtml(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}

// 3. Slugify
function slugify(str) {
    return str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"

// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x3C;day>/
const str = 'Hello World, hello again';

// replace - first match
str.replace('hello', 'hi'); // "Hello World, hi again"
str.replace(/hello/, 'hi'); // "Hello World, hi again"

// replace - all matches (with /g)
str.replace(/hello/gi, 'hi'); // "hi World, hi again"

// replaceAll - simpler for strings
'aaa'.replace(/a/g, 'b'); // "bbb"
'aaa'.replaceAll('a', 'b'); // "bbb"

// Replacement patterns
'test'.replace(/test/, '[$&]'); // "[test]"
'a-b'.replace(/-/, '($`|$\')'); // "a(a|b)b"

// Capture groups
'John Smith'.replace(/(\w+) (\w+)/, '$2, $1');
// "Smith, John"

// Named groups
const date = '2025-12-17';
date.replace(
    /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/,
    '$<month>/$<day>/$<year>'
); // "12/17/2025"

// Replace with function
str.replace(/\b\w+\b/g, match => match.toUpperCase());
// "HELLO WORLD, HELLO AGAIN"

// Function receives: match, p1, p2, ..., offset, string, groups
'1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => {
    return parseInt(p1) * 2;
}); // "2 plus 4 equals 6"

// Advanced replacements
const text = 'Price: $10.50, Total: $20.00';
text.replace(/\$(\d+\.\d+)/g, (match, price) => {
    return `$${(parseFloat(price) * 1.1).toFixed(2)}`;
}); // "Price: $11.55, Total: $22.00"

// split - by regex
'a1b2c3'.split(/\d/); // ["a", "b", "c", ""]
'one,two;three:four'.split(/[,;:]/);
// ["one", "two", "three", "four"]

// split - with limit
'a-b-c-d'.split(/-/, 2); // ["a", "b"]

// split - capture groups included in result
'a1b2c'.split(/(\d)/);
// ["a", "1", "b", "2", "c"]

// split - whitespace
'  hello   world  '.split(/\s+/);
// ["", "hello", "world", ""]

'  hello   world  '.trim().split(/\s+/);
// ["hello", "world"]

// search - find index
str.search(/world/i); // 6
str.search(/cat/); // -1

// search vs indexOf
'hello'.indexOf('l'); // 2 (first 'l')
'hello'.search(/l/); // 2 (first 'l')

// search with patterns
'abc123xyz'.search(/\d/); // 3 (first digit)
'test@email.com'.search(/@/); // 4

// Use cases
// 1. Format phone numbers
function formatPhone(phone) {
    return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3');
}
formatPhone('5551234567'); // "(555) 123-4567"

// 2. Sanitize HTML
function escapeHtml(str) {
    return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
}

// 3. Slugify
function slugify(str) {
    return str
        .toLowerCase()
        .trim()
        .replace(/[^\w\s-]/g, '')
        .replace(/[\s_-]+/g, '-')
        .replace(/^-+|-+$/g, '');
}
slugify('Hello World!'); // "hello-world"

// 4. Extract data
const email = 'Contact: user@example.com';
const match = email.match(/[\w.]+@[\w.]+/);
const emailAddr = match ? match[0] : null;
#x3C;year>'
); // "12/17/2025" // Replace with function str.replace(/\b\w+\b/g, match => match.toUpperCase()); // "HELLO WORLD, HELLO AGAIN" // Function receives: match, p1, p2, ..., offset, string, groups '1 plus 2 equals 3'.replace(/(\d+)/g, (match, p1, offset) => { return parseInt(p1) * 2; }); // "2 plus 4 equals 6" // Advanced replacements const text = 'Price: $10.50, Total: $20.00'; text.replace(/\$(\d+\.\d+)/g, (match, price) => { return `${(parseFloat(price) * 1.1).toFixed(2)}`; }); // "Price: $11.55, Total: $22.00" // split - by regex 'a1b2c3'.split(/\d/); // ["a", "b", "c", ""] 'one,two;three:four'.split(/[,;:]/); // ["one", "two", "three", "four"] // split - with limit 'a-b-c-d'.split(/-/, 2); // ["a", "b"] // split - capture groups included in result 'a1b2c'.split(/(\d)/); // ["a", "1", "b", "2", "c"] // split - whitespace ' hello world '.split(/\s+/); // ["", "hello", "world", ""] ' hello world '.trim().split(/\s+/); // ["hello", "world"] // search - find index str.search(/world/i); // 6 str.search(/cat/); // -1 // search vs indexOf 'hello'.indexOf('l'); // 2 (first 'l') 'hello'.search(/l/); // 2 (first 'l') // search with patterns 'abc123xyz'.search(/\d/); // 3 (first digit) 'test@email.com'.search(/@/); // 4 // Use cases // 1. Format phone numbers function formatPhone(phone) { return phone.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3'); } formatPhone('5551234567'); // "(555) 123-4567" // 2. Sanitize HTML function escapeHtml(str) { return str .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#39;'); } // 3. Slugify function slugify(str) { return str .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); } slugify('Hello World!'); // "hello-world" // 4. Extract data const email = 'Contact: user@example.com'; const match = email.match(/[\w.]+@[\w.]+/); const emailAddr = match ? match[0] : null;
Note: Use replace() with functions for complex transformations. replaceAll() requires /g flag for regex. split() with capture groups includes delimiters in result.

11.5 Character Classes and Quantifiers

Pattern Matches Description Example
. Any character Except newline (unless /s flag); use [\s\S] or /s for any /a.c/.test('abc') // true
\d Digit [0-9] ASCII digits only; use \p{N} with /u for Unicode digits /\d+/.test('123') // true
\D Non-digit [^0-9] Anything except ASCII digits /\D/.test('a') // true
\w Word [A-Za-z0-9_] Alphanumeric + underscore; ASCII only /\w+/.test('hello') // true
\W Non-word [^\w] Anything except word characters /\W/.test('@') // true
\s Whitespace Space, tab, newline, etc.: [ \t\n\r\f\v] /\s+/.test('a b') // true
\S Non-whitespace Anything except whitespace /\S/.test('a') // true
[abc] Character set Any single character in set; can use ranges /[aeiou]/.test('e') // true
[^abc] Negated set Any character NOT in set /[^0-9]/.test('a') // true
[a-z] Range Character range; case-sensitive /[a-z]/.test('m') // true
\b Word boundary Position between \w and \W; zero-width /\bword\b/.test('a word') // true
\B Non-word boundary Opposite of \b /\Bword/.test('sword') // true
^ Start of string/line String start, or line start with /m flag /^hello/.test('hello') // true
$ End of string/line String end, or line end with /m flag /world$/.test('world') // true
Quantifier Matches Description Example
* 0 or more Greedy; matches as many as possible /a*/.test('') // true
+ 1 or more Greedy; at least one required /a+/.test('aaa') // true
? 0 or 1 Optional; makes preceding element optional /colou?r/.test('color') // true
{n} Exactly n Precise count /\d{3}/.test('123') // true
{n,} n or more At least n /\d{2,}/.test('12') // true
{n,m} Between n and m Inclusive range /\d{2,4}/.test('123') // true
*? 0+ (lazy) Non-greedy; matches as few as possible /<.*?>/.exec('<a><b>')[0] // "<a>"
+? 1+ (lazy) Non-greedy; at least one /a+?/.exec('aaa')[0] // "a"
?? 0-1 (lazy) Non-greedy optional /a??/.exec('aa')[0] // ""
{n,m}? n-m (lazy) Non-greedy range /\d{2,4}?/.exec('12345')[0] // "12"

Example: Character classes and quantifiers

// Basic character classes
/\d+/.test('123'); // true (one or more digits)
/\D+/.test('abc'); // true (one or more non-digits)
/\w+/.test('hello_123'); // true (word characters)
/\s+/.test('   '); // true (whitespace)

// Dot (any character except newline)
/.+/.test('hello'); // true
/.+/.test('hello\nworld'); // false (. doesn't match \n)
/.+/s.test('hello\nworld'); // true (dotAll flag)

// Character sets
/[aeiou]/.test('hello'); // true (has vowel)
/[0-9]/.test('5'); // true (digit)
/[a-z]/i.test('A'); // true (case insensitive)
/[a-zA-Z0-9]/.test('x'); // true (alphanumeric)

// Negated sets
/[^0-9]/.test('a'); // true (not a digit)
/[^aeiou]/.test('b'); // true (not a vowel)

// Ranges
/[a-z]/.test('m'); // true
/[A-Z]/.test('M'); // true
/[0-9]/.test('5'); // true
/[a-zA-Z]/.test('X'); // true

// Word boundaries
/\bhello\b/.test('hello world'); // true
/\bhello\b/.test('helloworld'); // false
/\bworld\b/.test('hello world'); // true

// Extract whole words
'hello world'.match(/\b\w+\b/g); // ["hello", "world"]

// Start/End anchors
/^hello/.test('hello world'); // true
/^hello/.test('say hello'); // false
/world$/.test('hello world'); // true
/world$/.test('world peace'); // false

// Full string match
/^hello$/.test('hello'); // true
/^hello$/.test('hello world'); // false

// Greedy quantifiers (default)
'<a><b><c>'.match(/<.+>/)[0]; // "<a><b><c>" (greedy)
'aaaa'.match(/a+/)[0]; // "aaaa" (all)

// Non-greedy (lazy) quantifiers
'<a><b><c>'.match(/<.+?>/g); // ["<a>", "<b>", "<c>"]
'aaaa'.match(/a+?/)[0]; // "a" (minimal)

// Practical examples
// 1. Email (simple)
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test('user@example.com'); // true

// 2. Phone number
/^\d{3}-\d{3}-\d{4}$/.test('555-123-4567'); // true

// 3. Hex color
/^#[0-9A-F]{6}$/i.test('#FF5733'); // true

// 4. URL (simple)
/^https?:\/\/[\w.-]+/.test('https://example.com'); // true

// 5. Username (alphanumeric, 3-16 chars)
/^\w{3,16}$/.test('user_123'); // true

// 6. Password (at least 8 chars, has digit)
/^(?=.*\d).{8,}$/.test('Pass123!'); // true

// 7. Extract hashtags
'#javascript #coding #webdev'.match(/#\w+/g);
// ["#javascript", "#coding", "#webdev"]

// 8. Match HTML tags (non-greedy)
'<div>Hello</div>'.match(/<[^>]+>/g);
// ["<div>", "</div>"]

// 9. Trim whitespace
'  hello  '.replace(/^\s+|\s+$/g, ''); // "hello"

// 10. Multiple spaces to single
'hello    world'.replace(/\s+/g, ' '); // "hello world"

// Combining patterns
const urlRe = /^(https?:\/\/)?([\w.-]+)(:\d+)?(\/[\w.-]*)*\??/;

// Optional elements with ?
/colou?r/.test('color'); // true (US)
/colou?r/.test('colour'); // true (UK)

// Exact count
/^\d{5}$/.test('12345'); // true (5 digits)
/^\d{3}-\d{2}-\d{4}$/.test('123-45-6789'); // true (SSN format)

// Range
/^[a-z]{3,10}$/i.test('hello'); // true (3-10 letters)
Warning: Greedy quantifiers (+, *) can cause catastrophic backtracking with complex patterns. Use lazy quantifiers (+?, *?) or atomic groups when appropriate.

11.6 Lookahead and Lookbehind Assertions

Assertion Syntax Description Example
Positive Lookahead (?=pattern) Assert pattern matches ahead; zero-width (doesn't consume) /\d(?=px)/.exec('5px')[0] // "5"
Negative Lookahead (?!pattern) Assert pattern does NOT match ahead; zero-width /\d(?!px)/.test('5em') // true
Positive Lookbehind ES2018 (?<=pattern) Assert pattern matches behind; zero-width /(?<=\$)\d+/.exec('$50')[0] // "50"
Negative Lookbehind ES2018 (?<!pattern) Assert pattern does NOT match behind; zero-width /(?<!\$)\d+/.test('€50') // true

Example: Lookahead and lookbehind

// Positive lookahead (?=...)
// Match digit followed by "px" but don't include "px"
'5px 10em'.match(/\d+(?=px)/g); // ["5"]

// Password validation (at least one digit)
/^(?=.*\d).{8,}$/.test('Pass123!'); // true
/^(?=.*\d).{8,}$/.test('Password'); // false (no digit)

// Multiple lookaheads (AND conditions)
const strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{8,}$/;
strongPassword.test('Pass123!'); // true
strongPassword.test('password'); // false (no uppercase, digit, special)

// Negative lookahead (?!...)
// Match word NOT followed by "script"
'Java JavaScript'.match(/\bJava(?!Script)\b/); // ["Java"]

// Select numbers not followed by px
'5px 10em 15%'.match(/\d+(?!px)/g); // ["1", "0", "1", "5"]
// (matches last digit of 10, and 15)

// Better: not followed by px unit
'5px 10em 15%'.match(/\d+(?!px\b)/g); // ["10", "15"]

// Positive lookbehind (?<=...)
// Match digits preceded by $
'$50 €30 £40'.match(/(?<=\$)\d+/g); // ["50"]

// Extract value after key
'price=100 quantity=5'.match(/(?<==)\d+/g);
// ["100", "5"]

// Match word after "Mr. "
'Mr. Smith, Ms. Jones'.match(/(?<=Mr\. )\w+/);
// ["Smith"]

// Negative lookbehind (?<!...)
// Match digits NOT preceded by $
'$50 €30 £40'.match(/(?<!\$)\d+/g); // ["30", "40"]

// Word not preceded by "no "
'no cats, dogs, no birds'.match(/(?<!no )\b\w+s\b/g);
// ["dogs"] (cats and birds are preceded by "no ")

// Complex combinations
// 1. Price in $ (with decimal)
const priceRe = /(?<=\$)\d+(?:\.\d{2})?(?=\s|$)/g;
'Items: $10.50 and $25.00'.match(priceRe);
// ["10.50", "25.00"]

// 2. Password: 8+ chars, has lowercase, uppercase, digit
const passRe = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
passRe.test('Secure123'); // true
passRe.test('secure123'); // false (no uppercase)
passRe.test('SECURE123'); // false (no lowercase)
passRe.test('SecurePass'); // false (no digit)

// 3. Extract hashtags not in code blocks
// (not preceded by backtick)
const text = 'Use #javascript but not `#code`';
text.match(/(?<!`[^`]*)#\w+(?![^`]*`)/g); // ["#javascript"]

// 4. Numbers with thousands separator
const numRe = /\b(?<!\d,)\d{1,3}(?:,\d{3})*(?!\d)/g;
'1,000 and 10,000 and 999'.match(numRe);
// ["1,000", "10,000", "999"]

// 5. Email username (before @)
const emailRe = /\w+(?=@)/;
'user@example.com'.match(emailRe)[0]; // "user"

// 6. URL domain (after //)
const domainRe = /(?<=:\/\/)[^\/]+/;
'https://example.com/path'.match(domainRe)[0];
// "example.com"

// 7. Word boundaries with punctuation
// Word not followed by alphanumeric
'hello, world!'.match(/\w+(?!\w)/g);
// ["hello", "world"]

// 8. CSS selector (class not in comments)
const css = '.btn { } /* .comment */';
css.match(/(?<!\/\*.*)\.[a-z-]+(?!.*\*\/)/g);
// Tricky - better parsed differently

// Zero-width assertion chaining
// Number between 1-100
/^(?=\d{1,3}$)(?!0$)([1-9]\d?|100)$/.test('50'); // true
/^(?=\d{1,3}$)(?!0$)([1-9]\d?|100)$/.test('0'); // false
/^(?=\d{1,3}$)(?!0$)([1-9]\d?|100)$/.test('101'); // false

// Assertions don't consume characters
const re = /(?=\d{3})\d{2}/;
re.exec('12345'); // ["12"] (matched at position 0)
// Lookahead checked 3 digits, but only consumed 2

// Use in replace
// Insert comma in numbers
'1234567'.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
// "1,234,567"
Note: Assertions are zero-width (don't consume characters). Lookaheads work everywhere; lookbehinds require ES2018+. Use for complex validations and extractions.

11.7 Named Capture Groups and Replacement

Feature Syntax Description Example
Named Group (?<name>pattern) Capture group with identifier; access via .groups property /(?<year>\d{4})/
Group Reference \k<name> Backreference to named group within pattern /(?<tag>\w+)</\k<tag>/
Replacement $<name> Reference named group in replacement string .replace(re, '$<name>')
Non-capturing Group (?:pattern) Group without capturing; for grouping only; better performance /(?:abc)+/
Numbered Group (pattern) Traditional capture group; access via index /(\d+)/
Backreference \1 \2 ... Reference numbered group within pattern /(\w)\1/ (double char)

Example: Named groups and backreferences

// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;month>/
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;day>/
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;year>'
);
// "12/17/2025" // Function replacement with named groups date.replace(dateRe, (match, y, m, d, offset, str, groups) => { return `${groups.month}/${groups.day}/${groups.year}`; }); // Backreference to named group const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/; htmlTag.test('<div>content</div>'); // true htmlTag.test('<div>content</span>'); // false (tag mismatch) // Find duplicate words const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i; duplicateRe.test('the the'); // true duplicateRe.test('the cat'); // false // Email parsing const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/; const email = 'user@example.com'.match(emailRe); email.groups.user; // "user" email.groups.domain; // "example" email.groups.tld; // "com" // URL parsing const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/; const url = 'https://example.com/path/to/page'.match(urlRe); url.groups.protocol; // "https" url.groups.domain; // "example.com" url.groups.path; // "/path/to/page" // Phone number formatting const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/; '5551234567'.replace(phoneRe, '(
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;area>)
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;exchange>-
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;number>'
);
// "(555) 123-4567" // Non-capturing groups (?:...) // Group without capturing (performance) /(?:abc)+/.test('abcabc'); // true 'abcabc'.match(/(?:abc)+/)[0]; // "abcabc" 'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups) // Compare to capturing 'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture) // Use for alternation /(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group) // Numbered backreferences // Find doubled characters /(\w)\1/.test('hello'); // true (ll) 'hello'.match(/(\w)\1/)[0]; // "ll" // Find repeated words /(\b\w+\b)\s+\1/.test('the the'); // true // Match balanced quotes /'([^']*)'/.exec("'hello'")[1]; // "hello" // HTML attributes /(\w+)="([^"]*)"/.exec('class="btn"'); // ["class=\"btn\"", "class", "btn"] // Combining named and numbered groups const re = /(?<name>\w+):(\d+)/; const m = 'user:123'.match(re); m.groups.name; // "user" m[2]; // "123" (numbered after named) // Replace with function (named groups) 'John Smith, Jane Doe'.replace( /(?<first>\w+) (?<last>\w+)/g, (match, first, last, offset, string, groups) => { return `${groups.last}, ${groups.first}`; } ); // "Smith, John, Doe, Jane" // Case conversion in replacement 'user_name'.replace( /_(?<char>\w)/g, (match, char, offset, string, groups) => { return groups.char.toUpperCase(); } ); // "userName" // Swap parts 'firstName lastName'.replace( /(?<first>\w+) (?<last>\w+)/, '
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;last>
// Named capture groups
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = '2025-12-17'.match(dateRe);

match[1]; // "2025" (by index)
match.groups.year; // "2025" (by name)
match.groups.month; // "12"
match.groups.day; // "17"

// Destructuring with named groups
const {groups: {year, month, day}} = '2025-12-17'.match(dateRe);
console.log(year, month, day); // "2025" "12" "17"

// Replace with named groups
const date = '2025-12-17';
date.replace(dateRe, '$<month>/$<day>/$<year>');
// "12/17/2025"

// Function replacement with named groups
date.replace(dateRe, (match, y, m, d, offset, str, groups) => {
    return `${groups.month}/${groups.day}/${groups.year}`;
});

// Backreference to named group
const htmlTag = /<(?<tag>\w+)>.*?<\/\k<tag>>/;
htmlTag.test('<div>content</div>'); // true
htmlTag.test('<div>content</span>'); // false (tag mismatch)

// Find duplicate words
const duplicateRe = /\b(?<word>\w+)\s+\k<word>\b/i;
duplicateRe.test('the the'); // true
duplicateRe.test('the cat'); // false

// Email parsing
const emailRe = /^(?<user>[^\s@]+)@(?<domain>[^\s@]+)\.(?<tld>\w+)$/;
const email = 'user@example.com'.match(emailRe);
email.groups.user; // "user"
email.groups.domain; // "example"
email.groups.tld; // "com"

// URL parsing
const urlRe = /^(?<protocol>https?):\/\/(?<domain>[^\/]+)(?<path>\/.*)?$/;
const url = 'https://example.com/path/to/page'.match(urlRe);
url.groups.protocol; // "https"
url.groups.domain; // "example.com"
url.groups.path; // "/path/to/page"

// Phone number formatting
const phoneRe = /(?<area>\d{3})(?<exchange>\d{3})(?<number>\d{4})/;
'5551234567'.replace(phoneRe, '($<area>) $<exchange>-$<number>');
// "(555) 123-4567"

// Non-capturing groups (?:...)
// Group without capturing (performance)
/(?:abc)+/.test('abcabc'); // true
'abcabc'.match(/(?:abc)+/)[0]; // "abcabc"
'abcabc'.match(/(?:abc)+/).length; // 1 (no capture groups)

// Compare to capturing
'abcabc'.match(/(abc)+/); // ["abcabc", "abc"] (has capture)

// Use for alternation
/(?:cat|dog)s?/.test('cats'); // true (s is optional for whole group)

// Numbered backreferences
// Find doubled characters
/(\w)\1/.test('hello'); // true (ll)
'hello'.match(/(\w)\1/)[0]; // "ll"

// Find repeated words
/(\b\w+\b)\s+\1/.test('the the'); // true

// Match balanced quotes
/'([^']*)'/.exec("'hello'")[1]; // "hello"

// HTML attributes
/(\w+)="([^"]*)"/.exec('class="btn"');
// ["class=\"btn\"", "class", "btn"]

// Combining named and numbered groups
const re = /(?<name>\w+):(\d+)/;
const m = 'user:123'.match(re);
m.groups.name; // "user"
m[2]; // "123" (numbered after named)

// Replace with function (named groups)
'John Smith, Jane Doe'.replace(
    /(?<first>\w+) (?<last>\w+)/g,
    (match, first, last, offset, string, groups) => {
        return `${groups.last}, ${groups.first}`;
    }
);
// "Smith, John, Doe, Jane"

// Case conversion in replacement
'user_name'.replace(
    /_(?<char>\w)/g,
    (match, char, offset, string, groups) => {
        return groups.char.toUpperCase();
    }
);
// "userName"

// Swap parts
'firstName lastName'.replace(
    /(?<first>\w+) (?<last>\w+)/,
    '$<last> $<first>'
);
// "lastName firstName"

// Optional named groups
const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/;
'example.com'.match(optionalRe).groups;
// {protocol: undefined, domain: "example.com"}

'https://example.com'.match(optionalRe).groups;
// {protocol: "https://", domain: "example.com"}

// matchAll with named groups
const text = 'user:123 admin:456';
const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g);

for (const match of matches) {
    console.log(match.groups.role, match.groups.id);
    // "user" "123"
    // "admin" "456"
}
#x3C;first>'
); // "lastName firstName" // Optional named groups const optionalRe = /(?<protocol>https?:\/\/)?(?<domain>[^\/]+)/; 'example.com'.match(optionalRe).groups; // {protocol: undefined, domain: "example.com"} 'https://example.com'.match(optionalRe).groups; // {protocol: "https://", domain: "example.com"} // matchAll with named groups const text = 'user:123 admin:456'; const matches = text.matchAll(/(?<role>\w+):(?<id>\d+)/g); for (const match of matches) { console.log(match.groups.role, match.groups.id); // "user" "123" // "admin" "456" }
Note: Named groups improve readability and maintainability. Use (?:...) for grouping without capturing (better performance). Backreferences \k<name> or \1 match same text as group.

Section 11 Summary

  • Creation: Literal /pattern/flags for static (compile-time); constructor new RegExp(str, flags) for dynamic (escape backslashes: '\\d')
  • Flags: g (global), i (case-insensitive), m (multiline ^$), s (dotAll), u (Unicode), y (sticky), d (indices); g makes regex stateful
  • Methods: test() boolean; exec() details + loop with g; match() simple/all; matchAll() iterator with details (requires g)
  • String methods: replace/replaceAll modify; split divide (includes captures); search find index; use $1 $<name> in replacement
  • Patterns: \d \w \s classes; . [abc] [^abc] [a-z] sets; ^ $ \b anchors; + * ? {n,m} quantifiers; add ? for lazy
  • Assertions: (?=...) (?!...) lookahead; (?<=...) (?<!...) lookbehind; zero-width (don't consume); chain for complex validation
  • Groups: (?<name>...) named capture; (?:...) non-capturing; \k<name> \1 backreferences; $<name> $1 in replacements

12. Numbers, Math, and BigInt Operations

12.1 Number Types and Numeric Literals

Type/Literal Syntax Description Example
Decimal Integer 123 Base 10 integer; most common form 42, -17, 0
Decimal Float 1.23 Floating point number; IEEE 754 double precision (64-bit) 3.14, -0.5, 0.1
Scientific Notation 1e10 Exponent form: number × 10^exponent 1e6 (1000000), 5e-3 (0.005)
Binary ES2015 0b101 Base 2; prefix 0b or 0B; digits 0-1 0b1010 (10), 0b1111 (15)
Octal ES2015 0o17 Base 8; prefix 0o or 0O; digits 0-7 0o10 (8), 0o77 (63)
Hexadecimal 0xFF Base 16; prefix 0x or 0X; digits 0-9, A-F 0xFF (255), 0x1A (26)
Numeric Separators ES2021 1_000_000 Underscores for readability; ignored by engine 1_000, 0xFF_FF, 0b1010_1010
Special Values Infinity, -Infinity, NaN Special numeric values; NaN ≠ NaN 1/0, -1/0, 0/0
Signed Zero +0, -0 IEEE 754 has both; usually treated as equal -0 === +0 // true

Example: Numeric literal forms

// Decimal integers
const dec = 42;
const negative = -17;
const zero = 0;

// Decimal floats
const pi = 3.14159;
const small = 0.0001;
const leading = .5; // 0.5 (avoid - use 0.5)
const trailing = 5.; // 5 (avoid - use 5.0 or 5)

// Scientific notation
const million = 1e6; // 1000000
const billion = 1e9; // 1000000000
const tiny = 5e-3; // 0.005
const planck = 6.62607015e-34;

// Binary (base 2)
const binary = 0b1010; // 10
const flags = 0b11111111; // 255
const mask = 0b1100; // 12

// Octal (base 8)
const octal = 0o17; // 15
const permissions = 0o755; // 493 (rwxr-xr-x)

// Hexadecimal (base 16)
const hex = 0xFF; // 255
const color = 0xFF5733; // 16734003
const byte = 0xAB; // 171

// Mixed case OK
const HEX = 0XFF; // 255
const BIN = 0B1010; // 10

// Numeric separators (readability)
const oneMillion = 1_000_000;
const billion = 1_000_000_000;
const hexColor = 0xFF_55_33;
const binary8bit = 0b1111_0000;
const creditCard = 1234_5678_9012_3456;

// Separators anywhere except start/end
const amount = 123_456.789_012;

// Special values
const inf = Infinity;
const negInf = -Infinity;
const notANumber = NaN;

1 / 0; // Infinity
-1 / 0; // -Infinity
0 / 0; // NaN
Math.sqrt(-1); // NaN

// NaN is unique
NaN === NaN; // false ❌
isNaN(NaN); // true ✓
Number.isNaN(NaN); // true ✓ (preferred)

// Signed zeros
+0 === -0; // true
Object.is(+0, -0); // false (can distinguish)

1 / +0; // Infinity
1 / -0; // -Infinity

// Type checking
typeof 42; // "number"
typeof 3.14; // "number"
typeof NaN; // "number" (yes, really!)
typeof Infinity; // "number"

// Number range
Number.MAX_VALUE; // ~1.79e308
Number.MIN_VALUE; // ~5e-324 (smallest positive)
Number.MAX_SAFE_INTEGER; // 2^53 - 1 (9007199254740991)
Number.MIN_SAFE_INTEGER; // -(2^53 - 1)
Note: JavaScript numbers are 64-bit IEEE 754 floats. Use numeric separators for readability. Prefer Number.isNaN() over isNaN(). Safe integer range: ±2^53-1.

12.2 Number Methods and Constants

Method/Property Syntax Description Returns
isFinite() Number.isFinite(value) Check if finite number (not Infinity/NaN); no coercion Boolean
isInteger() Number.isInteger(value) Check if integer (no fractional part) Boolean
isNaN() Number.isNaN(value) Check if NaN; no coercion (safer than global isNaN()) Boolean
isSafeInteger() Number.isSafeInteger(value) Check if integer in safe range [-(2^53-1), 2^53-1] Boolean
toExponential() num.toExponential(digits) Scientific notation string; optional decimal digits String
toFixed() num.toFixed(digits) Fixed decimal places; rounds; 0-20 digits String
toPrecision() num.toPrecision(precision) Specified total significant digits; 1-21 String
toString() num.toString(radix) String representation; optional base (2-36) String
valueOf() num.valueOf() Primitive value; rarely needed Number
toLocaleString() num.toLocaleString(locale, options) Locale-aware formatting; currency, percentages, units String
Constant Value Description Use Case
EPSILON ~2.22e-16 Smallest difference between 1 and next representable number Float comparison tolerance
MAX_VALUE ~1.79e308 Largest positive number Range checking
MIN_VALUE ~5e-324 Smallest positive number (near zero) Precision limits
MAX_SAFE_INTEGER 9007199254740991 2^53 - 1; largest safe integer Integer arithmetic bounds
MIN_SAFE_INTEGER -9007199254740991 -(2^53 - 1); smallest safe integer Integer arithmetic bounds
POSITIVE_INFINITY Infinity Positive infinity; same as global Infinity Overflow detection
NEGATIVE_INFINITY -Infinity Negative infinity Underflow detection
NaN NaN Not-a-Number; same as global NaN Invalid operations

Example: Number methods and constants

// Type checking methods
Number.isFinite(42); // true
Number.isFinite(Infinity); // false
Number.isFinite(NaN); // false
Number.isFinite('42'); // false (no coercion!)

// Compare: global isFinite() coerces
isFinite('42'); // true (coerces to 42)
Number.isFinite('42'); // false (no coercion)

Number.isInteger(42); // true
Number.isInteger(42.0); // true (same as 42)
Number.isInteger(42.5); // false
Number.isInteger('42'); // false

Number.isNaN(NaN); // true
Number.isNaN(42); // false
Number.isNaN('hello'); // false (no coercion!)

// Compare: global isNaN() coerces
isNaN('hello'); // true (coerces to NaN)
Number.isNaN('hello'); // false (no coercion)

Number.isSafeInteger(42); // true
Number.isSafeInteger(9007199254740991); // true (MAX_SAFE_INTEGER)
Number.isSafeInteger(9007199254740992); // false (too large)
Number.isSafeInteger(42.5); // false (not integer)

// Formatting methods
const num = 123.456;

num.toFixed(2); // "123.46" (rounds)
num.toFixed(0); // "123"
num.toFixed(5); // "123.45600"

num.toExponential(2); // "1.23e+2"
(1234.5).toExponential(); // "1.2345e+3"

num.toPrecision(4); // "123.5"
num.toPrecision(2); // "1.2e+2" (switches to exponential)
num.toPrecision(10); // "123.4560000"

// Base conversion
(255).toString(); // "255"
(255).toString(16); // "ff" (hex)
(255).toString(2); // "11111111" (binary)
(255).toString(8); // "377" (octal)
(255).toString(36); // "73" (max base)

// Locale formatting
const price = 1234.56;
price.toLocaleString('en-US', {
    style: 'currency',
    currency: 'USD'
}); // "$1,234.56"

price.toLocaleString('de-DE', {
    style: 'currency',
    currency: 'EUR'
}); // "1.234,56 €"

const percent = 0.755;
percent.toLocaleString('en-US', {
    style: 'percent',
    minimumFractionDigits: 1
}); // "75.5%"

// Constants
Number.EPSILON; // 2.220446049250313e-16
Number.MAX_VALUE; // 1.7976931348623157e+308
Number.MIN_VALUE; // 5e-324
Number.MAX_SAFE_INTEGER; // 9007199254740991
Number.MIN_SAFE_INTEGER; // -9007199254740991

// Safe integer range check
function isSafeArithmetic(a, b) {
    const result = a + b;
    return Number.isSafeInteger(a) &&
           Number.isSafeInteger(b) &&
           Number.isSafeInteger(result);
}

// Floating point comparison with EPSILON
function almostEqual(a, b) {
    return Math.abs(a - b) < Number.EPSILON;
}

almostEqual(0.1 + 0.2, 0.3); // true
0.1 + 0.2 === 0.3; // false ❌

// Format large numbers
const large = 1234567890;
large.toLocaleString('en-US'); // "1,234,567,890"

// Scientific notation
(0.0000001).toExponential(); // "1e-7"
(10000000).toExponential(); // "1e+7"

// Precision control
const value = 123.456789;
value.toFixed(2); // "123.46"
value.toPrecision(5); // "123.46"
parseFloat(value.toFixed(2)); // 123.46 (back to number)
Warning: toFixed(), toExponential(), toPrecision() return strings. Use Number.is*() over global versions (no coercion). Safe integers: ±(2^53-1).

12.3 Math Object Methods and Constants

Method Description Example Result
abs(x) Absolute value Math.abs(-5) 5
ceil(x) Round up to integer Math.ceil(4.1) 5
floor(x) Round down to integer Math.floor(4.9) 4
round(x) Round to nearest integer Math.round(4.5) 5
trunc(x) Remove fractional part Math.trunc(4.9) 4
sign(x) Sign: -1, 0, or 1 Math.sign(-5) -1
min(...args) Smallest of arguments Math.min(2, 5, 1) 1
max(...args) Largest of arguments Math.max(2, 5, 1) 5
pow(x, y) x to power y; use ** operator Math.pow(2, 3) 8
sqrt(x) Square root Math.sqrt(16) 4
cbrt(x) Cube root Math.cbrt(27) 3
exp(x) e^x (Euler's number) Math.exp(1) ~2.718
log(x) Natural logarithm (base e) Math.log(Math.E) 1
log10(x) Base 10 logarithm Math.log10(100) 2
log2(x) Base 2 logarithm Math.log2(8) 3
random() Random [0, 1); pseudo-random Math.random() 0.123...
Trigonometry Description Hyperbolic Description
sin(x) Sine (radians) sinh(x) Hyperbolic sine
cos(x) Cosine (radians) cosh(x) Hyperbolic cosine
tan(x) Tangent (radians) tanh(x) Hyperbolic tangent
asin(x) Arcsine asinh(x) Inverse hyperbolic sine
acos(x) Arccosine acosh(x) Inverse hyperbolic cosine
atan(x) Arctangent atanh(x) Inverse hyperbolic tangent
atan2(y, x) Angle from origin to (x,y) hypot(...args) √(x²+y²+...) - Euclidean norm
Constant Value Description Formula
E ~2.718 Euler's number Base of natural logarithms
PI ~3.14159 Pi (π) Circle circumference / diameter
LN2 ~0.693 ln(2) Natural log of 2
LN10 ~2.303 ln(10) Natural log of 10
LOG2E ~1.443 log₂(e) Base 2 log of e
LOG10E ~0.434 log₁₀(e) Base 10 log of e
SQRT1_2 ~0.707 √(1/2) Square root of 0.5
SQRT2 ~1.414 √2 Square root of 2

Example: Math operations

// Rounding
Math.round(4.5); // 5
Math.round(4.4); // 4
Math.round(-4.5); // -4 (rounds to nearest even)

Math.ceil(4.1); // 5 (always up)
Math.floor(4.9); // 4 (always down)
Math.trunc(4.9); // 4 (remove decimal)

// Difference: floor vs trunc with negatives
Math.floor(-4.1); // -5 (down = more negative)
Math.trunc(-4.1); // -4 (just removes decimal)

// Absolute value and sign
Math.abs(-5); // 5
Math.abs(5); // 5

Math.sign(-5); // -1
Math.sign(0); // 0
Math.sign(5); // 1

// Min/Max
Math.min(5, 2, 8, 1); // 1
Math.max(5, 2, 8, 1); // 8

// With arrays (spread)
const nums = [5, 2, 8, 1];
Math.min(...nums); // 1
Math.max(...nums); // 8

// Power and roots
Math.pow(2, 3); // 8
2 ** 3; // 8 (exponentiation operator - preferred)

Math.sqrt(16); // 4
Math.sqrt(2); // 1.4142135623730951
Math.cbrt(27); // 3
Math.cbrt(8); // 2

// Logarithms
Math.log(Math.E); // 1 (natural log)
Math.log10(100); // 2
Math.log2(8); // 3

// Exponential
Math.exp(1); // 2.718281828459045 (e^1)
Math.exp(2); // 7.389... (e^2)

// Random numbers
Math.random(); // 0 <= x < 1

// Random integer in range [min, max]
function randomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
randomInt(1, 6); // dice roll (1-6)

// Random from array
function randomItem(arr) {
    return arr[Math.floor(Math.random() * arr.length)];
}

// Trigonometry (radians!)
Math.sin(Math.PI / 2); // 1
Math.cos(0); // 1
Math.tan(Math.PI / 4); // ~1

// Degrees to radians
function toRadians(degrees) {
    return degrees * Math.PI / 180;
}

// Radians to degrees
function toDegrees(radians) {
    return radians * 180 / Math.PI;
}

Math.sin(toRadians(30)); // 0.5

// atan2 - angle from origin to point
Math.atan2(1, 1); // π/4 (45 degrees)
Math.atan2(1, 0); // π/2 (90 degrees)

// Hypotenuse (Pythagorean)
Math.hypot(3, 4); // 5 (3² + 4² = 5²)
Math.hypot(1, 1); // √2 (1.414...)

// Distance between 2D points
function distance(x1, y1, x2, y2) {
    return Math.hypot(x2 - x1, y2 - y1);
}

// Constants
Math.PI; // 3.141592653589793
Math.E; // 2.718281828459045

// Circle calculations
const radius = 5;
const circumference = 2 * Math.PI * radius; // 31.4...
const area = Math.PI * radius ** 2; // 78.5...

// Practical uses
// 1. Clamp value to range
function clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
}

// 2. Linear interpolation
function lerp(start, end, t) {
    return start + (end - start) * t;
}

// 3. Map value from one range to another
function map(value, inMin, inMax, outMin, outMax) {
    return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}

// 4. Round to decimal places
function roundTo(num, places) {
    const factor = 10 ** places;
    return Math.round(num * factor) / factor;
}
roundTo(3.14159, 2); // 3.14
Note: Math methods work with radians, not degrees. Use ** operator over Math.pow(). Math.random() is pseudo-random (not cryptographically secure - use crypto.getRandomValues() for security).

12.4 Number Precision and Floating Point Issues

Issue Cause Example Solution
Binary Representation IEEE 754 binary cannot represent some decimals exactly 0.1 + 0.2 !== 0.3 Use epsilon comparison or integer math
Rounding Errors Accumulated precision loss in calculations 0.1 + 0.2 = 0.30000000000000004 Round to fixed decimals, use libraries (decimal.js)
Large Integer Loss Integers > 2^53-1 lose precision 9007199254740992 + 1 === 9007199254740992 Use BigInt for large integers
Division by Zero Returns Infinity (not error) 1 / 0 = Infinity Check denominator before division
NaN Propagation Any operation with NaN yields NaN NaN + 5 = NaN Validate inputs, use Number.isNaN()
Epsilon Comparison Float equality unreliable 0.1 + 0.2 === 0.3 // false Math.abs(a - b) < Number.EPSILON

Example: Floating point precision handling

// Classic floating point problem
0.1 + 0.2; // 0.30000000000000004 ❌
0.1 + 0.2 === 0.3; // false

// Epsilon comparison for floats
function almostEqual(a, b, epsilon = Number.EPSILON) {
    return Math.abs(a - b) < epsilon;
}

almostEqual(0.1 + 0.2, 0.3); // true ✓

// For larger numbers, scale epsilon
function almostEqualScaled(a, b) {
    const epsilon = Math.max(Math.abs(a), Math.abs(b)) * Number.EPSILON;
    return Math.abs(a - b) <= epsilon;
}

// Round to fixed decimals
function roundTo(num, decimals) {
    const factor = 10 ** decimals;
    return Math.round(num * factor) / factor;
}

roundTo(0.1 + 0.2, 1); // 0.3 ✓
roundTo(1.005, 2); // 1.01 (beware banker's rounding)

// Integer math for currency (cents)
// ❌ Don't do this:
const price = 0.1 + 0.2; // 0.30000000000000004

// ✓ Do this:
const priceInCents = 10 + 20; // 30
const price = priceInCents / 100; // 0.3

// Financial calculations
function addCurrency(a, b) {
    return (a * 100 + b * 100) / 100;
}
addCurrency(0.1, 0.2); // 0.3 ✓

// Large integer precision loss
const maxSafe = Number.MAX_SAFE_INTEGER; // 9007199254740991

maxSafe + 1; // 9007199254740992 ✓
maxSafe + 2; // 9007199254740992 ❌ (lost precision!)

// Check safe integer range
Number.isSafeInteger(maxSafe); // true
Number.isSafeInteger(maxSafe + 1); // true
Number.isSafeInteger(maxSafe + 2); // false

// Division by zero
1 / 0; // Infinity (not error!)
-1 / 0; // -Infinity
0 / 0; // NaN

// Check for valid result
function safeDivide(a, b) {
    if (b === 0) return null;
    return a / b;
}

// NaN propagation
const result = NaN + 5; // NaN
Math.sqrt(-1) * 2; // NaN

// Always check NaN
function safeCalculation(a, b) {
    const result = a / b;
    if (Number.isNaN(result)) {
        throw new Error('Invalid calculation');
    }
    return result;
}

// Rounding modes
Math.round(2.5); // 3
Math.round(-2.5); // -2 (rounds towards zero for negatives)

Math.floor(2.9); // 2
Math.floor(-2.1); // -3 (towards negative infinity)

Math.ceil(2.1); // 3
Math.ceil(-2.9); // -2 (towards positive infinity)

Math.trunc(2.9); // 2
Math.trunc(-2.9); // -2 (just removes decimal)

// Banker's rounding (round half to even)
function bankersRound(num, decimals = 0) {
    const factor = 10 ** decimals;
    const n = num * factor;
    const floor = Math.floor(n);
    const diff = n - floor;
    
    if (diff === 0.5) {
        return floor % 2 === 0 ? floor / factor : Math.ceil(n) / factor;
    }
    return Math.round(n) / factor;
}

// Comparing with tolerance
function withinTolerance(a, b, tolerance = 0.001) {
    return Math.abs(a - b) <= tolerance;
}

withinTolerance(0.1 + 0.2, 0.3, 0.001); // true

// Use decimal libraries for precision
// npm install decimal.js
// const Decimal = require('decimal.js');
// new Decimal(0.1).plus(0.2).equals(0.3); // true
Warning: Never use === for float comparison. Use epsilon tolerance. For currency, work in cents (integers). Large integers require BigInt. IEEE 754 has inherent precision limits.

12.5 BigInt Creation and Arithmetic Operations

Feature Syntax Description Example
Literal ES2020 123n Arbitrary precision integers; append n suffix 9007199254740993n
Constructor BigInt(value) Convert number/string to BigInt BigInt("999")
Addition a + b Both operands must be BigInt 10n + 20n = 30n
Subtraction a - b Both operands must be BigInt 50n - 20n = 30n
Multiplication a * b Both operands must be BigInt 10n * 20n = 200n
Division a / b Integer division (rounds towards zero) 10n / 3n = 3n
Remainder a % b Modulo operation 10n % 3n = 1n
Exponentiation a ** b Power operation; exponent must be non-negative 2n ** 100n
Comparison < > <= >= == Works across BigInt and Number (loose ==) 10n < 20 // true
Bitwise & | ^ ~ << >> All bitwise operators supported 5n & 3n = 1n
Type Check typeof value Returns "bigint" typeof 10n === "bigint"

Example: BigInt usage

// Creation
const big1 = 9007199254740993n; // literal with n suffix
const big2 = BigInt(9007199254740993); // constructor
const big3 = BigInt("9007199254740993"); // from string (for very large)

// Cannot mix BigInt and Number
10n + 20; // ❌ TypeError: Cannot mix BigInt and other types
10n + 20n; // ✓ 30n

// Convert between types
const num = 42;
const big = BigInt(num); // Number to BigInt
const backToNum = Number(big); // BigInt to Number (may lose precision!)

// Arithmetic operations
100n + 50n; // 150n
100n - 50n; // 50n
10n * 20n; // 200n

// Division truncates (integer division)
10n / 3n; // 3n (not 3.333...)
-10n / 3n; // -3n (rounds towards zero)

10n % 3n; // 1n (remainder)

// Exponentiation
2n ** 100n; // huge number
2n ** -1n; // ❌ RangeError: exponent must be non-negative

// Comparison
10n < 20n; // true
10n === 10n; // true
10n === 10; // false (strict equality - different types)
10n == 10; // true (loose equality - coerces)

// Mixed comparisons (non-equality)
10n < 20; // true ✓
10n > 5; // true ✓
20n <= 20; // true ✓

// Bitwise operations
const a = 5n; // 0101
const b = 3n; // 0011

a & b; // 1n (0001)
a | b; // 7n (0111)
a ^ b; // 6n (0110)
~a; // -6n (bitwise NOT)

5n << 2n; // 20n (shift left)
20n >> 2n; // 5n (shift right)

// No unsigned right shift (>>>) for BigInt

// Large factorials (no overflow!)
function factorial(n) {
    let result = 1n;
    for (let i = 2n; i <= n; i++) {
        result *= i;
    }
    return result;
}

factorial(100n); // huge number (no precision loss!)

// Compare with regular number (loses precision)
function factorialNumber(n) {
    let result = 1;
    for (let i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}
factorialNumber(100); // Infinity (overflow!)

// Large integer operations
const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
maxSafe + 1n; // works correctly!
maxSafe + 2n; // still correct!

// Crypto/hashing with BigInt
const large = 2n ** 256n; // 256-bit number

// No fractional BigInt
const frac = 1.5n; // ❌ SyntaxError
const fromFloat = BigInt(1.5); // ❌ RangeError

// Must be integer
const safe = BigInt(Math.floor(1.5)); // ✓ 1n

// Type checking
typeof 10n; // "bigint"
10n instanceof BigInt; // ❌ false (primitives not instances)

// Conversion gotchas
Number(2n ** 100n); // Infinity (too large for Number!)

// Safe conversion check
function safeToNumber(bigint) {
    const num = Number(bigint);
    if (!Number.isSafeInteger(num)) {
        throw new Error('BigInt too large for safe Number conversion');
    }
    return num;
}

// JSON doesn't support BigInt
JSON.stringify({value: 10n}); // ❌ TypeError

// Custom JSON serialization
JSON.stringify({value: 10n}, (key, value) =>
    typeof value === 'bigint' ? value.toString() : value
); // '{"value":"10"}'

// Math methods don't work with BigInt
Math.max(10n, 20n); // ❌ TypeError
Math.sqrt(100n); // ❌ TypeError

// Must use BigInt operations
function maxBigInt(...args) {
    return args.reduce((max, val) => val > max ? val : max);
}
maxBigInt(10n, 50n, 30n); // 50n

// Practical: microsecond timestamps
const timestamp = BigInt(Date.now()) * 1000n; // microseconds

// Practical: precise money calculations
const cents = 12345n; // $123.45
const dollars = Number(cents) / 100; // 123.45
Warning: Cannot mix BigInt with Number in operations. Division truncates. No fractional BigInt. JSON doesn't support BigInt. Math methods incompatible with BigInt.

12.6 Number Parsing and Validation

Function Syntax Description Returns
parseInt() parseInt(string, radix) Parse string to integer; optional base (2-36); stops at non-digit Integer or NaN
parseFloat() parseFloat(string) Parse string to float; stops at invalid character Float or NaN
Number() Number(value) Convert to number; strict (entire string must be valid) Number or NaN
Unary Plus +value Shorthand for Number(); strict conversion Number or NaN
isNaN() isNaN(value) Global; coerces to number first (avoid) Boolean
Number.isNaN() Number.isNaN(value) Strict; true only if value is NaN (preferred) Boolean
isFinite() isFinite(value) Global; coerces to number first Boolean
Number.isFinite() Number.isFinite(value) Strict; no coercion (preferred) Boolean
Number.isInteger() Number.isInteger(value) Check if integer (no fractional part) Boolean
Number.isSafeInteger() Number.isSafeInteger(value) Check if integer in safe range [-(2^53-1), 2^53-1] Boolean

Example: Parsing and validation

// parseInt - parses until invalid character
parseInt('123'); // 123
parseInt('123.45'); // 123 (stops at decimal)
parseInt('123px'); // 123 (stops at 'p')
parseInt('abc'); // NaN (no valid digits)
parseInt('  42'); // 42 (trims whitespace)

// Always specify radix!
parseInt('08'); // 8 (may be octal in old browsers without radix!)
parseInt('08', 10); // 8 ✓ (always use radix)

// Different bases
parseInt('FF', 16); // 255 (hexadecimal)
parseInt('1010', 2); // 10 (binary)
parseInt('77', 8); // 63 (octal)
parseInt('Z', 36); // 35 (max base)

// Radix validation
parseInt('123', 10); // 123
parseInt('123', 2); // 1 (stops at '2', invalid for binary)

// parseFloat - decimal parsing
parseFloat('123.45'); // 123.45
parseFloat('123.45.67'); // 123.45 (stops at second '.')
parseFloat('123.45px'); // 123.45 (stops at 'p')
parseFloat('.5'); // 0.5
parseFloat('5.'); // 5

// Scientific notation
parseFloat('1.23e10'); // 12300000000
parseFloat('1e-5'); // 0.00001

// Number() - strict conversion
Number('123'); // 123
Number('123.45'); // 123.45
Number('123px'); // NaN ❌ (entire string must be valid)
Number('  42  '); // 42 (trims whitespace)
Number(''); // 0 (empty string is 0!)
Number('   '); // 0 (whitespace only is 0)

// Unary plus (shorthand)
+'123'; // 123
+'123.45'; // 123.45
+'123px'; // NaN

// Comparison: parseInt vs parseFloat vs Number
const str = '123.45px';
parseInt(str); // 123 (stops at decimal)
parseFloat(str); // 123.45 (stops at 'p')
Number(str); // NaN (invalid)

// Type coercion gotchas
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
Number([]); // 0 (empty array)
Number([5]); // 5 (single element)
Number([1, 2]); // NaN (multiple elements)

// Validation: isNaN vs Number.isNaN
isNaN('hello'); // true (coerces 'hello' to NaN)
Number.isNaN('hello'); // false (string is not NaN)
Number.isNaN(NaN); // true ✓

isNaN(undefined); // true (coerces to NaN)
Number.isNaN(undefined); // false (undefined is not NaN)

// Rule: Use Number.isNaN() for strict checking
Number.isNaN(0 / 0); // true
Number.isNaN(parseFloat('abc')); // true

// Validation: isFinite vs Number.isFinite
isFinite('123'); // true (coerces to 123)
Number.isFinite('123'); // false (string is not finite number)

isFinite(Infinity); // false
Number.isFinite(Infinity); // false

isFinite(NaN); // false
Number.isFinite(NaN); // false

// Rule: Use Number.isFinite() for strict checking

// Integer validation
Number.isInteger(42); // true
Number.isInteger(42.0); // true (same as 42)
Number.isInteger(42.5); // false
Number.isInteger('42'); // false (no coercion)

// Safe integer validation
Number.isSafeInteger(42); // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER); // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1); // false
Number.isSafeInteger(42.0); // true

// Practical validation functions
function isValidNumber(value) {
    return typeof value === 'number' &&
           Number.isFinite(value) &&
           !Number.isNaN(value);
}

function parseOrDefault(str, defaultValue = 0) {
    const num = parseFloat(str);
    return Number.isNaN(num) ? defaultValue : num;
}

function strictParse(str) {
    const num = Number(str);
    if (Number.isNaN(num)) {
        throw new Error(`Invalid number: ${str}`);
    }
    return num;
}

// Safe parsing
function safeParse(str) {
    const trimmed = str.trim();
    if (trimmed === '') return null;
    
    const num = Number(trimmed);
    return Number.isNaN(num) ? null : num;
}

safeParse('123'); // 123
safeParse('abc'); // null
safeParse(''); // null

// Parsing with validation
function parsePositiveInt(str) {
    const num = parseInt(str, 10);
    if (Number.isNaN(num) || num <= 0 || !Number.isInteger(num)) {
        return null;
    }
    return num;
}

// Currency parsing
function parseCurrency(str) {
    // Remove currency symbols and commas
    const cleaned = str.replace(/[$,]/g, '');
    const num = parseFloat(cleaned);
    return Number.isNaN(num) ? null : num;
}

parseCurrency('$1,234.56'); // 1234.56
parseCurrency('€1.234,56'); // requires more complex parsing

// Input validation
function validateInput(input) {
    // Check if empty
    if (!input || input.trim() === '') {
        return {valid: false, error: 'Input required'};
    }
    
    // Try to parse
    const num = Number(input);
    
    // Check if valid number
    if (Number.isNaN(num)) {
        return {valid: false, error: 'Not a valid number'};
    }
    
    // Check if finite
    if (!Number.isFinite(num)) {
        return {valid: false, error: 'Must be finite'};
    }
    
    return {valid: true, value: num};
}
Note: Always specify radix for parseInt(). Use Number.isNaN() and Number.isFinite() (no coercion) over global versions. Number() strict (entire string), parseInt/parseFloat lenient (stops at invalid).

Section 12 Summary

  • Literals: Decimal, binary (0b), octal (0o), hex (0x), scientific (1e6); numeric separators (1_000_000) for readability; special values: Infinity, -Infinity, NaN
  • Number methods: isFinite/isInteger/isNaN/isSafeInteger validation (no coercion); toFixed/toExponential/toPrecision format (return strings); toString(radix) base conversion
  • Math object: round/ceil/floor/trunc rounding; min/max/abs/sign basic; pow/sqrt/cbrt powers/roots; random() [0,1); trig functions use radians
  • Precision: IEEE 754 float issues (0.1+0.2≠0.3); use epsilon comparison (Math.abs(a-b)<Number.EPSILON); work in integers for currency; safe integer range: ±(2^53-1)
  • BigInt: Arbitrary precision integers with n suffix (123n); cannot mix with Number; division truncates; no fractional BigInt; use for >2^53-1 integers
  • Parsing: parseInt(str, radix) lenient (stops at invalid); parseFloat(str) decimal; Number(value) strict (entire string); always use radix with parseInt
  • Validation: Prefer Number.is*() over global versions (no coercion); isNaN/isFinite/isInteger/isSafeInteger; validate before arithmetic to prevent NaN propagation

13. Date and Time Handling

13.1 Date Object Creation and Methods

Creation Method Syntax Description Example
Current Date/Time new Date() Current date and time (local timezone) new Date()
From Timestamp new Date(milliseconds) Milliseconds since Unix epoch (Jan 1, 1970 UTC) new Date(0)
From String new Date(dateString) Parse date string; format varies (prefer ISO 8601) new Date('2024-01-15')
From Components new Date(year, month, ...) Year, month (0-11), day (1-31), hour, min, sec, ms new Date(2024, 0, 15)
Static Now Date.now() Current timestamp in milliseconds Date.now()
Copy Date new Date(date) Create new Date from existing Date object new Date(oldDate)
Getter Methods Returns UTC Equivalent Description
getFullYear() 4-digit year getUTCFullYear() 2024 (not 24!)
getMonth() 0-11 getUTCMonth() 0=Jan, 11=Dec
getDate() 1-31 getUTCDate() Day of month
getDay() 0-6 getUTCDay() 0=Sunday, 6=Saturday
getHours() 0-23 getUTCHours() Hour (24-hour format)
getMinutes() 0-59 getUTCMinutes() Minutes
getSeconds() 0-59 getUTCSeconds() Seconds
getMilliseconds() 0-999 getUTCMilliseconds() Milliseconds
getTime() Number - Timestamp (ms since epoch)
getTimezoneOffset() Minutes - Offset from UTC (negative for ahead)
Setter Methods Parameters UTC Equivalent Returns
setFullYear() year, [month], [day] setUTCFullYear() Timestamp
setMonth() month (0-11), [day] setUTCMonth() Timestamp
setDate() day (1-31) setUTCDate() Timestamp
setHours() hour, [min], [sec], [ms] setUTCHours() Timestamp
setMinutes() min, [sec], [ms] setUTCMinutes() Timestamp
setSeconds() sec, [ms] setUTCSeconds() Timestamp
setMilliseconds() ms (0-999) setUTCMilliseconds() Timestamp
setTime() timestamp - Timestamp

Example: Date creation and methods

// Current date/time
const now = new Date();
console.log(now); // e.g., "2024-01-15T10:30:45.123Z"

// From timestamp (ms since Jan 1, 1970 UTC)
const epoch = new Date(0); // "1970-01-01T00:00:00.000Z"
const timestamp = new Date(1705318245123);

// Current timestamp (faster than new Date().getTime())
Date.now(); // 1705318245123

// From ISO string (recommended format)
const isoDate = new Date('2024-01-15T10:30:00.000Z'); // UTC
const isoLocal = new Date('2024-01-15T10:30:00'); // local time
const dateOnly = new Date('2024-01-15'); // midnight UTC

// ⚠️ Date string parsing is implementation-dependent!
new Date('Jan 15, 2024'); // works but inconsistent
new Date('15 Jan 2024'); // may fail in some browsers
new Date('2024/01/15'); // works

// From components (month is 0-indexed!)
const date = new Date(2024, 0, 15); // Jan 15, 2024 (local time)
const fullDate = new Date(2024, 0, 15, 10, 30, 45, 123);
// Year, Month(0-11), Day, Hour, Min, Sec, Ms

// ⚠️ Month is 0-indexed (0=Jan, 11=Dec)
new Date(2024, 0, 15); // January 15
new Date(2024, 11, 25); // December 25

// Copy date
const original = new Date();
const copy = new Date(original);
copy.setDate(copy.getDate() + 1); // doesn't affect original

// Getter methods (local time)
const d = new Date('2024-01-15T10:30:45.123');
d.getFullYear(); // 2024 (NOT getYear()!)
d.getMonth(); // 0 (January, 0-indexed!)
d.getDate(); // 15 (day of month, 1-31)
d.getDay(); // 1 (Monday, 0=Sunday)
d.getHours(); // 10
d.getMinutes(); // 30
d.getSeconds(); // 45
d.getMilliseconds(); // 123
d.getTime(); // 1705318245123 (timestamp)

// Timezone offset
d.getTimezoneOffset(); // e.g., -300 (minutes, UTC-5)
// Negative means ahead of UTC (e.g., UTC+5 = -300)

// Setter methods (chainable via return value)
const date = new Date('2024-01-15');
date.setFullYear(2025); // 2025-01-15
date.setMonth(11); // December (0-indexed!)
date.setDate(25); // 2025-12-25
date.setHours(10, 30, 0, 0); // 10:30:00.000

// Setters return timestamp
const timestamp = date.setDate(20); // returns new timestamp

// Out-of-range values auto-adjust
date.setDate(32); // rolls to next month
date.setMonth(12); // rolls to next year (Jan)
date.setDate(0); // goes to last day of previous month

const feb = new Date(2024, 1, 30); // Feb 30 → Mar 1
const endOfMonth = new Date(2024, 2, 0); // Mar 0 → Feb 29

// Invalid dates
const invalid = new Date('invalid');
invalid.toString(); // "Invalid Date"
isNaN(invalid.getTime()); // true
invalid.getTime(); // NaN

// Check validity
function isValidDate(d) {
    return d instanceof Date && !isNaN(d.getTime());
}

// Compare dates
const d1 = new Date('2024-01-15');
const d2 = new Date('2024-01-20');

d1 < d2; // true
d1.getTime() < d2.getTime(); // true (more explicit)

// Equality requires .getTime()
d1 === d2; // false (different objects)
d1.getTime() === d2.getTime(); // false (different times)

const d3 = new Date('2024-01-15');
d1.getTime() === d3.getTime(); // true

// Date arithmetic (use timestamps)
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);

const nextWeek = new Date();
nextWeek.setDate(nextWeek.getDate() + 7);

// Common patterns
// Start of day
const startOfDay = new Date();
startOfDay.setHours(0, 0, 0, 0);

// End of day
const endOfDay = new Date();
endOfDay.setHours(23, 59, 59, 999);

// Age calculation
function getAge(birthDate) {
    const today = new Date();
    let age = today.getFullYear() - birthDate.getFullYear();
    const monthDiff = today.getMonth() - birthDate.getMonth();
    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
        age--;
    }
    return age;
}
Warning: Month is 0-indexed (0=Jan, 11=Dec) but day is 1-indexed. Date string parsing varies by browser - use ISO 8601 format. getYear() is deprecated - use getFullYear().

13.2 Date Formatting and Parsing

Method Format Example Output Notes
toString() Full string "Mon Jan 15 2024 10:30:45 GMT-0500" Locale-dependent
toDateString() Date only "Mon Jan 15 2024" Human-readable
toTimeString() Time only "10:30:45 GMT-0500 (EST)" With timezone
toISOString() ISO 8601 UTC "2024-01-15T15:30:45.123Z" Standard, UTC only
toJSON() ISO 8601 UTC "2024-01-15T15:30:45.123Z" Same as toISOString()
toUTCString() UTC string "Mon, 15 Jan 2024 15:30:45 GMT" RFC-1123 format
toLocaleString() Locale format "1/15/2024, 10:30:45 AM" Locale-aware
toLocaleDateString() Locale date "1/15/2024" Date only, locale
toLocaleTimeString() Locale time "10:30:45 AM" Time only, locale
valueOf() Timestamp 1705318245123 Milliseconds since epoch
Parsing Function Input Returns Notes
Date.parse() Date string Timestamp (ms) or NaN Inconsistent across browsers
new Date(string) Date string Date object Uses Date.parse() internally
Date.UTC() Year, month, day, ... UTC timestamp Like constructor but UTC

Example: Date formatting and parsing

const date = new Date('2024-01-15T10:30:45.123Z');

// String representations
date.toString();
// "Mon Jan 15 2024 05:30:45 GMT-0500 (EST)"

date.toDateString();
// "Mon Jan 15 2024"

date.toTimeString();
// "05:30:45 GMT-0500 (Eastern Standard Time)"

date.toISOString();
// "2024-01-15T10:30:45.123Z" (always UTC, Z suffix)

date.toJSON();
// "2024-01-15T10:30:45.123Z" (same as toISOString)

date.toUTCString();
// "Mon, 15 Jan 2024 10:30:45 GMT" (RFC-1123)

// Locale-aware formatting
date.toLocaleString('en-US');
// "1/15/2024, 5:30:45 AM"

date.toLocaleDateString('en-US');
// "1/15/2024"

date.toLocaleTimeString('en-US');
// "5:30:45 AM"

// Different locales
date.toLocaleString('en-GB');
// "15/01/2024, 05:30:45"

date.toLocaleString('de-DE');
// "15.1.2024, 05:30:45"

date.toLocaleString('ja-JP');
// "2024/1/15 5:30:45"

// Locale with options
date.toLocaleString('en-US', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit'
});
// "Monday, January 15, 2024 at 05:30 AM"

// Custom formatting with options
date.toLocaleDateString('en-US', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit'
});
// "01/15/2024"

date.toLocaleDateString('en-US', {
    weekday: 'short',
    month: 'short',
    day: 'numeric'
});
// "Mon, Jan 15"

// Parsing strings
Date.parse('2024-01-15T10:30:45.123Z'); // 1705318245123
Date.parse('2024-01-15'); // midnight UTC
Date.parse('Jan 15, 2024'); // implementation-dependent ⚠️

// Check if parse succeeded
const timestamp = Date.parse('invalid');
if (isNaN(timestamp)) {
    console.log('Invalid date string');
}

// new Date() uses Date.parse()
new Date('2024-01-15T10:30:45.123Z');

// Date.UTC() - create UTC timestamp from components
const utcTimestamp = Date.UTC(2024, 0, 15, 10, 30, 45, 123);
// Same as new Date(2024, 0, 15, 10, 30, 45, 123) but UTC

const utcDate = new Date(Date.UTC(2024, 0, 15, 10, 30));

// Custom formatting (manual)
function formatDate(date) {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}-${month}-${day}`;
}
formatDate(new Date()); // "2024-01-15"

function formatDateTime(date) {
    const dateStr = formatDate(date);
    const hours = String(date.getHours()).padStart(2, '0');
    const minutes = String(date.getMinutes()).padStart(2, '0');
    const seconds = String(date.getSeconds()).padStart(2, '0');
    return `${dateStr} ${hours}:${minutes}:${seconds}`;
}

// Format with time zone
function formatWithTimezone(date) {
    const offset = -date.getTimezoneOffset();
    const sign = offset >= 0 ? '+' : '-';
    const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0');
    const minutes = String(Math.abs(offset) % 60).padStart(2, '0');
    return `${formatDateTime(date)} GMT${sign}${hours}:${minutes}`;
}

// Relative time formatting
function getRelativeTime(date) {
    const now = new Date();
    const diffMs = now - date;
    const diffSec = Math.floor(diffMs / 1000);
    const diffMin = Math.floor(diffSec / 60);
    const diffHour = Math.floor(diffMin / 60);
    const diffDay = Math.floor(diffHour / 24);
    
    if (diffSec < 60) return `${diffSec} seconds ago`;
    if (diffMin < 60) return `${diffMin} minutes ago`;
    if (diffHour < 24) return `${diffHour} hours ago`;
    if (diffDay < 30) return `${diffDay} days ago`;
    return date.toLocaleDateString();
}

// ISO 8601 variants
const d = new Date();

// Local ISO (no Z)
function toLocalISOString(date) {
    const offset = -date.getTimezoneOffset();
    const sign = offset >= 0 ? '+' : '-';
    const pad = (n) => String(n).padStart(2, '0');
    
    return date.getFullYear() +
        '-' + pad(date.getMonth() + 1) +
        '-' + pad(date.getDate()) +
        'T' + pad(date.getHours()) +
        ':' + pad(date.getMinutes()) +
        ':' + pad(date.getSeconds()) +
        '.' + String(date.getMilliseconds()).padStart(3, '0') +
        sign + pad(Math.floor(Math.abs(offset) / 60)) +
        ':' + pad(Math.abs(offset) % 60);
}

// Common date formats
function formatters(date) {
    return {
        iso: date.toISOString(), // "2024-01-15T10:30:45.123Z"
        date: date.toLocaleDateString(), // "1/15/2024"
        time: date.toLocaleTimeString(), // "10:30:45 AM"
        short: date.toLocaleString('en-US', {
            month: 'short',
            day: 'numeric',
            year: 'numeric'
        }), // "Jan 15, 2024"
        long: date.toLocaleString('en-US', {
            weekday: 'long',
            month: 'long',
            day: 'numeric',
            year: 'numeric'
        }) // "Monday, January 15, 2024"
    };
}
Note: toISOString() always outputs UTC (Z suffix). Date.parse() is inconsistent - prefer ISO 8601 format. Use Intl.DateTimeFormat for advanced locale formatting.

13.3 Timezone Handling and UTC Operations

Concept Method/Approach Description Example
Local Time get*() methods Methods without UTC prefix use local timezone date.getHours()
UTC Time getUTC*() methods Methods with UTC prefix use UTC timezone date.getUTCHours()
Timezone Offset getTimezoneOffset() Minutes difference from UTC (negative = ahead) -300 for UTC-5
UTC Constructor Date.UTC(y, m, d, ...) Create timestamp from UTC components Date.UTC(2024, 0, 15)
UTC String toISOString() Always returns UTC time with Z suffix "2024-01-15T10:30:45.123Z"
Timezone Name Intl.DateTimeFormat Get timezone identifier "America/New_York"

Example: Timezone handling

// Create date in local timezone
const local = new Date('2024-01-15T10:30:00'); // local 10:30

// Create date in UTC (Z suffix)
const utc = new Date('2024-01-15T10:30:00Z'); // UTC 10:30

// Local vs UTC getters
const date = new Date('2024-01-15T10:30:00Z');

// Local time (depends on system timezone)
date.getHours(); // e.g., 5 (if UTC-5)
date.getMinutes(); // 30

// UTC time (always same regardless of timezone)
date.getUTCHours(); // 10
date.getUTCMinutes(); // 30

// Timezone offset (minutes from UTC)
date.getTimezoneOffset();
// -300 for UTC-5 (EST) - negative means behind UTC
// 0 for UTC
// 60 for UTC+1 (CET)

// Convert offset to hours
const offsetHours = -date.getTimezoneOffset() / 60;
// -5 for EST, 0 for UTC, +1 for CET

// Get timezone name (modern browsers)
const timezoneName = Intl.DateTimeFormat().resolvedOptions().timeZone;
// "America/New_York", "Europe/London", "Asia/Tokyo"

// UTC constructor
const utcTimestamp = Date.UTC(2024, 0, 15, 10, 30, 0);
const utcDate = new Date(utcTimestamp);

// Difference: local vs UTC constructor
const localDate = new Date(2024, 0, 15, 10, 30); // 10:30 local
const utcDate2 = new Date(Date.UTC(2024, 0, 15, 10, 30)); // 10:30 UTC

localDate.getTime() !== utcDate2.getTime(); // true (different times!)

// Convert local to UTC
function toUTC(date) {
    return new Date(date.getTime() + date.getTimezoneOffset() * 60000);
}

// Convert UTC to local
function toLocal(date) {
    return new Date(date.getTime() - date.getTimezoneOffset() * 60000);
}

// Get UTC date at midnight
function getUTCDate(date) {
    return new Date(Date.UTC(
        date.getUTCFullYear(),
        date.getUTCMonth(),
        date.getUTCDate()
    ));
}

// Get local date at midnight
function getLocalDate(date) {
    return new Date(
        date.getFullYear(),
        date.getMonth(),
        date.getDate()
    );
}

// Compare dates ignoring time
function isSameDay(date1, date2) {
    return date1.getFullYear() === date2.getFullYear() &&
           date1.getMonth() === date2.getMonth() &&
           date1.getDate() === date2.getDate();
}

// Compare UTC dates
function isSameUTCDay(date1, date2) {
    return date1.getUTCFullYear() === date2.getUTCFullYear() &&
           date1.getUTCMonth() === date2.getUTCMonth() &&
           date1.getUTCDate() === date2.getUTCDate();
}

// Format with timezone
function formatWithTZ(date) {
    const offset = -date.getTimezoneOffset();
    const sign = offset >= 0 ? '+' : '-';
    const hours = String(Math.floor(Math.abs(offset) / 60)).padStart(2, '0');
    const minutes = String(Math.abs(offset) % 60).padStart(2, '0');
    
    return date.toISOString().slice(0, -1) + // remove Z
           `${sign}${hours}:${minutes}`;
}

// Parse ISO with timezone
const withTZ = new Date('2024-01-15T10:30:00+05:30'); // India
const withUTC = new Date('2024-01-15T10:30:00Z'); // UTC

withTZ.getTime() !== withUTC.getTime(); // different times

// Daylight Saving Time (DST) detection
function isDST(date) {
    const jan = new Date(date.getFullYear(), 0, 1);
    const jul = new Date(date.getFullYear(), 6, 1);
    const stdOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
    return date.getTimezoneOffset() < stdOffset;
}

isDST(new Date('2024-01-15')); // false (winter)
isDST(new Date('2024-07-15')); // true (summer, in DST zones)

// Working with specific timezones (use Intl)
const nyDate = new Date('2024-01-15T10:30:00Z');

const formatter = new Intl.DateTimeFormat('en-US', {
    timeZone: 'America/New_York',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false
});

formatter.format(nyDate); // "01/15/2024, 05:30:00" (EST)

// Different timezone
const tokyoFormatter = new Intl.DateTimeFormat('en-US', {
    timeZone: 'Asia/Tokyo',
    hour: '2-digit',
    minute: '2-digit',
    hour12: false
});

tokyoFormatter.format(nyDate); // "19:30" (JST, +9 hours)

// Get time in specific timezone
function getTimeInTimezone(date, timezone) {
    return new Intl.DateTimeFormat('en-US', {
        timeZone: timezone,
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        hour12: false
    }).format(date);
}

getTimeInTimezone(new Date(), 'America/Los_Angeles');
getTimeInTimezone(new Date(), 'Europe/London');
getTimeInTimezone(new Date(), 'Asia/Dubai');

// Best practices
// 1. Store dates as UTC timestamps
const stored = date.getTime(); // or date.toISOString()

// 2. Convert to local for display
const displayed = new Date(stored);

// 3. Always use UTC for date comparisons
const isAfter = date1.getTime() > date2.getTime();

// 4. Use libraries for complex timezone work
// moment-timezone, date-fns-tz, luxon
Warning: Timezone offset is negative when ahead of UTC (UTC-5 = -300 minutes). DST changes offset during year. Always store dates as UTC timestamps. Date string without timezone = local time (implementation-dependent).

13.4 Date Arithmetic and Calculations

Operation Method Description Example
Add Days setDate(getDate() + n) Add/subtract days; auto-handles month/year rollover date.setDate(date.getDate() + 7)
Add Months setMonth(getMonth() + n) Add/subtract months; handles year rollover date.setMonth(date.getMonth() + 3)
Add Years setFullYear(getFullYear() + n) Add/subtract years date.setFullYear(date.getFullYear() + 1)
Add Hours setHours(getHours() + n) Add/subtract hours; handles day rollover date.setHours(date.getHours() + 24)
Add Milliseconds setTime(getTime() + ms) Precise time arithmetic using timestamps date.setTime(date.getTime() + 1000)
Date Difference date1 - date2 Subtract dates to get milliseconds difference diff = date1 - date2
Days Between (date1 - date2) / MS_PER_DAY Convert millisecond difference to days Math.floor(diff / 86400000)
Compare Dates date1 < date2 Direct comparison operators work if (date1 < date2) {...}

Example: Date arithmetic

// Add/subtract days
const today = new Date('2024-01-15');

const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1); // Jan 16

const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1); // Jan 14

const nextWeek = new Date(today);
nextWeek.setDate(nextWeek.getDate() + 7); // Jan 22

// Handles month/year rollover automatically
const endOfMonth = new Date('2024-01-31');
endOfMonth.setDate(endOfMonth.getDate() + 1); // Feb 1, 2024

// Add months
const date = new Date('2024-01-15');
date.setMonth(date.getMonth() + 1); // Feb 15, 2024
date.setMonth(date.getMonth() + 12); // Feb 15, 2025

// Month rollover edge case
const jan31 = new Date('2024-01-31');
jan31.setMonth(jan31.getMonth() + 1); // Feb 29 or Mar 2/3 (Feb has fewer days)

// Safe month addition (preserve day if possible)
function addMonths(date, months) {
    const d = new Date(date);
    const targetMonth = d.getMonth() + months;
    const targetYear = d.getFullYear() + Math.floor(targetMonth / 12);
    const finalMonth = ((targetMonth % 12) + 12) % 12;
    
    d.setFullYear(targetYear);
    d.setMonth(finalMonth);
    
    // If day changed (e.g., Jan 31 + 1 month), go to last day of month
    if (d.getDate() !== date.getDate()) {
        d.setDate(0); // Last day of previous month
    }
    return d;
}

// Add years
const now = new Date();
const nextYear = new Date(now);
nextYear.setFullYear(nextYear.getFullYear() + 1);

// Add hours
const hour = new Date();
const nextHour = new Date(hour);
nextHour.setHours(nextHour.getHours() + 1);

// Add minutes
const minute = new Date();
const nextMinute = new Date(minute);
nextMinute.setMinutes(nextMinute.getMinutes() + 30);

// Precise arithmetic with timestamps
const MS_PER_SECOND = 1000;
const MS_PER_MINUTE = 60 * 1000;
const MS_PER_HOUR = 60 * 60 * 1000;
const MS_PER_DAY = 24 * 60 * 60 * 1000;

// Add 7 days using timestamps
const d = new Date();
d.setTime(d.getTime() + 7 * MS_PER_DAY);

// Alternative using Date constructor
const future = new Date(Date.now() + 7 * MS_PER_DAY);

// Date difference (returns milliseconds)
const date1 = new Date('2024-01-20');
const date2 = new Date('2024-01-15');
const diffMs = date1 - date2; // 432000000 ms (5 days)

// Convert to different units
const diffSeconds = diffMs / 1000;
const diffMinutes = diffMs / (1000 * 60);
const diffHours = diffMs / (1000 * 60 * 60);
const diffDays = diffMs / (1000 * 60 * 60 * 24);

// Days between dates (whole days)
function daysBetween(date1, date2) {
    const MS_PER_DAY = 24 * 60 * 60 * 1000;
    const utc1 = Date.UTC(date1.getFullYear(), date1.getMonth(), date1.getDate());
    const utc2 = Date.UTC(date2.getFullYear(), date2.getMonth(), date2.getDate());
    return Math.floor((utc1 - utc2) / MS_PER_DAY);
}

daysBetween(new Date('2024-01-20'), new Date('2024-01-15')); // 5

// Compare dates
date1 < date2; // false
date1 > date2; // true
date1 === date2; // false (different objects)
date1.getTime() === date2.getTime(); // false (different times)

// Check if same day
function isSameDay(d1, d2) {
    return d1.getFullYear() === d2.getFullYear() &&
           d1.getMonth() === d2.getMonth() &&
           d1.getDate() === d2.getDate();
}

// Business days calculation (excluding weekends)
function addBusinessDays(date, days) {
    const result = new Date(date);
    let added = 0;
    
    while (added < days) {
        result.setDate(result.getDate() + 1);
        const day = result.getDay();
        if (day !== 0 && day !== 6) { // Not Sunday or Saturday
            added++;
        }
    }
    return result;
}

// Count business days between dates
function businessDaysBetween(startDate, endDate) {
    let count = 0;
    const current = new Date(startDate);
    
    while (current < endDate) {
        const day = current.getDay();
        if (day !== 0 && day !== 6) {
            count++;
        }
        current.setDate(current.getDate() + 1);
    }
    return count;
}

// Age calculation
function calculateAge(birthDate, asOfDate = new Date()) {
    let age = asOfDate.getFullYear() - birthDate.getFullYear();
    const monthDiff = asOfDate.getMonth() - birthDate.getMonth();
    
    if (monthDiff < 0 || (monthDiff === 0 && asOfDate.getDate() < birthDate.getDate())) {
        age--;
    }
    return age;
}

calculateAge(new Date('1990-05-15')); // e.g., 34

// Start/end of period
function startOfDay(date) {
    const d = new Date(date);
    d.setHours(0, 0, 0, 0);
    return d;
}

function endOfDay(date) {
    const d = new Date(date);
    d.setHours(23, 59, 59, 999);
    return d;
}

function startOfMonth(date) {
    return new Date(date.getFullYear(), date.getMonth(), 1);
}

function endOfMonth(date) {
    return new Date(date.getFullYear(), date.getMonth() + 1, 0);
}

function startOfYear(date) {
    return new Date(date.getFullYear(), 0, 1);
}

function endOfYear(date) {
    return new Date(date.getFullYear(), 11, 31, 23, 59, 59, 999);
}

// Days in month
function daysInMonth(date) {
    return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
}

daysInMonth(new Date('2024-02-01')); // 29 (leap year)
daysInMonth(new Date('2023-02-01')); // 28

// Is leap year
function isLeapYear(year) {
    return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}

// Week number
function getWeekNumber(date) {
    const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
    const dayNum = d.getUTCDay() || 7;
    d.setUTCDate(d.getUTCDate() + 4 - dayNum);
    const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
    return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}

// Elapsed time
function timeElapsed(startDate, endDate = new Date()) {
    const diff = endDate - startDate;
    
    const days = Math.floor(diff / MS_PER_DAY);
    const hours = Math.floor((diff % MS_PER_DAY) / MS_PER_HOUR);
    const minutes = Math.floor((diff % MS_PER_HOUR) / MS_PER_MINUTE);
    const seconds = Math.floor((diff % MS_PER_MINUTE) / MS_PER_SECOND);
    
    return {days, hours, minutes, seconds};
}

// Countdown timer
function countdown(targetDate) {
    const now = new Date();
    const diff = targetDate - now;
    
    if (diff <= 0) return {expired: true};
    
    return {
        expired: false,
        days: Math.floor(diff / MS_PER_DAY),
        hours: Math.floor((diff % MS_PER_DAY) / MS_PER_HOUR),
        minutes: Math.floor((diff % MS_PER_HOUR) / MS_PER_MINUTE),
        seconds: Math.floor((diff % MS_PER_MINUTE) / MS_PER_SECOND)
    };
}
Note: Date methods auto-adjust for overflow (e.g., Feb 30 → Mar 2). Use timestamps for precise arithmetic. Subtraction returns milliseconds. Month edge cases need careful handling (Jan 31 + 1 month).

13.5 Internationalization Date Formatting

Intl.DateTimeFormat Option Values Description Example Output
dateStyle full, long, medium, short Preset date format style "Monday, January 15, 2024"
timeStyle full, long, medium, short Preset time format style "10:30:45 AM EST"
weekday narrow, short, long Day of week name "M", "Mon", "Monday"
year numeric, 2-digit Year format "2024", "24"
month numeric, 2-digit, narrow, short, long Month format "1", "01", "J", "Jan", "January"
day numeric, 2-digit Day of month "15", "15"
hour numeric, 2-digit Hour format "10", "10"
minute numeric, 2-digit Minute format "30", "30"
second numeric, 2-digit Second format "45", "45"
timeZone IANA timezone identifier Display in specific timezone "America/New_York"
timeZoneName short, long, shortOffset, longOffset Timezone display "EST", "Eastern Standard Time"
hour12 true, false 12 or 24 hour format "10:30 AM" vs "10:30"

Example: Intl.DateTimeFormat usage

const date = new Date('2024-01-15T10:30:45.123Z');

// Basic formatting
const formatter = new Intl.DateTimeFormat('en-US');
formatter.format(date); // "1/15/2024"

// Different locales
new Intl.DateTimeFormat('en-US').format(date); // "1/15/2024"
new Intl.DateTimeFormat('en-GB').format(date); // "15/01/2024"
new Intl.DateTimeFormat('de-DE').format(date); // "15.1.2024"
new Intl.DateTimeFormat('ja-JP').format(date); // "2024/1/15"
new Intl.DateTimeFormat('ar-EG').format(date); // "١٥‏/١‏/٢٠٢٤"

// Date and time styles
new Intl.DateTimeFormat('en-US', {
    dateStyle: 'full'
}).format(date);
// "Monday, January 15, 2024"

new Intl.DateTimeFormat('en-US', {
    dateStyle: 'long'
}).format(date);
// "January 15, 2024"

new Intl.DateTimeFormat('en-US', {
    dateStyle: 'medium'
}).format(date);
// "Jan 15, 2024"

new Intl.DateTimeFormat('en-US', {
    dateStyle: 'short'
}).format(date);
// "1/15/24"

new Intl.DateTimeFormat('en-US', {
    timeStyle: 'full'
}).format(date);
// "5:30:45 AM Eastern Standard Time"

new Intl.DateTimeFormat('en-US', {
    timeStyle: 'long'
}).format(date);
// "5:30:45 AM EST"

new Intl.DateTimeFormat('en-US', {
    timeStyle: 'medium'
}).format(date);
// "5:30:45 AM"

new Intl.DateTimeFormat('en-US', {
    timeStyle: 'short'
}).format(date);
// "5:30 AM"

// Combined date and time
new Intl.DateTimeFormat('en-US', {
    dateStyle: 'long',
    timeStyle: 'short'
}).format(date);
// "January 15, 2024 at 5:30 AM"

// Custom options
new Intl.DateTimeFormat('en-US', {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit'
}).format(date);
// "Monday, January 15, 2024, 05:30 AM"

// Weekday formats
new Intl.DateTimeFormat('en-US', {weekday: 'narrow'}).format(date); // "M"
new Intl.DateTimeFormat('en-US', {weekday: 'short'}).format(date); // "Mon"
new Intl.DateTimeFormat('en-US', {weekday: 'long'}).format(date); // "Monday"

// Month formats
new Intl.DateTimeFormat('en-US', {month: 'numeric'}).format(date); // "1"
new Intl.DateTimeFormat('en-US', {month: '2-digit'}).format(date); // "01"
new Intl.DateTimeFormat('en-US', {month: 'narrow'}).format(date); // "J"
new Intl.DateTimeFormat('en-US', {month: 'short'}).format(date); // "Jan"
new Intl.DateTimeFormat('en-US', {month: 'long'}).format(date); // "January"

// 12 vs 24 hour
new Intl.DateTimeFormat('en-US', {
    hour: '2-digit',
    minute: '2-digit',
    hour12: true
}).format(date);
// "05:30 AM"

new Intl.DateTimeFormat('en-US', {
    hour: '2-digit',
    minute: '2-digit',
    hour12: false
}).format(date);
// "05:30"

// Timezone handling
new Intl.DateTimeFormat('en-US', {
    timeZone: 'America/New_York',
    dateStyle: 'full',
    timeStyle: 'full'
}).format(date);
// "Monday, January 15, 2024 at 5:30:45 AM Eastern Standard Time"

new Intl.DateTimeFormat('en-US', {
    timeZone: 'Asia/Tokyo',
    dateStyle: 'full',
    timeStyle: 'full'
}).format(date);
// "Monday, January 15, 2024 at 7:30:45 PM Japan Standard Time"

new Intl.DateTimeFormat('en-US', {
    timeZone: 'Europe/London',
    dateStyle: 'long',
    timeStyle: 'long'
}).format(date);
// "January 15, 2024 at 10:30:45 AM GMT"

// Timezone name formats
new Intl.DateTimeFormat('en-US', {
    timeZone: 'America/New_York',
    timeZoneName: 'short'
}).format(date);
// "1/15/2024, EST"

new Intl.DateTimeFormat('en-US', {
    timeZone: 'America/New_York',
    timeZoneName: 'long'
}).format(date);
// "1/15/2024, Eastern Standard Time"

// Format parts (advanced)
const parts = new Intl.DateTimeFormat('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
}).formatToParts(date);

// [{type: 'month', value: 'January'}, {type: 'literal', value: ' '}, ...]

// Custom formatting using parts
function customFormat(date) {
    const parts = new Intl.DateTimeFormat('en-US', {
        year: 'numeric',
        month: 'long',
        day: 'numeric'
    }).formatToParts(date);
    
    const obj = {};
    parts.forEach(({type, value}) => {
        obj[type] = value;
    });
    
    return `${obj.day} of ${obj.month}, ${obj.year}`;
}
customFormat(date); // "15 of January, 2024"

// Format range (multiple dates)
const start = new Date('2024-01-15');
const end = new Date('2024-01-20');

new Intl.DateTimeFormat('en-US', {
    dateStyle: 'long'
}).formatRange(start, end);
// "January 15 – 20, 2024"

// Range across months
const start2 = new Date('2024-01-28');
const end2 = new Date('2024-02-05');

new Intl.DateTimeFormat('en-US', {
    dateStyle: 'long'
}).formatRange(start2, end2);
// "January 28 – February 5, 2024"

// Relative time formatting
const rtf = new Intl.RelativeTimeFormat('en-US', {numeric: 'auto'});

rtf.format(-1, 'day'); // "yesterday"
rtf.format(0, 'day'); // "today"
rtf.format(1, 'day'); // "tomorrow"
rtf.format(2, 'day'); // "in 2 days"
rtf.format(-2, 'week'); // "2 weeks ago"
rtf.format(3, 'month'); // "in 3 months"

// Numeric style
const rtfNumeric = new Intl.RelativeTimeFormat('en-US', {numeric: 'always'});
rtfNumeric.format(-1, 'day'); // "1 day ago" (not "yesterday")
rtfNumeric.format(0, 'day'); // "in 0 days" (not "today")

// Get relative time
function getRelativeTime(date) {
    const now = new Date();
    const diff = date - now;
    const absDiff = Math.abs(diff);
    
    const rtf = new Intl.RelativeTimeFormat('en-US', {numeric: 'auto'});
    
    if (absDiff < 60 * 1000) {
        return rtf.format(Math.round(diff / 1000), 'second');
    }
    if (absDiff < 60 * 60 * 1000) {
        return rtf.format(Math.round(diff / (60 * 1000)), 'minute');
    }
    if (absDiff < 24 * 60 * 60 * 1000) {
        return rtf.format(Math.round(diff / (60 * 60 * 1000)), 'hour');
    }
    if (absDiff < 30 * 24 * 60 * 60 * 1000) {
        return rtf.format(Math.round(diff / (24 * 60 * 60 * 1000)), 'day');
    }
    return rtf.format(Math.round(diff / (30 * 24 * 60 * 60 * 1000)), 'month');
}

// Reusable formatter
const dateFormatter = new Intl.DateTimeFormat('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
});

dateFormatter.format(date1);
dateFormatter.format(date2);
dateFormatter.format(date3);

// Get supported locales
Intl.DateTimeFormat.supportedLocalesOf(['en-US', 'de-DE', 'ja-JP']);
// ['en-US', 'de-DE', 'ja-JP']
Note: Intl.DateTimeFormat is locale-aware and timezone-aware. Use formatRange() for date ranges. Intl.RelativeTimeFormat for "2 days ago" style. Reuse formatters for performance.

Section 13 Summary

  • Creation: new Date() current; from timestamp/string/components; month is 0-indexed (0=Jan); use ISO 8601 for consistency; Date.now() for timestamp
  • Getters/Setters: get*/set* local time; getUTC*/setUTC* UTC; getTime() timestamp; setters auto-adjust overflow; getTimezoneOffset() minutes from UTC (negative = ahead)
  • Formatting: toISOString() UTC standard; toLocaleString() locale-aware; toString() local; toJSON() serialization; formatToParts() for custom formats
  • Timezones: Local vs UTC methods; offset in minutes; store as UTC timestamps; display in local; Intl.DateTimeFormat with timeZone option; DST affects offset
  • Arithmetic: setDate/Month/Year auto-rollover; timestamp math for precision; subtract dates = milliseconds; convert using MS_PER_DAY; daysBetween() use UTC dates
  • Intl.DateTimeFormat: Locale-aware formatting; dateStyle/timeStyle presets; custom options (weekday/month/year); timeZone for specific zones; formatRange() for periods; reuse formatters
  • Best practices: Store UTC timestamps; display in local; use ISO 8601; handle DST; beware month edge cases (Jan 31+1mo); use libraries (date-fns, luxon) for complex operations

14. Asynchronous Programming Mastery

14.1 Callback Patterns and Error Handling

Pattern Syntax Description Use Case
Error-first Callback (err, result) => {...} Node.js convention: first param is error (null if success) File I/O, network requests
Success Callback (result) => {...} Browser APIs: callback receives result on success setTimeout, event handlers
Separate Callbacks onSuccess, onError Two separate functions for success and error AJAX, animations
Callback Hell Nested callbacks Deep nesting; hard to read/maintain Anti-pattern (avoid!)
Named Functions Extract callbacks to functions Flatten callback structure; improve readability Complex async flows

Example: Callback patterns

// Error-first callback (Node.js style)
function readFile(path, callback) {
    // Simulated async operation
    setTimeout(() => {
        if (!path) {
            callback(new Error('Path is required'), null);
        } else {
            callback(null, 'file contents');
        }
    }, 100);
}

// Usage
readFile('file.txt', (err, data) => {
    if (err) {
        console.error('Error:', err.message);
        return;
    }
    console.log('Data:', data);
});

// Success callback (browser style)
setTimeout(() => {
    console.log('Executed after 1 second');
}, 1000);

// Separate success/error callbacks
function fetchData(url, onSuccess, onError) {
    setTimeout(() => {
        if (url.includes('fail')) {
            onError(new Error('Request failed'));
        } else {
            onSuccess({data: 'response'});
        }
    }, 100);
}

fetchData('api/data',
    (result) => console.log('Success:', result),
    (error) => console.error('Error:', error)
);

// ❌ Callback Hell (pyramid of doom)
getData((data1) => {
    processData(data1, (data2) => {
        saveData(data2, (data3) => {
            sendNotification(data3, (result) => {
                console.log('All done!');
            });
        });
    });
});

// ✓ Named functions (flattened)
function handleData1(data1) {
    processData(data1, handleData2);
}

function handleData2(data2) {
    saveData(data2, handleData3);
}

function handleData3(data3) {
    sendNotification(data3, handleFinal);
}

function handleFinal(result) {
    console.log('All done!');
}

getData(handleData1);

// Error handling patterns
function asyncOperation(callback) {
    try {
        setTimeout(() => {
            // Errors in setTimeout won't be caught by outer try-catch!
            throw new Error('Async error');
        }, 100);
    } catch (error) {
        // This won't catch the error ❌
        callback(error, null);
    }
}

// Correct: handle errors inside async code
function asyncOperationCorrect(callback) {
    setTimeout(() => {
        try {
            // Do work
            const result = riskyOperation();
            callback(null, result);
        } catch (error) {
            callback(error, null);
        }
    }, 100);
}

// Multiple callbacks pattern
function multiStep(callback) {
    step1((err, result1) => {
        if (err) return callback(err);
        
        step2(result1, (err, result2) => {
            if (err) return callback(err);
            
            step3(result2, (err, result3) => {
                if (err) return callback(err);
                callback(null, result3);
            });
        });
    });
}

// Parallel callbacks with counter
function parallelTasks(tasks, callback) {
    let completed = 0;
    const results = [];
    let hasError = false;
    
    tasks.forEach((task, index) => {
        task((err, result) => {
            if (hasError) return;
            
            if (err) {
                hasError = true;
                return callback(err);
            }
            
            results[index] = result;
            completed++;
            
            if (completed === tasks.length) {
                callback(null, results);
            }
        });
    });
}

// Usage
parallelTasks([
    (cb) => setTimeout(() => cb(null, 'task1'), 100),
    (cb) => setTimeout(() => cb(null, 'task2'), 50),
    (cb) => setTimeout(() => cb(null, 'task3'), 75)
], (err, results) => {
    console.log(results); // ['task1', 'task2', 'task3']
});

// Callback with context preservation
function Timer(duration, callback) {
    this.duration = duration;
    this.callback = callback;
}

Timer.prototype.start = function() {
    // Preserve 'this' context
    setTimeout(() => {
        this.callback(this.duration);
    }, this.duration);
};

// Timeout wrapper
function withTimeout(asyncFn, timeout, callback) {
    let called = false;
    
    const timer = setTimeout(() => {
        if (!called) {
            called = true;
            callback(new Error('Timeout'), null);
        }
    }, timeout);
    
    asyncFn((err, result) => {
        if (!called) {
            called = true;
            clearTimeout(timer);
            callback(err, result);
        }
    });
}
Warning: Callbacks inside async operations can't be caught by outer try-catch. Always handle errors inside the async code. Avoid callback hell - use Promises or async/await. Remember to call callback exactly once.

14.2 Promise API and Promise Chaining

Method Syntax Description Returns
Constructor new Promise((resolve, reject) => {...}) Create new Promise; executor runs immediately Promise
then() promise.then(onFulfilled, onRejected) Attach fulfillment/rejection handlers; returns new Promise Promise
catch() promise.catch(onRejected) Catch errors; shorthand for .then(null, onRejected) Promise
finally() promise.finally(onFinally) Runs regardless of outcome; no arguments Promise
Promise.resolve() Promise.resolve(value) Create fulfilled Promise with value Promise
Promise.reject() Promise.reject(reason) Create rejected Promise with reason Promise

Example: Promise creation and chaining

// Creating a Promise
const promise = new Promise((resolve, reject) => {
    // Executor function runs immediately
    setTimeout(() => {
        const success = true;
        if (success) {
            resolve('Success!'); // Fulfill
        } else {
            reject(new Error('Failed!')); // Reject
        }
    }, 1000);
});

// Consuming with then/catch
promise
    .then(result => {
        console.log(result); // 'Success!'
        return result.toUpperCase();
    })
    .then(upper => {
        console.log(upper); // 'SUCCESS!'
    })
    .catch(error => {
        console.error(error);
    })
    .finally(() => {
        console.log('Cleanup'); // Always runs
    });

// Promise states
// 1. Pending: initial state
// 2. Fulfilled: operation completed successfully
// 3. Rejected: operation failed

// Promise.resolve - create fulfilled Promise
Promise.resolve(42)
    .then(value => console.log(value)); // 42

// Resolving with another Promise
Promise.resolve(Promise.resolve(42))
    .then(value => console.log(value)); // 42 (unwrapped)

// Promise.reject - create rejected Promise
Promise.reject(new Error('Failed'))
    .catch(error => console.error(error));

// Chaining - each then returns new Promise
fetch('/api/user')
    .then(response => response.json()) // Parse JSON
    .then(data => data.userId) // Extract userId
    .then(userId => fetch(`/api/profile/${userId}`)) // Fetch profile
    .then(response => response.json())
    .then(profile => console.log(profile))
    .catch(error => console.error('Error:', error));

// Return value determines next Promise
Promise.resolve(1)
    .then(x => x + 1) // Returns 2 (wrapped in Promise)
    .then(x => x * 2) // Returns 4
    .then(x => console.log(x)); // 4

// Returning a Promise flattens the chain
Promise.resolve(1)
    .then(x => Promise.resolve(x + 1)) // Returns Promise
    .then(x => console.log(x)); // 2 (not Promise!)

// Error propagation
Promise.resolve(1)
    .then(x => {
        throw new Error('Oops!');
    })
    .then(x => {
        console.log('Skipped'); // Never executes
    })
    .catch(error => {
        console.error(error.message); // 'Oops!'
        return 'recovered';
    })
    .then(value => {
        console.log(value); // 'recovered' - chain continues
    });

// finally() doesn't receive value/error
Promise.resolve(42)
    .finally(() => {
        console.log('Cleanup'); // No arguments
        return 'ignored'; // Return value ignored
    })
    .then(value => console.log(value)); // 42 (original value)

// finally() propagates rejection
Promise.reject(new Error('Failed'))
    .finally(() => {
        console.log('Cleanup');
    })
    .catch(error => console.error(error)); // Original error

// Multiple handlers on same Promise
const p = Promise.resolve(42);
p.then(x => console.log('Handler 1:', x));
p.then(x => console.log('Handler 2:', x));
// Both execute independently

// Catching errors in specific parts
fetch('/api/data')
    .then(response => response.json())
    .catch(error => {
        console.error('Fetch failed:', error);
        return {fallback: true}; // Provide fallback
    })
    .then(data => {
        // Process data or fallback
        if (data.fallback) {
            console.log('Using fallback');
        }
    });

// Converting callbacks to Promises
function promisify(fn) {
    return function(...args) {
        return new Promise((resolve, reject) => {
            fn(...args, (err, result) => {
                if (err) reject(err);
                else resolve(result);
            });
        });
    };
}

// Usage
const readFilePromise = promisify(fs.readFile);
readFilePromise('file.txt', 'utf8')
    .then(contents => console.log(contents))
    .catch(error => console.error(error));

// Thenable objects (Promise-like)
const thenable = {
    then(onFulfilled, onRejected) {
        setTimeout(() => onFulfilled(42), 100);
    }
};

Promise.resolve(thenable)
    .then(value => console.log(value)); // 42

// Sequential execution
function sequential(promises) {
    return promises.reduce(
        (chain, promise) => chain.then(() => promise()),
        Promise.resolve()
    );
}

// Parallel with chaining
Promise.resolve()
    .then(() => fetch('/api/data1'))
    .then(r => r.json())
    .then(data1 => {
        // Now fetch data2 using data1
        return fetch(`/api/data2/${data1.id}`);
    })
    .then(r => r.json())
    .then(data2 => console.log(data2));
Note: Promises are chainable - each then() returns a new Promise. Errors bubble to nearest catch(). finally() doesn't receive arguments. Return values are auto-wrapped in Promises.

14.3 async/await Syntax and Error Handling

Keyword Syntax Description Returns
async function async function name() {...} Declares async function; always returns Promise Promise
await await promise Pause until Promise settles; only in async functions Resolved value
async arrow async () => {...} Async arrow function Promise
async method async methodName() {...} Async method in class/object Promise
try/catch try {await ...} catch (e) {...} Handle async errors synchronously -

Example: async/await usage

// Basic async function
async function fetchUser() {
    const response = await fetch('/api/user');
    const data = await response.json();
    return data; // Automatically wrapped in Promise
}

// Usage
fetchUser()
    .then(user => console.log(user))
    .catch(error => console.error(error));

// Or await the async function
async function main() {
    const user = await fetchUser();
    console.log(user);
}

// Return value is wrapped in Promise
async function getValue() {
    return 42; // Becomes Promise.resolve(42)
}

getValue().then(x => console.log(x)); // 42

// Equivalent to:
function getValuePromise() {
    return Promise.resolve(42);
}

// Throwing in async function = rejected Promise
async function throwError() {
    throw new Error('Failed!');
}

throwError()
    .catch(error => console.error(error)); // Error: Failed!

// try/catch for error handling
async function fetchData() {
    try {
        const response = await fetch('/api/data');
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Fetch failed:', error);
        throw error; // Re-throw or return fallback
    }
}

// Sequential execution (waits for each)
async function sequential() {
    const result1 = await fetch('/api/data1');
    const data1 = await result1.json();
    
    const result2 = await fetch('/api/data2');
    const data2 = await result2.json();
    
    const result3 = await fetch('/api/data3');
    const data3 = await result3.json();
    
    return [data1, data2, data3];
}

// Parallel execution (start all at once)
async function parallel() {
    // Start all requests simultaneously
    const [result1, result2, result3] = await Promise.all([
        fetch('/api/data1'),
        fetch('/api/data2'),
        fetch('/api/data3')
    ]);
    
    // Parse in parallel
    const [data1, data2, data3] = await Promise.all([
        result1.json(),
        result2.json(),
        result3.json()
    ]);
    
    return [data1, data2, data3];
}

// Conditional await
async function conditionalFetch(useCache) {
    let data;
    
    if (useCache) {
        data = getFromCache();
    } else {
        const response = await fetch('/api/data');
        data = await response.json();
    }
    
    return data;
}

// await in loops
async function processItems(items) {
    // Sequential processing
    for (const item of items) {
        const result = await processItem(item);
        console.log(result);
    }
    
    // Parallel processing (better for independent operations)
    const results = await Promise.all(
        items.map(item => processItem(item))
    );
}

// Error handling patterns
async function withErrorHandling() {
    try {
        const data = await fetchData();
        return {success: true, data};
    } catch (error) {
        return {success: false, error: error.message};
    }
}

// Multiple try/catch blocks
async function multipleOperations() {
    let userData, postsData;
    
    try {
        userData = await fetchUser();
    } catch (error) {
        console.error('User fetch failed:', error);
        userData = getDefaultUser();
    }
    
    try {
        postsData = await fetchPosts(userData.id);
    } catch (error) {
        console.error('Posts fetch failed:', error);
        postsData = [];
    }
    
    return {user: userData, posts: postsData};
}

// finally block
async function withFinally() {
    const loading = showLoader();
    
    try {
        const data = await fetchData();
        return data;
    } catch (error) {
        showError(error);
        throw error;
    } finally {
        hideLoader(loading); // Always runs
    }
}

// Async arrow functions
const asyncArrow = async () => {
    const result = await someAsyncOperation();
    return result;
};

// Async methods
class DataService {
    async fetchData() {
        const response = await fetch('/api/data');
        return response.json();
    }
    
    async saveData(data) {
        const response = await fetch('/api/data', {
            method: 'POST',
            body: JSON.stringify(data)
        });
        return response.json();
    }
}

// Async IIFE
(async () => {
    const data = await fetchData();
    console.log(data);
})();

// Top-level await (ES2022, in modules)
// const data = await fetchData(); // No async function needed

// Await non-Promise values (wrapped automatically)
async function awaitValues() {
    const a = await 42; // Promise.resolve(42)
    const b = await Promise.resolve(10);
    return a + b; // 52
}

// Error handling without try/catch (propagates)
async function propagateError() {
    const data = await fetchData(); // If rejected, function rejects
    return data;
}

propagateError()
    .then(data => console.log(data))
    .catch(error => console.error(error));

// Timeout with async/await
async function withTimeout(promise, timeout) {
    return Promise.race([
        promise,
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error('Timeout')), timeout)
        )
    ]);
}

// Usage
try {
    const data = await withTimeout(fetchData(), 5000);
} catch (error) {
    if (error.message === 'Timeout') {
        console.error('Request timed out');
    }
}

// Retry logic
async function retry(fn, maxAttempts = 3, delay = 1000) {
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
            return await fn();
        } catch (error) {
            if (attempt === maxAttempts) throw error;
            console.log(`Attempt ${attempt} failed, retrying...`);
            await new Promise(resolve => setTimeout(resolve, delay));
        }
    }
}

// Usage
const data = await retry(() => fetchData(), 3, 2000);
Warning: await only works inside async functions (except top-level await in modules). Sequential awaits block execution - use Promise.all() for parallel. Errors propagate unless caught.

14.4 Promise Combinators (all, race, allSettled, any)

Combinator Resolves When Rejects When Use Case
Promise.all() All promises fulfill Any promise rejects (fail-fast) Parallel operations, all required
Promise.race() First promise fulfills First promise rejects Timeouts, fastest response
Promise.allSettled() ES2020 All promises settle (fulfill or reject) Never rejects Independent operations, all results needed
Promise.any() ES2021 First promise fulfills All promises reject (AggregateError) First successful result, fallbacks

Example: Promise combinators

// Promise.all - wait for all (fail-fast)
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
    .then(results => {
        console.log(results); // [1, 2, 3]
    });

// With async/await
async function fetchAll() {
    const [user, posts, comments] = await Promise.all([
        fetch('/api/user').then(r => r.json()),
        fetch('/api/posts').then(r => r.json()),
        fetch('/api/comments').then(r => r.json())
    ]);
    
    return {user, posts, comments};
}

// Promise.all fails fast
Promise.all([
    Promise.resolve(1),
    Promise.reject(new Error('Failed!')),
    Promise.resolve(3) // Never executes
])
.catch(error => {
    console.error(error); // Error: Failed!
});

// Promise.race - first to settle wins
Promise.race([
    fetch('/api/fast'),
    fetch('/api/slow')
])
.then(result => {
    console.log('Fastest response:', result);
});

// Timeout with race
async function fetchWithTimeout(url, timeout) {
    return Promise.race([
        fetch(url),
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error('Timeout')), timeout)
        )
    ]);
}

try {
    const response = await fetchWithTimeout('/api/data', 5000);
} catch (error) {
    console.error('Request timed out or failed');
}

// Promise.allSettled - wait for all, never rejects
Promise.allSettled([
    Promise.resolve(1),
    Promise.reject(new Error('Failed')),
    Promise.resolve(3)
])
.then(results => {
    console.log(results);
    // [
    //   {status: 'fulfilled', value: 1},
    //   {status: 'rejected', reason: Error: Failed},
    //   {status: 'fulfilled', value: 3}
    // ]
});

// Process results
async function fetchMultiple(urls) {
    const promises = urls.map(url => fetch(url).then(r => r.json()));
    const results = await Promise.allSettled(promises);
    
    const successful = results
        .filter(r => r.status === 'fulfilled')
        .map(r => r.value);
    
    const failed = results
        .filter(r => r.status === 'rejected')
        .map(r => r.reason);
    
    return {successful, failed};
}

// Promise.any - first success (ES2021)
Promise.any([
    fetch('/api/server1'),
    fetch('/api/server2'),
    fetch('/api/server3')
])
.then(response => {
    console.log('First successful response:', response);
})
.catch(error => {
    console.error('All requests failed:', error);
});

// Fallback pattern with any
async function fetchWithFallback(urls) {
    try {
        return await Promise.any(
            urls.map(url => fetch(url).then(r => r.json()))
        );
    } catch (error) {
        // AggregateError - all promises rejected
        console.error('All sources failed:', error.errors);
        throw new Error('No data available');
    }
}

// Compare: race vs any
// race - first to settle (fulfill or reject)
Promise.race([
    Promise.reject(new Error('Fast fail')),
    Promise.resolve('Slow success')
])
.catch(error => console.error(error)); // Fast fail wins

// any - first to fulfill (ignores rejections until all fail)
Promise.any([
    Promise.reject(new Error('Fast fail')),
    Promise.resolve('Slow success')
])
.then(result => console.log(result)); // Slow success wins

// Practical examples

// 1. Parallel data fetching with all
async function loadPage() {
    const [header, content, sidebar] = await Promise.all([
        fetchHeader(),
        fetchContent(),
        fetchSidebar()
    ]);
    
    renderPage(header, content, sidebar);
}

// 2. Progress tracking with allSettled
async function batchProcess(items) {
    const promises = items.map(item => processItem(item));
    const results = await Promise.allSettled(promises);
    
    const successful = results.filter(r => r.status === 'fulfilled').length;
    const failed = results.filter(r => r.status === 'rejected').length;
    
    console.log(`Processed: ${successful} success, ${failed} failed`);
    return results;
}

// 3. Request racing
async function fastestSource(sources) {
    return Promise.race(
        sources.map(source => fetch(source).then(r => r.json()))
    );
}

// 4. Redundant requests with any
async function reliableFetch(mirrors) {
    try {
        return await Promise.any(
            mirrors.map(mirror => fetch(mirror).then(r => r.json()))
        );
    } catch (aggregateError) {
        console.error('All mirrors failed:', aggregateError.errors);
        throw new Error('Service unavailable');
    }
}

// 5. Mixed operations with allSettled
async function initializeApp() {
    const results = await Promise.allSettled([
        loadCriticalData(),     // Must succeed
        loadUserPreferences(),   // Optional
        loadAnalytics(),         // Optional
        connectWebSocket()       // Optional
    ]);
    
    // Handle critical failure
    if (results[0].status === 'rejected') {
        throw new Error('Critical initialization failed');
    }
    
    // Continue with partial data
    return processResults(results);
}

// Empty array behavior
Promise.all([]); // Resolves to []
Promise.race([]); // Never settles (hangs forever!)
Promise.allSettled([]); // Resolves to []
Promise.any([]); // Rejects with AggregateError

// Non-promise values (auto-wrapped)
Promise.all([1, 2, Promise.resolve(3)])
    .then(results => console.log(results)); // [1, 2, 3]

// Combining combinators
async function complexOperation() {
    // First get fastest mirror
    const data = await Promise.race([
        fetch('/mirror1'),
        fetch('/mirror2')
    ]);
    
    // Then fetch related data in parallel
    const [details, related] = await Promise.all([
        fetch(`/details/${data.id}`),
        fetch(`/related/${data.id}`)
    ]);
    
    return {data, details, related};
}
Note: Promise.all() fails fast (first rejection). Promise.allSettled() never rejects. Promise.race() first to settle. Promise.any() first to fulfill. Empty array in race() hangs forever!

14.5 Event Loop and Task Queue Management

Component Description Priority Examples
Call Stack Synchronous code execution; LIFO (Last In First Out) Highest Function calls, variable assignments
Microtask Queue High priority async tasks; runs after current task, before macrotasks High Promise callbacks, queueMicrotask
Macrotask Queue Lower priority async tasks; one per event loop tick Normal setTimeout, setInterval, I/O
Animation Frame Visual updates before next repaint; ~60fps Special requestAnimationFrame
Event Loop Orchestrates execution: call stack → microtasks → render → macrotask - JavaScript runtime

Example: Event loop execution order

// Execution order demonstration
console.log('1: Synchronous');

setTimeout(() => {
    console.log('2: Macrotask (setTimeout)');
}, 0);

Promise.resolve().then(() => {
    console.log('3: Microtask (Promise)');
});

console.log('4: Synchronous');

// Output order:
// 1: Synchronous
// 4: Synchronous
// 3: Microtask (Promise)
// 2: Macrotask (setTimeout)

// Event loop phases:
// 1. Execute all synchronous code (call stack)
// 2. Execute all microtasks
// 3. Render (if needed)
// 4. Execute one macrotask
// 5. Repeat from step 2

// Complex example
console.log('Start');

setTimeout(() => {
    console.log('Timeout 1');
    Promise.resolve().then(() => console.log('Promise 1'));
}, 0);

Promise.resolve().then(() => {
    console.log('Promise 2');
    setTimeout(() => console.log('Timeout 2'), 0);
});

console.log('End');

// Output:
// Start
// End
// Promise 2
// Timeout 1
// Promise 1
// Timeout 2

// Microtasks drain completely before next macrotask
Promise.resolve().then(() => {
    console.log('Microtask 1');
    Promise.resolve().then(() => {
        console.log('Microtask 2');
        Promise.resolve().then(() => {
            console.log('Microtask 3');
        });
    });
});

setTimeout(() => console.log('Macrotask'), 0);

// Output:
// Microtask 1
// Microtask 2
// Microtask 3
// Macrotask (only after ALL microtasks complete)

// queueMicrotask API
queueMicrotask(() => {
    console.log('Microtask via queueMicrotask');
});

Promise.resolve().then(() => {
    console.log('Microtask via Promise');
});

// Both have same priority (microtasks)

// Blocking the event loop (BAD!)
function blockFor(ms) {
    const start = Date.now();
    while (Date.now() - start < ms) {
        // Blocks event loop - nothing else can run!
    }
}

console.log('Before block');
blockFor(3000); // Blocks for 3 seconds
console.log('After block');

// setTimeout won't execute during block
setTimeout(() => console.log('Delayed'), 0);

// Call stack visualization
function first() {
    console.log('First function');
    second();
    console.log('First function end');
}

function second() {
    console.log('Second function');
    third();
    console.log('Second function end');
}

function third() {
    console.log('Third function');
}

first();

// Call stack:
// first() pushed
// second() pushed
// third() pushed
// third() popped (executed)
// second() popped (executed)
// first() popped (executed)

// Async breaks out of call stack
function asyncFirst() {
    console.log('Async first');
    
    setTimeout(() => {
        console.log('Async second (later)');
    }, 0);
    
    console.log('Async first end');
}

asyncFirst();
// Output:
// Async first
// Async first end
// Async second (later) - after event loop cycle

// Infinite microtask (BAD! - blocks rendering)
function infiniteMicrotasks() {
    Promise.resolve().then(() => {
        console.log('Microtask');
        infiniteMicrotasks(); // Never lets event loop continue!
    });
}

// DON'T DO THIS - page will freeze
// infiniteMicrotasks();

// Task priorities
async function taskPriorities() {
    console.log('1: Sync');
    
    // Macrotask
    setTimeout(() => console.log('5: Macrotask'), 0);
    
    // Microtask (Promise)
    Promise.resolve().then(() => console.log('3: Microtask Promise'));
    
    // Microtask (queueMicrotask)
    queueMicrotask(() => console.log('4: Microtask queue'));
    
    // Sync await
    await Promise.resolve();
    console.log('2: After await (microtask)');
}

taskPriorities();

// Long-running task splitting
function processLargeArray(items) {
    let index = 0;
    
    function processChunk() {
        const chunkSize = 100;
        const end = Math.min(index + chunkSize, items.length);
        
        for (; index < end; index++) {
            // Process item
            processItem(items[index]);
        }
        
        if (index < items.length) {
            // Schedule next chunk (lets event loop breathe)
            setTimeout(processChunk, 0);
        } else {
            console.log('Processing complete');
        }
    }
    
    processChunk();
}

// Yielding to event loop
async function yieldToEventLoop() {
    await new Promise(resolve => setTimeout(resolve, 0));
}

async function longTask() {
    for (let i = 0; i < 1000; i++) {
        doWork(i);
        
        // Yield every 100 iterations
        if (i % 100 === 0) {
            await yieldToEventLoop();
        }
    }
}

// Measuring event loop lag
let lastTime = Date.now();

setInterval(() => {
    const now = Date.now();
    const lag = now - lastTime - 100; // Expected: 100ms
    
    if (lag > 10) {
        console.warn(`Event loop lag: ${lag}ms`);
    }
    
    lastTime = now;
}, 100);

// Web Worker for heavy computation (doesn't block main thread)
const worker = new Worker('worker.js');

worker.postMessage({task: 'heavy-computation', data: largeDataset});

worker.onmessage = (event) => {
    console.log('Result from worker:', event.data);
};
Warning: All microtasks execute before next macrotask - can block rendering! Don't block event loop with long sync operations. Use Web Workers for heavy computation. Infinite microtasks freeze the page.

14.6 Microtasks vs Macrotasks Execution Order

Type APIs Execution Timing Characteristics
Microtasks Promise.then/catch/finally, queueMicrotask, async/await, MutationObserver After current task, before render All drain before next macrotask; high priority
Macrotasks setTimeout, setInterval, setImmediate (Node), I/O, UI rendering, postMessage Next event loop tick One per tick; lower priority
Render Steps requestAnimationFrame, style calc, layout, paint After microtasks, before next macrotask Browser optimization; ~60fps

Example: Microtask vs macrotask behavior

// Classic example
console.log('Script start');

setTimeout(() => console.log('setTimeout'), 0);

Promise.resolve()
    .then(() => console.log('Promise 1'))
    .then(() => console.log('Promise 2'));

console.log('Script end');

// Output order:
// Script start
// Script end
// Promise 1
// Promise 2
// setTimeout

// Why?
// 1. Sync code executes: "Script start", "Script end"
// 2. Call stack empty → drain microtask queue: "Promise 1", "Promise 2"
// 3. Microtasks done → execute one macrotask: "setTimeout"

// Nested example
setTimeout(() => {
    console.log('Timeout 1');
    
    Promise.resolve().then(() => {
        console.log('Promise in Timeout 1');
    });
    
    setTimeout(() => {
        console.log('Timeout 2');
    }, 0);
}, 0);

Promise.resolve().then(() => {
    console.log('Promise 1');
    
    setTimeout(() => {
        console.log('Timeout in Promise 1');
    }, 0);
});

// Output:
// Promise 1
// Timeout 1
// Promise in Timeout 1
// Timeout in Promise 1
// Timeout 2

// Detailed breakdown:
// Initial: microtask queue = [Promise 1], macrotask queue = [Timeout 1]
// Execute microtasks: "Promise 1" (adds Timeout in Promise 1 to macrotasks)
// Execute macrotask: "Timeout 1" (adds Promise in Timeout 1, Timeout 2)
// Execute microtasks: "Promise in Timeout 1"
// Execute macrotask: "Timeout in Promise 1"
// Execute macrotask: "Timeout 2"

// queueMicrotask vs setTimeout
console.log('Start');

setTimeout(() => console.log('Macro 1'), 0);
queueMicrotask(() => console.log('Micro 1'));

setTimeout(() => console.log('Macro 2'), 0);
queueMicrotask(() => console.log('Micro 2'));

console.log('End');

// Output:
// Start
// End
// Micro 1
// Micro 2
// Macro 1
// Macro 2

// Promise chaining creates microtasks
Promise.resolve()
    .then(() => {
        console.log('Then 1');
        return Promise.resolve(); // Creates microtask
    })
    .then(() => console.log('Then 2'));

setTimeout(() => console.log('Timeout'), 0);

// Output:
// Then 1
// Then 2
// Timeout

// async/await creates microtasks
async function example() {
    console.log('Async start');
    
    await Promise.resolve(); // Creates microtask
    console.log('After await');
}

example();
console.log('Sync');

// Output:
// Async start
// Sync
// After await

// Microtask queue never empties if you keep adding
let count = 0;

function scheduleMicrotask() {
    if (count < 5) {
        queueMicrotask(() => {
            console.log(`Microtask ${++count}`);
            scheduleMicrotask(); // Schedule another
        });
    }
}

scheduleMicrotask();

setTimeout(() => console.log('Timeout'), 0);

// Output:
// Microtask 1
// Microtask 2
// Microtask 3
// Microtask 4
// Microtask 5
// Timeout (only after all microtasks)

// Rendering blocked by microtasks
button.addEventListener('click', () => {
    // Update UI
    element.textContent = 'Processing...';
    
    // This microtask chain blocks rendering
    Promise.resolve()
        .then(() => heavyWork1())
        .then(() => heavyWork2())
        .then(() => heavyWork3());
    
    // User won't see "Processing..." until all work done!
});

// Better: use setTimeout to allow render
button.addEventListener('click', () => {
    element.textContent = 'Processing...';
    
    // Allow render before processing
    setTimeout(() => {
        Promise.resolve()
            .then(() => heavyWork1())
            .then(() => heavyWork2())
            .then(() => heavyWork3())
            .then(() => {
                element.textContent = 'Done!';
            });
    }, 0);
});

// Order comparison
function orderTest() {
    console.log('1');
    
    setTimeout(() => console.log('2'), 0);
    
    Promise.resolve().then(() => console.log('3'));
    
    queueMicrotask(() => console.log('4'));
    
    (async () => {
        console.log('5');
        await null;
        console.log('6');
    })();
    
    console.log('7');
}

orderTest();
// Output: 1, 5, 7, 3, 4, 6, 2

// MutationObserver (microtask)
const observer = new MutationObserver(() => {
    console.log('DOM changed (microtask)');
});

observer.observe(document.body, {childList: true});

console.log('Before mutation');
document.body.appendChild(document.createElement('div'));
console.log('After mutation');

setTimeout(() => console.log('Timeout'), 0);

// Output:
// Before mutation
// After mutation
// DOM changed (microtask)
// Timeout

// Event handlers are macrotasks
button.addEventListener('click', () => {
    console.log('Click handler (macrotask)');
    
    Promise.resolve().then(() => {
        console.log('Promise in handler (microtask)');
    });
});

// When button clicked:
// Click handler (macrotask)
// Promise in handler (microtask)

// setImmediate (Node.js only) vs setTimeout
// setImmediate: executes after I/O events
// setTimeout(0): executes in next timer phase

// Practical: debouncing with microtasks
let pending = false;

function debounceMicrotask(fn) {
    if (!pending) {
        pending = true;
        queueMicrotask(() => {
            pending = false;
            fn();
        });
    }
}

// Multiple calls in same task execute once
debounceMicrotask(() => console.log('Called'));
debounceMicrotask(() => console.log('Called'));
debounceMicrotask(() => console.log('Called'));
// Output: "Called" (only once)

// Comparison table
const examples = {
    microtask: () => Promise.resolve().then(() => console.log('micro')),
    macrotask: () => setTimeout(() => console.log('macro'), 0),
    sync: () => console.log('sync')
};

examples.sync();      // Executes immediately
examples.microtask(); // Next (after sync)
examples.macrotask(); // Last (next tick)

// Output: sync, micro, macro
Note: Microtasks execute before rendering and next macrotask. All microtasks drain before one macrotask. Use macrotasks (setTimeout) to allow rendering. Promise callbacks are microtasks.

14.7 Timer Functions (setTimeout, setInterval, requestAnimationFrame)

Function Syntax Description Use Case
setTimeout setTimeout(fn, ms, ...args) Execute once after delay; returns timer ID Delays, debouncing, async breaks
clearTimeout clearTimeout(id) Cancel setTimeout before execution Cancel pending timers
setInterval setInterval(fn, ms, ...args) Execute repeatedly at interval; returns timer ID Polling, periodic updates
clearInterval clearInterval(id) Stop setInterval execution Stop periodic timers
requestAnimationFrame requestAnimationFrame(fn) Execute before next repaint; ~60fps; returns ID Smooth animations, visual updates
cancelAnimationFrame cancelAnimationFrame(id) Cancel pending animation frame Stop animations

Example: Timer functions

// setTimeout - execute once
setTimeout(() => {
    console.log('Executed after 1 second');
}, 1000);

// With arguments
setTimeout((name, age) => {
    console.log(`${name} is ${age}`);
}, 1000, 'Alice', 30);

// Return value is timer ID
const timerId = setTimeout(() => {
    console.log('This might not execute');
}, 1000);

// Cancel before execution
clearTimeout(timerId);

// Minimum delay is ~4ms (browser throttling)
setTimeout(() => console.log('Actually ~4ms'), 0);

// setInterval - execute repeatedly
const intervalId = setInterval(() => {
    console.log('Every second');
}, 1000);

// Stop after 5 seconds
setTimeout(() => {
    clearInterval(intervalId);
    console.log('Interval stopped');
}, 5000);

// ⚠️ setInterval drift problem
let count = 0;
setInterval(() => {
    console.log(`Count: ${++count}`);
    // If this takes 50ms, next call is 950ms away
    // Drift accumulates over time!
}, 1000);

// Better: recursive setTimeout (self-correcting)
let startTime = Date.now();
let count = 0;

function accurateInterval() {
    count++;
    console.log(`Count: ${count}`);
    
    // Calculate next delay to maintain accuracy
    const elapsed = Date.now() - startTime;
    const nextDelay = (count * 1000) - elapsed;
    
    setTimeout(accurateInterval, Math.max(0, nextDelay));
}

accurateInterval();

// Simple recursive setTimeout
function repeat() {
    console.log('Every second');
    setTimeout(repeat, 1000);
}

repeat();

// requestAnimationFrame - smooth animations
function animate() {
    // Update animation
    element.style.left = position + 'px';
    position += 1;
    
    // Continue animation
    if (position < 500) {
        requestAnimationFrame(animate);
    }
}

requestAnimationFrame(animate);

// RAF with timestamp
function animateWithTime(timestamp) {
    console.log('Time since page load:', timestamp);
    
    // Smooth animation based on time
    const progress = timestamp / 1000; // seconds
    element.style.left = (progress * 100) + 'px';
    
    if (progress < 5) {
        requestAnimationFrame(animateWithTime);
    }
}

requestAnimationFrame(animateWithTime);

// Delta time for frame-independent animation
let lastTime = 0;

function gameLoop(currentTime) {
    const deltaTime = currentTime - lastTime;
    lastTime = currentTime;
    
    // Move based on time elapsed (60fps = ~16ms)
    position += speed * (deltaTime / 1000);
    
    render();
    requestAnimationFrame(gameLoop);
}

requestAnimationFrame(gameLoop);

// Cancel animation
const rafId = requestAnimationFrame(animate);
cancelAnimationFrame(rafId);

// Debouncing with setTimeout
function debounce(fn, delay) {
    let timeoutId;
    
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn.apply(this, args), delay);
    };
}

// Usage
const debouncedSearch = debounce((query) => {
    console.log('Searching for:', query);
}, 300);

input.addEventListener('input', (e) => {
    debouncedSearch(e.target.value);
});

// Throttling with setTimeout
function throttle(fn, delay) {
    let lastCall = 0;
    
    return function(...args) {
        const now = Date.now();
        
        if (now - lastCall >= delay) {
            lastCall = now;
            fn.apply(this, args);
        }
    };
}

// Usage
const throttledScroll = throttle(() => {
    console.log('Scroll handler');
}, 100);

window.addEventListener('scroll', throttledScroll);

// Polling with setInterval
function poll(fn, interval, maxAttempts) {
    let attempts = 0;
    
    const intervalId = setInterval(() => {
        attempts++;
        
        if (fn() || attempts >= maxAttempts) {
            clearInterval(intervalId);
        }
    }, interval);
}

// Check if element exists
poll(() => {
    const element = document.querySelector('.dynamic-element');
    if (element) {
        console.log('Element found!');
        return true;
    }
    return false;
}, 100, 50); // Check every 100ms, max 50 times

// Sleep function
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// Usage with async/await
async function example() {
    console.log('Start');
    await sleep(1000);
    console.log('After 1 second');
    await sleep(1000);
    console.log('After 2 seconds');
}

// Timeout promise
function timeout(ms) {
    return new Promise((_, reject) =>
        setTimeout(() => reject(new Error('Timeout')), ms)
    );
}

// Race against timeout
async function fetchWithTimeout(url, ms) {
    return Promise.race([
        fetch(url),
        timeout(ms)
    ]);
}

// Countdown timer
function countdown(seconds, callback) {
    let remaining = seconds;
    
    const intervalId = setInterval(() => {
        console.log(`${remaining} seconds remaining`);
        remaining--;
        
        if (remaining < 0) {
            clearInterval(intervalId);
            callback();
        }
    }, 1000);
    
    return intervalId; // Return for cancellation
}

countdown(5, () => console.log('Done!'));

// Animation loop comparison
// ❌ Bad: setInterval (not synced with screen refresh)
setInterval(() => {
    updateAnimation();
}, 16); // Tries for 60fps but not precise

// ✓ Good: requestAnimationFrame (synced with refresh)
function animationLoop() {
    updateAnimation();
    requestAnimationFrame(animationLoop);
}
requestAnimationFrame(animationLoop);

// Background tab throttling
// Browsers throttle timers in background tabs to save resources
setTimeout(() => {
    console.log('Might be delayed if tab inactive');
}, 1000);

// RAF stops in background tabs
requestAnimationFrame(() => {
    console.log('Only runs when tab visible');
});

// Immediate execution alternative
// setTimeout(fn, 0) vs setImmediate (Node.js)
// vs queueMicrotask

setTimeout(() => console.log('setTimeout 0'), 0); // Macrotask
queueMicrotask(() => console.log('queueMicrotask')); // Microtask

// Output: queueMicrotask, setTimeout 0

// Cleanup pattern
class Component {
    constructor() {
        this.timers = new Set();
    }
    
    setTimeout(fn, delay) {
        const id = setTimeout(() => {
            this.timers.delete(id);
            fn();
        }, delay);
        
        this.timers.add(id);
        return id;
    }
    
    destroy() {
        // Clear all timers
        this.timers.forEach(id => clearTimeout(id));
        this.timers.clear();
    }
}

// Accurate interval with RAF
function setIntervalRAF(callback, interval) {
    let lastTime = performance.now();
    
    function loop(currentTime) {
        const elapsed = currentTime - lastTime;
        
        if (elapsed >= interval) {
            lastTime = currentTime;
            callback();
        }
        
        requestAnimationFrame(loop);
    }
    
    const id = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(id);
}

// Usage
const cancel = setIntervalRAF(() => {
    console.log('Every 1000ms with RAF');
}, 1000);

// Later: cancel();
Warning: setInterval can drift and overlap if callback takes longer than interval. Background tabs throttle timers. Use requestAnimationFrame for animations. Minimum setTimeout delay ~4ms.

Section 14 Summary

  • Callbacks: Error-first pattern (err, result); handle errors inside async code; avoid callback hell (pyramid of doom); extract named functions; callbacks execute once
  • Promises: Three states (pending/fulfilled/rejected); then/catch/finally chainable; return value wrapped in Promise; errors bubble to nearest catch; Promise.resolve/reject create settled Promises
  • async/await: async functions return Promise; await pauses until Promise settles; try/catch for error handling; sequential await blocks execution; use Promise.all for parallel
  • Combinators: Promise.all fail-fast (all required); Promise.race first to settle; Promise.allSettled never rejects; Promise.any first to fulfill (ES2021)
  • Event Loop: Call stack → microtasks → render → one macrotask → repeat; microtasks drain completely before next macrotask; don't block event loop; split long tasks
  • Micro/Macro: Microtasks: Promises, queueMicrotask (high priority); Macrotasks: setTimeout, setInterval (lower priority); all microtasks before next macrotask; can block rendering
  • Timers: setTimeout once; setInterval repeats (can drift); requestAnimationFrame for animations (~60fps); use recursive setTimeout for accuracy; background tabs throttled

15. Modern ES6+ Features and Syntax

15.1 Destructuring Assignment (array, object, nested)

Type Syntax Description Example
Array Destructuring [a, b] = array Extract array elements into variables [x, y] = [1, 2]
Object Destructuring {a, b} = obj Extract object properties into variables {name, age} = user
Skip Elements [a, , c] = array Skip unwanted array elements [x, , z] = [1, 2, 3]
Rest Elements [a, ...rest] = array Collect remaining elements [first, ...others] = [1,2,3]
Default Values [a = 0] = array Fallback if undefined {name = 'Guest'} = obj
Rename Properties {a: newName} = obj Assign to different variable name {name: userName} = obj
Nested Destructuring {a: {b}} = obj Extract from nested structures {user: {name}} = data
Mixed Destructuring {a, b: [c, d]} = obj Combine object and array destructuring {items: [first]} = obj

Example: Destructuring patterns

// Array destructuring
const arr = [1, 2, 3, 4, 5];

const [first, second] = arr;
console.log(first, second); // 1, 2

const [a, b, c] = arr;
console.log(a, b, c); // 1, 2, 3

// Skip elements
const [x, , z] = arr;
console.log(x, z); // 1, 3

// Rest elements (...) must be last
const [head, ...tail] = arr;
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

// Default values
const [p = 10, q = 20] = [5];
console.log(p, q); // 5, 20

// Swap variables
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n); // 2, 1

// Object destructuring
const user = {name: 'Alice', age: 30, city: 'NYC'};

const {name, age} = user;
console.log(name, age); // 'Alice', 30

// Rename variables
const {name: userName, age: userAge} = user;
console.log(userName, userAge); // 'Alice', 30

// Default values
const {name, country = 'USA'} = user;
console.log(country); // 'USA' (not in user object)

// Rest properties
const {name, ...rest} = user;
console.log(rest); // {age: 30, city: 'NYC'}

// Nested destructuring
const data = {
    user: {
        name: 'Bob',
        address: {
            city: 'LA',
            zip: '90001'
        }
    },
    items: [1, 2, 3]
};

const {user: {name, address: {city}}} = data;
console.log(name, city); // 'Bob', 'LA'

// Array within object
const {items: [firstItem, secondItem]} = data;
console.log(firstItem, secondItem); // 1, 2

// Object within array
const users = [
    {name: 'Alice', age: 30},
    {name: 'Bob', age: 25}
];

const [{name: firstName}, {age: secondAge}] = users;
console.log(firstName, secondAge); // 'Alice', 25

// Function parameters
function greet({name, age = 18}) {
    console.log(`${name} is ${age}`);
}

greet({name: 'Alice', age: 30}); // Alice is 30
greet({name: 'Bob'}); // Bob is 18

// Array parameters
function sum([a, b, c = 0]) {
    return a + b + c;
}

sum([1, 2, 3]); // 6
sum([1, 2]); // 3 (c defaults to 0)

// Computed property names
const key = 'name';
const {[key]: value} = {name: 'Alice'};
console.log(value); // 'Alice'

// Destructuring in loops
const points = [
    {x: 10, y: 20},
    {x: 30, y: 40}
];

for (const {x, y} of points) {
    console.log(x, y);
}

// Destructuring returned arrays
function getCoords() {
    return [10, 20];
}

const [xCoord, yCoord] = getCoords();

// Destructuring returned objects
function getUser() {
    return {name: 'Alice', age: 30};
}

const {name: n, age: a} = getUser();

// Mixed destructuring with defaults
const config = {
    host: 'localhost',
    port: 8080,
    options: {
        timeout: 3000,
        retries: 3
    }
};

const {
    host = '0.0.0.0',
    port = 80,
    options: {
        timeout = 5000,
        retries = 5,
        cache = true
    } = {}
} = config;

// Destructuring with rest and rename
const person = {
    firstName: 'John',
    lastName: 'Doe',
    age: 30,
    city: 'NYC'
};

const {
    firstName: fName,
    lastName: lName,
    ...details
} = person;

console.log(fName, lName); // 'John', 'Doe'
console.log(details); // {age: 30, city: 'NYC'}

// Ignoring values
const [, , third] = [1, 2, 3, 4];
console.log(third); // 3

// Destructuring with existing variables
let existing1, existing2;
[existing1, existing2] = [10, 20];

// Must use parentheses for objects
let e1, e2;
({e1, e2} = {e1: 10, e2: 20}); // Parens required!

// Failed matches = undefined
const {nonExistent} = {};
console.log(nonExistent); // undefined

const [missing] = [];
console.log(missing); // undefined

// Practical: extracting data from API response
const response = {
    data: {
        user: {
            id: 123,
            profile: {
                name: 'Alice',
                email: 'alice@example.com'
            }
        }
    },
    meta: {
        timestamp: Date.now()
    }
};

const {
    data: {
        user: {
            id: userId,
            profile: {name: displayName, email}
        }
    }
} = response;

console.log(userId, displayName, email);
Note: Destructuring works with any iterable (arrays, strings, Maps, Sets). Use ... for rest (must be last). Defaults only apply for undefined (not null). Object destructuring needs parentheses for existing variables.

15.2 Spread and Rest Syntax Applications

Context Operator Purpose Example
Spread (Arrays) [...arr] Expand array elements [...arr1, ...arr2]
Spread (Objects) {...obj} Expand object properties {...obj1, ...obj2}
Spread (Function Calls) fn(...args) Pass array as individual arguments Math.max(...numbers)
Rest (Parameters) (...args) Collect remaining arguments into array function f(...args)
Rest (Destructuring) [a, ...rest] Collect remaining elements/properties {x, ...rest} = obj

Example: Spread and rest usage

// Spread in array literals
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]

const withMiddle = [...arr1, 'middle', ...arr2];
console.log(withMiddle); // [1, 2, 3, 'middle', 4, 5, 6]

// Array cloning (shallow)
const original = [1, 2, 3];
const copy = [...original];
copy.push(4); // Doesn't affect original

// Spread strings
const str = 'hello';
const chars = [...str];
console.log(chars); // ['h', 'e', 'l', 'l', 'o']

// Spread with Sets (removes duplicates)
const arr = [1, 2, 2, 3, 3, 4];
const unique = [...new Set(arr)];
console.log(unique); // [1, 2, 3, 4]

// Spread in function calls
const numbers = [5, 1, 9, 3, 7];

Math.max(...numbers); // 9 (instead of Math.max(5, 1, 9, 3, 7))
console.log(...numbers); // 5 1 9 3 7

// Spread objects (shallow merge)
const obj1 = {a: 1, b: 2};
const obj2 = {b: 3, c: 4};

const merged = {...obj1, ...obj2};
console.log(merged); // {a: 1, b: 3, c: 4} (obj2.b overwrites obj1.b)

// Adding/overriding properties
const user = {name: 'Alice', age: 30};
const updated = {...user, age: 31, city: 'NYC'};
console.log(updated); // {name: 'Alice', age: 31, city: 'NYC'}

// Object cloning (shallow)
const originalObj = {a: 1, b: {c: 2}};
const clonedObj = {...originalObj};
clonedObj.b.c = 3; // Affects original! (nested objects not cloned)

// Rest parameters (collect into array)
function sum(...numbers) {
    return numbers.reduce((acc, n) => acc + n, 0);
}

sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15

// Rest must be last parameter
function logAll(first, second, ...rest) {
    console.log(first); // 1
    console.log(second); // 2
    console.log(rest); // [3, 4, 5]
}

logAll(1, 2, 3, 4, 5);

// Rest in destructuring
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

const {x, y, ...rest} = {x: 1, y: 2, z: 3, w: 4};
console.log(x, y); // 1, 2
console.log(rest); // {z: 3, w: 4}

// Combining spread and rest
function multiply(multiplier, ...numbers) {
    return numbers.map(n => n * multiplier);
}

const nums = [1, 2, 3];
multiply(2, ...nums); // [2, 4, 6]

// Converting arguments to array (old way vs new)
function oldWay() {
    const args = Array.prototype.slice.call(arguments);
    return args;
}

function newWay(...args) {
    return args;
}

// Spread with iterables
const map = new Map([['a', 1], ['b', 2]]);
const mapArr = [...map];
console.log(mapArr); // [['a', 1], ['b', 2]]

const set = new Set([1, 2, 3]);
const setArr = [...set];
console.log(setArr); // [1, 2, 3]

// Practical: remove item from array
const items = [1, 2, 3, 4, 5];
const index = 2;
const removed = [...items.slice(0, index), ...items.slice(index + 1)];
console.log(removed); // [1, 2, 4, 5]

// Practical: insert item
const insert = [...items.slice(0, index), 99, ...items.slice(index)];
console.log(insert); // [1, 2, 99, 3, 4, 5]

// Practical: conditional spread
const baseConfig = {host: 'localhost', port: 8080};
const isDev = true;

const config = {
    ...baseConfig,
    ...(isDev && {debug: true, verbose: true})
};
// If isDev: {host: 'localhost', port: 8080, debug: true, verbose: true}
// If !isDev: {host: 'localhost', port: 8080}

// Practical: merge with priority
function mergeOptions(defaults, user) {
    return {...defaults, ...user};
}

const options = mergeOptions(
    {timeout: 5000, retries: 3},
    {retries: 5} // Overrides defaults.retries
);

// Practical: flatten array one level
const nested = [[1, 2], [3, 4], [5]];
const flat = [].concat(...nested);
console.log(flat); // [1, 2, 3, 4, 5]

// Better: use flat()
const flatBetter = nested.flat();

// Spread with null/undefined (error)
// const spread = {...null}; // TypeError
// const spread = {...undefined}; // TypeError

// But arrays are OK
const arr = [...(someVar || [])]; // Safe fallback

// Order matters in objects
const a = {x: 1, y: 2};
const b = {y: 3, z: 4};

{...a, ...b}; // {x: 1, y: 3, z: 4} (b.y wins)
{...b, ...a}; // {x: 1, y: 2, z: 4} (a.y wins)

// Spread doesn't deep clone
const obj = {
    a: 1,
    nested: {b: 2}
};

const clone = {...obj};
clone.nested.b = 3; // Affects original!
console.log(obj.nested.b); // 3

// Deep clone requires recursion or library
const deepClone = JSON.parse(JSON.stringify(obj)); // Quick but limited

// Rest with default parameters
function greet(greeting = 'Hello', ...names) {
    return `${greeting} ${names.join(' and ')}`;
}

greet('Hi', 'Alice', 'Bob'); // "Hi Alice and Bob"
greet(undefined, 'Alice'); // "Hello Alice"
Warning: Spread/rest creates shallow copies (nested objects/arrays still referenced). Rest must be last parameter/element. Spread objects only copies enumerable own properties. Can't spread null/undefined.

15.3 Default Parameters and Parameter Destructuring

Feature Syntax Description Example
Default Value function f(a = default) Fallback if undefined (not null) f(x = 0)
Expression Default function f(a = expr()) Default can be expression; evaluated when needed f(x = Date.now())
Reference Previous function f(a, b = a) Later params can reference earlier ones f(x, y = x * 2)
Destructured Params function f({x, y}) Destructure object/array parameter f({x: 1, y: 2})
Destructure + Default function f({x = 0}) Defaults within destructuring f({x: 1} = {})

Example: Default parameters

// Basic default parameters
function greet(name = 'Guest') {
    return `Hello, ${name}!`;
}

greet('Alice'); // "Hello, Alice!"
greet(); // "Hello, Guest!"
greet(undefined); // "Hello, Guest!"
greet(null); // "Hello, null!" (null is NOT undefined!)

// Multiple defaults
function createUser(name = 'Anonymous', age = 18, active = true) {
    return {name, age, active};
}

createUser('Alice', 30); // {name: 'Alice', age: 30, active: true}
createUser('Bob'); // {name: 'Bob', age: 18, active: true}

// Expression as default (evaluated each call)
function log(message, timestamp = Date.now()) {
    console.log(`[${timestamp}] ${message}`);
}

log('First'); // [1702812345123] First
log('Second'); // [1702812345456] Second (different timestamp)

// Function call as default
function getDefault() {
    console.log('Getting default');
    return 42;
}

function withDefault(x = getDefault()) {
    return x;
}

withDefault(10); // 10 (getDefault not called)
withDefault(); // 42 (getDefault called, logs "Getting default")

// Reference previous parameters
function createRange(start = 0, end = start + 10) {
    return {start, end};
}

createRange(5); // {start: 5, end: 15}
createRange(); // {start: 0, end: 10}

// Parameters evaluated left-to-right
function multiply(a, b = a, c = a * b) {
    return c;
}

multiply(2); // 4 (b=2, c=2*2)
multiply(2, 3); // 6 (c=2*3)
multiply(2, 3, 4); // 4 (c explicitly set)

// Can't reference later parameters
function invalid(a = b, b = 1) { // ReferenceError!
    return a;
}

// Object destructuring parameters
function drawCircle({x, y, radius, color = 'black'}) {
    console.log(`Circle at (${x}, ${y}), r=${radius}, color=${color}`);
}

drawCircle({x: 10, y: 20, radius: 5}); 
// "Circle at (10, 20), r=5, color=black"

drawCircle({x: 0, y: 0, radius: 10, color: 'red'});
// "Circle at (0, 0), r=10, color=red"

// Default for entire parameter object
function config({host = 'localhost', port = 8080} = {}) {
    return {host, port};
}

config({host: '0.0.0.0'}); // {host: '0.0.0.0', port: 8080}
config(); // {host: 'localhost', port: 8080}

// Array destructuring parameters
function sum([a = 0, b = 0, c = 0]) {
    return a + b + c;
}

sum([1, 2, 3]); // 6
sum([1, 2]); // 3 (c defaults to 0)
sum([1]); // 1 (b and c default to 0)

// Nested destructuring with defaults
function createUser({
    name = 'Anonymous',
    profile: {
        age = 18,
        email = 'no-email'
    } = {}
} = {}) {
    return {name, age, email};
}

createUser({name: 'Alice', profile: {age: 30}});
// {name: 'Alice', age: 30, email: 'no-email'}

createUser({name: 'Bob'});
// {name: 'Bob', age: 18, email: 'no-email'}

createUser();
// {name: 'Anonymous', age: 18, email: 'no-email'}

// Rest with defaults
function logAll(first = 'default', ...rest) {
    console.log('First:', first);
    console.log('Rest:', rest);
}

logAll(); // First: default, Rest: []
logAll('a', 'b', 'c'); // First: a, Rest: ['b', 'c']

// Complex example: options object
function ajax(url, {
    method = 'GET',
    headers = {},
    body = null,
    timeout = 5000,
    retries = 3
} = {}) {
    console.log({url, method, headers, body, timeout, retries});
}

ajax('/api/data'); // All defaults
ajax('/api/data', {method: 'POST', body: '{}'});

// Default in arrow functions
const greet = (name = 'Guest') => `Hello, ${name}!`;

// Destructuring in arrow functions
const getArea = ({width = 0, height = 0}) => width * height;

getArea({width: 10, height: 20}); // 200
getArea({width: 10}); // 0 (height defaults to 0)

// Optional destructuring
function processData(data = {}) {
    const {name = 'Unknown', value = 0} = data;
    return {name, value};
}

processData(); // {name: 'Unknown', value: 0}
processData({name: 'Test'}); // {name: 'Test', value: 0}

// Default with computed properties
const key = 'port';
function server({[key]: p = 8080} = {}) {
    return p;
}

server({port: 3000}); // 3000
server(); // 8080

// Avoiding undefined vs null confusion
function safe(value = 'default') {
    return value ?? 'fallback'; // Use nullish coalescing
}

safe(); // 'default' (undefined)
safe(undefined); // 'default'
safe(null); // 'fallback' (null not undefined)
safe(0); // 0
safe(''); // ''

// Practical: API request options
function fetchData(url, {
    method = 'GET',
    cache = true,
    retries = 3,
    onSuccess = () => {},
    onError = console.error
} = {}) {
    // Implementation
}
Note: Defaults only apply when parameter is undefined (not null, 0, '', etc). Expressions evaluated on each call. Can reference previous params. Use = {} for destructured param defaults.

15.4 Enhanced Object Literals and Method Shorthand

Feature ES6 Syntax ES5 Equivalent Description
Property Shorthand {name, age} {name: name, age: age} Variable name as property name
Method Shorthand {method() {}} {method: function() {}} Concise method syntax
Computed Properties {[expr]: value} obj[expr] = value (after creation) Dynamic property names
Computed Methods {[expr]() {}} obj[expr] = function() {} Dynamic method names
Getter/Setter {get prop() {}, set prop(v) {}} Object.defineProperty Accessor properties

Example: Enhanced object literals

// Property shorthand
const name = 'Alice';
const age = 30;

// ES6
const user = {name, age};

// ES5 equivalent
const userOld = {name: name, age: age};

console.log(user); // {name: 'Alice', age: 30}

// Method shorthand
const calculator = {
    // ES6
    add(a, b) {
        return a + b;
    },
    subtract(a, b) {
        return a - b;
    },
    
    // ES5 equivalent
    multiply: function(a, b) {
        return a * b;
    }
};

calculator.add(5, 3); // 8

// Computed property names
const prop = 'dynamicKey';
const value = 42;

const obj = {
    [prop]: value,
    ['key' + 2]: 'value2',
    [`computed_${Date.now()}`]: 'timestamp'
};

console.log(obj.dynamicKey); // 42
console.log(obj.key2); // 'value2'

// Computed method names
const methodName = 'sayHello';

const greeter = {
    [methodName]() {
        return 'Hello!';
    },
    [`${methodName}Loud`]() {
        return 'HELLO!';
    }
};

greeter.sayHello(); // 'Hello!'
greeter.sayHelloLoud(); // 'HELLO!'

// Combining features
const id = 1;
const name = 'Product';
const price = 99.99;

const product = {
    id,
    name,
    price,
    
    // Method shorthand
    getInfo() {
        return `${this.name}: ${this.price}`;
    },
    
    // Computed property
    [`item_${id}`]: true,
    
    // Getter
    get displayPrice() {
        return `${this.price.toFixed(2)}`;
    },
    
    // Setter
    set discount(percent) {
        this.price = this.price * (1 - percent / 100);
    }
};

product.getInfo(); // "Product: $99.99"
product.displayPrice; // "$99.99"
product.discount = 10; // Sets price to 89.99

// Getters and setters
const person = {
    firstName: 'John',
    lastName: 'Doe',
    
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    },
    
    set fullName(name) {
        [this.firstName, this.lastName] = name.split(' ');
    }
};

person.fullName; // "John Doe"
person.fullName = 'Jane Smith';
console.log(person.firstName); // "Jane"

// Async methods
const api = {
    async fetchData() {
        const response = await fetch('/api/data');
        return response.json();
    },
    
    async* generateData() {
        for (let i = 0; i < 5; i++) {
            yield await fetchItem(i);
        }
    }
};

// Generator methods
const iterator = {
    *range(start, end) {
        for (let i = start; i < end; i++) {
            yield i;
        }
    }
};

for (const num of iterator.range(1, 5)) {
    console.log(num); // 1, 2, 3, 4
}

// Computed properties with symbols
const MY_KEY = Symbol('myKey');

const obj = {
    [MY_KEY]: 'hidden value',
    public: 'visible'
};

obj[MY_KEY]; // 'hidden value'

// Dynamic object building
function createUser(name, age, ...permissions) {
    return {
        name,
        age,
        permissions,
        
        hasPermission(perm) {
            return this.permissions.includes(perm);
        },
        
        [`is${name}`]: true
    };
}

const user = createUser('Admin', 30, 'read', 'write');
user.isAdmin; // true
user.hasPermission('read'); // true

// Mixing with spread
const defaults = {
    timeout: 5000,
    retries: 3
};

const config = {
    ...defaults,
    host: 'localhost',
    port: 8080,
    
    getUrl() {
        return `http://${this.host}:${this.port}`;
    }
};

// Nested enhanced literals
const data = {
    user: {
        name,
        age,
        
        getInfo() {
            return `${this.name}, ${this.age}`;
        }
    },
    
    metadata: {
        created: Date.now(),
        
        format() {
            return new Date(this.created).toISOString();
        }
    }
};

// __proto__ property (not recommended)
const parent = {
    greet() {
        return 'Hello from parent';
    }
};

const child = {
    __proto__: parent,
    
    greet() {
        return super.greet() + ' and child';
    }
};

child.greet(); // "Hello from parent and child"

// Practical: factory function
function createPoint(x, y) {
    return {
        x,
        y,
        
        distanceFrom(other) {
            const dx = this.x - other.x;
            const dy = this.y - other.y;
            return Math.sqrt(dx * dx + dy * dy);
        },
        
        toString() {
            return `(${this.x}, ${this.y})`;
        }
    };
}

const p1 = createPoint(0, 0);
const p2 = createPoint(3, 4);
p1.distanceFrom(p2); // 5

// Practical: configuration object
const env = 'production';

const appConfig = {
    env,
    
    get isProduction() {
        return this.env === 'production';
    },
    
    get isDevelopment() {
        return this.env === 'development';
    },
    
    [`${env}_url`]: 'https://api.example.com',
    
    log(message) {
        if (this.isDevelopment) {
            console.log(message);
        }
    }
};
Note: Property shorthand uses variable name as key. Method shorthand omits : function. Computed properties use [expression]. Getters/setters accessed like properties. Methods can be async, generators, or both.

15.5 Template Literals and Tagged Templates

Feature Syntax Description Example
Basic Template `text` Backtick-delimited string `Hello`
Interpolation `text ${expr}` Embed expression result `x = ${x}`
Multi-line `line1<br>line2` Preserve newlines `First<br>Second`
Tagged Template tag`text ${expr}` Function processes template html`<div>${content}</div>`
Raw Strings String.raw`text` Get raw string (no escape processing) String.raw`C:\temp`

Example: Template literals

// Basic template literals
const name = 'Alice';
const greeting = `Hello, ${name}!`;
console.log(greeting); // "Hello, Alice!"

// Expression interpolation
const a = 10;
const b = 20;
console.log(`${a} + ${b} = ${a + b}`); // "10 + 20 = 30"

// Any expression
const price = 99.99;
console.log(`Price: ${price.toFixed(2)}`); // "Price: $99.99"

// Function calls
function upper(str) {
    return str.toUpperCase();
}

console.log(`Name: ${upper(name)}`); // "Name: ALICE"

// Multi-line strings
const html = `
    <div>
        <h1>Title</h1>
        <p>Content</p>
    </div>
`;
// Preserves indentation and newlines

// Old way (ES5)
const oldHtml = 
    '<div>\n' +
    '    <h1>Title</h1>\n' +
    '    <p>Content</p>\n' +
    '</div>';

// Nested templates
const user = {name: 'Bob', age: 30};
const info = `User: ${user.name} (${user.age > 18 ? 'Adult' : 'Minor'})`;

// Escaping backticks
const text = `This is a backtick: \``;

// Tagged templates (custom processing)
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => {
        const value = values[i] || '';
        return result + str + `<mark>${value}</mark>`;
    }, '');
}

const score = 95;
const result = highlight`Your score is ${score}%`;
// "Your score is <mark>95</mark>%"

// Tagged template: SQL escaping (example)
function sql(strings, ...values) {
    return strings.reduce((query, str, i) => {
        const value = values[i];
        const escaped = typeof value === 'string'
            ? value.replace(/'/g, "''")
            : value;
        return query + str + (escaped !== undefined ? escaped : '');
    }, '');
}

const userId = 123;
const userName = "O'Brien";
const query = sql`SELECT * FROM users WHERE id = ${userId} AND name = '${userName}'`;
// "SELECT * FROM users WHERE id = 123 AND name = 'O''Brien'"

// Tagged template: HTML escaping
function html(strings, ...values) {
    const escape = (str) => String(str)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
    
    return strings.reduce((result, str, i) => {
        const value = values[i] !== undefined ? escape(values[i]) : '';
        return result + str + value;
    }, '');
}

const userInput = '<script>alert("XSS")</script>';
const safe = html`<div>${userInput}</div>`;
// "<div><script>alert("XSS")</script></div>"

// String.raw (no escape processing)
const path = String.raw`C:\Users\Documents\file.txt`;
console.log(path); // "C:\Users\Documents\file.txt" (backslashes preserved)

// Normal template would process escapes
const normalPath = `C:\Users\Documents\file.txt`;
// Escape sequences like \U, \D, \f would be interpreted

// Raw string with interpolation
const dir = 'Documents';
const rawPath = String.raw`C:\${dir}\file.txt`;
console.log(rawPath); // "C:\Documents\file.txt"

// Access raw strings in tagged templates
function showRaw(strings, ...values) {
    console.log('Cooked:', strings);
    console.log('Raw:', strings.raw);
    console.log('Values:', values);
}

showRaw`Line 1\nLine 2 ${42}`;
// Cooked: ['Line 1
// Line 2 ', '']
// Raw: ['Line 1\\nLine 2 ', '']
// Values: [42]

// Practical: styled components (React)
function css(strings, ...values) {
    return strings.reduce((result, str, i) => {
        return result + str + (values[i] || '');
    }, '');
}

const Button = css`
    background: ${props => props.primary ? 'blue' : 'gray'};
    color: white;
    padding: 10px 20px;
`;

// Practical: i18n (internationalization)
function i18n(strings, ...values) {
    const key = strings.join('{}');
    // Look up translation for key
    return translate(key, values);
}

const count = 5;
const message = i18n`You have ${count} new messages`;

// Practical: GraphQL queries
const gql = (strings, ...values) => strings.join('');

const GET_USER = gql`
    query GetUser($id: ID!) {
        user(id: $id) {
            name
            email
        }
    }
`;

// Practical: conditional parts
const showDetails = true;
const card = `
    <div class="card">
        <h3>${name}</h3>
        ${showDetails ? `<p>Age: ${age}</p>` : ''}
    </div>
`;

// Practical: array to list
const items = ['Apple', 'Banana', 'Cherry'];
const list = `
    <ul>
        ${items.map(item => `<li>${item}</li>`).join('\n        ')}
    </ul>
`;

// Expression can be any valid JavaScript
console.log(`Random: ${Math.random()}`);
console.log(`Array length: ${[1, 2, 3].length}`);
console.log(`Object: ${JSON.stringify({a: 1})}`);

// Template in template
const inner = `inner text`;
const outer = `outer ${`nested ${inner}`} text`;

// Empty interpolation
const empty = `Value: ${''}`;
console.log(empty); // "Value: "

// Undefined/null
console.log(`Value: ${undefined}`); // "Value: undefined"
console.log(`Value: ${null}`); // "Value: null"
Note: Template literals use backticks (`). Interpolate with ${expression}. Preserve newlines/indentation. Tagged templates: function(strings, ...values) where strings.raw has unprocessed escapes. Expressions converted to strings.

15.6 Symbol Type and Symbol Registry

Method/Property Syntax Description Returns
Symbol() Symbol([description]) Create unique symbol; optional description Unique symbol value
Symbol.for() Symbol.for(key) Get/create global symbol by key Symbol (reuses if exists)
Symbol.keyFor() Symbol.keyFor(sym) Get key for global symbol String key or undefined
description symbol.description Get symbol description String or undefined
typeof typeof sym Type check 'symbol'

Well-Known Symbols

Symbol Purpose Used By
Symbol.iterator Define default iterator for...of, spread, destructuring
Symbol.asyncIterator Define async iterator for await...of
Symbol.toStringTag Customize Object.prototype.toString Object.prototype.toString.call()
Symbol.toPrimitive Convert object to primitive Type coercion
Symbol.hasInstance Customize instanceof behavior instanceof operator

Example: Symbols

// Create unique symbols
const sym1 = Symbol();
const sym2 = Symbol();

console.log(sym1 === sym2); // false (always unique)

// Symbols with descriptions
const id = Symbol('id');
const userId = Symbol('id');

console.log(id === userId); // false (still unique despite same description)

// Description property
console.log(id.description); // "id"

// typeof
console.log(typeof id); // "symbol"

// Symbols as object keys (hidden from normal iteration)
const obj = {
    name: 'Alice',
    [id]: 123
};

console.log(obj[id]); // 123
console.log(obj.name); // 'Alice'

// Not enumerable in for...in
for (let key in obj) {
    console.log(key); // Only logs "name" (not id)
}

// Not in Object.keys()
console.log(Object.keys(obj)); // ['name']

// Symbols visible with Object.getOwnPropertySymbols()
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id)]

// Or Reflect.ownKeys() (all keys)
console.log(Reflect.ownKeys(obj)); // ['name', Symbol(id)]

// Global symbol registry (shared across realms)
const globalSym1 = Symbol.for('app.id');
const globalSym2 = Symbol.for('app.id');

console.log(globalSym1 === globalSym2); // true (same key = same symbol)

// Get key for global symbol
console.log(Symbol.keyFor(globalSym1)); // "app.id"

// Local symbols have no key
const localSym = Symbol('local');
console.log(Symbol.keyFor(localSym)); // undefined

// Well-known symbols: Symbol.iterator
const iterable = {
    data: [1, 2, 3],
    
    [Symbol.iterator]() {
        let index = 0;
        const data = this.data;
        
        return {
            next() {
                if (index < data.length) {
                    return {value: data[index++], done: false};
                }
                return {done: true};
            }
        };
    }
};

for (const value of iterable) {
    console.log(value); // 1, 2, 3
}

// Symbol.toStringTag (customize toString)
class MyClass {
    get [Symbol.toStringTag]() {
        return 'MyClass';
    }
}

const instance = new MyClass();
console.log(Object.prototype.toString.call(instance)); // "[object MyClass]"

// Symbol.toPrimitive (customize type conversion)
const obj2 = {
    value: 100,
    
    [Symbol.toPrimitive](hint) {
        switch (hint) {
            case 'number':
                return this.value;
            case 'string':
                return `${this.value}`;
            case 'default':
                return this.value;
            default:
                throw new Error('Invalid hint');
        }
    }
};

console.log(+obj2); // 100 (number hint)
console.log(`${obj2}`); // "$100" (string hint)
console.log(obj2 + 50); // 150 (default hint)

// Symbol.hasInstance (customize instanceof)
class MyArray {
    static [Symbol.hasInstance](instance) {
        return Array.isArray(instance);
    }
}

console.log([1, 2, 3] instanceof MyArray); // true

// Private object properties (convention)
const _internal = Symbol('internal');

class Counter {
    constructor() {
        this[_internal] = 0;
    }
    
    increment() {
        this[_internal]++;
    }
    
    getValue() {
        return this[_internal];
    }
}

const counter = new Counter();
counter.increment();
console.log(counter.getValue()); // 1
console.log(counter[_internal]); // undefined (no access to local symbol)

// But symbols are not truly private
const symbols = Object.getOwnPropertySymbols(counter);
console.log(counter[symbols[0]]); // 1 (can still access)

// Symbols in JSON (ignored)
const data = {
    name: 'Alice',
    [Symbol('secret')]: 'hidden'
};

console.log(JSON.stringify(data)); // {"name":"Alice"}

// Cannot convert symbol to string directly
const sym = Symbol('test');
// String(sym); // TypeError
// "" + sym; // TypeError
sym.toString(); // "Symbol(test)" (explicit toString OK)

// Symbols in Maps/Sets
const map = new Map();
const key = Symbol('key');
map.set(key, 'value');
console.log(map.get(key)); // "value"

// Practical: unique constants
const STATUS = {
    PENDING: Symbol('pending'),
    APPROVED: Symbol('approved'),
    REJECTED: Symbol('rejected')
};

let orderStatus = STATUS.PENDING;

if (orderStatus === STATUS.PENDING) {
    console.log('Order is pending');
}

// Practical: metadata keys (avoid collisions)
const METADATA_KEY = Symbol.for('app.metadata');

function addMetadata(obj, data) {
    obj[METADATA_KEY] = data;
}

function getMetadata(obj) {
    return obj[METADATA_KEY];
}

const user = {name: 'Alice'};
addMetadata(user, {created: Date.now()});

// Practical: protocol/interface check
const SERIALIZABLE = Symbol.for('serializable');

class User {
    [SERIALIZABLE]() {
        return JSON.stringify(this);
    }
}

function serialize(obj) {
    if (SERIALIZABLE in obj) {
        return obj[SERIALIZABLE]();
    }
    throw new Error('Not serializable');
}

// Symbol.match, Symbol.replace, etc. (regex methods)
class MyMatcher {
    [Symbol.match](str) {
        return str.includes('test') ? ['test'] : null;
    }
}

const matcher = new MyMatcher();
console.log('testing'.match(matcher)); // ['test']
Warning: Symbols are unique (except global registry via Symbol.for()). Not enumerable in for...in or Object.keys(), but visible with Object.getOwnPropertySymbols(). Not truly private. Can't convert to string with + or ''.

15.7 WeakMap and WeakSet Collections

Collection Key Type Values GC Behavior
WeakMap Objects only Any type Keys weakly held; GC if no other references
WeakSet Objects only Objects (values are keys) Values weakly held; GC if no other references

WeakMap Methods

Method Syntax Description Returns
set() weakMap.set(key, value) Add/update key-value pair WeakMap instance
get() weakMap.get(key) Get value for key Value or undefined
has() weakMap.has(key) Check if key exists Boolean
delete() weakMap.delete(key) Remove key-value pair Boolean (true if deleted)

WeakSet Methods

Method Syntax Description Returns
add() weakSet.add(value) Add object to set WeakSet instance
has() weakSet.has(value) Check if value exists Boolean
delete() weakSet.delete(value) Remove value from set Boolean (true if deleted)

Example: WeakMap and WeakSet

// WeakMap basics
const weakMap = new WeakMap();

const obj1 = {name: 'Alice'};
const obj2 = {name: 'Bob'};

// Set values (keys must be objects)
weakMap.set(obj1, 'data for obj1');
weakMap.set(obj2, 'data for obj2');

// Get values
console.log(weakMap.get(obj1)); // "data for obj1"

// Check existence
console.log(weakMap.has(obj2)); // true

// Delete
weakMap.delete(obj1);
console.log(weakMap.has(obj1)); // false

// Primitives not allowed as keys
// weakMap.set('string', 'value'); // TypeError
// weakMap.set(123, 'value'); // TypeError
// weakMap.set(Symbol(), 'value'); // TypeError

// Garbage collection example
let user = {name: 'Alice'};
weakMap.set(user, {loginCount: 5});

user = null; // No more references to object
// Object and its WeakMap entry can be garbage collected

// No size property
console.log(weakMap.size); // undefined

// Not iterable
// for (let entry of weakMap) {} // TypeError
// weakMap.forEach() // undefined (no method)

// WeakSet basics
const weakSet = new WeakSet();

const item1 = {id: 1};
const item2 = {id: 2};

// Add objects
weakSet.add(item1);
weakSet.add(item2);

// Check membership
console.log(weakSet.has(item1)); // true

// Delete
weakSet.delete(item1);
console.log(weakSet.has(item1)); // false

// Primitives not allowed
// weakSet.add('string'); // TypeError
// weakSet.add(123); // TypeError

// Practical: Private data for objects
const privateData = new WeakMap();

class Person {
    constructor(name, ssn) {
        this.name = name; // Public
        privateData.set(this, {ssn}); // Private
    }
    
    getSSN() {
        return privateData.get(this).ssn;
    }
}

const person = new Person('Alice', '123-45-6789');
console.log(person.name); // "Alice"
console.log(person.getSSN()); // "123-45-6789"
console.log(person.ssn); // undefined (not accessible)

// When person is garbage collected, privateData entry is too

// Practical: DOM node metadata
const nodeMetadata = new WeakMap();

function trackNode(node, metadata) {
    nodeMetadata.set(node, metadata);
}

function getNodeData(node) {
    return nodeMetadata.get(node);
}

const div = document.createElement('div');
trackNode(div, {clicks: 0, created: Date.now()});

// When div is removed from DOM and no references exist, GC cleans up

// Practical: Cache/memoization
const cache = new WeakMap();

function expensiveOperation(obj) {
    if (cache.has(obj)) {
        return cache.get(obj);
    }
    
    // Expensive computation
    const result = obj.value * 2;
    cache.set(obj, result);
    return result;
}

const data = {value: 10};
expensiveOperation(data); // Computes and caches
expensiveOperation(data); // Returns cached result

// When data is no longer referenced, cache entry is GC'd

// Practical: Object flagging with WeakSet
const processedObjects = new WeakSet();

function processObject(obj) {
    if (processedObjects.has(obj)) {
        console.log('Already processed');
        return;
    }
    
    // Process object
    console.log('Processing', obj);
    processedObjects.add(obj);
}

const obj = {id: 1};
processObject(obj); // "Processing {id: 1}"
processObject(obj); // "Already processed"

// When obj is GC'd, WeakSet entry is too

// Practical: Preventing circular reference leaks
class Node {
    constructor(value) {
        this.value = value;
        this.children = [];
    }
    
    addChild(node) {
        this.children.push(node);
    }
}

// Track parent-child relationships without preventing GC
const parentMap = new WeakMap();

function setParent(child, parent) {
    parentMap.set(child, parent);
}

function getParent(child) {
    return parentMap.get(child);
}

const parent = new Node('parent');
const child = new Node('child');
parent.addChild(child);
setParent(child, parent);

// WeakMap vs Map comparison
const regularMap = new Map();
const weakMap2 = new WeakMap();

let key = {id: 1};

regularMap.set(key, 'value');
weakMap2.set(key, 'value');

key = null;
// regularMap still holds reference to object (prevents GC)
// weakMap2 does not hold reference (allows GC)

// Use cases for WeakMap:
// 1. Private instance data
// 2. Metadata for objects
// 3. Caching results for objects
// 4. DOM node annotations
// 5. Prevent memory leaks with object associations

// Use cases for WeakSet:
// 1. Track processed objects
// 2. Mark objects without modifying them
// 3. Prevent duplicate processing
// 4. Object flags/tags

// Limitations:
// - Keys/values must be objects (no primitives)
// - Not enumerable (can't iterate)
// - No size property
// - No clear() method
// - Can't get all keys/values

// Why WeakMap instead of Map with cleanup?
// - Automatic garbage collection
// - No manual memory management needed
// - Prevents memory leaks
// - Cleaner code (no cleanup logic)

// When NOT to use WeakMap/WeakSet:
// - Need to iterate over entries
// - Need to know collection size
// - Keys/values are primitives
// - Need to serialize/deserialize
// - Need persistent storage

// WeakMap with object literals (careful!)
weakMap.set({}, 'value'); // No reference = immediately GC'd!
console.log(weakMap.get({})); // undefined (different object)

// Must keep reference
const key2 = {};
weakMap.set(key2, 'value');
console.log(weakMap.get(key2)); // "value"
Note: WeakMap/WeakSet keys/values must be objects (no primitives). Entries automatically garbage collected when no other references exist. Not enumerable (no size, forEach, keys, values, entries). Use for private data, metadata, caching without memory leaks.

Section 15 Summary:

  • Destructuring: Extract array/object values with [a, b] = arr or {x, y} = obj; supports defaults, rest, skip, rename, nesting; works in parameters
  • Spread: Expand arrays/objects with ... in literals/calls ([...arr], {...obj}); shallow copy; merge collections; spread strings/iterables
  • Rest: Collect remaining items with ...rest (must be last); gather function arguments into array; collect remaining properties in destructuring
  • Defaults: Parameter fallbacks with = value; only for undefined (not null); expressions evaluated each call; can reference previous parameters
  • Object Literals: Property shorthand {x, y}; method shorthand method() {}; computed keys [expr]; getters/setters; can be async/generator
  • Templates: Backtick strings with interpolation `text ${expr}`; multi-line; tagged templates tag`...` for custom processing; String.raw for raw strings
  • Symbols: Unique immutable values via Symbol(); global registry Symbol.for(key); hidden from normal enumeration; well-known symbols customize behavior (iterator, toStringTag, toPrimitive)
  • WeakMap/WeakSet: Collections with object-only keys/values; weak references (allow GC); no size/iteration; use for private data, metadata, caching without memory leaks

16. ES2015+ Advanced Features

16.1 Map and Set Collections and Methods

Map vs Object

Feature Map Object
Key Types Any type (objects, functions, primitives) Strings and Symbols only
Key Order Insertion order guaranteed Insertion order (ES2015+) but complex
Size map.size property Must compute manually
Iteration Directly iterable with for...of Need Object.keys/values/entries
Performance Better for frequent add/delete Better for simple storage/lookup
Prototype No inherited keys Prototype chain pollution risk
JSON Not serializable directly JSON.stringify/parse support

Map Methods

Method Syntax Description Returns
set() map.set(key, value) Add or update key-value pair Map instance (chainable)
get() map.get(key) Retrieve value for key Value or undefined
has() map.has(key) Check if key exists Boolean
delete() map.delete(key) Remove key-value pair Boolean (true if existed)
clear() map.clear() Remove all entries undefined
keys() map.keys() Get iterator of keys MapIterator
values() map.values() Get iterator of values MapIterator
entries() map.entries() Get iterator of [key, value] pairs MapIterator
forEach() map.forEach(callback, thisArg) Execute callback for each entry undefined

Set vs Array

Feature Set Array
Uniqueness Only unique values (automatic) Can have duplicates
Lookup Speed O(1) for has() O(n) for includes()
Order Insertion order preserved Insertion order (indexed)
Index Access No index access array[index] access
Use Case Unique collections, fast lookup Ordered lists, frequent access

Set Methods

Method Syntax Description Returns
add() set.add(value) Add value to set Set instance (chainable)
has() set.has(value) Check if value exists Boolean
delete() set.delete(value) Remove value from set Boolean (true if existed)
clear() set.clear() Remove all values undefined
keys() set.keys() Get iterator of values (alias for values()) SetIterator
values() set.values() Get iterator of values SetIterator
entries() set.entries() Get iterator of [value, value] pairs SetIterator
forEach() set.forEach(callback, thisArg) Execute callback for each value undefined

Example: Map with different key types

// Create Map
const map = new Map();

// Different key types
const objKey = {id: 1};
const funcKey = () => {};
const numKey = 42;

map.set(objKey, 'object key');
map.set(funcKey, 'function key');
map.set(numKey, 'number key');
map.set('string', 'string key');

// Retrieve values
console.log(map.get(objKey));    // "object key"
console.log(map.get(funcKey));   // "function key"
console.log(map.get(42));        // "number key"

// Size and existence
console.log(map.size);           // 4
console.log(map.has(objKey));    // true
console.log(map.has({id: 1}));   // false (different object)

// Chaining
map.set('a', 1).set('b', 2).set('c', 3);

// Iteration
for (const [key, value] of map) {
    console.log(key, value);
}

// Iterate keys only
for (const key of map.keys()) {
    console.log(key);
}

// Iterate values only
for (const value of map.values()) {
    console.log(value);
}

// forEach with context
map.forEach(function(value, key, map) {
    console.log(`${key} = ${value}`);
});

// Convert to array
const entries = [...map];           // [[key, val], ...]
const keys = [...map.keys()];       // [key1, key2, ...]
const values = [...map.values()];   // [val1, val2, ...]

// Delete and clear
map.delete(numKey);   // true
map.clear();          // All entries removed

Example: Set operations and uniqueness

// Create Set
const set = new Set();

// Add values
set.add(1);
set.add(2);
set.add(3);
set.add(2);  // Duplicate ignored

console.log(set.size);  // 3 (only unique values)

// Chaining
set.add(4).add(5).add(6);

// Check existence
console.log(set.has(3));  // true
console.log(set.has(10)); // false

// Remove duplicates from array
const arr = [1, 2, 3, 2, 4, 3, 5];
const unique = [...new Set(arr)];  // [1, 2, 3, 4, 5]

// Iteration
for (const value of set) {
    console.log(value);
}

set.forEach(value => {
    console.log(value);
});

// Convert to array
const array = [...set];
const array2 = Array.from(set);

// Set operations (manual implementation)
const setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);

// Union
const union = new Set([...setA, ...setB]);  // {1, 2, 3, 4, 5}

// Intersection
const intersection = new Set([...setA].filter(x => setB.has(x)));  // {3}

// Difference
const difference = new Set([...setA].filter(x => !setB.has(x)));  // {1, 2}

// Symmetric difference
const symDiff = new Set([
    ...[...setA].filter(x => !setB.has(x)),
    ...[...setB].filter(x => !setA.has(x))
]);  // {1, 2, 4, 5}

// Delete and clear
set.delete(3);  // true
set.clear();    // All values removed

Example: Practical Map use cases

// Cache with object keys
const cache = new Map();

function expensiveOperation(obj) {
    if (cache.has(obj)) {
        return cache.get(obj);
    }
    
    const result = /* expensive computation */;
    cache.set(obj, result);
    return result;
}

// Counting occurrences
function countOccurrences(arr) {
    const counts = new Map();
    
    for (const item of arr) {
        counts.set(item, (counts.get(item) || 0) + 1);
    }
    
    return counts;
}

const items = ['a', 'b', 'a', 'c', 'b', 'a'];
const counts = countOccurrences(items);  // Map: {a => 3, b => 2, c => 1}

// Grouping by property
function groupBy(arr, key) {
    return arr.reduce((map, item) => {
        const group = item[key];
        if (!map.has(group)) {
            map.set(group, []);
        }
        map.get(group).push(item);
        return map;
    }, new Map());
}

const users = [
    {name: 'Alice', role: 'admin'},
    {name: 'Bob', role: 'user'},
    {name: 'Charlie', role: 'admin'}
];
const byRole = groupBy(users, 'role');
// Map: {admin => [{Alice}, {Charlie}], user => [{Bob}]}

// Map from array of pairs
const map2 = new Map([
    ['key1', 'value1'],
    ['key2', 'value2'],
    ['key3', 'value3']
]);

// Convert object to Map
const obj = {a: 1, b: 2, c: 3};
const map3 = new Map(Object.entries(obj));

// Convert Map to object
const mapToObj = Object.fromEntries(map3);
Performance Tips: Map is faster than Object for frequent additions/deletions. Set.has() is O(1) vs Array.includes() O(n). Use Map for non-string keys. Use Set for unique collections. Both maintain insertion order.

16.2 Proxy and Reflect API for Metaprogramming

Proxy Traps (Handler Methods)

Trap Intercepts Parameters Use Case
get Property access (target, prop, receiver) Validation, default values, logging
set Property assignment (target, prop, value, receiver) Validation, reactivity, computed
has in operator (target, prop) Hide properties, virtual props
deleteProperty delete operator (target, prop) Prevent deletion, logging
apply Function call (target, thisArg, args) Logging, parameter validation
construct new operator (target, args, newTarget) Factory pattern, validation
getPrototypeOf Object.getPrototypeOf() (target) Custom prototype behavior
setPrototypeOf Object.setPrototypeOf() (target, proto) Prevent prototype changes
isExtensible Object.isExtensible() (target) Custom extensibility logic
preventExtensions Object.preventExtensions() (target) Custom prevention logic
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor() (target, prop) Hide/modify descriptors
defineProperty Object.defineProperty() (target, prop, descriptor) Intercept property definitions
ownKeys Object.keys(), for...in (target) Filter properties, add virtual

Reflect API Methods

Method Description Returns
Reflect.get() Get property value Property value
Reflect.set() Set property value Boolean (success)
Reflect.has() Check if property exists Boolean
Reflect.deleteProperty() Delete property Boolean (success)
Reflect.apply() Call function with args Function result
Reflect.construct() Call constructor with new New instance
Reflect.getPrototypeOf() Get prototype Prototype object or null
Reflect.setPrototypeOf() Set prototype Boolean (success)
Reflect.isExtensible() Check if extensible Boolean
Reflect.preventExtensions() Make non-extensible Boolean (success)
Reflect.getOwnPropertyDescriptor() Get property descriptor Descriptor object or undefined
Reflect.defineProperty() Define property Boolean (success)
Reflect.ownKeys() Get all own property keys Array of keys

Example: Basic Proxy with get and set traps

// Simple property access logging
const target = {
    name: 'Alice',
    age: 30
};

const handler = {
    get(target, prop, receiver) {
        console.log(`Getting ${prop}`);
        return Reflect.get(target, prop, receiver);
    },
    
    set(target, prop, value, receiver) {
        console.log(`Setting ${prop} to ${value}`);
        return Reflect.set(target, prop, value, receiver);
    }
};

const proxy = new Proxy(target, handler);

proxy.name;        // Logs: "Getting name", returns "Alice"
proxy.age = 31;    // Logs: "Setting age to 31"

// Validation proxy
const validatedUser = new Proxy({}, {
    set(target, prop, value) {
        if (prop === 'age') {
            if (!Number.isInteger(value) || value < 0) {
                throw new TypeError('Age must be a non-negative integer');
            }
        }
        
        if (prop === 'email') {
            if (!value.includes('@')) {
                throw new TypeError('Invalid email format');
            }
        }
        
        return Reflect.set(target, prop, value);
    }
});

validatedUser.age = 25;           // OK
validatedUser.email = 'a@b.com';  // OK
// validatedUser.age = -5;        // TypeError
// validatedUser.age = 'old';     // TypeError
// validatedUser.email = 'bad';   // TypeError

Example: Default values and negative indexing

// Default values for undefined properties
const withDefaults = new Proxy({}, {
    get(target, prop) {
        return prop in target ? target[prop] : 'default value';
    }
});

console.log(withDefaults.name);     // "default value"
withDefaults.name = 'Alice';
console.log(withDefaults.name);     // "Alice"

// Negative array indexing (Python-style)
function createArray(arr) {
    return new Proxy(arr, {
        get(target, prop) {
            const index = Number(prop);
            
            // Handle negative indices
            if (index < 0) {
                return target[target.length + index];
            }
            
            return Reflect.get(target, prop);
        }
    });
}

const arr = createArray(['a', 'b', 'c', 'd', 'e']);
console.log(arr[-1]);  // 'e'
console.log(arr[-2]);  // 'd'
console.log(arr[0]);   // 'a'

// Read-only object
function createReadOnly(obj) {
    return new Proxy(obj, {
        set() {
            throw new Error('Object is read-only');
        },
        deleteProperty() {
            throw new Error('Object is read-only');
        }
    });
}

const readOnly = createReadOnly({name: 'Alice', age: 30});
console.log(readOnly.name);  // "Alice"
// readOnly.name = 'Bob';    // Error: Object is read-only
// delete readOnly.age;      // Error: Object is read-only

Example: Function call interception

// Log all function calls
function createLoggingProxy(func, name) {
    return new Proxy(func, {
        apply(target, thisArg, args) {
            console.log(`Calling ${name} with args:`, args);
            const result = Reflect.apply(target, thisArg, args);
            console.log(`${name} returned:`, result);
            return result;
        }
    });
}

function add(a, b) {
    return a + b;
}

const loggedAdd = createLoggingProxy(add, 'add');
loggedAdd(5, 3);
// Logs: "Calling add with args: [5, 3]"
// Logs: "add returned: 8"

// Memoization proxy
function createMemoized(func) {
    const cache = new Map();
    
    return new Proxy(func, {
        apply(target, thisArg, args) {
            const key = JSON.stringify(args);
            
            if (cache.has(key)) {
                console.log('Cache hit for', args);
                return cache.get(key);
            }
            
            console.log('Computing for', args);
            const result = Reflect.apply(target, thisArg, args);
            cache.set(key, result);
            return result;
        }
    });
}

function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

const memoFib = createMemoized(fibonacci);
console.log(memoFib(5));  // Computes
console.log(memoFib(5));  // Cache hit

// Constructor interception
class User {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

const UserProxy = new Proxy(User, {
    construct(target, args) {
        console.log('Creating user with:', args);
        
        // Add timestamp to all instances
        const instance = Reflect.construct(target, args);
        instance.createdAt = Date.now();
        return instance;
    }
});

const user = new UserProxy('Alice', 30);
console.log(user.createdAt);  // timestamp added automatically

Example: Observable/reactive object

// Reactive object that triggers callbacks on changes
function createObservable(target, callback) {
    return new Proxy(target, {
        set(target, prop, value, receiver) {
            const oldValue = target[prop];
            const result = Reflect.set(target, prop, value, receiver);
            
            if (oldValue !== value) {
                callback(prop, oldValue, value);
            }
            
            return result;
        },
        
        deleteProperty(target, prop) {
            const oldValue = target[prop];
            const result = Reflect.deleteProperty(target, prop);
            
            if (result) {
                callback(prop, oldValue, undefined);
            }
            
            return result;
        }
    });
}

const state = createObservable(
    {count: 0, name: 'Alice'},
    (prop, oldVal, newVal) => {
        console.log(`${prop} changed from ${oldVal} to ${newVal}`);
    }
);

state.count = 5;      // Logs: "count changed from 0 to 5"
state.name = 'Bob';   // Logs: "name changed from Alice to Bob"
delete state.count;   // Logs: "count changed from 5 to undefined"

// Virtual properties
const user2 = new Proxy({
    firstName: 'John',
    lastName: 'Doe'
}, {
    get(target, prop) {
        if (prop === 'fullName') {
            return `${target.firstName} ${target.lastName}`;
        }
        return Reflect.get(target, prop);
    },
    
    set(target, prop, value) {
        if (prop === 'fullName') {
            const [first, last] = value.split(' ');
            target.firstName = first;
            target.lastName = last;
            return true;
        }
        return Reflect.set(target, prop, value);
    }
});

console.log(user2.fullName);      // "John Doe"
user2.fullName = 'Jane Smith';
console.log(user2.firstName);     // "Jane"
console.log(user2.lastName);      // "Smith"
Important: Always use Reflect methods in Proxy handlers for proper behavior and receiver context. Proxy traps must respect invariants (e.g., can't report non-existent property as non-configurable). Proxies have performance overhead - use judiciously. Revocable proxies: Proxy.revocable(target, handler) returns {proxy, revoke()}.

16.3 Generator Functions and Iterator Protocol

Generator Function Syntax

Type Syntax Example
Function Declaration function* name() {} function* gen() { yield 1; }
Function Expression const f = function*() {} const g = function*() { yield 1; }
Object Method obj = { *method() {} } { *gen() { yield 1; } }
Class Method class C { *method() {} } class C { *gen() { yield 1; } }

Generator Methods

Method Syntax Description Returns
next() gen.next(value) Resume execution, send value to yield {value, done}
return() gen.return(value) Terminate generator, return value {value, done: true}
throw() gen.throw(error) Throw error at yield point {value, done} or throws

yield Expressions

Expression Syntax Description
yield yield value Pause and return value
yield* yield* iterable Delegate to another generator/iterable
yield (receive) const x = yield Receive value from next(value)

Example: Basic generator function

// Simple generator
function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = numberGenerator();

console.log(gen.next());  // {value: 1, done: false}
console.log(gen.next());  // {value: 2, done: false}
console.log(gen.next());  // {value: 3, done: false}
console.log(gen.next());  // {value: undefined, done: true}

// Generators are iterable
for (const num of numberGenerator()) {
    console.log(num);  // 1, 2, 3
}

// Spread with generator
const numbers = [...numberGenerator()];  // [1, 2, 3]

// Infinite generator
function* infiniteSequence() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

const seq = infiniteSequence();
console.log(seq.next().value);  // 0
console.log(seq.next().value);  // 1
console.log(seq.next().value);  // 2

// Fibonacci generator
function* fibonacci() {
    let [a, b] = [0, 1];
    while (true) {
        yield a;
        [a, b] = [b, a + b];
    }
}

const fib = fibonacci();
console.log(fib.next().value);  // 0
console.log(fib.next().value);  // 1
console.log(fib.next().value);  // 1
console.log(fib.next().value);  // 2
console.log(fib.next().value);  // 3
console.log(fib.next().value);  // 5

Example: Two-way communication with generators

// Generator that receives values
function* twoWayGenerator() {
    console.log('Generator started');
    
    const a = yield 'First yield';
    console.log('Received:', a);
    
    const b = yield 'Second yield';
    console.log('Received:', b);
    
    return 'Done';
}

const gen2 = twoWayGenerator();

console.log(gen2.next());        // {value: "First yield", done: false}
                                 // Logs: "Generator started"

console.log(gen2.next(10));      // {value: "Second yield", done: false}
                                 // Logs: "Received: 10"

console.log(gen2.next(20));      // {value: "Done", done: true}
                                 // Logs: "Received: 20"

// ID generator with reset
function* idGenerator() {
    let id = 1;
    
    while (true) {
        const reset = yield id++;
        if (reset) {
            id = 1;
        }
    }
}

const ids = idGenerator();
console.log(ids.next().value);       // 1
console.log(ids.next().value);       // 2
console.log(ids.next().value);       // 3
console.log(ids.next(true).value);   // 1 (reset)
console.log(ids.next().value);       // 2

Example: yield* delegation

// Delegate to another generator
function* gen1() {
    yield 1;
    yield 2;
}

function* gen2() {
    yield 'a';
    yield* gen1();  // Delegate
    yield 'b';
}

console.log([...gen2()]);  // ['a', 1, 2, 'b']

// Delegate to iterable
function* gen3() {
    yield* [1, 2, 3];
    yield* 'hello';
}

console.log([...gen3()]);  // [1, 2, 3, 'h', 'e', 'l', 'l', 'o']

// Tree traversal with delegation
const tree = {
    value: 1,
    children: [
        {
            value: 2,
            children: [
                { value: 4, children: [] },
                { value: 5, children: [] }
            ]
        },
        {
            value: 3,
            children: [
                { value: 6, children: [] }
            ]
        }
    ]
};

function* traverse(node) {
    yield node.value;
    for (const child of node.children) {
        yield* traverse(child);
    }
}

console.log([...traverse(tree)]);  // [1, 2, 4, 5, 3, 6]

// Flatten nested arrays
function* flatten(arr) {
    for (const item of arr) {
        if (Array.isArray(item)) {
            yield* flatten(item);
        } else {
            yield item;
        }
    }
}

const nested = [1, [2, [3, 4], 5], 6];
console.log([...flatten(nested)]);  // [1, 2, 3, 4, 5, 6]

Example: Generator control flow

// Early termination with return()
function* gen4() {
    try {
        yield 1;
        yield 2;
        yield 3;
    } finally {
        console.log('Cleanup');
    }
}

const g4 = gen4();
console.log(g4.next());      // {value: 1, done: false}
console.log(g4.return(99));  // Logs: "Cleanup"
                             // {value: 99, done: true}
console.log(g4.next());      // {value: undefined, done: true}

// Error handling with throw()
function* gen5() {
    try {
        yield 1;
        yield 2;
        yield 3;
    } catch (e) {
        console.log('Caught:', e.message);
        yield 'error handled';
    }
}

const g5 = gen5();
console.log(g5.next());                     // {value: 1, done: false}
console.log(g5.throw(new Error('Oops')));   // Logs: "Caught: Oops"
                                            // {value: "error handled", done: false}
console.log(g5.next());                     // {value: 3, done: false}

// Async-like flow control (pre-async/await pattern)
function run(genFunc) {
    const gen = genFunc();
    
    function handle(result) {
        if (result.done) return Promise.resolve(result.value);
        
        return Promise.resolve(result.value)
            .then(res => handle(gen.next(res)))
            .catch(err => handle(gen.throw(err)));
    }
    
    return handle(gen.next());
}

function* fetchUser() {
    try {
        const response = yield fetch('/api/user');
        const data = yield response.json();
        console.log(data);
    } catch (e) {
        console.error('Error:', e);
    }
}

// run(fetchUser);
Use Cases: Generators excel at: lazy evaluation (generate values on demand), infinite sequences, state machines, async control flow (pre-async/await), tree traversal, streaming data. Generators are pausable/resumable functions that return iterators.

16.4 Module System (import/export, dynamic imports)

Export Syntax

Type Syntax Description
Named Export export const x = 1; Export named binding
Named Export (list) export {x, y, z}; Export multiple named bindings
Named Export (rename) export {x as myX}; Export with different name
Default Export export default value; Export default value (one per module)
Default + Named export {x, y, z as default}; Mix default and named exports
Re-export export {x} from './mod'; Re-export from another module
Re-export All export * from './mod'; Re-export all named exports
Re-export All (namespace) export * as ns from './mod'; Re-export as namespace object

Import Syntax

Type Syntax Description
Named Import import {x, y} from './mod'; Import specific named exports
Named Import (rename) import {x as myX} from './mod'; Import with different name
Default Import import x from './mod'; Import default export
Default + Named import x, {y, z} from './mod'; Import default and named together
Namespace Import import * as mod from './mod'; Import all as namespace object
Side Effect Only import './mod'; Execute module (no imports)
Dynamic Import import('./mod').then(...) Load module asynchronously at runtime

Module Features

Feature Description Behavior
Strict Mode Always in strict mode No need for 'use strict'
Top-level this undefined (not global) Different from scripts
Hoisting Imports hoisted to top Executed before module code
Live Bindings Imports are references Updates reflect in importers
Singleton Module evaluated once Cached for all imports
Scope Module scope (not global) Variables are private by default
Static Structure Imports/exports must be top-level Analyzed before execution

Example: Named exports and imports

// math.js - Named exports
export const PI = 3.14159;
export const E = 2.71828;

export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

// Alternative: Export list
const PI2 = 3.14159;
const E2 = 2.71828;

function add2(a, b) {
    return a + b;
}

function multiply2(a, b) {
    return a * b;
}

export { PI2, E2, add2, multiply2 };

// Export with rename
export { add2 as addition, multiply2 as multiplication };

// ===== Importing =====

// app.js - Import specific named exports
import { PI, add, multiply } from './math.js';

console.log(PI);           // 3.14159
console.log(add(2, 3));    // 5

// Import with rename
import { add as sum, multiply as product } from './math.js';

console.log(sum(2, 3));       // 5
console.log(product(2, 3));   // 6

// Import all as namespace
import * as math from './math.js';

console.log(math.PI);           // 3.14159
console.log(math.add(2, 3));    // 5

// Import multiple
import { PI, E, add, multiply } from './math.js';

Example: Default exports

// user.js - Default export (class)
export default class User {
    constructor(name) {
        this.name = name;
    }
    
    greet() {
        return `Hello, ${this.name}`;
    }
}

// calculator.js - Default export (function)
export default function calculate(a, b, op) {
    switch (op) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/': return a / b;
        default: return NaN;
    }
}

// config.js - Default export (object)
export default {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retries: 3
};

// ===== Importing =====

// Import default (can use any name)
import User from './user.js';
import calculate from './calculator.js';
import config from './config.js';

const user = new User('Alice');
console.log(user.greet());          // "Hello, Alice"

console.log(calculate(5, 3, '+'));  // 8
console.log(config.apiUrl);         // "https://api.example.com"

// Mix default and named exports
// utils.js
export default function log(msg) {
    console.log(msg);
}

export const VERSION = '1.0.0';
export const DEBUG = true;

// app.js
import log, { VERSION, DEBUG } from './utils.js';

log('Starting app');
console.log(VERSION);  // "1.0.0"

Example: Re-exports and barrel exports

// components/Button.js
export default class Button {}

// components/Input.js
export default class Input {}

// components/Form.js
export default class Form {}

// components/index.js - Barrel export
export { default as Button } from './Button.js';
export { default as Input } from './Input.js';
export { default as Form } from './Form.js';

// Or re-export everything
export * from './Button.js';
export * from './Input.js';
export * from './Form.js';

// ===== Usage =====

// app.js - Single import for all components
import { Button, Input, Form } from './components/index.js';

// Re-export with namespace
// components/index.js
export * as Button from './Button.js';
export * as Input from './Input.js';

// app.js
import { Button, Input } from './components/index.js';

Button.default;  // Access default export
Button.someOtherExport;  // Access named exports

// Aggregate re-exports
// api/index.js
export * from './users.js';      // Re-export all from users
export * from './products.js';   // Re-export all from products
export * from './orders.js';     // Re-export all from orders

// Now import all API functions from one place
import { getUser, getProduct, getOrder } from './api/index.js';

Example: Dynamic imports

// Dynamic import returns a Promise
async function loadModule() {
    const module = await import('./math.js');
    
    console.log(module.PI);           // 3.14159
    console.log(module.add(2, 3));    // 5
}

loadModule();

// Conditional loading
async function loadComponent(name) {
    let module;
    
    if (name === 'dashboard') {
        module = await import('./Dashboard.js');
    } else if (name === 'settings') {
        module = await import('./Settings.js');
    }
    
    return module.default;
}

// Lazy loading with error handling
async function lazyLoad() {
    try {
        const { someFunction } = await import('./heavy-module.js');
        someFunction();
    } catch (error) {
        console.error('Failed to load module:', error);
    }
}

// Code splitting in routes
const routes = {
    '/home': () => import('./pages/Home.js'),
    '/about': () => import('./pages/About.js'),
    '/contact': () => import('./pages/Contact.js')
};

async function navigate(path) {
    const loader = routes[path];
    if (loader) {
        const module = await loader();
        module.default.render();
    }
}

// Dynamic import with destructuring
button.addEventListener('click', async () => {
    const { calculate, PI } = await import('./math.js');
    console.log(calculate(PI, 2, '*'));
});

// Load multiple modules in parallel
async function loadMultiple() {
    const [module1, module2, module3] = await Promise.all([
        import('./module1.js'),
        import('./module2.js'),
        import('./module3.js')
    ]);
    
    // Use modules...
}

// Feature detection with dynamic import
if ('IntersectionObserver' in window) {
    // Native support
} else {
    // Load polyfill
    await import('./intersection-observer-polyfill.js');
}
Key Points: Modules are singletons (evaluated once, cached). Imports are live bindings (not copies). Static imports are hoisted and synchronous. Dynamic imports are asynchronous and enable code splitting. Use type="module" in script tags. Modules always in strict mode with private scope.

16.5 Private Fields and Class Features

Private Field Syntax

Feature Syntax Description Support
Private Field #fieldName Private instance field (truly private) ES2022
Private Method #methodName() {} Private instance method ES2022
Private Static Field static #field Private static field ES2022
Private Static Method static #method() {} Private static method ES2022
Private Getter get #prop() {} Private getter accessor ES2022
Private Setter set #prop(val) {} Private setter accessor ES2022

Class Field Features

Feature Syntax Description Support
Public Field fieldName = value; Public instance field with initializer ES2022
Static Field static field = value; Static field (on class itself) ES2022
Static Block static { /* code */ } Static initialization block ES2022
Private in #field in obj Check if object has private field ES2022

Example: Private fields and methods

class BankAccount {
    // Private fields (must be declared)
    #balance = 0;
    #accountNumber;
    #transactions = [];
    
    // Public field
    accountType = 'checking';
    
    constructor(accountNumber, initialBalance) {
        this.#accountNumber = accountNumber;
        this.#balance = initialBalance;
        this.#addTransaction('Initial deposit', initialBalance);
    }
    
    // Private method
    #addTransaction(type, amount) {
        this.#transactions.push({
            type,
            amount,
            date: new Date(),
            balance: this.#balance
        });
    }
    
    // Public methods accessing private fields
    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
            this.#addTransaction('Deposit', amount);
            return true;
        }
        return false;
    }
    
    withdraw(amount) {
        if (amount > 0 && amount <= this.#balance) {
            this.#balance -= amount;
            this.#addTransaction('Withdrawal', -amount);
            return true;
        }
        return false;
    }
    
    getBalance() {
        return this.#balance;
    }
    
    getAccountNumber() {
        // Return masked number
        return '****' + this.#accountNumber.slice(-4);
    }
    
    getTransactions() {
        // Return copy to prevent modification
        return [...this.#transactions];
    }
}

const account = new BankAccount('1234567890', 1000);

console.log(account.getBalance());        // 1000
account.deposit(500);                     // true
console.log(account.getBalance());        // 1500

// Private fields truly inaccessible from outside
console.log(account.#balance);            // SyntaxError
console.log(account.balance);             // undefined
console.log(account['#balance']);         // undefined

// Check if object has private field (from inside class only)
class Demo {
    #private = 42;
    
    static hasPrivate(obj) {
        return #private in obj;
    }
}

const demo = new Demo();
console.log(Demo.hasPrivate(demo));       // true
console.log(Demo.hasPrivate({}));         // false

Example: Private getters and setters

class User {
    #firstName;
    #lastName;
    #age;
    
    constructor(firstName, lastName, age) {
        this.#firstName = firstName;
        this.#lastName = lastName;
        this.#age = age;
    }
    
    // Private getter
    get #fullName() {
        return `${this.#firstName} ${this.#lastName}`;
    }
    
    // Private setter with validation
    set #age(value) {
        if (value < 0 || value > 150) {
            throw new Error('Invalid age');
        }
        this.#age = value;
    }
    
    // Public method using private getter
    introduce() {
        return `Hi, I'm ${this.#fullName}, ${this.#age} years old`;
    }
    
    // Public method using private setter
    celebrateBirthday() {
        this.#age = this.#age + 1;
    }
    
    getAge() {
        return this.#age;
    }
}

const user = new User('John', 'Doe', 30);
console.log(user.introduce());  // "Hi, I'm John Doe, 30 years old"
user.celebrateBirthday();
console.log(user.getAge());     // 31

Example: Static private fields and methods

class DatabaseConnection {
    // Private static field for singleton instance
    static #instance = null;
    static #connectionCount = 0;
    
    // Private instance field
    #connectionId;
    
    constructor() {
        if (DatabaseConnection.#instance) {
            throw new Error('Use DatabaseConnection.getInstance()');
        }
        
        this.#connectionId = ++DatabaseConnection.#connectionCount;
        DatabaseConnection.#instance = this;
    }
    
    // Private static method
    static #validateConfig(config) {
        if (!config.host || !config.database) {
            throw new Error('Invalid configuration');
        }
    }
    
    // Public static method (singleton pattern)
    static getInstance(config) {
        if (!DatabaseConnection.#instance) {
            DatabaseConnection.#validateConfig(config);
            DatabaseConnection.#instance = new DatabaseConnection();
        }
        return DatabaseConnection.#instance;
    }
    
    // Static method to get connection count
    static getConnectionCount() {
        return DatabaseConnection.#connectionCount;
    }
    
    getConnectionId() {
        return this.#connectionId;
    }
}

const db1 = DatabaseConnection.getInstance({host: 'localhost', database: 'test'});
const db2 = DatabaseConnection.getInstance({});

console.log(db1 === db2);                           // true (singleton)
console.log(DatabaseConnection.getConnectionCount());  // 1
console.log(db1.getConnectionId());                 // 1

// Can't access private static field
console.log(DatabaseConnection.#instance);          // SyntaxError

Example: Static initialization blocks

class Config {
    static apiUrl;
    static timeout;
    static headers;
    
    // Static initialization block
    static {
        // Complex initialization logic
        const env = process.env.NODE_ENV || 'development';
        
        if (env === 'production') {
            this.apiUrl = 'https://api.prod.example.com';
            this.timeout = 5000;
        } else {
            this.apiUrl = 'https://api.dev.example.com';
            this.timeout = 10000;
        }
        
        this.headers = {
            'Content-Type': 'application/json',
            'X-API-Version': '1.0'
        };
        
        console.log(`Config initialized for ${env}`);
    }
    
    // Multiple static blocks are allowed
    static {
        // Additional initialization
        this.initialized = true;
    }
}

console.log(Config.apiUrl);      // URL based on environment
console.log(Config.initialized);  // true

// Private static with static block
class Counter {
    static #count = 0;
    static #instances = [];
    
    static {
        // Initialize private static fields
        console.log('Counter class initialized');
    }
    
    constructor(name) {
        this.name = name;
        Counter.#count++;
        Counter.#instances.push(this);
    }
    
    static getCount() {
        return Counter.#count;
    }
    
    static getInstances() {
        return [...Counter.#instances];
    }
}

const c1 = new Counter('First');
const c2 = new Counter('Second');
console.log(Counter.getCount());  // 2
Private Fields vs WeakMap: Private fields (#) are truly private (not accessible via reflection/bracket notation). WeakMap pattern was used before but less ergonomic. Private fields require declaration. Use #field in obj to check existence. Static blocks enable complex static initialization.

16.6 Optional Chaining and Nullish Coalescing

Optional Chaining Operators

Operator Syntax Description Returns
Property Access obj?.prop Access property if obj not null/undefined Value or undefined
Deep Property obj?.prop?.nested Chain multiple optional accesses Value or undefined
Bracket Notation obj?.[expr] Optional computed property access Value or undefined
Function Call func?.(args) Call function if it exists Value or undefined
Method Call obj.method?.(args) Call method if it exists Value or undefined

Nullish Coalescing vs OR

Operator Syntax Falsy Values Use When
Logical OR a || b Returns b for: false, 0, '', null, undefined, NaN Want any falsy to use default
Nullish Coalescing a ?? b Returns b only for: null, undefined Want to preserve 0, false, ''

Comparison Table

Value value || 'default' value ?? 'default'
null 'default' 'default'
undefined 'default' 'default'
false 'default' false
0 'default' 0
'' 'default' ''
NaN 'default' NaN
'hello' 'hello' 'hello'

Example: Optional chaining basics

// Without optional chaining (verbose)
let name1;
if (user && user.profile && user.profile.name) {
    name1 = user.profile.name;
}

// With optional chaining (concise)
const name2 = user?.profile?.name;

// Practical examples
const user = {
    name: 'Alice',
    address: {
        street: '123 Main St',
        city: 'Boston'
    }
};

// Safe property access
console.log(user?.name);              // "Alice"
console.log(user?.email);             // undefined
console.log(user?.address?.city);     // "Boston"
console.log(user?.address?.zip);      // undefined

// Works with null/undefined
const nullUser = null;
console.log(nullUser?.name);          // undefined (no error)

const undefinedUser = undefined;
console.log(undefinedUser?.name);     // undefined (no error)

// Array access
const users = [
    {name: 'Alice'},
    null,
    {name: 'Bob'}
];

console.log(users[0]?.name);          // "Alice"
console.log(users[1]?.name);          // undefined (no error)
console.log(users[2]?.name);          // "Bob"
console.log(users[10]?.name);         // undefined

// Computed property access
const key = 'address';
console.log(user?.[key]?.city);       // "Boston"

const dynamicKey = null;
console.log(user?.[dynamicKey]);      // undefined

Example: Optional chaining with function calls

// Optional function call
const obj = {
    method() {
        return 'called';
    }
};

console.log(obj.method?.());          // "called"
console.log(obj.missing?.());         // undefined (no error)

// Optional method call on potentially null object
let calculator = null;

console.log(calculator?.add?.(2, 3)); // undefined (no error)

calculator = {
    add(a, b) {
        return a + b;
    }
};

console.log(calculator?.add?.(2, 3)); // 5

// Callback handling
function processData(data, callback) {
    const result = /* process data */;
    callback?.(result);  // Only call if callback exists
}

processData([1, 2, 3]);                    // No error if no callback
processData([1, 2, 3], res => console.log(res));  // Callback called

// Event handlers
button.addEventListener('click', (e) => {
    e.target?.classList?.toggle?.('active');
});

// API calls with optional error handler
async function fetchData(url, options) {
    try {
        const response = await fetch(url);
        const data = await response.json();
        options?.onSuccess?.(data);
        return data;
    } catch (error) {
        options?.onError?.(error);
        throw error;
    }
}

Example: Nullish coalescing operator

// Basic usage
const value1 = null ?? 'default';       // "default"
const value2 = undefined ?? 'default';  // "default"
const value3 = 'hello' ?? 'default';    // "hello"

// Preserves falsy values (unlike ||)
const count = 0 ?? 10;           // 0 (not 10)
const isActive = false ?? true;  // false (not true)
const text = '' ?? 'default';    // '' (not "default")

// Compare with ||
console.log(0 || 10);            // 10
console.log(0 ?? 10);            // 0

console.log(false || true);      // true
console.log(false ?? true);      // false

console.log('' || 'default');    // "default"
console.log('' ?? 'default');    // ''

// Use case: Configuration defaults
function createConfig(options) {
    return {
        // ?? preserves explicit 0, false, ''
        timeout: options?.timeout ?? 5000,
        retries: options?.retries ?? 3,
        debug: options?.debug ?? false,
        prefix: options?.prefix ?? '',
        
        // Bad: || would replace 0, false, ''
        // timeout: options?.timeout || 5000,  // 0 becomes 5000!
    };
}

console.log(createConfig({timeout: 0}));  // {timeout: 0, ...}
console.log(createConfig({}));            // {timeout: 5000, ...}

// Chaining
const result = a ?? b ?? c ?? 'default';

// Cannot mix with && or || without parentheses
// const x = a ?? b || c;     // SyntaxError
const x = (a ?? b) || c;      // OK
const y = a ?? (b || c);      // OK

Example: Combined optional chaining and nullish coalescing

// Powerful combination
const config = {
    api: {
        timeout: 0,
        retries: 3
    }
};

// Get nested value with default
const timeout = config?.api?.timeout ?? 5000;  // 0 (preserved)
const maxRetries = config?.api?.retries ?? 3;  // 3
const debug = config?.api?.debug ?? false;     // false (default)

// User preferences with defaults
function getUserPreference(user, key, defaultValue) {
    return user?.preferences?.[key] ?? defaultValue;
}

const user = {
    preferences: {
        theme: 'dark',
        fontSize: 14,
        notifications: false
    }
};

console.log(getUserPreference(user, 'theme', 'light'));          // "dark"
console.log(getUserPreference(user, 'fontSize', 16));            // 14
console.log(getUserPreference(user, 'notifications', true));     // false
console.log(getUserPreference(user, 'language', 'en'));          // "en"
console.log(getUserPreference(null, 'theme', 'light'));          // "light"

// Real-world: Form field values
function getFieldValue(formData, fieldName, defaultValue = '') {
    return formData?.fields?.[fieldName]?.value ?? defaultValue;
}

// Real-world: API response handling
async function fetchUser(userId) {
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();
    
    return {
        id: data?.id ?? userId,
        name: data?.name ?? 'Unknown',
        email: data?.email ?? 'no-email@example.com',
        avatar: data?.profile?.avatar?.url ?? '/default-avatar.png',
        role: data?.permissions?.role ?? 'user',
        settings: {
            theme: data?.settings?.theme ?? 'light',
            language: data?.settings?.language ?? 'en',
            notifications: data?.settings?.notifications ?? true
        }
    };
}

// Optional call with default result
const result2 = obj.method?.() ?? 'no result';

// Chain optional property with default
const city = user?.address?.city ?? 'Unknown city';
Best Practices: Use ?? when you want to preserve falsy values like 0, false, ''. Use || when any falsy should trigger default. Optional chaining ?. short-circuits (stops evaluating if null/undefined). Both operators improve code readability and reduce defensive checks. Cannot mix ?? with && or || without parentheses.

16.7 Logical Assignment and Numeric Separators

Logical Assignment Operators

Operator Syntax Equivalent To Assigns When
Logical OR Assignment x ||= y x || (x = y) x is falsy
Logical AND Assignment x &&= y x && (x = y) x is truthy
Nullish Assignment x ??= y x ?? (x = y) x is null or undefined

Behavior Comparison

Initial Value x ||= 10 x &&= 10 x ??= 10
null 10 null 10
undefined 10 undefined 10
false 10 false false
0 10 0 0
'' 10 '' ''
5 5 10 5
'hello' 'hello' 10 'hello'
true true 10 true

Numeric Separators

Type Without Separator With Separator Value
Decimal 1000000 1_000_000 1000000
Binary 0b11111111 0b1111_1111 255
Octal 0o777 0o7_7_7 511
Hexadecimal 0xFFFFFF 0xFF_FF_FF 16777215
BigInt 1000000000000n 1_000_000_000_000n 1000000000000n
Decimal 3.14159265 3.141_592_65 3.14159265
Scientific 1e10 1_000e7 10000000000

Example: Logical OR assignment (||=)

// ||= assigns only if left side is falsy

// Initialize if not set
let config = {};
config.timeout ||= 5000;        // Assigns 5000
config.timeout ||= 3000;        // No assignment (already truthy)
console.log(config.timeout);    // 5000

// Default function parameters (alternative pattern)
function greet(options) {
    options ||= {};
    options.name ||= 'Guest';
    options.greeting ||= 'Hello';
    
    return `${options.greeting}, ${options.name}!`;
}

console.log(greet());                           // "Hello, Guest!"
console.log(greet({name: 'Alice'}));           // "Hello, Alice!"
console.log(greet({greeting: 'Hi'}));          // "Hi, Guest!"

// Lazy initialization
class LazyService {
    #cache;
    
    getData() {
        // Initialize cache on first access
        this.#cache ||= this.#loadData();
        return this.#cache;
    }
    
    #loadData() {
        console.log('Loading data...');
        return {loaded: true};
    }
}

const service = new LazyService();
service.getData();  // Logs: "Loading data..."
service.getData();  // No log (cache hit)

// Array default
let items = null;
items ||= [];           // Assigns []
items.push(1, 2, 3);

// Problem: replaces falsy values you want to keep
let count = 0;
count ||= 10;           // Becomes 10 (probably not intended)
console.log(count);     // 10

// Better: use ??= for this case

Example: Logical AND assignment (&&=)

// &&= assigns only if left side is truthy

// Conditional update
let user = {name: 'Alice', admin: true};

// Only update if user is admin
user.admin &&= 'super-admin';
console.log(user.admin);  // "super-admin"

let guest = {name: 'Bob', admin: false};
guest.admin &&= 'super-admin';
console.log(guest.admin);  // false (no assignment)

// Transform if exists
let settings = {
    theme: 'dark',
    language: 'en'
};

// Uppercase language if it exists
settings.language &&= settings.language.toUpperCase();
console.log(settings.language);  // "EN"

settings.missing &&= 'value';
console.log(settings.missing);   // undefined (no assignment)

// Validation chain
function processData(data) {
    // Only proceed if data exists and is valid
    data &&= validateData(data);
    data &&= transformData(data);
    data &&= enrichData(data);
    
    return data;
}

// Increment if enabled
let feature = {
    enabled: true,
    count: 5
};

feature.enabled &&= feature.count++;
console.log(feature);  // {enabled: 5, count: 6}

// Disable feature
feature.enabled = false;
feature.enabled &&= feature.count++;
console.log(feature);  // {enabled: false, count: 6} (no increment)

Example: Nullish assignment (??=)

// ??= assigns only if left side is null or undefined
// Preserves falsy values like 0, false, ''

// Configuration with defaults (preserves explicit falsy)
const config2 = {
    timeout: 0,          // Explicit 0
    retries: undefined,  // Not set
    debug: false,        // Explicit false
    prefix: ''           // Explicit empty string
};

config2.timeout ??= 5000;   // No assignment (0 is not null/undefined)
config2.retries ??= 3;      // Assigns 3
config2.debug ??= true;     // No assignment (false is not null/undefined)
config2.prefix ??= 'api_';  // No assignment ('' is not null/undefined)
config2.cache ??= true;     // Assigns true (cache was undefined)

console.log(config2);
// {
//   timeout: 0,
//   retries: 3,
//   debug: false,
//   prefix: '',
//   cache: true
// }

// Compare with ||=
let a = 0;
let b = 0;

a ||= 10;   // a becomes 10 (0 is falsy)
b ??= 10;   // b stays 0 (0 is not null/undefined)

console.log(a, b);  // 10, 0

// Form field defaults
function setDefaults(formData) {
    formData.name ??= 'Anonymous';
    formData.age ??= 0;           // Preserve explicit 0
    formData.agree ??= false;     // Preserve explicit false
    formData.email ??= '';        // Preserve explicit empty
}

// Nested object initialization
const state = {};
state.user ??= {};
state.user.preferences ??= {};
state.user.preferences.theme ??= 'light';

// Lazy property initialization
class Resource {
    #data;
    
    get data() {
        this.#data ??= this.#load();
        return this.#data;
    }
    
    #load() {
        return {loaded: true};
    }
}

// Array element default
const arr2 = [1, null, 3, undefined, 5];
arr2[1] ??= 2;  // Assigns 2
arr2[3] ??= 4;  // Assigns 4
console.log(arr2);  // [1, 2, 3, 4, 5]

Example: Numeric separators

// Improve readability of large numbers

// Large integers
const million = 1_000_000;
const billion = 1_000_000_000;
const trillion = 1_000_000_000_000;

console.log(million);    // 1000000
console.log(billion);    // 1000000000

// Financial calculations
const price = 1_299.99;
const salary = 75_000;
const budget = 2_500_000;

// Binary (bytes)
const byte = 0b1111_1111;           // 255
const kilobyte = 0b0100_0000_0000;  // 1024

// Hexadecimal (colors)
const white = 0xFF_FF_FF;
const black = 0x00_00_00;
const red = 0xFF_00_00;
const green = 0x00_FF_00;
const blue = 0x00_00_FF;

// Scientific notation
const lightSpeed = 299_792_458;         // m/s
const plancksConstant = 6.626_070_15e-34;  // J⋅s
const avogadro = 6.022_140_76e23;       // mol⁻¹

// BigInt
const veryLarge = 9_007_199_254_740_991n;  // Max safe integer
const hugeNumber = 1_000_000_000_000_000_000_000n;

// Credit card number (for display/parsing)
const cardNumber = '4532_1234_5678_9010';  // String format

// File sizes
const KB = 1_024;
const MB = 1_024 * KB;
const GB = 1_024 * MB;
const TB = 1_024 * GB;

console.log(`1 MB = ${MB} bytes`);  // 1 MB = 1048576 bytes

// Group by thousands (US style)
const population = 328_239_523;

// Group by ten-thousands (China/Japan style)
const population2 = 3_2823_9523;

// Custom grouping for phone numbers (display only as string)
// const phone = 555_123_4567;  // Numbers don't include separators at runtime

// Restrictions:
// Can't start or end with underscore
// const bad1 = _1000;      // SyntaxError
// const bad2 = 1000_;      // SyntaxError
// const bad3 = 1__000;     // SyntaxError (multiple consecutive)
// const bad4 = 1._000;     // SyntaxError (adjacent to decimal point)
// const bad5 = 1e_10;      // SyntaxError (adjacent to exponent)

// Valid placements
const ok1 = 1_000.500_5;  // OK
const ok2 = 0x1_2_3;      // OK
const ok3 = 1_000e5;      // OK (1000 * 10^5)
Summary: Logical assignments (||=, &&=, ??=) provide concise syntax for conditional assignment. Use ??= when preserving falsy values matters. Numeric separators improve code readability for large numbers, have no runtime effect, and work with all numeric literals (decimal, binary, hex, BigInt). Cannot place separators at start/end or adjacent to decimal/exponent.

Section 16 Summary

  • Map/Set: Collections with any key types (Map) or unique values (Set); O(1) lookup; iteration order preserved; methods: set, get, has, delete, clear
  • Proxy: Intercept object operations with 13 traps (get, set, has, apply, construct, etc.); use Reflect for proper forwarding; enables metaprogramming
  • Generators: Pausable functions with function* and yield; bidirectional communication; yield* delegation; lazy evaluation
  • Modules: Static imports (hoisted, analyzed pre-execution); dynamic imports (async, code splitting); live bindings; singletons; named/default exports
  • Private Fields: True privacy with #field; private methods/getters/setters; static private fields; #field in obj check; static initialization blocks
  • Optional Chaining: Safe property access with ?.; works with properties, methods, computed keys; short-circuits on null/undefined
  • Nullish Coalescing: Default values with ??; preserves 0, false, ''; only replaces null/undefined (unlike ||)
  • Logical Assignment: ||= (if falsy), &&= (if truthy), ??= (if null/undefined); concise conditional assignment
  • Numeric Separators: Underscore _ in numbers for readability; no runtime effect; works with all bases (decimal, binary, hex, BigInt)

17. Classes and Object-Oriented Programming

17.1 Class Declaration and Constructor Methods

Class Declaration Syntax

Feature Syntax Description
Basic Class class Name {} Class declaration (not hoisted)
Constructor constructor(params) {} Initialize instance (only one per class)
Instance Method method() {} Method on prototype
Static Method static method() {} Method on class itself
Field Declaration field = value; Public instance field
Static Field static field = value; Static field on class
Private Field #field = value; Private instance field
Getter get prop() {} Property getter accessor
Setter set prop(val) {} Property setter accessor

Constructor Features

Feature Description Behavior
Return Value Implicit return of this Explicit object return overrides instance
Super Call Must call super() in derived class Before accessing this in derived constructor
Parameters Support all parameter types Default, rest, destructuring parameters
Omitting Constructor Default constructor created Base: constructor() {}; Derived: super(...args)
Multiple Constructors Not supported Use factory methods or optional parameters

Example: Basic class declaration

// Simple class
class Person {
    // Constructor
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    // Instance method
    greet() {
        return `Hello, I'm ${this.name}`;
    }
    
    // Instance method
    getAge() {
        return this.age;
    }
}

// Create instances
const alice = new Person('Alice', 30);
const bob = new Person('Bob', 25);

console.log(alice.greet());     // "Hello, I'm Alice"
console.log(alice.getAge());    // 30
console.log(bob.greet());       // "Hello, I'm Bob"

// Methods are on prototype
console.log(alice.greet === bob.greet);  // true (shared method)

// instanceof check
console.log(alice instanceof Person);    // true

// Class is not hoisted
// const p = new MyClass();  // ReferenceError
// class MyClass {}

// Classes are strict mode by default
class StrictClass {
    constructor() {
        // Implicit strict mode
        // x = 10;  // ReferenceError (no implicit globals)
    }
}

Example: Constructor with field declarations

// Class with field declarations (ES2022)
class User {
    // Public fields (initialized on instance)
    id = 0;
    name = 'Anonymous';
    role = 'user';
    createdAt = Date.now();
    
    // Constructor overrides field defaults
    constructor(id, name, role) {
        this.id = id;
        this.name = name;
        if (role) this.role = role;
    }
    
    getInfo() {
        return `${this.name} (${this.role})`;
    }
}

const user1 = new User(1, 'Alice', 'admin');
console.log(user1.getInfo());        // "Alice (admin)"
console.log(user1.createdAt);        // timestamp

const user2 = new User(2, 'Bob');
console.log(user2.role);             // "user" (default)

// Fields are instance properties (not on prototype)
console.log(user1.hasOwnProperty('name'));  // true
console.log('name' in User.prototype);      // false

// Methods are on prototype
console.log(user1.hasOwnProperty('getInfo'));  // false
console.log('getInfo' in User.prototype);      // true

Example: Constructor patterns and validation

// Constructor with validation
class Rectangle {
    constructor(width, height) {
        if (width <= 0 || height <= 0) {
            throw new Error('Dimensions must be positive');
        }
        this.width = width;
        this.height = height;
    }
    
    area() {
        return this.width * this.height;
    }
}

const rect = new Rectangle(10, 5);
console.log(rect.area());  // 50

// const invalid = new Rectangle(-5, 10);  // Error

// Constructor with default parameters
class Point {
    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
    
    distance(other) {
        const dx = this.x - other.x;
        const dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}

const p1 = new Point(3, 4);
const p2 = new Point();  // (0, 0)
console.log(p1.distance(p2));  // 5

// Constructor with destructuring
class Config {
    constructor({host, port = 3000, secure = false} = {}) {
        this.host = host || 'localhost';
        this.port = port;
        this.secure = secure;
    }
    
    getUrl() {
        const protocol = this.secure ? 'https' : 'http';
        return `${protocol}://${this.host}:${this.port}`;
    }
}

const config1 = new Config({host: 'example.com', secure: true});
console.log(config1.getUrl());  // "https://example.com:3000"

const config2 = new Config();
console.log(config2.getUrl());  // "http://localhost:3000"

// Constructor with rest parameters
class Team {
    constructor(name, ...members) {
        this.name = name;
        this.members = members;
    }
    
    addMember(member) {
        this.members.push(member);
    }
}

const team = new Team('Development', 'Alice', 'Bob', 'Charlie');
console.log(team.members);  // ['Alice', 'Bob', 'Charlie']

Example: Constructor return behavior

// Normal constructor - returns instance
class Normal {
    constructor(value) {
        this.value = value;
    }
}

const n = new Normal(5);
console.log(n instanceof Normal);  // true
console.log(n.value);              // 5

// Constructor returning object - overrides instance
class Custom {
    constructor(value) {
        this.value = value;
        
        // Returning object overrides the instance
        return {
            value: value * 2,
            custom: true
        };
    }
}

const c = new Custom(5);
console.log(c instanceof Custom);  // false (returned object)
console.log(c.value);              // 10
console.log(c.custom);             // true

// Returning primitive - ignored
class Primitive {
    constructor(value) {
        this.value = value;
        return 42;  // Ignored
    }
}

const p = new Primitive(5);
console.log(p instanceof Primitive);  // true
console.log(p.value);                 // 5

// Singleton pattern with constructor
class Singleton {
    static #instance;
    
    constructor() {
        if (Singleton.#instance) {
            return Singleton.#instance;
        }
        
        this.createdAt = Date.now();
        Singleton.#instance = this;
    }
}

const s1 = new Singleton();
const s2 = new Singleton();
console.log(s1 === s2);  // true (same instance)
Key Points: Classes are not hoisted (temporal dead zone). Constructor runs when new is called. Only one constructor per class. Fields are instance properties; methods are prototype properties. Always in strict mode. Can return object from constructor to override instance.

17.2 Instance Methods and Static Methods

Method Types Comparison

Method Type Syntax Location Access via this refers to
Instance Method method() {} Class.prototype instance.method() Instance object
Static Method static method() {} Class itself Class.method() Class constructor
Private Instance #method() {} Class (hidden) this.#method() (internal) Instance object
Private Static static #method() {} Class (hidden) Class.#method() (internal) Class constructor

Method Features

Feature Instance Methods Static Methods
Access to this Instance properties and methods Static properties and methods
Inheritance Inherited by subclasses Inherited by subclasses
Shared Shared among all instances One copy on class
Use Case Instance-specific behavior Utility functions, factories, helpers
Call without new ❌ Needs instance ✓ Can call directly

Example: Instance methods

class Calculator {
    constructor(value = 0) {
        this.value = value;
    }
    
    // Instance methods - operate on instance data
    add(n) {
        this.value += n;
        return this;  // Method chaining
    }
    
    subtract(n) {
        this.value -= n;
        return this;
    }
    
    multiply(n) {
        this.value *= n;
        return this;
    }
    
    divide(n) {
        if (n === 0) throw new Error('Division by zero');
        this.value /= n;
        return this;
    }
    
    getValue() {
        return this.value;
    }
    
    reset() {
        this.value = 0;
        return this;
    }
}

const calc = new Calculator(10);
const result = calc.add(5).multiply(2).subtract(10).getValue();
console.log(result);  // 20

// Methods are on prototype
console.log(calc.hasOwnProperty('add'));        // false
console.log('add' in Calculator.prototype);     // true

// Shared across instances
const calc2 = new Calculator();
console.log(calc.add === calc2.add);  // true (same function)

Example: Static methods

class MathUtils {
    // Static methods - utility functions
    static add(a, b) {
        return a + b;
    }
    
    static multiply(a, b) {
        return a * b;
    }
    
    static max(...numbers) {
        return Math.max(...numbers);
    }
    
    static clamp(value, min, max) {
        return Math.min(Math.max(value, min), max);
    }
    
    // Static method accessing other static methods
    static average(...numbers) {
        const sum = numbers.reduce((a, b) => this.add(a, b), 0);
        return sum / numbers.length;
    }
}

// Call static methods on class (not instances)
console.log(MathUtils.add(5, 3));           // 8
console.log(MathUtils.max(1, 5, 3, 9, 2));  // 9
console.log(MathUtils.clamp(15, 0, 10));    // 10
console.log(MathUtils.average(1, 2, 3, 4)); // 2.5

// Static methods are on class, not prototype
console.log('add' in MathUtils);            // true
console.log('add' in MathUtils.prototype);  // false

// Can't call on instances
const util = new MathUtils();
// util.add(1, 2);  // TypeError: util.add is not a function

// Factory pattern with static methods
class User {
    constructor(name, email, role) {
        this.name = name;
        this.email = email;
        this.role = role;
    }
    
    // Static factory methods
    static createAdmin(name, email) {
        return new User(name, email, 'admin');
    }
    
    static createGuest(name) {
        return new User(name, `${name}@guest.local`, 'guest');
    }
    
    static fromJSON(json) {
        const data = JSON.parse(json);
        return new User(data.name, data.email, data.role);
    }
}

const admin = User.createAdmin('Alice', 'alice@example.com');
const guest = User.createGuest('Bob');
const parsed = User.fromJSON('{"name":"Charlie","email":"c@e.com","role":"user"}');

console.log(admin.role);   // "admin"
console.log(guest.email);  // "bob@guest.local"

Example: Mixing instance and static methods

class Counter {
    // Static field for counting all instances
    static totalInstances = 0;
    static #allCounters = [];
    
    // Instance field
    value = 0;
    
    constructor(initialValue = 0) {
        this.value = initialValue;
        Counter.totalInstances++;
        Counter.#allCounters.push(this);
    }
    
    // Instance methods
    increment() {
        this.value++;
        return this;
    }
    
    decrement() {
        this.value--;
        return this;
    }
    
    getValue() {
        return this.value;
    }
    
    // Static methods
    static getTotalInstances() {
        return Counter.totalInstances;
    }
    
    static getSumOfAll() {
        return Counter.#allCounters.reduce((sum, counter) => sum + counter.value, 0);
    }
    
    static resetAll() {
        Counter.#allCounters.forEach(counter => counter.value = 0);
    }
    
    static getMaxValue() {
        if (Counter.#allCounters.length === 0) return null;
        return Math.max(...Counter.#allCounters.map(c => c.value));
    }
}

const c1 = new Counter(5);
const c2 = new Counter(10);
const c3 = new Counter(3);

console.log(Counter.getTotalInstances());  // 3
console.log(Counter.getSumOfAll());        // 18

c1.increment().increment();
console.log(Counter.getSumOfAll());        // 20
console.log(Counter.getMaxValue());        // 10

Counter.resetAll();
console.log(c1.getValue());                // 0
console.log(c2.getValue());                // 0

Example: Static method inheritance

class Animal {
    constructor(name) {
        this.name = name;
    }
    
    // Instance method
    speak() {
        return `${this.name} makes a sound`;
    }
    
    // Static method
    static info() {
        return 'Animals are living organisms';
    }
    
    static compare(a, b) {
        return a.name.localeCompare(b.name);
    }
}

class Dog extends Animal {
    // Override instance method
    speak() {
        return `${this.name} barks`;
    }
    
    // Static method in derived class
    static info() {
        return super.info() + ', and dogs are mammals';
    }
}

// Static methods are inherited
console.log(Dog.info());  // "Animals are living organisms, and dogs are mammals"

const dog1 = new Dog('Max');
const dog2 = new Dog('Buddy');

// Inherited static method works with derived class instances
console.log(Dog.compare(dog1, dog2));  // Compares names

// Instance methods
console.log(dog1.speak());  // "Max barks"
Best Practices: Use instance methods for behavior specific to each object. Use static methods for utilities, factories, and class-level operations. Static methods can't access instance properties (no this referring to instance). Both instance and static methods are inherited. Use static for constants, configuration, and helper functions.

17.3 Class Inheritance and super Keyword

Inheritance Syntax

Syntax Description Usage
extends class Child extends Parent Create subclass inheriting from parent
super() super(args) Call parent constructor (required in child constructor)
super.method() super.methodName(args) Call parent's instance method
super.property super.prop Access parent's property or method
Static super super.staticMethod() Call parent's static method (in static context)

super Rules

Rule Description Example/Note
Constructor super Must call super() before accessing this ReferenceError if accessing this before super()
No constructor Default constructor calls super(...args) Automatically generated if omitted
Return before super Can return object without calling super() Only if returning object explicitly
Method context super only in class methods Not in arrow functions or regular functions
Static super In static method, super refers to parent class Access static methods/properties

Example: Basic inheritance

// Parent class
class Animal {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    speak() {
        return `${this.name} makes a sound`;
    }
    
    getAge() {
        return this.age;
    }
}

// Child class
class Dog extends Animal {
    constructor(name, age, breed) {
        // Must call super() before accessing this
        super(name, age);  // Call parent constructor
        this.breed = breed;
    }
    
    // Override parent method
    speak() {
        return `${this.name} barks`;
    }
    
    // New method specific to Dog
    fetch() {
        return `${this.name} fetches the ball`;
    }
}

const dog = new Dog('Max', 3, 'Labrador');
console.log(dog.name);      // "Max" (from parent)
console.log(dog.breed);     // "Labrador" (from child)
console.log(dog.speak());   // "Max barks" (overridden)
console.log(dog.getAge());  // 3 (inherited)
console.log(dog.fetch());   // "Max fetches the ball" (child method)

// Prototype chain
console.log(dog instanceof Dog);      // true
console.log(dog instanceof Animal);   // true
console.log(dog instanceof Object);   // true

// Inheritance chain
console.log(Object.getPrototypeOf(dog) === Dog.prototype);  // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype);  // true

Example: super in methods

class Shape {
    constructor(color) {
        this.color = color;
    }
    
    describe() {
        return `A ${this.color} shape`;
    }
    
    getColor() {
        return this.color;
    }
}

class Circle extends Shape {
    constructor(color, radius) {
        super(color);
        this.radius = radius;
    }
    
    // Call parent method and extend
    describe() {
        const base = super.describe();  // Call parent's describe()
        return `${base} with radius ${this.radius}`;
    }
    
    area() {
        return Math.PI * this.radius ** 2;
    }
}

const circle = new Circle('red', 5);
console.log(circle.describe());  // "A red shape with radius 5"
console.log(circle.getColor());  // "red" (inherited)
console.log(circle.area());      // 78.54...

// More complex example
class Rectangle extends Shape {
    constructor(color, width, height) {
        super(color);
        this.width = width;
        this.height = height;
    }
    
    describe() {
        // Extend parent's description
        return super.describe() + ` (${this.width}x${this.height})`;
    }
    
    area() {
        return this.width * this.height;
    }
    
    // Method using both parent and child properties
    getFullDescription() {
        return `${super.describe()}, area: ${this.area()}`;
    }
}

const rect = new Rectangle('blue', 10, 5);
console.log(rect.describe());           // "A blue shape (10x5)"
console.log(rect.getFullDescription()); // "A blue shape, area: 50"

Example: Multi-level inheritance

// Three levels of inheritance
class LivingBeing {
    constructor(name) {
        this.name = name;
        this.alive = true;
    }
    
    breathe() {
        return `${this.name} is breathing`;
    }
}

class Animal2 extends LivingBeing {
    constructor(name, species) {
        super(name);
        this.species = species;
    }
    
    move() {
        return `${this.name} is moving`;
    }
    
    identify() {
        return `${this.name} is a ${this.species}`;
    }
}

class Dog2 extends Animal2 {
    constructor(name, breed) {
        super(name, 'dog');  // Species is always 'dog'
        this.breed = breed;
    }
    
    bark() {
        return `${this.name} barks`;
    }
    
    // Override with super call
    identify() {
        return super.identify() + ` (${this.breed})`;
    }
}

const dog2 = new Dog2('Rex', 'German Shepherd');
console.log(dog2.breathe());   // "Rex is breathing" (from LivingBeing)
console.log(dog2.move());      // "Rex is moving" (from Animal2)
console.log(dog2.bark());      // "Rex barks" (from Dog2)
console.log(dog2.identify());  // "Rex is a dog (German Shepherd)"

// Prototype chain
console.log(dog2 instanceof Dog2);         // true
console.log(dog2 instanceof Animal2);      // true
console.log(dog2 instanceof LivingBeing);  // true

Example: Static method inheritance

class BaseModel {
    static tableName = 'base';
    
    constructor(data) {
        this.data = data;
    }
    
    // Static method
    static findAll() {
        console.log(`Finding all from ${this.tableName}`);
        return [];
    }
    
    static findById(id) {
        console.log(`Finding ${id} from ${this.tableName}`);
        return null;
    }
    
    // Static method calling other static methods
    static findOrCreate(id) {
        let record = this.findById(id);
        if (!record) {
            console.log(`Creating new record in ${this.tableName}`);
            record = new this({id});
        }
        return record;
    }
}

class User2 extends BaseModel {
    static tableName = 'users';
    
    // Override static method
    static findById(id) {
        console.log(`[User] Finding user ${id}`);
        return super.findById(id);  // Can call parent's static method
    }
    
    // New static method
    static findByEmail(email) {
        console.log(`Finding user with email ${email} from ${this.tableName}`);
        return null;
    }
}

// Static methods are inherited
User2.findAll();         // "Finding all from users"
User2.findById(1);       // "[User] Finding user 1" then "Finding 1 from users"
User2.findByEmail('a@b.com');  // "Finding user with email a@b.com from users"

// Static method using this correctly
const user = User2.findOrCreate(5);
console.log(user instanceof User2);  // true
Important: super() must be called before accessing this in derived constructor. super in methods refers to parent's prototype. super in static methods refers to parent class. Method overriding creates new method; use super.method() to call parent version. Prototype chain: Child → Parent → ... → Object.

17.4 Private Fields and Private Methods

Private Member Types

Type Syntax Access Inheritance
Private Field #field = value Only within class body Not inherited
Private Method #method() {} Only within class body Not inherited
Private Getter get #prop() {} Only within class body Not inherited
Private Setter set #prop(v) {} Only within class body Not inherited
Private Static Field static #field = value Only within class (static context) Not inherited
Private Static Method static #method() {} Only within class (static context) Not inherited

Private vs Public Comparison

Feature Public Private (#)
Access Outside Class ✓ Yes ✗ No (SyntaxError)
Access via this this.field this.#field
Inheritance ✓ Inherited ✗ Not inherited
Bracket Notation ✓ obj['field'] ✗ Not accessible
Reflection ✓ Object.keys, etc. ✗ Not enumerable
Name Collision Can collide with child Each class has separate namespace

Example: Private fields and methods

class BankAccount {
    // Private fields (must be declared)
    #balance = 0;
    #accountNumber;
    #transactions = [];
    
    // Public field
    accountType = 'checking';
    
    constructor(accountNumber, initialBalance) {
        this.#accountNumber = accountNumber;
        this.#balance = initialBalance;
        this.#logTransaction('Initial deposit', initialBalance);
    }
    
    // Private method
    #logTransaction(type, amount) {
        this.#transactions.push({
            type,
            amount,
            date: new Date(),
            balance: this.#balance
        });
    }
    
    // Private validation method
    #validateAmount(amount) {
        if (typeof amount !== 'number' || amount <= 0) {
            throw new Error('Invalid amount');
        }
    }
    
    // Public methods using private fields/methods
    deposit(amount) {
        this.#validateAmount(amount);
        this.#balance += amount;
        this.#logTransaction('Deposit', amount);
        return this.#balance;
    }
    
    withdraw(amount) {
        this.#validateAmount(amount);
        
        if (amount > this.#balance) {
            throw new Error('Insufficient funds');
        }
        
        this.#balance -= amount;
        this.#logTransaction('Withdrawal', -amount);
        return this.#balance;
    }
    
    getBalance() {
        return this.#balance;
    }
    
    getTransactionHistory() {
        // Return copy to prevent external modification
        return [...this.#transactions];
    }
    
    // Private getter
    get #formattedBalance() {
        return `${this.#balance.toFixed(2)}`;
    }
    
    toString() {
        return `Account ${this.#accountNumber}: ${this.#formattedBalance}`;
    }
}

const account = new BankAccount('1234567890', 1000);
console.log(account.getBalance());  // 1000

account.deposit(500);
console.log(account.getBalance());  // 1500

account.withdraw(200);
console.log(account.getBalance());  // 1300

// Private fields are truly private
console.log(account.#balance);       // SyntaxError
console.log(account['#balance']);    // undefined
console.log(account.balance);        // undefined

// Private methods not accessible
// account.#logTransaction('test', 100);  // SyntaxError

console.log(account.toString());  // "Account 1234567890: $1300.00"

Example: Private static members

class IDGenerator {
    // Private static field
    static #currentId = 0;
    static #usedIds = new Set();
    
    // Public instance field
    id;
    
    constructor() {
        this.id = IDGenerator.#generateId();
    }
    
    // Private static method
    static #generateId() {
        let newId;
        do {
            newId = ++IDGenerator.#currentId;
        } while (IDGenerator.#usedIds.has(newId));
        
        IDGenerator.#usedIds.add(newId);
        return newId;
    }
    
    // Public static method
    static releaseId(id) {
        IDGenerator.#usedIds.delete(id);
    }
    
    // Public static method accessing private static
    static getTotalGenerated() {
        return IDGenerator.#currentId;
    }
    
    static getActiveCount() {
        return IDGenerator.#usedIds.size;
    }
}

const obj1 = new IDGenerator();
const obj2 = new IDGenerator();
const obj3 = new IDGenerator();

console.log(obj1.id);  // 1
console.log(obj2.id);  // 2
console.log(obj3.id);  // 3

console.log(IDGenerator.getTotalGenerated());  // 3
console.log(IDGenerator.getActiveCount());     // 3

IDGenerator.releaseId(2);
console.log(IDGenerator.getActiveCount());     // 2

// Private static members not accessible
// console.log(IDGenerator.#currentId);  // SyntaxError

Example: Private fields with inheritance

class Base {
    #privateBase = 'base private';
    publicBase = 'base public';
    
    getPrivateBase() {
        return this.#privateBase;
    }
}

class Derived extends Base {
    #privateDerived = 'derived private';
    publicDerived = 'derived public';
    
    // Can have same name as parent's private field (separate namespace)
    #privateBase = 'derived has its own #privateBase';
    
    getPrivateDerived() {
        return this.#privateDerived;
    }
    
    getDerivedPrivateBase() {
        return this.#privateBase;  // Accesses Derived's #privateBase
    }
    
    getAllInfo() {
        return {
            // Can access parent's private via parent's public method
            parentPrivate: this.getPrivateBase(),  // "base private"
            // Own private fields
            derivedPrivate: this.#privateDerived,
            derivedPrivateBase: this.#privateBase,
            // Public fields
            publicBase: this.publicBase,
            publicDerived: this.publicDerived
        };
    }
}

const derived = new Derived();
console.log(derived.getAllInfo());
// {
//   parentPrivate: "base private",
//   derivedPrivate: "derived private",
//   derivedPrivateBase: "derived has its own #privateBase",
//   publicBase: "base public",
//   publicDerived: "derived public"
// }

// Each class has separate private namespace
console.log(derived.getPrivateBase());        // "base private"
console.log(derived.getDerivedPrivateBase()); // "derived has its own #privateBase"

Example: Practical private member use case

class SecureCache {
    // Private storage
    #cache = new Map();
    #accessLog = [];
    #maxSize = 100;
    
    // Private method for logging
    #log(operation, key) {
        this.#accessLog.push({
            operation,
            key,
            timestamp: Date.now()
        });
        
        // Keep log size manageable
        if (this.#accessLog.length > 1000) {
            this.#accessLog = this.#accessLog.slice(-500);
        }
    }
    
    // Private method for size check
    #checkSize() {
        if (this.#cache.size >= this.#maxSize) {
            // Remove oldest entry (first key)
            const firstKey = this.#cache.keys().next().value;
            this.#cache.delete(firstKey);
        }
    }
    
    // Public API
    set(key, value) {
        this.#checkSize();
        this.#cache.set(key, value);
        this.#log('set', key);
    }
    
    get(key) {
        this.#log('get', key);
        return this.#cache.get(key);
    }
    
    has(key) {
        return this.#cache.has(key);
    }
    
    delete(key) {
        this.#log('delete', key);
        return this.#cache.delete(key);
    }
    
    clear() {
        this.#cache.clear();
        this.#log('clear', null);
    }
    
    // Private getter
    get #stats() {
        return {
            size: this.#cache.size,
            maxSize: this.#maxSize,
            accessCount: this.#accessLog.length
        };
    }
    
    getStats() {
        return {...this.#stats};  // Return copy
    }
    
    getRecentAccess(count = 10) {
        return this.#accessLog.slice(-count);
    }
}

const cache = new SecureCache();
cache.set('key1', 'value1');
cache.set('key2', 'value2');
cache.get('key1');

console.log(cache.getStats());
// {size: 2, maxSize: 100, accessCount: 3}

console.log(cache.getRecentAccess(5));
// Recent access log

// Internal state completely private
// cache.#cache  // SyntaxError
// cache.#log()  // SyntaxError
Key Points: Private members use # prefix and must be declared in class body. Truly private (not accessible outside class body). Not inherited by subclasses. Each class has separate private namespace (can have same names). Use for encapsulation, internal implementation details, sensitive data. Check existence with #field in obj (inside class only).

17.5 Getters and Setters in Classes

Accessor Types

Accessor Syntax Purpose Usage
Getter get propName() {} Read computed property obj.propName
Setter set propName(value) {} Write with validation obj.propName = value
Private Getter get #propName() {} Private computed property this.#propName
Private Setter set #propName(v) {} Private validated write this.#propName = v
Static Getter static get propName() {} Class-level computed property Class.propName
Static Setter static set propName(v) {} Class-level validated write Class.propName = v

Getter/Setter Features

Feature Description Behavior
Access Syntax Use like property (no parentheses) obj.prop not obj.prop()
Computed Values Calculate value on access No storage, derived from other properties
Validation Validate before setting Throw error or coerce value
Lazy Evaluation Compute only when accessed Can cache result
Side Effects Can trigger actions on get/set Logging, updates, notifications
Read-only Getter without setter Assignment ignored in non-strict, error in strict
Write-only Setter without getter Reading returns undefined

Example: Basic getters and setters

class Person {
    constructor(firstName, lastName, birthYear) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.birthYear = birthYear;
    }
    
    // Getter - computed property
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }
    
    // Setter - split and assign
    set fullName(name) {
        const [first, last] = name.split(' ');
        this.firstName = first;
        this.lastName = last;
    }
    
    // Getter - computed from other property
    get age() {
        return new Date().getFullYear() - this.birthYear;
    }
    
    // Read-only (getter without setter)
    get initials() {
        return `${this.firstName[0]}.${this.lastName[0]}.`;
    }
}

const person = new Person('John', 'Doe', 1990);

// Access like properties (no parentheses)
console.log(person.fullName);    // "John Doe"
console.log(person.age);         // 35 (computed)
console.log(person.initials);    // "J.D."

// Use setter
person.fullName = 'Jane Smith';
console.log(person.firstName);   // "Jane"
console.log(person.lastName);    // "Smith"
console.log(person.fullName);    // "Jane Smith"

// Read-only property
person.initials = 'X.Y.';        // Ignored (non-strict) or error (strict)
console.log(person.initials);    // Still "J.S."

// Age is computed each time
console.log(person.age);         // Computed from current year

Example: Validation with setters

class Product {
    #price = 0;
    #quantity = 0;
    
    constructor(name) {
        this.name = name;
    }
    
    // Getter returns private field
    get price() {
        return this.#price;
    }
    
    // Setter with validation
    set price(value) {
        if (typeof value !== 'number') {
            throw new TypeError('Price must be a number');
        }
        if (value < 0) {
            throw new RangeError('Price cannot be negative');
        }
        this.#price = value;
    }
    
    get quantity() {
        return this.#quantity;
    }
    
    set quantity(value) {
        if (!Number.isInteger(value)) {
            throw new TypeError('Quantity must be an integer');
        }
        if (value < 0) {
            throw new RangeError('Quantity cannot be negative');
        }
        this.#quantity = value;
    }
    
    // Computed property
    get total() {
        return this.#price * this.#quantity;
    }
    
    // Computed with formatting
    get formattedTotal() {
        return `${this.total.toFixed(2)}`;
    }
}

const product = new Product('Widget');

product.price = 19.99;
product.quantity = 5;

console.log(product.price);          // 19.99
console.log(product.quantity);       // 5
console.log(product.total);          // 99.95
console.log(product.formattedTotal); // "$99.95"

// Validation in action
try {
    product.price = -10;  // RangeError: Price cannot be negative
} catch (e) {
    console.error(e.message);
}

try {
    product.quantity = 3.5;  // TypeError: Quantity must be an integer
} catch (e) {
    console.error(e.message);
}

Example: Lazy evaluation and caching

class DataProcessor {
    #data;
    #processedCache = null;
    #isDirty = true;
    
    constructor(data) {
        this.#data = data;
    }
    
    // Setter invalidates cache
    set data(newData) {
        this.#data = newData;
        this.#isDirty = true;  // Mark cache as invalid
        this.#processedCache = null;
    }
    
    get data() {
        return this.#data;
    }
    
    // Lazy evaluation with caching
    get processed() {
        if (this.#isDirty || this.#processedCache === null) {
            console.log('Computing processed data...');
            // Expensive operation
            this.#processedCache = this.#data.map(x => x * 2);
            this.#isDirty = false;
        } else {
            console.log('Using cached data');
        }
        return this.#processedCache;
    }
    
    // Read-only computed statistics
    get stats() {
        const data = this.processed;
        return {
            count: data.length,
            sum: data.reduce((a, b) => a + b, 0),
            average: data.reduce((a, b) => a + b, 0) / data.length,
            min: Math.min(...data),
            max: Math.max(...data)
        };
    }
}

const processor = new DataProcessor([1, 2, 3, 4, 5]);

console.log(processor.processed);  // Logs: "Computing...", returns [2, 4, 6, 8, 10]
console.log(processor.processed);  // Logs: "Using cached data", returns same

console.log(processor.stats);      // Computes statistics

processor.data = [10, 20, 30];     // Invalidates cache
console.log(processor.processed);  // Logs: "Computing...", returns [20, 40, 60]

Example: Static getters and setters

class Configuration {
    static #config = {
        apiUrl: 'https://api.example.com',
        timeout: 5000,
        debug: false
    };
    
    // Static getter
    static get apiUrl() {
        return Configuration.#config.apiUrl;
    }
    
    // Static setter with validation
    static set apiUrl(url) {
        if (!url.startsWith('http://') && !url.startsWith('https://')) {
            throw new Error('URL must start with http:// or https://');
        }
        Configuration.#config.apiUrl = url;
    }
    
    static get timeout() {
        return Configuration.#config.timeout;
    }
    
    static set timeout(ms) {
        if (ms < 0) throw new Error('Timeout cannot be negative');
        Configuration.#config.timeout = ms;
    }
    
    static get debug() {
        return Configuration.#config.debug;
    }
    
    static set debug(value) {
        Configuration.#config.debug = Boolean(value);
    }
    
    // Read-only computed configuration
    static get summary() {
        return `API: ${Configuration.apiUrl}, Timeout: ${Configuration.timeout}ms`;
    }
    
    // Method to get all config (snapshot)
    static getAll() {
        return {...Configuration.#config};
    }
}

// Access static getters/setters on class
console.log(Configuration.apiUrl);     // "https://api.example.com"
console.log(Configuration.timeout);    // 5000

Configuration.apiUrl = 'https://new-api.com';
Configuration.timeout = 10000;
Configuration.debug = true;

console.log(Configuration.summary);
// "API: https://new-api.com, Timeout: 10000ms"

console.log(Configuration.getAll());
// {apiUrl: "https://new-api.com", timeout: 10000, debug: true}

Example: Private getters and setters

class Temperature {
    #celsius = 0;
    
    constructor(celsius) {
        this.celsius = celsius;  // Use public setter
    }
    
    // Public getter/setter for Celsius
    get celsius() {
        return this.#celsius;
    }
    
    set celsius(value) {
        if (value < -273.15) {
            throw new Error('Below absolute zero');
        }
        this.#celsius = value;
    }
    
    // Public getter/setter for Fahrenheit
    get fahrenheit() {
        return this.#celsius * 9/5 + 32;
    }
    
    set fahrenheit(value) {
        this.celsius = (value - 32) * 5/9;  // Use celsius setter
    }
    
    // Private getter - internal formatting
    get #formatted() {
        return {
            c: `${this.#celsius.toFixed(1)}°C`,
            f: `${this.fahrenheit.toFixed(1)}°F`
        };
    }
    
    // Public method using private getter
    toString() {
        const fmt = this.#formatted;
        return `${fmt.c} (${fmt.f})`;
    }
    
    // Computed properties
    get kelvin() {
        return this.#celsius + 273.15;
    }
    
    get description() {
        if (this.#celsius < 0) return 'Freezing';
        if (this.#celsius < 10) return 'Cold';
        if (this.#celsius < 20) return 'Cool';
        if (this.#celsius < 30) return 'Warm';
        return 'Hot';
    }
}

const temp = new Temperature(25);
console.log(temp.celsius);      // 25
console.log(temp.fahrenheit);   // 77
console.log(temp.kelvin);       // 298.15
console.log(temp.description);  // "Warm"
console.log(temp.toString());   // "25.0°C (77.0°F)"

temp.fahrenheit = 32;  // Set via Fahrenheit
console.log(temp.celsius);      // 0
console.log(temp.description);  // "Freezing"
Best Practices: Use getters for computed properties and data transformation. Use setters for validation and side effects. Getters should be fast (cache if expensive). Avoid side effects in getters when possible. Combine with private fields for true encapsulation. Setters can coerce values or throw errors.

17.6 Class Expressions and Anonymous Classes

Class Expression Types

Type Syntax Name Use Case
Named Class Expression const C = class Name {} Name only inside class Recursion, debugging
Anonymous Class Expression const C = class {} No internal name Simple assignments
IIFE Class (class {})() Anonymous Immediate instantiation
Inline Class func(class {}) Anonymous Factory functions, callbacks

Class Expression vs Declaration

Feature Class Declaration Class Expression
Hoisting Not hoisted (TDZ) Not hoisted (like var/let/const)
Name Required Optional
Assignment Creates binding Can assign to variable
Inline Usage ❌ Cannot use inline ✓ Can use as expression
Conditional ❌ Cannot be conditional ✓ Can be conditional

Example: Class expressions

// Anonymous class expression
const Person1 = class {
    constructor(name) {
        this.name = name;
    }
    
    greet() {
        return `Hello, I'm ${this.name}`;
    }
};

const p1 = new Person1('Alice');
console.log(p1.greet());  // "Hello, I'm Alice"
console.log(Person1.name);  // "" (anonymous)

// Named class expression
const Person2 = class PersonClass {
    constructor(name) {
        this.name = name;
    }
    
    greet() {
        // Name available inside class
        return `Hello from ${PersonClass.name}`;
    }
    
    clone() {
        // Can use internal name for recursion
        return new PersonClass(this.name);
    }
};

const p2 = new Person2('Bob');
console.log(p2.greet());  // "Hello from PersonClass"
console.log(Person2.name);  // "PersonClass"

// Internal name not accessible outside
// console.log(PersonClass);  // ReferenceError

// Reassignment
const OldPerson = Person2;
const Person3 = class extends OldPerson {
    greet() {
        return super.greet() + ' (extended)';
    }
};

const p3 = new Person3('Charlie');
console.log(p3.greet());  // "Hello from PersonClass (extended)"

Example: Conditional class creation

// Create different classes based on condition
function createUserClass(type) {
    if (type === 'admin') {
        return class AdminUser {
            constructor(name) {
                this.name = name;
                this.role = 'admin';
            }
            
            manage() {
                return `${this.name} is managing`;
            }
        };
    } else {
        return class RegularUser {
            constructor(name) {
                this.name = name;
                this.role = 'user';
            }
            
            view() {
                return `${this.name} is viewing`;
            }
        };
    }
}

const AdminClass = createUserClass('admin');
const UserClass = createUserClass('user');

const admin = new AdminClass('Alice');
const user = new UserClass('Bob');

console.log(admin.manage());  // "Alice is managing"
console.log(user.view());     // "Bob is viewing"

// Environment-specific classes
const Storage = typeof window !== 'undefined'
    ? class BrowserStorage {
        save(key, value) {
            localStorage.setItem(key, value);
        }
        load(key) {
            return localStorage.getItem(key);
        }
    }
    : class NodeStorage {
        #data = new Map();
        
        save(key, value) {
            this.#data.set(key, value);
        }
        load(key) {
            return this.#data.get(key);
        }
    };

const storage = new Storage();
storage.save('key', 'value');

Example: Inline and IIFE classes

// Inline class as argument
function createInstance(ClassDef, ...args) {
    return new ClassDef(...args);
}

const point = createInstance(class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    
    distance() {
        return Math.sqrt(this.x ** 2 + this.y ** 2);
    }
}, 3, 4);

console.log(point.distance());  // 5

// IIFE class - immediate instantiation
const singleton = new class Singleton {
    constructor() {
        this.id = Math.random();
        this.createdAt = Date.now();
    }
    
    getId() {
        return this.id;
    }
}();

console.log(singleton.getId());  // Random number

// Array of different classes
const shapes = [
    new (class Circle {
        constructor(r) { this.radius = r; }
        area() { return Math.PI * this.radius ** 2; }
    })(5),
    
    new (class Rectangle {
        constructor(w, h) { this.width = w; this.height = h; }
        area() { return this.width * this.height; }
    })(4, 6),
    
    new (class Triangle {
        constructor(b, h) { this.base = b; this.height = h; }
        area() { return 0.5 * this.base * this.height; }
    })(3, 4)
];

shapes.forEach(shape => console.log(shape.area()));
// 78.54, 24, 6

// Factory with inline class
function createPlugin(name, handler) {
    return new (class Plugin {
        constructor() {
            this.name = name;
            this.handler = handler;
        }
        
        execute(...args) {
            return this.handler(...args);
        }
    })();
}

const logger = createPlugin('logger', (msg) => console.log(`[LOG] ${msg}`));
logger.execute('Hello');  // "[LOG] Hello"

Example: Mixin pattern with class expressions

// Class expression for mixins
const Flyable = Base => class extends Base {
    fly() {
        return `${this.name} is flying`;
    }
    
    land() {
        return `${this.name} has landed`;
    }
};

const Swimmable = Base => class extends Base {
    swim() {
        return `${this.name} is swimming`;
    }
};

const Runnable = Base => class extends Base {
    run() {
        return `${this.name} is running`;
    }
};

// Base class
class Animal {
    constructor(name) {
        this.name = name;
    }
}

// Compose abilities
class Duck extends Swimmable(Flyable(Animal)) {
    quack() {
        return `${this.name} says quack`;
    }
}

class Fish extends Swimmable(Animal) {
    // Fish can only swim
}

class Bird extends Flyable(Runnable(Animal)) {
    // Bird can fly and run
}

const duck = new Duck('Donald');
console.log(duck.fly());   // "Donald is flying"
console.log(duck.swim());  // "Donald is swimming"
console.log(duck.quack()); // "Donald says quack"

const fish = new Fish('Nemo');
console.log(fish.swim());  // "Nemo is swimming"
// fish.fly();  // TypeError

const bird = new Bird('Tweety');
console.log(bird.fly());   // "Tweety is flying"
console.log(bird.run());   // "Tweety is running"
Use Cases: Class expressions enable conditional class creation, factory patterns, mixins, and inline class definitions. Named expressions provide internal name for recursion and debugging. Anonymous expressions are simpler for one-time use. IIFE classes for immediate instantiation.

17.7 Mixin Patterns and Class Composition

Mixin Approaches

Approach Method Pros Cons
Functional Mixin Function that modifies prototype Simple, flexible Pollutes prototype
Subclass Factory Function returning class extending Base Proper inheritance, composable More complex
Object.assign Copy methods to prototype Very simple Shallow, no super
Symbol-based Use symbols to avoid collisions No name collisions Less discoverable

Composition Patterns

Pattern Description Use When
Multiple Inheritance Combine multiple behaviors Need features from multiple sources
Trait-based Small reusable units of behavior Fine-grained feature composition
Delegation Forward calls to composed objects Prefer composition over inheritance
Strategy Swap implementations at runtime Need dynamic behavior changes

Example: Subclass factory mixins

// Mixin functions returning class
const TimestampMixin = Base => class extends Base {
    constructor(...args) {
        super(...args);
        this.createdAt = Date.now();
        this.updatedAt = Date.now();
    }
    
    touch() {
        this.updatedAt = Date.now();
    }
    
    getAge() {
        return Date.now() - this.createdAt;
    }
};

const ValidationMixin = Base => class extends Base {
    validate() {
        // Validate implementation
        return this.isValid();
    }
    
    isValid() {
        // Override in subclass
        return true;
    }
};

const SerializableMixin = Base => class extends Base {
    toJSON() {
        // Get all enumerable properties
        return Object.keys(this).reduce((obj, key) => {
            obj[key] = this[key];
            return obj;
        }, {});
    }
    
    static fromJSON(json) {
        const data = typeof json === 'string' ? JSON.parse(json) : json;
        return new this(data);
    }
};

// Base class
class Entity {
    constructor(id, name) {
        this.id = id;
        this.name = name;
    }
}

// Compose mixins
class User extends SerializableMixin(ValidationMixin(TimestampMixin(Entity))) {
    constructor(id, name, email) {
        super(id, name);
        this.email = email;
    }
    
    // Override validation
    isValid() {
        return this.email && this.email.includes('@');
    }
}

const user = new User(1, 'Alice', 'alice@example.com');
console.log(user.validate());  // true
console.log(user.getAge());    // Time since creation

user.touch();
console.log(user.updatedAt > user.createdAt);  // true

const json = user.toJSON();
console.log(json);  // {id: 1, name: "Alice", email: "alice@example.com", ...}

const restored = User.fromJSON(json);
console.log(restored.name);  // "Alice"

Example: Object.assign mixin pattern

// Simple mixin objects
const EventEmitterMixin = {
    on(event, handler) {
        this._handlers = this._handlers || {};
        this._handlers[event] = this._handlers[event] || [];
        this._handlers[event].push(handler);
        return this;
    },
    
    off(event, handler) {
        if (!this._handlers || !this._handlers[event]) return;
        
        if (handler) {
            const index = this._handlers[event].indexOf(handler);
            if (index > -1) {
                this._handlers[event].splice(index, 1);
            }
        } else {
            delete this._handlers[event];
        }
        return this;
    },
    
    emit(event, ...args) {
        if (!this._handlers || !this._handlers[event]) return;
        
        this._handlers[event].forEach(handler => {
            handler.apply(this, args);
        });
        return this;
    }
};

const LoggableMixin = {
    log(message) {
        console.log(`[${this.constructor.name}] ${message}`);
    },
    
    error(message) {
        console.error(`[${this.constructor.name}] ERROR: ${message}`);
    }
};

// Apply mixins to class
class Component {
    constructor(name) {
        this.name = name;
    }
}

// Mix in methods
Object.assign(Component.prototype, EventEmitterMixin, LoggableMixin);

const comp = new Component('MyComponent');

// Use mixed-in methods
comp.log('Component created');  // "[Component] Component created"

comp.on('update', (data) => {
    console.log('Updated:', data);
});

comp.emit('update', {value: 42});  // "Updated: {value: 42}"

// Helper function for multiple mixins
function applyMixins(targetClass, ...mixins) {
    mixins.forEach(mixin => {
        Object.assign(targetClass.prototype, mixin);
    });
}

class App {
    constructor(name) {
        this.name = name;
    }
}

applyMixins(App, EventEmitterMixin, LoggableMixin);

const app = new App('MyApp');
app.log('App started');
app.on('error', (err) => app.error(err.message));

Example: Functional composition pattern

// Functional mixin - modifies instance
function withId(obj) {
    obj.id = Math.random().toString(36).substr(2, 9);
    obj.getId = function() { return this.id; };
    return obj;
}

function withTimestamp(obj) {
    obj.timestamp = Date.now();
    obj.getTimestamp = function() { return this.timestamp; };
    return obj;
}

function withVersion(obj) {
    obj.version = '1.0.0';
    obj.getVersion = function() { return this.version; };
    return obj;
}

// Composition function
function compose(...funcs) {
    return (obj) => funcs.reduce((acc, func) => func(acc), obj);
}

const enhance = compose(withId, withTimestamp, withVersion);

// Use with any object
const data = {name: 'Test'};
const enhanced = enhance(data);

console.log(enhanced.getId());        // Random ID
console.log(enhanced.getTimestamp()); // Timestamp
console.log(enhanced.getVersion());   // "1.0.0"

// Pipeline pattern
const pipeline = (...fns) => x => fns.reduce((v, f) => f(v), x);

const createUser = pipeline(
    (name) => ({name}),
    withId,
    withTimestamp,
    (obj) => {
        obj.greet = function() { return `Hello, ${this.name}`; };
        return obj;
    }
);

const user2 = createUser('Bob');
console.log(user2.greet());  // "Hello, Bob"
console.log(user2.id);       // Random ID

Example: Advanced mixin composition

// Mixin with dependencies
const RequiresName = Base => class extends Base {
    constructor(...args) {
        super(...args);
        if (!this.name) {
            throw new Error('Name is required');
        }
    }
};

const Describable = Base => class extends Base {
    describe() {
        return `${this.constructor.name}: ${this.name}`;
    }
};

const Comparable = Base => class extends Base {
    compareTo(other) {
        if (this.name < other.name) return -1;
        if (this.name > other.name) return 1;
        return 0;
    }
    
    equals(other) {
        return this.compareTo(other) === 0;
    }
};

// Composable mixin builder
function mix(baseClass) {
    return {
        with(...mixins) {
            return mixins.reduce((c, mixin) => mixin(c), baseClass);
        }
    };
}

// Build class with mixins
class Item {
    constructor(name, value) {
        this.name = name;
        this.value = value;
    }
}

const EnhancedItem = mix(Item)
    .with(RequiresName, Describable, Comparable);

const item1 = new EnhancedItem('Apple', 10);
const item2 = new EnhancedItem('Banana', 5);

console.log(item1.describe());      // "EnhancedItem: Apple"
console.log(item1.compareTo(item2)); // -1 (Apple < Banana)

// Conditional mixins
function withFeature(feature, Mixin) {
    return Base => {
        if (feature.enabled) {
            return Mixin(Base);
        }
        return Base;
    };
}

const DebugMixin = Base => class extends Base {
    debug(msg) {
        console.log(`[DEBUG] ${msg}`);
    }
};

const config = {debug: {enabled: true}};

const DebugItem = mix(Item)
    .with(withFeature(config.debug, DebugMixin));

const debugItem = new DebugItem('Test', 1);
if (debugItem.debug) {
    debugItem.debug('Item created');  // "[DEBUG] Item created"
}

Example: Trait-based composition

// Trait objects (small, focused behaviors)
const Clickable = {
    click() {
        this.emit('click', this);
    },
    
    onClick(handler) {
        this.on('click', handler);
        return this;
    }
};

const Hoverable = {
    hover() {
        this.emit('hover', this);
    },
    
    onHover(handler) {
        this.on('hover', handler);
        return this;
    }
};

const Draggable = {
    startDrag(x, y) {
        this.dragStart = {x, y};
        this.emit('dragstart', this);
    },
    
    drag(x, y) {
        if (!this.dragStart) return;
        this.emit('drag', {x, y, start: this.dragStart});
    },
    
    endDrag() {
        this.dragStart = null;
        this.emit('dragend', this);
    }
};

// Trait composer
function withTraits(target, ...traits) {
    traits.forEach(trait => {
        Object.keys(trait).forEach(key => {
            if (key in target.prototype) {
                console.warn(`Overriding ${key} in ${target.name}`);
            }
            target.prototype[key] = trait[key];
        });
    });
    return target;
}

// Base with event system
class UIElement {
    constructor(type) {
        this.type = type;
        this._handlers = {};
    }
    
    on(event, handler) {
        this._handlers[event] = this._handlers[event] || [];
        this._handlers[event].push(handler);
        return this;
    }
    
    emit(event, data) {
        if (this._handlers[event]) {
            this._handlers[event].forEach(h => h(data));
        }
        return this;
    }
}

// Compose specific elements
const Button = withTraits(
    class Button extends UIElement {
        constructor(label) {
            super('button');
            this.label = label;
        }
    },
    Clickable,
    Hoverable
);

const Panel = withTraits(
    class Panel extends UIElement {
        constructor(title) {
            super('panel');
            this.title = title;
        }
    },
    Clickable,
    Hoverable,
    Draggable
);

const btn = new Button('Click Me');
btn.onClick(() => console.log('Button clicked'));
btn.click();  // "Button clicked"

const panel = new Panel('My Panel');
panel.onClick(() => console.log('Panel clicked'));
panel.onHover(() => console.log('Panel hovered'));

panel.startDrag(0, 0);
panel.drag(10, 10);
panel.endDrag();
Best Practices: Prefer composition over inheritance for flexibility. Use subclass factory mixins for proper inheritance chain. Keep mixins small and focused (single responsibility). Document mixin dependencies. Avoid naming conflicts (use symbols if needed). Consider trait-based composition for fine-grained features. Use composition for "has-a" relationships, inheritance for "is-a".

Section 17 Summary

  • Classes: Syntactic sugar over prototypes; not hoisted; always strict mode; constructor initializes; methods on prototype; fields on instance
  • Methods: Instance methods (prototype, access this), static methods (class itself, utilities/factories), inherited by subclasses
  • Inheritance: extends creates subclass; super() calls parent constructor (required before this); super.method() calls parent methods
  • Private Members: #field and #method truly private; not inherited; separate namespace per class; use for encapsulation
  • Getters/Setters: get/set for computed properties, validation, side effects; access like properties; can be private/static
  • Class Expressions: Named or anonymous; enable conditional creation, factories, inline usage; IIFE classes for immediate instantiation
  • Mixins: Subclass factory pattern for composition; Object.assign for simple cases; functional composition; trait-based for fine-grained features
  • Composition: Prefer composition over inheritance for flexibility; combine multiple behaviors; avoid deep inheritance hierarchies; mix(...).with(mixins) pattern

18. Iterators, Generators, and Iteration Protocols

18.1 Iterator Interface and Iterable Protocol

Iterator Protocol

Requirement Description Returns
next() method Returns object with value and done properties {value: any, done: boolean}
value Current iteration value Any type
done true if iterator finished, false otherwise Boolean

Iterable Protocol

Requirement Description Symbol
[Symbol.iterator] method Returns iterator object with next() method Symbol.iterator
Return value Object implementing iterator protocol Iterator with next()

Built-in Iterables

Type Iterable Iteration Order
Array ✓ Yes Index order (0, 1, 2, ...)
String ✓ Yes Character order
Map ✓ Yes Insertion order [key, value] pairs
Set ✓ Yes Insertion order values
TypedArray ✓ Yes Index order
arguments ✓ Yes Index order
NodeList ✓ Yes Index order
Object ✗ No (use Object.keys/values/entries) N/A

Consuming Iterables

Construct Syntax Description
for...of loop for (const x of iterable) Iterate over iterable values
Spread operator [...iterable] Convert iterable to array
Array.from() Array.from(iterable) Create array from iterable
Destructuring const [a, b] = iterable Extract values from iterable
yield* yield* iterable Delegate to iterable in generator
Promise.all() Promise.all(iterable) Wait for all promises in iterable
Map/Set constructor new Map(iterable) Create Map/Set from iterable

Example: Understanding iterator protocol

// Array is iterable
const arr = [1, 2, 3];

// Get iterator from iterable
const iterator = arr[Symbol.iterator]();

// Call next() manually
console.log(iterator.next());  // {value: 1, done: false}
console.log(iterator.next());  // {value: 2, done: false}
console.log(iterator.next());  // {value: 3, done: false}
console.log(iterator.next());  // {value: undefined, done: true}
console.log(iterator.next());  // {value: undefined, done: true}

// for...of uses iterator protocol internally
for (const value of arr) {
    console.log(value);  // 1, 2, 3
}

// String is iterable
const str = 'hello';
const strIterator = str[Symbol.iterator]();

console.log(strIterator.next());  // {value: 'h', done: false}
console.log(strIterator.next());  // {value: 'e', done: false}

for (const char of str) {
    console.log(char);  // 'h', 'e', 'l', 'l', 'o'
}

// Map is iterable (yields [key, value] pairs)
const map = new Map([['a', 1], ['b', 2]]);

for (const [key, value] of map) {
    console.log(key, value);  // 'a' 1, 'b' 2
}

// Set is iterable
const set = new Set([1, 2, 3]);

for (const value of set) {
    console.log(value);  // 1, 2, 3
}

Example: Creating simple iterator

// Simple iterator (not iterable)
function createRangeIterator(start, end) {
    let current = start;
    
    return {
        next() {
            if (current <= end) {
                return {
                    value: current++,
                    done: false
                };
            }
            return {
                value: undefined,
                done: true
            };
        }
    };
}

const rangeIter = createRangeIterator(1, 5);

console.log(rangeIter.next());  // {value: 1, done: false}
console.log(rangeIter.next());  // {value: 2, done: false}
console.log(rangeIter.next());  // {value: 3, done: false}
console.log(rangeIter.next());  // {value: 4, done: false}
console.log(rangeIter.next());  // {value: 5, done: false}
console.log(rangeIter.next());  // {value: undefined, done: true}

// Cannot use with for...of (not iterable, just iterator)
// for (const n of rangeIter) {}  // TypeError

Example: Creating iterable object

// Iterable object (has Symbol.iterator method)
const range = {
    start: 1,
    end: 5,
    
    // Implement iterable protocol
    [Symbol.iterator]() {
        let current = this.start;
        const end = this.end;
        
        // Return iterator
        return {
            next() {
                if (current <= end) {
                    return {
                        value: current++,
                        done: false
                    };
                }
                return {
                    value: undefined,
                    done: true
                };
            }
        };
    }
};

// Can use with for...of
for (const n of range) {
    console.log(n);  // 1, 2, 3, 4, 5
}

// Can use spread operator
const arr2 = [...range];
console.log(arr2);  // [1, 2, 3, 4, 5]

// Can destructure
const [first, second, ...rest] = range;
console.log(first);   // 1
console.log(second);  // 2
console.log(rest);    // [3, 4, 5]

// Each call to Symbol.iterator creates new iterator
const iter1 = range[Symbol.iterator]();
const iter2 = range[Symbol.iterator]();

console.log(iter1.next());  // {value: 1, done: false}
console.log(iter2.next());  // {value: 1, done: false} (independent)

Example: Iterable with state

// Iterable collection
class LinkedList {
    constructor() {
        this.head = null;
        this.tail = null;
        this.length = 0;
    }
    
    append(value) {
        const node = {value, next: null};
        
        if (!this.head) {
            this.head = node;
            this.tail = node;
        } else {
            this.tail.next = node;
            this.tail = node;
        }
        
        this.length++;
    }
    
    // Make iterable
    [Symbol.iterator]() {
        let current = this.head;
        
        return {
            next() {
                if (current) {
                    const value = current.value;
                    current = current.next;
                    return {value, done: false};
                }
                return {value: undefined, done: true};
            }
        };
    }
}

const list = new LinkedList();
list.append(10);
list.append(20);
list.append(30);

// Use with for...of
for (const value of list) {
    console.log(value);  // 10, 20, 30
}

// Convert to array
const listArray = [...list];
console.log(listArray);  // [10, 20, 30]

// Use Array methods via spread
const doubled = [...list].map(x => x * 2);
console.log(doubled);  // [20, 40, 60]
Key Points: Iterator protocol: object with next() returning {value, done}. Iterable protocol: object with [Symbol.iterator] method returning iterator. Built-in iterables: Array, String, Map, Set, TypedArray. Iterables work with for...of, spread, destructuring. Each call to Symbol.iterator creates fresh iterator.

18.2 Generator Functions and yield Expressions

Generator Function Syntax

Type Syntax Example
Function Declaration function* name() {} function* gen() { yield 1; }
Function Expression const f = function*() {} const g = function*() { yield 1; }
Method *method() {} obj = {*gen() { yield 1; }}
Class Method *method() {} class C {*gen() { yield 1; }}
Arrow Function ❌ Not supported Cannot create generator arrows

yield Expression Types

Expression Syntax Description Returns
yield yield value Pause and yield value Value from next() or undefined
yield* yield* iterable Delegate to iterable/generator Final return value of iterable
yield (no value) yield Pause, yield undefined Value from next()
yield (assignment) const x = yield Receive value from next(value) Value passed to next()

Generator Object Properties

Feature Description Behavior
Is Iterable Has [Symbol.iterator]() method Returns itself
Is Iterator Has next() method Returns {value, done}
Lazy Executes on demand Code runs only when next() called
Pausable Execution pauses at yield Resumes on next next() call
State Maintains local state between yields Variables persist across pauses

Example: Basic generator functions

// Simple generator
function* simpleGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = simpleGenerator();

// Generator is both iterator and iterable
console.log(typeof gen.next);              // "function" (iterator)
console.log(typeof gen[Symbol.iterator]);  // "function" (iterable)

// Use as iterator
console.log(gen.next());  // {value: 1, done: false}
console.log(gen.next());  // {value: 2, done: false}
console.log(gen.next());  // {value: 3, done: false}
console.log(gen.next());  // {value: undefined, done: true}

// Use as iterable
function* gen2() {
    yield 1;
    yield 2;
    yield 3;
}

for (const value of gen2()) {
    console.log(value);  // 1, 2, 3
}

// Spread operator
const values = [...gen2()];
console.log(values);  // [1, 2, 3]

// Generator with logic
function* fibonacci(n) {
    let [a, b] = [0, 1];
    let count = 0;
    
    while (count < n) {
        yield a;
        [a, b] = [b, a + b];
        count++;
    }
}

console.log([...fibonacci(10)]);
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Example: yield expressions and two-way communication

// Generator receiving values
function* twoWay() {
    console.log('Start');
    
    const a = yield 'First';
    console.log('Received:', a);
    
    const b = yield 'Second';
    console.log('Received:', b);
    
    return 'Done';
}

const gen3 = twoWay();

// First next() starts generator
console.log(gen3.next());        // Logs: "Start"
                                 // Returns: {value: 'First', done: false}

// Send value 10
console.log(gen3.next(10));      // Logs: "Received: 10"
                                 // Returns: {value: 'Second', done: false}

// Send value 20
console.log(gen3.next(20));      // Logs: "Received: 20"
                                 // Returns: {value: 'Done', done: true}

// Practical example: ID generator
function* idGenerator(prefix = 'ID') {
    let id = 1;
    
    while (true) {
        const reset = yield `${prefix}-${id}`;
        if (reset) {
            id = 1;
        } else {
            id++;
        }
    }
}

const ids = idGenerator('USER');
console.log(ids.next().value);       // "USER-1"
console.log(ids.next().value);       // "USER-2"
console.log(ids.next().value);       // "USER-3"
console.log(ids.next(true).value);   // "USER-1" (reset)
console.log(ids.next().value);       // "USER-2"

Example: yield* delegation

// Delegate to another generator
function* gen1() {
    yield 1;
    yield 2;
}

function* gen2() {
    yield 'a';
    yield* gen1();  // Delegate to gen1
    yield 'b';
}

console.log([...gen2()]);  // ['a', 1, 2, 'b']

// Delegate to any iterable
function* gen3() {
    yield* [1, 2, 3];
    yield* 'hello';
    yield* new Set([4, 5, 6]);
}

console.log([...gen3()]);
// [1, 2, 3, 'h', 'e', 'l', 'l', 'o', 4, 5, 6]

// Tree traversal
const tree = {
    value: 1,
    children: [
        {
            value: 2,
            children: [
                {value: 4, children: []},
                {value: 5, children: []}
            ]
        },
        {
            value: 3,
            children: [
                {value: 6, children: []}
            ]
        }
    ]
};

function* traverse(node) {
    yield node.value;
    for (const child of node.children) {
        yield* traverse(child);  // Recursive delegation
    }
}

console.log([...traverse(tree)]);  // [1, 2, 4, 5, 3, 6]

// Flatten nested arrays
function* flatten(arr) {
    for (const item of arr) {
        if (Array.isArray(item)) {
            yield* flatten(item);
        } else {
            yield item;
        }
    }
}

const nested = [1, [2, [3, 4], 5], 6];
console.log([...flatten(nested)]);  // [1, 2, 3, 4, 5, 6]

// yield* returns the final value
function* innerGen() {
    yield 1;
    yield 2;
    return 'finished';
}

function* outerGen() {
    const result = yield* innerGen();
    console.log('Inner returned:', result);  // "finished"
    yield 3;
}

console.log([...outerGen()]);
// Logs: "Inner returned: finished"
// Returns: [1, 2, 3] (return value not included in iteration)

Example: Infinite sequences and lazy evaluation

// Infinite sequence
function* naturals() {
    let n = 1;
    while (true) {
        yield n++;
    }
}

const nums = naturals();
console.log(nums.next().value);  // 1
console.log(nums.next().value);  // 2
console.log(nums.next().value);  // 3

// Take first N
function* take(n, iterable) {
    let count = 0;
    for (const value of iterable) {
        if (count++ >= n) break;
        yield value;
    }
}

console.log([...take(5, naturals())]);  // [1, 2, 3, 4, 5]

// Filter
function* filter(predicate, iterable) {
    for (const value of iterable) {
        if (predicate(value)) {
            yield value;
        }
    }
}

function* evens() {
    yield* filter(n => n % 2 === 0, naturals());
}

console.log([...take(5, evens())]);  // [2, 4, 6, 8, 10]

// Map
function* map(fn, iterable) {
    for (const value of iterable) {
        yield fn(value);
    }
}

function* squares() {
    yield* map(n => n ** 2, naturals());
}

console.log([...take(5, squares())]);  // [1, 4, 9, 16, 25]

// Compose transformations (lazy!)
const result = take(5, 
    map(n => n ** 2,
        filter(n => n % 2 === 0, naturals())
    )
);

console.log([...result]);  // [4, 16, 36, 64, 100]

// Random number generator
function* randomInRange(min, max) {
    while (true) {
        yield Math.floor(Math.random() * (max - min + 1)) + min;
    }
}

const dice = randomInRange(1, 6);
console.log([...take(10, dice)]);  // 10 random numbers 1-6
Key Points: Generators are both iterator and iterable. Execution pauses at yield, resumes at next next(). yield* delegates to another iterable. Generators enable lazy evaluation (compute on demand). Two-way communication: yield sends value out, next(value) sends value in. Perfect for infinite sequences and streaming data.

18.3 Generator Methods and Return Values

Generator Methods

Method Syntax Description Effect
next() gen.next(value) Resume execution, send value to yield Returns {value, done}
return() gen.return(value) Terminate generator, set return value Returns {value, done: true}
throw() gen.throw(error) Throw error at yield point Returns {value, done} or throws

Generator Return Behavior

Scenario Result done value
yield expression Normal iteration false Yielded value
return statement Generator completes true Returned value
Implicit return (end) Generator completes true undefined
gen.return(value) Forced completion true Provided value
gen.throw(error) Error thrown at yield Depends on handling Next yield or throws

finally Block Behavior

Method finally Executes Notes
return() ✓ Yes Cleanup code runs before completion
throw() ✓ Yes Runs before exception propagates
Normal completion ✓ Yes Runs when generator ends normally

Example: Generator return() method

// Early termination with return()
function* gen4() {
    try {
        yield 1;
        yield 2;
        yield 3;
        yield 4;
    } finally {
        console.log('Cleanup');
    }
}

const g4 = gen4();

console.log(g4.next());      // {value: 1, done: false}
console.log(g4.next());      // {value: 2, done: false}
console.log(g4.return(99));  // Logs: "Cleanup"
                             // Returns: {value: 99, done: true}
console.log(g4.next());      // {value: undefined, done: true}

// return() without value
function* gen5() {
    yield 1;
    yield 2;
    yield 3;
}

const g5 = gen5();

console.log(g5.next());    // {value: 1, done: false}
console.log(g5.return());  // {value: undefined, done: true}
console.log(g5.next());    // {value: undefined, done: true}

// Using return in for...of
function* gen6() {
    yield 1;
    yield 2;
    return 3;  // Return value not included in iteration
    yield 4;   // Never reached
}

const values = [...gen6()];
console.log(values);  // [1, 2] (return value excluded)

// But visible with manual next()
const g6 = gen6();
console.log(g6.next());  // {value: 1, done: false}
console.log(g6.next());  // {value: 2, done: false}
console.log(g6.next());  // {value: 3, done: true} (return value visible)

Example: Generator throw() method

// Error handling with throw()
function* gen7() {
    try {
        yield 1;
        yield 2;
        yield 3;
    } catch (e) {
        console.log('Caught:', e.message);
        yield 'error handled';
    }
    yield 4;
}

const g7 = gen7();

console.log(g7.next());                   // {value: 1, done: false}
console.log(g7.throw(new Error('Oops'))); // Logs: "Caught: Oops"
                                          // Returns: {value: 'error handled', done: false}
console.log(g7.next());                   // {value: 4, done: false}
console.log(g7.next());                   // {value: undefined, done: true}

// Uncaught error
function* gen8() {
    yield 1;
    yield 2;  // No try-catch
    yield 3;
}

const g8 = gen8();

console.log(g8.next());  // {value: 1, done: false}

try {
    g8.throw(new Error('Uncaught'));
} catch (e) {
    console.log('Exception propagated:', e.message);
    // "Exception propagated: Uncaught"
}

console.log(g8.next());  // {value: undefined, done: true} (generator closed)

// finally with throw
function* gen9() {
    try {
        yield 1;
        yield 2;
    } finally {
        console.log('Cleanup after error');
    }
}

const g9 = gen9();
console.log(g9.next());  // {value: 1, done: false}

try {
    g9.throw(new Error('Error'));
} catch (e) {
    console.log('Caught outside');
    // Logs: "Cleanup after error" (finally runs)
    // Logs: "Caught outside"
}

Example: Cleanup with finally

// Resource management
function* databaseQuery() {
    console.log('Opening connection');
    
    try {
        yield 'row 1';
        yield 'row 2';
        yield 'row 3';
    } finally {
        console.log('Closing connection');
    }
}

// Normal completion
console.log('--- Normal completion ---');
for (const row of databaseQuery()) {
    console.log(row);
}
// Logs:
// "Opening connection"
// "row 1"
// "row 2"
// "row 3"
// "Closing connection"

// Early termination
console.log('--- Early termination ---');
const query = databaseQuery();
console.log(query.next().value);  // "Opening connection", "row 1"
console.log(query.next().value);  // "row 2"
query.return();                   // "Closing connection"

// Break in for...of
console.log('--- Break in loop ---');
for (const row of databaseQuery()) {
    console.log(row);
    if (row === 'row 2') break;  // Triggers cleanup
}
// Logs:
// "Opening connection"
// "row 1"
// "row 2"
// "Closing connection"

// Error handling
console.log('--- Error ---');
function* withCleanup() {
    console.log('Setup');
    
    try {
        yield 1;
        throw new Error('Internal error');
        yield 2;  // Never reached
    } catch (e) {
        console.log('Error in generator:', e.message);
    } finally {
        console.log('Cleanup');
    }
}

for (const value of withCleanup()) {
    console.log(value);
}
// Logs:
// "Setup"
// "1"
// "Error in generator: Internal error"
// "Cleanup"

Example: Return values and yield*

// Return value from generator
function* inner() {
    yield 1;
    yield 2;
    return 'inner done';
}

function* outer1() {
    const result = yield* inner();
    console.log('Inner returned:', result);
    yield 3;
}

console.log([...outer1()]);
// Logs: "Inner returned: inner done"
// Returns: [1, 2, 3]

// Return value not included in iteration
function* gen10() {
    yield 1;
    yield 2;
    return 42;
}

console.log([...gen10()]);  // [1, 2] (42 not included)

// But captured by yield*
function* outer2() {
    const returnValue = yield* gen10();
    console.log('Got return value:', returnValue);
    yield returnValue;
}

console.log([...outer2()]);
// Logs: "Got return value: 42"
// Returns: [1, 2, 42]

// Chaining generators
function* pipeline() {
    const r1 = yield* stage1();
    console.log('Stage 1 result:', r1);
    
    const r2 = yield* stage2(r1);
    console.log('Stage 2 result:', r2);
    
    return r2;
}

function* stage1() {
    yield 'processing...';
    return 'stage1-complete';
}

function* stage2(input) {
    yield `using ${input}`;
    return 'stage2-complete';
}

console.log([...pipeline()]);
// Logs: "Stage 1 result: stage1-complete"
// Logs: "Stage 2 result: stage2-complete"
// Returns: ["processing...", "using stage1-complete"]

// Final result
const gen11 = pipeline();
let result;
while (true) {
    result = gen11.next();
    if (result.done) {
        console.log('Final return:', result.value);  // "stage2-complete"
        break;
    }
}
Important: return() terminates generator and runs finally blocks. throw() throws error at yield point (must be caught or generator closes). Return values from generators not included in iteration but captured by yield*. Use finally for cleanup. Early break in for...of triggers cleanup via return().

18.4 Async Generators and for-await-of Loops

Async Generator Syntax

Type Syntax Returns
Function Declaration async function* name() {} AsyncGenerator object
Function Expression const f = async function*() {} AsyncGenerator object
Method async *method() {} AsyncGenerator object
Class Method async *method() {} AsyncGenerator object

AsyncGenerator Methods

Method Syntax Description Returns
next() await gen.next(value) Resume, send value Promise<{value, done}>
return() await gen.return(value) Terminate generator Promise<{value, done: true}>
throw() await gen.throw(error) Throw error at yield Promise<{value, done}>

for-await-of Loop

Feature Syntax Works With
Basic for await (const x of asyncIterable) Async iterables
Await promises Automatically awaits each value AsyncGenerator, async iterables
Context Must be in async function Any async function or top-level
Break/Continue Supported Same as regular for...of

Async Iterable Protocol

Requirement Description Returns
[Symbol.asyncIterator] Method returning async iterator Object with next() returning Promise
next() method Returns promise of {value, done} Promise<{value: any, done: boolean}>

Example: Basic async generators

// Simple async generator
async function* asyncNumbers() {
    yield 1;
    yield 2;
    yield 3;
}

// Consume with for-await-of
async function main1() {
    for await (const num of asyncNumbers()) {
        console.log(num);  // 1, 2, 3
    }
}

main1();

// Async generator with delays
async function* delayedSequence() {
    yield await Promise.resolve(1);
    await new Promise(resolve => setTimeout(resolve, 100));
    yield 2;
    await new Promise(resolve => setTimeout(resolve, 100));
    yield 3;
}

async function main2() {
    console.log('Start');
    
    for await (const value of delayedSequence()) {
        console.log(value);  // 1 (then 100ms), 2 (then 100ms), 3
    }
    
    console.log('Done');
}

main2();

// Fetch data in chunks
async function* fetchPages(baseUrl, maxPages) {
    for (let page = 1; page <= maxPages; page++) {
        const response = await fetch(`${baseUrl}?page=${page}`);
        const data = await response.json();
        yield data;
    }
}

async function processAllPages() {
    for await (const pageData of fetchPages('/api/items', 5)) {
        console.log('Processing page:', pageData);
        // Process page data
    }
}

Example: Async generator methods

// Manual iteration
async function* asyncGen() {
    yield 1;
    yield 2;
    yield 3;
}

async function manual() {
    const gen = asyncGen();
    
    console.log(await gen.next());  // {value: 1, done: false}
    console.log(await gen.next());  // {value: 2, done: false}
    console.log(await gen.next());  // {value: 3, done: false}
    console.log(await gen.next());  // {value: undefined, done: true}
}

manual();

// Early termination with return()
async function* withCleanup() {
    try {
        yield 1;
        yield 2;
        yield 3;
    } finally {
        console.log('Cleanup');
    }
}

async function earlyExit() {
    const gen = withCleanup();
    
    console.log(await gen.next());      // {value: 1, done: false}
    console.log(await gen.return(99));  // Logs: "Cleanup"
                                        // Returns: {value: 99, done: true}
    console.log(await gen.next());      // {value: undefined, done: true}
}

earlyExit();

// Error handling with throw()
async function* withErrorHandling() {
    try {
        yield 1;
        yield 2;
        yield 3;
    } catch (e) {
        console.log('Caught:', e.message);
        yield 'recovered';
    }
}

async function throwError() {
    const gen = withErrorHandling();
    
    console.log(await gen.next());                   // {value: 1, done: false}
    console.log(await gen.throw(new Error('Oops'))); // Logs: "Caught: Oops"
                                                     // Returns: {value: 'recovered', done: false}
    console.log(await gen.next());                   // {value: undefined, done: true}
}

throwError();

Example: Streaming data processing

// Read file in chunks
async function* readFileInChunks(filename, chunkSize = 1024) {
    // Simulated file reading (in real code, use fs.createReadStream)
    const fileContent = 'Large file content...';
    
    for (let i = 0; i < fileContent.length; i += chunkSize) {
        const chunk = fileContent.slice(i, i + chunkSize);
        yield chunk;
        // Simulate async I/O
        await new Promise(resolve => setTimeout(resolve, 10));
    }
}

async function processFile() {
    for await (const chunk of readFileInChunks('data.txt', 100)) {
        console.log('Processing chunk:', chunk.length, 'bytes');
    }
}

// Event stream
async function* eventStream(eventEmitter, eventName) {
    const queue = [];
    let resolveNext;
    
    const handler = (data) => {
        if (resolveNext) {
            resolveNext(data);
            resolveNext = null;
        } else {
            queue.push(data);
        }
    };
    
    eventEmitter.on(eventName, handler);
    
    try {
        while (true) {
            if (queue.length > 0) {
                yield queue.shift();
            } else {
                yield await new Promise(resolve => {
                    resolveNext = resolve;
                });
            }
        }
    } finally {
        eventEmitter.off(eventName, handler);
    }
}

// Usage with EventEmitter
async function processEvents(emitter) {
    for await (const event of eventStream(emitter, 'data')) {
        console.log('Event:', event);
        if (event.type === 'end') break;
    }
}

// WebSocket stream
async function* websocketStream(url) {
    const ws = new WebSocket(url);
    const queue = [];
    let resolveNext;
    let done = false;
    
    ws.onmessage = (event) => {
        if (resolveNext) {
            resolveNext({value: event.data, done: false});
            resolveNext = null;
        } else {
            queue.push(event.data);
        }
    };
    
    ws.onclose = () => {
        done = true;
        if (resolveNext) {
            resolveNext({done: true});
        }
    };
    
    try {
        while (!done) {
            if (queue.length > 0) {
                yield queue.shift();
            } else {
                const result = await new Promise(resolve => {
                    resolveNext = resolve;
                });
                if (result.done) break;
                yield result.value;
            }
        }
    } finally {
        ws.close();
    }
}

async function processWebSocketMessages() {
    for await (const message of websocketStream('ws://example.com')) {
        console.log('Message:', message);
    }
}

Example: Combining async generators

// Merge multiple async streams
async function* merge(...asyncIterables) {
    const iterators = asyncIterables.map(it => it[Symbol.asyncIterator]());
    const promises = new Map(
        iterators.map((it, i) => [i, it.next().then(result => ({i, result}))])
    );
    
    while (promises.size > 0) {
        const {i, result} = await Promise.race(promises.values());
        
        if (result.done) {
            promises.delete(i);
        } else {
            yield result.value;
            promises.set(i, 
                iterators[i].next().then(result => ({i, result}))
            );
        }
    }
}

async function* source1() {
    yield 1;
    await new Promise(r => setTimeout(r, 100));
    yield 2;
}

async function* source2() {
    yield 'a';
    await new Promise(r => setTimeout(r, 50));
    yield 'b';
}

async function testMerge() {
    for await (const value of merge(source1(), source2())) {
        console.log(value);  // Order depends on timing: a, 1, b, 2 or a, b, 1, 2
    }
}

// Transform async stream
async function* map(fn, asyncIterable) {
    for await (const value of asyncIterable) {
        yield fn(value);
    }
}

async function* filter(predicate, asyncIterable) {
    for await (const value of asyncIterable) {
        if (predicate(value)) {
            yield value;
        }
    }
}

async function* numbers() {
    for (let i = 1; i <= 10; i++) {
        yield i;
        await new Promise(r => setTimeout(r, 10));
    }
}

async function processNumbers() {
    const evens = filter(n => n % 2 === 0, numbers());
    const doubled = map(n => n * 2, evens);
    
    for await (const value of doubled) {
        console.log(value);  // 4, 8, 12, 16, 20
    }
}

// Throttle async stream
async function* throttle(asyncIterable, delay) {
    for await (const value of asyncIterable) {
        yield value;
        await new Promise(resolve => setTimeout(resolve, delay));
    }
}

async function throttleExample() {
    for await (const num of throttle(numbers(), 100)) {
        console.log(num);  // 1, (100ms), 2, (100ms), 3, ...
    }
}
Key Points: async function* creates async generator. Use for await...of to iterate (must be in async context). Methods return promises: await gen.next(). Perfect for streaming data, event processing, paginated APIs. AsyncGenerator is both async iterable and async iterator.

18.5 Custom Iterator Implementation

Iterator Implementation Checklist

Step Requirement Implementation
1. Make iterable Add [Symbol.iterator] method Returns iterator object
2. Return iterator Iterator has next() method Returns {value, done}
3. Track state Maintain iteration position Use closure or object properties
4. Signal completion Set done: true when finished Return {value: undefined, done: true}
5. Optional: return/throw Add cleanup support Implement return() and throw() methods

Iterator Pattern Variations

Pattern When to Use Example
Simple Iterator Single iteration, no cleanup Range, sequence
Reusable Iterator Multiple iterations from same object Array, collection
Stateful Iterator Complex state management Tree traversal, pagination
Iterator with Cleanup Resource management needed File reader, DB cursor
Infinite Iterator Unbounded sequences Counter, random generator

Example: Custom collection iterators

// Simple range iterator
class Range {
    constructor(start, end, step = 1) {
        this.start = start;
        this.end = end;
        this.step = step;
    }
    
    [Symbol.iterator]() {
        let current = this.start;
        const end = this.end;
        const step = this.step;
        
        return {
            next() {
                if (current <= end) {
                    const value = current;
                    current += step;
                    return {value, done: false};
                }
                return {value: undefined, done: true};
            }
        };
    }
}

const range = new Range(1, 10, 2);

for (const n of range) {
    console.log(n);  // 1, 3, 5, 7, 9
}

console.log([...range]);  // [1, 3, 5, 7, 9]

// Multiple independent iterations
const iter1 = range[Symbol.iterator]();
const iter2 = range[Symbol.iterator]();

console.log(iter1.next().value);  // 1
console.log(iter2.next().value);  // 1 (independent)
console.log(iter1.next().value);  // 3
console.log(iter2.next().value);  // 3

// Linked list iterator
class Node {
    constructor(value, next = null) {
        this.value = value;
        this.next = next;
    }
}

class LinkedList {
    constructor() {
        this.head = null;
        this.tail = null;
        this.size = 0;
    }
    
    append(value) {
        const node = new Node(value);
        
        if (!this.head) {
            this.head = node;
            this.tail = node;
        } else {
            this.tail.next = node;
            this.tail = node;
        }
        
        this.size++;
    }
    
    [Symbol.iterator]() {
        let current = this.head;
        
        return {
            next() {
                if (current) {
                    const value = current.value;
                    current = current.next;
                    return {value, done: false};
                }
                return {value: undefined, done: true};
            }
        };
    }
}

const list = new LinkedList();
list.append(10);
list.append(20);
list.append(30);

for (const value of list) {
    console.log(value);  // 10, 20, 30
}

// Map and filter on custom iterable
const doubled = [...list].map(x => x * 2);
console.log(doubled);  // [20, 40, 60]

Example: Iterators with cleanup

// Iterator with return() method
class FileReader {
    constructor(filename) {
        this.filename = filename;
    }
    
    [Symbol.iterator]() {
        let file = this.openFile(this.filename);
        let lineNumber = 0;
        
        return {
            next() {
                const line = this.readLine(file);
                
                if (line !== null) {
                    return {
                        value: {lineNumber: ++lineNumber, text: line},
                        done: false
                    };
                }
                
                this.closeFile(file);
                return {value: undefined, done: true};
            }.bind(this),
            
            return(value) {
                // Cleanup on early termination
                console.log('Closing file early');
                this.closeFile(file);
                return {value, done: true};
            }.bind(this)
        };
    }
    
    openFile(filename) {
        console.log('Opening file:', filename);
        return {/* file handle */};
    }
    
    readLine(file) {
        // Simulated
        return null;
    }
    
    closeFile(file) {
        console.log('Closing file');
    }
}

const reader = new FileReader('data.txt');

// Early break triggers return()
for (const line of reader) {
    console.log(line);
    if (line.lineNumber === 5) break;  // Calls return()
}
// Logs: "Closing file early"

// Iterator with throw() support
class SafeIterator {
    constructor(items) {
        this.items = items;
    }
    
    [Symbol.iterator]() {
        let index = 0;
        const items = this.items;
        let errorHandled = false;
        
        return {
            next() {
                if (errorHandled) {
                    return {value: undefined, done: true};
                }
                
                if (index < items.length) {
                    return {value: items[index++], done: false};
                }
                
                return {value: undefined, done: true};
            },
            
            throw(error) {
                console.log('Error in iterator:', error.message);
                errorHandled = true;
                return {value: 'error-handled', done: false};
            },
            
            return(value) {
                console.log('Cleanup');
                return {value, done: true};
            }
        };
    }
}

const safe = new SafeIterator([1, 2, 3]);
const safeIter = safe[Symbol.iterator]();

console.log(safeIter.next());                   // {value: 1, done: false}
console.log(safeIter.throw(new Error('Oops'))); // Logs: "Error in iterator: Oops"
                                                // Returns: {value: 'error-handled', done: false}
console.log(safeIter.next());                   // {value: undefined, done: true}

Example: Advanced iterator patterns

// Bidirectional iterator
class BidirectionalList {
    constructor(items) {
        this.items = items;
    }
    
    [Symbol.iterator]() {
        let index = 0;
        const items = this.items;
        
        return {
            next() {
                if (index < items.length) {
                    return {value: items[index++], done: false};
                }
                return {value: undefined, done: true};
            },
            
            // Custom method for backward iteration
            prev() {
                if (index > 0) {
                    return {value: items[--index], done: false};
                }
                return {value: undefined, done: true};
            }
        };
    }
}

const biList = new BidirectionalList([1, 2, 3, 4, 5]);
const biIter = biList[Symbol.iterator]();

console.log(biIter.next().value);  // 1
console.log(biIter.next().value);  // 2
console.log(biIter.next().value);  // 3
console.log(biIter.prev().value);  // 2
console.log(biIter.prev().value);  // 1
console.log(biIter.next().value);  // 2

// Peekable iterator
class PeekableIterator {
    constructor(iterable) {
        this.iterator = iterable[Symbol.iterator]();
        this.peeked = null;
        this.hasPeeked = false;
    }
    
    next() {
        if (this.hasPeeked) {
            this.hasPeeked = false;
            return this.peeked;
        }
        return this.iterator.next();
    }
    
    peek() {
        if (!this.hasPeeked) {
            this.peeked = this.iterator.next();
            this.hasPeeked = true;
        }
        return this.peeked;
    }
    
    [Symbol.iterator]() {
        return this;
    }
}

const peekable = new PeekableIterator([1, 2, 3, 4, 5]);

console.log(peekable.peek().value);  // 1 (doesn't advance)
console.log(peekable.peek().value);  // 1 (still doesn't advance)
console.log(peekable.next().value);  // 1 (now advances)
console.log(peekable.next().value);  // 2
console.log(peekable.peek().value);  // 3
console.log(peekable.next().value);  // 3

// Cycling iterator
class CycleIterator {
    constructor(iterable) {
        this.iterable = iterable;
        this.iterator = null;
    }
    
    [Symbol.iterator]() {
        return this;
    }
    
    next() {
        if (!this.iterator) {
            this.iterator = this.iterable[Symbol.iterator]();
        }
        
        const result = this.iterator.next();
        
        if (result.done) {
            // Restart
            this.iterator = this.iterable[Symbol.iterator]();
            return this.iterator.next();
        }
        
        return result;
    }
}

const cycle = new CycleIterator([1, 2, 3]);

console.log(cycle.next().value);  // 1
console.log(cycle.next().value);  // 2
console.log(cycle.next().value);  // 3
console.log(cycle.next().value);  // 1 (cycles back)
console.log(cycle.next().value);  // 2
console.log(cycle.next().value);  // 3
console.log(cycle.next().value);  // 1 (cycles again)
Key Points: Implement [Symbol.iterator]() returning object with next(). Track state with closure or properties. Optional: return() for cleanup, throw() for error handling. Each [Symbol.iterator]() call should return new independent iterator. Use generators for simpler implementation.

18.6 Iterator Helper Methods (take, drop, filter)

Iterator Helper Methods (ES2024+) New

Method Syntax Description Returns
take() iter.take(n) Take first n elements Iterator
drop() iter.drop(n) Skip first n elements Iterator
filter() iter.filter(predicate) Filter elements by predicate Iterator
map() iter.map(fn) Transform each element Iterator
flatMap() iter.flatMap(fn) Map and flatten one level Iterator
reduce() iter.reduce(fn, init) Reduce to single value Any
forEach() iter.forEach(fn) Execute function for each element undefined
some() iter.some(predicate) Test if any element matches Boolean
every() iter.every(predicate) Test if all elements match Boolean
find() iter.find(predicate) Find first matching element Any or undefined
toArray() iter.toArray() Convert to array Array

Lazy vs Eager Evaluation

Category Methods Evaluation Chainable
Lazy take, drop, filter, map, flatMap On demand, returns iterator ✓ Yes
Eager reduce, forEach, some, every, find, toArray Immediate, returns value ✗ No (terminal)

Example: Polyfill implementations (pre-ES2024)

// Helper method polyfills for older environments
function* take(n, iterable) {
    let count = 0;
    for (const value of iterable) {
        if (count++ >= n) break;
        yield value;
    }
}

function* drop(n, iterable) {
    let count = 0;
    for (const value of iterable) {
        if (count++ >= n) {
            yield value;
        }
    }
}

function* filter(predicate, iterable) {
    for (const value of iterable) {
        if (predicate(value)) {
            yield value;
        }
    }
}

function* map(fn, iterable) {
    for (const value of iterable) {
        yield fn(value);
    }
}

function* flatMap(fn, iterable) {
    for (const value of iterable) {
        yield* fn(value);
    }
}

// Usage examples
function* naturals() {
    let n = 1;
    while (true) yield n++;
}

// Take first 5 numbers
const first5 = [...take(5, naturals())];
console.log(first5);  // [1, 2, 3, 4, 5]

// Skip first 10, take next 5
const numbers = [...take(5, drop(10, naturals()))];
console.log(numbers);  // [11, 12, 13, 14, 15]

// Filter even numbers
const evens = filter(n => n % 2 === 0, naturals());
console.log([...take(5, evens)]);  // [2, 4, 6, 8, 10]

// Chain transformations (lazy evaluation)
const result = take(5,
    map(n => n * n,
        filter(n => n % 2 === 0, naturals())
    )
);

console.log([...result]);  // [4, 16, 36, 64, 100]

// FlatMap example
function* duplicateEach(n) {
    yield n;
    yield n;
}

const duplicated = flatMap(duplicateEach, [1, 2, 3]);
console.log([...duplicated]);  // [1, 1, 2, 2, 3, 3]

Example: Terminal operations

// Reduce
function reduce(fn, initial, iterable) {
    let accumulator = initial;
    
    for (const value of iterable) {
        accumulator = fn(accumulator, value);
    }
    
    return accumulator;
}

function* range(start, end) {
    for (let i = start; i <= end; i++) {
        yield i;
    }
}

const sum = reduce((acc, n) => acc + n, 0, range(1, 10));
console.log(sum);  // 55

// forEach (side effects)
function forEach(fn, iterable) {
    for (const value of iterable) {
        fn(value);
    }
}

forEach(n => console.log(n), range(1, 5));
// Logs: 1, 2, 3, 4, 5

// some
function some(predicate, iterable) {
    for (const value of iterable) {
        if (predicate(value)) {
            return true;
        }
    }
    return false;
}

console.log(some(n => n > 5, range(1, 10)));  // true
console.log(some(n => n > 20, range(1, 10))); // false

// every
function every(predicate, iterable) {
    for (const value of iterable) {
        if (!predicate(value)) {
            return false;
        }
    }
    return true;
}

console.log(every(n => n > 0, range(1, 10)));  // true
console.log(every(n => n < 5, range(1, 10)));  // false

// find
function find(predicate, iterable) {
    for (const value of iterable) {
        if (predicate(value)) {
            return value;
        }
    }
    return undefined;
}

console.log(find(n => n > 5, range(1, 10)));  // 6
console.log(find(n => n > 20, range(1, 10))); // undefined

// toArray
function toArray(iterable) {
    return [...iterable];
}

console.log(toArray(range(1, 5)));  // [1, 2, 3, 4, 5]

Example: Practical use cases

// Process large dataset lazily
function* readLargeFile() {
    // Simulate reading millions of lines
    for (let i = 1; i <= 1000000; i++) {
        yield `Line ${i}`;
    }
}

// Only processes first 100 matching lines (lazy!)
const validLines = take(100,
    filter(line => line.includes('error'),
        map(line => line.toLowerCase(),
            readLargeFile()
        )
    )
);

// No processing until we iterate
for (const line of validLines) {
    console.log(line);
}

// Pagination helper
function* paginate(items, pageSize) {
    for (let i = 0; i < items.length; i += pageSize) {
        yield items.slice(i, i + pageSize);
    }
}

const data = [...range(1, 100)];
const pages = paginate(data, 10);

// Get specific page
const page3 = [...take(1, drop(2, pages))][0];
console.log(page3);  // [21, 22, 23, ..., 30]

// Sliding window
function* window(size, iterable) {
    const buffer = [];
    
    for (const value of iterable) {
        buffer.push(value);
        
        if (buffer.length === size) {
            yield [...buffer];
            buffer.shift();
        }
    }
}

const windows = [...window(3, [1, 2, 3, 4, 5])];
console.log(windows);
// [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

// Batch processing
function* batch(size, iterable) {
    let batch = [];
    
    for (const value of iterable) {
        batch.push(value);
        
        if (batch.length === size) {
            yield batch;
            batch = [];
        }
    }
    
    if (batch.length > 0) {
        yield batch;
    }
}

const batches = [...batch(3, range(1, 10))];
console.log(batches);
// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

// Zip iterators
function* zip(...iterables) {
    const iterators = iterables.map(it => it[Symbol.iterator]());
    
    while (true) {
        const results = iterators.map(it => it.next());
        
        if (results.some(r => r.done)) {
            break;
        }
        
        yield results.map(r => r.value);
    }
}

const zipped = [...zip([1, 2, 3], ['a', 'b', 'c'], [true, false, true])];
console.log(zipped);
// [[1, 'a', true], [2, 'b', false], [3, 'c', true]]

// Enumerate (with index)
function* enumerate(iterable) {
    let index = 0;
    for (const value of iterable) {
        yield [index++, value];
    }
}

const enumerated = [...enumerate(['a', 'b', 'c'])];
console.log(enumerated);
// [[0, 'a'], [1, 'b'], [2, 'c']]

Example: Composing iterator helpers

// Create reusable iterator pipelines
class IteratorPipeline {
    constructor(iterable) {
        this.iterable = iterable;
    }
    
    take(n) {
        this.iterable = take(n, this.iterable);
        return this;
    }
    
    drop(n) {
        this.iterable = drop(n, this.iterable);
        return this;
    }
    
    filter(predicate) {
        this.iterable = filter(predicate, this.iterable);
        return this;
    }
    
    map(fn) {
        this.iterable = map(fn, this.iterable);
        return this;
    }
    
    flatMap(fn) {
        this.iterable = flatMap(fn, this.iterable);
        return this;
    }
    
    // Terminal operations
    toArray() {
        return [...this.iterable];
    }
    
    reduce(fn, initial) {
        return reduce(fn, initial, this.iterable);
    }
    
    forEach(fn) {
        forEach(fn, this.iterable);
    }
    
    some(predicate) {
        return some(predicate, this.iterable);
    }
    
    every(predicate) {
        return every(predicate, this.iterable);
    }
    
    find(predicate) {
        return find(predicate, this.iterable);
    }
}

// Helper function
function pipeline(iterable) {
    return new IteratorPipeline(iterable);
}

// Usage
const result1 = pipeline(naturals())
    .filter(n => n % 2 === 0)
    .map(n => n ** 2)
    .take(5)
    .toArray();

console.log(result1);  // [4, 16, 36, 64, 100]

// Complex pipeline
const result2 = pipeline(range(1, 100))
    .filter(n => n % 3 === 0)
    .drop(5)
    .map(n => n * 2)
    .take(10)
    .reduce((sum, n) => sum + n, 0);

console.log(result2);  // Sum of processed values

// Find first match
const found = pipeline(naturals())
    .filter(n => n % 7 === 0)
    .find(n => n > 100);

console.log(found);  // 105

// Test condition
const hasLarge = pipeline(range(1, 10))
    .map(n => n ** 2)
    .some(n => n > 50);

console.log(hasLarge);  // true
Key Points: Iterator helpers enable functional programming patterns with lazy evaluation. Lazy methods (take, drop, filter, map) return iterators. Eager methods (reduce, forEach, some) consume iterator immediately. Chain helpers for complex transformations. Perfect for large datasets - only processes needed elements. ES2024+ adds native support.

Section 18 Summary: Iterators, Generators, and Iteration Protocols

  • Iterator Protocol: Object with next() returning {value, done}
  • Iterable Protocol: Object with [Symbol.iterator] method
  • Built-in Iterables: Array, String, Map, Set, TypedArray, arguments
  • Generators: function* syntax, pausable execution with yield
  • yield Expressions: yield value, yield* delegation, two-way communication
  • Generator Methods: next(), return(), throw()
  • Async Generators: async function*, for await...of loops
  • Custom Iterators: Implement [Symbol.iterator], track state, optional cleanup
  • Iterator Helpers: take, drop, filter, map, reduce (lazy evaluation)
  • Lazy Evaluation: Process data on demand, efficient for infinite sequences
  • Use Cases: Streaming data, pagination, infinite sequences, resource management

19. Error Handling and Debugging Techniques

19.1 Error Types and Error Object Properties

Built-in Error Types

Error Type Description Common Causes
Error Generic error (base class) Manually thrown errors
SyntaxError Invalid JavaScript syntax Parse errors, malformed code
ReferenceError Invalid reference to variable Undefined variables, accessing before declaration
TypeError Value is not expected type Calling non-function, accessing property on null/undefined
RangeError Value not in allowed range Invalid array length, recursion limit
URIError Invalid URI encoding/decoding encodeURI/decodeURI with malformed URI
EvalError Error in eval() (legacy) Rarely used in modern JavaScript
AggregateError ES2021 Multiple errors wrapped together Promise.any() rejection, multiple failures

Error Object Properties

Property Description Standard
name Error type name (e.g., "TypeError") ✓ Standard
message Human-readable error description ✓ Standard
stack Stack trace (non-standard but widely supported) ✗ Non-standard
cause ES2022 Underlying error that caused this error ✓ Standard
fileName File where error occurred (Firefox) ✗ Non-standard
lineNumber Line number where error occurred (Firefox) ✗ Non-standard
columnNumber Column number where error occurred (Firefox) ✗ Non-standard

Error Constructor Options

Constructor Syntax Parameters
new Error() new Error(message, options) message: string, options: {cause}
message Error description String
options.cause Original error that caused this error Any (typically Error object)

Example: Built-in error types

// SyntaxError (parse-time error, not catchable at runtime)
// eval('var x = ;');  // SyntaxError: Unexpected token ';'

// ReferenceError
try {
    console.log(nonExistentVariable);
} catch (e) {
    console.log(e.name);      // "ReferenceError"
    console.log(e.message);   // "nonExistentVariable is not defined"
}

// TypeError
try {
    const obj = null;
    obj.someMethod();
} catch (e) {
    console.log(e.name);      // "TypeError"
    console.log(e.message);   // "Cannot read properties of null"
}

try {
    const notAFunction = 42;
    notAFunction();
} catch (e) {
    console.log(e.name);      // "TypeError"
    console.log(e.message);   // "notAFunction is not a function"
}

// RangeError
try {
    const arr = new Array(-1);
} catch (e) {
    console.log(e.name);      // "RangeError"
    console.log(e.message);   // "Invalid array length"
}

function recursiveFunction() {
    recursiveFunction();
}

try {
    recursiveFunction();
} catch (e) {
    console.log(e.name);      // "RangeError"
    console.log(e.message);   // "Maximum call stack size exceeded"
}

// URIError
try {
    decodeURIComponent('%');
} catch (e) {
    console.log(e.name);      // "URIError"
    console.log(e.message);   // "URI malformed"
}

// AggregateError (ES2021)
const errors = [
    new Error('Error 1'),
    new Error('Error 2'),
    new Error('Error 3')
];

const aggregateError = new AggregateError(errors, 'Multiple errors occurred');

console.log(aggregateError.name);     // "AggregateError"
console.log(aggregateError.message);  // "Multiple errors occurred"
console.log(aggregateError.errors);   // Array of 3 errors

aggregateError.errors.forEach((err, i) => {
    console.log(`Error ${i + 1}:`, err.message);
});

Example: Error object properties

// Creating error manually
const error = new Error('Something went wrong');

console.log(error.name);     // "Error"
console.log(error.message);  // "Something went wrong"
console.log(error.stack);    // Stack trace (multi-line string)

// Stack trace example
function a() {
    b();
}

function b() {
    c();
}

function c() {
    throw new Error('Error in function c');
}

try {
    a();
} catch (e) {
    console.log(e.stack);
    // Error: Error in function c
    //     at c (file.js:10:11)
    //     at b (file.js:6:5)
    //     at a (file.js:2:5)
    //     at file.js:14:5
}

// Error cause (ES2022)
async function fetchData(url) {
    try {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        return await response.json();
    } catch (originalError) {
        // Wrap original error with context
        throw new Error('Failed to fetch data', {
            cause: originalError
        });
    }
}

try {
    await fetchData('https://invalid-url');
} catch (error) {
    console.log(error.message);        // "Failed to fetch data"
    console.log(error.cause);          // Original fetch error
    console.log(error.cause.message);  // Original error message
}

// Error with custom properties
const customError = new Error('Custom error');
customError.code = 'ERR_CUSTOM';
customError.statusCode = 400;
customError.timestamp = Date.now();

console.log(customError.code);        // "ERR_CUSTOM"
console.log(customError.statusCode);  // 400
console.log(customError.timestamp);   // Unix timestamp

Example: Parsing stack traces

// Parse stack trace for debugging
function parseStackTrace(error) {
    if (!error.stack) return [];
    
    const lines = error.stack.split('\n');
    const frames = [];
    
    // Skip first line (error message)
    for (let i = 1; i < lines.length; i++) {
        const line = lines[i].trim();
        
        // Example format: "at functionName (file.js:10:5)"
        const match = line.match(/at (.+?) \((.+?):(\d+):(\d+)\)/);
        
        if (match) {
            frames.push({
                functionName: match[1],
                fileName: match[2],
                lineNumber: parseInt(match[3], 10),
                columnNumber: parseInt(match[4], 10)
            });
        }
    }
    
    return frames;
}

try {
    throw new Error('Test error');
} catch (e) {
    const frames = parseStackTrace(e);
    console.log('Stack frames:', frames);
    
    // Print formatted stack
    frames.forEach((frame, i) => {
        console.log(
            `  ${i + 1}. ${frame.functionName}`,
            `at ${frame.fileName}:${frame.lineNumber}:${frame.columnNumber}`
        );
    });
}

// Capture stack trace programmatically
function captureStack() {
    const obj = {};
    Error.captureStackTrace(obj, captureStack);
    return obj.stack;
}

console.log(captureStack());
Key Points: JavaScript has 8 built-in error types. All errors have name and message properties. stack property (non-standard but universal) provides call stack. ES2022 cause option enables error chaining. AggregateError (ES2021) wraps multiple errors.

19.2 Custom Error Classes and Error Inheritance

Custom Error Pattern

Step Requirement Implementation
1. Extend Error Inherit from Error class class CustomError extends Error
2. Set name Set error name to class name this.name = this.constructor.name
3. Call super() Initialize Error with message super(message)
4. Capture stack Fix stack trace (optional) Error.captureStackTrace(this, this.constructor)
5. Add properties Add custom data this.code = code

Common Custom Error Types

Error Type Use Case Additional Properties
ValidationError Input validation failures field, value, rule
NotFoundError Resource not found (404) resourceType, resourceId
UnauthorizedError Authentication failure (401) userId, reason
ForbiddenError Authorization failure (403) action, resource
NetworkError Network/HTTP failures statusCode, url, method
TimeoutError Operation timeout duration, operation
DatabaseError Database operation failures query, code

Example: Basic custom error class

// Simple custom error
class CustomError extends Error {
    constructor(message) {
        super(message);
        this.name = this.constructor.name;
        
        // Maintains proper stack trace (V8 only)
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
}

// Usage
try {
    throw new CustomError('Something went wrong');
} catch (e) {
    console.log(e instanceof CustomError);  // true
    console.log(e instanceof Error);        // true
    console.log(e.name);                    // "CustomError"
    console.log(e.message);                 // "Something went wrong"
}

// Custom error with additional properties
class ValidationError extends Error {
    constructor(message, field, value) {
        super(message);
        this.name = 'ValidationError';
        this.field = field;
        this.value = value;
        
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
}

function validateAge(age) {
    if (typeof age !== 'number') {
        throw new ValidationError(
            'Age must be a number',
            'age',
            age
        );
    }
    
    if (age < 0 || age > 150) {
        throw new ValidationError(
            'Age must be between 0 and 150',
            'age',
            age
        );
    }
    
    return true;
}

try {
    validateAge('twenty');
} catch (e) {
    if (e instanceof ValidationError) {
        console.log(`Validation failed for field "${e.field}"`);
        console.log(`Invalid value: ${e.value}`);
        console.log(`Error: ${e.message}`);
    }
}

try {
    validateAge(200);
} catch (e) {
    console.log(e.field);    // "age"
    console.log(e.value);    // 200
    console.log(e.message);  // "Age must be between 0 and 150"
}

Example: Domain-specific error classes

// HTTP error hierarchy
class HttpError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.name = 'HttpError';
        this.statusCode = statusCode;
        
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
}

class NotFoundError extends HttpError {
    constructor(resource, id) {
        super(`${resource} with id ${id} not found`, 404);
        this.name = 'NotFoundError';
        this.resource = resource;
        this.id = id;
    }
}

class UnauthorizedError extends HttpError {
    constructor(message = 'Unauthorized') {
        super(message, 401);
        this.name = 'UnauthorizedError';
    }
}

class ForbiddenError extends HttpError {
    constructor(action, resource) {
        super(`Not allowed to ${action} ${resource}`, 403);
        this.name = 'ForbiddenError';
        this.action = action;
        this.resource = resource;
    }
}

// Usage
async function getUser(userId) {
    const user = await db.findUser(userId);
    
    if (!user) {
        throw new NotFoundError('User', userId);
    }
    
    return user;
}

async function deletePost(userId, postId) {
    const post = await db.findPost(postId);
    
    if (!post) {
        throw new NotFoundError('Post', postId);
    }
    
    if (post.authorId !== userId) {
        throw new ForbiddenError('delete', 'post');
    }
    
    await db.deletePost(postId);
}

// Error handling
try {
    await getUser(999);
} catch (e) {
    if (e instanceof NotFoundError) {
        console.log(`Status: ${e.statusCode}`);
        console.log(`${e.resource} ${e.id} not found`);
    } else if (e instanceof HttpError) {
        console.log(`HTTP Error ${e.statusCode}: ${e.message}`);
    }
}

Example: Advanced error patterns

// Error with error codes
class ApplicationError extends Error {
    constructor(message, code, details = {}) {
        super(message);
        this.name = 'ApplicationError';
        this.code = code;
        this.details = details;
        this.timestamp = new Date().toISOString();
        
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
    
    toJSON() {
        return {
            name: this.name,
            message: this.message,
            code: this.code,
            details: this.details,
            timestamp: this.timestamp
        };
    }
}

// Error factory
class ErrorFactory {
    static validation(field, message) {
        return new ApplicationError(
            message,
            'VALIDATION_ERROR',
            {field}
        );
    }
    
    static notFound(resource, id) {
        return new ApplicationError(
            `${resource} not found`,
            'NOT_FOUND',
            {resource, id}
        );
    }
    
    static network(url, statusCode) {
        return new ApplicationError(
            'Network request failed',
            'NETWORK_ERROR',
            {url, statusCode}
        );
    }
}

try {
    throw ErrorFactory.validation('email', 'Invalid email format');
} catch (e) {
    console.log(e.code);              // "VALIDATION_ERROR"
    console.log(e.details.field);     // "email"
    console.log(JSON.stringify(e));   // Full error as JSON
}

// Async error wrapper
class AsyncOperationError extends Error {
    constructor(message, operation, originalError) {
        super(message);
        this.name = 'AsyncOperationError';
        this.operation = operation;
        this.originalError = originalError;
        
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
}

async function wrapAsync(operation, fn) {
    try {
        return await fn();
    } catch (error) {
        throw new AsyncOperationError(
            `Failed to ${operation}`,
            operation,
            error
        );
    }
}

try {
    await wrapAsync('fetch user data', async () => {
        throw new Error('Network timeout');
    });
} catch (e) {
    console.log(e.message);                    // "Failed to fetch user data"
    console.log(e.operation);                  // "fetch user data"
    console.log(e.originalError.message);      // "Network timeout"
}

// Retryable error
class RetryableError extends Error {
    constructor(message, retryAfter = 1000) {
        super(message);
        this.name = 'RetryableError';
        this.retryAfter = retryAfter;
        
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, this.constructor);
        }
    }
}

async function fetchWithRetry(url, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await fetch(url);
        } catch (error) {
            if (attempt === maxRetries) {
                throw error;
            }
            
            throw new RetryableError(
                `Attempt ${attempt} failed`,
                1000 * attempt
            );
        }
    }
}
Best Practices: Extend Error class for custom errors. Set name property to class name. Use Error.captureStackTrace for clean stack traces. Add domain-specific properties (field, statusCode, code). Create error hierarchies for different error categories. Implement toJSON() for serialization.

19.3 Exception Propagation and Error Boundaries

Exception Propagation Rules

Context Behavior Handling
Synchronous code Propagates up call stack try-catch in caller
Promise chain Propagates through .then() chain .catch() or try-catch with await
Async function Returns rejected promise try-catch or .catch()
Event handlers Does not propagate to caller Handle in event handler or window.onerror
Callbacks Does not propagate to caller Error-first callback pattern
setTimeout/setInterval Does not propagate Handle in callback or window.onerror

Error Boundary Patterns

Pattern Scope Use Case
try-catch Block-level Synchronous code, async/await
.catch() Promise chain Promise-based operations
finally Cleanup after try-catch Resource cleanup (always runs)
window.onerror Global (window) Uncaught exceptions
window.onunhandledrejection Global (promises) Unhandled promise rejections
process.on('uncaughtException') Global (Node.js) Uncaught exceptions in Node
process.on('unhandledRejection') Global (Node.js promises) Unhandled rejections in Node

Error Recovery Strategies

Strategy Description When to Use
Fail fast Throw immediately, don't catch Programming errors, invalid state
Retry Attempt operation again Transient failures (network, rate limits)
Fallback Use alternative approach/data Non-critical features, degraded mode
Circuit breaker Stop trying after repeated failures External service failures
Log and continue Record error but don't stop Non-critical operations
Graceful degradation Reduce functionality but keep working Feature failures in production

Example: Exception propagation patterns

// Synchronous propagation
function level3() {
    throw new Error('Error at level 3');
}

function level2() {
    level3();  // Error propagates up
}

function level1() {
    try {
        level2();
    } catch (e) {
        console.log('Caught at level 1:', e.message);
    }
}

level1();  // "Caught at level 1: Error at level 3"

// Async propagation with promises
function asyncLevel3() {
    return Promise.reject(new Error('Async error'));
}

function asyncLevel2() {
    return asyncLevel3();  // Rejection propagates
}

function asyncLevel1() {
    return asyncLevel2()
        .catch(e => {
            console.log('Caught in promise chain:', e.message);
        });
}

asyncLevel1();

// Async/await propagation
async function asyncAwaitLevel3() {
    throw new Error('Async/await error');
}

async function asyncAwaitLevel2() {
    await asyncAwaitLevel3();  // Error propagates
}

async function asyncAwaitLevel1() {
    try {
        await asyncAwaitLevel2();
    } catch (e) {
        console.log('Caught with async/await:', e.message);
    }
}

asyncAwaitLevel1();

// Event handler - no propagation
document.getElementById('button').addEventListener('click', () => {
    throw new Error('Error in event handler');
    // This will NOT be caught by outer try-catch
});

// Must handle in event handler
document.getElementById('button').addEventListener('click', () => {
    try {
        // risky operation
        throw new Error('Handled error');
    } catch (e) {
        console.log('Caught in handler:', e.message);
    }
});

// Callback - no propagation
function asyncOperation(callback) {
    setTimeout(() => {
        try {
            throw new Error('Error in callback');
        } catch (e) {
            callback(e, null);  // Error-first callback pattern
        }
    }, 100);
}

asyncOperation((error, result) => {
    if (error) {
        console.log('Error received:', error.message);
        return;
    }
    console.log('Result:', result);
});

Example: Global error handlers

// Browser: Global error handler
window.onerror = function(message, source, lineno, colno, error) {
    console.log('Global error caught:');
    console.log('Message:', message);
    console.log('Source:', source);
    console.log('Line:', lineno, 'Column:', colno);
    console.log('Error object:', error);
    
    // Return true to prevent default error handling
    return true;
};

// Browser: Unhandled promise rejections
window.onunhandledrejection = function(event) {
    console.log('Unhandled rejection:');
    console.log('Reason:', event.reason);
    console.log('Promise:', event.promise);
    
    // Prevent default handling
    event.preventDefault();
};

// Alternative with addEventListener
window.addEventListener('error', (event) => {
    console.log('Error event:', event.error);
});

window.addEventListener('unhandledrejection', (event) => {
    console.log('Unhandled rejection:', event.reason);
});

// Node.js: Global error handlers
process.on('uncaughtException', (error, origin) => {
    console.error('Uncaught exception:');
    console.error('Error:', error);
    console.error('Origin:', origin);
    
    // Clean up and exit
    // process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
    console.error('Unhandled rejection:');
    console.error('Reason:', reason);
    console.error('Promise:', promise);
});

// Example: Triggering global handlers
setTimeout(() => {
    throw new Error('Uncaught error in setTimeout');
}, 100);

Promise.reject(new Error('Unhandled promise rejection'));

// Centralized error logger
class ErrorLogger {
    static log(error, context = {}) {
        const errorInfo = {
            message: error.message,
            name: error.name,
            stack: error.stack,
            timestamp: new Date().toISOString(),
            context,
            url: typeof window !== 'undefined' ? window.location.href : null,
            userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : null
        };
        
        console.error('Error logged:', errorInfo);
        
        // Send to monitoring service
        // sendToMonitoring(errorInfo);
    }
}

window.onerror = function(message, source, lineno, colno, error) {
    ErrorLogger.log(error || new Error(message), {
        source,
        lineno,
        colno
    });
    return true;
};

window.onunhandledrejection = function(event) {
    ErrorLogger.log(
        event.reason instanceof Error 
            ? event.reason 
            : new Error(String(event.reason)),
        {type: 'unhandled-rejection'}
    );
};

Example: Error recovery strategies

// Retry with exponential backoff
async function retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await fn();
        } catch (error) {
            if (attempt === maxRetries) {
                throw error;
            }
            
            const delay = baseDelay * Math.pow(2, attempt - 1);
            console.log(`Attempt ${attempt} failed, retrying in ${delay}ms`);
            await new Promise(resolve => setTimeout(resolve, delay));
        }
    }
}

// Usage
await retryWithBackoff(async () => {
    const response = await fetch('/api/data');
    if (!response.ok) throw new Error('Fetch failed');
    return response.json();
});

// Fallback pattern
async function fetchWithFallback(primaryUrl, fallbackUrl) {
    try {
        return await fetch(primaryUrl);
    } catch (error) {
        console.log('Primary failed, using fallback');
        return await fetch(fallbackUrl);
    }
}

// Circuit breaker
class CircuitBreaker {
    constructor(threshold = 5, timeout = 60000) {
        this.failureCount = 0;
        this.threshold = threshold;
        this.timeout = timeout;
        this.state = 'CLOSED';  // CLOSED, OPEN, HALF_OPEN
        this.nextAttempt = Date.now();
    }
    
    async execute(fn) {
        if (this.state === 'OPEN') {
            if (Date.now() < this.nextAttempt) {
                throw new Error('Circuit breaker is OPEN');
            }
            this.state = 'HALF_OPEN';
        }
        
        try {
            const result = await fn();
            this.onSuccess();
            return result;
        } catch (error) {
            this.onFailure();
            throw error;
        }
    }
    
    onSuccess() {
        this.failureCount = 0;
        this.state = 'CLOSED';
    }
    
    onFailure() {
        this.failureCount++;
        if (this.failureCount >= this.threshold) {
            this.state = 'OPEN';
            this.nextAttempt = Date.now() + this.timeout;
            console.log(`Circuit breaker OPEN for ${this.timeout}ms`);
        }
    }
}

const breaker = new CircuitBreaker(3, 30000);

async function callExternalService() {
    return await breaker.execute(async () => {
        const response = await fetch('/external-api');
        if (!response.ok) throw new Error('Service unavailable');
        return response.json();
    });
}

// Graceful degradation
async function getUserData(userId) {
    try {
        // Try to fetch from API
        const response = await fetch(`/api/users/${userId}`);
        return await response.json();
    } catch (error) {
        console.warn('API unavailable, using cached data');
        
        try {
            // Fallback to cache
            return await getCachedUserData(userId);
        } catch (cacheError) {
            console.warn('Cache unavailable, using default data');
            
            // Final fallback to defaults
            return {
                id: userId,
                name: 'Guest User',
                avatar: '/default-avatar.png'
            };
        }
    }
}
Key Points: Exceptions propagate up synchronous call stack until caught. Promises propagate rejections through chain. Event handlers and timers don't propagate errors. Use window.onerror and onunhandledrejection for global handling. Implement retry with exponential backoff for transient failures. Circuit breaker pattern prevents cascading failures.

19.4 Console API and Logging Methods

Console Logging Methods

Method Purpose Output Style
console.log() General logging Standard output
console.info() Informational messages Info icon (browser dependent)
console.warn() Warning messages Yellow warning icon
console.error() Error messages Red error icon with stack trace
console.debug() Debug messages (may be hidden) Debug level (filterable)

Console Formatting and Inspection

Method Syntax Description
console.dir() console.dir(object) Display object properties interactively
console.dirxml() console.dirxml(node) Display XML/HTML element tree
console.table() console.table(array) Display array/object as table
console.group() console.group(label) Start collapsible group
console.groupCollapsed() console.groupCollapsed(label) Start collapsed group
console.groupEnd() console.groupEnd() End current group

Console Utilities

Method Syntax Description
console.assert() console.assert(condition, message) Log error if condition false
console.count() console.count(label) Count calls with label
console.countReset() console.countReset(label) Reset counter for label
console.time() console.time(label) Start timer with label
console.timeLog() console.timeLog(label) Log current timer value
console.timeEnd() console.timeEnd(label) Stop timer and log duration
console.trace() console.trace(message) Log stack trace
console.clear() console.clear() Clear console

String Substitution Patterns

Pattern Type Example
%s String console.log('Hello %s', 'World')
%d or %i Integer console.log('Count: %d', 42)
%f Float console.log('Pi: %f', 3.14159)
%o Object console.log('Object: %o', obj)
%O Object (optimized) console.log('Object: %O', obj)
%c CSS styling console.log('%cStyled', 'color: red')

Example: Basic console methods

// Log levels
console.log('Standard log message');
console.info('Informational message');
console.warn('Warning message');
console.error('Error message');
console.debug('Debug message');

// Multiple arguments
console.log('Name:', 'John', 'Age:', 30, 'Active:', true);

// String substitution
console.log('Hello %s, you are %d years old', 'Alice', 25);
console.log('Pi is approximately %f', Math.PI);

// CSS styling
console.log(
    '%cStyled Text',
    'color: blue; font-size: 20px; font-weight: bold'
);

console.log(
    '%cError: %cSomething went wrong',
    'color: red; font-weight: bold',
    'color: orange'
);

// Object inspection
const user = {
    name: 'John',
    age: 30,
    address: {
        city: 'New York',
        country: 'USA'
    }
};

console.log(user);        // Standard object display
console.dir(user);        // Interactive object properties
console.dir(user, {depth: null});  // Show all nested levels

// Table display
const users = [
    {id: 1, name: 'Alice', age: 25},
    {id: 2, name: 'Bob', age: 30},
    {id: 3, name: 'Charlie', age: 35}
];

console.table(users);
console.table(users, ['name', 'age']);  // Show specific columns

// DOM element
const element = document.getElementById('app');
console.log(element);      // DOM representation
console.dir(element);      // Object properties
console.dirxml(element);   // XML tree structure

Example: Console grouping and organization

// Simple grouping
console.group('User Details');
console.log('Name: John');
console.log('Age: 30');
console.log('Email: john@example.com');
console.groupEnd();

// Nested groups
console.group('Application Startup');
console.log('Initializing...');

console.group('Loading Modules');
console.log('Module A loaded');
console.log('Module B loaded');
console.log('Module C loaded');
console.groupEnd();

console.group('Connecting to Services');
console.log('Database connected');
console.log('API connected');
console.groupEnd();

console.log('Startup complete');
console.groupEnd();

// Collapsed groups
console.groupCollapsed('Debug Information');
console.log('This is collapsed by default');
console.log('User must expand to see');
console.groupEnd();

// Organizing API calls
async function fetchUserData(userId) {
    console.group(`Fetching user ${userId}`);
    console.time('fetch-duration');
    
    try {
        console.log('Sending request...');
        const response = await fetch(`/api/users/${userId}`);
        
        console.log('Response:', response.status);
        const data = await response.json();
        
        console.log('Data received:', data);
        console.timeEnd('fetch-duration');
        console.groupEnd();
        
        return data;
    } catch (error) {
        console.error('Fetch failed:', error);
        console.timeEnd('fetch-duration');
        console.groupEnd();
        throw error;
    }
}

// Function call tracing
function traceFunction(fn, name) {
    return function(...args) {
        console.group(`${name} called`);
        console.log('Arguments:', args);
        
        try {
            const result = fn(...args);
            console.log('Result:', result);
            console.groupEnd();
            return result;
        } catch (error) {
            console.error('Error:', error);
            console.groupEnd();
            throw error;
        }
    };
}

const add = traceFunction((a, b) => a + b, 'add');
add(2, 3);  // Logs group with args and result

Example: Console utilities

// Assertions
console.assert(1 === 1, 'This will not log');
console.assert(1 === 2, 'This WILL log as error');

function divide(a, b) {
    console.assert(b !== 0, 'Division by zero');
    return a / b;
}

divide(10, 0);  // Logs assertion error

// Counting
function processItem(item) {
    console.count('processItem');
    console.log('Processing:', item);
}

processItem('A');  // "processItem: 1"
processItem('B');  // "processItem: 2"
processItem('C');  // "processItem: 3"
console.countReset('processItem');
processItem('D');  // "processItem: 1" (reset)

// Counting with labels
function handleEvent(type) {
    console.count(type);
}

handleEvent('click');    // "click: 1"
handleEvent('scroll');   // "scroll: 1"
handleEvent('click');    // "click: 2"
handleEvent('scroll');   // "scroll: 2"

// Timing operations
console.time('operation');

// Simulate work
for (let i = 0; i < 1000000; i++) {
    // work
}

console.timeLog('operation');  // Intermediate timing
// More work
console.timeEnd('operation');  // Final timing and stop

// Timing async operations
async function measureAsync() {
    console.time('async-operation');
    
    await fetch('/api/data');
    console.timeLog('async-operation', 'After fetch');
    
    await processData();
    console.timeLog('async-operation', 'After processing');
    
    console.timeEnd('async-operation');
}

// Multiple timers
console.time('timer-1');
console.time('timer-2');

setTimeout(() => console.timeEnd('timer-1'), 100);
setTimeout(() => console.timeEnd('timer-2'), 200);

// Stack trace
function a() {
    b();
}

function b() {
    c();
}

function c() {
    console.trace('Called from c()');
}

a();  // Shows full call stack: c <- b <- a

// Performance monitoring
function measurePerformance(fn, name) {
    console.time(name);
    console.count(name);
    
    try {
        const result = fn();
        console.timeEnd(name);
        return result;
    } catch (error) {
        console.error(`${name} failed:`, error);
        console.timeEnd(name);
        throw error;
    }
}

measurePerformance(() => {
    // Some operation
    return Array(1000).fill(0).map((_, i) => i * 2);
}, 'array-operation');

Example: Custom logger implementation

// Production-ready logger
class Logger {
    constructor(options = {}) {
        this.level = options.level || 'info';
        this.prefix = options.prefix || '';
        this.levels = {
            debug: 0,
            info: 1,
            warn: 2,
            error: 3
        };
    }
    
    shouldLog(level) {
        return this.levels[level] >= this.levels[this.level];
    }
    
    format(level, ...args) {
        const timestamp = new Date().toISOString();
        const prefix = this.prefix ? `[${this.prefix}]` : '';
        return [`[${timestamp}]`, prefix, `[${level.toUpperCase()}]`, ...args];
    }
    
    debug(...args) {
        if (this.shouldLog('debug')) {
            console.debug(...this.format('debug', ...args));
        }
    }
    
    info(...args) {
        if (this.shouldLog('info')) {
            console.info(...this.format('info', ...args));
        }
    }
    
    warn(...args) {
        if (this.shouldLog('warn')) {
            console.warn(...this.format('warn', ...args));
        }
    }
    
    error(...args) {
        if (this.shouldLog('error')) {
            console.error(...this.format('error', ...args));
            console.trace();
        }
    }
    
    group(label) {
        console.group(...this.format('group', label));
    }
    
    groupEnd() {
        console.groupEnd();
    }
    
    time(label) {
        console.time(label);
    }
    
    timeEnd(label) {
        console.timeEnd(label);
    }
}

// Usage
const logger = new Logger({
    level: 'info',
    prefix: 'MyApp'
});

logger.debug('This will not show (below info level)');
logger.info('Application started');
logger.warn('Low memory');
logger.error('Fatal error occurred');

// Module-specific loggers
const apiLogger = new Logger({level: 'debug', prefix: 'API'});
const dbLogger = new Logger({level: 'info', prefix: 'DB'});

apiLogger.debug('Request received');
dbLogger.info('Query executed');

// Environment-aware logger
const isDevelopment = process.env.NODE_ENV === 'development';

const appLogger = new Logger({
    level: isDevelopment ? 'debug' : 'warn',
    prefix: 'App'
});

appLogger.debug('This shows only in development');
Key Points: Use console.log() for general, console.warn() for warnings, console.error() for errors. console.table() displays arrays beautifully. Group related logs with console.group(). console.time() measures performance. Use console.assert() for condition checks. %c enables CSS styling. Implement custom logger for production with log levels.

19.5 Debugging Techniques and Breakpoint Usage

Breakpoint Types

Type Usage When to Use
Line breakpoint Click line number in debugger Pause at specific line
Conditional breakpoint Right-click line, add condition Pause only when condition true
Logpoint Log without stopping execution Non-intrusive logging
debugger statement debugger; in code Programmatic breakpoint
DOM breakpoint Break on attribute/subtree changes Debug DOM mutations
XHR breakpoint Break on network requests Debug AJAX calls
Event listener breakpoint Break on event types Debug event handling
Exception breakpoint Break on caught/uncaught exceptions Debug error handling

Debugger Commands

Command Shortcut Action
Continue F8 / Cmd+\ Resume execution until next breakpoint
Step Over F10 / Cmd+' Execute current line, don't enter functions
Step Into F11 / Cmd+; Enter function calls
Step Out Shift+F11 / Shift+Cmd+; Exit current function
Step F9 Single-step through code (Node.js)

Debug Console Commands

Command Description Example
Evaluate expression Type any JavaScript expression user.name, 2 + 2
Modify variables Change values while paused count = 10
Call functions Execute functions processData()
$0 - $4 Recently inspected elements $0.classList.add('active')
$_ Last evaluated expression $_ * 2
$() querySelector shorthand $('#myId')
$$() querySelectorAll shorthand $$('.myClass')

Debugging Best Practices

Practice Description Benefit
Source maps Map minified code to original Debug production builds
Conditional breakpoints Break only on specific conditions Avoid breaking on every iteration
Watch expressions Monitor variable values Track state changes
Call stack inspection View function call hierarchy Understand execution flow
Blackbox scripts Skip library code Focus on application code

Example: Debugger statement and breakpoints

// debugger statement
function calculateTotal(items) {
    let total = 0;
    
    for (const item of items) {
        debugger;  // Execution pauses here when DevTools open
        total += item.price * item.quantity;
    }
    
    return total;
}

// Conditional debugger
function processUsers(users) {
    for (const user of users) {
        // Only break for specific user
        if (user.id === 123) {
            debugger;
        }
        
        processUser(user);
    }
}

// Debug with assertions
function divide(a, b) {
    console.assert(typeof a === 'number', 'a must be number');
    console.assert(typeof b === 'number', 'b must be number');
    console.assert(b !== 0, 'Division by zero');
    
    return a / b;
}

// Debug wrapper
function debug(fn, name) {
    return function(...args) {
        console.group(`Debug: ${name}`);
        console.log('Arguments:', args);
        console.time(name);
        
        debugger;  // Pause before execution
        
        try {
            const result = fn(...args);
            console.log('Result:', result);
            console.timeEnd(name);
            console.groupEnd();
            return result;
        } catch (error) {
            console.error('Error:', error);
            console.timeEnd(name);
            console.groupEnd();
            throw error;
        }
    };
}

const debugAdd = debug((a, b) => a + b, 'add');
debugAdd(2, 3);

// Breakpoint on error
function riskyOperation() {
    try {
        // risky code
        throw new Error('Something went wrong');
    } catch (error) {
        debugger;  // Pause to inspect error
        console.error('Error caught:', error);
    }
}

Example: Debugging async code

// Debug promise chain
fetch('/api/users')
    .then(response => {
        debugger;  // Inspect response
        return response.json();
    })
    .then(data => {
        debugger;  // Inspect parsed data
        console.log('Users:', data);
    })
    .catch(error => {
        debugger;  // Inspect error
        console.error('Fetch failed:', error);
    });

// Debug async/await
async function fetchUserData(userId) {
    try {
        console.log('Fetching user:', userId);
        
        debugger;  // Before fetch
        const response = await fetch(`/api/users/${userId}`);
        
        debugger;  // After fetch, inspect response
        const data = await response.json();
        
        debugger;  // After parsing, inspect data
        return data;
    } catch (error) {
        debugger;  // On error
        console.error('Error:', error);
        throw error;
    }
}

// Debug with detailed logging
async function debugAsyncOperation(operation, name) {
    console.group(`Async: ${name}`);
    console.time(name);
    
    try {
        console.log('Starting...');
        const result = await operation();
        
        console.log('Completed:', result);
        console.timeEnd(name);
        console.groupEnd();
        
        return result;
    } catch (error) {
        console.error('Failed:', error);
        console.trace();
        console.timeEnd(name);
        console.groupEnd();
        
        throw error;
    }
}

// Usage
await debugAsyncOperation(
    () => fetch('/api/data').then(r => r.json()),
    'fetch-data'
);

// Debug race conditions
let requestCount = 0;

async function debugRaceCondition() {
    const id = ++requestCount;
    
    console.log(`Request ${id} started`);
    debugger;
    
    await new Promise(r => setTimeout(r, Math.random() * 1000));
    
    console.log(`Request ${id} completed`);
    debugger;
}

// Multiple concurrent calls
Promise.all([
    debugRaceCondition(),
    debugRaceCondition(),
    debugRaceCondition()
]);

Example: Advanced debugging techniques

// Performance profiling
function profileFunction(fn, iterations = 1000) {
    console.time('profile');
    
    for (let i = 0; i < iterations; i++) {
        fn();
    }
    
    console.timeEnd('profile');
}

profileFunction(() => {
    // Code to profile
    const arr = Array(100).fill(0).map((_, i) => i * 2);
});

// Memory usage tracking (Chrome)
function checkMemory() {
    if (performance.memory) {
        console.group('Memory Usage');
        console.log('Used:', 
            Math.round(performance.memory.usedJSHeapSize / 1048576), 'MB');
        console.log('Total:', 
            Math.round(performance.memory.totalJSHeapSize / 1048576), 'MB');
        console.log('Limit:', 
            Math.round(performance.memory.jsHeapSizeLimit / 1048576), 'MB');
        console.groupEnd();
    }
}

// Before operation
checkMemory();

// Do memory-intensive work
const largeArray = Array(1000000).fill({data: 'test'});

// After operation
checkMemory();

// Stack trace analysis
function captureCallers() {
    const stack = new Error().stack;
    const lines = stack.split('\n');
    
    console.group('Call Stack');
    lines.slice(2).forEach(line => {  // Skip Error and this function
        console.log(line.trim());
    });
    console.groupEnd();
}

function a() { b(); }
function b() { c(); }
function c() { captureCallers(); }

a();

// Debugging higher-order functions
function debugHOF(fn, name) {
    return function(...args) {
        console.group(`HOF: ${name}`);
        console.log('Input:', args);
        
        const result = fn(...args);
        
        console.log('Output:', result);
        console.groupEnd();
        
        return result;
    };
}

const numbers = [1, 2, 3, 4, 5];

const debugMap = debugHOF(x => x * 2, 'double');
const debugFilter = debugHOF(x => x > 2, 'greaterThan2');
const debugReduce = debugHOF((a, b) => a + b, 'sum');

const doubled = numbers.map(debugMap);
const filtered = numbers.filter(debugFilter);
const sum = numbers.reduce(debugReduce, 0);

// Debug event listeners
function debugEventListener(element, event, handler) {
    element.addEventListener(event, function(...args) {
        console.group(`Event: ${event}`);
        console.log('Target:', args[0].target);
        console.log('Event:', args[0]);
        console.trace('Call stack');
        
        const result = handler.apply(this, args);
        
        console.groupEnd();
        return result;
    });
}

const button = document.getElementById('myButton');
debugEventListener(button, 'click', (e) => {
    console.log('Button clicked');
});
Key Points: Use debugger statement for programmatic breakpoints. Conditional breakpoints prevent breaking on every iteration. Step Over skips function calls, Step Into enters them. Inspect call stack to understand execution flow. Use watch expressions to monitor variables. Enable source maps for debugging transpiled code.

19.6 Error Monitoring and Production Debugging

Error Monitoring Services

Service Features Use Case
Sentry Error tracking, performance, releases Full-featured error monitoring
Rollbar Real-time error tracking Error aggregation and alerts
Bugsnag Error monitoring, stability score Mobile and web apps
LogRocket Session replay, error tracking User session debugging
Datadog APM, logging, error tracking Full observability platform
New Relic APM, browser monitoring Application performance

Error Context Data

Category Data Purpose
User Info ID, email, username Identify affected users
Environment Browser, OS, device Reproduce environment
Application Version, build, release Track regressions
Request URL, method, headers Understand context
Breadcrumbs User actions, navigation Trace user journey
Custom Tags Feature flags, experiments Segment and filter errors

Production Debugging Strategies

Strategy Implementation Benefit
Source maps Upload to error monitoring service Debug minified code
Structured logging JSON format with context Searchable, parseable logs
Correlation IDs Track requests across services Distributed tracing
Feature flags Enable debug mode for specific users Production debugging
Error sampling Rate limit error reports Reduce noise, cost
Release tracking Tag errors with version Identify regressions

Example: Error monitoring setup

// Sentry integration example
import * as Sentry from '@sentry/browser';

Sentry.init({
    dsn: 'YOUR_SENTRY_DSN',
    environment: process.env.NODE_ENV,
    release: 'my-app@1.0.0',
    
    // Performance monitoring
    tracesSampleRate: 0.1,
    
    // Error filtering
    beforeSend(event, hint) {
        // Filter out certain errors
        if (event.exception) {
            const error = hint.originalException;
            
            // Ignore network errors
            if (error.message.includes('NetworkError')) {
                return null;
            }
            
            // Ignore specific status codes
            if (error.statusCode === 404) {
                return null;
            }
        }
        
        return event;
    },
    
    // PII scrubbing
    beforeBreadcrumb(breadcrumb) {
        if (breadcrumb.category === 'xhr') {
            // Remove sensitive data from URLs
            breadcrumb.data.url = breadcrumb.data.url.replace(
                /token=([^&]+)/,
                'token=[REDACTED]'
            );
        }
        return breadcrumb;
    }
});

// Set user context
Sentry.setUser({
    id: user.id,
    email: user.email,
    username: user.username
});

// Set custom tags
Sentry.setTag('feature_flag_new_ui', 'enabled');
Sentry.setTag('subscription_tier', 'premium');

// Set custom context
Sentry.setContext('app', {
    build: '12345',
    commit: 'abc123'
});

// Manual error reporting
try {
    riskyOperation();
} catch (error) {
    Sentry.captureException(error, {
        level: 'error',
        tags: {
            operation: 'riskyOperation'
        },
        contexts: {
            operation: {
                input: operationInput,
                attemptNumber: retryCount
            }
        }
    });
}

// Breadcrumbs for debugging
Sentry.addBreadcrumb({
    category: 'auth',
    message: 'User logged in',
    level: 'info'
});

Sentry.addBreadcrumb({
    category: 'navigation',
    message: 'Navigated to /dashboard',
    level: 'info'
});

// Capture message (non-error)
Sentry.captureMessage('Unusual condition detected', 'warning');

Example: Production logging system

// Structured logger for production
class ProductionLogger {
    constructor(config = {}) {
        this.serviceName = config.serviceName || 'app';
        this.environment = config.environment || 'production';
        this.version = config.version || '1.0.0';
    }
    
    createLogEntry(level, message, context = {}) {
        return {
            timestamp: new Date().toISOString(),
            level,
            message,
            service: this.serviceName,
            environment: this.environment,
            version: this.version,
            correlation_id: context.correlationId || this.generateCorrelationId(),
            user_id: context.userId,
            session_id: context.sessionId,
            url: typeof window !== 'undefined' ? window.location.href : undefined,
            user_agent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
            ...context.extra
        };
    }
    
    generateCorrelationId() {
        return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    }
    
    log(level, message, context) {
        const entry = this.createLogEntry(level, message, context);
        
        // Send to logging service
        this.send(entry);
        
        // Also log to console in development
        if (this.environment === 'development') {
            console[level](message, entry);
        }
    }
    
    async send(entry) {
        try {
            await fetch('/api/logs', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(entry)
            });
        } catch (error) {
            // Fallback: log to console
            console.error('Failed to send log:', error);
        }
    }
    
    info(message, context) {
        this.log('info', message, context);
    }
    
    warn(message, context) {
        this.log('warn', message, context);
    }
    
    error(message, error, context = {}) {
        this.log('error', message, {
            ...context,
            extra: {
                ...context.extra,
                error_name: error.name,
                error_message: error.message,
                error_stack: error.stack
            }
        });
    }
}

const logger = new ProductionLogger({
    serviceName: 'frontend-app',
    environment: process.env.NODE_ENV,
    version: process.env.APP_VERSION
});

// Usage
logger.info('User logged in', {
    userId: '123',
    extra: {
        login_method: 'oauth'
    }
});

try {
    await fetchData();
} catch (error) {
    logger.error('Failed to fetch data', error, {
        userId: currentUser.id,
        extra: {
            endpoint: '/api/data',
            retry_count: 3
        }
    });
}

Example: Error sampling and rate limiting

// Error sampling to reduce noise
class ErrorSampler {
    constructor(sampleRate = 0.1) {
        this.sampleRate = sampleRate;
        this.errorCounts = new Map();
        this.resetInterval = 60000;  // 1 minute
        
        setInterval(() => this.errorCounts.clear(), this.resetInterval);
    }
    
    shouldReport(error) {
        const errorKey = `${error.name}:${error.message}`;
        const count = this.errorCounts.get(errorKey) || 0;
        
        // Always report first occurrence
        if (count === 0) {
            this.errorCounts.set(errorKey, 1);
            return true;
        }
        
        // Sample subsequent occurrences
        this.errorCounts.set(errorKey, count + 1);
        return Math.random() < this.sampleRate;
    }
    
    report(error, context = {}) {
        if (this.shouldReport(error)) {
            // Send to monitoring service
            errorMonitoring.captureException(error, {
                ...context,
                extra: {
                    ...context.extra,
                    error_count: this.errorCounts.get(
                        `${error.name}:${error.message}`
                    )
                }
            });
        }
    }
}

const sampler = new ErrorSampler(0.1);  // 10% sample rate

// Use in error handler
window.onerror = function(message, source, lineno, colno, error) {
    sampler.report(error || new Error(message), {
        extra: {source, lineno, colno}
    });
};

// Rate limiter for specific error types
class ErrorRateLimiter {
    constructor(maxErrors = 10, windowMs = 60000) {
        this.maxErrors = maxErrors;
        this.windowMs = windowMs;
        this.errors = new Map();
    }
    
    canReport(errorType) {
        const now = Date.now();
        const errors = this.errors.get(errorType) || [];
        
        // Remove old errors outside window
        const recentErrors = errors.filter(
            time => now - time < this.windowMs
        );
        
        if (recentErrors.length >= this.maxErrors) {
            return false;
        }
        
        recentErrors.push(now);
        this.errors.set(errorType, recentErrors);
        
        return true;
    }
}

const rateLimiter = new ErrorRateLimiter(10, 60000);

function reportError(error) {
    if (rateLimiter.canReport(error.name)) {
        errorMonitoring.captureException(error);
    } else {
        console.warn(`Rate limit exceeded for ${error.name}`);
    }
}

Example: Release tracking and version management

// Release tracking
class ReleaseTracker {
    constructor(config) {
        this.version = config.version;
        this.buildId = config.buildId;
        this.gitCommit = config.gitCommit;
        this.environment = config.environment;
        
        this.initMonitoring();
    }
    
    initMonitoring() {
        // Configure error monitoring with release info
        errorMonitoring.setRelease({
            version: this.version,
            buildId: this.buildId,
            commit: this.gitCommit
        });
        
        // Track release deployment
        this.trackDeployment();
    }
    
    async trackDeployment() {
        try {
            await fetch('/api/deployments', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    version: this.version,
                    buildId: this.buildId,
                    commit: this.gitCommit,
                    environment: this.environment,
                    timestamp: new Date().toISOString()
                })
            });
        } catch (error) {
            console.error('Failed to track deployment:', error);
        }
    }
    
    async compareWithPrevious() {
        // Fetch error rates from monitoring service
        const currentErrors = await this.getErrorRate(this.version);
        const previousErrors = await this.getErrorRate(this.previousVersion);
        
        const increase = currentErrors - previousErrors;
        const percentIncrease = (increase / previousErrors) * 100;
        
        if (percentIncrease > 50) {
            this.alertHighErrorRate(percentIncrease);
        }
    }
}

// Initialize on app start
const tracker = new ReleaseTracker({
    version: process.env.APP_VERSION,
    buildId: process.env.BUILD_ID,
    gitCommit: process.env.GIT_COMMIT,
    environment: process.env.NODE_ENV
});

// Feature flag debugging
class FeatureFlagDebugger {
    constructor() {
        this.flags = new Map();
        this.debugEnabled = false;
    }
    
    enableDebug(userId) {
        // Enable verbose logging for specific user
        if (this.isDebugUser(userId)) {
            this.debugEnabled = true;
            console.log('Debug mode enabled for user:', userId);
        }
    }
    
    isDebugUser(userId) {
        // Check if user is in debug list
        return ['debug-user-1', 'admin'].includes(userId);
    }
    
    log(...args) {
        if (this.debugEnabled) {
            console.log('[DEBUG]', ...args);
        }
    }
    
    setFlag(name, value) {
        this.flags.set(name, value);
        this.log(`Feature flag set: ${name} = ${value}`);
    }
    
    getFlag(name) {
        const value = this.flags.get(name);
        this.log(`Feature flag get: ${name} = ${value}`);
        return value;
    }
}

const featureFlags = new FeatureFlagDebugger();

// Enable debug mode for specific user
if (currentUser.id === 'debug-user-1') {
    featureFlags.enableDebug(currentUser.id);
}
Best Practices: Use error monitoring service like Sentry or Rollbar. Include context: user, environment, version. Implement error sampling to reduce noise. Upload source maps for debugging minified code. Track releases to identify regressions. Use structured logging with correlation IDs. Implement rate limiting for error reporting. Enable debug mode for specific users in production.

Section 19 Summary: Error Handling and Debugging Techniques

  • Error Types: Error, TypeError, ReferenceError, RangeError, SyntaxError, URIError, AggregateError
  • Error Properties: name, message, stack, cause (ES2022)
  • Custom Errors: Extend Error class, add domain-specific properties
  • Exception Propagation: Sync code up call stack, promises through chain, async/await
  • Global Handlers: window.onerror, onunhandledrejection, process uncaughtException
  • Error Recovery: Retry with backoff, fallback, circuit breaker, graceful degradation
  • Console API: log, warn, error, table, group, time, trace, assert
  • Debugging: debugger statement, breakpoints, step commands, watch expressions
  • Production Monitoring: Sentry, Rollbar, structured logging, source maps
  • Best Practices: Error sampling, rate limiting, release tracking, correlation IDs

20. Memory Management and Performance

20.1 Garbage Collection and Memory Lifecycle

JavaScript Memory Lifecycle

Phase Description Automatic/Manual
1. Allocation Memory allocated for variables, objects Automatic
2. Usage Read/write operations on allocated memory Manual (developer code)
3. Deallocation Release unused memory back to system Automatic (GC)

Garbage Collection Algorithms

Algorithm Strategy Usage
Reference Counting (legacy) Track number of references to object Old browsers (circular ref issues)
Mark-and-Sweep Mark reachable objects, sweep unreachable Modern browsers (default)
Generational GC Separate young/old objects V8 (Chrome, Node.js)
Incremental GC Split GC work into small chunks Reduce pause times
Concurrent GC Run GC in parallel with app V8 Orinoco

V8 Heap Structure

Space Purpose GC Strategy
New Space (Young Gen) Recently allocated objects Scavenge (fast, frequent)
Old Space (Old Gen) Objects survived multiple GC cycles Mark-Sweep-Compact (slower, less frequent)
Large Object Space Objects > 1MB Never moved
Code Space Compiled code Mark-Sweep
Map Space Hidden classes (object shapes) Mark-Sweep

Reachability and GC Roots

GC Root Description Example
Global object window (browser), global (Node.js) window.myGlobal
Local variables Variables in current execution context Function parameters, local vars
Closures Variables captured by closures Outer scope variables
Active stack Call stack variables Variables in executing functions
DOM elements Elements attached to document document.body references

Example: Understanding garbage collection

// Objects become eligible for GC when unreachable
function createObjects() {
    const obj1 = {data: new Array(1000).fill('x')};
    const obj2 = {data: new Array(1000).fill('y')};
    
    // Both objects are reachable (local variables)
    console.log('Objects created');
    
    return obj1;  // obj2 becomes unreachable after return
}

const result = createObjects();
// obj2 is now eligible for GC
// result (obj1) is still reachable

// Make obj1 eligible for GC
result = null;  // or let it go out of scope

// Circular references (handled by mark-and-sweep)
function createCircularRef() {
    const obj1 = {};
    const obj2 = {};
    
    obj1.ref = obj2;  // obj1 references obj2
    obj2.ref = obj1;  // obj2 references obj1
    
    // Both have references to each other
    // But when function returns, both become unreachable
    // Mark-and-sweep handles this correctly
}

createCircularRef();  // Both objects will be GC'd

// Closure keeping object alive
function createClosure() {
    const largeData = new Array(1000000).fill('data');
    
    return function() {
        // This closure keeps largeData alive
        console.log(largeData.length);
    };
}

const fn = createClosure();
// largeData is kept in memory because closure references it

// Release closure and its captured variables
fn = null;  // Now largeData can be GC'd

// Global variables never collected
window.globalData = new Array(1000000);  // Stays until page unload

// WeakMap/WeakSet allow GC
const cache = new WeakMap();

function cacheUserData(user) {
    // user object is key, data is value
    cache.set(user, {expensive: 'computation'});
}

let user = {id: 1, name: 'Alice'};
cacheUserData(user);

// When user is no longer referenced elsewhere
user = null;
// The WeakMap entry will be GC'd automatically

// Regular Map prevents GC
const regularCache = new Map();
let user2 = {id: 2, name: 'Bob'};
regularCache.set(user2, {data: 'value'});

user2 = null;
// Object is still in Map, won't be GC'd
// Must manually: regularCache.delete(user2)

Example: Monitoring memory usage

// Check memory usage (Chrome only)
if (performance.memory) {
    console.log('Memory usage:');
    console.log('Used JS Heap:', 
        Math.round(performance.memory.usedJSHeapSize / 1048576), 'MB');
    console.log('Total JS Heap:', 
        Math.round(performance.memory.totalJSHeapSize / 1048576), 'MB');
    console.log('JS Heap Limit:', 
        Math.round(performance.memory.jsHeapSizeLimit / 1048576), 'MB');
}

// Memory monitor
class MemoryMonitor {
    constructor(interval = 5000) {
        this.interval = interval;
        this.measurements = [];
    }
    
    start() {
        this.timer = setInterval(() => {
            if (performance.memory) {
                const measurement = {
                    timestamp: Date.now(),
                    used: performance.memory.usedJSHeapSize,
                    total: performance.memory.totalJSHeapSize,
                    limit: performance.memory.jsHeapSizeLimit
                };
                
                this.measurements.push(measurement);
                this.checkForLeaks();
            }
        }, this.interval);
    }
    
    stop() {
        clearInterval(this.timer);
    }
    
    checkForLeaks() {
        if (this.measurements.length < 10) return;
        
        // Check if memory is consistently growing
        const recent = this.measurements.slice(-10);
        const growthRate = recent.map((m, i) => {
            if (i === 0) return 0;
            return m.used - recent[i - 1].used;
        });
        
        const avgGrowth = growthRate.reduce((a, b) => a + b, 0) / growthRate.length;
        
        if (avgGrowth > 1048576) {  // 1MB average growth
            console.warn('Potential memory leak detected');
            console.log('Average growth:', Math.round(avgGrowth / 1024), 'KB/measurement');
        }
    }
    
    getReport() {
        if (this.measurements.length === 0) return null;
        
        const first = this.measurements[0];
        const last = this.measurements[this.measurements.length - 1];
        
        return {
            duration: last.timestamp - first.timestamp,
            initialMemory: Math.round(first.used / 1048576),
            currentMemory: Math.round(last.used / 1048576),
            growth: Math.round((last.used - first.used) / 1048576),
            measurements: this.measurements.length
        };
    }
}

const monitor = new MemoryMonitor(5000);
monitor.start();

// Later...
const report = monitor.getReport();
console.log('Memory Report:', report);
monitor.stop();

// Force garbage collection (Node.js with --expose-gc flag)
if (global.gc) {
    console.log('Before GC:', process.memoryUsage().heapUsed);
    global.gc();
    console.log('After GC:', process.memoryUsage().heapUsed);
}

// Node.js memory usage
function logMemoryUsage() {
    const usage = process.memoryUsage();
    
    console.log('Memory Usage:');
    console.log('RSS:', Math.round(usage.rss / 1048576), 'MB');
    console.log('Heap Total:', Math.round(usage.heapTotal / 1048576), 'MB');
    console.log('Heap Used:', Math.round(usage.heapUsed / 1048576), 'MB');
    console.log('External:', Math.round(usage.external / 1048576), 'MB');
}
Key Points: JavaScript uses automatic garbage collection. Modern engines use mark-and-sweep algorithm. V8 uses generational GC (young/old). Objects are GC'd when unreachable from GC roots. Circular references are handled correctly. Use WeakMap/WeakSet for cache that allows GC. Monitor memory with performance.memory.

20.2 Memory Leak Prevention and Detection

Common Memory Leak Patterns

Pattern Cause Solution
Global variables Accidental globals, never released Use strict mode, const/let, proper scoping
Forgotten timers setInterval/setTimeout not cleared clearInterval, clearTimeout in cleanup
Event listeners Listeners not removed removeEventListener in cleanup
Closures Large data captured in closure Limit scope, nullify references
Detached DOM DOM elements removed but referenced Remove JS references to deleted elements
Cache without limit Unbounded cache growth Use WeakMap, LRU cache, size limits
Console.log references Objects logged kept in memory Remove logs in production

Detection Tools and Techniques

Tool Purpose How to Use
Chrome DevTools Memory Heap snapshots, allocation timeline Performance > Memory, take snapshots
Heap Snapshot Comparison Find objects not being freed Take 2+ snapshots, compare
Allocation Timeline Track allocations over time Record timeline, analyze patterns
performance.memory Monitor heap size programmatically Check usedJSHeapSize periodically
Node.js --inspect Debug Node.js memory node --inspect app.js
process.memoryUsage() Node.js memory stats Log heapUsed, rss

Prevention Best Practices

Practice Description Benefit
Use strict mode Prevent accidental globals Catches undeclared variables
Cleanup in lifecycle Remove listeners, clear timers Proper resource management
WeakMap/WeakSet Allow garbage collection Automatic cache cleanup
Limit closure scope Don't capture unnecessary data Reduce memory footprint
Nullify references Set to null when done Make objects eligible for GC
Use object pools Reuse objects instead of creating Reduce allocation pressure

Example: Common memory leak patterns

// LEAK 1: Accidental global
function createLeak1() {
    // Missing 'const', creates global
    leakedVariable = 'This is global';  // BAD
}

createLeak1();
console.log(window.leakedVariable);  // Accessible globally

// FIX: Use strict mode and proper declarations
'use strict';

function noLeak1() {
    const properVariable = 'This is local';  // GOOD
}

// LEAK 2: Forgotten timer
function createLeak2() {
    const largeData = new Array(1000000).fill('data');
    
    setInterval(() => {
        console.log(largeData.length);  // Keeps largeData in memory
    }, 1000);  // BAD: Never cleared
}

createLeak2();

// FIX: Clear timer
function noLeak2() {
    const largeData = new Array(1000000).fill('data');
    
    const timer = setInterval(() => {
        console.log(largeData.length);
    }, 1000);
    
    // Clear when done
    setTimeout(() => clearInterval(timer), 10000);  // GOOD
}

// LEAK 3: Event listener not removed
class ComponentWithLeak {
    constructor() {
        this.data = new Array(1000000).fill('data');
        
        // BAD: Listener keeps component in memory
        document.addEventListener('click', this.handleClick.bind(this));
    }
    
    handleClick() {
        console.log(this.data.length);
    }
    
    // No cleanup method
}

// FIX: Remove listener
class ComponentWithoutLeak {
    constructor() {
        this.data = new Array(1000000).fill('data');
        this.boundHandleClick = this.handleClick.bind(this);
        
        document.addEventListener('click', this.boundHandleClick);
    }
    
    handleClick() {
        console.log(this.data.length);
    }
    
    destroy() {
        // GOOD: Remove listener
        document.removeEventListener('click', this.boundHandleClick);
        this.data = null;
    }
}

// LEAK 4: Detached DOM nodes
let detachedNodes = [];

function createLeak4() {
    const div = document.createElement('div');
    div.innerHTML = '<p>Content</p>';
    document.body.appendChild(div);
    
    // Store reference
    detachedNodes.push(div);
    
    // Remove from DOM but still referenced
    document.body.removeChild(div);  // BAD: Still in detachedNodes array
}

// FIX: Remove all references
function noLeak4() {
    const div = document.createElement('div');
    div.innerHTML = '<p>Content</p>';
    document.body.appendChild(div);
    
    // When removing from DOM, clear references
    document.body.removeChild(div);
    div = null;  // GOOD
}

// LEAK 5: Closures capturing large data
function createLeak5() {
    const hugeArray = new Array(1000000).fill('data');
    
    return function() {
        // This closure captures hugeArray even if not used
        console.log('Function called');
    };
}

const fn = createLeak5();  // hugeArray kept in memory

// FIX: Limit closure scope
function noLeak5() {
    const hugeArray = new Array(1000000).fill('data');
    const length = hugeArray.length;  // Extract only what's needed
    
    return function() {
        console.log('Array had', length, 'elements');  // GOOD
        // hugeArray is not captured
    };
}

// LEAK 6: Unbounded cache
const cache = {};

function addToCache(key, value) {
    cache[key] = value;  // BAD: Grows forever
}

// FIX: Use WeakMap or size limit
const properCache = new WeakMap();
// Or LRU cache with size limit

class LRUCache {
    constructor(maxSize = 100) {
        this.maxSize = maxSize;
        this.cache = new Map();
    }
    
    set(key, value) {
        // Remove oldest if at capacity
        if (this.cache.size >= this.maxSize) {
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }
        
        this.cache.set(key, value);
    }
    
    get(key) {
        const value = this.cache.get(key);
        
        if (value !== undefined) {
            // Move to end (most recently used)
            this.cache.delete(key);
            this.cache.set(key, value);
        }
        
        return value;
    }
}

Example: Memory leak detection

// Automated leak detection
class LeakDetector {
    constructor(threshold = 50 * 1024 * 1024) {  // 50MB
        this.threshold = threshold;
        this.measurements = [];
        this.warnings = 0;
    }
    
    measure() {
        if (!performance.memory) {
            console.warn('performance.memory not available');
            return;
        }
        
        const current = performance.memory.usedJSHeapSize;
        this.measurements.push({
            timestamp: Date.now(),
            heapSize: current
        });
        
        // Keep last 100 measurements
        if (this.measurements.length > 100) {
            this.measurements.shift();
        }
        
        this.analyze();
    }
    
    analyze() {
        if (this.measurements.length < 10) return;
        
        const first = this.measurements[0];
        const last = this.measurements[this.measurements.length - 1];
        const growth = last.heapSize - first.heapSize;
        const duration = last.timestamp - first.timestamp;
        const growthRate = growth / (duration / 1000);  // bytes per second
        
        // Check if consistently growing
        if (growth > this.threshold) {
            this.warnings++;
            console.warn('Memory leak suspected!');
            console.log('Growth:', Math.round(growth / 1048576), 'MB');
            console.log('Duration:', Math.round(duration / 1000), 'seconds');
            console.log('Rate:', Math.round(growthRate / 1024), 'KB/s');
            
            this.captureSnapshot();
        }
    }
    
    captureSnapshot() {
        console.log('Capturing heap snapshot...');
        // In real implementation, take heap snapshot via DevTools Protocol
    }
    
    startMonitoring(interval = 5000) {
        this.timer = setInterval(() => this.measure(), interval);
    }
    
    stopMonitoring() {
        clearInterval(this.timer);
    }
}

const detector = new LeakDetector();
detector.startMonitoring(5000);

// Leak test helper
async function testForLeaks(fn, iterations = 10) {
    if (!performance.memory) {
        console.log('Memory profiling not available');
        return;
    }
    
    // Force GC if available
    if (global.gc) global.gc();
    
    const before = performance.memory.usedJSHeapSize;
    
    for (let i = 0; i < iterations; i++) {
        fn();
    }
    
    // Force GC if available
    if (global.gc) global.gc();
    
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    const after = performance.memory.usedJSHeapSize;
    const growth = after - before;
    
    console.log('Memory before:', Math.round(before / 1024), 'KB');
    console.log('Memory after:', Math.round(after / 1024), 'KB');
    console.log('Growth:', Math.round(growth / 1024), 'KB');
    
    if (growth > 1024 * 1024) {  // 1MB
        console.warn('Possible memory leak detected');
    } else {
        console.log('No significant memory growth');
    }
}

// Test a function for leaks
await testForLeaks(() => {
    // Function to test
    const data = new Array(10000).fill('test');
    // Should be cleaned up after each iteration
});

Example: Proper cleanup patterns

// React-style lifecycle with cleanup
class Component {
    constructor() {
        this.timers = [];
        this.listeners = [];
        this.subscriptions = [];
    }
    
    mount() {
        // Add timer
        const timer = setInterval(() => {
            this.update();
        }, 1000);
        this.timers.push(timer);
        
        // Add event listener
        const handler = this.handleClick.bind(this);
        document.addEventListener('click', handler);
        this.listeners.push({element: document, event: 'click', handler});
        
        // Add subscription
        const sub = eventBus.subscribe('data', this.handleData.bind(this));
        this.subscriptions.push(sub);
    }
    
    unmount() {
        // Clean up timers
        this.timers.forEach(timer => clearInterval(timer));
        this.timers = [];
        
        // Remove event listeners
        this.listeners.forEach(({element, event, handler}) => {
            element.removeEventListener(event, handler);
        });
        this.listeners = [];
        
        // Unsubscribe
        this.subscriptions.forEach(sub => sub.unsubscribe());
        this.subscriptions = [];
        
        // Nullify large data
        this.data = null;
    }
    
    handleClick(e) {
        console.log('Clicked');
    }
    
    handleData(data) {
        this.data = data;
    }
    
    update() {
        console.log('Update');
    }
}

// Cleanup helper
class CleanupManager {
    constructor() {
        this.cleanupFns = [];
    }
    
    register(cleanupFn) {
        this.cleanupFns.push(cleanupFn);
    }
    
    cleanup() {
        this.cleanupFns.forEach(fn => {
            try {
                fn();
            } catch (error) {
                console.error('Cleanup error:', error);
            }
        });
        this.cleanupFns = [];
    }
}

// Usage
const manager = new CleanupManager();

// Register cleanup for timer
const timer = setInterval(() => {}, 1000);
manager.register(() => clearInterval(timer));

// Register cleanup for listener
const handler = () => {};
document.addEventListener('click', handler);
manager.register(() => document.removeEventListener('click', handler));

// Later: cleanup all
manager.cleanup();

// Automatic cleanup with Symbol.dispose (TC39 proposal)
class ResourceWithCleanup {
    constructor() {
        this.resource = acquireResource();
    }
    
    [Symbol.dispose]() {
        this.resource.close();
        this.resource = null;
    }
}

// Will auto-cleanup when leaving scope
{
    using resource = new ResourceWithCleanup();
    // Use resource
}  // Automatic cleanup here
Key Points: Common leaks: globals, forgotten timers, event listeners, closures, detached DOM. Use Chrome DevTools Memory profiler for detection. Take heap snapshots and compare. Always clean up: removeEventListener, clearInterval. Use WeakMap/WeakSet for auto-cleanup caches. Implement proper lifecycle cleanup. Test for leaks by running operations repeatedly.

20.3 Performance Optimization Strategies

Rendering Performance

Strategy Technique Impact
Minimize DOM access Cache DOM references, batch reads/writes Reduce layout thrashing
Avoid layout thrashing Separate read and write operations Prevent forced reflows
Use DocumentFragment Build DOM off-document, append once Single reflow instead of many
Virtual scrolling Render only visible items Handle large lists efficiently
Debounce/throttle Limit expensive operations frequency Reduce CPU usage
requestAnimationFrame Sync visual changes with refresh rate Smooth animations, no jank

JavaScript Performance

Strategy Technique Impact
Avoid premature optimization Profile first, optimize bottlenecks Focus effort where it matters
Use efficient algorithms Choose O(n log n) over O(n²) Better scalability
Minimize object creation Reuse objects, object pools Reduce GC pressure
Memoization Cache expensive computation results Avoid redundant calculations
Lazy evaluation Defer work until needed Faster initial load
Web Workers Move heavy computation off main thread Keep UI responsive

Network Performance

Strategy Technique Impact
Minimize bundle size Code splitting, tree shaking Faster download and parse
Compress assets Gzip, Brotli compression Smaller transfer size
Cache effectively HTTP caching, service workers Avoid re-downloading
CDN usage Serve assets from edge locations Reduced latency
Resource hints preload, prefetch, dns-prefetch Anticipate needed resources
HTTP/2 Multiplexing, server push Better resource loading

Example: Avoiding layout thrashing

// BAD: Layout thrashing (alternating read/write)
function layoutThrashing() {
    const elements = document.querySelectorAll('.item');
    
    elements.forEach(el => {
        // Read (causes layout)
        const height = el.offsetHeight;
        
        // Write (invalidates layout)
        el.style.height = height + 10 + 'px';
        
        // Next read will force reflow
    });
}

// GOOD: Batch reads, then writes
function noLayoutThrashing() {
    const elements = document.querySelectorAll('.item');
    
    // Phase 1: Read all
    const heights = Array.from(elements).map(el => el.offsetHeight);
    
    // Phase 2: Write all
    elements.forEach((el, i) => {
        el.style.height = heights[i] + 10 + 'px';
    });
}

// Use FastDOM library for automatic batching
fastdom.measure(() => {
    const height = element.offsetHeight;
    
    fastdom.mutate(() => {
        element.style.height = height + 10 + 'px';
    });
});

// Efficient DOM manipulation
// BAD: Multiple reflows
function inefficientDOMUpdate() {
    for (let i = 0; i < 100; i++) {
        const div = document.createElement('div');
        div.textContent = `Item ${i}`;
        document.body.appendChild(div);  // Reflow on each append
    }
}

// GOOD: Single reflow with DocumentFragment
function efficientDOMUpdate() {
    const fragment = document.createDocumentFragment();
    
    for (let i = 0; i < 100; i++) {
        const div = document.createElement('div');
        div.textContent = `Item ${i}`;
        fragment.appendChild(div);  // No reflow
    }
    
    document.body.appendChild(fragment);  // Single reflow
}

// GOOD: Clone and replace
function efficientDOMReplace() {
    const container = document.getElementById('container');
    const clone = container.cloneNode(false);  // Empty clone
    
    // Build new content
    for (let i = 0; i < 100; i++) {
        const div = document.createElement('div');
        div.textContent = `Item ${i}`;
        clone.appendChild(div);
    }
    
    // Replace in one operation
    container.parentNode.replaceChild(clone, container);
}

Example: Debounce and throttle

// Debounce: Wait for quiet period
function debounce(func, delay) {
    let timeoutId;
    
    return function(...args) {
        clearTimeout(timeoutId);
        
        timeoutId = setTimeout(() => {
            func.apply(this, args);
        }, delay);
    };
}

// Usage: Search as user types (wait for pause)
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((query) => {
    console.log('Searching for:', query);
    // Expensive API call
    fetch(`/api/search?q=${query}`);
}, 300);

searchInput.addEventListener('input', (e) => {
    debouncedSearch(e.target.value);
});

// Throttle: Limit execution frequency
function throttle(func, limit) {
    let inThrottle;
    
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            
            setTimeout(() => {
                inThrottle = false;
            }, limit);
        }
    };
}

// Usage: Scroll event (execute at most once per 100ms)
const throttledScroll = throttle(() => {
    console.log('Scroll position:', window.scrollY);
    // Update UI based on scroll
}, 100);

window.addEventListener('scroll', throttledScroll);

// requestAnimationFrame throttle (better for visuals)
function rafThrottle(func) {
    let rafId = null;
    
    return function(...args) {
        if (rafId === null) {
            rafId = requestAnimationFrame(() => {
                func.apply(this, args);
                rafId = null;
            });
        }
    };
}

const rafThrottledScroll = rafThrottle(() => {
    // Smooth visual updates
    updateScrollIndicator();
});

window.addEventListener('scroll', rafThrottledScroll);

// Debounce with immediate option
function debounceImmediate(func, delay) {
    let timeoutId;
    
    return function(...args) {
        const callNow = !timeoutId;
        
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            timeoutId = null;
        }, delay);
        
        if (callNow) {
            func.apply(this, args);
        }
    };
}

// Execute immediately, then debounce
const immediateDebounce = debounceImmediate((e) => {
    console.log('Button clicked');
}, 1000);

button.addEventListener('click', immediateDebounce);

Example: Memoization and caching

// Simple memoization
function memoize(fn) {
    const cache = new Map();
    
    return function(...args) {
        const key = JSON.stringify(args);
        
        if (cache.has(key)) {
            return cache.get(key);
        }
        
        const result = fn.apply(this, args);
        cache.set(key, result);
        
        return result;
    };
}

// Expensive computation
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

const memoizedFib = memoize(fibonacci);

console.time('first');
console.log(memoizedFib(40));  // Slow first time
console.timeEnd('first');

console.time('second');
console.log(memoizedFib(40));  // Instant from cache
console.timeEnd('second');

// Memoization with TTL
function memoizeWithTTL(fn, ttl = 60000) {
    const cache = new Map();
    
    return function(...args) {
        const key = JSON.stringify(args);
        const cached = cache.get(key);
        
        if (cached && Date.now() - cached.timestamp < ttl) {
            return cached.value;
        }
        
        const result = fn.apply(this, args);
        cache.set(key, {
            value: result,
            timestamp: Date.now()
        });
        
        return result;
    };
}

const cachedFetch = memoizeWithTTL(async (url) => {
    const response = await fetch(url);
    return response.json();
}, 5000);  // 5 second cache

// LRU Cache for bounded memory
class LRUCache {
    constructor(maxSize = 100) {
        this.maxSize = maxSize;
        this.cache = new Map();
    }
    
    get(key) {
        if (!this.cache.has(key)) return undefined;
        
        const value = this.cache.get(key);
        // Move to end (most recent)
        this.cache.delete(key);
        this.cache.set(key, value);
        
        return value;
    }
    
    set(key, value) {
        // Delete if exists (to reinsert at end)
        if (this.cache.has(key)) {
            this.cache.delete(key);
        }
        
        // Evict oldest if at capacity
        if (this.cache.size >= this.maxSize) {
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }
        
        this.cache.set(key, value);
    }
}

// Memoize with LRU
function memoizeLRU(fn, maxSize = 100) {
    const cache = new LRUCache(maxSize);
    
    return function(...args) {
        const key = JSON.stringify(args);
        let result = cache.get(key);
        
        if (result === undefined) {
            result = fn.apply(this, args);
            cache.set(key, result);
        }
        
        return result;
    };
}

// Object pool for reusing objects
class ObjectPool {
    constructor(factory, reset, initialSize = 10) {
        this.factory = factory;
        this.reset = reset;
        this.available = [];
        
        for (let i = 0; i < initialSize; i++) {
            this.available.push(factory());
        }
    }
    
    acquire() {
        return this.available.length > 0
            ? this.available.pop()
            : this.factory();
    }
    
    release(obj) {
        this.reset(obj);
        this.available.push(obj);
    }
}

// Usage
const vectorPool = new ObjectPool(
    () => ({x: 0, y: 0, z: 0}),
    (v) => {
        v.x = 0;
        v.y = 0;
        v.z = 0;
    }
);

function processVectors() {
    const v1 = vectorPool.acquire();
    const v2 = vectorPool.acquire();
    
    // Use vectors
    v1.x = 10;
    v2.y = 20;
    
    // Release back to pool
    vectorPool.release(v1);
    vectorPool.release(v2);
}
Key Points: Avoid layout thrashing by batching DOM reads/writes. Use debounce for search, throttle for scroll/resize. requestAnimationFrame for smooth animations. Memoize expensive computations. Use LRU cache for bounded memory. Object pools reduce GC pressure. Profile before optimizing - measure, don't guess.

20.4 Profiling and Performance Monitoring

Performance API Methods

Method Purpose Returns
performance.now() High-resolution timestamp DOMHighResTimeStamp (microsecond precision)
performance.mark() Create named timestamp undefined
performance.measure() Measure between two marks Performance entry
performance.getEntries() Get all performance entries Array of PerformanceEntry
performance.getEntriesByType() Filter entries by type Array of PerformanceEntry
performance.getEntriesByName() Get entries by name Array of PerformanceEntry
performance.clearMarks() Clear marks undefined
performance.clearMeasures() Clear measures undefined

Performance Entry Types

Type Description Key Properties
navigation Page navigation timing domContentLoadedEventEnd, loadEventEnd
resource Resource loading (scripts, images) duration, transferSize, initiatorType
mark Custom timestamp markers startTime, name
measure Time between marks duration, startTime
paint Paint timing (FCP, FP) startTime
longtask Tasks > 50ms (Long Task API) duration, startTime

Core Web Vitals

Metric Measures Good Threshold
LCP (Largest Contentful Paint) Loading performance < 2.5 seconds
FID (First Input Delay) Interactivity < 100 milliseconds
CLS (Cumulative Layout Shift) Visual stability < 0.1
FCP (First Contentful Paint) Perceived load speed < 1.8 seconds
TTFB (Time to First Byte) Server response time < 600 milliseconds
TTI (Time to Interactive) When page becomes interactive < 3.8 seconds

Chrome DevTools Profiling

Tool Purpose Usage
Performance tab Record runtime performance Identify bottlenecks, long tasks
Memory tab Heap snapshots, allocation timeline Find memory leaks
Lighthouse Automated performance audit Get recommendations
Coverage tab Find unused JavaScript/CSS Reduce bundle size
Network tab Monitor requests and timing Optimize loading

Example: Performance measurement

// Basic timing with performance.now()
const start = performance.now();

// Some operation
for (let i = 0; i < 1000000; i++) {
    // work
}

const end = performance.now();
console.log(`Operation took ${end - start} milliseconds`);

// User Timing API with marks and measures
performance.mark('fetch-start');

fetch('/api/data')
    .then(response => response.json())
    .then(data => {
        performance.mark('fetch-end');
        
        // Measure between marks
        performance.measure('fetch-duration', 'fetch-start', 'fetch-end');
        
        const measure = performance.getEntriesByName('fetch-duration')[0];
        console.log(`Fetch took ${measure.duration}ms`);
    });

// Measure component render
function measureRender(component, props) {
    performance.mark(`${component}-render-start`);
    
    const result = renderComponent(component, props);
    
    performance.mark(`${component}-render-end`);
    performance.measure(
        `${component}-render`,
        `${component}-render-start`,
        `${component}-render-end`
    );
    
    return result;
}

// Get all measures
const measures = performance.getEntriesByType('measure');
measures.forEach(measure => {
    console.log(`${measure.name}: ${measure.duration.toFixed(2)}ms`);
});

// Clear marks and measures
performance.clearMarks();
performance.clearMeasures();

// Navigation timing
const navTiming = performance.getEntriesByType('navigation')[0];

console.log('Page Load Metrics:');
console.log('DNS lookup:', navTiming.domainLookupEnd - navTiming.domainLookupStart, 'ms');
console.log('TCP connection:', navTiming.connectEnd - navTiming.connectStart, 'ms');
console.log('Request time:', navTiming.responseStart - navTiming.requestStart, 'ms');
console.log('Response time:', navTiming.responseEnd - navTiming.responseStart, 'ms');
console.log('DOM processing:', navTiming.domComplete - navTiming.domLoading, 'ms');
console.log('Load event:', navTiming.loadEventEnd - navTiming.loadEventStart, 'ms');

// Resource timing
const resources = performance.getEntriesByType('resource');

resources.forEach(resource => {
    console.log(resource.name);
    console.log('  Duration:', resource.duration.toFixed(2), 'ms');
    console.log('  Transfer size:', resource.transferSize, 'bytes');
    console.log('  Type:', resource.initiatorType);
});

// Find slow resources
const slowResources = resources.filter(r => r.duration > 1000);
console.log('Resources > 1s:', slowResources.length);

Example: Performance monitoring class

// Comprehensive performance monitor
class PerformanceMonitor {
    constructor() {
        this.metrics = new Map();
        this.observers = [];
        
        this.initObservers();
    }
    
    initObservers() {
        // Observe long tasks (> 50ms)
        if ('PerformanceLongTaskTiming' in window) {
            const observer = new PerformanceObserver((list) => {
                for (const entry of list.getEntries()) {
                    console.warn('Long task detected:', entry.duration, 'ms');
                    this.recordMetric('long-task', entry.duration);
                }
            });
            
            observer.observe({entryTypes: ['longtask']});
            this.observers.push(observer);
        }
        
        // Observe layout shifts (CLS)
        if ('LayoutShift' in window) {
            const observer = new PerformanceObserver((list) => {
                for (const entry of list.getEntries()) {
                    if (!entry.hadRecentInput) {
                        this.recordMetric('layout-shift', entry.value);
                    }
                }
            });
            
            observer.observe({entryTypes: ['layout-shift']});
            this.observers.push(observer);
        }
        
        // Observe largest contentful paint (LCP)
        if ('LargestContentfulPaint' in window) {
            const observer = new PerformanceObserver((list) => {
                const entries = list.getEntries();
                const lastEntry = entries[entries.length - 1];
                
                this.recordMetric('lcp', lastEntry.startTime);
                console.log('LCP:', lastEntry.startTime, 'ms');
            });
            
            observer.observe({entryTypes: ['largest-contentful-paint']});
            this.observers.push(observer);
        }
        
        // Observe first input delay (FID)
        if ('PerformanceEventTiming' in window) {
            const observer = new PerformanceObserver((list) => {
                for (const entry of list.getEntries()) {
                    const fid = entry.processingStart - entry.startTime;
                    this.recordMetric('fid', fid);
                    console.log('FID:', fid, 'ms');
                }
            });
            
            observer.observe({entryTypes: ['first-input']});
            this.observers.push(observer);
        }
    }
    
    recordMetric(name, value) {
        if (!this.metrics.has(name)) {
            this.metrics.set(name, []);
        }
        
        this.metrics.get(name).push({
            value,
            timestamp: Date.now()
        });
    }
    
    measureFunction(name, fn) {
        const start = performance.now();
        
        try {
            const result = fn();
            const duration = performance.now() - start;
            
            this.recordMetric(name, duration);
            
            if (duration > 50) {
                console.warn(`${name} took ${duration.toFixed(2)}ms`);
            }
            
            return result;
        } catch (error) {
            const duration = performance.now() - start;
            this.recordMetric(name, duration);
            throw error;
        }
    }
    
    async measureAsync(name, fn) {
        const start = performance.now();
        
        try {
            const result = await fn();
            const duration = performance.now() - start;
            
            this.recordMetric(name, duration);
            
            return result;
        } catch (error) {
            const duration = performance.now() - start;
            this.recordMetric(name, duration);
            throw error;
        }
    }
    
    getReport() {
        const report = {};
        
        for (const [name, values] of this.metrics) {
            const durations = values.map(v => v.value);
            
            report[name] = {
                count: durations.length,
                min: Math.min(...durations),
                max: Math.max(...durations),
                avg: durations.reduce((a, b) => a + b, 0) / durations.length,
                p50: this.percentile(durations, 0.5),
                p95: this.percentile(durations, 0.95),
                p99: this.percentile(durations, 0.99)
            };
        }
        
        return report;
    }
    
    percentile(values, p) {
        const sorted = values.slice().sort((a, b) => a - b);
        const index = Math.ceil(sorted.length * p) - 1;
        return sorted[index];
    }
    
    sendToAnalytics() {
        const report = this.getReport();
        
        // Send to analytics service
        navigator.sendBeacon('/api/performance', JSON.stringify(report));
    }
    
    cleanup() {
        this.observers.forEach(observer => observer.disconnect());
        this.observers = [];
    }
}

// Usage
const monitor = new PerformanceMonitor();

// Measure function
monitor.measureFunction('processData', () => {
    // Some work
    return processData();
});

// Measure async function
await monitor.measureAsync('fetchData', async () => {
    return await fetch('/api/data').then(r => r.json());
});

// Get performance report
const report = monitor.getReport();
console.log('Performance Report:', report);

// Send to analytics on page unload
window.addEventListener('beforeunload', () => {
    monitor.sendToAnalytics();
});

Example: Core Web Vitals measurement

// Measure Core Web Vitals
class WebVitals {
    constructor() {
        this.vitals = {};
    }
    
    measureLCP() {
        if (!('LargestContentfulPaint' in window)) return;
        
        const observer = new PerformanceObserver((list) => {
            const entries = list.getEntries();
            const lastEntry = entries[entries.length - 1];
            
            this.vitals.lcp = lastEntry.startTime;
            
            // Report
            this.reportVital('LCP', lastEntry.startTime);
        });
        
        observer.observe({entryTypes: ['largest-contentful-paint']});
    }
    
    measureFID() {
        if (!('PerformanceEventTiming' in window)) return;
        
        const observer = new PerformanceObserver((list) => {
            for (const entry of list.getEntries()) {
                const fid = entry.processingStart - entry.startTime;
                this.vitals.fid = fid;
                
                this.reportVital('FID', fid);
            }
        });
        
        observer.observe({entryTypes: ['first-input']});
    }
    
    measureCLS() {
        if (!('LayoutShift' in window)) return;
        
        let clsScore = 0;
        
        const observer = new PerformanceObserver((list) => {
            for (const entry of list.getEntries()) {
                if (!entry.hadRecentInput) {
                    clsScore += entry.value;
                }
            }
            
            this.vitals.cls = clsScore;
        });
        
        observer.observe({entryTypes: ['layout-shift']});
        
        // Report on page hide
        document.addEventListener('visibilitychange', () => {
            if (document.visibilityState === 'hidden') {
                this.reportVital('CLS', clsScore);
            }
        });
    }
    
    measureFCP() {
        if (!('PerformancePaintTiming' in window)) return;
        
        const observer = new PerformanceObserver((list) => {
            for (const entry of list.getEntries()) {
                if (entry.name === 'first-contentful-paint') {
                    this.vitals.fcp = entry.startTime;
                    this.reportVital('FCP', entry.startTime);
                }
            }
        });
        
        observer.observe({entryTypes: ['paint']});
    }
    
    measureTTFB() {
        const navTiming = performance.getEntriesByType('navigation')[0];
        
        if (navTiming) {
            const ttfb = navTiming.responseStart - navTiming.requestStart;
            this.vitals.ttfb = ttfb;
            this.reportVital('TTFB', ttfb);
        }
    }
    
    reportVital(name, value) {
        console.log(`${name}:`, value.toFixed(2), 'ms');
        
        // Check against thresholds
        const thresholds = {
            LCP: {good: 2500, needsImprovement: 4000},
            FID: {good: 100, needsImprovement: 300},
            CLS: {good: 0.1, needsImprovement: 0.25},
            FCP: {good: 1800, needsImprovement: 3000},
            TTFB: {good: 600, needsImprovement: 1500}
        };
        
        const threshold = thresholds[name];
        if (threshold) {
            let rating;
            if (value <= threshold.good) {
                rating = 'good';
            } else if (value <= threshold.needsImprovement) {
                rating = 'needs improvement';
            } else {
                rating = 'poor';
            }
            
            console.log(`${name} rating: ${rating}`);
        }
        
        // Send to analytics
        this.sendToAnalytics(name, value);
    }
    
    sendToAnalytics(name, value) {
        // Send to analytics service
        if (navigator.sendBeacon) {
            navigator.sendBeacon('/api/vitals', JSON.stringify({
                metric: name,
                value: value,
                timestamp: Date.now(),
                url: window.location.href
            }));
        }
    }
    
    measureAll() {
        this.measureLCP();
        this.measureFID();
        this.measureCLS();
        this.measureFCP();
        this.measureTTFB();
    }
}

// Initialize
const webVitals = new WebVitals();
webVitals.measureAll();

// Alternative: Use web-vitals library
import {getLCP, getFID, getCLS, getFCP, getTTFB} from 'web-vitals';

getLCP(console.log);
getFID(console.log);
getCLS(console.log);
getFCP(console.log);
getTTFB(console.log);
Key Points: Use performance.now() for precise timing. performance.mark() and performance.measure() for custom metrics. Monitor Core Web Vitals: LCP, FID, CLS. Use PerformanceObserver to track long tasks, layout shifts. Send metrics to analytics with navigator.sendBeacon(). Profile with Chrome DevTools Performance tab.

20.5 Code Splitting and Lazy Loading

Code Splitting Strategies

Strategy When to Use Implementation
Route-based Different pages/routes Split by route, load on navigation
Component-based Large/heavy components Dynamic import, lazy load
Vendor splitting Third-party libraries Separate vendor bundle, cache long-term
Feature-based Optional features Load only when feature used
Conditional Environment-specific code Load based on conditions

Dynamic Import Syntax

Syntax Returns Usage
import() Promise<Module> Dynamic module loading
await import() Module object Top-level await or async function
import().then() Promise chain Handle module in .then()

Lazy Loading Techniques

Technique Target Trigger
Intersection Observer Images, components When entering viewport
On interaction Modal, tooltip, menu Click, hover, focus
On route change Page components Navigation event
Prefetching Likely-needed code Idle time, link hover
Loading attribute Images, iframes <img loading="lazy">

Bundle Optimization

Technique Purpose Tool
Tree shaking Remove unused exports Webpack, Rollup
Minification Reduce code size Terser, UglifyJS
Compression Reduce transfer size Gzip, Brotli
Scope hoisting Flatten module scope Webpack ModuleConcatenationPlugin
Bundle analysis Visualize bundle size webpack-bundle-analyzer

Example: Dynamic imports

// Basic dynamic import
button.addEventListener('click', async () => {
    const module = await import('./heavyModule.js');
    module.doSomething();
});

// Import specific exports
const {default: Component} = await import('./Component.js');
const {helper1, helper2} = await import('./helpers.js');

// Conditional import
async function loadAnalytics() {
    if (user.hasConsent) {
        const {init} = await import('./analytics.js');
        init();
    }
}

// Route-based code splitting
const routes = {
    '/home': () => import('./pages/Home.js'),
    '/about': () => import('./pages/About.js'),
    '/contact': () => import('./pages/Contact.js')
};

async function navigate(path) {
    const loader = routes[path];
    if (loader) {
        const module = await loader();
        const Page = module.default;
        renderPage(Page);
    }
}

// Component lazy loading with React
import React, {lazy, Suspense} from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
    return (
        <Suspense fallback={<div>Loading...</div>}>
            <HeavyComponent />
        </Suspense>
    );
}

// Lazy load on interaction
class LazyButton {
    constructor(element) {
        this.element = element;
        this.loaded = false;
        
        // Load on first click
        element.addEventListener('click', this.onClick.bind(this), {once: true});
    }
    
    async onClick(event) {
        if (!this.loaded) {
            const {handler} = await import('./buttonHandler.js');
            this.loaded = true;
            
            // Call the handler
            handler(event);
            
            // Future clicks go directly to handler
            this.element.addEventListener('click', handler);
        }
    }
}

// Prefetch on hover
link.addEventListener('mouseenter', async () => {
    // Prefetch when user hovers (likely to click)
    await import('./nextPage.js');
}, {once: true});

// Webpack magic comments for chunking
const LazyComponent = lazy(() =>
    import(
        /* webpackChunkName: "heavy-component" */
        /* webpackPrefetch: true */
        './HeavyComponent'
    )
);

Example: Intersection Observer for lazy loading

// Lazy load images
class LazyImageLoader {
    constructor(options = {}) {
        this.rootMargin = options.rootMargin || '50px';
        this.threshold = options.threshold || 0.01;
        
        this.observer = new IntersectionObserver(
            this.onIntersection.bind(this),
            {
                rootMargin: this.rootMargin,
                threshold: this.threshold
            }
        );
    }
    
    observe(element) {
        this.observer.observe(element);
    }
    
    onIntersection(entries) {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                this.loadImage(entry.target);
                this.observer.unobserve(entry.target);
            }
        });
    }
    
    loadImage(img) {
        const src = img.dataset.src;
        const srcset = img.dataset.srcset;
        
        if (src) {
            img.src = src;
        }
        
        if (srcset) {
            img.srcset = srcset;
        }
        
        img.classList.add('loaded');
    }
}

// Usage
const loader = new LazyImageLoader();
const images = document.querySelectorAll('img[data-src]');
images.forEach(img => loader.observe(img));

// HTML:
// <img data-src="image.jpg" alt="Lazy loaded image">

// Lazy load components
class LazyComponentLoader {
    constructor() {
        this.observer = new IntersectionObserver(
            this.onIntersection.bind(this),
            {rootMargin: '100px'}
        );
        
        this.components = new Map();
    }
    
    register(element, loader) {
        this.components.set(element, loader);
        this.observer.observe(element);
    }
    
    async onIntersection(entries) {
        for (const entry of entries) {
            if (entry.isIntersecting) {
                const element = entry.target;
                const loader = this.components.get(element);
                
                if (loader) {
                    await this.loadComponent(element, loader);
                    this.observer.unobserve(element);
                    this.components.delete(element);
                }
            }
        }
    }
    
    async loadComponent(element, loader) {
        try {
            element.classList.add('loading');
            
            const module = await loader();
            const Component = module.default;
            
            // Render component
            const instance = new Component(element);
            instance.render();
            
            element.classList.remove('loading');
            element.classList.add('loaded');
        } catch (error) {
            console.error('Failed to load component:', error);
            element.classList.add('error');
        }
    }
}

// Usage
const componentLoader = new LazyComponentLoader();

const heavySection = document.getElementById('heavy-section');
componentLoader.register(
    heavySection,
    () => import('./HeavySection.js')
);

// Native lazy loading for images
// <img src="image.jpg" loading="lazy" alt="...">

// Polyfill for browsers without native support
if ('loading' in HTMLImageElement.prototype) {
    // Native lazy loading supported
    const images = document.querySelectorAll('img[loading="lazy"]');
    // Nothing to do
} else {
    // Fallback to Intersection Observer
    const images = document.querySelectorAll('img[loading="lazy"]');
    const loader = new LazyImageLoader();
    images.forEach(img => {
        img.dataset.src = img.src;
        img.removeAttribute('src');
        loader.observe(img);
    });
}

Example: Module loading strategies

// Module loader with caching
class ModuleLoader {
    constructor() {
        this.cache = new Map();
        this.loading = new Map();
    }
    
    async load(path) {
        // Return cached module
        if (this.cache.has(path)) {
            return this.cache.get(path);
        }
        
        // Return existing promise if already loading
        if (this.loading.has(path)) {
            return this.loading.get(path);
        }
        
        // Start loading
        const promise = import(path)
            .then(module => {
                this.cache.set(path, module);
                this.loading.delete(path);
                return module;
            })
            .catch(error => {
                this.loading.delete(path);
                throw error;
            });
        
        this.loading.set(path, promise);
        return promise;
    }
    
    preload(paths) {
        paths.forEach(path => {
            if (!this.cache.has(path) && !this.loading.has(path)) {
                this.load(path).catch(() => {
                    // Ignore preload errors
                });
            }
        });
    }
    
    clear(path) {
        this.cache.delete(path);
        this.loading.delete(path);
    }
}

const moduleLoader = new ModuleLoader();

// Load module
const module = await moduleLoader.load('./module.js');

// Preload modules
moduleLoader.preload([
    './page1.js',
    './page2.js',
    './page3.js'
]);

// Priority-based loading
class PriorityLoader {
    constructor() {
        this.queues = {
            high: [],
            medium: [],
            low: []
        };
        
        this.processing = false;
    }
    
    load(path, priority = 'medium') {
        return new Promise((resolve, reject) => {
            this.queues[priority].push({
                path,
                resolve,
                reject
            });
            
            this.process();
        });
    }
    
    async process() {
        if (this.processing) return;
        
        this.processing = true;
        
        while (this.hasWork()) {
            const item = this.getNext();
            
            if (item) {
                try {
                    const module = await import(item.path);
                    item.resolve(module);
                } catch (error) {
                    item.reject(error);
                }
            }
            
            // Yield to browser
            await new Promise(resolve => setTimeout(resolve, 0));
        }
        
        this.processing = false;
    }
    
    hasWork() {
        return this.queues.high.length > 0 ||
               this.queues.medium.length > 0 ||
               this.queues.low.length > 0;
    }
    
    getNext() {
        if (this.queues.high.length > 0) {
            return this.queues.high.shift();
        }
        if (this.queues.medium.length > 0) {
            return this.queues.medium.shift();
        }
        if (this.queues.low.length > 0) {
            return this.queues.low.shift();
        }
        return null;
    }
}

const priorityLoader = new PriorityLoader();

// Load critical module with high priority
await priorityLoader.load('./critical.js', 'high');

// Load nice-to-have module with low priority
priorityLoader.load('./analytics.js', 'low');
Key Points: Use import() for dynamic code splitting. Split by route, component, or feature. Use IntersectionObserver for lazy loading images/components. Prefetch likely-needed modules on hover/idle. Native loading="lazy" for images. Tree shaking removes unused code. Analyze bundles with webpack-bundle-analyzer. Cache loaded modules to avoid re-loading.

20.6 Optimization for V8 Engine

V8 Optimization Concepts

Concept Description Impact
Hidden Classes Object shape/structure Property access performance
Inline Caching Cache property lookup locations Faster repeated access
JIT Compilation Compile hot code to machine code Execution speed
Optimization Tiers Ignition (interpreter), TurboFan (optimizer) Progressive optimization
Deoptimization Fall back to interpreter on type change Performance penalty

Optimization-Friendly Patterns

Pattern Why It's Fast Example
Monomorphic functions Same types always, optimizer-friendly Always pass same object shape
Fixed object structure Stable hidden class Initialize all properties in constructor
Array of same type Optimized element access All numbers or all objects
Small functions Can be inlined Single responsibility
Avoid delete Maintains hidden class Set to null/undefined instead
Predictable types Reduces deoptimization Consistent return types

Common Deoptimization Triggers

Trigger Why It's Slow Solution
Dynamic property addition Changes hidden class Initialize all properties upfront
delete operator Breaks hidden class optimization Set to null/undefined
Polymorphic functions Multiple code paths Use monomorphic versions
Sparse arrays Dictionary mode storage Use dense arrays
Mixed array types Generic element storage Homogeneous arrays
try-catch in hot path Prevents optimization Isolate try-catch to helper function

Example: Hidden classes and object shape

// BAD: Dynamic property addition (different hidden classes)
function createPoint1() {
    const point = {};
    point.x = 10;  // Hidden class transition 1
    point.y = 20;  // Hidden class transition 2
    return point;
}

const p1 = createPoint1();
const p2 = createPoint1();
p2.z = 30;  // p2 now has different hidden class than p1

// GOOD: Initialize all properties (same hidden class)
function createPoint2() {
    return {
        x: 10,  // All properties at once
        y: 20,
        z: 0
    };
}

const p3 = createPoint2();
const p4 = createPoint2();
// p3 and p4 share the same hidden class

// GOOD: Constructor with fixed structure
class Point {
    constructor(x, y) {
        this.x = x;  // Always same order
        this.y = y;  // Always same properties
    }
}

// BAD: Inconsistent object structure
function createUser(name, age, email) {
    const user = {name, age};
    
    // Sometimes add email, sometimes don't
    if (email) {
        user.email = email;  // Different hidden class!
    }
    
    return user;
}

// GOOD: Always same structure
function createUser2(name, age, email = null) {
    return {
        name,
        age,
        email  // Always present, even if null
    };
}

// BAD: Delete property (kills optimization)
const obj = {a: 1, b: 2, c: 3};
delete obj.b;  // BAD: Changes hidden class

// GOOD: Set to undefined
const obj2 = {a: 1, b: 2, c: 3};
obj2.b = undefined;  // GOOD: Maintains hidden class

// Monomorphic vs Polymorphic functions
// BAD: Polymorphic (accepts different shapes)
function processData(data) {
    if (Array.isArray(data)) {
        return data.map(x => x * 2);
    } else if (typeof data === 'object') {
        return Object.values(data).map(x => x * 2);
    }
    return data * 2;
}

// GOOD: Monomorphic (always same type)
function processArray(arr) {
    return arr.map(x => x * 2);
}

function processObject(obj) {
    return Object.values(obj).map(x => x * 2);
}

function processNumber(num) {
    return num * 2;
}

Example: Array optimization

// BAD: Mixed types (generic array storage)
const mixed = [1, 'two', {three: 3}, null];
// V8 can't optimize - uses generic storage

// GOOD: Homogeneous types (optimized storage)
const numbers = [1, 2, 3, 4, 5];
const strings = ['a', 'b', 'c'];
const objects = [{id: 1}, {id: 2}, {id: 3}];

// BAD: Sparse array (dictionary mode)
const sparse = [];
sparse[0] = 'a';
sparse[1000] = 'b';  // Creates sparse array
// V8 switches to hash table storage

// GOOD: Dense array
const dense = new Array(1001).fill(null);
dense[0] = 'a';
dense[1000] = 'b';

// BAD: Holey array (slower access)
const holey = [1, 2, 3];
holey[10] = 11;  // Creates holes at 3-9

// GOOD: Pre-size and fill
const filled = Array(11).fill(0);
filled[0] = 1;
filled[1] = 2;
filled[2] = 3;
filled[10] = 11;

// Array access patterns
// GOOD: Sequential access
function sumSequential(arr) {
    let sum = 0;
    for (let i = 0; i < arr.length; i++) {
        sum += arr[i];
    }
    return sum;
}

// GOOD: forEach (also optimized)
function sumForEach(arr) {
    let sum = 0;
    arr.forEach(x => {
        sum += x;
    });
    return sum;
}

// Array element kinds (fastest to slowest)
// PACKED_SMI_ELEMENTS (small integers)
const smi = [1, 2, 3, 4, 5];

// PACKED_DOUBLE_ELEMENTS (doubles)
const doubles = [1.1, 2.2, 3.3, 4.4, 5.5];

// PACKED_ELEMENTS (objects)
const objects2 = [{}, {}, {}];

// HOLEY_SMI_ELEMENTS (holes + small integers)
const holeSmi = [1, 2, , 4, 5];

// DICTIONARY_ELEMENTS (slowest)
const dict = [];
dict[100000] = 1;

Example: Function optimization

// BAD: Try-catch in hot path
function hotFunction(x) {
    try {
        return x * 2;
    } catch (e) {
        return 0;
    }
}

// GOOD: Isolate try-catch
function hotFunction2(x) {
    return x * 2;  // Can be optimized
}

function safeHotFunction(x) {
    try {
        return hotFunction2(x);
    } catch (e) {
        return 0;
    }
}

// BAD: Large function (won't inline)
function largeFunction(a, b, c, d, e) {
    // 100+ lines of code
    // ...many operations
    return result;
}

// GOOD: Small functions (can inline)
function add(a, b) {
    return a + b;
}

function multiply(a, b) {
    return a * b;
}

function compute(a, b) {
    return multiply(add(a, b), 2);  // Inlined
}

// BAD: Type-changing return
function inconsistentReturn(flag) {
    if (flag) {
        return 42;  // Number
    }
    return 'error';  // String - causes deopt
}

// GOOD: Consistent return type
function consistentReturn(flag) {
    if (flag) {
        return {success: true, value: 42};
    }
    return {success: false, error: 'Failed'};
}

// Function inlining budget
// Small functions (< 600 bytes bytecode) can be inlined

// BAD: Megamorphic call site (4+ different types)
function process(obj) {
    return obj.getValue();  // If called with 4+ different shapes
}

// Call with many different object shapes
process(new ClassA());
process(new ClassB());
process(new ClassC());
process(new ClassD());
process(new ClassE());  // Goes megamorphic!

// GOOD: Monomorphic (1 type) or Polymorphic (2-3 types)
function processA(obj) {
    return obj.getValue();  // Only ClassA
}

function processB(obj) {
    return obj.getValue();  // Only ClassB
}

// Object allocation optimization
// GOOD: Allocate in function (can escape analysis)
function createLocal() {
    const obj = {x: 1, y: 2};
    return obj.x + obj.y;  // obj doesn't escape
}

// Escape analysis can eliminate allocation

// GOOD: Predictable control flow
function predictable(x) {
    if (x > 0) {
        return x * 2;
    }
    return 0;
}

// BAD: Complex control flow
function unpredictable(x) {
    switch (x % 7) {
        case 0: return x * 1;
        case 1: return x * 2;
        case 2: return x * 3;
        // ... many cases
    }
}
Key Points: V8 uses hidden classes for object shape. Initialize all properties at once. Avoid delete, use = undefined. Keep arrays homogeneous (same type). Avoid sparse/holey arrays. Monomorphic functions (same types) optimize best. Small functions can be inlined. Isolate try-catch from hot paths. Consistent return types prevent deoptimization.

Section 20 Summary: Memory Management and Performance

  • Garbage Collection: Automatic mark-and-sweep, generational (young/old), V8 Orinoco
  • Reachability: Objects GC'd when unreachable from roots (global, stack, closures)
  • Memory Leaks: Globals, timers, listeners, closures, detached DOM, unbounded caches
  • Prevention: Cleanup lifecycle, WeakMap/WeakSet, remove listeners, clear timers
  • Detection: Chrome DevTools Memory, heap snapshots, performance.memory monitoring
  • Performance: Minimize DOM access, batch reads/writes, debounce/throttle
  • Profiling: performance.mark/measure, PerformanceObserver, Core Web Vitals (LCP, FID, CLS)
  • Code Splitting: Dynamic import(), route/component-based, lazy loading with IntersectionObserver
  • V8 Optimization: Hidden classes, monomorphic functions, homogeneous arrays, avoid delete
  • Best Practices: Profile first, optimize bottlenecks, memoization, object pools, tree shaking

21. Web APIs and Browser Integration

21.1 Fetch API and HTTP Request Handling

Fetch API Basics

Method Syntax Returns
fetch() fetch(url, options?) Promise<Response>
response.json() response.json() Promise<any>
response.text() response.text() Promise<string>
response.blob() response.blob() Promise<Blob>
response.arrayBuffer() response.arrayBuffer() Promise<ArrayBuffer>
response.formData() response.formData() Promise<FormData>

Response Properties

Property Type Description
response.ok boolean True if status 200-299
response.status number HTTP status code (200, 404, etc.)
response.statusText string Status message ("OK", "Not Found")
response.headers Headers Response headers
response.url string Final URL (after redirects)
response.redirected boolean True if response redirected
response.type string "basic", "cors", "opaque", etc.

Request Options

Option Type Description
method string "GET", "POST", "PUT", "DELETE", etc.
headers object Request headers
body string|FormData|Blob Request body (not for GET/HEAD)
mode string "cors", "no-cors", "same-origin"
credentials string "omit", "same-origin", "include"
cache string "default", "no-cache", "reload", etc.
signal AbortSignal For request cancellation

HTTP Status Code Categories

Range Category Common Codes
100-199 Informational 100 Continue, 101 Switching Protocols
200-299 Success 200 OK, 201 Created, 204 No Content
300-399 Redirection 301 Moved, 302 Found, 304 Not Modified
400-499 Client Error 400 Bad Request, 401 Unauthorized, 404 Not Found
500-599 Server Error 500 Internal Error, 503 Service Unavailable

Example: Basic fetch requests

// GET request
fetch('https://api.example.com/users')
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.json();
    })
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.error('Fetch error:', error);
    });

// Async/await version
async function getUsers() {
    try {
        const response = await fetch('https://api.example.com/users');
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Error fetching users:', error);
        throw error;
    }
}

// POST request with JSON
async function createUser(userData) {
    const response = await fetch('https://api.example.com/users', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer token123'
        },
        body: JSON.stringify(userData)
    });
    
    if (!response.ok) {
        const error = await response.json();
        throw new Error(error.message);
    }
    
    return response.json();
}

// Usage
await createUser({
    name: 'John Doe',
    email: 'john@example.com'
});

// PUT request
async function updateUser(id, updates) {
    const response = await fetch(`https://api.example.com/users/${id}`, {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(updates)
    });
    
    return response.json();
}

// DELETE request
async function deleteUser(id) {
    const response = await fetch(`https://api.example.com/users/${id}`, {
        method: 'DELETE'
    });
    
    if (response.status === 204) {
        return {success: true};
    }
    
    return response.json();
}

// Upload file with FormData
async function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    formData.append('description', 'My file');
    
    const response = await fetch('https://api.example.com/upload', {
        method: 'POST',
        body: formData  // Don't set Content-Type header - browser sets it
    });
    
    return response.json();
}

// Request with timeout
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);
    
    try {
        const response = await fetch(url, {
            ...options,
            signal: controller.signal
        });
        
        clearTimeout(timeoutId);
        return response;
    } catch (error) {
        clearTimeout(timeoutId);
        
        if (error.name === 'AbortError') {
            throw new Error('Request timeout');
        }
        
        throw error;
    }
}

Example: Advanced fetch patterns

// Retry logic
async function fetchWithRetry(url, options = {}, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await fetch(url, options);
            
            if (response.ok) {
                return response;
            }
            
            // Don't retry client errors (4xx)
            if (response.status >= 400 && response.status < 500) {
                throw new Error(`Client error: ${response.status}`);
            }
            
            // Retry on server errors (5xx)
            if (i === retries - 1) {
                throw new Error(`Max retries reached. Status: ${response.status}`);
            }
            
            // Wait before retry (exponential backoff)
            await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
        } catch (error) {
            if (i === retries - 1) {
                throw error;
            }
            
            // Wait before retry
            await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
        }
    }
}

// Parallel requests
async function fetchMultiple(urls) {
    const promises = urls.map(url => fetch(url).then(r => r.json()));
    return Promise.all(promises);
}

// Usage
const [users, posts, comments] = await fetchMultiple([
    'https://api.example.com/users',
    'https://api.example.com/posts',
    'https://api.example.com/comments'
]);

// Sequential requests (when one depends on another)
async function fetchUserAndPosts(userId) {
    const user = await fetch(`/api/users/${userId}`).then(r => r.json());
    const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
    
    return {user, posts};
}

// Progress tracking for upload
async function uploadWithProgress(file, onProgress) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        
        xhr.upload.addEventListener('progress', (e) => {
            if (e.lengthComputable) {
                const percentComplete = (e.loaded / e.total) * 100;
                onProgress(percentComplete);
            }
        });
        
        xhr.addEventListener('load', () => {
            if (xhr.status === 200) {
                resolve(JSON.parse(xhr.responseText));
            } else {
                reject(new Error(`Upload failed: ${xhr.status}`));
            }
        });
        
        xhr.addEventListener('error', () => reject(new Error('Upload error')));
        
        const formData = new FormData();
        formData.append('file', file);
        
        xhr.open('POST', '/api/upload');
        xhr.send(formData);
    });
}

// Usage
await uploadWithProgress(file, (percent) => {
    console.log(`Upload progress: ${percent.toFixed(2)}%`);
});

// API client class
class ApiClient {
    constructor(baseURL, defaultHeaders = {}) {
        this.baseURL = baseURL;
        this.defaultHeaders = defaultHeaders;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseURL}${endpoint}`;
        
        const config = {
            ...options,
            headers: {
                ...this.defaultHeaders,
                ...options.headers
            }
        };
        
        const response = await fetch(url, config);
        
        if (!response.ok) {
            const error = await response.json().catch(() => ({}));
            throw new Error(error.message || `HTTP error! status: ${response.status}`);
        }
        
        // Handle no-content responses
        if (response.status === 204) {
            return null;
        }
        
        return response.json();
    }
    
    get(endpoint, options) {
        return this.request(endpoint, {...options, method: 'GET'});
    }
    
    post(endpoint, data, options) {
        return this.request(endpoint, {
            ...options,
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
        });
    }
    
    put(endpoint, data, options) {
        return this.request(endpoint, {
            ...options,
            method: 'PUT',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
        });
    }
    
    delete(endpoint, options) {
        return this.request(endpoint, {...options, method: 'DELETE'});
    }
}

// Usage
const api = new ApiClient('https://api.example.com', {
    'Authorization': 'Bearer token123'
});

const users = await api.get('/users');
const newUser = await api.post('/users', {name: 'John'});
await api.delete(`/users/${newUser.id}`);
Key Points: fetch() returns Promise<Response>. Check response.ok for success (status 200-299). Use response.json() to parse JSON. AbortController for request cancellation. FormData for file uploads. Set headers for authentication and content type. Handle errors with try-catch. Always check response.ok before parsing body.

21.2 DOM Manipulation and Element Selection

Element Selection Methods

Method Returns Description
document.getElementById() Element | null Select by ID
document.querySelector() Element | null First match of CSS selector
document.querySelectorAll() NodeList All matches of CSS selector
document.getElementsByClassName() HTMLCollection (live) Elements by class name
document.getElementsByTagName() HTMLCollection (live) Elements by tag name
element.closest() Element | null Nearest ancestor matching selector
element.matches() boolean Check if element matches selector

Element Creation and Modification

Method Purpose Usage
document.createElement() Create element createElement('div')
document.createTextNode() Create text node createTextNode('text')
element.cloneNode() Clone element cloneNode(true) for deep clone
element.appendChild() Add child at end parent.appendChild(child)
element.insertBefore() Insert before reference parent.insertBefore(new, ref)
element.removeChild() Remove child parent.removeChild(child)
element.remove() Remove self element.remove()
element.replaceChild() Replace child parent.replaceChild(new, old)

Modern DOM Insertion Methods

Method Position Accepts
element.append() Inside, at end Nodes or strings
element.prepend() Inside, at start Nodes or strings
element.before() Before element (sibling) Nodes or strings
element.after() After element (sibling) Nodes or strings
element.replaceWith() Replace element Nodes or strings

Element Properties and Attributes

Property/Method Purpose Example
element.innerHTML Get/set HTML content div.innerHTML = '<p>text</p>'
element.textContent Get/set text (no HTML parsing) div.textContent = 'text'
element.innerText Get/set rendered text div.innerText = 'text'
element.getAttribute() Get attribute value img.getAttribute('src')
element.setAttribute() Set attribute img.setAttribute('src', 'url')
element.removeAttribute() Remove attribute div.removeAttribute('class')
element.hasAttribute() Check if attribute exists div.hasAttribute('data-id')
element.dataset Access data-* attributes div.dataset.userId

CSS Class Manipulation

Method Purpose Example
element.classList.add() Add class(es) div.classList.add('active', 'highlighted')
element.classList.remove() Remove class(es) div.classList.remove('active')
element.classList.toggle() Toggle class div.classList.toggle('hidden')
element.classList.contains() Check if has class div.classList.contains('active')
element.classList.replace() Replace class div.classList.replace('old', 'new')
element.className Get/set all classes (string) div.className = 'class1 class2'

Example: DOM selection and manipulation

// Element selection
const header = document.getElementById('header');
const firstButton = document.querySelector('.btn');
const allButtons = document.querySelectorAll('.btn');
const items = document.getElementsByClassName('item');

// Modern selection with optional chaining
const content = document.querySelector('.container')?.querySelector('.content');

// Creating elements
const div = document.createElement('div');
div.className = 'card';
div.id = 'user-card';
div.textContent = 'User Card';

// Setting attributes
div.setAttribute('data-user-id', '123');
div.setAttribute('role', 'article');

// Using dataset for data attributes
div.dataset.userId = '123';  // Creates data-user-id
div.dataset.userName = 'John';  // Creates data-user-name

console.log(div.dataset.userId);  // '123'

// Creating complex structures
const card = document.createElement('div');
card.className = 'card';

const title = document.createElement('h3');
title.textContent = 'Card Title';

const content2 = document.createElement('p');
content2.textContent = 'Card content...';

const button = document.createElement('button');
button.textContent = 'Click Me';
button.className = 'btn btn-primary';

card.append(title, content2, button);
document.body.appendChild(card);

// Inserting elements
const container = document.querySelector('.container');

// Insert at end
container.append('Some text', div);

// Insert at start
container.prepend('First content');

// Insert before/after
const reference = document.querySelector('.reference');
reference.before(document.createElement('div'));
reference.after(document.createElement('div'));

// Replace element
const oldElement = document.querySelector('.old');
const newElement = document.createElement('div');
newElement.textContent = 'New element';
oldElement.replaceWith(newElement);

// Removing elements
const element = document.querySelector('.to-remove');
element.remove();  // Modern way

// Or
element.parentNode.removeChild(element);  // Old way

// Clone elements
const original = document.querySelector('.original');
const clone = original.cloneNode(true);  // Deep clone
document.body.appendChild(clone);

// Class manipulation
const box = document.querySelector('.box');

box.classList.add('active');
box.classList.remove('inactive');
box.classList.toggle('visible');

if (box.classList.contains('active')) {
    console.log('Box is active');
}

// Toggle with force parameter
box.classList.toggle('active', true);  // Always add
box.classList.toggle('active', false);  // Always remove

// Replace class
box.classList.replace('old-class', 'new-class');

// Working with innerHTML vs textContent
const container2 = document.querySelector('.container');

// innerHTML - parses HTML
container2.innerHTML = '<strong>Bold text</strong>';

// textContent - treats as plain text (safer, faster)
container2.textContent = '<strong>Not parsed</strong>';

// Finding elements relative to another
const parent = document.querySelector('.parent');
const child = parent.querySelector('.child');
const ancestor = child.closest('.ancestor');
const sibling = child.nextElementSibling;
const prevSibling = child.previousElementSibling;

// Check if element matches selector
if (element.matches('.active')) {
    console.log('Element is active');
}

Example: Efficient DOM manipulation

// BAD: Multiple reflows
for (let i = 0; i < 100; i++) {
    const div = document.createElement('div');
    div.textContent = `Item ${i}`;
    document.body.appendChild(div);  // Causes reflow each time
}

// GOOD: Use DocumentFragment
const fragment = document.createDocumentFragment();

for (let i = 0; i < 100; i++) {
    const div = document.createElement('div');
    div.textContent = `Item ${i}`;
    fragment.appendChild(div);
}

document.body.appendChild(fragment);  // Single reflow

// GOOD: Build HTML string and insert once
const items = Array.from({length: 100}, (_, i) => `
    <div class="item">Item ${i}</div>
`).join('');

document.querySelector('.container').innerHTML = items;

// Helper function to create elements with properties
function createElement(tag, props = {}, children = []) {
    const element = document.createElement(tag);
    
    // Set properties
    Object.entries(props).forEach(([key, value]) => {
        if (key === 'className') {
            element.className = value;
        } else if (key === 'dataset') {
            Object.assign(element.dataset, value);
        } else if (key === 'style') {
            Object.assign(element.style, value);
        } else if (key.startsWith('on')) {
            const event = key.slice(2).toLowerCase();
            element.addEventListener(event, value);
        } else {
            element.setAttribute(key, value);
        }
    });
    
    // Add children
    children.forEach(child => {
        if (typeof child === 'string') {
            element.appendChild(document.createTextNode(child));
        } else {
            element.appendChild(child);
        }
    });
    
    return element;
}

// Usage
const button = createElement('button', {
    className: 'btn btn-primary',
    dataset: {userId: '123'},
    onclick: () => console.log('Clicked'),
    type: 'button'
}, ['Click Me']);

// Batch DOM reads and writes
// BAD: Interleaving reads and writes
elements.forEach(el => {
    const height = el.offsetHeight;  // Read
    el.style.height = height + 10 + 'px';  // Write
});

// GOOD: Batch all reads, then all writes
const heights = elements.map(el => el.offsetHeight);  // All reads
elements.forEach((el, i) => {
    el.style.height = heights[i] + 10 + 'px';  // All writes
});

// Template element for reusable DOM structures
const template = document.createElement('template');
template.innerHTML = `
    <div class="card">
        <h3 class="card-title"></h3>
        <p class="card-body"></p>
        <button class="card-btn">Action</button>
    </div>
`;

function createCard(title, body) {
    const clone = template.content.cloneNode(true);
    clone.querySelector('.card-title').textContent = title;
    clone.querySelector('.card-body').textContent = body;
    return clone;
}

document.body.appendChild(createCard('Title', 'Body text'));

// Observing DOM changes
const observer = new MutationObserver((mutations) => {
    mutations.forEach(mutation => {
        console.log('Type:', mutation.type);
        console.log('Added:', mutation.addedNodes);
        console.log('Removed:', mutation.removedNodes);
    });
});

observer.observe(document.body, {
    childList: true,
    subtree: true,
    attributes: true,
    attributeOldValue: true
});

// Stop observing
observer.disconnect();
Key Points: Use querySelector() for flexible selection with CSS selectors. querySelectorAll() returns static NodeList. classList for class manipulation. textContent is safer than innerHTML. Use DocumentFragment for batch inserts. Modern methods: append(), prepend(), before(), after(), remove(). Access data attributes via element.dataset.

21.3 Event System and Event Delegation

Event Registration Methods

Method Syntax Notes
addEventListener() element.addEventListener(type, handler, options) Recommended, multiple handlers allowed
removeEventListener() element.removeEventListener(type, handler, options) Remove specific handler
element.onclick element.onclick = handler Only one handler, overwrites previous
HTML attribute <button onclick="handler()"> Not recommended

Event Listener Options

Option Type Description
capture boolean Use capture phase (default: false)
once boolean Remove after first invocation
passive boolean Won't call preventDefault() (improves scroll performance)
signal AbortSignal Remove listener when signal aborted

Common Event Types

Category Events When Fired
Mouse click, dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave Mouse interactions
Keyboard keydown, keyup, keypress (deprecated) Keyboard input
Form submit, change, input, focus, blur, invalid Form interactions
Focus focus, blur, focusin, focusout Element focus changes
Touch touchstart, touchmove, touchend, touchcancel Touch device interactions
Drag dragstart, drag, dragend, dragover, drop Drag and drop
Document DOMContentLoaded, load, beforeunload, unload Document lifecycle
Clipboard copy, cut, paste Clipboard operations

Event Object Properties

Property Type Description
event.type string Event type ("click", "keydown", etc.)
event.target Element Element that triggered event
event.currentTarget Element Element with listener attached
event.bubbles boolean Whether event bubbles
event.cancelable boolean Whether can be cancelled
event.defaultPrevented boolean Whether preventDefault called
event.timeStamp number Time event created (ms)
event.isTrusted boolean True if user-generated

Event Control Methods

Method Purpose Effect
event.preventDefault() Cancel default action Prevents default browser behavior
event.stopPropagation() Stop bubbling Prevents event from propagating
event.stopImmediatePropagation() Stop all propagation Stops propagation and remaining handlers

Example: Event handling basics

// Basic event listener
const button = document.querySelector('.btn');

button.addEventListener('click', (event) => {
    console.log('Button clicked!');
    console.log('Target:', event.target);
    console.log('Current target:', event.currentTarget);
});

// Event listener with options
button.addEventListener('click', handler, {
    once: true,      // Remove after first call
    capture: false,  // Bubbling phase
    passive: true    // Won't call preventDefault
});

// Remove event listener
function handleClick(event) {
    console.log('Clicked');
}

button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick);

// Using AbortController to remove listeners
const controller = new AbortController();

button.addEventListener('click', () => {
    console.log('Clicked');
}, {signal: controller.signal});

// Remove listener
controller.abort();

// Multiple elements
const buttons = document.querySelectorAll('.btn');

buttons.forEach(button => {
    button.addEventListener('click', (e) => {
        console.log('Button clicked:', e.target.textContent);
    });
});

// Prevent default behavior
const link = document.querySelector('a');

link.addEventListener('click', (event) => {
    event.preventDefault();  // Don't navigate
    console.log('Link clicked but not followed');
});

// Form submission
const form = document.querySelector('form');

form.addEventListener('submit', (event) => {
    event.preventDefault();  // Don't submit to server
    
    const formData = new FormData(form);
    const data = Object.fromEntries(formData);
    
    console.log('Form data:', data);
    
    // Handle form submission with fetch
    fetch('/api/submit', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(data)
    });
});

// Keyboard events
const input = document.querySelector('input');

input.addEventListener('keydown', (event) => {
    console.log('Key:', event.key);
    console.log('Code:', event.code);
    console.log('Ctrl:', event.ctrlKey);
    console.log('Shift:', event.shiftKey);
    console.log('Alt:', event.altKey);
    
    // Check for specific key
    if (event.key === 'Enter') {
        console.log('Enter pressed');
    }
    
    // Check for key combination
    if (event.ctrlKey && event.key === 's') {
        event.preventDefault();  // Prevent browser save
        console.log('Ctrl+S pressed');
    }
});

// Input event (fires on every change)
input.addEventListener('input', (event) => {
    console.log('Current value:', event.target.value);
});

// Change event (fires when input loses focus)
input.addEventListener('change', (event) => {
    console.log('Final value:', event.target.value);
});

// Mouse events
const box = document.querySelector('.box');

box.addEventListener('mouseenter', () => {
    console.log('Mouse entered');
});

box.addEventListener('mouseleave', () => {
    console.log('Mouse left');
});

box.addEventListener('mousemove', (event) => {
    console.log('Mouse position:', event.clientX, event.clientY);
});

// Touch events
box.addEventListener('touchstart', (event) => {
    console.log('Touch started');
    console.log('Touches:', event.touches.length);
});

box.addEventListener('touchmove', (event) => {
    event.preventDefault();  // Prevent scrolling
    const touch = event.touches[0];
    console.log('Touch position:', touch.clientX, touch.clientY);
}, {passive: false});  // Must be false to call preventDefault

// Document ready
document.addEventListener('DOMContentLoaded', () => {
    console.log('DOM fully loaded');
});

// Page load (including images, styles)
window.addEventListener('load', () => {
    console.log('Page fully loaded');
});

// Before page unload
window.addEventListener('beforeunload', (event) => {
    event.preventDefault();
    event.returnValue = '';  // Show confirmation dialog
});

Example: Event delegation

// Event delegation - handle events on parent
const list = document.querySelector('.list');

// BAD: Add listener to each item (inefficient for many items)
document.querySelectorAll('.list-item').forEach(item => {
    item.addEventListener('click', handleClick);
});

// GOOD: Single listener on parent (event delegation)
list.addEventListener('click', (event) => {
    // Find closest list item
    const item = event.target.closest('.list-item');
    
    if (item) {
        console.log('Item clicked:', item.textContent);
        item.classList.toggle('selected');
    }
});

// More complex delegation
list.addEventListener('click', (event) => {
    const target = event.target;
    
    // Handle delete button
    if (target.matches('.delete-btn')) {
        const item = target.closest('.list-item');
        item.remove();
    }
    
    // Handle edit button
    if (target.matches('.edit-btn')) {
        const item = target.closest('.list-item');
        editItem(item);
    }
    
    // Handle item click (but not buttons)
    if (target.matches('.list-item') && !target.closest('button')) {
        toggleItem(target);
    }
});

// Delegation helper function
function delegate(parent, selector, eventType, handler) {
    parent.addEventListener(eventType, (event) => {
        const target = event.target.closest(selector);
        
        if (target && parent.contains(target)) {
            handler.call(target, event);
        }
    });
}

// Usage
delegate(list, '.list-item', 'click', function(event) {
    console.log('Item clicked:', this.textContent);
});

// Dynamic content with delegation
function addItem(text) {
    const item = document.createElement('div');
    item.className = 'list-item';
    item.innerHTML = `
        <span>${text}</span>
        <button class="delete-btn">Delete</button>
    `;
    list.appendChild(item);
    // No need to add new event listeners!
}

// Event bubbling example
document.querySelector('.grandparent').addEventListener('click', () => {
    console.log('Grandparent clicked');
});

document.querySelector('.parent').addEventListener('click', () => {
    console.log('Parent clicked');
});

document.querySelector('.child').addEventListener('click', (event) => {
    console.log('Child clicked');
    
    // Stop propagation to parent
    // event.stopPropagation();
});

// Clicking child logs: "Child clicked", "Parent clicked", "Grandparent clicked"

// Event capture phase
document.querySelector('.grandparent').addEventListener('click', () => {
    console.log('Grandparent (capture)');
}, true);  // Capture phase

document.querySelector('.parent').addEventListener('click', () => {
    console.log('Parent (capture)');
}, true);

document.querySelector('.child').addEventListener('click', () => {
    console.log('Child (bubble)');
});

// Order: "Grandparent (capture)", "Parent (capture)", "Child (bubble)"

// Custom event dispatcher
class EventBus {
    constructor() {
        this.events = {};
    }
    
    on(event, handler) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(handler);
    }
    
    off(event, handler) {
        if (!this.events[event]) return;
        
        this.events[event] = this.events[event].filter(h => h !== handler);
    }
    
    emit(event, data) {
        if (!this.events[event]) return;
        
        this.events[event].forEach(handler => {
            handler(data);
        });
    }
}

// Usage
const bus = new EventBus();

bus.on('user:login', (user) => {
    console.log('User logged in:', user);
});

bus.emit('user:login', {name: 'John', id: 123});

// Custom DOM events
const customEvent = new CustomEvent('myevent', {
    detail: {message: 'Hello'},
    bubbles: true,
    cancelable: true
});

element.addEventListener('myevent', (event) => {
    console.log('Custom event:', event.detail);
});

element.dispatchEvent(customEvent);
Key Points: Use addEventListener() for flexibility. Event delegation: attach listener to parent, check event.target. Use event.preventDefault() to cancel default action. event.stopPropagation() stops bubbling. once: true for one-time listeners. AbortController for cleanup. passive: true for scroll performance. Event phases: capture → target → bubble.

21.4 Storage APIs (localStorage, sessionStorage, IndexedDB)

Web Storage API (localStorage & sessionStorage)

Method Purpose Returns
setItem(key, value) Store key-value pair undefined
getItem(key) Retrieve value string | null
removeItem(key) Remove item undefined
clear() Remove all items undefined
key(index) Get key at index string | null
length Number of items number

localStorage vs sessionStorage

Feature localStorage sessionStorage
Persistence Until manually cleared Until tab/window closed
Scope Shared across tabs Per tab/window
Storage limit ~5-10 MB ~5-10 MB
Data type Strings only Strings only
Use case Long-term data, preferences Session data, temporary state

IndexedDB Key Concepts

Concept Description Example
Database Container for object stores indexedDB.open('myDB', 1)
Object Store Like a table in SQL db.createObjectStore('users')
Transaction Atomic operation wrapper db.transaction(['users'], 'readwrite')
Index Query by non-key property store.createIndex('email', 'email')
Key Unique identifier autoIncrement or keyPath
Cursor Iterate over records store.openCursor()

IndexedDB Transaction Modes

Mode Access When to Use
readonly Read only Queries, retrieving data
readwrite Read and write Creating, updating, deleting
versionchange Schema changes onupgradeneeded event

Example: localStorage and sessionStorage

// localStorage - persists across sessions
// Store data
localStorage.setItem('username', 'john_doe');
localStorage.setItem('theme', 'dark');

// Retrieve data
const username = localStorage.getItem('username');  // 'john_doe'
const theme = localStorage.getItem('theme');        // 'dark'

// Store objects (must stringify)
const user = {
    id: 123,
    name: 'John Doe',
    email: 'john@example.com'
};

localStorage.setItem('user', JSON.stringify(user));

// Retrieve and parse
const savedUser = JSON.parse(localStorage.getItem('user'));
console.log(savedUser.name);  // 'John Doe'

// Remove item
localStorage.removeItem('theme');

// Clear all
localStorage.clear();

// Check if key exists
if (localStorage.getItem('username')) {
    console.log('Username exists');
}

// Iterate over all items
for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    const value = localStorage.getItem(key);
    console.log(key, value);
}

// sessionStorage - cleared when tab closes
sessionStorage.setItem('tempData', 'temporary');

// Storage helper class
class Storage {
    constructor(storage = localStorage) {
        this.storage = storage;
    }
    
    set(key, value) {
        try {
            this.storage.setItem(key, JSON.stringify(value));
            return true;
        } catch (error) {
            console.error('Storage error:', error);
            return false;
        }
    }
    
    get(key, defaultValue = null) {
        try {
            const item = this.storage.getItem(key);
            return item ? JSON.parse(item) : defaultValue;
        } catch (error) {
            console.error('Parse error:', error);
            return defaultValue;
        }
    }
    
    remove(key) {
        this.storage.removeItem(key);
    }
    
    clear() {
        this.storage.clear();
    }
    
    has(key) {
        return this.storage.getItem(key) !== null;
    }
    
    keys() {
        return Object.keys(this.storage);
    }
}

// Usage
const store = new Storage();

store.set('user', {name: 'John', age: 30});
const user2 = store.get('user');
console.log(user2.name);  // 'John'

// Listen for storage changes (in other tabs)
window.addEventListener('storage', (event) => {
    console.log('Storage changed:');
    console.log('Key:', event.key);
    console.log('Old value:', event.oldValue);
    console.log('New value:', event.newValue);
    console.log('URL:', event.url);
});

// Storage with expiration
class StorageWithExpiry {
    set(key, value, ttl) {
        const item = {
            value: value,
            expiry: Date.now() + ttl
        };
        localStorage.setItem(key, JSON.stringify(item));
    }
    
    get(key) {
        const itemStr = localStorage.getItem(key);
        
        if (!itemStr) {
            return null;
        }
        
        const item = JSON.parse(itemStr);
        
        if (Date.now() > item.expiry) {
            localStorage.removeItem(key);
            return null;
        }
        
        return item.value;
    }
}

// Usage
const storage = new StorageWithExpiry();

// Store for 1 hour
storage.set('session', {token: 'abc123'}, 60 * 60 * 1000);

// Later...
const session = storage.get('session');  // null if expired

// Handle quota exceeded errors
try {
    localStorage.setItem('large-data', 'x'.repeat(10 * 1024 * 1024));
} catch (error) {
    if (error.name === 'QuotaExceededError') {
        console.error('Storage quota exceeded');
        // Clear old data or notify user
    }
}

Example: IndexedDB basic operations

// Open database
function openDatabase() {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open('myDatabase', 1);
        
        // Create/upgrade schema
        request.onupgradeneeded = (event) => {
            const db = event.target.result;
            
            // Create object store
            if (!db.objectStoreNames.contains('users')) {
                const store = db.createObjectStore('users', {
                    keyPath: 'id',
                    autoIncrement: true
                });
                
                // Create indexes
                store.createIndex('email', 'email', {unique: true});
                store.createIndex('name', 'name', {unique: false});
            }
        };
        
        request.onsuccess = (event) => {
            resolve(event.target.result);
        };
        
        request.onerror = (event) => {
            reject(event.target.error);
        };
    });
}

// Add data
async function addUser(user) {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readwrite');
        const store = transaction.objectStore('users');
        const request = store.add(user);
        
        request.onsuccess = () => {
            resolve(request.result);  // Returns generated key
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Get data by key
async function getUser(id) {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readonly');
        const store = transaction.objectStore('users');
        const request = store.get(id);
        
        request.onsuccess = () => {
            resolve(request.result);
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Get data by index
async function getUserByEmail(email) {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readonly');
        const store = transaction.objectStore('users');
        const index = store.index('email');
        const request = index.get(email);
        
        request.onsuccess = () => {
            resolve(request.result);
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Get all data
async function getAllUsers() {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readonly');
        const store = transaction.objectStore('users');
        const request = store.getAll();
        
        request.onsuccess = () => {
            resolve(request.result);
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Update data
async function updateUser(user) {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readwrite');
        const store = transaction.objectStore('users');
        const request = store.put(user);  // Updates or inserts
        
        request.onsuccess = () => {
            resolve(request.result);
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Delete data
async function deleteUser(id) {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readwrite');
        const store = transaction.objectStore('users');
        const request = store.delete(id);
        
        request.onsuccess = () => {
            resolve();
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// Usage
await addUser({name: 'John Doe', email: 'john@example.com'});
const user3 = await getUser(1);
const userByEmail = await getUserByEmail('john@example.com');
await updateUser({id: 1, name: 'John Smith', email: 'john@example.com'});
await deleteUser(1);

// Cursor iteration
async function iterateUsers() {
    const db = await openDatabase();
    
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(['users'], 'readonly');
        const store = transaction.objectStore('users');
        const request = store.openCursor();
        
        const results = [];
        
        request.onsuccess = (event) => {
            const cursor = event.target.result;
            
            if (cursor) {
                results.push(cursor.value);
                cursor.continue();  // Move to next
            } else {
                resolve(results);  // Done
            }
        };
        
        request.onerror = () => {
            reject(request.error);
        };
    });
}

// IndexedDB wrapper class
class IDBWrapper {
    constructor(dbName, version = 1) {
        this.dbName = dbName;
        this.version = version;
        this.db = null;
    }
    
    async open(upgrade) {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(this.dbName, this.version);
            
            request.onupgradeneeded = (event) => {
                if (upgrade) {
                    upgrade(event.target.result);
                }
            };
            
            request.onsuccess = (event) => {
                this.db = event.target.result;
                resolve(this.db);
            };
            
            request.onerror = () => reject(request.error);
        });
    }
    
    async add(storeName, data) {
        const transaction = this.db.transaction([storeName], 'readwrite');
        const store = transaction.objectStore(storeName);
        
        return new Promise((resolve, reject) => {
            const request = store.add(data);
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
    
    async get(storeName, key) {
        const transaction = this.db.transaction([storeName], 'readonly');
        const store = transaction.objectStore(storeName);
        
        return new Promise((resolve, reject) => {
            const request = store.get(key);
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
    
    async getAll(storeName) {
        const transaction = this.db.transaction([storeName], 'readonly');
        const store = transaction.objectStore(storeName);
        
        return new Promise((resolve, reject) => {
            const request = store.getAll();
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
    
    async delete(storeName, key) {
        const transaction = this.db.transaction([storeName], 'readwrite');
        const store = transaction.objectStore(storeName);
        
        return new Promise((resolve, reject) => {
            const request = store.delete(key);
            request.onsuccess = () => resolve();
            request.onerror = () => reject(request.error);
        });
    }
}

// Usage
const db2 = new IDBWrapper('myApp', 1);

await db2.open((db) => {
    db.createObjectStore('users', {keyPath: 'id', autoIncrement: true});
});

await db2.add('users', {name: 'John'});
const users = await db2.getAll('users');
Key Points: localStorage persists, sessionStorage clears on tab close. Both store strings only - use JSON.stringify/parse for objects. IndexedDB for large/complex data (NoSQL database). Use transactions for IndexedDB operations. Create indexes for efficient queries. Handle QuotaExceededError. storage event for cross-tab communication.

21.5 Clipboard API and File API

Clipboard API Methods

Method Purpose Returns
navigator.clipboard.writeText() Copy text to clipboard Promise<void>
navigator.clipboard.readText() Read text from clipboard Promise<string>
navigator.clipboard.write() Copy data (images, HTML) to clipboard Promise<void>
navigator.clipboard.read() Read data from clipboard Promise<ClipboardItem[]>

File Input Properties

Property Type Description
input.files FileList Selected files
input.accept string File type filter (e.g., "image/*")
input.multiple boolean Allow multiple file selection

File Object Properties

Property Type Description
file.name string File name
file.size number Size in bytes
file.type string MIME type (e.g., "image/png")
file.lastModified number Last modified timestamp

FileReader Methods

Method Result Format Use Case
readAsText() String Text files
readAsDataURL() Data URL (base64) Display images, download links
readAsArrayBuffer() ArrayBuffer Binary data, manipulation
readAsBinaryString() Binary string (deprecated) Legacy support

Drag and Drop Events

Event Fires On Usage
dragstart Start dragging Set drag data
drag During drag Track drag position
dragenter Enter drop zone Visual feedback
dragover Over drop zone Allow drop (preventDefault)
dragleave Leave drop zone Remove feedback
drop Dropped Handle dropped data
dragend Drag finished Cleanup

Example: Clipboard API

// Copy text to clipboard
async function copyToClipboard(text) {
    try {
        await navigator.clipboard.writeText(text);
        console.log('Copied to clipboard');
    } catch (error) {
        console.error('Failed to copy:', error);
    }
}

// Usage
await copyToClipboard('Hello, World!');

// Read text from clipboard
async function readFromClipboard() {
    try {
        const text = await navigator.clipboard.readText();
        console.log('Clipboard text:', text);
        return text;
    } catch (error) {
        console.error('Failed to read:', error);
    }
}

// Copy button
document.querySelector('.copy-btn').addEventListener('click', async () => {
    const text = document.querySelector('.code').textContent;
    await copyToClipboard(text);
});

// Copy image to clipboard
async function copyImageToClipboard(blob) {
    try {
        await navigator.clipboard.write([
            new ClipboardItem({
                [blob.type]: blob
            })
        ]);
        console.log('Image copied');
    } catch (error) {
        console.error('Failed to copy image:', error);
    }
}

// Fetch and copy image
async function copyImageFromUrl(url) {
    const response = await fetch(url);
    const blob = await response.blob();
    await copyImageToClipboard(blob);
}

// Fallback for older browsers
function fallbackCopy(text) {
    const textarea = document.createElement('textarea');
    textarea.value = text;
    textarea.style.position = 'fixed';
    textarea.style.opacity = '0';
    document.body.appendChild(textarea);
    textarea.select();
    
    try {
        const successful = document.execCommand('copy');
        console.log('Copy:', successful ? 'success' : 'failed');
    } catch (error) {
        console.error('Fallback copy failed:', error);
    }
    
    document.body.removeChild(textarea);
}

// Copy with fallback
async function copyText(text) {
    if (navigator.clipboard) {
        await navigator.clipboard.writeText(text);
    } else {
        fallbackCopy(text);
    }
}

// Listen for copy event
document.addEventListener('copy', (event) => {
    // Customize copied content
    const selection = document.getSelection().toString();
    const customText = `${selection}\n\nSource: ${window.location.href}`;
    
    event.clipboardData.setData('text/plain', customText);
    event.preventDefault();
});

// Listen for paste event
document.addEventListener('paste', (event) => {
    event.preventDefault();
    
    const text = event.clipboardData.getData('text/plain');
    console.log('Pasted:', text);
    
    // Insert at cursor or handle paste
    document.execCommand('insertText', false, text);
});

Example: File API - reading files

// HTML: <input type="file" id="fileInput" multiple accept="image/*">

const fileInput = document.getElementById('fileInput');

fileInput.addEventListener('change', (event) => {
    const files = event.target.files;
    
    for (const file of files) {
        console.log('Name:', file.name);
        console.log('Size:', file.size, 'bytes');
        console.log('Type:', file.type);
        console.log('Last modified:', new Date(file.lastModified));
    }
});

// Read text file
fileInput.addEventListener('change', (event) => {
    const file = event.target.files[0];
    
    if (file) {
        const reader = new FileReader();
        
        reader.onload = (e) => {
            const text = e.target.result;
            console.log('File content:', text);
        };
        
        reader.onerror = (e) => {
            console.error('Error reading file:', e);
        };
        
        reader.readAsText(file);
    }
});

// Read image and display
fileInput.addEventListener('change', (event) => {
    const file = event.target.files[0];
    
    if (file && file.type.startsWith('image/')) {
        const reader = new FileReader();
        
        reader.onload = (e) => {
            const img = document.createElement('img');
            img.src = e.target.result;  // Data URL
            document.body.appendChild(img);
        };
        
        reader.readAsDataURL(file);
    }
});

// Read as ArrayBuffer for binary processing
fileInput.addEventListener('change', async (event) => {
    const file = event.target.files[0];
    
    if (file) {
        const arrayBuffer = await file.arrayBuffer();
        const bytes = new Uint8Array(arrayBuffer);
        
        console.log('First 10 bytes:', bytes.slice(0, 10));
    }
});

// Progress tracking
function readFileWithProgress(file) {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        
        reader.onprogress = (event) => {
            if (event.lengthComputable) {
                const percent = (event.loaded / event.total) * 100;
                console.log(`Progress: ${percent.toFixed(2)}%`);
            }
        };
        
        reader.onload = (event) => {
            resolve(event.target.result);
        };
        
        reader.onerror = (event) => {
            reject(reader.error);
        };
        
        reader.readAsArrayBuffer(file);
    });
}

// Multiple files
fileInput.addEventListener('change', async (event) => {
    const files = Array.from(event.target.files);
    
    const promises = files.map(file => {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = (e) => resolve({file, data: e.target.result});
            reader.onerror = reject;
            reader.readAsDataURL(file);
        });
    });
    
    const results = await Promise.all(promises);
    results.forEach(({file, data}) => {
        console.log(`${file.name}: ${data.length} characters`);
    });
});

// File validation
function validateFile(file) {
    const maxSize = 5 * 1024 * 1024;  // 5 MB
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    
    if (file.size > maxSize) {
        throw new Error('File too large (max 5MB)');
    }
    
    if (!allowedTypes.includes(file.type)) {
        throw new Error('Invalid file type');
    }
    
    return true;
}

fileInput.addEventListener('change', (event) => {
    const file = event.target.files[0];
    
    try {
        validateFile(file);
        // Process file
    } catch (error) {
        alert(error.message);
        fileInput.value = '';  // Clear input
    }
});

Example: Drag and drop file upload

// HTML: <div id="dropZone">Drop files here</div>

const dropZone = document.getElementById('dropZone');

// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
    dropZone.addEventListener(eventName, preventDefaults, false);
    document.body.addEventListener(eventName, preventDefaults, false);
});

function preventDefaults(e) {
    e.preventDefault();
    e.stopPropagation();
}

// Visual feedback
['dragenter', 'dragover'].forEach(eventName => {
    dropZone.addEventListener(eventName, () => {
        dropZone.classList.add('highlight');
    }, false);
});

['dragleave', 'drop'].forEach(eventName => {
    dropZone.addEventListener(eventName, () => {
        dropZone.classList.remove('highlight');
    }, false);
});

// Handle drop
dropZone.addEventListener('drop', handleDrop, false);

function handleDrop(e) {
    const dt = e.dataTransfer;
    const files = dt.files;
    
    handleFiles(files);
}

function handleFiles(files) {
    [...files].forEach(uploadFile);
}

function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    
    fetch('/upload', {
        method: 'POST',
        body: formData
    })
    .then(response => response.json())
    .then(data => {
        console.log('Upload success:', data);
    })
    .catch(error => {
        console.error('Upload error:', error);
    });
}

// Preview dropped images
function handleDrop2(e) {
    const dt = e.dataTransfer;
    const files = dt.files;
    
    [...files].forEach(file => {
        if (file.type.startsWith('image/')) {
            const reader = new FileReader();
            
            reader.onload = (e) => {
                const img = document.createElement('img');
                img.src = e.target.result;
                img.style.maxWidth = '200px';
                dropZone.appendChild(img);
            };
            
            reader.readAsDataURL(file);
        }
    });
}

// Drag from element to another
const draggableItem = document.querySelector('.draggable');

draggableItem.addEventListener('dragstart', (e) => {
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/html', e.target.innerHTML);
    e.dataTransfer.setData('text/plain', e.target.textContent);
});

const dropTarget = document.querySelector('.drop-target');

dropTarget.addEventListener('dragover', (e) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
});

dropTarget.addEventListener('drop', (e) => {
    e.preventDefault();
    
    const html = e.dataTransfer.getData('text/html');
    dropTarget.innerHTML = html;
});

// File System Access API (modern browsers)
async function openFilePicker() {
    try {
        const [fileHandle] = await window.showOpenFilePicker({
            types: [{
                description: 'Text Files',
                accept: {'text/plain': ['.txt']}
            }],
            multiple: false
        });
        
        const file = await fileHandle.getFile();
        const text = await file.text();
        
        console.log('File content:', text);
    } catch (error) {
        console.error('Error:', error);
    }
}

// Save file
async function saveFile(content) {
    try {
        const fileHandle = await window.showSaveFilePicker({
            suggestedName: 'document.txt',
            types: [{
                description: 'Text Files',
                accept: {'text/plain': ['.txt']}
            }]
        });
        
        const writable = await fileHandle.createWritable();
        await writable.write(content);
        await writable.close();
        
        console.log('File saved');
    } catch (error) {
        console.error('Error:', error);
    }
}
Key Points: navigator.clipboard.writeText() for copying text (requires HTTPS). Use FileReader to read file contents (text, dataURL, arrayBuffer). File validation (size, type) before upload. Drag and drop: preventDefault on dragover and drop. FormData for file uploads. file.arrayBuffer() for modern async file reading. File System Access API for advanced file operations.

21.6 Geolocation and Device APIs

Geolocation API Methods

Method Purpose Callback Parameters
getCurrentPosition() Get current location once success, error, options
watchPosition() Track location changes success, error, options
clearWatch() Stop watching position watchId

Position Object Properties

Property Type Description
coords.latitude number Latitude in decimal degrees
coords.longitude number Longitude in decimal degrees
coords.accuracy number Accuracy in meters
coords.altitude number | null Altitude in meters
coords.altitudeAccuracy number | null Altitude accuracy in meters
coords.heading number | null Direction in degrees (0=North)
coords.speed number | null Speed in meters per second
timestamp number Timestamp of position

Geolocation Options

Option Type Description
enableHighAccuracy boolean Request best possible results (uses GPS)
timeout number Maximum time to wait (ms)
maximumAge number Max age of cached position (ms)

Device Orientation Properties

Property Range Description
alpha 0-360 Z-axis rotation (compass heading)
beta -180 to 180 X-axis rotation (front-to-back tilt)
gamma -90 to 90 Y-axis rotation (left-to-right tilt)
absolute boolean True if relative to Earth's coordinate frame

Other Device APIs

API Purpose Access Via
Battery Status Battery level and charging status navigator.getBattery()
Network Information Connection type and speed navigator.connection
Vibration Trigger device vibration navigator.vibrate()
Screen Orientation Lock/unlock screen orientation screen.orientation
Media Devices Access camera/microphone navigator.mediaDevices

Example: Geolocation API

// Get current position
if ('geolocation' in navigator) {
    navigator.geolocation.getCurrentPosition(
        (position) => {
            console.log('Latitude:', position.coords.latitude);
            console.log('Longitude:', position.coords.longitude);
            console.log('Accuracy:', position.coords.accuracy, 'meters');
            console.log('Timestamp:', new Date(position.timestamp));
        },
        (error) => {
            console.error('Error:', error.code, error.message);
            
            switch(error.code) {
                case error.PERMISSION_DENIED:
                    console.log('User denied geolocation');
                    break;
                case error.POSITION_UNAVAILABLE:
                    console.log('Location unavailable');
                    break;
                case error.TIMEOUT:
                    console.log('Request timeout');
                    break;
            }
        },
        {
            enableHighAccuracy: true,
            timeout: 5000,
            maximumAge: 0
        }
    );
} else {
    console.log('Geolocation not supported');
}

// Async/await wrapper
function getPosition(options) {
    return new Promise((resolve, reject) => {
        navigator.geolocation.getCurrentPosition(resolve, reject, options);
    });
}

// Usage
try {
    const position = await getPosition({
        enableHighAccuracy: true,
        timeout: 10000
    });
    
    const {latitude, longitude} = position.coords;
    console.log(`Location: ${latitude}, ${longitude}`);
} catch (error) {
    console.error('Geolocation error:', error);
}

// Watch position (continuous tracking)
const watchId = navigator.geolocation.watchPosition(
    (position) => {
        const {latitude, longitude, speed, heading} = position.coords;
        
        console.log(`Lat: ${latitude}, Lng: ${longitude}`);
        
        if (speed !== null) {
            console.log(`Speed: ${speed} m/s`);
        }
        
        if (heading !== null) {
            console.log(`Heading: ${heading}°`);
        }
        
        // Update map or UI
        updateMap(latitude, longitude);
    },
    (error) => {
        console.error('Watch error:', error);
    },
    {
        enableHighAccuracy: true,
        maximumAge: 30000,
        timeout: 27000
    }
);

// Stop watching
navigator.geolocation.clearWatch(watchId);

// Calculate distance between two points (Haversine formula)
function calculateDistance(lat1, lon1, lat2, lon2) {
    const R = 6371e3; // Earth's radius in meters
    const φ1 = lat1 * Math.PI / 180;
    const φ2 = lat2 * Math.PI / 180;
    const Δφ = (lat2 - lat1) * Math.PI / 180;
    const Δλ = (lon2 - lon1) * Math.PI / 180;
    
    const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
              Math.cos(φ1) * Math.cos(φ2) *
              Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    
    return R * c; // Distance in meters
}

// Usage
const distance = calculateDistance(51.5074, -0.1278, 48.8566, 2.3522);
console.log(`Distance: ${(distance / 1000).toFixed(2)} km`);

// Geolocation with React hook
function useGeolocation(options) {
    const [position, setPosition] = useState(null);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        const watchId = navigator.geolocation.watchPosition(
            setPosition,
            setError,
            options
        );
        
        return () => navigator.geolocation.clearWatch(watchId);
    }, []);
    
    return {position, error};
}

// Usage in component
function LocationTracker() {
    const {position, error} = useGeolocation({enableHighAccuracy: true});
    
    if (error) return <div>Error: {error.message}</div>;
    if (!position) return <div>Loading...</div>;
    
    return (
        <div>
            <p>Lat: {position.coords.latitude}</p>
            <p>Lng: {position.coords.longitude}</p>
        </div>
    );
}

Example: Device orientation and other APIs

// Device orientation (gyroscope)
window.addEventListener('deviceorientation', (event) => {
    const alpha = event.alpha;  // Z-axis (0-360)
    const beta = event.beta;    // X-axis (-180 to 180)
    const gamma = event.gamma;  // Y-axis (-90 to 90)
    
    console.log(`α: ${alpha}°, β: ${beta}°, γ: ${gamma}°`);
    
    // Use for compass, level, or 3D effects
    updateCompass(alpha);
});

// Device motion (accelerometer)
window.addEventListener('devicemotion', (event) => {
    const acceleration = event.acceleration;
    const rotationRate = event.rotationRate;
    
    console.log('Acceleration:');
    console.log('  x:', acceleration.x);
    console.log('  y:', acceleration.y);
    console.log('  z:', acceleration.z);
    
    console.log('Rotation rate:');
    console.log('  α:', rotationRate.alpha);
    console.log('  β:', rotationRate.beta);
    console.log('  γ:', rotationRate.gamma);
    
    // Detect shake
    const totalAcceleration = Math.abs(acceleration.x) +
                             Math.abs(acceleration.y) +
                             Math.abs(acceleration.z);
    
    if (totalAcceleration > 30) {
        console.log('Device shaken!');
    }
});

// Request permission for iOS 13+
async function requestMotionPermission() {
    if (typeof DeviceMotionEvent.requestPermission === 'function') {
        const permission = await DeviceMotionEvent.requestPermission();
        
        if (permission === 'granted') {
            window.addEventListener('devicemotion', handleMotion);
        }
    }
}

// Battery API
navigator.getBattery().then((battery) => {
    console.log('Battery level:', battery.level * 100, '%');
    console.log('Charging:', battery.charging);
    console.log('Charging time:', battery.chargingTime, 'seconds');
    console.log('Discharging time:', battery.dischargingTime, 'seconds');
    
    // Listen for changes
    battery.addEventListener('levelchange', () => {
        console.log('Battery level:', battery.level * 100, '%');
    });
    
    battery.addEventListener('chargingchange', () => {
        console.log('Charging:', battery.charging);
    });
});

// Network Information API
if ('connection' in navigator) {
    const connection = navigator.connection;
    
    console.log('Connection type:', connection.effectiveType);
    console.log('Downlink speed:', connection.downlink, 'Mb/s');
    console.log('RTT:', connection.rtt, 'ms');
    console.log('Save data:', connection.saveData);
    
    // Adjust quality based on connection
    if (connection.effectiveType === '4g') {
        loadHighQualityImages();
    } else {
        loadLowQualityImages();
    }
    
    // Listen for connection changes
    connection.addEventListener('change', () => {
        console.log('Connection changed to:', connection.effectiveType);
    });
}

// Vibration API
if ('vibrate' in navigator) {
    // Vibrate for 200ms
    navigator.vibrate(200);
    
    // Pattern: vibrate, pause, vibrate
    navigator.vibrate([200, 100, 200]);
    
    // Stop vibration
    navigator.vibrate(0);
    
    // Feedback on button click
    button.addEventListener('click', () => {
        navigator.vibrate(50);
    });
}

// Screen Orientation API
if ('orientation' in screen) {
    console.log('Current orientation:', screen.orientation.type);
    console.log('Angle:', screen.orientation.angle);
    
    // Lock orientation
    screen.orientation.lock('landscape').then(() => {
        console.log('Orientation locked');
    }).catch((error) => {
        console.error('Lock failed:', error);
    });
    
    // Unlock orientation
    screen.orientation.unlock();
    
    // Listen for orientation changes
    screen.orientation.addEventListener('change', () => {
        console.log('New orientation:', screen.orientation.type);
    });
}

// Media Devices (Camera/Microphone)
async function getUserMedia() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({
            video: {
                width: {ideal: 1920},
                height: {ideal: 1080}
            },
            audio: true
        });
        
        // Display video
        const video = document.querySelector('video');
        video.srcObject = stream;
        
        return stream;
    } catch (error) {
        console.error('Media access denied:', error);
    }
}

// List available devices
async function listDevices() {
    const devices = await navigator.mediaDevices.enumerateDevices();
    
    devices.forEach(device => {
        console.log(device.kind, ':', device.label);
    });
}

// Online/offline detection
window.addEventListener('online', () => {
    console.log('Back online');
    syncOfflineData();
});

window.addEventListener('offline', () => {
    console.log('Gone offline');
    showOfflineMessage();
});

// Check current status
if (navigator.onLine) {
    console.log('Currently online');
} else {
    console.log('Currently offline');
}

// Page Visibility API
document.addEventListener('visibilitychange', () => {
    if (document.hidden) {
        console.log('Page hidden');
        pauseVideo();
    } else {
        console.log('Page visible');
        resumeVideo();
    }
});
Key Points: Geolocation API requires user permission. Use enableHighAccuracy for GPS. watchPosition() for continuous tracking. Device orientation for gyroscope data. Battery API for power management. Network Information to adapt to connection quality. Vibration API for haptic feedback. Always check for API support before use.

21.7 Web Workers and Service Workers

Web Worker Types

Type Scope Use Case
Dedicated Worker Single script Heavy computation, background tasks
Shared Worker Multiple scripts/tabs Cross-tab communication
Service Worker Network proxy Offline support, caching, push notifications

Worker Methods and Events

Method/Event Purpose Context
postMessage() Send message Main thread ↔ Worker
onmessage Receive message Main thread ↔ Worker
terminate() Stop worker Main thread
close() Stop self Worker
onerror Handle errors Main thread
importScripts() Load external scripts Worker

Service Worker Lifecycle

State Description Event
Installing First time registration install
Installed (Waiting) Installed but not active -
Activating Taking control activate
Activated Controlling pages -
Redundant Replaced by newer version -

Service Worker Events

Event When Fired Use Case
install First installation Cache resources
activate Becomes active Clean old caches
fetch Network request Cache strategy, offline fallback
message Receive message from page Communication
push Push notification received Show notification
sync Background sync triggered Sync data when online

Cache Strategies

Strategy Description Best For
Cache First Cache, fallback to network Static assets (CSS, JS, images)
Network First Network, fallback to cache API calls, dynamic content
Cache Only Always from cache Offline-first apps
Network Only Always from network Real-time data
Stale While Revalidate Cache first, update in background Balance freshness and speed

Example: Web Workers

// Main thread (main.js)
const worker = new Worker('worker.js');

// Send message to worker
worker.postMessage({
    type: 'calculate',
    data: [1, 2, 3, 4, 5]
});

// Receive message from worker
worker.onmessage = (event) => {
    console.log('Result from worker:', event.data);
};

// Handle errors
worker.onerror = (error) => {
    console.error('Worker error:', error.message);
};

// Terminate worker
worker.terminate();

// Worker file (worker.js)
self.onmessage = (event) => {
    const {type, data} = event.data;
    
    if (type === 'calculate') {
        // Heavy computation
        const result = data.reduce((sum, num) => sum + num, 0);
        
        // Send result back
        self.postMessage({
            type: 'result',
            result: result
        });
    }
};

// Import external scripts in worker
importScripts('math-utils.js', 'helpers.js');

// Example: Image processing in worker
// Main thread
const imageWorker = new Worker('image-processor.js');

canvas.toBlob((blob) => {
    imageWorker.postMessage({
        type: 'process',
        image: blob
    });
});

imageWorker.onmessage = (event) => {
    const processedImage = event.data;
    displayImage(processedImage);
};

// Worker (image-processor.js)
self.onmessage = async (event) => {
    const {type, image} = event.data;
    
    if (type === 'process') {
        // Process image
        const bitmap = await createImageBitmap(image);
        
        // Apply filters, transformations
        const processed = applyFilters(bitmap);
        
        self.postMessage(processed);
    }
};

// Transferable objects (zero-copy transfer)
const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]);  // Transfer ownership
// buffer is now unusable in main thread

// Worker pool for parallel processing
class WorkerPool {
    constructor(workerScript, poolSize = 4) {
        this.workers = [];
        this.queue = [];
        
        for (let i = 0; i < poolSize; i++) {
            const worker = new Worker(workerScript);
            worker.busy = false;
            
            worker.onmessage = (event) => {
                worker.busy = false;
                worker.currentTask.resolve(event.data);
                this.processQueue();
            };
            
            worker.onerror = (error) => {
                worker.busy = false;
                worker.currentTask.reject(error);
                this.processQueue();
            };
            
            this.workers.push(worker);
        }
    }
    
    run(data) {
        return new Promise((resolve, reject) => {
            this.queue.push({data, resolve, reject});
            this.processQueue();
        });
    }
    
    processQueue() {
        if (this.queue.length === 0) return;
        
        const availableWorker = this.workers.find(w => !w.busy);
        
        if (availableWorker) {
            const task = this.queue.shift();
            availableWorker.busy = true;
            availableWorker.currentTask = task;
            availableWorker.postMessage(task.data);
        }
    }
    
    terminate() {
        this.workers.forEach(w => w.terminate());
    }
}

// Usage
const pool = new WorkerPool('worker.js', 4);

const results = await Promise.all([
    pool.run({task: 'process', data: data1}),
    pool.run({task: 'process', data: data2}),
    pool.run({task: 'process', data: data3})
]);

pool.terminate();

Example: Service Workers

// Register service worker (main.js)
if ('serviceWorker' in navigator) {
    window.addEventListener('load', async () => {
        try {
            const registration = await navigator.serviceWorker.register('/sw.js');
            
            console.log('Service Worker registered:', registration.scope);
            
            // Check for updates
            registration.addEventListener('updatefound', () => {
                const newWorker = registration.installing;
                
                newWorker.addEventListener('statechange', () => {
                    if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
                        console.log('New version available!');
                        // Prompt user to refresh
                    }
                });
            });
        } catch (error) {
            console.error('Service Worker registration failed:', error);
        }
    });
}

// Service Worker file (sw.js)
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
    '/',
    '/styles.css',
    '/script.js',
    '/images/logo.png'
];

// Install event - cache resources
self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then((cache) => {
                console.log('Caching files');
                return cache.addAll(urlsToCache);
            })
    );
    
    // Skip waiting to activate immediately
    self.skipWaiting();
});

// Activate event - clean old caches
self.addEventListener('activate', (event) => {
    event.waitUntil(
        caches.keys().then((cacheNames) => {
            return Promise.all(
                cacheNames.map((cacheName) => {
                    if (cacheName !== CACHE_NAME) {
                        console.log('Deleting old cache:', cacheName);
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
    
    // Take control of all pages immediately
    self.clients.claim();
});

// Fetch event - serve from cache or network
self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request)
            .then((response) => {
                // Cache hit - return response
                if (response) {
                    return response;
                }
                
                // Clone request
                const fetchRequest = event.request.clone();
                
                return fetch(fetchRequest).then((response) => {
                    // Check valid response
                    if (!response || response.status !== 200 || response.type !== 'basic') {
                        return response;
                    }
                    
                    // Clone response
                    const responseToCache = response.clone();
                    
                    // Cache new response
                    caches.open(CACHE_NAME)
                        .then((cache) => {
                            cache.put(event.request, responseToCache);
                        });
                    
                    return response;
                });
            })
    );
});

// Network First strategy
self.addEventListener('fetch', (event) => {
    event.respondWith(
        fetch(event.request)
            .then((response) => {
                // Cache successful response
                const responseToCache = response.clone();
                caches.open(CACHE_NAME)
                    .then((cache) => cache.put(event.request, responseToCache));
                
                return response;
            })
            .catch(() => {
                // Network failed, try cache
                return caches.match(event.request);
            })
    );
});

// Stale While Revalidate
self.addEventListener('fetch', (event) => {
    event.respondWith(
        caches.match(event.request)
            .then((cachedResponse) => {
                const fetchPromise = fetch(event.request)
                    .then((networkResponse) => {
                        // Update cache
                        caches.open(CACHE_NAME)
                            .then((cache) => cache.put(event.request, networkResponse.clone()));
                        
                        return networkResponse;
                    });
                
                // Return cached response immediately, update in background
                return cachedResponse || fetchPromise;
            })
    );
});

// Message handling
self.addEventListener('message', (event) => {
    if (event.data.action === 'skipWaiting') {
        self.skipWaiting();
    }
});

// Push notifications
self.addEventListener('push', (event) => {
    const data = event.data.json();
    
    const options = {
        body: data.body,
        icon: '/images/icon.png',
        badge: '/images/badge.png',
        data: {url: data.url}
    };
    
    event.waitUntil(
        self.registration.showNotification(data.title, options)
    );
});

// Notification click
self.addEventListener('notificationclick', (event) => {
    event.notification.close();
    
    event.waitUntil(
        clients.openWindow(event.notification.data.url)
    );
});

// Background sync
self.addEventListener('sync', (event) => {
    if (event.tag === 'sync-messages') {
        event.waitUntil(syncMessages());
    }
});

async function syncMessages() {
    // Get pending messages from IndexedDB
    const messages = await getPendingMessages();
    
    // Send to server
    for (const message of messages) {
        try {
            await fetch('/api/messages', {
                method: 'POST',
                body: JSON.stringify(message)
            });
            
            // Remove from pending
            await removePendingMessage(message.id);
        } catch (error) {
            console.error('Sync failed:', error);
        }
    }
}

// Unregister service worker
navigator.serviceWorker.getRegistrations().then((registrations) => {
    registrations.forEach((registration) => {
        registration.unregister();
    });
});
Key Points: Web Workers run on separate thread for heavy computation. Use postMessage() for communication. Service Workers act as network proxy for offline support. Cache strategies: cache-first, network-first, stale-while-revalidate. Service worker lifecycle: install → activate. Handle fetch events to intercept requests. Push notifications and background sync. Transferable objects for efficient data transfer.

Section 21 Summary: Web APIs and Browser Integration

  • Fetch API: Promise-based HTTP requests, response.json(), request options (method, headers, body)
  • Response Handling: Check response.ok, status codes, AbortController for cancellation
  • DOM Selection: querySelector/All, getElementById, closest, matches for flexible selection
  • DOM Manipulation: createElement, append/prepend, classList, innerHTML vs textContent
  • Events: addEventListener with options (once, capture, passive), event delegation pattern
  • Storage: localStorage (persistent), sessionStorage (tab-scoped), IndexedDB (NoSQL database)
  • Clipboard API: navigator.clipboard.writeText/readText, requires HTTPS and permissions
  • File API: FileReader (readAsText, readAsDataURL), drag and drop, file validation
  • Geolocation: getCurrentPosition, watchPosition, coordinates (lat/lng/accuracy)
  • Device APIs: Orientation, motion, battery, network info, vibration, media devices
  • Web Workers: Separate thread for computation, postMessage communication, worker pools
  • Service Workers: Network proxy, offline support, caching strategies, push notifications

22. Modern JavaScript Patterns and Techniques

22.1 Module Patterns and Namespace Management

Module Pattern Types

Pattern Purpose Usage
IIFE Module Encapsulation with closure Pre-ES6 modules
Revealing Module Explicit public API Clear interface definition
ES6 Modules Native import/export Modern JavaScript standard
CommonJS Node.js modules require/module.exports
Namespace Object Group related functionality Avoid global pollution

ES6 Module Syntax

Syntax Description Example
export Named export export const fn = () => {}
export default Default export export default class {}
import Import named exports import {fn} from './module'
import default Import default export import MyClass from './module'
import * as Import namespace import * as utils from './utils'
export {} Re-export export {fn} from './module'

Module Benefits

Benefit Description Impact
Encapsulation Hide internal implementation Prevent unwanted access
Reusability Share code across projects DRY principle
Maintainability Organized code structure Easier to debug and update
Dependency Management Explicit dependencies Clear module relationships
Namespace Avoid global scope pollution Prevent naming conflicts

Example: Module patterns

// IIFE Module Pattern (pre-ES6)
const myModule = (function() {
    // Private variables and functions
    let privateVar = 'I am private';
    
    function privateMethod() {
        console.log(privateVar);
    }
    
    // Public API
    return {
        publicMethod: function() {
            privateMethod();
        },
        publicVar: 'I am public'
    };
})();

myModule.publicMethod();  // Works
// myModule.privateVar  // undefined (not accessible)

// Revealing Module Pattern
const calculator = (function() {
    // Private
    function add(a, b) {
        return a + b;
    }
    
    function subtract(a, b) {
        return a - b;
    }
    
    function multiply(a, b) {
        return a * b;
    }
    
    // Reveal public API
    return {
        add: add,
        subtract: subtract
        // multiply is private
    };
})();

calculator.add(2, 3);       // 5
calculator.subtract(5, 2);  // 3
// calculator.multiply(2, 3)  // Error: not exposed

// Namespace Pattern
const MyApp = MyApp || {};

MyApp.Utils = {
    formatDate: function(date) {
        return date.toLocaleDateString();
    },
    
    formatCurrency: function(amount) {
        return `${amount.toFixed(2)}`;
    }
};

MyApp.Models = {
    User: class {
        constructor(name) {
            this.name = name;
        }
    }
};

MyApp.Controllers = {
    UserController: class {
        createUser(name) {
            return new MyApp.Models.User(name);
        }
    }
};

// Usage
const formatted = MyApp.Utils.formatCurrency(99.99);
const user = new MyApp.Models.User('John');

// ES6 Modules
// math.js
export function add(a, b) {
    return a + b;
}

export function subtract(a, b) {
    return a - b;
}

export const PI = 3.14159;

// Default export
export default function multiply(a, b) {
    return a * b;
}

// Import in another file
import multiply, {add, subtract, PI} from './math.js';

console.log(add(2, 3));        // 5
console.log(multiply(2, 3));   // 6
console.log(PI);               // 3.14159

// Import all as namespace
import * as math from './math.js';

console.log(math.add(2, 3));
console.log(math.default(2, 3));  // multiply

// Re-export pattern (index.js)
export {add, subtract} from './math.js';
export {User} from './user.js';
export {default as multiply} from './math.js';

// Lazy loading modules
async function loadModule() {
    const module = await import('./heavy-module.js');
    module.doSomething();
}

// Module with initialization
// logger.js
class Logger {
    constructor() {
        this.logs = [];
    }
    
    log(message) {
        this.logs.push({
            message,
            timestamp: Date.now()
        });
        console.log(message);
    }
    
    getLogs() {
        return this.logs;
    }
}

// Export singleton instance
export default new Logger();

// Usage
import logger from './logger.js';

logger.log('Application started');
logger.log('User logged in');

// Dependency injection with modules
// services.js
export class ApiService {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    async get(endpoint) {
        const response = await fetch(`${this.baseUrl}${endpoint}`);
        return response.json();
    }
}

export class AuthService {
    constructor(apiService) {
        this.api = apiService;
    }
    
    async login(credentials) {
        return this.api.get('/auth/login', credentials);
    }
}

// app.js
import {ApiService, AuthService} from './services.js';

const api = new ApiService('https://api.example.com');
const auth = new AuthService(api);

await auth.login({username: 'john', password: 'pass123'});
Key Points: Module pattern uses IIFE for encapsulation. Revealing module pattern explicitly defines public API. ES6 modules with import/export are standard. Use export default for main export. Namespace objects prevent global pollution. Dynamic imports for lazy loading. Modules provide encapsulation, reusability, maintainability.

22.2 Observer Pattern and Event Emitters

Observer Pattern Components

Component Role Responsibility
Subject Observable Maintains observers list, notifies on change
Observer Subscriber Receives notifications, reacts to changes
ConcreteSubject Specific observable Stores state, triggers notifications
ConcreteObserver Specific subscriber Implements update logic

Event Emitter Methods

Method Purpose Usage
on(event, handler) Register listener Subscribe to event
off(event, handler) Remove listener Unsubscribe from event
emit(event, data) Trigger event Notify all listeners
once(event, handler) One-time listener Auto-remove after first call
removeAllListeners() Clear all listeners Cleanup

Observer Pattern Benefits

Benefit Description Use Case
Loose Coupling Subject and observers independent Flexible system architecture
Dynamic Relationships Add/remove observers at runtime Plugins, extensions
Broadcast Communication One-to-many notification State changes, events
Separation of Concerns Observers handle own logic Clean code organization

Example: Observer pattern and Event Emitter

// Classic Observer Pattern
class Subject {
    constructor() {
        this.observers = [];
    }
    
    attach(observer) {
        if (!this.observers.includes(observer)) {
            this.observers.push(observer);
        }
    }
    
    detach(observer) {
        const index = this.observers.indexOf(observer);
        if (index > -1) {
            this.observers.splice(index, 1);
        }
    }
    
    notify(data) {
        this.observers.forEach(observer => {
            observer.update(data);
        });
    }
}

class Observer {
    update(data) {
        console.log('Observer received:', data);
    }
}

// Usage
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.attach(observer1);
subject.attach(observer2);

subject.notify({message: 'Hello observers!'});

subject.detach(observer1);
subject.notify({message: 'Only observer2 gets this'});

// Event Emitter Pattern
class EventEmitter {
    constructor() {
        this.events = {};
    }
    
    on(event, handler) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(handler);
        
        // Return unsubscribe function
        return () => this.off(event, handler);
    }
    
    off(event, handler) {
        if (!this.events[event]) return;
        
        this.events[event] = this.events[event].filter(h => h !== handler);
    }
    
    emit(event, data) {
        if (!this.events[event]) return;
        
        this.events[event].forEach(handler => {
            handler(data);
        });
    }
    
    once(event, handler) {
        const onceHandler = (data) => {
            handler(data);
            this.off(event, onceHandler);
        };
        
        this.on(event, onceHandler);
    }
    
    removeAllListeners(event) {
        if (event) {
            delete this.events[event];
        } else {
            this.events = {};
        }
    }
    
    listenerCount(event) {
        return this.events[event] ? this.events[event].length : 0;
    }
}

// Usage
const emitter = new EventEmitter();

const unsubscribe = emitter.on('data', (data) => {
    console.log('Data received:', data);
});

emitter.emit('data', {value: 42});

// One-time listener
emitter.once('complete', () => {
    console.log('Completed!');
});

emitter.emit('complete');  // Logs "Completed!"
emitter.emit('complete');  // Nothing happens

// Unsubscribe
unsubscribe();

// Real-world example: Store with observers
class Store extends EventEmitter {
    constructor(initialState = {}) {
        super();
        this.state = initialState;
    }
    
    getState() {
        return this.state;
    }
    
    setState(updates) {
        const prevState = {...this.state};
        this.state = {...this.state, ...updates};
        
        this.emit('change', {
            state: this.state,
            prevState: prevState
        });
    }
    
    subscribe(handler) {
        return this.on('change', handler);
    }
}

// Usage
const store = new Store({count: 0, user: null});

const unsubscribeStore = store.subscribe(({state, prevState}) => {
    console.log('State changed from', prevState, 'to', state);
});

store.setState({count: 1});
store.setState({user: {name: 'John'}});

// Real-world: UI Component with events
class Button extends EventEmitter {
    constructor(label) {
        super();
        this.label = label;
        this.element = null;
    }
    
    render() {
        this.element = document.createElement('button');
        this.element.textContent = this.label;
        
        this.element.addEventListener('click', () => {
            this.emit('click', {label: this.label});
        });
        
        return this.element;
    }
    
    setText(label) {
        this.label = label;
        if (this.element) {
            this.element.textContent = label;
        }
        this.emit('textChange', {label});
    }
}

// Usage
const button = new Button('Click Me');

button.on('click', ({label}) => {
    console.log(`Button "${label}" clicked`);
});

button.on('textChange', ({label}) => {
    console.log(`Button text changed to "${label}"`);
});

document.body.appendChild(button.render());

// Async event emitter
class AsyncEventEmitter extends EventEmitter {
    async emit(event, data) {
        if (!this.events[event]) return;
        
        await Promise.all(
            this.events[event].map(handler => handler(data))
        );
    }
}

const asyncEmitter = new AsyncEventEmitter();

asyncEmitter.on('fetch', async (url) => {
    const response = await fetch(url);
    console.log('Fetched:', response.status);
});

await asyncEmitter.emit('fetch', 'https://api.example.com/data');

// Event namespacing
class NamespacedEventEmitter extends EventEmitter {
    emit(event, data) {
        // Emit specific event
        super.emit(event, data);
        
        // Emit namespace events
        const parts = event.split(':');
        for (let i = 1; i < parts.length; i++) {
            const namespace = parts.slice(0, i).join(':');
            super.emit(namespace, data);
        }
    }
}

const nsEmitter = new NamespacedEventEmitter();

nsEmitter.on('user', (data) => {
    console.log('Any user event:', data);
});

nsEmitter.on('user:login', (data) => {
    console.log('User login:', data);
});

nsEmitter.emit('user:login', {id: 123});
// Triggers both 'user' and 'user:login' handlers
Key Points: Observer pattern enables one-to-many communication. Subject maintains observers list, notifies on changes. Event emitter with on/off/emit methods. once() for one-time listeners. Return unsubscribe function from on(). Useful for state management, UI components, pub/sub systems. Loose coupling between components.

22.3 Factory and Builder Patterns

Factory Pattern Types

Type Description Use Case
Simple Factory Single factory function Basic object creation
Factory Method Subclasses decide type Polymorphic creation
Abstract Factory Family of related objects Platform-specific objects
Static Factory Static method creates instances Named constructors

Builder Pattern Benefits

Benefit Description When to Use
Fluent Interface Chainable method calls Readable construction code
Immutability Build once, immutable result Thread-safe objects
Complex Construction Step-by-step building Objects with many parameters
Validation Validate before creation Ensure valid state

Factory vs Constructor

Aspect Factory Constructor
Return type Can return any type Always returns instance
Naming Descriptive names Class name only
Caching Can return cached instances Always creates new
Polymorphism Return different subtypes Fixed type

Example: Factory patterns

// Simple Factory
class User {
    constructor(name, role) {
        this.name = name;
        this.role = role;
    }
    
    getPermissions() {
        return [];
    }
}

class Admin extends User {
    getPermissions() {
        return ['read', 'write', 'delete'];
    }
}

class Guest extends User {
    getPermissions() {
        return ['read'];
    }
}

// Factory function
function createUser(name, role) {
    switch (role) {
        case 'admin':
            return new Admin(name, role);
        case 'guest':
            return new Guest(name, role);
        default:
            return new User(name, role);
    }
}

// Usage
const admin = createUser('John', 'admin');
console.log(admin.getPermissions());  // ['read', 'write', 'delete']

const guest = createUser('Jane', 'guest');
console.log(guest.getPermissions());  // ['read']

// Factory Method Pattern
class Document {
    constructor(title) {
        this.title = title;
    }
    
    render() {
        throw new Error('Must implement render()');
    }
}

class PDFDocument extends Document {
    render() {
        return `PDF: ${this.title}`;
    }
}

class WordDocument extends Document {
    render() {
        return `Word: ${this.title}`;
    }
}

class DocumentFactory {
    createDocument(type, title) {
        switch (type) {
            case 'pdf':
                return new PDFDocument(title);
            case 'word':
                return new WordDocument(title);
            default:
                throw new Error('Unknown document type');
        }
    }
}

// Usage
const factory = new DocumentFactory();
const pdf = factory.createDocument('pdf', 'Report');
console.log(pdf.render());

// Static Factory Methods
class Color {
    constructor(r, g, b) {
        this.r = r;
        this.g = g;
        this.b = b;
    }
    
    static fromRGB(r, g, b) {
        return new Color(r, g, b);
    }
    
    static fromHex(hex) {
        const r = parseInt(hex.slice(1, 3), 16);
        const g = parseInt(hex.slice(3, 5), 16);
        const b = parseInt(hex.slice(5, 7), 16);
        return new Color(r, g, b);
    }
    
    static red() {
        return new Color(255, 0, 0);
    }
    
    static green() {
        return new Color(0, 255, 0);
    }
    
    static blue() {
        return new Color(0, 0, 255);
    }
}

// Usage
const color1 = Color.fromRGB(255, 128, 0);
const color2 = Color.fromHex('#FF8000');
const red = Color.red();

// Object Pool Factory
class ObjectPool {
    constructor(factory, maxSize = 10) {
        this.factory = factory;
        this.maxSize = maxSize;
        this.available = [];
        this.inUse = [];
    }
    
    acquire() {
        let obj;
        
        if (this.available.length > 0) {
            obj = this.available.pop();
        } else if (this.inUse.length < this.maxSize) {
            obj = this.factory();
        } else {
            throw new Error('Pool exhausted');
        }
        
        this.inUse.push(obj);
        return obj;
    }
    
    release(obj) {
        const index = this.inUse.indexOf(obj);
        if (index > -1) {
            this.inUse.splice(index, 1);
            this.available.push(obj);
        }
    }
}

// Usage
const connectionPool = new ObjectPool(
    () => ({id: Math.random(), connected: true}),
    5
);

const conn1 = connectionPool.acquire();
const conn2 = connectionPool.acquire();

connectionPool.release(conn1);

// Abstract Factory Pattern
class Button {
    render() {
        throw new Error('Must implement');
    }
}

class WindowsButton extends Button {
    render() {
        return '<button class="windows">Click</button>';
    }
}

class MacButton extends Button {
    render() {
        return '<button class="mac">Click</button>';
    }
}

class Checkbox {
    render() {
        throw new Error('Must implement');
    }
}

class WindowsCheckbox extends Checkbox {
    render() {
        return '<input type="checkbox" class="windows">';
    }
}

class MacCheckbox extends Checkbox {
    render() {
        return '<input type="checkbox" class="mac">';
    }
}

// Abstract Factory
class UIFactory {
    createButton() {
        throw new Error('Must implement');
    }
    
    createCheckbox() {
        throw new Error('Must implement');
    }
}

class WindowsFactory extends UIFactory {
    createButton() {
        return new WindowsButton();
    }
    
    createCheckbox() {
        return new WindowsCheckbox();
    }
}

class MacFactory extends UIFactory {
    createButton() {
        return new MacButton();
    }
    
    createCheckbox() {
        return new MacCheckbox();
    }
}

// Usage
function createUI(factory) {
    const button = factory.createButton();
    const checkbox = factory.createCheckbox();
    
    return {
        button: button.render(),
        checkbox: checkbox.render()
    };
}

const windowsFactory = new WindowsFactory();
const windowsUI = createUI(windowsFactory);

const macFactory = new MacFactory();
const macUI = createUI(macFactory);

Example: Builder pattern

// Builder Pattern
class User2 {
    constructor(builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.age = builder.age;
        this.address = builder.address;
        this.phone = builder.phone;
    }
}

class UserBuilder {
    constructor(name) {
        this.name = name;
    }
    
    withEmail(email) {
        this.email = email;
        return this;  // Return this for chaining
    }
    
    withAge(age) {
        this.age = age;
        return this;
    }
    
    withAddress(address) {
        this.address = address;
        return this;
    }
    
    withPhone(phone) {
        this.phone = phone;
        return this;
    }
    
    build() {
        // Validation
        if (!this.email) {
            throw new Error('Email is required');
        }
        
        return new User2(this);
    }
}

// Usage (fluent interface)
const user = new UserBuilder('John Doe')
    .withEmail('john@example.com')
    .withAge(30)
    .withAddress('123 Main St')
    .withPhone('555-1234')
    .build();

console.log(user);

// Query Builder
class QueryBuilder {
    constructor() {
        this.query = {
            table: null,
            fields: ['*'],
            conditions: [],
            orderBy: null,
            limit: null
        };
    }
    
    select(...fields) {
        this.query.fields = fields;
        return this;
    }
    
    from(table) {
        this.query.table = table;
        return this;
    }
    
    where(condition) {
        this.query.conditions.push(condition);
        return this;
    }
    
    orderBy(field, direction = 'ASC') {
        this.query.orderBy = {field, direction};
        return this;
    }
    
    limit(count) {
        this.query.limit = count;
        return this;
    }
    
    build() {
        let sql = `SELECT ${this.query.fields.join(', ')}`;
        sql += ` FROM ${this.query.table}`;
        
        if (this.query.conditions.length > 0) {
            sql += ` WHERE ${this.query.conditions.join(' AND ')}`;
        }
        
        if (this.query.orderBy) {
            sql += ` ORDER BY ${this.query.orderBy.field} ${this.query.orderBy.direction}`;
        }
        
        if (this.query.limit) {
            sql += ` LIMIT ${this.query.limit}`;
        }
        
        return sql;
    }
}

// Usage
const query = new QueryBuilder()
    .select('id', 'name', 'email')
    .from('users')
    .where('age > 18')
    .where('status = "active"')
    .orderBy('name', 'ASC')
    .limit(10)
    .build();

console.log(query);
// SELECT id, name, email FROM users WHERE age > 18 AND status = "active" ORDER BY name ASC LIMIT 10

// HTML Builder
class HTMLBuilder {
    constructor(tag) {
        this.element = {
            tag: tag,
            attributes: {},
            children: [],
            text: null
        };
    }
    
    attr(name, value) {
        this.element.attributes[name] = value;
        return this;
    }
    
    text(content) {
        this.element.text = content;
        return this;
    }
    
    child(builder) {
        if (builder instanceof HTMLBuilder) {
            this.element.children.push(builder.element);
        } else {
            this.element.children.push(builder);
        }
        return this;
    }
    
    build() {
        let html = `<${this.element.tag}`;
        
        // Add attributes
        for (const [key, value] of Object.entries(this.element.attributes)) {
            html += ` ${key}="${value}"`;
        }
        
        html += '>';
        
        // Add text or children
        if (this.element.text) {
            html += this.element.text;
        } else {
            this.element.children.forEach(child => {
                if (typeof child === 'string') {
                    html += child;
                } else {
                    html += new HTMLBuilder(child.tag)
                        .attr(Object.keys(child.attributes)[0], Object.values(child.attributes)[0])
                        .text(child.text)
                        .build();
                }
            });
        }
        
        html += `</${this.element.tag}>`;
        
        return html;
    }
}

// Usage
const html = new HTMLBuilder('div')
    .attr('class', 'container')
    .child(
        new HTMLBuilder('h1')
            .attr('class', 'title')
            .text('Hello World')
    )
    .child(
        new HTMLBuilder('p')
            .text('This is a paragraph')
    )
    .build();

console.log(html);
// <div class="container"><h1 class="title">Hello World</h1><p>This is a paragraph</p></div>
Key Points: Factory pattern encapsulates object creation logic. Use static factory methods for named constructors. Abstract factory creates families of related objects. Builder pattern uses fluent interface for complex construction. Builder validates before creating object. Good for objects with many optional parameters. Both patterns improve code maintainability.

22.4 Singleton and Registry Patterns

Singleton Characteristics

Characteristic Description Implementation
Single Instance Only one instance exists Private constructor or closure
Global Access Accessible from anywhere Static getInstance() method
Lazy Initialization Created only when needed Check if instance exists
Thread Safety Safe in multi-threaded env Synchronization (JS single-threaded)

Registry Pattern Uses

Use Case Purpose Example
Service Locator Central service registry DI container
Plugin System Register/retrieve plugins Extension manager
Factory Registry Register factory functions Component registry
Event Registry Central event handlers Event bus

Singleton vs Global Variable

Aspect Singleton Global Variable
Instantiation Lazy, controlled Immediate
Encapsulation Private members possible All public
Inheritance Can extend classes Cannot extend
Testability Can be mocked Hard to test

Example: Singleton pattern

// Classic Singleton (ES6 class)
class Database {
    constructor() {
        if (Database.instance) {
            return Database.instance;
        }
        
        this.connection = null;
        this.connected = false;
        
        Database.instance = this;
    }
    
    connect(connectionString) {
        if (!this.connected) {
            this.connection = connectionString;
            this.connected = true;
            console.log('Database connected');
        }
    }
    
    query(sql) {
        if (!this.connected) {
            throw new Error('Not connected');
        }
        console.log(`Executing: ${sql}`);
        return [];
    }
    
    disconnect() {
        if (this.connected) {
            this.connection = null;
            this.connected = false;
            console.log('Database disconnected');
        }
    }
}

// Usage
const db1 = new Database();
db1.connect('mongodb://localhost');

const db2 = new Database();
console.log(db1 === db2);  // true (same instance)

// Module Singleton (Using ES6 modules)
// logger.js
class Logger {
    constructor() {
        this.logs = [];
    }
    
    log(message) {
        const entry = {
            message,
            timestamp: new Date(),
            level: 'info'
        };
        this.logs.push(entry);
        console.log(`[${entry.timestamp.toISOString()}] ${message}`);
    }
    
    error(message) {
        const entry = {
            message,
            timestamp: new Date(),
            level: 'error'
        };
        this.logs.push(entry);
        console.error(`[${entry.timestamp.toISOString()}] ERROR: ${message}`);
    }
    
    getLogs() {
        return this.logs;
    }
    
    clear() {
        this.logs = [];
    }
}

// Export singleton instance
export default new Logger();

// Usage in other files
// import logger from './logger.js';
// logger.log('Application started');

// Singleton with getInstance()
class Config {
    static instance = null;
    
    constructor() {
        if (Config.instance) {
            throw new Error('Use Config.getInstance()');
        }
        
        this.settings = {};
    }
    
    static getInstance() {
        if (!Config.instance) {
            Config.instance = new Config();
        }
        return Config.instance;
    }
    
    set(key, value) {
        this.settings[key] = value;
    }
    
    get(key) {
        return this.settings[key];
    }
    
    getAll() {
        return {...this.settings};
    }
}

// Usage
const config1 = Config.getInstance();
config1.set('apiUrl', 'https://api.example.com');

const config2 = Config.getInstance();
console.log(config2.get('apiUrl'));  // https://api.example.com
console.log(config1 === config2);    // true

// Closure-based Singleton
const AppState = (function() {
    let instance;
    
    function createInstance() {
        const state = {
            user: null,
            settings: {},
            cache: new Map()
        };
        
        return {
            getState() {
                return state;
            },
            
            setUser(user) {
                state.user = user;
            },
            
            getUser() {
                return state.user;
            },
            
            setSetting(key, value) {
                state.settings[key] = value;
            },
            
            getSetting(key) {
                return state.settings[key];
            }
        };
    }
    
    return {
        getInstance() {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();

// Usage
const state1 = AppState.getInstance();
state1.setUser({name: 'John', id: 123});

const state2 = AppState.getInstance();
console.log(state2.getUser());  // {name: 'John', id: 123}

// Singleton with initialization options
class Cache {
    static instances = new Map();
    
    constructor(name, options = {}) {
        const key = name;
        
        if (Cache.instances.has(key)) {
            return Cache.instances.get(key);
        }
        
        this.name = name;
        this.maxSize = options.maxSize || 100;
        this.ttl = options.ttl || 60000;  // 1 minute
        this.store = new Map();
        
        Cache.instances.set(key, this);
    }
    
    set(key, value) {
        if (this.store.size >= this.maxSize) {
            const firstKey = this.store.keys().next().value;
            this.store.delete(firstKey);
        }
        
        this.store.set(key, {
            value,
            expires: Date.now() + this.ttl
        });
    }
    
    get(key) {
        const item = this.store.get(key);
        
        if (!item) return null;
        
        if (Date.now() > item.expires) {
            this.store.delete(key);
            return null;
        }
        
        return item.value;
    }
    
    clear() {
        this.store.clear();
    }
}

// Usage - named singletons
const userCache = new Cache('users', {maxSize: 50});
const productCache = new Cache('products', {maxSize: 100});

userCache.set('user:1', {name: 'John'});

const userCache2 = new Cache('users');
console.log(userCache2.get('user:1'));  // {name: 'John'}
console.log(userCache === userCache2);  // true

Example: Registry pattern

// Service Registry / Dependency Injection Container
class ServiceRegistry {
    constructor() {
        this.services = new Map();
        this.singletons = new Map();
    }
    
    register(name, factory, options = {}) {
        this.services.set(name, {
            factory,
            singleton: options.singleton || false,
            dependencies: options.dependencies || []
        });
    }
    
    resolve(name) {
        const service = this.services.get(name);
        
        if (!service) {
            throw new Error(`Service '${name}' not registered`);
        }
        
        // Return singleton if already created
        if (service.singleton && this.singletons.has(name)) {
            return this.singletons.get(name);
        }
        
        // Resolve dependencies
        const dependencies = service.dependencies.map(dep => this.resolve(dep));
        
        // Create instance
        const instance = service.factory(...dependencies);
        
        // Store singleton
        if (service.singleton) {
            this.singletons.set(name, instance);
        }
        
        return instance;
    }
    
    has(name) {
        return this.services.has(name);
    }
    
    clear() {
        this.services.clear();
        this.singletons.clear();
    }
}

// Usage
const registry = new ServiceRegistry();

// Register services
registry.register('database', () => {
    return {
        query: (sql) => console.log('Query:', sql)
    };
}, {singleton: true});

registry.register('userService', (db) => {
    return {
        getUser: (id) => {
            db.query(`SELECT * FROM users WHERE id = ${id}`);
            return {id, name: 'John'};
        }
    };
}, {dependencies: ['database']});

// Resolve services
const userService = registry.resolve('userService');
userService.getUser(123);

// Plugin Registry
class PluginRegistry {
    constructor() {
        this.plugins = new Map();
        this.hooks = new Map();
    }
    
    register(name, plugin) {
        if (this.plugins.has(name)) {
            throw new Error(`Plugin '${name}' already registered`);
        }
        
        this.plugins.set(name, plugin);
        
        // Initialize plugin
        if (typeof plugin.init === 'function') {
            plugin.init(this);
        }
        
        return this;
    }
    
    get(name) {
        return this.plugins.get(name);
    }
    
    has(name) {
        return this.plugins.has(name);
    }
    
    unregister(name) {
        const plugin = this.plugins.get(name);
        
        if (plugin && typeof plugin.destroy === 'function') {
            plugin.destroy();
        }
        
        return this.plugins.delete(name);
    }
    
    // Hook system
    registerHook(hookName, callback) {
        if (!this.hooks.has(hookName)) {
            this.hooks.set(hookName, []);
        }
        this.hooks.get(hookName).push(callback);
    }
    
    executeHook(hookName, data) {
        const callbacks = this.hooks.get(hookName) || [];
        
        let result = data;
        for (const callback of callbacks) {
            result = callback(result);
        }
        
        return result;
    }
}

// Usage
const plugins = new PluginRegistry();

// Register plugins
plugins.register('markdown', {
    init(registry) {
        console.log('Markdown plugin initialized');
        registry.registerHook('beforeRender', (text) => {
            return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
        });
    },
    
    render(text) {
        return text;
    },
    
    destroy() {
        console.log('Markdown plugin destroyed');
    }
});

plugins.register('emoji', {
    init(registry) {
        registry.registerHook('beforeRender', (text) => {
            return text.replace(/:smile:/g, '😊');
        });
    }
});

// Execute hooks
let content = 'Hello **world** :smile:';
content = plugins.executeHook('beforeRender', content);
console.log(content);  // Hello <strong>world</strong> 😊

// Component Registry
class ComponentRegistry {
    constructor() {
        this.components = new Map();
    }
    
    register(name, component) {
        this.components.set(name, component);
        return this;
    }
    
    create(name, props = {}) {
        const Component = this.components.get(name);
        
        if (!Component) {
            throw new Error(`Component '${name}' not registered`);
        }
        
        if (typeof Component === 'function') {
            return new Component(props);
        }
        
        return Component;
    }
    
    getAll() {
        return Array.from(this.components.keys());
    }
}

// Usage
const components = new ComponentRegistry();

components.register('Button', class {
    constructor(props) {
        this.label = props.label || 'Click';
    }
    
    render() {
        return `<button>${this.label}</button>`;
    }
});

components.register('Input', class {
    constructor(props) {
        this.type = props.type || 'text';
        this.placeholder = props.placeholder || '';
    }
    
    render() {
        return `<input type="${this.type}" placeholder="${this.placeholder}">`;
    }
});

const button = components.create('Button', {label: 'Submit'});
console.log(button.render());

const input = components.create('Input', {placeholder: 'Enter name'});
console.log(input.render());

console.log('Registered components:', components.getAll());
Key Points: Singleton pattern ensures single instance with global access. Use constructor check or getInstance() method. ES6 module exports create natural singletons. Registry pattern manages collections of objects. Service registry for dependency injection. Plugin registry for extensible systems. Component registry for dynamic creation.

22.5 Strategy and Command Patterns

Strategy Pattern Components

Component Role Responsibility
Context Uses strategy Maintains reference to strategy
Strategy Interface Common interface Defines algorithm method
Concrete Strategy Implements algorithm Specific implementation

Command Pattern Elements

Element Purpose Implementation
Command Encapsulates action execute() method
Receiver Performs action Business logic
Invoker Triggers command Calls execute()
Client Creates command Binds receiver to command

Pattern Benefits

Pattern Key Benefit Use Case
Strategy Runtime algorithm selection Sorting, validation, payment methods
Command Undo/redo, queuing, logging Text editors, transactions, macros

Example: Strategy pattern

// Strategy Pattern - Sorting algorithms
class SortStrategy {
    sort(array) {
        throw new Error('Must implement sort()');
    }
}

class BubbleSortStrategy extends SortStrategy {
    sort(array) {
        const arr = [...array];
        for (let i = 0; i < arr.length; i++) {
            for (let j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
                }
            }
        }
        return arr;
    }
}

class QuickSortStrategy extends SortStrategy {
    sort(array) {
        if (array.length <= 1) return array;
        
        const pivot = array[Math.floor(array.length / 2)];
        const left = array.filter(x => x < pivot);
        const middle = array.filter(x => x === pivot);
        const right = array.filter(x => x > pivot);
        
        return [...this.sort(left), ...middle, ...this.sort(right)];
    }
}

class Sorter {
    constructor(strategy) {
        this.strategy = strategy;
    }
    
    setStrategy(strategy) {
        this.strategy = strategy;
    }
    
    sort(array) {
        console.log(`Sorting with ${this.strategy.constructor.name}`);
        return this.strategy.sort(array);
    }
}

// Usage
const data = [5, 2, 8, 1, 9];

const sorter = new Sorter(new BubbleSortStrategy());
console.log(sorter.sort(data));

sorter.setStrategy(new QuickSortStrategy());
console.log(sorter.sort(data));

// Strategy Pattern - Validation
class ValidationStrategy {
    validate(value) {
        throw new Error('Must implement validate()');
    }
}

class EmailValidation extends ValidationStrategy {
    validate(value) {
        const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return regex.test(value);
    }
}

class PhoneValidation extends ValidationStrategy {
    validate(value) {
        const regex = /^\d{3}-\d{3}-\d{4}$/;
        return regex.test(value);
    }
}

class PasswordValidation extends ValidationStrategy {
    validate(value) {
        return value.length >= 8 && 
               /[A-Z]/.test(value) &&
               /[a-z]/.test(value) &&
               /[0-9]/.test(value);
    }
}

class Validator {
    constructor() {
        this.strategies = new Map();
    }
    
    addStrategy(field, strategy) {
        this.strategies.set(field, strategy);
    }
    
    validate(data) {
        const errors = {};
        
        for (const [field, strategy] of this.strategies) {
            const value = data[field];
            
            if (!strategy.validate(value)) {
                errors[field] = `Invalid ${field}`;
            }
        }
        
        return {
            valid: Object.keys(errors).length === 0,
            errors
        };
    }
}

// Usage
const validator = new Validator();
validator.addStrategy('email', new EmailValidation());
validator.addStrategy('phone', new PhoneValidation());
validator.addStrategy('password', new PasswordValidation());

const result = validator.validate({
    email: 'user@example.com',
    phone: '555-123-4567',
    password: 'Pass123'
});

console.log(result);  // {valid: true, errors: {}}

// Strategy Pattern - Payment methods
class PaymentStrategy {
    pay(amount) {
        throw new Error('Must implement pay()');
    }
}

class CreditCardPayment extends PaymentStrategy {
    constructor(cardNumber) {
        super();
        this.cardNumber = cardNumber;
    }
    
    pay(amount) {
        console.log(`Paid ${amount} with credit card ${this.cardNumber}`);
        return {success: true, transactionId: Math.random()};
    }
}

class PayPalPayment extends PaymentStrategy {
    constructor(email) {
        super();
        this.email = email;
    }
    
    pay(amount) {
        console.log(`Paid ${amount} via PayPal (${this.email})`);
        return {success: true, transactionId: Math.random()};
    }
}

class CryptoPayment extends PaymentStrategy {
    constructor(wallet) {
        super();
        this.wallet = wallet;
    }
    
    pay(amount) {
        console.log(`Paid ${amount} with crypto (${this.wallet})`);
        return {success: true, transactionId: Math.random()};
    }
}

class ShoppingCart {
    constructor() {
        this.items = [];
        this.paymentStrategy = null;
    }
    
    addItem(item) {
        this.items.push(item);
    }
    
    setPaymentStrategy(strategy) {
        this.paymentStrategy = strategy;
    }
    
    checkout() {
        const total = this.items.reduce((sum, item) => sum + item.price, 0);
        
        if (!this.paymentStrategy) {
            throw new Error('Payment method not set');
        }
        
        return this.paymentStrategy.pay(total);
    }
}

// Usage
const cart = new ShoppingCart();
cart.addItem({name: 'Book', price: 20});
cart.addItem({name: 'Pen', price: 5});

cart.setPaymentStrategy(new CreditCardPayment('1234-5678-9012-3456'));
cart.checkout();

cart.setPaymentStrategy(new PayPalPayment('user@example.com'));
cart.checkout();

Example: Command pattern

// Command Pattern - Text editor
class Command {
    execute() {
        throw new Error('Must implement execute()');
    }
    
    undo() {
        throw new Error('Must implement undo()');
    }
}

class InsertTextCommand extends Command {
    constructor(receiver, text, position) {
        super();
        this.receiver = receiver;
        this.text = text;
        this.position = position;
    }
    
    execute() {
        this.receiver.insertText(this.text, this.position);
    }
    
    undo() {
        this.receiver.deleteText(this.position, this.text.length);
    }
}

class DeleteTextCommand extends Command {
    constructor(receiver, position, length) {
        super();
        this.receiver = receiver;
        this.position = position;
        this.length = length;
        this.deletedText = null;
    }
    
    execute() {
        this.deletedText = this.receiver.deleteText(this.position, this.length);
    }
    
    undo() {
        this.receiver.insertText(this.deletedText, this.position);
    }
}

// Receiver
class TextEditor {
    constructor() {
        this.content = '';
    }
    
    insertText(text, position) {
        this.content = 
            this.content.slice(0, position) + 
            text + 
            this.content.slice(position);
        console.log('Content:', this.content);
    }
    
    deleteText(position, length) {
        const deleted = this.content.slice(position, position + length);
        this.content = 
            this.content.slice(0, position) + 
            this.content.slice(position + length);
        console.log('Content:', this.content);
        return deleted;
    }
    
    getText() {
        return this.content;
    }
}

// Invoker
class CommandManager {
    constructor() {
        this.history = [];
        this.currentIndex = -1;
    }
    
    execute(command) {
        // Remove any commands after current index
        this.history = this.history.slice(0, this.currentIndex + 1);
        
        command.execute();
        
        this.history.push(command);
        this.currentIndex++;
    }
    
    undo() {
        if (this.currentIndex < 0) {
            console.log('Nothing to undo');
            return;
        }
        
        const command = this.history[this.currentIndex];
        command.undo();
        this.currentIndex--;
    }
    
    redo() {
        if (this.currentIndex >= this.history.length - 1) {
            console.log('Nothing to redo');
            return;
        }
        
        this.currentIndex++;
        const command = this.history[this.currentIndex];
        command.execute();
    }
}

// Usage
const editor = new TextEditor();
const manager = new CommandManager();

manager.execute(new InsertTextCommand(editor, 'Hello', 0));
manager.execute(new InsertTextCommand(editor, ' World', 5));
manager.execute(new DeleteTextCommand(editor, 5, 6));

manager.undo();  // Restore " World"
manager.undo();  // Remove " World"
manager.redo();  // Add " World" again

// Command Pattern - Remote control
class Light {
    constructor(location) {
        this.location = location;
        this.isOn = false;
    }
    
    on() {
        this.isOn = true;
        console.log(`${this.location} light is ON`);
    }
    
    off() {
        this.isOn = false;
        console.log(`${this.location} light is OFF`);
    }
}

class LightOnCommand extends Command {
    constructor(light) {
        super();
        this.light = light;
    }
    
    execute() {
        this.light.on();
    }
    
    undo() {
        this.light.off();
    }
}

class LightOffCommand extends Command {
    constructor(light) {
        super();
        this.light = light;
    }
    
    execute() {
        this.light.off();
    }
    
    undo() {
        this.light.on();
    }
}

class RemoteControl {
    constructor() {
        this.commands = {};
        this.history = [];
    }
    
    setCommand(button, command) {
        this.commands[button] = command;
    }
    
    pressButton(button) {
        const command = this.commands[button];
        
        if (command) {
            command.execute();
            this.history.push(command);
        }
    }
    
    pressUndo() {
        const command = this.history.pop();
        
        if (command) {
            command.undo();
        }
    }
}

// Usage
const livingRoomLight = new Light('Living Room');
const bedroomLight = new Light('Bedroom');

const remote = new RemoteControl();
remote.setCommand('button1', new LightOnCommand(livingRoomLight));
remote.setCommand('button2', new LightOffCommand(livingRoomLight));
remote.setCommand('button3', new LightOnCommand(bedroomLight));

remote.pressButton('button1');  // Living Room light ON
remote.pressButton('button3');  // Bedroom light ON
remote.pressUndo();             // Bedroom light OFF

// Macro Command - multiple commands
class MacroCommand extends Command {
    constructor(commands) {
        super();
        this.commands = commands;
    }
    
    execute() {
        this.commands.forEach(cmd => cmd.execute());
    }
    
    undo() {
        // Undo in reverse order
        for (let i = this.commands.length - 1; i >= 0; i--) {
            this.commands[i].undo();
        }
    }
}

// Usage
const partyMode = new MacroCommand([
    new LightOnCommand(livingRoomLight),
    new LightOnCommand(bedroomLight)
]);

remote.setCommand('party', partyMode);
remote.pressButton('party');  // Turn on all lights
Key Points: Strategy pattern encapsulates algorithms as interchangeable objects. Set strategy at runtime. Use for sorting, validation, payment methods. Command pattern encapsulates actions as objects. Enables undo/redo with command history. Macro commands combine multiple commands. Both patterns promote loose coupling.

22.6 Decorator and Proxy Patterns

Decorator Pattern Features

Feature Description Benefit
Wrapping Wrap object to add behavior Extend without modifying
Composition Stack multiple decorators Flexible combinations
Transparent Same interface as original Drop-in replacement
Runtime Add behavior dynamically Configuration flexibility

Proxy Pattern Types

Type Purpose Use Case
Virtual Proxy Lazy initialization Expensive object creation
Protection Proxy Access control Authentication, authorization
Remote Proxy Remote object access API calls, RPC
Caching Proxy Cache results Performance optimization
Logging Proxy Log method calls Debugging, auditing

ES6 Proxy Traps

Trap Intercepts Usage
get Property access obj.prop
set Property assignment obj.prop = value
has in operator 'prop' in obj
deleteProperty delete operator delete obj.prop
apply Function call func(...args)
construct new operator new Constructor()

Example: Decorator pattern

// Decorator Pattern - Coffee shop
class Coffee {
    cost() {
        return 5;
    }
    
    description() {
        return 'Coffee';
    }
}

class CoffeeDecorator {
    constructor(coffee) {
        this.coffee = coffee;
    }
    
    cost() {
        return this.coffee.cost();
    }
    
    description() {
        return this.coffee.description();
    }
}

class MilkDecorator extends CoffeeDecorator {
    cost() {
        return this.coffee.cost() + 1;
    }
    
    description() {
        return this.coffee.description() + ', Milk';
    }
}

class SugarDecorator extends CoffeeDecorator {
    cost() {
        return this.coffee.cost() + 0.5;
    }
    
    description() {
        return this.coffee.description() + ', Sugar';
    }
}

class WhipDecorator extends CoffeeDecorator {
    cost() {
        return this.coffee.cost() + 1.5;
    }
    
    description() {
        return this.coffee.description() + ', Whip';
    }
}

// Usage
let myCoffee = new Coffee();
console.log(`${myCoffee.description()}: ${myCoffee.cost()}`);

myCoffee = new MilkDecorator(myCoffee);
console.log(`${myCoffee.description()}: ${myCoffee.cost()}`);

myCoffee = new SugarDecorator(myCoffee);
myCoffee = new WhipDecorator(myCoffee);
console.log(`${myCoffee.description()}: ${myCoffee.cost()}`);
// Coffee, Milk, Sugar, Whip: $8

// Function Decorator
function logger(func) {
    return function(...args) {
        console.log(`Calling ${func.name} with`, args);
        const result = func(...args);
        console.log(`Result:`, result);
        return result;
    };
}

function memoize(func) {
    const cache = new Map();
    
    return function(...args) {
        const key = JSON.stringify(args);
        
        if (cache.has(key)) {
            console.log('Cache hit');
            return cache.get(key);
        }
        
        const result = func(...args);
        cache.set(key, result);
        return result;
    };
}

function timer(func) {
    return function(...args) {
        const start = performance.now();
        const result = func(...args);
        const end = performance.now();
        console.log(`${func.name} took ${(end - start).toFixed(2)}ms`);
        return result;
    };
}

// Usage
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

const decoratedFib = timer(memoize(logger(fibonacci)));
decoratedFib(10);

// Class Method Decorator (using decorator proposal)
function readonly(target, key, descriptor) {
    descriptor.writable = false;
    return descriptor;
}

function validate(target, key, descriptor) {
    const original = descriptor.value;
    
    descriptor.value = function(...args) {
        if (args.some(arg => typeof arg !== 'number')) {
            throw new Error('All arguments must be numbers');
        }
        return original.apply(this, args);
    };
    
    return descriptor;
}

class Calculator {
    @readonly
    PI = 3.14159;
    
    @validate
    add(a, b) {
        return a + b;
    }
}

// Without decorator syntax (current JavaScript)
class Calculator2 {
    constructor() {
        Object.defineProperty(this, 'PI', {
            value: 3.14159,
            writable: false
        });
    }
}

// Wrap with validation
Calculator2.prototype.add = function(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new Error('All arguments must be numbers');
    }
    return a + b;
};

Example: Proxy pattern

// Virtual Proxy - Lazy loading
class Image {
    constructor(filename) {
        this.filename = filename;
        this.loadFromDisk();
    }
    
    loadFromDisk() {
        console.log(`Loading ${this.filename} from disk`);
    }
    
    display() {
        console.log(`Displaying ${this.filename}`);
    }
}

class ImageProxy {
    constructor(filename) {
        this.filename = filename;
        this.image = null;
    }
    
    display() {
        if (!this.image) {
            this.image = new Image(this.filename);
        }
        this.image.display();
    }
}

// Usage
const proxy = new ImageProxy('photo.jpg');
// Image not loaded yet

proxy.display();  // Loads and displays
proxy.display();  // Just displays (already loaded)

// Protection Proxy - Access control
class BankAccount {
    constructor(balance) {
        this.balance = balance;
    }
    
    deposit(amount) {
        this.balance += amount;
        console.log(`Deposited ${amount}, balance: ${this.balance}`);
    }
    
    withdraw(amount) {
        if (amount > this.balance) {
            throw new Error('Insufficient funds');
        }
        this.balance -= amount;
        console.log(`Withdrew ${amount}, balance: ${this.balance}`);
    }
    
    getBalance() {
        return this.balance;
    }
}

class BankAccountProxy {
    constructor(account, password) {
        this.account = account;
        this.password = password;
    }
    
    authenticate(password) {
        return password === this.password;
    }
    
    deposit(amount, password) {
        if (!this.authenticate(password)) {
            throw new Error('Authentication failed');
        }
        this.account.deposit(amount);
    }
    
    withdraw(amount, password) {
        if (!this.authenticate(password)) {
            throw new Error('Authentication failed');
        }
        this.account.withdraw(amount);
    }
    
    getBalance(password) {
        if (!this.authenticate(password)) {
            throw new Error('Authentication failed');
        }
        return this.account.getBalance();
    }
}

// Usage
const account = new BankAccount(1000);
const secureAccount = new BankAccountProxy(account, 'secret123');

secureAccount.deposit(500, 'secret123');
secureAccount.withdraw(200, 'secret123');
// secureAccount.withdraw(200, 'wrong');  // Error

// ES6 Proxy - Validation
const user = {
    name: 'John',
    age: 30,
    email: 'john@example.com'
};

const validatedUser = new Proxy(user, {
    set(target, property, value) {
        if (property === 'age') {
            if (typeof value !== 'number' || value < 0) {
                throw new Error('Age must be a positive number');
            }
        }
        
        if (property === 'email') {
            if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
                throw new Error('Invalid email format');
            }
        }
        
        target[property] = value;
        return true;
    }
});

validatedUser.age = 35;  // OK
// validatedUser.age = -5;  // Error
// validatedUser.email = 'invalid';  // Error

// ES6 Proxy - Logging
function createLoggingProxy(obj, name) {
    return new Proxy(obj, {
        get(target, property) {
            console.log(`[GET] ${name}.${property}`);
            return target[property];
        },
        
        set(target, property, value) {
            console.log(`[SET] ${name}.${property} = ${value}`);
            target[property] = value;
            return true;
        }
    });
}

const person = createLoggingProxy({
    name: 'John',
    age: 30
}, 'person');

person.name;       // [GET] person.name
person.age = 31;   // [SET] person.age = 31

// ES6 Proxy - Negative array indexes
function createArrayProxy(arr) {
    return new Proxy(arr, {
        get(target, property) {
            const index = parseInt(property);
            
            if (!isNaN(index) && index < 0) {
                return target[target.length + index];
            }
            
            return target[property];
        }
    });
}

const arr = createArrayProxy([1, 2, 3, 4, 5]);
console.log(arr[-1]);  // 5
console.log(arr[-2]);  // 4

// ES6 Proxy - Observable
function createObservable(obj, onChange) {
    return new Proxy(obj, {
        set(target, property, value) {
            const oldValue = target[property];
            target[property] = value;
            
            onChange({
                property,
                oldValue,
                newValue: value
            });
            
            return true;
        }
    });
}

const state = createObservable({count: 0}, (change) => {
    console.log(`${change.property} changed from ${change.oldValue} to ${change.newValue}`);
});

state.count = 1;  // count changed from 0 to 1
state.count = 2;  // count changed from 1 to 2

// Caching Proxy
function createCachingProxy(target) {
    const cache = new Map();
    
    return new Proxy(target, {
        apply(target, thisArg, args) {
            const key = JSON.stringify(args);
            
            if (cache.has(key)) {
                console.log('Cache hit');
                return cache.get(key);
            }
            
            const result = target.apply(thisArg, args);
            cache.set(key, result);
            return result;
        }
    });
}

function expensiveOperation(n) {
    console.log('Computing...');
    let result = 0;
    for (let i = 0; i < n * 1000000; i++) {
        result += i;
    }
    return result;
}

const cachedOp = createCachingProxy(expensiveOperation);

cachedOp(10);  // Computing...
cachedOp(10);  // Cache hit (instant)
cachedOp(20);  // Computing...
Key Points: Decorator pattern wraps objects to add behavior dynamically. Stack multiple decorators for flexible combinations. Proxy pattern controls access to objects. Virtual proxy for lazy loading. Protection proxy for access control. ES6 Proxy with traps (get, set, apply) enables powerful interceptors. Use for validation, logging, caching, observables.

Section 22 Summary: Modern JavaScript Patterns and Techniques

  • Module Patterns: IIFE modules, revealing module, ES6 modules (import/export), namespace objects for organization
  • Observer Pattern: Subject maintains observers, notify on changes, Event Emitter with on/off/emit methods
  • Factory Pattern: Encapsulate object creation, static factory methods, abstract factory for related objects
  • Builder Pattern: Fluent interface for complex construction, chainable methods, validation before creation
  • Singleton: Single instance with global access, getInstance() method, ES6 module exports as natural singletons
  • Registry Pattern: Manage collections of objects, service registry (DI), plugin registry, component registry
  • Strategy Pattern: Encapsulate algorithms, runtime selection, interchangeable strategies for sorting/validation/payments
  • Command Pattern: Encapsulate actions as objects, undo/redo with command history, macro commands for multiple actions
  • Decorator Pattern: Wrap objects to add behavior, stack decorators, same interface as original object
  • Proxy Pattern: Control object access, types (virtual, protection, caching), ES6 Proxy traps (get/set/apply)
  • Pattern Benefits: All patterns promote loose coupling, code reusability, maintainability, testability
  • Use Cases: Choose pattern based on problem: Observer for events, Factory for creation, Strategy for algorithms, Command for undo/redo, Proxy for access control

23. Functional Programming Concepts

23.1 Pure Functions and Side Effects Management

Pure Function Characteristics

Characteristic Description Benefit
Deterministic Same input → same output Predictable, testable
No Side Effects Doesn't modify external state Safer, easier to reason about
No External Dependencies Only uses parameters Self-contained, portable
Referential Transparency Can replace call with result Optimizable, cacheable

Common Side Effects

Side Effect Example Impact
Mutation Modifying objects/arrays Unpredictable state changes
I/O Operations Console.log, file read/write Non-deterministic behavior
DOM Manipulation Changing HTML elements External state modification
Network Requests API calls, fetch Async, unpredictable timing
Global State Accessing/modifying globals Hidden dependencies
Random/Date Math.random(), Date.now() Non-deterministic output

Managing Side Effects

Strategy Technique Usage
Isolation Keep side effects at boundaries Pure core, impure shell
Dependency Injection Pass dependencies as parameters Explicit dependencies
Immutability Never modify, create new Prevent mutation side effects
Functional Core Pure logic separate from I/O Testable business logic

Example: Pure functions vs impure

// IMPURE: Modifies external state
let total = 0;

function addToTotal(value) {
    total += value;  // Side effect: modifies global
    return total;
}

console.log(addToTotal(5));   // 5
console.log(addToTotal(5));   // 10 (different output!)

// PURE: No side effects
function add(a, b) {
    return a + b;
}

console.log(add(5, 5));  // 10
console.log(add(5, 5));  // 10 (always same output)

// IMPURE: Mutates input
function addItemImpure(array, item) {
    array.push(item);  // Mutates original array
    return array;
}

const arr1 = [1, 2, 3];
addItemImpure(arr1, 4);
console.log(arr1);  // [1, 2, 3, 4] - original modified!

// PURE: Returns new array
function addItemPure(array, item) {
    return [...array, item];  // Creates new array
}

const arr2 = [1, 2, 3];
const arr3 = addItemPure(arr2, 4);
console.log(arr2);  // [1, 2, 3] - original unchanged
console.log(arr3);  // [1, 2, 3, 4] - new array

// IMPURE: Relies on external state
let discount = 0.1;

function calculatePriceImpure(price) {
    return price * (1 - discount);  // Uses external variable
}

// PURE: All dependencies explicit
function calculatePricePure(price, discount) {
    return price * (1 - discount);  // All inputs are parameters
}

console.log(calculatePricePure(100, 0.1));  // 90

// IMPURE: I/O operations
function logAndDouble(x) {
    console.log(`Value: ${x}`);  // Side effect: console output
    return x * 2;
}

// PURE: Separate logic from I/O
function double(x) {
    return x * 2;
}

function logValue(x) {
    console.log(`Value: ${x}`);
}

// Use together
const result = double(5);
logValue(result);

// IMPURE: Non-deterministic
function getCurrentTimestamp() {
    return Date.now();  // Different each call
}

// PURE: Pass time as parameter
function addHours(timestamp, hours) {
    return timestamp + (hours * 60 * 60 * 1000);
}

// Managing side effects pattern
class UserService {
    // Impure methods at boundaries
    async getUser(id) {
        const response = await fetch(`/api/users/${id}`);
        const data = await response.json();
        
        // Call pure function for transformation
        return this.transformUserData(data);
    }
    
    // Pure function - easy to test
    transformUserData(data) {
        return {
            id: data.id,
            name: data.name.toUpperCase(),
            age: data.age,
            isAdult: data.age >= 18
        };
    }
}

// Pure core, impure shell pattern
// Pure core - business logic
const calculateOrderTotal = (items, taxRate) => {
    const subtotal = items.reduce((sum, item) => 
        sum + (item.price * item.quantity), 0
    );
    const tax = subtotal * taxRate;
    return {
        subtotal,
        tax,
        total: subtotal + tax
    };
};

const validateOrder = (order) => {
    if (!order.items || order.items.length === 0) {
        return {valid: false, error: 'No items in order'};
    }
    
    if (order.items.some(item => item.price < 0)) {
        return {valid: false, error: 'Invalid price'};
    }
    
    return {valid: true};
};

// Impure shell - handles I/O
const processOrder = async (orderId) => {
    // Side effect: fetch from API
    const order = await fetch(`/api/orders/${orderId}`).then(r => r.json());
    
    // Pure: validate
    const validation = validateOrder(order);
    if (!validation.valid) {
        throw new Error(validation.error);
    }
    
    // Pure: calculate
    const total = calculateOrderTotal(order.items, 0.08);
    
    // Side effect: save to database
    await fetch(`/api/orders/${orderId}`, {
        method: 'PUT',
        body: JSON.stringify({...order, ...total})
    });
    
    // Side effect: send email
    await sendOrderConfirmation(order.customerId, total);
    
    return total;
};

// Dependency injection for testability
// Impure: hard to test
function getUserDataImpure(userId) {
    const user = database.query(`SELECT * FROM users WHERE id = ${userId}`);
    return user;
}

// Pure: inject dependencies
function getUserDataPure(userId, dbQuery) {
    return dbQuery(`SELECT * FROM users WHERE id = ${userId}`);
}

// Usage
const result2 = getUserDataPure(123, database.query);

// Testing
const mockQuery = (sql) => ({id: 123, name: 'Test'});
const testResult = getUserDataPure(123, mockQuery);

// Memoization for pure functions
function memoize(fn) {
    const cache = new Map();
    
    return function(...args) {
        const key = JSON.stringify(args);
        
        if (cache.has(key)) {
            return cache.get(key);
        }
        
        const result = fn(...args);
        cache.set(key, result);
        return result;
    };
}

// Works perfectly with pure functions
const expensivePureCalc = (n) => {
    console.log('Calculating...');
    return n * n;
};

const memoizedCalc = memoize(expensivePureCalc);

console.log(memoizedCalc(5));  // Calculating... 25
console.log(memoizedCalc(5));  // 25 (cached, no calculation)
Key Points: Pure functions have no side effects, return same output for same input. Side effects include mutation, I/O, DOM manipulation, network requests. Isolate side effects at program boundaries. Use dependency injection for testability. Pure functions are cacheable with memoization. Separate pure core (business logic) from impure shell (I/O).

23.2 Higher-Order Functions and Function Composition

Higher-Order Function Types

Type Description Example
Takes Function Accepts function as parameter map, filter, reduce
Returns Function Returns new function Factory functions, currying
Both Takes and returns functions Decorators, middleware

Common Higher-Order Functions

Function Purpose Returns
map(fn) Transform each element New array
filter(fn) Select elements by predicate Filtered array
reduce(fn, init) Accumulate to single value Accumulated value
forEach(fn) Execute for each element undefined
find(fn) Find first matching element Element or undefined
some(fn) Test if any match Boolean
every(fn) Test if all match Boolean

Function Composition Patterns

Pattern Direction Usage
compose Right-to-left compose(f, g, h)(x) = f(g(h(x)))
pipe Left-to-right pipe(f, g, h)(x) = h(g(f(x)))
chain Method chaining obj.method1().method2()

Example: Higher-order functions

// Higher-order function: takes function as argument
function repeat(n, action) {
    for (let i = 0; i < n; i++) {
        action(i);
    }
}

repeat(3, (i) => console.log(`Iteration ${i}`));

// Higher-order function: returns function
function greaterThan(n) {
    return (m) => m > n;
}

const greaterThan10 = greaterThan(10);
console.log(greaterThan10(15));  // true
console.log(greaterThan10(5));   // false

// Array methods as higher-order functions
const numbers = [1, 2, 3, 4, 5];

// map: transform
const doubled = numbers.map(x => x * 2);
console.log(doubled);  // [2, 4, 6, 8, 10]

// filter: select
const evens = numbers.filter(x => x % 2 === 0);
console.log(evens);  // [2, 4]

// reduce: accumulate
const sum = numbers.reduce((acc, x) => acc + x, 0);
console.log(sum);  // 15

// Complex reduce example
const users = [
    {name: 'John', age: 30, country: 'USA'},
    {name: 'Jane', age: 25, country: 'UK'},
    {name: 'Bob', age: 30, country: 'USA'}
];

const groupedByCountry = users.reduce((acc, user) => {
    if (!acc[user.country]) {
        acc[user.country] = [];
    }
    acc[user.country].push(user);
    return acc;
}, {});

console.log(groupedByCountry);
// {USA: [{John}, {Bob}], UK: [{Jane}]}

// Creating custom higher-order functions
function filter(array, predicate) {
    const result = [];
    for (const item of array) {
        if (predicate(item)) {
            result.push(item);
        }
    }
    return result;
}

function map(array, transform) {
    const result = [];
    for (const item of array) {
        result.push(transform(item));
    }
    return result;
}

// Function factory
function multiplier(factor) {
    return (number) => number * factor;
}

const double = multiplier(2);
const triple = multiplier(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

// Function that returns function with closure
function counter(start = 0) {
    let count = start;
    
    return {
        increment: () => ++count,
        decrement: () => --count,
        value: () => count
    };
}

const myCounter = counter(10);
console.log(myCounter.increment());  // 11
console.log(myCounter.increment());  // 12
console.log(myCounter.value());      // 12

// Decorator pattern with higher-order functions
function withLogging(fn) {
    return function(...args) {
        console.log(`Calling ${fn.name} with`, args);
        const result = fn(...args);
        console.log(`Result:`, result);
        return result;
    };
}

function withTiming(fn) {
    return function(...args) {
        const start = performance.now();
        const result = fn(...args);
        const end = performance.now();
        console.log(`${fn.name} took ${(end - start).toFixed(2)}ms`);
        return result;
    };
}

function add2(a, b) {
    return a + b;
}

const loggedAdd = withLogging(add2);
const timedAdd = withTiming(add2);

loggedAdd(2, 3);
timedAdd(2, 3);

// Stack decorators
const decoratedAdd = withLogging(withTiming(add2));
decoratedAdd(5, 10);

// Partial application helper
function partial(fn, ...fixedArgs) {
    return function(...remainingArgs) {
        return fn(...fixedArgs, ...remainingArgs);
    };
}

function greet(greeting, name) {
    return `${greeting}, ${name}!`;
}

const sayHello = partial(greet, 'Hello');
console.log(sayHello('John'));  // Hello, John!

// Once function - execute only once
function once(fn) {
    let called = false;
    let result;
    
    return function(...args) {
        if (!called) {
            called = true;
            result = fn(...args);
        }
        return result;
    };
}

const initialize = once(() => {
    console.log('Initializing...');
    return {initialized: true};
});

initialize();  // Initializing...
initialize();  // No output (already called)

Example: Function composition

// Compose: right-to-left execution
function compose(...fns) {
    return function(value) {
        return fns.reduceRight((acc, fn) => fn(acc), value);
    };
}

// Pipe: left-to-right execution
function pipe(...fns) {
    return function(value) {
        return fns.reduce((acc, fn) => fn(acc), value);
    };
}

// Example functions
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

// Using compose (right-to-left)
const composed = compose(square, double, addOne);
console.log(composed(3));  // square(double(addOne(3))) = square(8) = 64

// Using pipe (left-to-right)
const piped = pipe(addOne, double, square);
console.log(piped(3));  // square(double(addOne(3))) = square(8) = 64

// Real-world example: data transformation pipeline
const users2 = [
    {name: 'john doe', age: 17, active: true},
    {name: 'jane smith', age: 25, active: false},
    {name: 'bob jones', age: 30, active: true}
];

// Individual transformation functions
const filterActive = users => users.filter(u => u.active);
const filterAdults = users => users.filter(u => u.age >= 18);
const capitalizeNames = users => users.map(u => ({
    ...u,
    name: u.name.split(' ').map(word => 
        word.charAt(0).toUpperCase() + word.slice(1)
    ).join(' ')
}));
const extractNames = users => users.map(u => u.name);

// Compose pipeline
const processUsers = pipe(
    filterActive,
    filterAdults,
    capitalizeNames,
    extractNames
);

console.log(processUsers(users2));  // ['Bob Jones']

// Point-free style (no explicit arguments)
const isEven = x => x % 2 === 0;
const increment = x => x + 1;

// Instead of: numbers.filter(x => isEven(x)).map(x => increment(x))
// Point-free:
const processNumbers = pipe(
    arr => arr.filter(isEven),
    arr => arr.map(increment)
);

console.log(processNumbers([1, 2, 3, 4, 5]));  // [3, 5]

// Composable validation
const validateEmail = email => {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(email) ? {valid: true} : {valid: false, error: 'Invalid email'};
};

const validateLength = (min, max) => str => {
    return str.length >= min && str.length <= max
        ? {valid: true}
        : {valid: false, error: `Length must be ${min}-${max}`};
};

const validatePassword = pipe(
    validateLength(8, 20),
    result => result.valid ? 
        (/[A-Z]/.test(result) ? {valid: true} : {valid: false, error: 'Need uppercase'}) :
        result
);

// Composition with async functions
const asyncPipe = (...fns) => {
    return async (value) => {
        let result = value;
        for (const fn of fns) {
            result = await fn(result);
        }
        return result;
    };
};

const fetchUser = async (id) => {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
};

const enrichUser = async (user) => {
    const posts = await fetch(`/api/users/${user.id}/posts`).then(r => r.json());
    return {...user, posts};
};

const formatUser = (user) => ({
    name: user.name.toUpperCase(),
    postCount: user.posts.length
});

const getUserData = asyncPipe(
    fetchUser,
    enrichUser,
    formatUser
);

// await getUserData(123);

// Transducer pattern (advanced composition)
const mapTransducer = (transform) => (reducer) => {
    return (acc, value) => reducer(acc, transform(value));
};

const filterTransducer = (predicate) => (reducer) => {
    return (acc, value) => predicate(value) ? reducer(acc, value) : acc;
};

const transduce = (transducer, reducer, initial, collection) => {
    const xform = transducer(reducer);
    return collection.reduce(xform, initial);
};

// Compose transducers
const composeTransducers = (...transducers) => {
    return transducers.reduce((a, b) => (...args) => a(b(...args)));
};

// Usage
const xform = composeTransducers(
    mapTransducer(x => x * 2),
    filterTransducer(x => x > 5)
);

const result3 = transduce(
    xform,
    (acc, x) => acc.concat(x),
    [],
    [1, 2, 3, 4, 5]
);

console.log(result3);  // [6, 8, 10]
Key Points: Higher-order functions accept functions as arguments or return functions. Common examples: map, filter, reduce. Function composition combines functions: compose (right-to-left), pipe (left-to-right). Decorators add behavior by wrapping functions. Point-free style eliminates explicit arguments. Composition enables building complex operations from simple functions.

23.3 Currying and Partial Application

Currying vs Partial Application

Aspect Currying Partial Application
Definition Transform f(a,b,c) to f(a)(b)(c) Fix some arguments of function
Arguments One argument at a time Can fix multiple at once
Result Always returns function May return final result
Arity Reduces to unary functions Reduces arity by N

Currying Benefits

Benefit Description Use Case
Reusability Create specialized functions Configuration functions
Composition Easier function composition Building pipelines
Delayed Execution Apply arguments over time Event handlers, callbacks
Point-free Style Write without explicit arguments Cleaner code

Partial Application Techniques

Technique Method Example
bind Function.prototype.bind fn.bind(null, arg1, arg2)
Wrapper Function Manual wrapping (...args) => fn(fixed, ...args)
Helper Library Lodash/Ramda partial _.partial(fn, arg1)

Example: Currying

// Manual currying
function add3(a, b, c) {
    return a + b + c;
}

function curriedAdd(a) {
    return function(b) {
        return function(c) {
            return a + b + c;
        };
    };
}

// Usage
console.log(curriedAdd(1)(2)(3));  // 6

// Arrow function currying
const curriedAdd2 = a => b => c => a + b + c;
console.log(curriedAdd2(1)(2)(3));  // 6

// Generic curry function
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return function(...moreArgs) {
                return curried.apply(this, args.concat(moreArgs));
            };
        }
    };
}

// Usage
const add = (a, b, c) => a + b + c;
const curriedAddFunc = curry(add);

console.log(curriedAddFunc(1)(2)(3));      // 6
console.log(curriedAddFunc(1, 2)(3));      // 6
console.log(curriedAddFunc(1)(2, 3));      // 6
console.log(curriedAddFunc(1, 2, 3));      // 6

// Practical currying example: event handlers
const handleClick = curry((action, id, event) => {
    event.preventDefault();
    console.log(`Action: ${action}, ID: ${id}`);
});

// Create specialized handlers
const deleteHandler = handleClick('delete');
const editHandler = handleClick('edit');

// Use in event listeners
// element.addEventListener('click', deleteHandler(123));
// element.addEventListener('click', editHandler(456));

// Currying for configuration
const createLogger = level => prefix => message => {
    const timestamp = new Date().toISOString();
    console.log(`[${timestamp}] [${level}] ${prefix}: ${message}`);
};

const infoLogger = createLogger('INFO');
const errorLogger = createLogger('ERROR');

const appInfoLogger = infoLogger('App');
const dbInfoLogger = infoLogger('Database');

appInfoLogger('Application started');
dbInfoLogger('Connected to database');

// Currying for API calls
const fetchFromAPI = curry((baseUrl, endpoint, params) => {
    const url = new URL(endpoint, baseUrl);
    Object.keys(params).forEach(key => 
        url.searchParams.append(key, params[key])
    );
    return fetch(url.toString()).then(r => r.json());
});

const fetchFromMyAPI = fetchFromAPI('https://api.example.com');
const fetchUsers = fetchFromMyAPI('/users');
const fetchPosts = fetchFromMyAPI('/posts');

// await fetchUsers({page: 1, limit: 10});
// await fetchPosts({userId: 123});

// Currying for validation
const validate = curry((regex, message, value) => {
    return regex.test(value) 
        ? {valid: true} 
        : {valid: false, error: message};
});

const validateEmail = validate(
    /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    'Invalid email format'
);

const validatePhone = validate(
    /^\d{3}-\d{3}-\d{4}$/,
    'Invalid phone format'
);

console.log(validateEmail('user@example.com'));
console.log(validatePhone('555-123-4567'));

// Currying with multiple arguments preserved
const multiply = (a, b) => a * b;
const curriedMultiply = curry(multiply);

const double2 = curriedMultiply(2);
const triple2 = curriedMultiply(3);

console.log(double2(5));   // 10
console.log(triple2(5));   // 15

// Auto-currying with Proxy
function autoCurry(fn) {
    return new Proxy(fn, {
        apply(target, thisArg, args) {
            if (args.length >= target.length) {
                return target.apply(thisArg, args);
            }
            return autoCurry(target.bind(thisArg, ...args));
        }
    });
}

const sum = autoCurry((a, b, c, d) => a + b + c + d);

console.log(sum(1, 2, 3, 4));        // 10
console.log(sum(1)(2)(3)(4));        // 10
console.log(sum(1, 2)(3, 4));        // 10
console.log(sum(1)(2, 3)(4));        // 10

Example: Partial application

// Partial application with bind
function greet2(greeting, name) {
    return `${greeting}, ${name}!`;
}

const sayHello2 = greet2.bind(null, 'Hello');
console.log(sayHello2('John'));  // Hello, John!

const sayHi = greet2.bind(null, 'Hi');
console.log(sayHi('Jane'));  // Hi, Jane!

// Custom partial function
function partial2(fn, ...fixedArgs) {
    return function(...remainingArgs) {
        return fn(...fixedArgs, ...remainingArgs);
    };
}

function calculate(operation, a, b) {
    switch(operation) {
        case 'add': return a + b;
        case 'subtract': return a - b;
        case 'multiply': return a * b;
        case 'divide': return a / b;
    }
}

const add4 = partial2(calculate, 'add');
const subtract = partial2(calculate, 'subtract');

console.log(add4(5, 3));        // 8
console.log(subtract(10, 3));   // 7

// Partial from right (partial right)
function partialRight(fn, ...fixedArgs) {
    return function(...remainingArgs) {
        return fn(...remainingArgs, ...fixedArgs);
    };
}

function divide(a, b) {
    return a / b;
}

const divideBy2 = partialRight(divide, 2);
console.log(divideBy2(10));  // 5

// Placeholder support
const _ = Symbol('placeholder');

function partialWithPlaceholder(fn, ...fixedArgs) {
    return function(...remainingArgs) {
        const args = fixedArgs.map(arg => 
            arg === _ ? remainingArgs.shift() : arg
        );
        return fn(...args, ...remainingArgs);
    };
}

function formatDate(year, month, day) {
    return `${year}-${month}-${day}`;
}

const formatWithYear = partialWithPlaceholder(formatDate, 2024, _, _);
console.log(formatWithYear(12, 25));  // 2024-12-25

const formatDecember = partialWithPlaceholder(formatDate, _, 12, _);
console.log(formatDecember(2024, 25));  // 2024-12-25

// Practical example: Array methods with partial
const numbers2 = [1, 2, 3, 4, 5];

// Create reusable predicates
const isGreaterThan = (threshold, value) => value > threshold;
const multiplyBy = (factor, value) => value * factor;

const isGreaterThan3 = partial2(isGreaterThan, 3);
const multiplyBy10 = partial2(multiplyBy, 10);

console.log(numbers2.filter(isGreaterThan3));  // [4, 5]
console.log(numbers2.map(multiplyBy10));       // [10, 20, 30, 40, 50]

// Partial for event handlers
function handleEvent(eventType, selector, handler, event) {
    if (event.target.matches(selector)) {
        handler(event);
    }
}

const handleClick2 = partial2(handleEvent, 'click');
const handleButtonClick = partial2(handleClick2, 'button');

// Usage
// document.addEventListener('click', handleButtonClick((e) => {
//     console.log('Button clicked');
// }));

// Combining curry and partial
const log = (level, module, message) => {
    console.log(`[${level}] ${module}: ${message}`);
};

const curriedLog = curry(log);

// Create specialized loggers
const errorLog = curriedLog('ERROR');
const infoLog = curriedLog('INFO');

const appError = errorLog('App');
const dbInfo = infoLog('Database');

appError('Connection failed');
dbInfo('Query executed');

// Memoization with partial
function memoize2(fn) {
    const cache = new Map();
    
    return function(...args) {
        const key = JSON.stringify(args);
        if (cache.has(key)) {
            return cache.get(key);
        }
        const result = fn(...args);
        cache.set(key, result);
        return result;
    };
}

const expensiveCalc = (a, b, c) => {
    console.log('Computing...');
    return a + b + c;
};

const memoizedCalc2 = memoize2(expensiveCalc);

// Partially applied memoized function
const addTo10 = partial2(memoizedCalc2, 10);

console.log(addTo10(5, 5));   // Computing... 20
console.log(addTo10(5, 5));   // 20 (cached)

// Function application helper
const applyArgs = (...args) => fn => fn(...args);

const args = [1, 2, 3];
const result4 = applyArgs(...args)(add3);
console.log(result4);  // 6

// Flip function (reverse argument order)
function flip(fn) {
    return function(a, b) {
        return fn(b, a);
    };
}

const subtract2 = (a, b) => a - b;
const flippedSubtract = flip(subtract2);

console.log(subtract2(10, 3));          // 7
console.log(flippedSubtract(10, 3));    // -7
Key Points: Currying transforms multi-argument function to sequence of unary functions. Partial application fixes some arguments, returns function expecting remaining arguments. Currying enables point-free style and composition. Use bind or custom wrapper for partial application. Placeholder support allows flexible argument positioning. Both techniques create specialized, reusable functions.

23.4 Immutability and Functional Data Structures

Immutability Principles

Principle Description Benefit
No Mutation Never modify existing data Predictable state changes
Copy on Write Create new copy when updating Preserve history
Structural Sharing Share unchanged parts Memory efficiency
Persistent Data All versions accessible Time travel, undo/redo

Immutable Operations

Operation Mutable (Avoid) Immutable (Use)
Array Add arr.push(item) [...arr, item] or arr.concat(item)
Array Remove arr.splice(idx, 1) arr.filter((_, i) => i !== idx)
Array Update arr[i] = value arr.map((v, idx) => idx === i ? value : v)
Object Update obj.prop = value {...obj, prop: value}
Object Delete delete obj.prop const {prop, ...rest} = obj; return rest;
Object Merge Object.assign(obj, updates) {...obj, ...updates}

Immutability Benefits

Benefit Description Use Case
Predictability Data doesn't change unexpectedly Debugging, testing
Thread Safety Safe concurrent access Parallel processing
Change Detection Reference equality checks React shouldComponentUpdate
Time Travel Keep all previous states Undo/redo, debugging

Example: Immutable operations

// MUTABLE (Avoid)
const arr = [1, 2, 3];
arr.push(4);           // Modifies original
arr[0] = 10;           // Modifies original
arr.sort();            // Modifies original

const obj = {name: 'John', age: 30};
obj.age = 31;          // Modifies original
delete obj.name;       // Modifies original

// IMMUTABLE (Prefer)
const arr2 = [1, 2, 3];
const arr3 = [...arr2, 4];           // New array
const arr4 = arr2.map((v, i) => i === 0 ? 10 : v);  // New array
const arr5 = [...arr2].sort();       // New array

const obj2 = {name: 'John', age: 30};
const obj3 = {...obj2, age: 31};     // New object
const {name, ...obj4} = obj2;        // New object without 'name'

// Array immutable operations
const numbers3 = [1, 2, 3, 4, 5];

// Add item
const withSix = [...numbers3, 6];
const withZero = [0, ...numbers3];
const withMiddle = [...numbers3.slice(0, 2), 99, ...numbers3.slice(2)];

// Remove item
const withoutThree = numbers3.filter(n => n !== 3);
const withoutIndex2 = numbers3.filter((_, i) => i !== 2);

// Update item
const doubled = numbers3.map(n => n * 2);
const updateIndex1 = numbers3.map((n, i) => i === 1 ? 99 : n);

// Object immutable operations
const user = {
    name: 'John',
    age: 30,
    address: {
        city: 'NYC',
        country: 'USA'
    }
};

// Update property
const olderUser = {...user, age: 31};

// Add property
const userWithEmail = {...user, email: 'john@example.com'};

// Remove property
const {age, ...userWithoutAge} = user;

// Deep update (nested)
const movedUser = {
    ...user,
    address: {
        ...user.address,
        city: 'LA'
    }
};

// Nested updates helper
function updateNested(obj, path, value) {
    const [first, ...rest] = path;
    
    if (rest.length === 0) {
        return {...obj, [first]: value};
    }
    
    return {
        ...obj,
        [first]: updateNested(obj[first], rest, value)
    };
}

const updated = updateNested(user, ['address', 'city'], 'LA');
console.log(updated);
// {name: 'John', age: 30, address: {city: 'LA', country: 'USA'}}

// Immutable array operations helper
const immutableArray = {
    add: (arr, item) => [...arr, item],
    
    addAt: (arr, index, item) => [
        ...arr.slice(0, index),
        item,
        ...arr.slice(index)
    ],
    
    remove: (arr, index) => [
        ...arr.slice(0, index),
        ...arr.slice(index + 1)
    ],
    
    update: (arr, index, value) => 
        arr.map((v, i) => i === index ? value : v),
    
    updateWhere: (arr, predicate, updater) =>
        arr.map(item => predicate(item) ? updater(item) : item)
};

const items = [{id: 1, name: 'A'}, {id: 2, name: 'B'}];

const newItems = immutableArray.updateWhere(
    items,
    item => item.id === 1,
    item => ({...item, name: 'Updated A'})
);

console.log(newItems);

// Object.freeze for immutability
const frozen = Object.freeze({name: 'John', age: 30});
// frozen.age = 31;  // Silent fail in non-strict, error in strict

// Deep freeze
function deepFreeze(obj) {
    Object.freeze(obj);
    
    Object.getOwnPropertyNames(obj).forEach(prop => {
        if (obj[prop] !== null &&
            (typeof obj[prop] === 'object' || typeof obj[prop] === 'function') &&
            !Object.isFrozen(obj[prop])) {
            deepFreeze(obj[prop]);
        }
    });
    
    return obj;
}

const deepFrozen = deepFreeze({
    name: 'John',
    address: {city: 'NYC'}
});

// deepFrozen.address.city = 'LA';  // Error

// Immutable state management
class ImmutableState {
    constructor(initialState) {
        this.state = initialState;
        this.history = [initialState];
        this.currentIndex = 0;
    }
    
    setState(updates) {
        const newState = {...this.state, ...updates};
        
        // Remove any "future" states if we went back
        this.history = this.history.slice(0, this.currentIndex + 1);
        
        this.history.push(newState);
        this.currentIndex++;
        this.state = newState;
        
        return newState;
    }
    
    getState() {
        return this.state;
    }
    
    undo() {
        if (this.currentIndex > 0) {
            this.currentIndex--;
            this.state = this.history[this.currentIndex];
        }
        return this.state;
    }
    
    redo() {
        if (this.currentIndex < this.history.length - 1) {
            this.currentIndex++;
            this.state = this.history[this.currentIndex];
        }
        return this.state;
    }
    
    canUndo() {
        return this.currentIndex > 0;
    }
    
    canRedo() {
        return this.currentIndex < this.history.length - 1;
    }
}

// Usage
const state = new ImmutableState({count: 0, user: null});

state.setState({count: 1});
state.setState({count: 2});
state.setState({count: 3});

console.log(state.getState());  // {count: 3, user: null}
console.log(state.undo());      // {count: 2, user: null}
console.log(state.undo());      // {count: 1, user: null}
console.log(state.redo());      // {count: 2, user: null}

// Lens pattern for immutable updates
function lens(getter, setter) {
    return {
        get: getter,
        set: setter,
        over: (fn, obj) => setter(fn(getter(obj)), obj)
    };
}

const nameLens = lens(
    user => user.name,
    (name, user) => ({...user, name})
);

const cityLens = lens(
    user => user.address.city,
    (city, user) => ({
        ...user,
        address: {...user.address, city}
    })
);

const user2 = {name: 'John', address: {city: 'NYC'}};
const user3 = nameLens.set('Jane', user2);
const user4 = cityLens.over(city => city.toUpperCase(), user2);

console.log(user3);  // {name: 'Jane', address: {city: 'NYC'}}
console.log(user4);  // {name: 'John', address: {city: 'NYC'}}

Example: Functional data structures

// Immutable List
class List {
    constructor(head, tail = null) {
        this.head = head;
        this.tail = tail;
    }
    
    static empty() {
        return null;
    }
    
    static of(...values) {
        return values.reduceRight((tail, head) => 
            new List(head, tail), null
        );
    }
    
    prepend(value) {
        return new List(value, this);
    }
    
    map(fn) {
        if (this.tail === null) {
            return new List(fn(this.head));
        }
        return new List(fn(this.head), this.tail.map(fn));
    }
    
    filter(predicate) {
        if (!predicate(this.head)) {
            return this.tail ? this.tail.filter(predicate) : null;
        }
        return new List(
            this.head,
            this.tail ? this.tail.filter(predicate) : null
        );
    }
    
    toArray() {
        const result = [];
        let current = this;
        while (current !== null) {
            result.push(current.head);
            current = current.tail;
        }
        return result;
    }
}

// Usage
const list = List.of(1, 2, 3, 4, 5);
const doubled2 = list.map(x => x * 2);
const evens2 = list.filter(x => x % 2 === 0);

console.log(doubled2.toArray());  // [2, 4, 6, 8, 10]
console.log(evens2.toArray());    // [2, 4]

// Immutable Stack
class Stack {
    constructor(items = []) {
        this.items = Object.freeze([...items]);
    }
    
    push(item) {
        return new Stack([...this.items, item]);
    }
    
    pop() {
        if (this.items.length === 0) {
            return [null, this];
        }
        return [
            this.items[this.items.length - 1],
            new Stack(this.items.slice(0, -1))
        ];
    }
    
    peek() {
        return this.items[this.items.length - 1] || null;
    }
    
    isEmpty() {
        return this.items.length === 0;
    }
    
    size() {
        return this.items.length;
    }
}

// Usage
const stack1 = new Stack();
const stack2 = stack1.push(1);
const stack3 = stack2.push(2).push(3);

const [value, stack4] = stack3.pop();
console.log(value);           // 3
console.log(stack4.peek());   // 2
console.log(stack3.peek());   // 3 (original unchanged)

// Immutable Queue
class Queue {
    constructor(front = [], back = []) {
        this.front = Object.freeze([...front]);
        this.back = Object.freeze([...back]);
    }
    
    enqueue(item) {
        return new Queue(this.front, [...this.back, item]);
    }
    
    dequeue() {
        if (this.front.length === 0 && this.back.length === 0) {
            return [null, this];
        }
        
        if (this.front.length === 0) {
            const [first, ...rest] = this.back.reverse();
            return [first, new Queue(rest, [])];
        }
        
        const [first, ...rest] = this.front;
        return [first, new Queue(rest, this.back)];
    }
    
    peek() {
        if (this.front.length > 0) {
            return this.front[0];
        }
        return this.back[this.back.length - 1] || null;
    }
    
    isEmpty() {
        return this.front.length === 0 && this.back.length === 0;
    }
}

// Usage
const queue1 = new Queue();
const queue2 = queue1.enqueue(1).enqueue(2).enqueue(3);

const [val1, queue3] = queue2.dequeue();
const [val2, queue4] = queue3.dequeue();

console.log(val1);  // 1
console.log(val2);  // 2

// Immutable Tree
class TreeNode {
    constructor(value, left = null, right = null) {
        this.value = value;
        this.left = left;
        this.right = right;
    }
    
    insert(value) {
        if (value < this.value) {
            return new TreeNode(
                this.value,
                this.left ? this.left.insert(value) : new TreeNode(value),
                this.right
            );
        } else {
            return new TreeNode(
                this.value,
                this.left,
                this.right ? this.right.insert(value) : new TreeNode(value)
            );
        }
    }
    
    contains(value) {
        if (value === this.value) return true;
        if (value < this.value) return this.left ? this.left.contains(value) : false;
        return this.right ? this.right.contains(value) : false;
    }
    
    map(fn) {
        return new TreeNode(
            fn(this.value),
            this.left ? this.left.map(fn) : null,
            this.right ? this.right.map(fn) : null
        );
    }
}

// Usage
const tree1 = new TreeNode(5);
const tree2 = tree1.insert(3).insert(7).insert(1).insert(9);

console.log(tree2.contains(7));   // true
console.log(tree2.contains(10));  // false
console.log(tree1.contains(7));   // false (original unchanged)

// Record type with immutable updates
class Record {
    constructor(data) {
        Object.entries(data).forEach(([key, value]) => {
            Object.defineProperty(this, key, {
                value,
                writable: false,
                enumerable: true
            });
        });
        Object.freeze(this);
    }
    
    set(key, value) {
        return new Record({...this, [key]: value});
    }
    
    merge(updates) {
        return new Record({...this, ...updates});
    }
}

// Usage
const person = new Record({name: 'John', age: 30});
const older = person.set('age', 31);
const updated2 = person.merge({age: 31, city: 'NYC'});

console.log(person);    // Record {name: 'John', age: 30}
console.log(older);     // Record {name: 'John', age: 31}
console.log(updated2);  // Record {name: 'John', age: 31, city: 'NYC'}
Key Points: Immutability means never modifying existing data. Use spread operator, array methods (map, filter, concat) for immutable updates. Object.freeze() prevents mutations. Deep freeze for nested objects. Immutable data enables time travel (undo/redo), change detection, predictable state. Functional data structures (List, Stack, Queue, Tree) use structural sharing.

23.5 Monads and Functional Error Handling

Monad Characteristics

Characteristic Description Law
Container Wraps a value Type constructor
of/return Put value in container Left identity
map/fmap Transform value inside Functor law
flatMap/bind Chain operations Right identity, associativity

Common Monads

Monad Purpose Use Case
Maybe/Option Handle null/undefined Nullable values
Either/Result Handle success/failure Error handling
Promise Handle async operations Async workflows
Array Handle multiple values Non-determinism
IO Handle side effects Lazy evaluation

Functional Error Handling Patterns

Pattern Approach Benefit
Maybe null/undefined as type Explicit optionality
Either Left (error) or Right (success) Error as value
Result Ok or Err variant Type-safe errors
Validation Accumulate errors Multiple validations

Example: Maybe monad

// Maybe Monad - handles null/undefined
class Maybe {
    constructor(value) {
        this.value = value;
    }
    
    static of(value) {
        return new Maybe(value);
    }
    
    static nothing() {
        return new Maybe(null);
    }
    
    isNothing() {
        return this.value === null || this.value === undefined;
    }
    
    map(fn) {
        return this.isNothing() ? Maybe.nothing() : Maybe.of(fn(this.value));
    }
    
    flatMap(fn) {
        return this.isNothing() ? Maybe.nothing() : fn(this.value);
    }
    
    getOrElse(defaultValue) {
        return this.isNothing() ? defaultValue : this.value;
    }
    
    filter(predicate) {
        return this.isNothing() || !predicate(this.value)
            ? Maybe.nothing()
            : this;
    }
}

// Usage
const safeDivide = (a, b) => 
    b === 0 ? Maybe.nothing() : Maybe.of(a / b);

const result5 = safeDivide(10, 2)
    .map(x => x * 2)
    .map(x => x + 5)
    .getOrElse(0);

console.log(result5);  // 15

const result6 = safeDivide(10, 0)
    .map(x => x * 2)  // Not executed
    .map(x => x + 5)  // Not executed
    .getOrElse(0);

console.log(result6);  // 0

// Safe property access
const getNestedProp = (obj, path) => {
    return path.reduce((maybe, prop) => 
        maybe.flatMap(o => Maybe.of(o[prop])),
        Maybe.of(obj)
    );
};

const user5 = {
    name: 'John',
    address: {
        city: 'NYC'
    }
};

const city = getNestedProp(user5, ['address', 'city']).getOrElse('Unknown');
const zip = getNestedProp(user5, ['address', 'zip']).getOrElse('Unknown');

console.log(city);  // NYC
console.log(zip);   // Unknown

// Maybe with find
const findUser = (id) => {
    const users3 = [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}];
    const user = users3.find(u => u.id === id);
    return user ? Maybe.of(user) : Maybe.nothing();
};

const userName = findUser(1)
    .map(u => u.name)
    .map(name => name.toUpperCase())
    .getOrElse('Not Found');

console.log(userName);  // JOHN

const missing = findUser(999)
    .map(u => u.name)
    .getOrElse('Not Found');

console.log(missing);  // Not Found

Example: Either monad and Result pattern

// Either Monad - Left (error) or Right (success)
class Either {
    constructor(value, isLeft = false) {
        this.value = value;
        this.isLeft = isLeft;
    }
    
    static left(value) {
        return new Either(value, true);
    }
    
    static right(value) {
        return new Either(value, false);
    }
    
    map(fn) {
        return this.isLeft ? this : Either.right(fn(this.value));
    }
    
    flatMap(fn) {
        return this.isLeft ? this : fn(this.value);
    }
    
    mapLeft(fn) {
        return this.isLeft ? Either.left(fn(this.value)) : this;
    }
    
    fold(leftFn, rightFn) {
        return this.isLeft ? leftFn(this.value) : rightFn(this.value);
    }
    
    getOrElse(defaultValue) {
        return this.isLeft ? defaultValue : this.value;
    }
}

// Usage with validation
const validateAge = (age) => {
    if (typeof age !== 'number') {
        return Either.left('Age must be a number');
    }
    if (age < 0) {
        return Either.left('Age must be positive');
    }
    if (age < 18) {
        return Either.left('Must be 18 or older');
    }
    return Either.right(age);
};

const result7 = validateAge(25)
    .map(age => age * 2)
    .fold(
        error => `Error: ${error}`,
        value => `Success: ${value}`
    );

console.log(result7);  // Success: 50

const result8 = validateAge(15)
    .map(age => age * 2)  // Not executed
    .fold(
        error => `Error: ${error}`,
        value => `Success: ${value}`
    );

console.log(result8);  // Error: Must be 18 or older

// Result pattern (Rust-inspired)
class Result {
    static ok(value) {
        return {
            isOk: true,
            isErr: false,
            value,
            error: null
        };
    }
    
    static err(error) {
        return {
            isOk: false,
            isErr: true,
            value: null,
            error
        };
    }
}

// Usage
function parseJSON(str) {
    try {
        return Result.ok(JSON.parse(str));
    } catch (error) {
        return Result.err(error.message);
    }
}

const validJSON = parseJSON('{"name": "John"}');
console.log(validJSON);  // {isOk: true, value: {name: 'John'}, ...}

const invalidJSON = parseJSON('invalid json');
console.log(invalidJSON);  // {isErr: true, error: '...', ...}

// Chain Result operations
function divideResult(a, b) {
    return b === 0
        ? Result.err('Division by zero')
        : Result.ok(a / b);
}

function sqrtResult(n) {
    return n < 0
        ? Result.err('Cannot sqrt negative number')
        : Result.ok(Math.sqrt(n));
}

function calculate(a, b) {
    const divResult = divideResult(a, b);
    
    if (divResult.isErr) {
        return divResult;
    }
    
    return sqrtResult(divResult.value);
}

console.log(calculate(16, 4));  // {isOk: true, value: 2}
console.log(calculate(16, 0));  // {isErr: true, error: 'Division by zero'}
console.log(calculate(-16, 4)); // {isErr: true, error: 'Cannot sqrt negative...'}

// Try-catch wrapper
function tryCatch(fn) {
    return (...args) => {
        try {
            return Either.right(fn(...args));
        } catch (error) {
            return Either.left(error);
        }
    };
}

const safeJSONParse = tryCatch(JSON.parse);

const result9 = safeJSONParse('{"valid": true}');
console.log(result9.fold(
    err => `Parse error: ${err.message}`,
    data => `Parsed: ${JSON.stringify(data)}`
));

// Validation with error accumulation
class Validation {
    constructor(value, errors = []) {
        this.value = value;
        this.errors = errors;
    }
    
    static success(value) {
        return new Validation(value, []);
    }
    
    static failure(error) {
        return new Validation(null, [error]);
    }
    
    isValid() {
        return this.errors.length === 0;
    }
    
    map(fn) {
        return this.isValid()
            ? Validation.success(fn(this.value))
            : this;
    }
    
    chain(fn) {
        if (!this.isValid()) {
            return this;
        }
        
        const result = fn(this.value);
        return new Validation(result.value, [...this.errors, ...result.errors]);
    }
}

// Validate user input
function validateUser(data) {
    const errors = [];
    
    if (!data.email || !data.email.includes('@')) {
        errors.push('Invalid email');
    }
    
    if (!data.password || data.password.length < 8) {
        errors.push('Password must be at least 8 characters');
    }
    
    if (!data.age || data.age < 18) {
        errors.push('Must be 18 or older');
    }
    
    return errors.length === 0
        ? Validation.success(data)
        : new Validation(null, errors);
}

const validation1 = validateUser({
    email: 'john@example.com',
    password: 'secret123',
    age: 25
});
console.log(validation1);  // {value: {...}, errors: []}

const validation2 = validateUser({
    email: 'invalid',
    password: 'short',
    age: 15
});
console.log(validation2);  
// {value: null, errors: ['Invalid email', 'Password...', 'Must be 18...']}
Key Points: Monads are containers with map and flatMap methods. Maybe monad handles null/undefined safely. Either monad represents success (Right) or failure (Left). Result pattern from Rust (Ok/Err). Validation monad accumulates errors. Functional error handling treats errors as values, not exceptions. Railway-oriented programming with Either/Result enables composable error handling.

23.6 Function Pipelines and Data Transformation

Pipeline Patterns

Pattern Flow Usage
Pipe Left-to-right data |> fn1 |> fn2 |> fn3
Compose Right-to-left fn3(fn2(fn1(data)))
Method Chain Sequential methods obj.method1().method2()
Transducer Composed transformations Single pass over data

Data Transformation Operations

Operation Purpose Example
Map Transform each element arr.map(x => x * 2)
Filter Select elements arr.filter(x => x > 5)
Reduce Aggregate to single value arr.reduce((sum, x) => sum + x)
FlatMap Map then flatten arr.flatMap(x => [x, x*2])
GroupBy Group by property Custom reduce
Partition Split by predicate [pass, fail] arrays

Pipeline Benefits

Benefit Description Impact
Readability Sequential, declarative Easier to understand
Composability Build from small functions Reusable components
Testability Test each step independently Better test coverage
Maintainability Easy to add/remove steps Flexible architecture

Example: Pipeline operators and data transformation

// Pipe function (left-to-right)
const pipe2 = (...fns) => (value) =>
    fns.reduce((acc, fn) => fn(acc), value);

// Example: Process user data
const users4 = [
    {name: 'john doe', age: 17, active: true, score: 85},
    {name: 'jane smith', age: 25, active: false, score: 92},
    {name: 'bob jones', age: 30, active: true, score: 78},
    {name: 'alice brown', age: 22, active: true, score: 95}
];

// Small, focused functions
const filterActive2 = users => users.filter(u => u.active);
const filterAdults2 = users => users.filter(u => u.age >= 18);
const sortByScore = users => [...users].sort((a, b) => b.score - a.score);
const takeFive = users => users.slice(0, 5);
const capitalizeNames2 = users => users.map(u => ({
    ...u,
    name: u.name.split(' ').map(w => 
        w.charAt(0).toUpperCase() + w.slice(1)
    ).join(' ')
}));
const extractNames2 = users => users.map(u => u.name);

// Build pipeline
const processUsers2 = pipe2(
    filterActive2,
    filterAdults2,
    sortByScore,
    takeFive,
    capitalizeNames2,
    extractNames2
);

console.log(processUsers2(users4));
// ['Alice Brown', 'Bob Jones']

// Alternative: Point-free style
const getTopActiveAdultNames = pipe2(
    filterActive2,
    filterAdults2,
    sortByScore,
    takeFive,
    capitalizeNames2,
    extractNames2
);

// Data transformation pipeline
const numbers4 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const transform = pipe2(
    arr => arr.filter(x => x % 2 === 0),
    arr => arr.map(x => x * x),
    arr => arr.reduce((sum, x) => sum + x, 0),
    sum => Math.sqrt(sum)
);

console.log(transform(numbers4));  // Square root of sum of squares of evens

// Async pipeline
const asyncPipe2 = (...fns) => async (value) => {
    let result = value;
    for (const fn of fns) {
        result = await fn(result);
    }
    return result;
};

const fetchUserData = async (id) => {
    // Simulated fetch
    return {id, name: 'John', posts: []};
};

const enrichWithPosts = async (user) => {
    // Simulated fetch
    const posts = [{id: 1, title: 'Post 1'}];
    return {...user, posts};
};

const formatUserData = (user) => ({
    username: user.name.toUpperCase(),
    postCount: user.posts.length
});

const getUserInfo = asyncPipe2(
    fetchUserData,
    enrichWithPosts,
    formatUserData
);

// await getUserInfo(123);

// Tap function for debugging
const tap = (fn) => (value) => {
    fn(value);
    return value;
};

const debugPipeline = pipe2(
    filterActive2,
    tap(data => console.log('After filter:', data.length)),
    filterAdults2,
    tap(data => console.log('After adults filter:', data.length)),
    sortByScore,
    tap(data => console.log('After sort:', data[0]))
);

// GroupBy implementation
const groupBy = (key) => (array) => {
    return array.reduce((groups, item) => {
        const group = item[key];
        if (!groups[group]) {
            groups[group] = [];
        }
        groups[group].push(item);
        return groups;
    }, {});
};

const byAge = groupBy('age');
const grouped = byAge(users4);
console.log(grouped);

// Partition function
const partition = (predicate) => (array) => {
    return array.reduce(
        ([pass, fail], item) => 
            predicate(item) 
                ? [[...pass, item], fail]
                : [pass, [...fail, item]],
        [[], []]
    );
};

const [adults, minors] = partition(u => u.age >= 18)(users4);
console.log('Adults:', adults.length);
console.log('Minors:', minors.length);

// Map with index
const mapWithIndex = (fn) => (array) =>
    array.map((item, index) => fn(item, index));

const addIndex = mapWithIndex((item, index) => ({
    ...item,
    index
}));

// Chunk array
const chunk = (size) => (array) => {
    const chunks = [];
    for (let i = 0; i < array.length; i += size) {
        chunks.push(array.slice(i, i + size));
    }
    return chunks;
};

const chunked = chunk(3)([1, 2, 3, 4, 5, 6, 7, 8]);
console.log(chunked);  // [[1,2,3], [4,5,6], [7,8]]

// Flatten array
const flatten = (array) => 
    array.reduce((acc, val) => 
        Array.isArray(val) ? acc.concat(flatten(val)) : acc.concat(val),
    []);

const nested = [1, [2, [3, 4], 5], 6];
console.log(flatten(nested));  // [1, 2, 3, 4, 5, 6]

// Unique values
const unique = (array) => [...new Set(array)];

const duplicates = [1, 2, 2, 3, 3, 3, 4];
console.log(unique(duplicates));  // [1, 2, 3, 4]

// Pluck property
const pluck = (prop) => (array) => 
    array.map(item => item[prop]);

const names = pluck('name')(users4);
console.log(names);

// Complex transformation pipeline
const processOrders = pipe2(
    // Filter valid orders
    orders => orders.filter(o => o.items.length > 0),
    // Add total
    orders => orders.map(o => ({
        ...o,
        total: o.items.reduce((sum, item) => 
            sum + (item.price * item.quantity), 0
        )
    })),
    // Filter high value
    orders => orders.filter(o => o.total > 100),
    // Sort by total
    orders => [...orders].sort((a, b) => b.total - a.total),
    // Group by customer
    groupBy('customerId')
);

const orders = [
    {customerId: 1, items: [{price: 10, quantity: 2}]},
    {customerId: 1, items: [{price: 50, quantity: 3}]},
    {customerId: 2, items: [{price: 20, quantity: 1}]},
    {customerId: 2, items: [{price: 100, quantity: 2}]}
];

console.log(processOrders(orders));

// Memoized pipeline
const memoizePipeline = (pipeline) => {
    const cache = new Map();
    
    return (input) => {
        const key = JSON.stringify(input);
        if (cache.has(key)) {
            return cache.get(key);
        }
        const result = pipeline(input);
        cache.set(key, result);
        return result;
    };
};

const expensivePipeline = memoizePipeline(
    pipe2(
        arr => arr.map(x => x * x),
        arr => arr.filter(x => x > 10),
        arr => arr.reduce((sum, x) => sum + x, 0)
    )
);

console.log(expensivePipeline([1, 2, 3, 4, 5]));  // Computed
console.log(expensivePipeline([1, 2, 3, 4, 5]));  // Cached
Key Points: Function pipelines compose small functions into complex transformations. Pipe (left-to-right) vs compose (right-to-left). Common operations: map, filter, reduce, flatMap, groupBy, partition. Benefits: readability, testability, maintainability. Use tap for debugging. Async pipelines for async operations. Memoize pipelines for performance. Build complex data transformations from simple, pure functions.

Section 23 Summary: Functional Programming Concepts

  • Pure Functions: Deterministic, no side effects, same input → same output, easier to test and reason about
  • Side Effects: Mutation, I/O, DOM, network, random - isolate at program boundaries, use pure core/impure shell
  • Higher-Order Functions: Accept functions as arguments or return functions (map, filter, reduce, decorators)
  • Function Composition: Combine functions with pipe (left-to-right) or compose (right-to-left)
  • Currying: Transform f(a,b,c) to f(a)(b)(c), one argument at a time, enables partial application
  • Partial Application: Fix some arguments, return function expecting remaining args, use bind or custom wrapper
  • Immutability: Never modify data, use spread/map/filter for updates, Object.freeze, functional data structures
  • Data Structures: Immutable List, Stack, Queue, Tree - structural sharing for efficiency
  • Monads: Containers with map/flatMap - Maybe (null handling), Either (error handling), Result pattern
  • Error Handling: Errors as values not exceptions, Either for success/failure, Validation for error accumulation
  • Pipelines: Compose transformations (map, filter, reduce, groupBy, partition) for data processing
  • Benefits: Functional programming enables predictability, testability, composability, maintainability, fewer bugs

24. Testing and Code Quality

24.1 Unit Testing Strategies and Test Structure

Unit Test Principles

Principle Description Benefit
Isolation Test one unit independently Pinpoint failures
Fast Run quickly (<100ms each) Rapid feedback
Repeatable Same result every time Reliable CI/CD
Self-validating Pass or fail, no manual check Automation friendly
Timely Written before/with code Better design

Test Structure (AAA Pattern)

Phase Purpose Activities
Arrange Setup test preconditions Create objects, set state, prepare inputs
Act Execute the code under test Call method, trigger action
Assert Verify expected outcome Check results, validate state

Test Naming Conventions

Pattern Format Example
MethodName_StateUnderTest_ExpectedBehavior method_condition_result divide_byZero_throwsError
should_ExpectedBehavior_When_StateUnderTest should_result_when_condition should_throwError_when_dividingByZero
Given_When_Then given_when_then givenEmptyCart_whenAddingItem_thenCartHasOneItem
Descriptive sentence Natural language returns sum of two numbers

Test Organization

Level Purpose Syntax
describe Group related tests describe('Calculator', () => {})
it/test Individual test case it('adds two numbers', () => {})
beforeEach Setup before each test beforeEach(() => {})
afterEach Cleanup after each test afterEach(() => {})
beforeAll Setup once before all tests beforeAll(() => {})
afterAll Cleanup once after all tests afterAll(() => {})

Example: Unit test structure

// Function to test
function add(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new Error('Arguments must be numbers');
    }
    return a + b;
}

function divide(a, b) {
    if (b === 0) {
        throw new Error('Division by zero');
    }
    return a / b;
}

// Unit tests using Jest/Vitest syntax
describe('Math operations', () => {
    // Test suite for add function
    describe('add', () => {
        // AAA Pattern: Arrange, Act, Assert
        it('should add two positive numbers', () => {
            // Arrange
            const a = 2;
            const b = 3;
            
            // Act
            const result = add(a, b);
            
            // Assert
            expect(result).toBe(5);
        });
        
        it('should add negative numbers', () => {
            expect(add(-2, -3)).toBe(-5);
        });
        
        it('should handle zero', () => {
            expect(add(5, 0)).toBe(5);
            expect(add(0, 5)).toBe(5);
        });
        
        it('should throw error for non-number inputs', () => {
            expect(() => add('2', 3)).toThrow('Arguments must be numbers');
            expect(() => add(2, null)).toThrow('Arguments must be numbers');
        });
    });
    
    // Test suite for divide function
    describe('divide', () => {
        it('should divide two numbers', () => {
            expect(divide(10, 2)).toBe(5);
        });
        
        it('should handle decimals', () => {
            expect(divide(5, 2)).toBe(2.5);
        });
        
        it('should throw error when dividing by zero', () => {
            expect(() => divide(10, 0)).toThrow('Division by zero');
        });
    });
});

// Testing a class
class Calculator {
    constructor() {
        this.result = 0;
    }
    
    add(n) {
        this.result += n;
        return this;
    }
    
    subtract(n) {
        this.result -= n;
        return this;
    }
    
    multiply(n) {
        this.result *= n;
        return this;
    }
    
    getResult() {
        return this.result;
    }
    
    clear() {
        this.result = 0;
        return this;
    }
}

describe('Calculator', () => {
    let calculator;
    
    // Setup before each test
    beforeEach(() => {
        calculator = new Calculator();
    });
    
    // Cleanup after each test (if needed)
    afterEach(() => {
        calculator = null;
    });
    
    describe('add', () => {
        it('should add number to result', () => {
            calculator.add(5);
            expect(calculator.getResult()).toBe(5);
        });
        
        it('should support method chaining', () => {
            calculator.add(5).add(3);
            expect(calculator.getResult()).toBe(8);
        });
    });
    
    describe('subtract', () => {
        it('should subtract number from result', () => {
            calculator.add(10).subtract(3);
            expect(calculator.getResult()).toBe(7);
        });
    });
    
    describe('multiply', () => {
        it('should multiply result by number', () => {
            calculator.add(5).multiply(3);
            expect(calculator.getResult()).toBe(15);
        });
    });
    
    describe('clear', () => {
        it('should reset result to zero', () => {
            calculator.add(10).clear();
            expect(calculator.getResult()).toBe(0);
        });
    });
    
    describe('complex operations', () => {
        it('should handle chained operations', () => {
            calculator
                .add(10)
                .subtract(3)
                .multiply(2);
            
            expect(calculator.getResult()).toBe(14);
        });
    });
});

// Testing async functions
async function fetchUser(id) {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
        throw new Error('User not found');
    }
    return response.json();
}

describe('fetchUser', () => {
    it('should fetch user data', async () => {
        // Mock fetch
        global.fetch = jest.fn(() =>
            Promise.resolve({
                ok: true,
                json: () => Promise.resolve({id: 1, name: 'John'})
            })
        );
        
        const user = await fetchUser(1);
        
        expect(user).toEqual({id: 1, name: 'John'});
        expect(fetch).toHaveBeenCalledWith('/api/users/1');
    });
    
    it('should throw error when user not found', async () => {
        global.fetch = jest.fn(() =>
            Promise.resolve({
                ok: false
            })
        );
        
        await expect(fetchUser(999)).rejects.toThrow('User not found');
    });
});

// Parameterized tests (test.each)
describe('add with multiple inputs', () => {
    test.each([
        [1, 1, 2],
        [2, 3, 5],
        [5, -5, 0],
        [-10, -20, -30]
    ])('add(%i, %i) should return %i', (a, b, expected) => {
        expect(add(a, b)).toBe(expected);
    });
});

// Testing edge cases
describe('Edge cases', () => {
    it('should handle large numbers', () => {
        expect(add(Number.MAX_SAFE_INTEGER, 1))
            .toBe(Number.MAX_SAFE_INTEGER + 1);
    });
    
    it('should handle floating point precision', () => {
        expect(add(0.1, 0.2)).toBeCloseTo(0.3);
    });
    
    it('should handle negative zero', () => {
        expect(add(-0, 0)).toBe(0);
    });
});

// Snapshot testing (for objects/arrays)
describe('User object', () => {
    it('should match snapshot', () => {
        const user = {
            id: 1,
            name: 'John',
            email: 'john@example.com',
            createdAt: new Date('2024-01-01')
        };
        
        expect(user).toMatchSnapshot();
    });
});

// Testing with setup and teardown
describe('Database operations', () => {
    let db;
    
    beforeAll(async () => {
        // Setup: connect to test database
        db = await connectToTestDB();
    });
    
    afterAll(async () => {
        // Teardown: disconnect from database
        await db.disconnect();
    });
    
    beforeEach(async () => {
        // Reset database state before each test
        await db.clear();
    });
    
    it('should insert user', async () => {
        const user = await db.users.insert({name: 'John'});
        expect(user.id).toBeDefined();
        expect(user.name).toBe('John');
    });
    
    it('should find user by id', async () => {
        const inserted = await db.users.insert({name: 'Jane'});
        const found = await db.users.findById(inserted.id);
        expect(found).toEqual(inserted);
    });
});

// Skip and only for focused testing
describe('Feature tests', () => {
    it('should run this test', () => {
        expect(true).toBe(true);
    });
    
    it.skip('should skip this test', () => {
        // This test will be skipped
        expect(false).toBe(true);
    });
    
    it.only('should only run this test', () => {
        // Only this test will run (useful for debugging)
        expect(true).toBe(true);
    });
});
Key Points: Unit tests test individual units in isolation. Follow AAA pattern: Arrange, Act, Assert. Use descriptive test names. Organize with describe/it blocks. Setup/teardown with beforeEach/afterEach. Test edge cases, error conditions. Use test.each for parameterized tests. Keep tests fast (<100ms), repeatable, independent. One assertion per test (generally).

24.2 Mocking and Stubbing Techniques

Test Doubles Types

Type Purpose Usage
Mock Verify interactions Track calls, arguments, return values
Stub Provide predefined responses Return fixed values
Spy Wrap real function Track calls but execute real code
Fake Working implementation Simplified version (in-memory DB)
Dummy Placeholder Passed but never used

Jest Mock Methods

Method Purpose Example
jest.fn() Create mock function const mock = jest.fn()
jest.spyOn() Spy on existing method jest.spyOn(obj, 'method')
mockReturnValue() Set return value mock.mockReturnValue(42)
mockResolvedValue() Resolve with value mock.mockResolvedValue(data)
mockRejectedValue() Reject with error mock.mockRejectedValue(error)
mockImplementation() Custom implementation mock.mockImplementation(fn)

Mock Assertions

Assertion Purpose Usage
toHaveBeenCalled() Verify called expect(mock).toHaveBeenCalled()
toHaveBeenCalledTimes() Verify call count expect(mock).toHaveBeenCalledTimes(3)
toHaveBeenCalledWith() Verify arguments expect(mock).toHaveBeenCalledWith(arg1, arg2)
toHaveBeenLastCalledWith() Verify last call arguments expect(mock).toHaveBeenLastCalledWith(arg)
toHaveReturnedWith() Verify return value expect(mock).toHaveReturnedWith(42)

Example: Mocking and stubbing

// Function using dependencies
class UserService {
    constructor(api, logger) {
        this.api = api;
        this.logger = logger;
    }
    
    async getUser(id) {
        this.logger.log(`Fetching user ${id}`);
        
        try {
            const user = await this.api.get(`/users/${id}`);
            this.logger.log(`User ${id} fetched successfully`);
            return user;
        } catch (error) {
            this.logger.error(`Failed to fetch user ${id}`, error);
            throw error;
        }
    }
    
    async createUser(data) {
        this.logger.log('Creating user');
        return this.api.post('/users', data);
    }
}

// Testing with mocks
describe('UserService', () => {
    let userService;
    let mockApi;
    let mockLogger;
    
    beforeEach(() => {
        // Create mock objects
        mockApi = {
            get: jest.fn(),
            post: jest.fn()
        };
        
        mockLogger = {
            log: jest.fn(),
            error: jest.fn()
        };
        
        userService = new UserService(mockApi, mockLogger);
    });
    
    describe('getUser', () => {
        it('should fetch user from API', async () => {
            // Arrange
            const mockUser = {id: 1, name: 'John'};
            mockApi.get.mockResolvedValue(mockUser);
            
            // Act
            const result = await userService.getUser(1);
            
            // Assert
            expect(result).toEqual(mockUser);
            expect(mockApi.get).toHaveBeenCalledWith('/users/1');
            expect(mockLogger.log).toHaveBeenCalledWith('Fetching user 1');
            expect(mockLogger.log).toHaveBeenCalledWith('User 1 fetched successfully');
        });
        
        it('should log error when fetch fails', async () => {
            // Arrange
            const error = new Error('Network error');
            mockApi.get.mockRejectedValue(error);
            
            // Act & Assert
            await expect(userService.getUser(1)).rejects.toThrow('Network error');
            expect(mockLogger.error).toHaveBeenCalledWith(
                'Failed to fetch user 1',
                error
            );
        });
    });
    
    describe('createUser', () => {
        it('should create user via API', async () => {
            const userData = {name: 'Jane', email: 'jane@example.com'};
            const createdUser = {id: 2, ...userData};
            
            mockApi.post.mockResolvedValue(createdUser);
            
            const result = await userService.createUser(userData);
            
            expect(result).toEqual(createdUser);
            expect(mockApi.post).toHaveBeenCalledWith('/users', userData);
            expect(mockLogger.log).toHaveBeenCalledWith('Creating user');
        });
    });
});

// Spying on existing methods
describe('Math operations with spy', () => {
    it('should spy on Math.random', () => {
        const spy = jest.spyOn(Math, 'random').mockReturnValue(0.5);
        
        const result = Math.random();
        
        expect(result).toBe(0.5);
        expect(spy).toHaveBeenCalled();
        
        spy.mockRestore(); // Restore original implementation
    });
    
    it('should spy on console.log', () => {
        const spy = jest.spyOn(console, 'log').mockImplementation();
        
        console.log('Hello');
        console.log('World');
        
        expect(spy).toHaveBeenCalledTimes(2);
        expect(spy).toHaveBeenCalledWith('Hello');
        expect(spy).toHaveBeenCalledWith('World');
        
        spy.mockRestore();
    });
});

// Mock implementations
describe('Mock implementations', () => {
    it('should use custom implementation', () => {
        const mockFn = jest.fn((a, b) => a + b);
        
        expect(mockFn(2, 3)).toBe(5);
        expect(mockFn(5, 7)).toBe(12);
        expect(mockFn).toHaveBeenCalledTimes(2);
    });
    
    it('should return different values per call', () => {
        const mockFn = jest.fn()
            .mockReturnValueOnce('first')
            .mockReturnValueOnce('second')
            .mockReturnValue('default');
        
        expect(mockFn()).toBe('first');
        expect(mockFn()).toBe('second');
        expect(mockFn()).toBe('default');
        expect(mockFn()).toBe('default');
    });
    
    it('should handle async operations', async () => {
        const mockFn = jest.fn()
            .mockResolvedValueOnce({id: 1})
            .mockRejectedValueOnce(new Error('Failed'));
        
        await expect(mockFn()).resolves.toEqual({id: 1});
        await expect(mockFn()).rejects.toThrow('Failed');
    });
});

// Mocking modules
// math.js
function add2(a, b) {
    return a + b;
}

function multiply2(a, b) {
    return a * b;
}

// calculator.js
function calculate2(operation, a, b) {
    if (operation === 'add') {
        return add2(a, b);
    } else if (operation === 'multiply') {
        return multiply2(a, b);
    }
}

// calculator.test.js
jest.mock('./math', () => ({
    add: jest.fn((a, b) => 100),  // Always return 100
    multiply: jest.fn((a, b) => 200)
}));

describe('calculator with mocked module', () => {
    it('should use mocked add', () => {
        const result = calculate2('add', 2, 3);
        expect(result).toBe(100); // Uses mocked value
    });
});

// Partial mocking
jest.mock('./math', () => {
    const actual = jest.requireActual('./math');
    return {
        ...actual,
        add: jest.fn((a, b) => 100) // Only mock add, keep multiply real
    };
});

// Timer mocks
describe('Timer mocks', () => {
    beforeEach(() => {
        jest.useFakeTimers();
    });
    
    afterEach(() => {
        jest.useRealTimers();
    });
    
    it('should execute after timeout', () => {
        const callback = jest.fn();
        
        setTimeout(callback, 1000);
        
        expect(callback).not.toHaveBeenCalled();
        
        jest.advanceTimersByTime(1000);
        
        expect(callback).toHaveBeenCalled();
    });
    
    it('should execute interval', () => {
        const callback = jest.fn();
        
        setInterval(callback, 1000);
        
        jest.advanceTimersByTime(3000);
        
        expect(callback).toHaveBeenCalledTimes(3);
    });
});

// Mocking fetch
describe('Fetch mocking', () => {
    beforeEach(() => {
        global.fetch = jest.fn();
    });
    
    it('should fetch user data', async () => {
        const mockUser = {id: 1, name: 'John'};
        
        global.fetch.mockResolvedValue({
            ok: true,
            json: async () => mockUser
        });
        
        const response = await fetch('/api/users/1');
        const data = await response.json();
        
        expect(data).toEqual(mockUser);
        expect(fetch).toHaveBeenCalledWith('/api/users/1');
    });
    
    it('should handle fetch error', async () => {
        global.fetch.mockResolvedValue({
            ok: false,
            status: 404
        });
        
        const response = await fetch('/api/users/999');
        
        expect(response.ok).toBe(false);
        expect(response.status).toBe(404);
    });
});

// Manual mocks
class MockLocalStorage {
    constructor() {
        this.store = {};
    }
    
    getItem(key) {
        return this.store[key] || null;
    }
    
    setItem(key, value) {
        this.store[key] = value.toString();
    }
    
    removeItem(key) {
        delete this.store[key];
    }
    
    clear() {
        this.store = {};
    }
}

describe('LocalStorage operations', () => {
    let mockStorage;
    
    beforeEach(() => {
        mockStorage = new MockLocalStorage();
        global.localStorage = mockStorage;
    });
    
    it('should store and retrieve item', () => {
        localStorage.setItem('key', 'value');
        expect(localStorage.getItem('key')).toBe('value');
    });
    
    it('should remove item', () => {
        localStorage.setItem('key', 'value');
        localStorage.removeItem('key');
        expect(localStorage.getItem('key')).toBeNull();
    });
});
Key Points: Mocks verify interactions (calls, arguments). Stubs provide fixed responses. Spies track calls while executing real code. Use jest.fn() for mocks, jest.spyOn() for spies. Mock return values with mockReturnValue, mockResolvedValue. Assert with toHaveBeenCalled, toHaveBeenCalledWith. Mock modules with jest.mock(). Use fake timers for testing setTimeout/setInterval.

24.3 Test-Driven Development Patterns

TDD Cycle (Red-Green-Refactor)

Phase Action Goal
Red Write failing test Define expected behavior
Green Write minimal code to pass Make test pass quickly
Refactor Improve code quality Clean up while maintaining tests

TDD Benefits

Benefit Description Impact
Better Design Forces thinking about API first More modular, testable code
Documentation Tests document expected behavior Living specifications
Confidence Safety net for changes Fearless refactoring
Regression Prevention Catch bugs early Fewer production issues
Focus One requirement at a time Incremental progress

TDD Best Practices

Practice Description Example
Small Steps Test one thing at a time One assertion per test
Test First Always write test before code Define API through tests
Simplest Solution Write minimal code to pass Avoid over-engineering
Refactor Often Clean up after green Remove duplication, improve names
Fast Feedback Run tests frequently Catch issues immediately

Example: TDD workflow

// Example: Building a shopping cart with TDD

// STEP 1: RED - Write failing test
describe('ShoppingCart', () => {
    it('should start empty', () => {
        const cart = new ShoppingCart();
        expect(cart.getItemCount()).toBe(0);
    });
});

// Run test → FAIL (ShoppingCart doesn't exist)

// STEP 2: GREEN - Write minimal code to pass
class ShoppingCart {
    constructor() {
        this.items = [];
    }
    
    getItemCount() {
        return 0; // Hardcoded to pass test
    }
}

// Run test → PASS

// STEP 3: REFACTOR - (none needed yet)

// STEP 4: RED - Next test
describe('ShoppingCart', () => {
    it('should start empty', () => {
        const cart = new ShoppingCart();
        expect(cart.getItemCount()).toBe(0);
    });
    
    it('should add item to cart', () => {
        const cart = new ShoppingCart();
        cart.addItem({name: 'Apple', price: 1.5});
        expect(cart.getItemCount()).toBe(1);
    });
});

// Run test → FAIL (addItem doesn't exist)

// STEP 5: GREEN - Implement addItem
class ShoppingCart2 {
    constructor() {
        this.items = [];
    }
    
    addItem(item) {
        this.items.push(item);
    }
    
    getItemCount() {
        return this.items.length; // Now dynamic
    }
}

// Run test → PASS

// STEP 6: RED - Test multiple items
it('should add multiple items', () => {
    const cart = new ShoppingCart2();
    cart.addItem({name: 'Apple', price: 1.5});
    cart.addItem({name: 'Banana', price: 2.0});
    expect(cart.getItemCount()).toBe(2);
});

// Run test → PASS (already works!)

// STEP 7: RED - Test total calculation
it('should calculate total', () => {
    const cart = new ShoppingCart2();
    cart.addItem({name: 'Apple', price: 1.5});
    cart.addItem({name: 'Banana', price: 2.0});
    expect(cart.getTotal()).toBe(3.5);
});

// Run test → FAIL (getTotal doesn't exist)

// STEP 8: GREEN - Implement getTotal
class ShoppingCart3 {
    constructor() {
        this.items = [];
    }
    
    addItem(item) {
        this.items.push(item);
    }
    
    getItemCount() {
        return this.items.length;
    }
    
    getTotal() {
        return this.items.reduce((sum, item) => sum + item.price, 0);
    }
}

// Run test → PASS

// STEP 9: RED - Test remove item
it('should remove item from cart', () => {
    const cart = new ShoppingCart3();
    cart.addItem({id: 1, name: 'Apple', price: 1.5});
    cart.addItem({id: 2, name: 'Banana', price: 2.0});
    
    cart.removeItem(1);
    
    expect(cart.getItemCount()).toBe(1);
    expect(cart.getTotal()).toBe(2.0);
});

// Run test → FAIL (removeItem doesn't exist)

// STEP 10: GREEN - Implement removeItem
class ShoppingCart4 {
    constructor() {
        this.items = [];
    }
    
    addItem(item) {
        this.items.push(item);
    }
    
    removeItem(id) {
        this.items = this.items.filter(item => item.id !== id);
    }
    
    getItemCount() {
        return this.items.length;
    }
    
    getTotal() {
        return this.items.reduce((sum, item) => sum + item.price, 0);
    }
}

// Run test → PASS

// STEP 11: REFACTOR - Extract duplicated test setup
describe('ShoppingCart (refactored)', () => {
    let cart;
    
    beforeEach(() => {
        cart = new ShoppingCart4();
    });
    
    it('should start empty', () => {
        expect(cart.getItemCount()).toBe(0);
    });
    
    describe('addItem', () => {
        it('should add single item', () => {
            cart.addItem({id: 1, name: 'Apple', price: 1.5});
            expect(cart.getItemCount()).toBe(1);
        });
        
        it('should add multiple items', () => {
            cart.addItem({id: 1, name: 'Apple', price: 1.5});
            cart.addItem({id: 2, name: 'Banana', price: 2.0});
            expect(cart.getItemCount()).toBe(2);
        });
    });
    
    describe('getTotal', () => {
        it('should calculate total for multiple items', () => {
            cart.addItem({id: 1, name: 'Apple', price: 1.5});
            cart.addItem({id: 2, name: 'Banana', price: 2.0});
            expect(cart.getTotal()).toBe(3.5);
        });
        
        it('should return 0 for empty cart', () => {
            expect(cart.getTotal()).toBe(0);
        });
    });
    
    describe('removeItem', () => {
        beforeEach(() => {
            cart.addItem({id: 1, name: 'Apple', price: 1.5});
            cart.addItem({id: 2, name: 'Banana', price: 2.0});
        });
        
        it('should remove item', () => {
            cart.removeItem(1);
            expect(cart.getItemCount()).toBe(1);
            expect(cart.getTotal()).toBe(2.0);
        });
        
        it('should handle removing non-existent item', () => {
            cart.removeItem(999);
            expect(cart.getItemCount()).toBe(2);
        });
    });
});

// TDD with edge cases
describe('ShoppingCart edge cases', () => {
    let cart;
    
    beforeEach(() => {
        cart = new ShoppingCart4();
    });
    
    it('should handle item with quantity', () => {
        cart.addItem({id: 1, name: 'Apple', price: 1.5, quantity: 3});
        
        // This test will fail, driving us to add quantity support
        expect(cart.getTotal()).toBe(4.5); // 1.5 * 3
    });
    
    it('should apply discount', () => {
        cart.addItem({id: 1, name: 'Apple', price: 10});
        cart.applyDiscount(0.1); // 10% discount
        
        expect(cart.getTotal()).toBe(9);
    });
    
    it('should clear cart', () => {
        cart.addItem({id: 1, name: 'Apple', price: 1.5});
        cart.clear();
        
        expect(cart.getItemCount()).toBe(0);
        expect(cart.getTotal()).toBe(0);
    });
});

// Test-first for bug fixes
describe('Bug fix: duplicate items', () => {
    it('should prevent adding duplicate items', () => {
        const cart = new ShoppingCart4();
        
        cart.addItem({id: 1, name: 'Apple', price: 1.5});
        cart.addItem({id: 1, name: 'Apple', price: 1.5}); // Duplicate
        
        expect(cart.getItemCount()).toBe(1); // Should only have 1 item
    });
    
    // This test fails, so we fix the implementation
});

// Implementation with duplicate prevention
class ShoppingCart5 {
    constructor() {
        this.items = [];
    }
    
    addItem(item) {
        const exists = this.items.find(i => i.id === item.id);
        if (!exists) {
            this.items.push(item);
        }
    }
    
    // ... rest of methods
}
Key Points: TDD cycle: Red (write failing test), Green (make it pass), Refactor (improve code). Write test first to define API. Write minimal code to pass test. Refactor with safety of tests. Benefits: better design, documentation, confidence. Test one thing at a time. Run tests frequently for fast feedback. Use TDD for bug fixes: write test that reproduces bug, fix code, test passes.

24.4 Code Coverage and Quality Metrics

Coverage Types

Type Measures Goal
Line Coverage % of lines executed 80-90% typically
Branch Coverage % of if/else branches tested More thorough than line
Function Coverage % of functions called All functions tested
Statement Coverage % of statements executed Similar to line coverage

Quality Metrics

Metric Description Tool
Cyclomatic Complexity Number of execution paths ESLint, SonarQube
Maintainability Index Ease of maintenance score Code Climate
Code Duplication % of duplicated code SonarQube, jscpd
Technical Debt Time to fix issues SonarQube
Test Quality Mutation score, assertion density Stryker

Coverage Tools

Tool Purpose Usage
Istanbul/nyc Code coverage reporting jest --coverage
Codecov Coverage tracking service CI/CD integration
Coveralls Coverage history tracking GitHub integration
SonarQube Quality metrics platform Enterprise quality gate

Example: Code coverage

// Function with branches
function getDiscount(customer) {
    if (customer.isPremium) {
        if (customer.purchaseAmount > 100) {
            return 0.2; // 20% discount
        } else {
            return 0.1; // 10% discount
        }
    } else {
        if (customer.purchaseAmount > 50) {
            return 0.05; // 5% discount
        } else {
            return 0; // No discount
        }
    }
}

// Tests for 100% branch coverage
describe('getDiscount', () => {
    it('should give 20% discount for premium customer with high purchase', () => {
        const customer = {isPremium: true, purchaseAmount: 150};
        expect(getDiscount(customer)).toBe(0.2);
    });
    
    it('should give 10% discount for premium customer with low purchase', () => {
        const customer = {isPremium: true, purchaseAmount: 50};
        expect(getDiscount(customer)).toBe(0.1);
    });
    
    it('should give 5% discount for non-premium with medium purchase', () => {
        const customer = {isPremium: false, purchaseAmount: 75};
        expect(getDiscount(customer)).toBe(0.05);
    });
    
    it('should give no discount for non-premium with low purchase', () => {
        const customer = {isPremium: false, purchaseAmount: 25};
        expect(getDiscount(customer)).toBe(0);
    });
});

// All 4 branches are now covered!

// Running coverage with Jest
// package.json
{
    "scripts": {
        "test": "jest",
        "test:coverage": "jest --coverage",
        "test:watch": "jest --watch",
        "test:coverage:watch": "jest --coverage --watch"
    },
    "jest": {
        "collectCoverageFrom": [
            "src/**/*.js",
            "!src/**/*.test.js",
            "!src/index.js"
        ],
        "coverageThreshold": {
            "global": {
                "branches": 80,
                "functions": 80,
                "lines": 80,
                "statements": 80
            }
        }
    }
}

// Coverage report shows:
// File      | % Stmts | % Branch | % Funcs | % Lines |
// ----------|---------|----------|---------|---------|
// discount.js |  100    |   100    |   100   |   100   |

// Uncovered code example
function processPayment(amount, method) {
    if (method === 'credit') {
        return chargeCreditCard(amount);
    } else if (method === 'debit') {
        return chargeDebitCard(amount);
    } else if (method === 'paypal') {
        return chargePayPal(amount);
    }
    // No else branch - what if method is invalid?
    // Coverage report will show this as uncovered
}

// Better version with default case
function processPayment2(amount, method) {
    if (method === 'credit') {
        return chargeCreditCard(amount);
    } else if (method === 'debit') {
        return chargeDebitCard(amount);
    } else if (method === 'paypal') {
        return chargePayPal(amount);
    } else {
        throw new Error(`Unknown payment method: ${method}`);
    }
}

// Test for error case
it('should throw error for invalid payment method', () => {
    expect(() => processPayment2(100, 'bitcoin'))
        .toThrow('Unknown payment method: bitcoin');
});

// Cyclomatic complexity example
// Bad: High complexity (hard to test, maintain)
function calculateShipping(weight, distance, isPremium, isInternational, isFragile) {
    let cost = 0;
    
    if (weight > 10) {
        if (distance > 100) {
            if (isPremium) {
                if (isInternational) {
                    if (isFragile) {
                        cost = 100;
                    } else {
                        cost = 80;
                    }
                } else {
                    cost = 50;
                }
            } else {
                cost = 30;
            }
        } else {
            cost = 20;
        }
    } else {
        cost = 10;
    }
    
    return cost;
}
// Cyclomatic complexity: 7 (too high!)

// Good: Lower complexity (easier to test, maintain)
function calculateShipping2(options) {
    const {weight, distance, isPremium, isInternational, isFragile} = options;
    
    let cost = getBaseCost(weight, distance);
    
    if (isPremium) {
        cost = applyPremiumRate(cost);
    }
    
    if (isInternational) {
        cost = applyInternationalRate(cost);
    }
    
    if (isFragile) {
        cost = applyFragileHandling(cost);
    }
    
    return cost;
}

function getBaseCost(weight, distance) {
    if (weight > 10 && distance > 100) return 30;
    if (weight > 10) return 20;
    return 10;
}

function applyPremiumRate(cost) {
    return cost * 1.5;
}

function applyInternationalRate(cost) {
    return cost * 2;
}

function applyFragileHandling(cost) {
    return cost + 20;
}
// Each function has lower complexity, easier to test individually

// Mutation testing concept
function isPositive(n) {
    return n > 0;  // Mutant: change > to >=
}

// This test would NOT catch the mutation
it('should return true for positive number', () => {
    expect(isPositive(5)).toBe(true);
});

// Better test suite that catches mutations
describe('isPositive', () => {
    it('should return true for positive number', () => {
        expect(isPositive(5)).toBe(true);
    });
    
    it('should return false for zero', () => {
        expect(isPositive(0)).toBe(false); // Catches >= mutation
    });
    
    it('should return false for negative number', () => {
        expect(isPositive(-5)).toBe(false);
    });
});

// Coverage doesn't guarantee quality
function divide2(a, b) {
    return a / b;
}

// 100% coverage, but missing important test
it('divides two numbers', () => {
    expect(divide2(10, 2)).toBe(5);
});
// No test for division by zero!

// Better test suite
describe('divide', () => {
    it('should divide two numbers', () => {
        expect(divide2(10, 2)).toBe(5);
    });
    
    it('should handle division by zero', () => {
        expect(divide2(10, 0)).toBe(Infinity);
    });
    
    it('should handle negative numbers', () => {
        expect(divide2(-10, 2)).toBe(-5);
    });
    
    it('should handle decimals', () => {
        expect(divide2(5, 2)).toBe(2.5);
    });
});

// Code quality checks with ESLint
// .eslintrc.json
{
    "extends": "eslint:recommended",
    "rules": {
        "complexity": ["error", 5],  // Max cyclomatic complexity
        "max-depth": ["error", 3],   // Max nesting depth
        "max-lines-per-function": ["warn", 50],
        "max-params": ["error", 4],  // Max function parameters
        "no-duplicate-code": "error"
    }
}

// Measuring test quality
describe('Test metrics', () => {
    // Number of assertions per test
    it('should have focused assertions', () => {
        const user = createUser();
        expect(user.name).toBeDefined();  // 1
        expect(user.email).toBeDefined(); // 2
        expect(user.id).toBeDefined();    // 3
        // 3 assertions per test
    });
    
    // Test execution time
    it('should run fast', () => {
        const start = performance.now();
        
        const result = someOperation();
        
        const duration = performance.now() - start;
        expect(duration).toBeLessThan(100); // Should be fast
    });
});

// Coverage configuration for different file types
// jest.config.js
module.exports = {
    collectCoverageFrom: [
        'src/**/*.{js,jsx,ts,tsx}',
        '!src/**/*.d.ts',
        '!src/**/*.test.{js,jsx,ts,tsx}',
        '!src/**/__tests__/**',
        '!src/index.js',
        '!src/setupTests.js'
    ],
    coverageThreshold: {
        global: {
            branches: 80,
            functions: 80,
            lines: 80,
            statements: 80
        },
        './src/utils/': {
            branches: 90,
            functions: 90,
            lines: 90,
            statements: 90
        }
    },
    coverageReporters: ['text', 'lcov', 'html']
};
Key Points: Code coverage measures % of code executed by tests. Types: line, branch, function, statement coverage. Aim for 80-90% coverage. Branch coverage more thorough than line coverage. Coverage doesn't guarantee quality - need good assertions. Cyclomatic complexity measures code complexity. Mutation testing validates test quality. Use ESLint for complexity limits. Configure coverage thresholds in jest.config.

24.5 Assertion Libraries and Testing Utilities

Common Assertion Libraries

Library Style Usage
Jest expect() Built-in, batteries included
Chai expect/should/assert Flexible, plugin ecosystem
Vitest expect() (Jest-compatible) Vite-native, fast
Node assert assert.equal() Built-in Node.js

Jest Matchers

Matcher Purpose Example
toBe Strict equality (===) expect(value).toBe(5)
toEqual Deep equality expect(obj).toEqual({a: 1})
toMatch Regex match expect(str).toMatch(/hello/)
toContain Array/string contains expect(arr).toContain(item)
toThrow Function throws error expect(fn).toThrow()
toBeTruthy/toBeFalsy Boolean coercion expect(value).toBeTruthy()
toBeNull/toBeUndefined Null/undefined check expect(val).toBeNull()
toBeGreaterThan Numeric comparison expect(5).toBeGreaterThan(3)
toHaveLength Array/string length expect(arr).toHaveLength(3)
toHaveProperty Object property check expect(obj).toHaveProperty('key')

Testing Utilities

Utility Purpose Library
Faker Generate fake data @faker-js/faker
Testing Library DOM testing utilities @testing-library/react
MSW Mock service worker (API mocking) msw
Supertest HTTP assertion supertest
Nock HTTP mocking nock

Example: Assertions and matchers

// Basic matchers
describe('Basic matchers', () => {
    it('toBe for primitives (===)', () => {
        expect(2 + 2).toBe(4);
        expect('hello').toBe('hello');
        expect(true).toBe(true);
    });
    
    it('toEqual for objects/arrays (deep equality)', () => {
        const obj1 = {name: 'John', age: 30};
        const obj2 = {name: 'John', age: 30};
        
        expect(obj1).toEqual(obj2);  // Pass
        // expect(obj1).toBe(obj2);  // Fail (different references)
        
        expect([1, 2, 3]).toEqual([1, 2, 3]);
    });
    
    it('toStrictEqual (stricter than toEqual)', () => {
        const obj = {a: 1, b: undefined};
        
        expect(obj).toEqual({a: 1});        // Pass (ignores undefined)
        expect(obj).toStrictEqual({a: 1});  // Fail (checks undefined)
    });
});

// Number matchers
describe('Number matchers', () => {
    it('comparison matchers', () => {
        expect(5).toBeGreaterThan(3);
        expect(3).toBeLessThan(5);
        expect(5).toBeGreaterThanOrEqual(5);
        expect(3).toBeLessThanOrEqual(3);
    });
    
    it('toBeCloseTo for floating point', () => {
        expect(0.1 + 0.2).toBeCloseTo(0.3);  // Handles floating point precision
        expect(0.1 + 0.2).toBeCloseTo(0.3, 5);  // 5 decimal places
    });
});

// String matchers
describe('String matchers', () => {
    it('toMatch with regex', () => {
        expect('hello world').toMatch(/world/);
        expect('hello world').toMatch(/^hello/);
        expect('test@example.com').toMatch(/^[^@]+@[^@]+\.[^@]+$/);
    });
    
    it('string contains', () => {
        expect('hello world').toContain('world');
    });
});

// Array matchers
describe('Array matchers', () => {
    it('toContain for arrays', () => {
        const arr = [1, 2, 3, 4, 5];
        expect(arr).toContain(3);
        expect(arr).toContain(5);
    });
    
    it('toHaveLength', () => {
        expect([1, 2, 3]).toHaveLength(3);
        expect('hello').toHaveLength(5);
    });
    
    it('toContainEqual for object arrays', () => {
        const users = [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}];
        expect(users).toContainEqual({id: 1, name: 'John'});
    });
    
    it('arrayContaining for partial match', () => {
        expect([1, 2, 3, 4]).toEqual(expect.arrayContaining([2, 3]));
    });
});

// Object matchers
describe('Object matchers', () => {
    it('toHaveProperty', () => {
        const user = {name: 'John', age: 30};
        
        expect(user).toHaveProperty('name');
        expect(user).toHaveProperty('name', 'John');
        expect(user).toHaveProperty('age', 30);
    });
    
    it('objectContaining for partial match', () => {
        const user = {id: 1, name: 'John', email: 'john@example.com'};
        
        expect(user).toEqual(expect.objectContaining({
            name: 'John',
            email: 'john@example.com'
        }));
    });
    
    it('toMatchObject for subset', () => {
        const user = {id: 1, name: 'John', age: 30};
        
        expect(user).toMatchObject({name: 'John'});
        expect(user).toMatchObject({name: 'John', age: 30});
    });
});

// Error matchers
describe('Error matchers', () => {
    it('toThrow', () => {
        function throwError() {
            throw new Error('Something went wrong');
        }
        
        expect(throwError).toThrow();
        expect(throwError).toThrow(Error);
        expect(throwError).toThrow('Something went wrong');
        expect(throwError).toThrow(/went wrong/);
    });
    
    it('not.toThrow', () => {
        function noError() {
            return 'ok';
        }
        
        expect(noError).not.toThrow();
    });
});

// Truthiness matchers
describe('Truthiness matchers', () => {
    it('toBeTruthy/toBeFalsy', () => {
        expect(true).toBeTruthy();
        expect(1).toBeTruthy();
        expect('hello').toBeTruthy();
        expect([]).toBeTruthy();
        
        expect(false).toBeFalsy();
        expect(0).toBeFalsy();
        expect('').toBeFalsy();
        expect(null).toBeFalsy();
        expect(undefined).toBeFalsy();
    });
    
    it('toBeDefined/toBeUndefined', () => {
        const obj = {name: 'John'};
        
        expect(obj.name).toBeDefined();
        expect(obj.age).toBeUndefined();
    });
    
    it('toBeNull', () => {
        expect(null).toBeNull();
        expect(undefined).not.toBeNull();
    });
});

// Async matchers
describe('Async matchers', () => {
    it('resolves', async () => {
        const promise = Promise.resolve('success');
        
        await expect(promise).resolves.toBe('success');
    });
    
    it('rejects', async () => {
        const promise = Promise.reject(new Error('failed'));
        
        await expect(promise).rejects.toThrow('failed');
    });
});

// Custom matchers
expect.extend({
    toBeWithinRange(received, floor, ceiling) {
        const pass = received >= floor && received <= ceiling;
        
        if (pass) {
            return {
                message: () => 
                    `expected ${received} not to be within range ${floor} - ${ceiling}`,
                pass: true
            };
        } else {
            return {
                message: () => 
                    `expected ${received} to be within range ${floor} - ${ceiling}`,
                pass: false
            };
        }
    },
    
    toBeValidEmail(received) {
        const pass = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(received);
        
        return {
            message: () => pass
                ? `expected ${received} not to be a valid email`
                : `expected ${received} to be a valid email`,
            pass
        };
    }
});

describe('Custom matchers', () => {
    it('toBeWithinRange', () => {
        expect(5).toBeWithinRange(1, 10);
        expect(15).not.toBeWithinRange(1, 10);
    });
    
    it('toBeValidEmail', () => {
        expect('user@example.com').toBeValidEmail();
        expect('invalid-email').not.toBeValidEmail();
    });
});

// Test data generation with Faker
// import { faker } from '@faker-js/faker';

function generateUser() {
    return {
        id: faker.datatype.uuid(),
        name: faker.name.fullName(),
        email: faker.internet.email(),
        age: faker.datatype.number({min: 18, max: 80}),
        address: {
            street: faker.address.streetAddress(),
            city: faker.address.city(),
            country: faker.address.country()
        }
    };
}

describe('Faker data generation', () => {
    it('generates realistic test data', () => {
        const user = generateUser();
        
        expect(user.id).toBeDefined();
        expect(user.name).toMatch(/\w+ \w+/);
        expect(user.email).toBeValidEmail();
        expect(user.age).toBeGreaterThanOrEqual(18);
        expect(user.age).toBeLessThanOrEqual(80);
    });
    
    it('generates multiple users', () => {
        const users = Array.from({length: 5}, generateUser);
        
        expect(users).toHaveLength(5);
        expect(users[0].id).not.toBe(users[1].id); // Unique IDs
    });
});

// Snapshot testing
describe('Snapshot testing', () => {
    it('matches inline snapshot', () => {
        const user = {id: 1, name: 'John', email: 'john@example.com'};
        
        expect(user).toMatchInlineSnapshot(`
            {
              "email": "john@example.com",
              "id": 1,
              "name": "John",
            }
        `);
    });
    
    it('matches snapshot file', () => {
        const component = renderComponent();
        expect(component).toMatchSnapshot();
    });
});

// Asymmetric matchers
describe('Asymmetric matchers', () => {
    it('expect.anything()', () => {
        expect({id: 1, name: 'John'}).toEqual({
            id: expect.anything(),
            name: 'John'
        });
    });
    
    it('expect.any(constructor)', () => {
        expect({id: 1, createdAt: new Date()}).toEqual({
            id: 1,
            createdAt: expect.any(Date)
        });
    });
    
    it('expect.stringContaining()', () => {
        expect('hello world').toEqual(expect.stringContaining('world'));
    });
    
    it('expect.stringMatching()', () => {
        expect('test@example.com').toEqual(
            expect.stringMatching(/^[^@]+@[^@]+\.[^@]+$/)
        );
    });
});
Key Points: Jest matchers: toBe (===), toEqual (deep), toMatch (regex), toContain (array/string), toThrow (errors). Number matchers: toBeGreaterThan, toBeCloseTo (floats). Object matchers: toHaveProperty, objectContaining, toMatchObject. Async: resolves, rejects. Create custom matchers with expect.extend. Use Faker for test data generation. Snapshot testing for complex objects. Asymmetric matchers: expect.anything(), expect.any(Type).

24.6 Integration and End-to-End Testing

Testing Pyramid

Level Speed Cost Coverage Quantity
Unit Fast Low Small Many (70%)
Integration Medium Medium Medium Some (20%)
E2E Slow High Large Few (10%)

Integration Test Types

Type Tests Example
Component Integration Multiple units together Service + Repository
API Integration HTTP endpoints REST API routes
Database Integration DB operations CRUD operations
External Service Third-party APIs Payment gateway

E2E Testing Tools

Tool Purpose Features
Cypress Modern E2E testing Real browser, time travel debugging
Playwright Cross-browser E2E Multi-browser, parallel, API testing
Puppeteer Chrome automation Headless Chrome, screenshots
WebdriverIO WebDriver protocol Mobile, desktop, cloud services

Example: Integration testing

// Integration test: Service + Repository
class UserRepository {
    constructor(db) {
        this.db = db;
    }
    
    async create(userData) {
        return this.db.users.insert(userData);
    }
    
    async findById(id) {
        return this.db.users.findOne({id});
    }
    
    async update(id, updates) {
        return this.db.users.update({id}, updates);
    }
}

class UserService2 {
    constructor(repository, emailService) {
        this.repository = repository;
        this.emailService = emailService;
    }
    
    async createUser(userData) {
        const user = await this.repository.create(userData);
        await this.emailService.sendWelcomeEmail(user.email);
        return user;
    }
    
    async updateUser(id, updates) {
        const user = await this.repository.findById(id);
        if (!user) {
            throw new Error('User not found');
        }
        return this.repository.update(id, updates);
    }
}

// Integration test
describe('UserService Integration', () => {
    let db;
    let repository;
    let emailService;
    let userService;
    
    beforeAll(async () => {
        // Setup test database
        db = await setupTestDatabase();
    });
    
    afterAll(async () => {
        await db.disconnect();
    });
    
    beforeEach(async () => {
        // Clear database before each test
        await db.users.clear();
        
        // Create real repository
        repository = new UserRepository(db);
        
        // Mock email service
        emailService = {
            sendWelcomeEmail: jest.fn().mockResolvedValue(true)
        };
        
        userService = new UserService2(repository, emailService);
    });
    
    it('should create user and send welcome email', async () => {
        const userData = {
            name: 'John Doe',
            email: 'john@example.com'
        };
        
        const user = await userService.createUser(userData);
        
        // Verify user created in database
        expect(user.id).toBeDefined();
        expect(user.name).toBe('John Doe');
        
        // Verify can retrieve from database
        const retrieved = await repository.findById(user.id);
        expect(retrieved).toEqual(user);
        
        // Verify email sent
        expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith('john@example.com');
    });
    
    it('should update existing user', async () => {
        // Create user first
        const user = await repository.create({
            name: 'John',
            email: 'john@example.com'
        });
        
        // Update user
        const updated = await userService.updateUser(user.id, {
            name: 'John Updated'
        });
        
        expect(updated.name).toBe('John Updated');
        
        // Verify in database
        const retrieved = await repository.findById(user.id);
        expect(retrieved.name).toBe('John Updated');
    });
    
    it('should throw error when updating non-existent user', async () => {
        await expect(
            userService.updateUser(999, {name: 'Test'})
        ).rejects.toThrow('User not found');
    });
});

// API Integration testing with Supertest
// import request from 'supertest';
// import express from 'express';

const app = express();
app.use(express.json());

app.post('/api/users', async (req, res) => {
    const user = await createUser(req.body);
    res.status(201).json(user);
});

app.get('/api/users/:id', async (req, res) => {
    const user = await findUser(req.params.id);
    if (!user) {
        return res.status(404).json({error: 'User not found'});
    }
    res.json(user);
});

describe('User API', () => {
    beforeAll(async () => {
        await setupTestDatabase();
    });
    
    afterEach(async () => {
        await clearDatabase();
    });
    
    describe('POST /api/users', () => {
        it('should create user', async () => {
            const response = await request(app)
                .post('/api/users')
                .send({
                    name: 'John Doe',
                    email: 'john@example.com'
                })
                .expect(201)
                .expect('Content-Type', /json/);
            
            expect(response.body).toMatchObject({
                name: 'John Doe',
                email: 'john@example.com'
            });
            expect(response.body.id).toBeDefined();
        });
        
        it('should return 400 for invalid data', async () => {
            await request(app)
                .post('/api/users')
                .send({name: 'John'}) // Missing email
                .expect(400);
        });
    });
    
    describe('GET /api/users/:id', () => {
        it('should get user by id', async () => {
            // Create user first
            const created = await createUser({
                name: 'Jane',
                email: 'jane@example.com'
            });
            
            const response = await request(app)
                .get(`/api/users/${created.id}`)
                .expect(200);
            
            expect(response.body).toEqual(created);
        });
        
        it('should return 404 for non-existent user', async () => {
            await request(app)
                .get('/api/users/999')
                .expect(404);
        });
    });
});

Example: E2E testing with Playwright

// Playwright E2E test
// import { test, expect } from '@playwright/test';

test.describe('Shopping Cart E2E', () => {
    test.beforeEach(async ({ page }) => {
        // Navigate to app
        await page.goto('http://localhost:3000');
    });
    
    test('should add item to cart and checkout', async ({ page }) => {
        // Navigate to products
        await page.click('text=Products');
        
        // Wait for products to load
        await page.waitForSelector('.product-card');
        
        // Add first product to cart
        await page.click('.product-card:first-child button:has-text("Add to Cart")');
        
        // Verify cart count updated
        const cartCount = await page.textContent('.cart-count');
        expect(cartCount).toBe('1');
        
        // Go to cart
        await page.click('text=Cart');
        
        // Verify item in cart
        await expect(page.locator('.cart-item')).toHaveCount(1);
        
        // Verify total
        const total = await page.textContent('.cart-total');
        expect(total).toContain("$");
        
        // Proceed to checkout
        await page.click('button:has-text("Checkout")');
        
        // Fill checkout form
        await page.fill('input[name="name"]', 'John Doe');
        await page.fill('input[name="email"]', 'john@example.com');
        await page.fill('input[name="address"]', '123 Main St');
        await page.fill('input[name="cardNumber"]', '4111111111111111');
        
        // Submit order
        await page.click('button:has-text("Place Order")');
        
        // Wait for confirmation
        await page.waitForSelector('.order-confirmation');
        
        // Verify success message
        const message = await page.textContent('.order-confirmation h2');
        expect(message).toContain('Order Confirmed');
        
        // Verify order number
        await expect(page.locator('.order-number')).toBeVisible();
    });
    
    test('should search for products', async ({ page }) => {
        // Enter search term
        await page.fill('input[placeholder="Search products"]', 'laptop');
        await page.press('input[placeholder="Search products"]', 'Enter');
        
        // Wait for results
        await page.waitForSelector('.product-card');
        
        // Verify all results contain search term
        const products = await page.$('.product-card');
        expect(products.length).toBeGreaterThan(0);
        
        for (const product of products) {
            const text = await product.textContent();
            expect(text.toLowerCase()).toContain('laptop');
        }
    });
    
    test('should handle empty cart checkout', async ({ page }) => {
        // Try to checkout with empty cart
        await page.click('text=Cart');
        
        // Checkout button should be disabled
        const checkoutBtn = page.locator('button:has-text("Checkout")');
        await expect(checkoutBtn).toBeDisabled();
        
        // Verify empty cart message
        await expect(page.locator('text=Your cart is empty')).toBeVisible();
    });
});

// Cypress E2E test
// describe('Login Flow', () => {
//     beforeEach(() => {
//         cy.visit('http://localhost:3000/login');
//     });
//     
//     it('should login successfully', () => {
//         // Fill form
//         cy.get('input[name="email"]').type('user@example.com');
//         cy.get('input[name="password"]').type('password123');
//         
//         // Submit
//         cy.get('button[type="submit"]').click();
//         
//         // Verify redirect
//         cy.url().should('include', '/dashboard');
//         
//         // Verify user info displayed
//         cy.contains('Welcome, user@example.com');
//     });
//     
//     it('should show error for invalid credentials', () => {
//         cy.get('input[name="email"]').type('wrong@example.com');
//         cy.get('input[name="password"]').type('wrongpass');
//         cy.get('button[type="submit"]').click();
//         
//         // Verify error message
//         cy.contains('Invalid credentials');
//         
//         // Should stay on login page
//         cy.url().should('include', '/login');
//     });
//     
//     it('should validate form fields', () => {
//         // Try to submit empty form
//         cy.get('button[type="submit"]').click();
//         
//         // Verify validation errors
//         cy.contains('Email is required');
//         cy.contains('Password is required');
//     });
// });

// Visual regression testing
test('should match screenshot', async ({ page }) => {
    await page.goto('http://localhost:3000');
    
    // Take full page screenshot
    await expect(page).toHaveScreenshot('homepage.png');
    
    // Take element screenshot
    const header = page.locator('header');
    await expect(header).toHaveScreenshot('header.png');
});

// Mobile testing
test('should work on mobile', async ({ page }) => {
    // Set mobile viewport
    await page.setViewportSize({ width: 375, height: 667 });
    
    await page.goto('http://localhost:3000');
    
    // Verify mobile menu
    await page.click('.mobile-menu-icon');
    await expect(page.locator('.mobile-menu')).toBeVisible();
});

// Performance testing
test('should load within 3 seconds', async ({ page }) => {
    const startTime = Date.now();
    
    await page.goto('http://localhost:3000');
    await page.waitForLoadState('networkidle');
    
    const loadTime = Date.now() - startTime;
    expect(loadTime).toBeLessThan(3000);
});
Key Points: Integration tests test multiple components together (service + repository, API endpoints). Use real database or test database. E2E tests test complete user workflows in browser. Testing pyramid: many unit tests (70%), some integration (20%), few E2E (10%). Use Supertest for API testing. Playwright/Cypress for browser automation. E2E tests are slow, expensive - focus on critical paths. Include visual regression, mobile, performance testing.

Section 24 Summary: Testing and Code Quality

  • Unit Testing: Test individual units in isolation, follow AAA pattern (Arrange, Act, Assert), fast and repeatable
  • Test Structure: Organize with describe/it blocks, setup with beforeEach/afterEach, descriptive test names
  • Mocking: Use mocks (verify interactions), stubs (fixed responses), spies (track calls), jest.fn() for mocks
  • Mock Assertions: toHaveBeenCalled, toHaveBeenCalledWith, toHaveBeenCalledTimes, mockReturnValue, mockResolvedValue
  • TDD Cycle: Red (write failing test), Green (make it pass), Refactor (improve code while tests pass)
  • TDD Benefits: Better design, documentation, confidence, regression prevention, focus on requirements
  • Code Coverage: Measure line, branch, function, statement coverage - aim for 80-90%, branch coverage most thorough
  • Quality Metrics: Cyclomatic complexity, maintainability index, code duplication, technical debt
  • Assertions: toBe (===), toEqual (deep), toMatch (regex), toContain, toThrow, toBeGreaterThan, toHaveProperty
  • Test Utilities: Faker for test data, custom matchers with expect.extend, snapshot testing, asymmetric matchers
  • Integration Tests: Test multiple components together (service + repository, API endpoints), use test database
  • E2E Testing: Test complete workflows with Playwright/Cypress, testing pyramid (70% unit, 20% integration, 10% E2E)

25. Security and Best Practices

25.1 Input Validation and Sanitization

Common Input Threats

Threat Description Prevention
SQL Injection Malicious SQL in input Parameterized queries, ORM
XSS (Cross-Site Scripting) Malicious scripts in content Escape output, CSP headers
Command Injection OS commands in input Avoid shell execution, whitelist
Path Traversal Access unauthorized files Validate paths, use basedir
LDAP Injection Malicious LDAP queries Escape special chars, whitelist

Validation Strategies

Strategy When to Use Example
Whitelist Known valid values Enum values, file extensions
Blacklist Last resort only Block specific patterns
Type Checking Enforce data types typeof, instanceof, schema
Length Limits Prevent overflow/DoS Max string/array length
Format Validation Structured data Email, URL, phone regex

Sanitization Techniques

Technique Purpose Library
HTML Escaping Prevent XSS DOMPurify, he
URL Encoding Safe URL parameters encodeURIComponent()
SQL Escaping Prevent SQL injection ORM, prepared statements
Trim/Normalize Consistent format trim(), normalize()

Example: Input validation and sanitization

// Email validation
function isValidEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
}

// Strong password validation
function isStrongPassword(password) {
    // At least 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special char
    const minLength = password.length >= 8;
    const hasUpper = /[A-Z]/.test(password);
    const hasLower = /[a-z]/.test(password);
    const hasNumber = /\d/.test(password);
    const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
    
    return minLength && hasUpper && hasLower && hasNumber && hasSpecial;
}

// Comprehensive input validator
class InputValidator {
    static validateString(value, options = {}) {
        const {
            minLength = 0,
            maxLength = Infinity,
            pattern = null,
            allowEmpty = false
        } = options;
        
        if (!value && !allowEmpty) {
            throw new Error('Value is required');
        }
        
        if (typeof value !== 'string') {
            throw new Error('Value must be a string');
        }
        
        if (value.length < minLength) {
            throw new Error(`Value must be at least ${minLength} characters`);
        }
        
        if (value.length > maxLength) {
            throw new Error(`Value must not exceed ${maxLength} characters`);
        }
        
        if (pattern && !pattern.test(value)) {
            throw new Error('Value does not match required pattern');
        }
        
        return true;
    }
    
    static validateNumber(value, options = {}) {
        const {
            min = -Infinity,
            max = Infinity,
            integer = false
        } = options;
        
        const num = Number(value);
        
        if (isNaN(num)) {
            throw new Error('Value must be a number');
        }
        
        if (integer && !Number.isInteger(num)) {
            throw new Error('Value must be an integer');
        }
        
        if (num < min) {
            throw new Error(`Value must be at least ${min}`);
        }
        
        if (num > max) {
            throw new Error(`Value must not exceed ${max}`);
        }
        
        return true;
    }
    
    static validateEnum(value, allowedValues) {
        if (!allowedValues.includes(value)) {
            throw new Error(
                `Value must be one of: ${allowedValues.join(', ')}`
            );
        }
        return true;
    }
    
    static validateArray(value, options = {}) {
        const {
            minLength = 0,
            maxLength = Infinity,
            itemValidator = null
        } = options;
        
        if (!Array.isArray(value)) {
            throw new Error('Value must be an array');
        }
        
        if (value.length < minLength) {
            throw new Error(`Array must have at least ${minLength} items`);
        }
        
        if (value.length > maxLength) {
            throw new Error(`Array must not exceed ${maxLength} items`);
        }
        
        if (itemValidator) {
            value.forEach((item, index) => {
                try {
                    itemValidator(item);
                } catch (error) {
                    throw new Error(`Item at index ${index}: ${error.message}`);
                }
            });
        }
        
        return true;
    }
}

// Usage examples
try {
    InputValidator.validateString('hello', {minLength: 3, maxLength: 10});
    InputValidator.validateNumber(25, {min: 0, max: 100});
    InputValidator.validateEnum('active', ['active', 'inactive', 'pending']);
    console.log('All validations passed');
} catch (error) {
    console.error('Validation error:', error.message);
}

// HTML escaping to prevent XSS
function escapeHtml(text) {
    const map = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#039;'
    };
    return text.replace(/[&<>"']/g, m => map[m]);
}

// Safe HTML rendering
function safeRender(userInput) {
    const escaped = escapeHtml(userInput);
    document.getElementById('output').textContent = escaped;
    // or use textContent instead of innerHTML
}

// URL parameter sanitization
function sanitizeUrlParam(param) {
    return encodeURIComponent(param);
}

// Build safe URL
function buildUrl(base, params) {
    const queryString = Object.entries(params)
        .map(([key, value]) => 
            `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
        )
        .join('&');
    
    return `${base}?${queryString}`;
}

const url = buildUrl('https://api.example.com/search', {
    q: 'user input <script>',
    page: 1
});
// Result: https://api.example.com/search?q=user%20input%20%3Cscript%3E&page=1

// Filename sanitization (prevent path traversal)
function sanitizeFilename(filename) {
    // Remove path separators and null bytes
    return filename
        .replace(/[\/\\]/g, '')
        .replace(/\0/g, '')
        .replace(/\.\./g, '')
        .slice(0, 255); // Limit length
}

// Path traversal prevention
function safePath(userPath, baseDir) {
    const path = require('path');
    const resolvedPath = path.resolve(baseDir, userPath);
    
    // Ensure resolved path is within baseDir
    if (!resolvedPath.startsWith(path.resolve(baseDir))) {
        throw new Error('Path traversal detected');
    }
    
    return resolvedPath;
}

// Schema validation with custom validator
const userSchema = {
    username: {
        type: 'string',
        minLength: 3,
        maxLength: 20,
        pattern: /^[a-zA-Z0-9_]+$/
    },
    email: {
        type: 'string',
        validator: isValidEmail
    },
    age: {
        type: 'number',
        min: 18,
        max: 120,
        integer: true
    },
    role: {
        type: 'enum',
        values: ['user', 'admin', 'moderator']
    }
};

function validateSchema(data, schema) {
    const errors = [];
    
    for (const [field, rules] of Object.entries(schema)) {
        const value = data[field];
        
        try {
            if (rules.type === 'string') {
                InputValidator.validateString(value, rules);
            } else if (rules.type === 'number') {
                InputValidator.validateNumber(value, rules);
            } else if (rules.type === 'enum') {
                InputValidator.validateEnum(value, rules.values);
            }
            
            if (rules.validator && !rules.validator(value)) {
                errors.push(`${field}: Custom validation failed`);
            }
        } catch (error) {
            errors.push(`${field}: ${error.message}`);
        }
    }
    
    return {
        valid: errors.length === 0,
        errors
    };
}

// Usage
const userData = {
    username: 'john_doe',
    email: 'john@example.com',
    age: 25,
    role: 'user'
};

const result = validateSchema(userData, userSchema);
if (result.valid) {
    console.log('User data is valid');
} else {
    console.error('Validation errors:', result.errors);
}

// SQL injection prevention (using parameterized queries)
// BAD: Vulnerable to SQL injection
function getUserBad(userId) {
    const query = `SELECT * FROM users WHERE id = ${userId}`;
    // If userId is "1 OR 1=1", returns all users!
    return db.query(query);
}

// GOOD: Parameterized query
function getUserGood(userId) {
    const query = 'SELECT * FROM users WHERE id = ?';
    return db.query(query, [userId]);
}

// ORM approach (safer)
function getUserORM(userId) {
    return User.findById(userId);
}

// Command injection prevention
// BAD: Vulnerable to command injection
function processBad(filename) {
    const exec = require('child_process').exec;
    exec(`cat ${filename}`, (error, stdout) => {
        console.log(stdout);
    });
    // If filename is "file.txt; rm -rf /", disaster!
}

// GOOD: Use array syntax, avoid shell
function processGood(filename) {
    const {execFile} = require('child_process');
    execFile('cat', [filename], (error, stdout) => {
        console.log(stdout);
    });
}

// Rate limiting for API endpoints
class RateLimiter {
    constructor(maxRequests, windowMs) {
        this.maxRequests = maxRequests;
        this.windowMs = windowMs;
        this.requests = new Map();
    }
    
    isAllowed(identifier) {
        const now = Date.now();
        const userRequests = this.requests.get(identifier) || [];
        
        // Remove old requests outside window
        const validRequests = userRequests.filter(
            time => now - time < this.windowMs
        );
        
        if (validRequests.length >= this.maxRequests) {
            return false;
        }
        
        validRequests.push(now);
        this.requests.set(identifier, validRequests);
        return true;
    }
}

// Usage
const limiter = new RateLimiter(100, 60000); // 100 requests per minute

function handleRequest(req, res) {
    const userId = req.userId;
    
    if (!limiter.isAllowed(userId)) {
        return res.status(429).json({error: 'Too many requests'});
    }
    
    // Process request
}

// Content Security Policy (CSP) nonce generation
function generateNonce() {
    const crypto = require('crypto');
    return crypto.randomBytes(16).toString('base64');
}
Key Points: Always validate input on server side (client-side validation is for UX only). Use whitelist validation when possible. Escape HTML to prevent XSS. Use parameterized queries to prevent SQL injection. Sanitize filenames to prevent path traversal. Implement rate limiting to prevent DoS. Never trust user input. Validate type, length, format, and range. Use established libraries for validation (Joi, Yup, validator.js).

25.2 XSS Prevention and Content Security Policy

XSS Attack Types

Type Description Example
Reflected XSS Script in URL/input reflected immediately ?q=<script>alert(1)</script>
Stored XSS Script stored in database, served to users Comment with malicious script
DOM-based XSS Client-side script manipulation document.write(location.hash)
Mutation XSS Browser mutates sanitized HTML Nested tags, encoding tricks

XSS Prevention Techniques

Technique How It Works Implementation
Output Encoding Escape HTML entities &lt; &gt; &amp; &quot;
Use textContent Automatically escapes content element.textContent = data
Avoid innerHTML Prevents script execution Use DOM methods instead
CSP Headers Restrict script sources Content-Security-Policy header
DOMPurify Sanitize HTML safely Library for HTML cleaning

CSP Directives

Directive Purpose Example Value
default-src Fallback for all sources 'self'
script-src JavaScript sources 'self' 'nonce-xyz123'
style-src CSS sources 'self' 'unsafe-inline'
img-src Image sources 'self' data: https:
connect-src XHR/WebSocket/EventSource 'self' https://api.example.com
font-src Font sources 'self' https://fonts.gstatic.com
frame-ancestors Who can embed in iframe 'none' or 'self'

Example: XSS prevention

// XSS vulnerable code examples
// BAD: innerHTML with user input
function displayUsernameBad(username) {
    document.getElementById('greeting').innerHTML = 
        `<h1>Welcome, ${username}!</h1>`;
    // If username is "<img src=x onerror=alert('XSS')>", XSS occurs!
}

// GOOD: Use textContent
function displayUsernameGood(username) {
    const h1 = document.createElement('h1');
    h1.textContent = `Welcome, ${username}!`;
    document.getElementById('greeting').appendChild(h1);
}

// BAD: eval with user input
function executeBad(userCode) {
    eval(userCode); // NEVER DO THIS!
}

// GOOD: Use safe alternatives
function executeGood(userCode) {
    // If you must parse JSON
    const data = JSON.parse(userCode);
    
    // For calculations, use Function constructor with validation
    // But still be very careful!
}

// BAD: Direct DOM manipulation from URL
function displayFromUrlBad() {
    const params = new URLSearchParams(window.location.search);
    document.getElementById('content').innerHTML = params.get('message');
}

// GOOD: Sanitize before display
function displayFromUrlGood() {
    const params = new URLSearchParams(window.location.search);
    const message = params.get('message');
    document.getElementById('content').textContent = message;
}

// HTML escaping function
function escapeHtml2(unsafe) {
    return unsafe
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}

// JavaScript escaping (for embedding in <script> tags)
function escapeJs(unsafe) {
    return unsafe
        .replace(/\\/g, '\\\\')
        .replace(/'/g, "\\'")
        .replace(/"/g, '\\"')
        .replace(/\n/g, '\\n')
        .replace(/\r/g, '\\r')
        .replace(/</g, '\\x3c')
        .replace(/>/g, '\\x3e');
}

// URL encoding
function escapeUrl(unsafe) {
    return encodeURIComponent(unsafe);
}

// Context-aware escaping
function safeDomInsert(element, content, context) {
    switch (context) {
        case 'text':
            element.textContent = content;
            break;
            
        case 'attribute':
            element.setAttribute('data-value', escapeHtml2(content));
            break;
            
        case 'url':
            element.href = escapeUrl(content);
            break;
            
        case 'html':
            // Use DOMPurify if you must allow HTML
            // element.innerHTML = DOMPurify.sanitize(content);
            break;
            
        default:
            throw new Error('Invalid context');
    }
}

// DOMPurify usage (safe HTML sanitization)
// import DOMPurify from 'dompurify';

function renderSafeHtml(userHtml) {
    const clean = DOMPurify.sanitize(userHtml, {
        ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
        ALLOWED_ATTR: ['href']
    });
    
    document.getElementById('content').innerHTML = clean;
}

// CSP Implementation
// Setting CSP via HTTP header (server-side)
/*
Content-Security-Policy: 
    default-src 'self'; 
    script-src 'self' 'nonce-xyz123' https://trusted.cdn.com; 
    style-src 'self' 'unsafe-inline'; 
    img-src 'self' data: https:; 
    font-src 'self' https://fonts.gstatic.com; 
    connect-src 'self' https://api.example.com; 
    frame-ancestors 'none'; 
    base-uri 'self'; 
    form-action 'self'
*/

// Setting CSP via meta tag
const cspMeta = document.createElement('meta');
cspMeta.httpEquiv = 'Content-Security-Policy';
cspMeta.content = "default-src 'self'; script-src 'self' 'unsafe-inline'";
document.head.appendChild(cspMeta);

// Nonce-based CSP
// Server generates nonce
function generateCspNonce() {
    const crypto = require('crypto');
    return crypto.randomBytes(16).toString('base64');
}

// In Express.js middleware
function cspMiddleware(req, res, next) {
    const nonce = generateCspNonce();
    res.locals.nonce = nonce;
    
    res.setHeader(
        'Content-Security-Policy',
        `script-src 'self' 'nonce-${nonce}'`
    );
    
    next();
}

// In HTML template
// <script nonce="<%= nonce %>">
//   console.log('This script is allowed');
// </script>

// CSP violation reporting
const cspWithReporting = `
    default-src 'self'; 
    script-src 'self'; 
    report-uri /csp-violation-report-endpoint; 
    report-to csp-endpoint
`;

// Report-To header
const reportTo = JSON.stringify({
    group: 'csp-endpoint',
    max_age: 10886400,
    endpoints: [{url: 'https://example.com/csp-reports'}]
});

// Handle CSP violation reports
function handleCspReport(req, res) {
    const report = req.body;
    console.log('CSP Violation:', {
        documentUri: report['document-uri'],
        violatedDirective: report['violated-directive'],
        blockedUri: report['blocked-uri'],
        sourceFile: report['source-file'],
        lineNumber: report['line-number']
    });
    
    res.status(204).send();
}

// Trusted Types API (modern XSS prevention)
if (window.trustedTypes && trustedTypes.createPolicy) {
    const policy = trustedTypes.createPolicy('myPolicy', {
        createHTML: (string) => {
            // Sanitize string
            return DOMPurify.sanitize(string);
        },
        createScriptURL: (string) => {
            // Validate script URL
            const url = new URL(string, window.location.href);
            if (url.origin === window.location.origin) {
                return string;
            }
            throw new TypeError('Invalid script URL');
        }
    });
    
    // Usage with Trusted Types
    const html = policy.createHTML('<b>Safe HTML</b>');
    element.innerHTML = html; // No XSS!
}

// XSS protection headers
function setSecurityHeaders(res) {
    // XSS Protection (legacy browsers)
    res.setHeader('X-XSS-Protection', '1; mode=block');
    
    // Prevent MIME sniffing
    res.setHeader('X-Content-Type-Options', 'nosniff');
    
    // Frame options (clickjacking prevention)
    res.setHeader('X-Frame-Options', 'DENY');
    
    // Referrer policy
    res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
}

// Safe JSON embedding in HTML
function safeJsonEmbed(data) {
    return JSON.stringify(data)
        .replace(/</g, '\\u003c')
        .replace(/>/g, '\\u003e')
        .replace(/&/g, '\\u0026');
}

// In HTML:
// <script>
//   const data = <%= safeJsonEmbed(userData) %>;
// </script>

// Safe event handler binding
// BAD: Inline event handlers
// <button onclick="userFunction()">Click</button>

// GOOD: addEventListener
const button = document.getElementById('myButton');
button.addEventListener('click', function(e) {
    e.preventDefault();
    // Safe handler
});

// Sanitize URLs to prevent javascript: protocol
function isSafeUrl(url) {
    try {
        const parsed = new URL(url, window.location.href);
        return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);
    } catch {
        return false;
    }
}

function setSafeHref(element, url) {
    if (isSafeUrl(url)) {
        element.href = url;
    } else {
        console.error('Unsafe URL blocked:', url);
        element.href = '#';
    }
}
Key Points: Never use innerHTML with user input - use textContent or DOM methods. Never use eval() or Function() with user input. Implement CSP headers to restrict script sources. Use nonce or hash for inline scripts. DOMPurify for safe HTML sanitization. Trusted Types API for modern browsers. Context-aware escaping (HTML, JavaScript, URL, CSS). Validate URLs to prevent javascript: protocol. Report CSP violations for monitoring.

25.3 Secure Coding Practices and Code Review

OWASP Top 10 Web Vulnerabilities

Vulnerability Risk Prevention
Broken Access Control Unauthorized access to resources Enforce authorization checks
Cryptographic Failures Sensitive data exposure Encrypt data at rest and transit
Injection SQL/Command/LDAP injection Parameterized queries, validation
Insecure Design Missing security controls Threat modeling, secure design
Security Misconfiguration Default configs, verbose errors Harden configs, disable defaults
Vulnerable Components Outdated libraries with CVEs Regular updates, dependency scanning
Authentication Failures Weak auth, session management Strong passwords, MFA, secure sessions
Data Integrity Failures Insecure deserialization Validate serialized data, use safe formats
Logging Failures Insufficient monitoring Comprehensive logging, alerting
SSRF Server-side request forgery Whitelist URLs, validate destinations

Secure Coding Principles

Principle Description Example
Defense in Depth Multiple security layers Input validation + CSP + WAF
Least Privilege Minimum necessary permissions Read-only DB user for queries
Fail Securely Deny by default on errors Default to unauthorized
Complete Mediation Check every access Validate auth on each request
Open Design Security through design, not obscurity Public algorithms, secret keys
Separation of Duties Split critical operations Approver ≠ Requester

Code Review Security Checklist

Category What to Check Red Flags
Input Validation All inputs validated and sanitized Direct use of user input
Authentication Proper auth checks, session management Missing auth, weak passwords
Authorization Access control enforced Missing permission checks
Cryptography Strong algorithms, proper key management Hardcoded secrets, weak ciphers
Error Handling No sensitive info in errors Stack traces to users
Logging Security events logged Passwords in logs
Dependencies Up-to-date, no known vulnerabilities Outdated libraries

Example: Secure coding practices

// Secure configuration management
// BAD: Hardcoded secrets
const API_KEY = 'sk_live_abc123xyz'; // NEVER DO THIS!
const DB_PASSWORD = 'mypassword123';

// GOOD: Environment variables
const API_KEY2 = process.env.API_KEY;
const DB_PASSWORD2 = process.env.DB_PASSWORD;

// BETTER: Use a secrets manager
const {SecretManagerServiceClient} = require('@google-cloud/secret-manager');
const client = new SecretManagerServiceClient();

async function getSecret(secretName) {
    const [version] = await client.accessSecretVersion({
        name: secretName
    });
    return version.payload.data.toString();
}

// Error handling - don't leak sensitive info
// BAD: Verbose error messages
function handleErrorBad(error, res) {
    res.status(500).json({
        error: error.message,
        stack: error.stack, // Exposes internal structure!
        query: error.sql    // Exposes SQL queries!
    });
}

// GOOD: Generic error messages
function handleErrorGood(error, res) {
    console.error('Internal error:', error); // Log for debugging
    
    res.status(500).json({
        error: 'An error occurred. Please try again later.',
        requestId: generateRequestId() // For support tracking
    });
}

// Secure session management
const session = require('express-session');
const RedisStore = require('connect-redis')(session);

app.use(session({
    store: new RedisStore({client: redisClient}),
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: true,        // HTTPS only
        httpOnly: true,      // No JavaScript access
        sameSite: 'strict',  // CSRF protection
        maxAge: 3600000     // 1 hour
    },
    name: 'sessionId'       // Don't use default name
}));

// Authorization middleware
function requireAuth(req, res, next) {
    if (!req.session.userId) {
        return res.status(401).json({error: 'Unauthorized'});
    }
    next();
}

function requireRole(role) {
    return async (req, res, next) => {
        const user = await User.findById(req.session.userId);
        
        if (!user || user.role !== role) {
            return res.status(403).json({error: 'Forbidden'});
        }
        
        next();
    };
}

// Usage
app.get('/admin', requireAuth, requireRole('admin'), (req, res) => {
    // Admin-only endpoint
});

// Secure file uploads
const multer = require('multer');
const path = require('path');

const storage = multer.diskStorage({
    destination: './uploads/',
    filename: (req, file, cb) => {
        // Generate safe filename
        const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
        cb(null, uniqueSuffix + path.extname(file.originalname));
    }
});

const upload = multer({
    storage: storage,
    limits: {
        fileSize: 5 * 1024 * 1024 // 5MB limit
    },
    fileFilter: (req, file, cb) => {
        // Whitelist file types
        const allowedTypes = /jpeg|jpg|png|pdf/;
        const extname = allowedTypes.test(
            path.extname(file.originalname).toLowerCase()
        );
        const mimetype = allowedTypes.test(file.mimetype);
        
        if (extname && mimetype) {
            cb(null, true);
        } else {
            cb(new Error('Invalid file type'));
        }
    }
});

// Resource access control
class ResourceAccessControl {
    static canAccess(user, resource, action) {
        // Check if user owns resource
        if (resource.userId === user.id) {
            return true;
        }
        
        // Check role-based permissions
        const permissions = {
            admin: ['read', 'write', 'delete'],
            moderator: ['read', 'write'],
            user: ['read']
        };
        
        const userPermissions = permissions[user.role] || [];
        return userPermissions.includes(action);
    }
}

// Usage in endpoint
app.delete('/posts/:id', requireAuth, async (req, res) => {
    const post = await Post.findById(req.params.id);
    const user = await User.findById(req.session.userId);
    
    if (!ResourceAccessControl.canAccess(user, post, 'delete')) {
        return res.status(403).json({error: 'Forbidden'});
    }
    
    await post.delete();
    res.json({success: true});
});

// Secure random number generation
// BAD: Predictable
const badRandom = Math.random();

// GOOD: Cryptographically secure
const crypto = require('crypto');
const goodRandom = crypto.randomBytes(32).toString('hex');

// Generate secure token
function generateSecureToken() {
    return crypto.randomBytes(32).toString('base64url');
}

// Time-constant string comparison (prevent timing attacks)
// BAD: Vulnerable to timing attacks
function compareTokenBad(a, b) {
    return a === b; // Early return leaks info
}

// GOOD: Constant-time comparison
function compareTokenGood(a, b) {
    return crypto.timingSafeEqual(
        Buffer.from(a),
        Buffer.from(b)
    );
}

// Dependency scanning
// package.json scripts
{
    "scripts": {
        "audit": "npm audit",
        "audit:fix": "npm audit fix",
        "audit:check": "npm audit --audit-level=high"
    }
}

// Automated security checks in CI/CD
// .github/workflows/security.yml
/*
name: Security Scan
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run npm audit
        run: npm audit --audit-level=moderate
      - name: Run Snyk
        run: npx snyk test
*/

// Secure API key storage
class ApiKeyManager {
    constructor() {
        this.keys = new Map();
    }
    
    async storeKey(userId, apiKey) {
        const crypto = require('crypto');
        
        // Hash the key (don't store plain text)
        const hash = crypto
            .createHash('sha256')
            .update(apiKey)
            .digest('hex');
        
        await db.apiKeys.insert({
            userId,
            keyHash: hash,
            createdAt: new Date()
        });
        
        // Return key once to user, then forget it
        return apiKey;
    }
    
    async validateKey(apiKey) {
        const hash = crypto
            .createHash('sha256')
            .update(apiKey)
            .digest('hex');
        
        const record = await db.apiKeys.findOne({keyHash: hash});
        return !!record;
    }
}

// Logging security events
class SecurityLogger {
    static logAuthSuccess(userId, ip) {
        logger.info('Auth success', {userId, ip, event: 'LOGIN'});
    }
    
    static logAuthFailure(username, ip, reason) {
        logger.warn('Auth failure', {username, ip, reason, event: 'LOGIN_FAIL'});
    }
    
    static logAccessDenied(userId, resource, action) {
        logger.warn('Access denied', {
            userId,
            resource,
            action,
            event: 'ACCESS_DENIED'
        });
    }
    
    static logSensitiveOperation(userId, operation, details) {
        logger.info('Sensitive operation', {
            userId,
            operation,
            details,
            event: 'SENSITIVE_OP'
        });
    }
}

// Never log sensitive data
// BAD
logger.info('User login', {password: userPassword}); // NEVER!

// GOOD
logger.info('User login', {userId: user.id, timestamp: new Date()});

// Secure headers middleware
function securityHeaders(req, res, next) {
    // Prevent clickjacking
    res.setHeader('X-Frame-Options', 'DENY');
    
    // Prevent MIME sniffing
    res.setHeader('X-Content-Type-Options', 'nosniff');
    
    // XSS protection
    res.setHeader('X-XSS-Protection', '1; mode=block');
    
    // HTTPS only
    res.setHeader('Strict-Transport-Security', 
        'max-age=31536000; includeSubDomains; preload');
    
    // Referrer policy
    res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
    
    // Permissions policy
    res.setHeader('Permissions-Policy', 
        'geolocation=(), microphone=(), camera=()');
    
    next();
}

app.use(securityHeaders);
Key Points: Never hardcode secrets - use environment variables or secrets manager. Don't expose detailed errors to users. Implement proper authorization checks. Use secure session management (httpOnly, secure, sameSite cookies). Whitelist file uploads by type and size. Use cryptographically secure random for tokens. Constant-time comparison for sensitive strings. Run npm audit regularly. Log security events without sensitive data. Apply security headers. Follow OWASP Top 10. Code review for security issues. Defense in depth approach.

25.4 Data Encryption and Hashing

Encryption vs Hashing

Feature Encryption Hashing
Reversible Yes (with key) No (one-way)
Purpose Protect data confidentiality Verify data integrity
Use Case Store credit cards, encrypt messages Store passwords, checksums
Output Variable length ciphertext Fixed length hash digest
Key Required Yes No (salt optional)

Encryption Algorithms

Algorithm Type Key Size Use Case
AES Symmetric 128, 192, 256 bits General purpose encryption
RSA Asymmetric 2048, 4096 bits Key exchange, digital signatures
ChaCha20 Symmetric stream 256 bits Mobile devices, TLS
ECC Asymmetric 256, 384 bits Smaller keys, IoT devices

Hashing Algorithms

Algorithm Output Size Status Use Case
bcrypt 60 chars ✓ Secure Password hashing (recommended)
scrypt Variable ✓ Secure Password hashing, key derivation
Argon2 Variable ✓ Secure (newest) Password hashing (best)
SHA-256 256 bits ✓ Secure Checksums, NOT passwords
SHA-1 160 bits ✗ Broken Deprecated
MD5 128 bits ✗ Broken Never use

Example: Encryption and hashing

const crypto = require('crypto');

// === PASSWORD HASHING ===

// BAD: Plain MD5 (broken, too fast)
function hashPasswordBad(password) {
    return crypto.createHash('md5').update(password).digest('hex');
    // Vulnerable to rainbow tables and brute force
}

// GOOD: bcrypt with salt
const bcrypt = require('bcrypt');

async function hashPassword(password) {
    const saltRounds = 12; // Higher = more secure but slower
    const hash = await bcrypt.hash(password, saltRounds);
    return hash;
}

async function verifyPassword(password, hash) {
    return await bcrypt.compare(password, hash);
}

// Usage
async function registerUser(username, password) {
    const passwordHash = await hashPassword(password);
    
    await db.users.insert({
        username,
        passwordHash, // Store hash, never plain password
        createdAt: new Date()
    });
}

async function loginUser(username, password) {
    const user = await db.users.findOne({username});
    
    if (!user) {
        return false;
    }
    
    const isValid = await verifyPassword(password, user.passwordHash);
    return isValid ? user : null;
}

// === SYMMETRIC ENCRYPTION (AES) ===

// Encrypt data with AES-256-GCM
function encrypt(plaintext, key) {
    // Generate random IV (initialization vector)
    const iv = crypto.randomBytes(16);
    
    // Create cipher
    const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
    
    // Encrypt
    let encrypted = cipher.update(plaintext, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    
    // Get authentication tag
    const authTag = cipher.getAuthTag();
    
    // Return IV + authTag + encrypted data
    return {
        iv: iv.toString('hex'),
        authTag: authTag.toString('hex'),
        encrypted: encrypted
    };
}

function decrypt(encryptedData, key) {
    // Create decipher
    const decipher = crypto.createDecipheriv(
        'aes-256-gcm',
        key,
        Buffer.from(encryptedData.iv, 'hex')
    );
    
    // Set authentication tag
    decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
    
    // Decrypt
    let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    
    return decrypted;
}

// Usage
const encryptionKey = crypto.randomBytes(32); // 256 bits
const message = 'Sensitive data';

const encrypted = encrypt(message, encryptionKey);
console.log('Encrypted:', encrypted);

const decrypted = decrypt(encrypted, encryptionKey);
console.log('Decrypted:', decrypted); // 'Sensitive data'

// === KEY DERIVATION ===

// Derive encryption key from password using PBKDF2
function deriveKey(password, salt) {
    return crypto.pbkdf2Sync(
        password,
        salt,
        100000,  // iterations
        32,      // key length (256 bits)
        'sha256' // hash algorithm
    );
}

// Encrypt with password
function encryptWithPassword(plaintext, password) {
    const salt = crypto.randomBytes(16);
    const key = deriveKey(password, salt);
    const encrypted = encrypt(plaintext, key);
    
    return {
        salt: salt.toString('hex'),
        ...encrypted
    };
}

function decryptWithPassword(encryptedData, password) {
    const salt = Buffer.from(encryptedData.salt, 'hex');
    const key = deriveKey(password, salt);
    
    return decrypt({
        iv: encryptedData.iv,
        authTag: encryptedData.authTag,
        encrypted: encryptedData.encrypted
    }, key);
}

// === ASYMMETRIC ENCRYPTION (RSA) ===

// Generate RSA key pair
function generateKeyPair() {
    const {publicKey, privateKey} = crypto.generateKeyPairSync('rsa', {
        modulusLength: 4096,
        publicKeyEncoding: {
            type: 'spki',
            format: 'pem'
        },
        privateKeyEncoding: {
            type: 'pkcs8',
            format: 'pem',
            cipher: 'aes-256-cbc',
            passphrase: process.env.KEY_PASSPHRASE
        }
    });
    
    return {publicKey, privateKey};
}

// Encrypt with public key
function rsaEncrypt(plaintext, publicKey) {
    const buffer = Buffer.from(plaintext, 'utf8');
    const encrypted = crypto.publicEncrypt(publicKey, buffer);
    return encrypted.toString('base64');
}

// Decrypt with private key
function rsaDecrypt(encrypted, privateKey, passphrase) {
    const buffer = Buffer.from(encrypted, 'base64');
    const decrypted = crypto.privateDecrypt(
        {
            key: privateKey,
            passphrase: passphrase
        },
        buffer
    );
    return decrypted.toString('utf8');
}

// Digital signature
function sign(message, privateKey, passphrase) {
    const signer = crypto.createSign('sha256');
    signer.update(message);
    signer.end();
    
    return signer.sign({
        key: privateKey,
        passphrase: passphrase
    }, 'base64');
}

function verify(message, signature, publicKey) {
    const verifier = crypto.createVerify('sha256');
    verifier.update(message);
    verifier.end();
    
    return verifier.verify(publicKey, signature, 'base64');
}

// === HMAC (Hash-based Message Authentication Code) ===

function generateHmac(message, secret) {
    return crypto
        .createHmac('sha256', secret)
        .update(message)
        .digest('hex');
}

function verifyHmac(message, hmac, secret) {
    const calculated = generateHmac(message, secret);
    return crypto.timingSafeEqual(
        Buffer.from(hmac),
        Buffer.from(calculated)
    );
}

// Use for API request signing
function signRequest(payload, apiSecret) {
    const timestamp = Date.now();
    const message = `${timestamp}.${JSON.stringify(payload)}`;
    const signature = generateHmac(message, apiSecret);
    
    return {
        timestamp,
        payload,
        signature
    };
}

function verifyRequest(request, apiSecret, maxAge = 300000) {
    const {timestamp, payload, signature} = request;
    
    // Check timestamp (prevent replay attacks)
    if (Date.now() - timestamp > maxAge) {
        return false;
    }
    
    // Verify signature
    const message = `${timestamp}.${JSON.stringify(payload)}`;
    return verifyHmac(message, signature, apiSecret);
}

// === SECURE TOKEN GENERATION ===

function generateToken(length = 32) {
    return crypto.randomBytes(length).toString('base64url');
}

// JWT-like token (simplified)
function createToken(payload, secret, expiresIn = 3600) {
    const header = {alg: 'HS256', typ: 'JWT'};
    const claims = {
        ...payload,
        iat: Math.floor(Date.now() / 1000),
        exp: Math.floor(Date.now() / 1000) + expiresIn
    };
    
    const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
    const encodedPayload = Buffer.from(JSON.stringify(claims)).toString('base64url');
    const signature = generateHmac(`${encodedHeader}.${encodedPayload}`, secret);
    
    return `${encodedHeader}.${encodedPayload}.${signature}`;
}

function verifyToken(token, secret) {
    const [encodedHeader, encodedPayload, signature] = token.split('.');
    
    // Verify signature
    const expectedSignature = generateHmac(
        `${encodedHeader}.${encodedPayload}`,
        secret
    );
    
    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
        throw new Error('Invalid signature');
    }
    
    // Decode and check expiration
    const payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString());
    
    if (payload.exp < Math.floor(Date.now() / 1000)) {
        throw new Error('Token expired');
    }
    
    return payload;
}

// === ENCRYPTING DATA AT REST ===

class EncryptedStorage {
    constructor(key) {
        this.key = key;
    }
    
    set(key, value) {
        const encrypted = encrypt(JSON.stringify(value), this.key);
        localStorage.setItem(key, JSON.stringify(encrypted));
    }
    
    get(key) {
        const stored = localStorage.getItem(key);
        if (!stored) return null;
        
        const encrypted = JSON.parse(stored);
        const decrypted = decrypt(encrypted, this.key);
        return JSON.parse(decrypted);
    }
    
    remove(key) {
        localStorage.removeItem(key);
    }
}

// Usage
const masterKey = deriveKey(userPassword, userSalt);
const storage = new EncryptedStorage(masterKey);

storage.set('sensitiveData', {ssn: '123-45-6789', account: '9876543210'});
const data = storage.get('sensitiveData');

// === WEB CRYPTO API (Browser) ===

async function generateAesKey() {
    return await window.crypto.subtle.generateKey(
        {
            name: 'AES-GCM',
            length: 256
        },
        true,  // extractable
        ['encrypt', 'decrypt']
    );
}

async function encryptBrowser(plaintext, key) {
    const iv = window.crypto.getRandomValues(new Uint8Array(12));
    const encoded = new TextEncoder().encode(plaintext);
    
    const ciphertext = await window.crypto.subtle.encrypt(
        {
            name: 'AES-GCM',
            iv: iv
        },
        key,
        encoded
    );
    
    return {
        iv: Array.from(iv),
        ciphertext: Array.from(new Uint8Array(ciphertext))
    };
}

async function decryptBrowser(encrypted, key) {
    const decrypted = await window.crypto.subtle.decrypt(
        {
            name: 'AES-GCM',
            iv: new Uint8Array(encrypted.iv)
        },
        key,
        new Uint8Array(encrypted.ciphertext)
    );
    
    return new TextDecoder().decode(decrypted);
}

// === KEY MANAGEMENT BEST PRACTICES ===

// Store keys securely
// - Use environment variables
// - Use a secrets manager (AWS Secrets Manager, HashiCorp Vault)
// - Never commit keys to version control
// - Rotate keys regularly
// - Use different keys for different environments

// Key rotation example
class KeyRotation {
    constructor() {
        this.currentKey = null;
        this.oldKeys = [];
    }
    
    rotateKey() {
        if (this.currentKey) {
            this.oldKeys.push(this.currentKey);
        }
        this.currentKey = crypto.randomBytes(32);
        
        // Keep only last 3 old keys
        if (this.oldKeys.length > 3) {
            this.oldKeys.shift();
        }
    }
    
    encrypt(data) {
        return encrypt(data, this.currentKey);
    }
    
    decrypt(encryptedData) {
        // Try current key first
        try {
            return decrypt(encryptedData, this.currentKey);
        } catch (error) {
            // Try old keys
            for (const oldKey of this.oldKeys) {
                try {
                    return decrypt(encryptedData, oldKey);
                } catch {
                    continue;
                }
            }
            throw new Error('Failed to decrypt with any key');
        }
    }
}
Key Points: Use bcrypt/Argon2 for password hashing, never plain MD5/SHA. Use AES-256-GCM for symmetric encryption. Use RSA/ECC for asymmetric encryption. Always use random IV for each encryption. HMAC for message authentication. Derive keys from passwords with PBKDF2/scrypt. Store keys securely (never hardcode). Rotate keys regularly. Use Web Crypto API in browsers. Encryption protects confidentiality, hashing verifies integrity. Salt passwords to prevent rainbow tables.

25.5 Authentication and Authorization Patterns

Authentication Methods

Method How It Works Use Case
Session-based Server stores session, client has session ID Traditional web apps
Token-based (JWT) Stateless token with claims APIs, SPAs, mobile apps
OAuth 2.0 Delegated authorization Third-party login
OpenID Connect OAuth 2.0 + identity layer SSO, enterprise auth
API Keys Static key per client Server-to-server
MFA/2FA Multiple authentication factors High security accounts

Authorization Models

Model Description Example
RBAC Role-Based Access Control Admin, User, Guest roles
ABAC Attribute-Based Access Control Rules based on attributes
ACL Access Control List Per-resource permissions
PBAC Policy-Based Access Control Complex business rules

JWT Structure

Part Content Purpose
Header Algorithm, token type Specify signing method
Payload Claims (user ID, exp, etc.) Carry user information
Signature HMAC or RSA signature Verify token integrity

Example: Authentication and authorization

// === JWT AUTHENTICATION ===

const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET;

// Generate JWT token
function generateJWT(user) {
    const payload = {
        userId: user.id,
        email: user.email,
        role: user.role
    };
    
    const token = jwt.sign(
        payload,
        JWT_SECRET,
        {
            expiresIn: '1h',
            issuer: 'myapp.com',
            audience: 'myapp-users'
        }
    );
    
    return token;
}

// Verify JWT token
function verifyJWT(token) {
    try {
        const decoded = jwt.verify(token, JWT_SECRET, {
            issuer: 'myapp.com',
            audience: 'myapp-users'
        });
        return decoded;
    } catch (error) {
        if (error.name === 'TokenExpiredError') {
            throw new Error('Token expired');
        } else if (error.name === 'JsonWebTokenError') {
            throw new Error('Invalid token');
        }
        throw error;
    }
}

// Authentication middleware
function authenticate(req, res, next) {
    const authHeader = req.headers.authorization;
    
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({error: 'No token provided'});
    }
    
    const token = authHeader.substring(7);
    
    try {
        const decoded = verifyJWT(token);
        req.user = decoded;
        next();
    } catch (error) {
        return res.status(401).json({error: error.message});
    }
}

// === REFRESH TOKEN PATTERN ===

function generateTokenPair(user) {
    const accessToken = jwt.sign(
        {userId: user.id, email: user.email, role: user.role},
        JWT_SECRET,
        {expiresIn: '15m'} // Short-lived
    );
    
    const refreshToken = jwt.sign(
        {userId: user.id, type: 'refresh'},
        process.env.REFRESH_TOKEN_SECRET,
        {expiresIn: '7d'} // Long-lived
    );
    
    return {accessToken, refreshToken};
}

async function refreshAccessToken(refreshToken) {
    try {
        const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
        
        if (decoded.type !== 'refresh') {
            throw new Error('Invalid token type');
        }
        
        // Check if refresh token is revoked
        const isRevoked = await checkTokenRevocation(refreshToken);
        if (isRevoked) {
            throw new Error('Token revoked');
        }
        
        // Generate new access token
        const user = await User.findById(decoded.userId);
        const accessToken = generateJWT(user);
        
        return accessToken;
    } catch (error) {
        throw new Error('Invalid refresh token');
    }
}

// === ROLE-BASED ACCESS CONTROL (RBAC) ===

const ROLES = {
    ADMIN: 'admin',
    MODERATOR: 'moderator',
    USER: 'user',
    GUEST: 'guest'
};

const PERMISSIONS = {
    READ_POSTS: 'read:posts',
    WRITE_POSTS: 'write:posts',
    DELETE_POSTS: 'delete:posts',
    MANAGE_USERS: 'manage:users',
    VIEW_ANALYTICS: 'view:analytics'
};

const ROLE_PERMISSIONS = {
    [ROLES.ADMIN]: [
        PERMISSIONS.READ_POSTS,
        PERMISSIONS.WRITE_POSTS,
        PERMISSIONS.DELETE_POSTS,
        PERMISSIONS.MANAGE_USERS,
        PERMISSIONS.VIEW_ANALYTICS
    ],
    [ROLES.MODERATOR]: [
        PERMISSIONS.READ_POSTS,
        PERMISSIONS.WRITE_POSTS,
        PERMISSIONS.DELETE_POSTS
    ],
    [ROLES.USER]: [
        PERMISSIONS.READ_POSTS,
        PERMISSIONS.WRITE_POSTS
    ],
    [ROLES.GUEST]: [
        PERMISSIONS.READ_POSTS
    ]
};

function hasPermission(userRole, permission) {
    const permissions = ROLE_PERMISSIONS[userRole] || [];
    return permissions.includes(permission);
}

// Authorization middleware
function requirePermission(permission) {
    return (req, res, next) => {
        if (!req.user) {
            return res.status(401).json({error: 'Unauthorized'});
        }
        
        if (!hasPermission(req.user.role, permission)) {
            return res.status(403).json({error: 'Forbidden'});
        }
        
        next();
    };
}

// Usage
app.delete('/posts/:id', 
    authenticate, 
    requirePermission(PERMISSIONS.DELETE_POSTS),
    async (req, res) => {
        // Delete post
    }
);

// === ATTRIBUTE-BASED ACCESS CONTROL (ABAC) ===

class AbacPolicy {
    static canAccess(subject, resource, action, context) {
        // Subject: who is trying to access (user)
        // Resource: what is being accessed (document, post, etc.)
        // Action: what operation (read, write, delete)
        // Context: environmental factors (time, location, etc.)
        
        // Rule 1: Users can read their own posts
        if (action === 'read' && resource.authorId === subject.userId) {
            return true;
        }
        
        // Rule 2: Admins can do anything
        if (subject.role === 'admin') {
            return true;
        }
        
        // Rule 3: Users can edit posts during business hours only
        if (action === 'write' && resource.authorId === subject.userId) {
            const hour = new Date().getHours();
            return hour >= 9 && hour < 17; // 9 AM - 5 PM
        }
        
        // Rule 4: Moderators can delete flagged posts
        if (action === 'delete' && subject.role === 'moderator' && resource.flagged) {
            return true;
        }
        
        // Rule 5: Premium users can access premium content
        if (resource.type === 'premium' && subject.subscription === 'premium') {
            return true;
        }
        
        return false;
    }
}

// Usage
app.put('/posts/:id', authenticate, async (req, res) => {
    const post = await Post.findById(req.params.id);
    const context = {timestamp: Date.now()};
    
    if (!AbacPolicy.canAccess(req.user, post, 'write', context)) {
        return res.status(403).json({error: 'Forbidden'});
    }
    
    // Update post
});

// === OAUTH 2.0 IMPLEMENTATION ===

// Authorization code flow
app.get('/auth/google', (req, res) => {
    const params = new URLSearchParams({
        client_id: process.env.GOOGLE_CLIENT_ID,
        redirect_uri: 'http://localhost:3000/auth/google/callback',
        response_type: 'code',
        scope: 'openid email profile',
        state: generateSecureToken() // CSRF protection
    });
    
    res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params}`);
});

app.get('/auth/google/callback', async (req, res) => {
    const {code, state} = req.query;
    
    // Verify state (CSRF protection)
    if (!verifyState(state)) {
        return res.status(400).json({error: 'Invalid state'});
    }
    
    // Exchange code for token
    const response = await fetch('https://oauth2.googleapis.com/token', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
            code,
            client_id: process.env.GOOGLE_CLIENT_ID,
            client_secret: process.env.GOOGLE_CLIENT_SECRET,
            redirect_uri: 'http://localhost:3000/auth/google/callback',
            grant_type: 'authorization_code'
        })
    });
    
    const tokens = await response.json();
    
    // Get user info
    const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
        headers: {Authorization: `Bearer ${tokens.access_token}`}
    });
    
    const googleUser = await userResponse.json();
    
    // Create or update user in database
    let user = await User.findOne({googleId: googleUser.id});
    
    if (!user) {
        user = await User.create({
            googleId: googleUser.id,
            email: googleUser.email,
            name: googleUser.name,
            avatar: googleUser.picture
        });
    }
    
    // Generate JWT for our app
    const appToken = generateJWT(user);
    
    res.json({token: appToken});
});

// === MULTI-FACTOR AUTHENTICATION (MFA) ===

const speakeasy = require('speakeasy');
const QRCode = require('qrcode');

// Setup MFA
async function setupMFA(user) {
    const secret = speakeasy.generateSecret({
        name: `MyApp (${user.email})`,
        issuer: 'MyApp'
    });
    
    // Generate QR code
    const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
    
    // Store secret in database (encrypted!)
    await User.update(user.id, {
        mfaSecret: encrypt(secret.base32, encryptionKey),
        mfaEnabled: false // Not enabled until verified
    });
    
    return {
        secret: secret.base32,
        qrCode: qrCodeUrl
    };
}

// Verify MFA token
function verifyMFA(token, secret) {
    return speakeasy.totp.verify({
        secret: secret,
        encoding: 'base32',
        token: token,
        window: 2 // Allow 2 time steps before/after
    });
}

// Login with MFA
async function loginWithMFA(email, password, mfaToken) {
    // Verify password
    const user = await User.findOne({email});
    const passwordValid = await verifyPassword(password, user.passwordHash);
    
    if (!passwordValid) {
        throw new Error('Invalid credentials');
    }
    
    // If MFA enabled, verify token
    if (user.mfaEnabled) {
        const secret = decrypt(user.mfaSecret, encryptionKey);
        const mfaValid = verifyMFA(mfaToken, secret);
        
        if (!mfaValid) {
            throw new Error('Invalid MFA token');
        }
    }
    
    return generateJWT(user);
}

// === API KEY AUTHENTICATION ===

async function generateApiKey(userId) {
    const apiKey = `sk_${generateSecureToken(32)}`;
    const hashedKey = crypto.createHash('sha256').update(apiKey).digest('hex');
    
    await db.apiKeys.insert({
        userId,
        keyHash: hashedKey,
        createdAt: new Date(),
        lastUsed: null
    });
    
    return apiKey; // Show once, then forget
}

async function authenticateApiKey(apiKey) {
    const hashedKey = crypto.createHash('sha256').update(apiKey).digest('hex');
    const record = await db.apiKeys.findOne({keyHash: hashedKey});
    
    if (!record) {
        return null;
    }
    
    // Update last used
    await db.apiKeys.update(record.id, {lastUsed: new Date()});
    
    return await User.findById(record.userId);
}

// API key middleware
async function authenticateApi(req, res, next) {
    const apiKey = req.headers['x-api-key'];
    
    if (!apiKey) {
        return res.status(401).json({error: 'API key required'});
    }
    
    const user = await authenticateApiKey(apiKey);
    
    if (!user) {
        return res.status(401).json({error: 'Invalid API key'});
    }
    
    req.user = user;
    next();
}
Key Points: JWT for stateless auth - include expiration and issuer claims. Use refresh tokens for long-lived sessions. RBAC for simple permission models. ABAC for complex rules. OAuth 2.0 for third-party login. MFA for high-security accounts. Hash API keys before storage. Validate tokens on every request. Use short-lived access tokens (15 min) with refresh tokens (7 days). Store refresh tokens securely, support revocation. Always use HTTPS for auth.

25.6 Security Headers and HTTPS Integration

Essential Security Headers

Header Purpose Recommended Value
Strict-Transport-Security Force HTTPS max-age=31536000; includeSubDomains
Content-Security-Policy Prevent XSS, injection default-src 'self'; script-src 'self'
X-Frame-Options Prevent clickjacking DENY or SAMEORIGIN
X-Content-Type-Options Prevent MIME sniffing nosniff
Referrer-Policy Control referrer info strict-origin-when-cross-origin
Permissions-Policy Control browser features geolocation=(), camera=()
X-XSS-Protection XSS filter (legacy) 1; mode=block

CORS Headers

Header Purpose Example Value
Access-Control-Allow-Origin Allowed origins https://example.com
Access-Control-Allow-Methods Allowed HTTP methods GET, POST, PUT, DELETE
Access-Control-Allow-Headers Allowed request headers Content-Type, Authorization
Access-Control-Allow-Credentials Allow cookies true
Access-Control-Max-Age Preflight cache time 86400 (24 hours)

HTTPS Best Practices

Practice Description Benefit
TLS 1.3 Use latest TLS version Faster, more secure
Strong Ciphers Disable weak ciphers Prevent downgrade attacks
HSTS Force HTTPS always Prevent protocol downgrade
Certificate Pinning Pin expected certificates Prevent MITM with fake certs
OCSP Stapling Include cert status Faster cert validation

Example: Security headers and HTTPS

// === SECURITY HEADERS MIDDLEWARE ===

// Using helmet (recommended)
const helmet = require('helmet');

app.use(helmet({
    contentSecurityPolicy: {
        directives: {
            defaultSrc: ["'self'"],
            scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.example.com"],
            styleSrc: ["'self'", "'unsafe-inline'"],
            imgSrc: ["'self'", "data:", "https:"],
            connectSrc: ["'self'", "https://api.example.com"],
            fontSrc: ["'self'", "https://fonts.gstatic.com"],
            objectSrc: ["'none'"],
            mediaSrc: ["'self'"],
            frameSrc: ["'none'"]
        }
    },
    hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true
    },
    frameguard: {
        action: 'deny'
    },
    noSniff: true,
    xssFilter: true,
    referrerPolicy: {
        policy: 'strict-origin-when-cross-origin'
    },
    permissionsPolicy: {
        features: {
            geolocation: ["'none'"],
            microphone: ["'none'"],
            camera: ["'none'"],
            payment: ["'self'"]
        }
    }
}));

// Manual security headers
function securityHeaders2(req, res, next) {
    // HTTPS enforcement
    res.setHeader(
        'Strict-Transport-Security',
        'max-age=31536000; includeSubDomains; preload'
    );
    
    // XSS Protection
    res.setHeader('X-XSS-Protection', '1; mode=block');
    
    // Prevent clickjacking
    res.setHeader('X-Frame-Options', 'DENY');
    
    // Prevent MIME sniffing
    res.setHeader('X-Content-Type-Options', 'nosniff');
    
    // Referrer policy
    res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
    
    // Content Security Policy
    res.setHeader(
        'Content-Security-Policy',
        "default-src 'self'; script-src 'self' 'unsafe-inline'"
    );
    
    // Permissions Policy
    res.setHeader(
        'Permissions-Policy',
        'geolocation=(), microphone=(), camera=()'
    );
    
    next();
}

// === CORS CONFIGURATION ===

const cors = require('cors');

// Simple CORS
app.use(cors({
    origin: 'https://example.com',
    credentials: true
}));

// Advanced CORS with whitelist
const allowedOrigins = [
    'https://example.com',
    'https://app.example.com',
    'http://localhost:3000'
];

app.use(cors({
    origin: function(origin, callback) {
        // Allow requests with no origin (mobile apps, curl, etc.)
        if (!origin) return callback(null, true);
        
        if (allowedOrigins.includes(origin)) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
    exposedHeaders: ['X-Total-Count', 'X-Page-Number'],
    maxAge: 86400 // 24 hours
}));

// Manual CORS middleware
function corsMiddleware(req, res, next) {
    const origin = req.headers.origin;
    
    if (allowedOrigins.includes(origin)) {
        res.setHeader('Access-Control-Allow-Origin', origin);
        res.setHeader('Access-Control-Allow-Credentials', 'true');
    }
    
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.setHeader('Access-Control-Max-Age', '86400');
    
    // Handle preflight
    if (req.method === 'OPTIONS') {
        return res.status(204).end();
    }
    
    next();
}

// === HTTPS ENFORCEMENT ===

// Redirect HTTP to HTTPS
function forceHttps(req, res, next) {
    if (req.secure || req.headers['x-forwarded-proto'] === 'https') {
        return next();
    }
    
    res.redirect(301, `https://${req.hostname}${req.url}`);
}

app.use(forceHttps);

// Create HTTPS server
const https = require('https');
const fs = require('fs');

const httpsOptions = {
    key: fs.readFileSync('./certs/private-key.pem'),
    cert: fs.readFileSync('./certs/certificate.pem'),
    ca: fs.readFileSync('./certs/ca-bundle.pem'),
    
    // TLS configuration
    minVersion: 'TLSv1.3',
    ciphers: [
        'TLS_AES_128_GCM_SHA256',
        'TLS_AES_256_GCM_SHA384',
        'TLS_CHACHA20_POLY1305_SHA256'
    ].join(':'),
    
    // Prefer server cipher order
    honorCipherOrder: true,
    
    // Enable OCSP stapling
    // requestOCSP: true
};

https.createServer(httpsOptions, app).listen(443, () => {
    console.log('HTTPS server running on port 443');
});

// === SUBRESOURCE INTEGRITY (SRI) ===

// Generate SRI hash
function generateSriHash(content) {
    const hash = crypto.createHash('sha384').update(content).digest('base64');
    return `sha384-${hash}`;
}

// Usage in HTML
// <script 
//   src="https://cdn.example.com/library.js"
//   integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
//   crossorigin="anonymous"
// ></script>

// === CERTIFICATE PINNING (Mobile/Native Apps) ===

// HTTP Public Key Pinning header (deprecated, use cert pinning in app)
// res.setHeader(
//     'Public-Key-Pins',
//     'pin-sha256="base64=="; max-age=5184000; includeSubDomains'
// );

// Certificate pinning in Node.js
const tls = require('tls');

const pinnedFingerprint = 'AA:BB:CC:DD:EE:FF:...';

function checkCertificate(socket) {
    const cert = socket.getPeerCertificate();
    const fingerprint = cert.fingerprint256;
    
    if (fingerprint !== pinnedFingerprint) {
        throw new Error('Certificate pinning failed');
    }
}

// === RATE LIMITING WITH HEADERS ===

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // Limit each IP to 100 requests per windowMs
    standardHeaders: true, // Return rate limit info in headers
    legacyHeaders: false,
    handler: (req, res) => {
        res.status(429).json({
            error: 'Too many requests',
            retryAfter: req.rateLimit.resetTime
        });
    }
});

app.use('/api/', limiter);

// Custom rate limit headers
function rateLimitHeaders(req, res, next) {
    res.setHeader('X-RateLimit-Limit', '100');
    res.setHeader('X-RateLimit-Remaining', '95');
    res.setHeader('X-RateLimit-Reset', Math.floor(Date.now() / 1000) + 900);
    next();
}

// === SECURE COOKIES ===

// Set secure cookie
function setSecureCookie(res, name, value) {
    res.cookie(name, value, {
        httpOnly: true,      // No JavaScript access
        secure: true,        // HTTPS only
        sameSite: 'strict',  // CSRF protection
        maxAge: 3600000,     // 1 hour
        domain: '.example.com',
        path: '/',
        signed: true         // Sign cookie with secret
    });
}

// === SECURITY.TXT FILE ===

// Serve security.txt for responsible disclosure
app.get('/.well-known/security.txt', (req, res) => {
    res.type('text/plain');
    res.send(`
Contact: security@example.com
Expires: 2026-12-31T23:59:59.000Z
Preferred-Languages: en
Canonical: https://example.com/.well-known/security.txt
Policy: https://example.com/security-policy
Acknowledgments: https://example.com/hall-of-fame
    `.trim());
});

// === SECURITY MONITORING ===

// Log security events
function logSecurityEvent(type, details) {
    console.log(JSON.stringify({
        timestamp: new Date().toISOString(),
        type,
        details,
        level: 'security'
    }));
}

// Monitor suspicious activity
function detectSuspiciousActivity(req) {
    // Check for SQL injection patterns
    const sqlInjectionPattern = /(\bunion\b|\bselect\b|\bdrop\b|\binsert\b)/i;
    if (sqlInjectionPattern.test(req.url) || 
        sqlInjectionPattern.test(JSON.stringify(req.body))) {
        logSecurityEvent('SQL_INJECTION_ATTEMPT', {
            ip: req.ip,
            url: req.url,
            body: req.body
        });
    }
    
    // Check for XSS patterns
    const xssPattern = /(<script|javascript:|onerror=|onload=)/i;
    if (xssPattern.test(JSON.stringify(req.body))) {
        logSecurityEvent('XSS_ATTEMPT', {
            ip: req.ip,
            url: req.url
        });
    }
    
    // Check for path traversal
    if (req.url.includes('../') || req.url.includes('..\\')) {
        logSecurityEvent('PATH_TRAVERSAL_ATTEMPT', {
            ip: req.ip,
            url: req.url
        });
    }
}

app.use((req, res, next) => {
    detectSuspiciousActivity(req);
    next();
});
Key Points: Use helmet.js for comprehensive security headers. HSTS forces HTTPS. CSP prevents XSS and injection. X-Frame-Options prevents clickjacking. Configure CORS properly - whitelist origins. Use TLS 1.3 with strong ciphers. Set secure cookie flags (httpOnly, secure, sameSite). Implement rate limiting. Use SRI for CDN resources. Certificate pinning for critical apps. Monitor security events. Serve security.txt for disclosure. HTTPS everywhere - no exceptions.

Section 25 Summary: Security and Best Practices

  • Input Validation: Validate all input server-side, whitelist over blacklist, prevent SQL/command/XSS injection
  • Sanitization: Escape HTML entities, use parameterized queries, sanitize filenames, implement rate limiting
  • XSS Prevention: Never use innerHTML with user input, avoid eval(), implement CSP headers, use DOMPurify
  • CSP Directives: default-src, script-src (use nonces), style-src, img-src, connect-src, frame-ancestors
  • Secure Coding: Never hardcode secrets, follow OWASP Top 10, defense in depth, least privilege principle
  • Code Review: Check auth/authz, validate crypto usage, prevent sensitive data in errors/logs
  • Password Hashing: Use bcrypt/Argon2 with salt, never plain MD5/SHA, use key derivation (PBKDF2)
  • Encryption: AES-256-GCM for symmetric, RSA for asymmetric, random IV per encryption, HMAC for auth
  • Authentication: JWT for stateless auth, refresh tokens for sessions, OAuth 2.0 for third-party, MFA for high security
  • Authorization: RBAC for simple models, ABAC for complex rules, validate permissions on every request
  • Security Headers: HSTS forces HTTPS, CSP prevents XSS, X-Frame-Options prevents clickjacking, use helmet.js
  • HTTPS: Use TLS 1.3, strong ciphers, secure cookies (httpOnly, secure, sameSite), CORS whitelist