JavaScript Core: Functions, Data Structures, and Scope Mastery
Beyond simple syntax lies the power to engineer robust applications. This deep dive unpacks the versatility of JavaScript functions—from Arrow syntax to Currying—and explores the full depth of Object and Array manipulation. We conclude by demystifying the scope chain and closures, providing the mental model necessary to manage memory and state effectively.
Functions
Function declarations
Hoisted function definitions using the function keyword; available throughout their scope even before the line they're declared.
sayHello(); // Works due to hoisting function sayHello() { console.log("Hello!"); }
Function expressions
Functions assigned to variables; not hoisted, so they must be defined before use.
const greet = function(name) { return `Hello, ${name}`; }; console.log(greet("Alice"));
Anonymous functions
Functions without a name, commonly used as callbacks or immediately invoked; harder to debug due to missing stack trace names.
setTimeout(function() { console.log("Anonymous callback"); }, 1000);
Arrow functions
ES6 compact syntax with lexical this binding; cannot be used as constructors and don't have their own arguments object.
const add = (a, b) => a + b; const square = x => x * x; const greet = () => console.log("Hi");
Arrow function vs regular function
┌─────────────────────┬─────────────────────┬─────────────────────┐
│ Feature │ Regular Function │ Arrow Function │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ this binding │ Dynamic (caller) │ Lexical (parent) │
│ arguments object │ Yes │ No │
│ Constructor (new) │ Yes │ No │
│ Hoisting │ Yes (declarations) │ No │
│ Implicit return │ No │ Yes (single expr) │
└─────────────────────┴─────────────────────┴─────────────────────┘
this keyword in different contexts
this refers to different objects based on invocation: global object (global), the object (method), new instance (constructor), or lexically inherited (arrow functions).
const obj = { name: "Alice", regular: function() { console.log(this.name); }, // "Alice" arrow: () => { console.log(this.name); } // undefined (global) };
Function parameters
Variables listed in function definition that receive values (arguments) when the function is called; JavaScript doesn't enforce parameter count.
function greet(name, age) { console.log(`${name} is ${age}`); } greet("Alice", 25); // Alice is 25 greet("Bob"); // Bob is undefined
Default parameters
ES6 feature allowing parameters to have fallback values when undefined is passed or argument is omitted.
function greet(name = "Guest", greeting = "Hello") { return `${greeting}, ${name}!`; } greet(); // "Hello, Guest!" greet("Alice"); // "Hello, Alice!"
Rest parameters
ES6 syntax using ... to collect remaining arguments into an array; must be the last parameter in the function definition.
function sum(...numbers) { return numbers.reduce((a, b) => a + b, 0); } sum(1, 2, 3, 4); // 10
arguments object
An array-like object (not a real array) containing all passed arguments; available in regular functions but not arrow functions.
function showArgs() { console.log(arguments.length); console.log(Array.from(arguments)); // Convert to real array } showArgs(1, 2, 3); // 3, [1, 2, 3]
Return statements
Exits function execution and optionally returns a value; functions without return (or empty return) yield undefined.
function divide(a, b) { if (b === 0) return null; // Early exit return a / b; }
Function scope
Variables declared inside a function with var are only accessible within that function; let/const add block-level scoping.
function example() { var x = 10; // Function-scoped if (true) { let y = 20; // Block-scoped } console.log(x); // 10 // console.log(y); // ReferenceError }
IIFE (Immediately Invoked Function Expression)
A function that executes immediately after creation, commonly used to create private scope and avoid polluting global namespace.
(function() { var private = "I'm private"; console.log(private); })(); // Also with arrow: (() => console.log("IIFE"))();
Callback functions
Functions passed as arguments to other functions, to be executed later (synchronously or asynchronously); foundation of async JavaScript.
function fetchData(callback) { setTimeout(() => { callback("Data received"); }, 1000); } fetchData(data => console.log(data));
Higher-order functions
Functions that take functions as arguments or return functions; enables functional programming patterns like map, filter, reduce.
const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10] const evens = numbers.filter(n => n % 2 === 0); // [2, 4] const sum = numbers.reduce((a, b) => a + b, 0); // 15
Pure functions
Functions that always return the same output for the same input and have no side effects (don't modify external state); highly testable and predictable.
// Pure const add = (a, b) => a + b; // Impure (modifies external state) let total = 0; const addToTotal = (n) => total += n;
Function composition
Combining multiple functions where the output of one becomes the input of the next; creates complex operations from simple, reusable functions.
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x); const add1 = x => x + 1; const double = x => x * 2; const add1ThenDouble = compose(double, add1); add1ThenDouble(5); // 12 → (5+1)*2
Currying
Transforming a function with multiple arguments into a sequence of functions, each taking a single argument; enables partial application.
const curry = (a) => (b) => (c) => a + b + c; curry(1)(2)(3); // 6 const add5 = curry(5); add5(3)(2); // 10
Partial application
Pre-filling some arguments of a function, returning a new function that takes the remaining arguments; different from currying which splits all arguments.
const greet = (greeting, name) => `${greeting}, ${name}!`; const sayHello = greet.bind(null, "Hello"); sayHello("Alice"); // "Hello, Alice!" // Using closure const partial = (fn, ...args) => (...rest) => fn(...args, ...rest);
Recursion
A function calling itself to solve problems by breaking them into smaller subproblems; requires a base case to prevent infinite loops.
function factorial(n) { if (n <= 1) return 1; // Base case return n * factorial(n - 1); // Recursive case } factorial(5); // 120 /* factorial(5) └─ 5 * factorial(4) └─ 4 * factorial(3) └─ 3 * factorial(2) └─ 2 * factorial(1) └─ 1 (base case) */
Tail call optimization
When a function's last action is calling another function, the engine can optimize by reusing the stack frame; only implemented in Safari currently.
// Not tail-optimized (multiplication after recursive call) const factorial = n => n <= 1 ? 1 : n * factorial(n - 1); // Tail-optimized (recursive call is final action) const factorialTCO = (n, acc = 1) => n <= 1 ? acc : factorialTCO(n - 1, n * acc);
Named function expressions
Function expressions with a name, useful for self-reference (recursion) and better stack traces in debugging.
const factorial = function fact(n) { if (n <= 1) return 1; return n * fact(n - 1); // Can reference itself by name }; console.log(factorial(5)); // 120 // console.log(fact); // ReferenceError (name not in outer scope)
╔══════════════════════════════════════════════════════════════════╗
║ FUNCTIONS MENTAL MAP ║
╠══════════════════════════════════════════════════════════════════╣
║ Declaration Types │ Invocation Patterns ║
║ ───────────────── │ ─────────────────── ║
║ • Declaration │ • Direct call: fn() ║
║ • Expression │ • Method: obj.fn() ║
║ • Arrow │ • Constructor: new Fn() ║
║ • IIFE │ • call/apply/bind ║
╠═══════════════════════╪══════════════════════════════════════════╣
║ Parameters │ Functional Patterns ║
║ ────────── │ ─────────────────── ║
║ • Default │ • Higher-order ║
║ • Rest (...) │ • Callbacks ║
║ • arguments obj │ • Composition ║
║ │ • Currying/Partial ║
╚═══════════════════════╧══════════════════════════════════════════╝
Objects
Object literals
Object literals are the simplest way to create objects in JavaScript using curly braces {} with key-value pairs separated by commas.
const person = { name: "John", age: 30, city: "NYC" };
Property access (dot vs bracket notation)
Dot notation is cleaner for known property names, while bracket notation allows dynamic keys and special characters.
const obj = { name: "Alice", "my-key": 42 }; console.log(obj.name); // "Alice" (dot) console.log(obj["my-key"]); // 42 (bracket - required for special chars) const key = "name"; console.log(obj[key]); // "Alice" (dynamic access)
Adding and deleting properties
Properties can be added dynamically by assignment and removed using the delete operator.
const obj = { a: 1 }; obj.b = 2; // Add property delete obj.a; // Remove property console.log(obj); // { b: 2 }
Object methods
Methods are functions stored as object properties, allowing objects to have behavior.
const calculator = { value: 0, add(n) { this.value += n; }, subtract(n) { this.value -= n; } }; calculator.add(5); // calculator.value = 5
this keyword
this refers to the object that is executing the current function; its value depends on how the function is called.
const user = { name: "Bob", greet() { console.log(`Hi, I'm ${this.name}`); } }; user.greet(); // "Hi, I'm Bob" // ⚠️ Arrow functions don't have their own 'this'
Object.keys, Object.values, Object.entries
These static methods return arrays of an object's enumerable property names, values, or key-value pairs respectively.
const obj = { a: 1, b: 2, c: 3 }; Object.keys(obj); // ["a", "b", "c"] Object.values(obj); // [1, 2, 3] Object.entries(obj); // [["a",1], ["b",2], ["c",3]]
Object.assign
Copies all enumerable own properties from one or more source objects to a target object, returning the modified target (shallow copy).
const target = { a: 1 }; const source = { b: 2, c: 3 }; Object.assign(target, source); // { a: 1, b: 2, c: 3 } // Common pattern: clone object const clone = Object.assign({}, original);
Object.create
Creates a new object with the specified prototype object and optional property descriptors.
const proto = { greet() { return `Hello, ${this.name}`; } }; const obj = Object.create(proto); obj.name = "Alice"; obj.greet(); // "Hello, Alice"
┌─────────────┐
│ obj │──→ proto (has greet method)
│ name:"Alice"│
└─────────────┘
Object.freeze, Object.seal
freeze makes an object completely immutable (no add/delete/modify), while seal prevents adding/deleting but allows modifying existing properties.
const frozen = Object.freeze({ a: 1 }); frozen.a = 2; // Silently fails (or throws in strict mode) frozen.b = 3; // Silently fails const sealed = Object.seal({ a: 1 }); sealed.a = 2; // ✓ Works sealed.b = 3; // ✗ Fails
Object.preventExtensions
Prevents new properties from being added to an object, but existing properties can still be modified or deleted.
const obj = { a: 1 }; Object.preventExtensions(obj); obj.b = 2; // ✗ Fails obj.a = 99; // ✓ Works delete obj.a; // ✓ Works
Comparison:
Add Delete Modify
preventExtensions ✗ ✓ ✓
seal ✗ ✗ ✓
freeze ✗ ✗ ✗
Property descriptors
Property descriptors define how a property behaves with attributes: value, writable, enumerable, and configurable.
// { value: any, writable: bool, enumerable: bool, configurable: bool } // OR for accessor properties: // { get: fn, set: fn, enumerable: bool, configurable: bool }
Object.defineProperty
Defines a new property or modifies an existing property with fine-grained control over property descriptors.
const obj = {}; Object.defineProperty(obj, 'secret', { value: 42, writable: false, enumerable: false, configurable: false }); console.log(obj.secret); // 42 obj.secret = 100; // Fails silently console.log(Object.keys(obj)); // [] (not enumerable)
Object.getOwnPropertyDescriptor
Returns the property descriptor for an own property of an object.
const obj = { name: "John" }; const desc = Object.getOwnPropertyDescriptor(obj, 'name'); // { value: "John", writable: true, enumerable: true, configurable: true }
Computed property names
Allows using expressions inside square brackets as property names when defining objects.
const prefix = "user"; const id = 123; const obj = { [prefix + "_" + id]: "data", [`${prefix}Name`]: "Alice" }; // { user_123: "data", userName: "Alice" }
Property shorthand
When variable names match property names, you can use shorthand syntax to avoid repetition.
const name = "Alice"; const age = 30; // Instead of: { name: name, age: age } const person = { name, age }; // { name: "Alice", age: 30 }
Method shorthand
Allows defining methods without the function keyword, providing cleaner syntax.
// Old way const obj1 = { sayHi: function() { return "Hi"; } }; // Shorthand const obj2 = { sayHi() { return "Hi"; } };
Getters and setters
Getters and setters are special methods that provide computed properties with read/write control.
const user = { firstName: "John", lastName: "Doe", get fullName() { return `${this.firstName} ${this.lastName}`; }, set fullName(value) { [this.firstName, this.lastName] = value.split(' '); } }; console.log(user.fullName); // "John Doe" user.fullName = "Jane Smith"; // Sets both names
Object destructuring
Extracts properties from objects into distinct variables with concise syntax.
const person = { name: "Alice", age: 30, city: "NYC" }; const { name, age } = person; // Basic const { name: n, age: a } = person; // Rename const { country = "USA" } = person; // Default value const { name, ...rest } = person; // Rest pattern
Spread operator with objects
Spreads an object's own enumerable properties into a new object, useful for shallow copying and merging.
const defaults = { theme: "dark", lang: "en" }; const userPrefs = { lang: "es" }; const settings = { ...defaults, ...userPrefs }; // { theme: "dark", lang: "es" } const clone = { ...original }; // Shallow clone
Object.is
Provides strict equality comparison that handles special cases like NaN and signed zeros correctly.
Object.is(25, 25); // true Object.is(NaN, NaN); // true (unlike ===) Object.is(0, -0); // false (unlike ===) // Comparison: NaN === NaN; // false 0 === -0; // true
Arrays
Array creation
Arrays can be created using literals, constructor, or static methods.
const arr1 = [1, 2, 3]; // Literal (preferred) const arr2 = new Array(3); // Creates array with 3 empty slots const arr3 = Array.of(1, 2, 3); // Creates [1, 2, 3] const arr4 = Array.from("abc"); // Creates ["a", "b", "c"]
Array literals
Array literals use square brackets with comma-separated values, the most common way to create arrays.
const numbers = [1, 2, 3, 4, 5]; const mixed = [1, "two", { three: 3 }, [4]]; const empty = [];
Array constructor
The Array constructor creates arrays but has quirky behavior with a single numeric argument.
const arr1 = new Array(5); // [empty × 5] - 5 empty slots! const arr2 = new Array(5, 6); // [5, 6] const arr3 = new Array("5"); // ["5"] // ⚠️ Prefer literals or Array.of() to avoid confusion
Array length
The length property returns the count of elements and can be modified to truncate or extend arrays.
const arr = [1, 2, 3, 4, 5]; console.log(arr.length); // 5 arr.length = 3; // Truncates to [1, 2, 3] arr.length = 5; // Extends to [1, 2, 3, empty × 2] arr.length = 0; // Clears array: []
Array indexing
Arrays use zero-based indexing; access elements with bracket notation, negative indices don't work natively.
const arr = ['a', 'b', 'c', 'd']; arr[0]; // 'a' arr[3]; // 'd' arr[-1]; // undefined (use arr.at(-1) for last element) arr.at(-1); // 'd' (ES2022)
Index: 0 1 2 3
┌────┬────┬────┬────┐
│ 'a'│ 'b'│ 'c'│ 'd'│
└────┴────┴────┴────┘
at(-1): -4 -3 -2 -1
Array methods (push, pop, shift, unshift)
These methods add/remove elements from array ends: push/pop for end, shift/unshift for beginning.
const arr = [2, 3]; arr.push(4); // [2, 3, 4] - returns new length arr.pop(); // [2, 3] - returns removed element (4) arr.unshift(1); // [1, 2, 3] - returns new length arr.shift(); // [2, 3] - returns removed element (1)
unshift → ┌───┬───┬───┐ ← push
│ 1 │ 2 │ 3 │
shift ← └───┴───┴───┘ → pop
splice, slice
splice modifies array in-place (remove/insert elements), slice returns a shallow copy of a portion without modifying original.
// splice(start, deleteCount, ...items) - MUTATES const arr = [1, 2, 3, 4, 5]; arr.splice(2, 1); // Removes 1 element at index 2 → [1, 2, 4, 5] arr.splice(2, 0, 'a'); // Inserts 'a' at index 2 → [1, 2, 'a', 4, 5] // slice(start, end) - NO MUTATION const arr2 = [1, 2, 3, 4, 5]; arr2.slice(1, 4); // [2, 3, 4] (end exclusive) arr2.slice(-2); // [4, 5]
concat
Returns a new array combining the original array with additional arrays or values.
const a = [1, 2]; const b = [3, 4]; const c = a.concat(b); // [1, 2, 3, 4] const d = a.concat(3, [4, 5]); // [1, 2, 3, 4, 5] // Original arrays unchanged
join, split
join converts array to string with separator; split (string method) converts string to array.
// join - Array → String const arr = ['a', 'b', 'c']; arr.join('-'); // "a-b-c" arr.join(''); // "abc" // split - String → Array const str = "a-b-c"; str.split('-'); // ["a", "b", "c"] str.split(''); // ["a", "-", "b", "-", "c"]
indexOf, lastIndexOf
Return the first/last index of an element, or -1 if not found; uses strict equality.
const arr = [1, 2, 3, 2, 1]; arr.indexOf(2); // 1 (first occurrence) arr.lastIndexOf(2); // 3 (last occurrence) arr.indexOf(99); // -1 (not found) arr.indexOf(2, 2); // 3 (search from index 2)
includes
Returns boolean indicating whether array contains a specified element; handles NaN correctly.
const arr = [1, 2, NaN, 4]; arr.includes(2); // true arr.includes(99); // false arr.includes(NaN); // true (indexOf would return -1) arr.includes(2, 2); // false (search from index 2)
find, findIndex, findLast, findLastIndex
Return the first/last element or index matching a predicate function, or undefined/-1 if not found.
const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 3, name: 'Alice' } ]; users.find(u => u.name === 'Alice'); // { id: 1, name: 'Alice' } users.findIndex(u => u.name === 'Alice'); // 0 users.findLast(u => u.name === 'Alice'); // { id: 3, name: 'Alice' } users.findLastIndex(u => u.name === 'Alice'); // 2
filter
Creates a new array with all elements that pass the test implemented by the provided function.
const numbers = [1, 2, 3, 4, 5, 6]; const evens = numbers.filter(n => n % 2 === 0); // [2, 4, 6] const users = [{ name: 'Alice', active: true }, { name: 'Bob', active: false }]; const activeUsers = users.filter(u => u.active);
map
Creates a new array with the results of calling a function on every element.
const numbers = [1, 2, 3, 4]; const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8] const names = [{name: 'Alice'}, {name: 'Bob'}]; const nameList = names.map(u => u.name); // ['Alice', 'Bob']
reduce, reduceRight
Executes a reducer function on each element, resulting in a single output value; reduceRight processes right-to-left.
const numbers = [1, 2, 3, 4]; // reduce(callback, initialValue) const sum = numbers.reduce((acc, cur) => acc + cur, 0); // 10 // Without initial value, first element is used const product = numbers.reduce((acc, cur) => acc * cur); // 24 // reduceRight - right to left ['a','b','c'].reduceRight((acc, cur) => acc + cur); // "cba"
reduce flow: [1, 2, 3, 4] → sum
acc=0, cur=1 → 1
acc=1, cur=2 → 3
acc=3, cur=3 → 6
acc=6, cur=4 → 10
forEach
Executes a function once for each array element; returns undefined and cannot be stopped (use for...of for breaking).
const arr = ['a', 'b', 'c']; arr.forEach((element, index, array) => { console.log(`${index}: ${element}`); }); // 0: a // 1: b // 2: c
some, every
some returns true if at least one element passes the test; every returns true only if all elements pass.
const numbers = [1, 2, 3, 4, 5]; numbers.some(n => n > 4); // true (5 > 4) numbers.every(n => n > 0); // true (all positive) numbers.every(n => n > 2); // false (1,2 fail) // Short-circuits when result is determined
sort
Sorts array in-place; converts elements to strings by default, so provide compare function for numbers.
const arr = [3, 1, 4, 1, 5]; // Default (string comparison) - WRONG for numbers! [10, 2, 1].sort(); // [1, 10, 2] ❌ // Correct numeric sort arr.sort((a, b) => a - b); // [1, 1, 3, 4, 5] ascending arr.sort((a, b) => b - a); // [5, 4, 3, 1, 1] descending // Strings ['banana', 'apple'].sort(); // ['apple', 'banana']
reverse
Reverses array in-place and returns the reference to the same array.
const arr = [1, 2, 3]; arr.reverse(); // [3, 2, 1] console.log(arr); // [3, 2, 1] - original mutated // Non-mutating reverse (ES2023) const reversed = arr.toReversed(); // New array
flat, flatMap
flat creates a new array with sub-array elements concatenated to specified depth; flatMap maps then flattens by one level.
// flat(depth = 1) const nested = [1, [2, [3, [4]]]]; nested.flat(); // [1, 2, [3, [4]]] nested.flat(2); // [1, 2, 3, [4]] nested.flat(Infinity); // [1, 2, 3, 4] // flatMap - map + flat(1) in one step const arr = [1, 2, 3]; arr.flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6]
Array.from
Creates a new array from array-like or iterable objects, with optional map function.
Array.from('abc'); // ['a', 'b', 'c'] Array.from({ length: 3 }); // [undefined, undefined, undefined] Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4] Array.from(document.querySelectorAll('div')); // NodeList to Array Array.from(new Set([1, 2, 2, 3])); // [1, 2, 3]
Array.of
Creates a new array with the given arguments as elements; fixes the single-number constructor issue.
Array.of(5); // [5] new Array(5); // [empty × 5] - confusing! Array.of(1, 2, 3); // [1, 2, 3] Array.of(undefined); // [undefined]
Array.isArray
Returns true if the value is an array; the reliable way to check for arrays.
Array.isArray([1, 2, 3]); // true Array.isArray('string'); // false Array.isArray({ length: 3 }); // false (array-like but not array) Array.isArray(new Array()); // true // Better than: typeof arr (returns 'object') // Better than: instanceof Array (fails across frames)
fill
Fills all or part of an array with a static value, modifying in-place.
const arr = [1, 2, 3, 4, 5]; arr.fill(0); // [0, 0, 0, 0, 0] arr.fill(9, 1, 3); // [0, 9, 9, 0, 0] (index 1 to 3) // Create array of same values new Array(5).fill(1); // [1, 1, 1, 1, 1] // ⚠️ Warning with objects new Array(3).fill({}); // Same object reference × 3!
copyWithin
Shallow copies part of an array to another location in the same array, without modifying its length.
const arr = [1, 2, 3, 4, 5]; // copyWithin(target, start, end) arr.copyWithin(0, 3); // [4, 5, 3, 4, 5] (copy from index 3 to index 0) arr.copyWithin(1, 3, 4); // [4, 4, 3, 4, 5] (copy index 3-4 to index 1)
[1, 2, 3, 4, 5].copyWithin(0, 3)
↑______↑
copy [4,5] to position 0
Result: [4, 5, 3, 4, 5]
Array destructuring
Extracts values from arrays into distinct variables using position-based syntax.
const rgb = [255, 128, 64]; const [r, g, b] = rgb; // r=255, g=128, b=64 const [first, , third] = rgb; // Skip elements const [head, ...tail] = rgb; // head=255, tail=[128, 64] const [a, b, c = 0] = [1, 2]; // Default values: c=0 // Swap variables let x = 1, y = 2; [x, y] = [y, x]; // x=2, y=1
Spread operator with arrays
Expands array elements in places where multiple elements are expected.
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6] const copy = [...arr1]; // Shallow clone const withMore = [0, ...arr1, 4]; // [0, 1, 2, 3, 4] Math.max(...arr1); // 3 console.log(...arr1); // 1 2 3
Rest in destructuring
Collects remaining elements into a new array; must be the last element in the pattern.
const [first, second, ...rest] = [1, 2, 3, 4, 5]; // first = 1, second = 2, rest = [3, 4, 5] // In function parameters function sum(first, ...others) { return first + others.reduce((a, b) => a + b, 0); } sum(1, 2, 3, 4); // 10
Sparse arrays
Arrays with "holes" where indices have no values; created by setting length or deleting elements.
const sparse = [1, , , 4]; // Two holes sparse.length; // 4 sparse[1]; // undefined 1 in sparse; // false (hole) 0 in sparse; // true (has value) // Methods handle holes differently: sparse.forEach(x => console.log(x)); // 1, 4 (skips holes) sparse.map(x => x * 2); // [2, empty × 2, 8]
Index: 0 1 2 3
┌────┬──────┬──────┬────┐
│ 1 │ hole │ hole │ 4 │
└────┴──────┴──────┴────┘
Array-like objects
Objects with numeric indices and a length property but lacking array methods; common in DOM and arguments.
function example() { console.log(arguments); // { '0': 1, '1': 2, length: 2 } console.log(arguments.map); // undefined - not an array! // Convert to real array const arr1 = Array.from(arguments); const arr2 = [...arguments]; const arr3 = Array.prototype.slice.call(arguments); } const nodeList = document.querySelectorAll('div'); // Array-like
Typed arrays
Fixed-length arrays for raw binary data with specific numeric types; essential for WebGL, file processing, and performance-critical code.
// Create typed arrays const int8 = new Int8Array(4); // 4 signed 8-bit integers const float64 = new Float64Array([1.5, 2.5, 3.5]); const buffer = new ArrayBuffer(16); // 16 bytes raw memory const view = new Uint32Array(buffer); // View as 4 uint32s int8[0] = 127; int8[1] = 128; // Wraps to -128 (overflow)
Types available:
┌──────────────────┬─────────┬───────────┐
│ Type │ Bytes │ Range │
├──────────────────┼─────────┼───────────┤
│ Int8Array │ 1 │ -128..127 │
│ Uint8Array │ 1 │ 0..255 │
│ Int16Array │ 2 │ -32768... │
│ Uint16Array │ 2 │ 0..65535 │
│ Int32Array │ 4 │ ±2.1B │
│ Float32Array │ 4 │ IEEE 754 │
│ Float64Array │ 8 │ IEEE 754 │
└──────────────────┴─────────┴───────────┘
Scope and Closures
Global Scope
Variables declared outside any function or block are in the global scope, accessible from anywhere in your code; in browsers, they become properties of the window object, which can lead to naming collisions and is generally considered bad practice.
var globalVar = "I'm global"; // Accessible everywhere console.log(window.globalVar); // "I'm global" (in browsers)
Function Scope
Variables declared with var inside a function are only accessible within that function, creating a private namespace that protects variables from external access.
function myFunc() { var secret = "hidden"; // Only accessible inside myFunc console.log(secret); // ✓ Works } console.log(secret); // ✗ ReferenceError
Block Scope
Variables declared with let and const are scoped to the nearest enclosing block (curly braces {}), unlike var which ignores block boundaries and is function-scoped.
if (true) { let blockScoped = "inside"; var functionScoped = "leaks out"; } console.log(functionScoped); // ✓ "leaks out" console.log(blockScoped); // ✗ ReferenceError
Lexical Scope
JavaScript uses lexical (static) scoping, meaning a function's scope is determined by where it's written in the source code, not where it's called from.
┌─────────────────────────────────┐
│ Global Scope │
│ ┌─────────────────────────┐ │
│ │ outer() Scope │ │
│ │ ┌─────────────────┐ │ │
│ │ │ inner() Scope │ │ │ ← inner can access outer & global
│ │ └─────────────────┘ │ │
│ └─────────────────────────┘ │
└─────────────────────────────────┘
Scope Chain
When resolving a variable, JavaScript looks up through nested scopes from innermost to outermost until it finds the variable or reaches global scope; this linked chain of scope objects is the scope chain.
const a = 1; function outer() { const b = 2; function inner() { const c = 3; console.log(a + b + c); // Walks chain: inner → outer → global } inner(); }
Closures
A closure is a function that remembers and accesses variables from its lexical scope even when executed outside that scope; the inner function "closes over" the outer variables.
function createCounter() { let count = 0; // Enclosed variable return function() { return ++count; // Still accesses count after createCounter returns }; } const counter = createCounter(); console.log(counter()); // 1 console.log(counter()); // 2
Closure Use Cases
Closures enable data privacy, function factories, partial application, event handlers with state, and callbacks that maintain context.
// Function factory const multiplyBy = (x) => (y) => x * y; const double = multiplyBy(2); const triple = multiplyBy(3); console.log(double(5)); // 10 console.log(triple(5)); // 15
Module Pattern
The module pattern uses an IIFE (Immediately Invoked Function Expression) with closures to create private state and expose only a public API, simulating encapsulation before ES6 modules existed.
const Calculator = (function() { let result = 0; // Private return { add: (x) => result += x, // Public API get: () => result }; })(); Calculator.add(5); console.log(Calculator.get()); // 5 console.log(Calculator.result); // undefined (private!)
Private Variables with Closures
Closures provide true privacy in JavaScript by keeping variables in an outer function's scope, inaccessible to external code but available to returned inner functions.
function BankAccount(initial) { let balance = initial; // Truly private return { deposit: (amt) => balance += amt, withdraw: (amt) => balance -= amt, getBalance: () => balance }; } const account = BankAccount(100); account.deposit(50); console.log(account.getBalance()); // 150 console.log(account.balance); // undefined
Memory Leaks with Closures
Closures can cause memory leaks when they unintentionally retain references to large objects or DOM elements that should be garbage collected, especially in long-lived callbacks or event handlers.
// ❌ Memory leak - largeData never freed function leaky() { const largeData = new Array(1000000); return function() { console.log(largeData.length); // Holds reference forever }; } // ✅ Fix - null out when done function fixed() { let largeData = new Array(1000000); const result = largeData.length; largeData = null; // Allow GC return function() { console.log(result); }; }