Advanced JavaScript

Master advanced JavaScript concepts including design patterns, performance optimization, and modern development practices.

advanced JavaScript 7 hours

Chapter 3: Advanced Functions and Methods

Chapter 3 of 15

Chapter 3: Advanced Functions and Methods

3.1 Higher-Order Functions

Higher-order functions are functions that either take other functions as arguments, return functions, or both. They enable powerful functional programming patterns.

Functions as Arguments:

// Array methods are higher-order functions
const numbers = [1, 2, 3, 4, 5];

// map() - transforms each element
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// filter() - selects elements based on condition
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]

// reduce() - accumulates values
const sum = numbers.reduce((acc, n) => acc + n, 0);
console.log(sum); // 15

// forEach() - executes function for each element
numbers.forEach(n => console.log(n));

Functions as Return Values:

function createValidator(rule) {
    return function(value) {
        return rule(value);
    };
}

const isEmail = createValidator(value => value.includes('@'));
const isPositive = createValidator(value => value > 0);

console.log(isEmail('test@example.com')); // true
console.log(isPositive(5)); // true

Custom Higher-Order Functions:

function withLogging(fn) {
    return function(...args) {
        console.log(`Calling function with args:`, args);
        const result = fn(...args);
        console.log(`Function returned:`, result);
        return result;
    };
}

const add = (a, b) => a + b;
const loggedAdd = withLogging(add);

loggedAdd(2, 3);
// Output:
// Calling function with args: [2, 3]
// Function returned: 5

3.2 Currying and Partial Application

Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument.

// Curried function
const multiply = (a) => (b) => a * b;
const double = multiply(2);
const triple = multiply(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

// Multi-argument currying
const add = (a) => (b) => (c) => a + b + c;
const add5 = add(5);
const add5And10 = add5(10);
console.log(add5And10(15)); // 30

Practical Currying Example:

// Curried API request function
const apiRequest = (method) => (url) => (data) => {
    return fetch(url, {
        method: method,
        body: JSON.stringify(data),
        headers: { 'Content-Type': 'application/json' }
    });
};

const get = apiRequest('GET');
const post = apiRequest('POST');
const put = apiRequest('PUT');

const getUsers = get('/api/users');
const createUser = post('/api/users');

Partial Application:

Partial application is similar to currying but allows fixing some arguments while leaving others to be provided later.

function partial(fn, ...fixedArgs) {
    return function(...remainingArgs) {
        return fn(...fixedArgs, ...remainingArgs);
    };
}

function greet(greeting, name, punctuation) {
    return `${greeting}, ${name}${punctuation}`;
}

const sayHello = partial(greet, 'Hello');
const sayHelloToJohn = partial(greet, 'Hello', 'John');

console.log(sayHello('Alice', '!'));      // "Hello, Alice!"
console.log(sayHelloToJohn('!'));           // "Hello, John!"

3.3 Function Composition

Function composition is combining multiple functions to create a new function. The output of one function becomes the input of the next.

const compose = (...fns) => (value) => 
    fns.reduceRight((acc, fn) => fn(acc), value);

const pipe = (...fns) => (value) => 
    fns.reduce((acc, fn) => fn(acc), value);

// Example functions
const addOne = x => x + 1;
const multiplyByTwo = x => x * 2;
const square = x => x * x;

// Compose: right to left
const composed = compose(square, multiplyByTwo, addOne);
console.log(composed(3)); // ((3 + 1) * 2)² = 64

// Pipe: left to right
const piped = pipe(addOne, multiplyByTwo, square);
console.log(piped(3)); // ((3 + 1) * 2)² = 64

3.4 Memoization

Memoization is an optimization technique that caches the results of expensive function calls.

function memoize(fn) {
    const cache = new Map();
    
    return function(...args) {
        const key = JSON.stringify(args);
        
        if (cache.has(key)) {
            console.log('Cache hit!');
            return cache.get(key);
        }
        
        console.log('Computing...');
        const result = fn(...args);
        cache.set(key, result);
        return result;
    };
}

// Expensive function
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(40)); // Computes once
console.log(memoizedFib(40)); // Uses cache