Advanced Function Patterns
This lesson covers advanced function techniques that you'll encounter in professional codebases.
The this Keyword in Depth
this is one of the most confusing aspects of JavaScript. Its value depends on how a function is called, not where it's defined.
Four Rules for this
javascript1// 1. Default binding: this = global (or undefined in strict mode)2function showThis() {3 console.log(this);4}5showThis(); // window (or undefined)67// 2. Implicit binding: this = object before the dot8const user = {9 name: "Alice",10 greet() {11 console.log(this.name);12 }13};14user.greet(); // "Alice" (this = user)1516// 3. Explicit binding: this = what you specify17function greet() {18 console.log(this.name);19}20const bob = { name: "Bob" };21greet.call(bob); // "Bob"22greet.apply(bob); // "Bob"23greet.bind(bob)(); // "Bob"2425// 4. new binding: this = newly created object26function Person(name) {27 this.name = name;28}29const alice = new Person("Alice"); // this = new object
Losing this
A common bug occurs when methods are passed as callbacks:
javascript1const user = {2 name: "Alice",3 greet() {4 console.log(`Hi, I'm ${this.name}`);5 }6};78user.greet(); // "Hi, I'm Alice"910// BUG: this is lost!11const greetFn = user.greet;12greetFn(); // "Hi, I'm undefined"1314// BUG: In callbacks15setTimeout(user.greet, 100); // "Hi, I'm undefined"1617// FIX 1: bind18setTimeout(user.greet.bind(user), 100);1920// FIX 2: Arrow function wrapper21setTimeout(() => user.greet(), 100);2223// FIX 3: Arrow method (but with caveats)24const user2 = {25 name: "Bob",26 greet: () => console.log(`Hi, I'm ${this.name}`) // Still wrong!27};
call, apply, and bind
These methods let you control this:
call() - Invoke with arguments
javascript1function introduce(greeting, punctuation) {2 console.log(`${greeting}, I'm ${this.name}${punctuation}`);3}45const person = { name: "Alice" };6introduce.call(person, "Hello", "!"); // "Hello, I'm Alice!"
apply() - Invoke with array of arguments
javascript1introduce.apply(person, ["Hi", "?"]); // "Hi, I'm Alice?"23// Useful for spreading arrays into functions4const numbers = [5, 6, 2, 3, 7];5Math.max.apply(null, numbers); // 767// Modern alternative: spread8Math.max(...numbers); // 7
bind() - Create new function with fixed this
javascript1const boundIntroduce = introduce.bind(person);2boundIntroduce("Hey", "."); // "Hey, I'm Alice."34// Partial application5const sayHiToAlice = introduce.bind(person, "Hi");6sayHiToAlice("!"); // "Hi, I'm Alice!"
The arguments Object
While rest parameters are preferred, understanding arguments is useful:
javascript1function oldStyleSum() {2 // arguments is array-like but NOT an array3 console.log(arguments.length); // Works4 console.log(arguments[0]); // Works5 arguments.map(x => x); // Error! Not an array67 // Convert to real array8 const args = Array.from(arguments);9 // or10 const args2 = [...arguments];11 // or (old way)12 const args3 = Array.prototype.slice.call(arguments);1314 return args.reduce((a, b) => a + b, 0);15}1617// Arrow functions don't have arguments18const arrowSum = () => {19 console.log(arguments); // ReferenceError (or parent's arguments)20};
Recursion
Functions calling themselves:
javascript1// Factorial: n! = n * (n-1) * (n-2) * ... * 12function factorial(n) {3 if (n <= 1) return 1; // Base case4 return n * factorial(n - 1); // Recursive case5}67factorial(5); // 120 (5 * 4 * 3 * 2 * 1)89// Traversing nested structures10function sumNested(arr) {11 let total = 0;12 for (const item of arr) {13 if (Array.isArray(item)) {14 total += sumNested(item); // Recurse15 } else {16 total += item;17 }18 }19 return total;20}2122sumNested([1, [2, [3, 4]], 5]); // 15
Tail Call Optimization (TCO)
A special optimization for recursive functions (limited browser support):
javascript1// NOT tail-recursive (has to do multiplication after return)2function factorial(n) {3 if (n <= 1) return 1;4 return n * factorial(n - 1);5}67// Tail-recursive (return value is just the recursive call)8function factorialTCO(n, accumulator = 1) {9 if (n <= 1) return accumulator;10 return factorialTCO(n - 1, n * accumulator);11}
Memoization
Caching function results for expensive computations:
javascript1function memoize(fn) {2 const cache = new Map();34 return function(...args) {5 const key = JSON.stringify(args);67 if (cache.has(key)) {8 console.log("Cache hit!");9 return cache.get(key);10 }1112 const result = fn.apply(this, args);13 cache.set(key, result);14 return result;15 };16}1718// Expensive function19function fibonacci(n) {20 if (n <= 1) return n;21 return fibonacci(n - 1) + fibonacci(n - 2);22}2324// Memoized version25const memoFib = memoize(function fib(n) {26 if (n <= 1) return n;27 return memoFib(n - 1) + memoFib(n - 2);28});2930memoFib(40); // Fast!31fibonacci(40); // Very slow without memoization
Currying
Transforming a function that takes multiple arguments into a sequence of functions:
javascript1// Non-curried2function add(a, b, c) {3 return a + b + c;4}5add(1, 2, 3); // 667// Curried8function curriedAdd(a) {9 return function(b) {10 return function(c) {11 return a + b + c;12 };13 };14}15curriedAdd(1)(2)(3); // 61617// Arrow function curry18const curriedAdd2 = a => b => c => a + b + c;1920// Partial application via currying21const add1 = curriedAdd(1);22const add1and2 = add1(2);23add1and2(3); // 6
Auto-curry Helper
javascript1function curry(fn) {2 return function curried(...args) {3 if (args.length >= fn.length) {4 return fn.apply(this, args);5 }6 return function(...moreArgs) {7 return curried.apply(this, args.concat(moreArgs));8 };9 };10}1112const add = curry((a, b, c) => a + b + c);13add(1, 2, 3); // 614add(1)(2)(3); // 615add(1, 2)(3); // 616add(1)(2, 3); // 6
Debouncing and Throttling
Control how often a function executes:
Debounce
Wait for a pause in calls before executing:
javascript1function debounce(fn, delay) {2 let timeoutId;34 return function(...args) {5 clearTimeout(timeoutId);67 timeoutId = setTimeout(() => {8 fn.apply(this, args);9 }, delay);10 };11}1213// Use case: Search input14const search = debounce((query) => {15 console.log(`Searching for: ${query}`);16}, 300);1718// Rapid typing only triggers one search after 300ms pause19search("h");20search("he");21search("hel");22search("hell");23search("hello"); // Only this triggers the search
Throttle
Execute at most once per time period:
javascript1function throttle(fn, limit) {2 let inThrottle;34 return function(...args) {5 if (!inThrottle) {6 fn.apply(this, args);7 inThrottle = true;8 setTimeout(() => inThrottle = false, limit);9 }10 };11}1213// Use case: Scroll handler14const onScroll = throttle(() => {15 console.log("Scroll position:", window.scrollY);16}, 100);1718window.addEventListener('scroll', onScroll);
Partial Application
Pre-fill some arguments of a function:
javascript1function partial(fn, ...presetArgs) {2 return function(...laterArgs) {3 return fn(...presetArgs, ...laterArgs);4 };5}67function greet(greeting, name, punctuation) {8 return `${greeting}, ${name}${punctuation}`;9}1011const sayHello = partial(greet, "Hello");12sayHello("Alice", "!"); // "Hello, Alice!"1314const sayHelloToAlice = partial(greet, "Hello", "Alice");15sayHelloToAlice("?"); // "Hello, Alice?"
Pure Functions
Functions without side effects that always return the same output for the same input:
javascript1// PURE: No side effects, predictable2function add(a, b) {3 return a + b;4}56function sortArray(arr) {7 return [...arr].sort(); // Doesn't modify original8}910// IMPURE: Has side effects11let counter = 0;12function increment() {13 counter++; // Modifies external state14 return counter;15}1617function appendToArray(arr, item) {18 arr.push(item); // Mutates input19 return arr;20}2122// IMPURE: Unpredictable output23function getRandomValue() {24 return Math.random();25}
Benefits of Pure Functions
- Predictable: Same input = same output
- Testable: Easy to unit test
- Cacheable: Can be memoized
- Parallelizable: No shared state
Summary
| Pattern | Use Case |
|---|---|
bind/call/apply | Control this explicitly |
| Recursion | Tree/nested data traversal |
| Memoization | Cache expensive calculations |
| Currying | Create specialized functions |
| Debounce | Wait for input to stop |
| Throttle | Limit execution rate |
| Partial | Pre-fill function arguments |
| Pure Functions | Predictable, testable code |
These patterns form the foundation of functional programming in JavaScript.