Backend Development
February 27, 2024
15 min

📌 The Ultimate Guide to Functions in JavaScript & TypeScript 🚀

A Deep Dive into Function Types, Overloading, Callbacks, Performance Optimization, and Best Practices.

BU

Balaji Udayagiri

Frontend Lead Engineer

Technologies Used

JavaScript ES6+TypeScript 5.x

Summary

Functions are the backbone of JavaScript & TypeScript. This guide explores every function type, best practices, and advanced concepts like higher-order functions, memoization, and decorators.

Key Points

  • Understand function types: Regular, Arrow, Async, and more.
  • Learn function overloading and higher-order functions.
  • Optimize performance using memoization and throttling.
  • Explore function decorators and metaprogramming.

Code Examples

📌 Function Declaration

Function declarations are hoisted, meaning they can be called before they are defined. They are useful for defining helper functions that can be reused throughout a program.

function greet(name: string): string {
    return `Hello, ${name}!`;
}

console.log(greet("Alice")); // ✅ Works

📌 Function Expression

Function expressions are not hoisted and must be defined before use. They are useful for creating anonymous functions, assigning functions to variables, and passing functions as arguments.

const greet = function(name: string): string {
    return `Hello, ${name}!`;
};

console.log(greet("Alice")); // ✅ Works

📌 Arrow Function

Arrow functions provide a concise syntax and do not bind their own `this`. They are commonly used for callbacks and functional programming.

const greet = (name: string): string => `Hello, ${name}!`;

console.log(greet("Alice")); // ✅ Works

📌 Immediately Invoked Function Expression (IIFE)

IIFE functions execute immediately after they are defined. They are used to avoid polluting the global scope and are often used in modular programming.

(function() {
    console.log("This function runs immediately!");
})();

📌 Generator Function

Generator functions allow functions to pause and resume execution using the `yield` keyword. They are useful for handling large datasets and implementing iterators.

function* counter() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = counter();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

📌 Async Function

Async functions make it easier to work with Promises using the `await` keyword. They simplify asynchronous code and improve readability.

async function fetchData(): Promise<string> {
    return new Promise(resolve => setTimeout(() => resolve("Data loaded"), 1000));
}

async function main() {
    const data = await fetchData();
    console.log(data);
}

main();

📌 Constructor Function

Constructor functions create object instances using the `new` keyword. They were widely used before the introduction of ES6 classes.

function Person(name: string) {
    this.name = name;
}

const user = new (Person as any)("Alice");
console.log(user.name); // Alice

📌 Callback Function

Callbacks are functions passed as arguments to other functions. They are commonly used in asynchronous programming and event handling.

function processUser(id: number, callback: (name: string) => void): void {
    const name = "Alice";
    callback(name);
}

processUser(1, (userName) => {
    console.log(`Processed user: ${userName}`);
});

📌 Higher-Order Function

Higher-order functions are functions that take other functions as arguments or return functions. They are widely used in functional programming.

function multiplier(factor: number) {
    return (value: number) => value * factor;
}

const double = multiplier(2);
console.log(double(10)); // 20

📌 Recursive Function

A recursive function calls itself until a base condition is met. Recursion is commonly used for solving problems like tree traversal and factorial calculations.

function factorial(n: number): number {
    return n === 0 ? 1 : n * factorial(n - 1);
}

console.log(factorial(5)); // 120

📌 Currying Function

Currying transforms a function with multiple arguments into a sequence of functions, each taking a single argument. It enhances code reusability and modularity.

const add = (a: number) => (b: number) => a + b;

console.log(add(2)(3)); // 5

📌 Memoization Function

Memoization is a caching technique that stores the results of function calls to avoid redundant calculations. It improves performance in computation-heavy applications.

function memoize(fn: Function) {
    let cache: Record<string, any> = {};
    return function (...args: any[]) {
        let key = JSON.stringify(args);
        return cache[key] || (cache[key] = fn(...args));
    };
}

📌 Debounce Function

Debounce delays the execution of a function until a specified time has passed since the last time it was called. It is useful for optimizing event handling, such as search inputs.

function debounce(fn: Function, delay: number) {
    let timer: any;
    return function (...args: any[]) {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), delay);
    };
}

📌 Throttle Function

Throttle ensures that a function is executed at most once within a specified time interval. It is commonly used for optimizing scroll and resize events.

function throttle(fn: Function, limit: number) {
    let inThrottle: boolean;
    return function (...args: any[]) {
        if (!inThrottle) {
            fn(...args);
            inThrottle = true;
            setTimeout(() => (inThrottle = false), limit);
        }
    };
}

📌 Proxy Function

Proxy functions allow interception of function calls to modify their behavior. They are used in logging, validation, and middleware systems.

const handler = {
    apply: function(target: Function, thisArg: any, argumentsList: any) {
        console.log(`Calling function with arguments: ${argumentsList}`);
        return target(...argumentsList);
    }
};

function sum(a: number, b: number): number {
    return a + b;
}

const proxiedSum = new Proxy(sum, handler);

console.log(proxiedSum(5, 10)); // Logs function call and result

📌 Function Decorator

Function decorators allow for metaprogramming, modifying the behavior of functions at runtime. They are commonly used in TypeScript, Angular, and NestJS.

function Log(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Calling ${key} with`, args);
        return original.apply(this, args);
    };
}

References

Related Topics

JavaScriptTypeScriptFunctional ProgrammingAsync/AwaitDecorators