Module 3: Functions & Scope

Deep dive into JavaScript functions, scope, closures, and advanced function concepts.

Back to Course|3 hours|Intermediate

Functions & Scope

Deep dive into JavaScript functions, scope, closures, and advanced function concepts.

Progress: 0/3 topics completed0%

Select Topics Overview

JavaScript Function Definitions

Learn different ways to define functions in JavaScript and understand when to use each approach

Content by: Dharti Dudhat

MERN Stack Developer

Connect

Function Declaration Methods

JavaScript provides multiple ways to define functions, each with different characteristics, hoisting behavior, and use cases. Understanding these differences is crucial for writing clean, maintainable code.

Function Definition Types

  • Function Declaration: Traditional function definition
  • Function Expression: Function assigned to variable
  • Arrow Function: Modern concise syntax (ES6+)
  • Generator Function: Functions that can pause execution
  • Async Function: Functions that return promises

Function Declaration Examples

Code Example
// 1. Function Declaration (hoisted)
function greet(name) {
    return `Hello, ${name}!`;
}

// 2. Function Expression
const greetUser = function(name) {
    return `Hello, ${name}!`;
};

// 3. Arrow Function (ES6+)
const greetArrow = (name) => `Hello, ${name}!`;

// 4. Generator Function
function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

// 5. Async Function
async function fetchUser(id) {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
}

// Hoisting demonstration
console.log(greet("John")); // Works - function is hoisted
// console.log(greetUser("Jane")); // Error - not hoisted

// Function expressions with different syntax
const add = function(a, b) {
    return a + b;
};

const multiply = (a, b) => a * b;

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

// Immediately Invoked Function Expression (IIFE)
(function() {
    console.log("This function runs immediately");
})();

// Modern function patterns (2025)
// Function with default parameters
function createUser(name, email, role = "user", isActive = true) {
    return {
        name,
        email,
        role,
        isActive,
        createdAt: new Date()
    };
}

// Function with rest parameters
function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0);
}

// Function with destructuring
function processUser({ name, email, age = 0 }) {
    return `Processing ${name} (${email}) - Age: ${age}`;
}

// Arrow functions with different bodies
const square = x => x * x;
const cube = x => x * x * x;

const processArray = arr => {
    const doubled = arr.map(x => x * 2);
    const filtered = doubled.filter(x => x > 10);
    return filtered.reduce((sum, x) => sum + x, 0);
};

// Generator function example
function* fibonacci() {
    let [prev, curr] = [0, 1];
    while (true) {
        yield curr;
        [prev, curr] = [curr, prev + curr];
    }
}

// Async function with error handling
async function fetchData(url) {
    try {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        console.error('Fetch error:', error.message);
        return null;
    }
}
Swipe to see more code

Function Parameters & Arguments

Code Example
// Function parameters and arguments
// Default parameters
function greet(name = "Guest", greeting = "Hello") {
    return `${greeting}, ${name}!`;
}

console.log(greet()); // "Hello, Guest!"
console.log(greet("John")); // "Hello, John!"
console.log(greet("Jane", "Hi")); // "Hi, Jane!"

// Rest parameters
function calculateSum(...numbers) {
    return numbers.reduce((sum, num) => sum + num, 0);
}

console.log(calculateSum(1, 2, 3, 4, 5)); // 15
console.log(calculateSum(10, 20)); // 30

// Destructuring parameters
function processUser({ name, email, age = 0, preferences = {} }) {
    return {
        id: Date.now(),
        name,
        email,
        age,
        preferences,
        processedAt: new Date()
    };
}

const user = {
    name: "John Doe",
    email: "john@example.com",
    age: 30,
    preferences: { theme: "dark" }
};

console.log(processUser(user));

// Arguments object (legacy)
function legacyFunction() {
    console.log("Number of arguments:", arguments.length);
    console.log("Arguments:", Array.from(arguments));
}

legacyFunction(1, 2, 3, "hello");

// Parameter validation
function validateAndProcess(data, options = {}) {
    // Validate required parameters
    if (!data || typeof data !== 'object') {
        throw new Error('Data must be a valid object');
    }
    
    if (!data.name || typeof data.name !== 'string') {
        throw new Error('Name is required and must be a string');
    }
    
    // Process with options
    const { validateEmail = true, strictMode = false } = options;
    
    if (validateEmail && data.email) {
        const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
        if (!emailRegex.test(data.email)) {
            throw new Error('Invalid email format');
        }
    }
    
    if (strictMode && data.age < 0) {
        throw new Error('Age cannot be negative');
    }
    
    return {
        ...data,
        validated: true,
        processedAt: new Date()
    };
}

// Function overloading simulation
function processData(data, type = 'default') {
    switch (type) {
        case 'user':
            return processUserData(data);
        case 'product':
            return processProductData(data);
        case 'order':
            return processOrderData(data);
        default:
            return processDefaultData(data);
    }
}

function processUserData(data) {
    return { ...data, type: 'user', processed: true };
}

function processProductData(data) {
    return { ...data, type: 'product', processed: true };
}

function processOrderData(data) {
    return { ...data, type: 'order', processed: true };
}

function processDefaultData(data) {
    return { ...data, type: 'default', processed: true };
}
Swipe to see more code

Practice Exercise: Function Definitions

Code Example
// Exercise: Build a Function Library
class FunctionLibrary {
    constructor() {
        this.functions = new Map();
    }
    
    // Register a function
    register(name, func, description = '') {
        if (typeof func !== 'function') {
            throw new Error('Second argument must be a function');
        }
        
        this.functions.set(name, {
            func,
            description,
            registeredAt: new Date()
        });
        
        console.log(`Function '${name}' registered successfully`);
    }
    
    // Execute a registered function
    execute(name, ...args) {
        const funcData = this.functions.get(name);
        
        if (!funcData) {
            throw new Error(`Function '${name}' not found`);
        }
        
        try {
            const result = funcData.func(...args);
            console.log(`Function '${name}' executed successfully`);
            return result;
        } catch (error) {
            console.error(`Error executing function '${name}':`, error.message);
            throw error;
        }
    }
    
    // List all registered functions
    list() {
        return Array.from(this.functions.entries()).map(([name, data]) => ({
            name,
            description: data.description,
            registeredAt: data.registeredAt
        }));
    }
    
    // Remove a function
    remove(name) {
        if (this.functions.delete(name)) {
            console.log(`Function '${name}' removed successfully`);
        } else {
            console.log(`Function '${name}' not found`);
        }
    }
}

// Exercise: Build a Calculator with Functions
class FunctionCalculator {
    constructor() {
        this.operations = new Map();
        this.history = [];
        this.initializeOperations();
    }
    
    initializeOperations() {
        // Basic arithmetic
        this.operations.set('add', (a, b) => a + b);
        this.operations.set('subtract', (a, b) => a - b);
        this.operations.set('multiply', (a, b) => a * b);
        this.operations.set('divide', (a, b) => {
            if (b === 0) throw new Error('Division by zero');
            return a / b;
        });
        
        // Advanced operations
        this.operations.set('power', (a, b) => Math.pow(a, b));
        this.operations.set('sqrt', (a) => {
            if (a < 0) throw new Error('Cannot calculate square root of negative number');
            return Math.sqrt(a);
        });
        this.operations.set('log', (a) => Math.log(a));
        
        // Statistical operations
        this.operations.set('average', (...numbers) => {
            if (numbers.length === 0) throw new Error('No numbers provided');
            return numbers.reduce((sum, num) => sum + num, 0) / numbers.length;
        });
        this.operations.set('max', (...numbers) => Math.max(...numbers));
        this.operations.set('min', (...numbers) => Math.min(...numbers));
    }
    
    calculate(operation, ...args) {
        const func = this.operations.get(operation);
        
        if (!func) {
            throw new Error(`Unknown operation: ${operation}`);
        }
        
        try {
            const result = func(...args);
            this.history.push({
                operation,
                args,
                result,
                timestamp: new Date()
            });
            return result;
        } catch (error) {
            console.error(`Calculation error: ${error.message}`);
            throw error;
        }
    }
    
    addCustomOperation(name, func) {
        if (typeof func !== 'function') {
            throw new Error('Second argument must be a function');
        }
        
        this.operations.set(name, func);
        console.log(`Custom operation '${name}' added successfully`);
    }
    
    getHistory() {
        return this.history;
    }
    
    clearHistory() {
        this.history = [];
    }
}

// Test the exercises
const lib = new FunctionLibrary();

// Register some functions
lib.register('greet', (name) => `Hello, ${name}!`, 'Greets a person');
lib.register('add', (a, b) => a + b, 'Adds two numbers');
lib.register('multiply', (a, b) => a * b, 'Multiplies two numbers');

// Execute functions
console.log(lib.execute('greet', 'John')); // "Hello, John!"
console.log(lib.execute('add', 5, 3)); // 8

// List registered functions
console.log(lib.list());

const calc = new FunctionCalculator();

// Basic calculations
console.log(calc.calculate('add', 10, 5)); // 15
console.log(calc.calculate('multiply', 4, 3)); // 12
console.log(calc.calculate('power', 2, 8)); // 256

// Statistical calculations
console.log(calc.calculate('average', 1, 2, 3, 4, 5)); // 3
console.log(calc.calculate('max', 10, 5, 20, 15)); // 20

// Add custom operation
calc.addCustomOperation('factorial', (n) => {
    if (n < 0) throw new Error('Factorial not defined for negative numbers');
    if (n === 0 || n === 1) return 1;
    return n * calc.calculate('factorial', n - 1);
});

console.log(calc.calculate('factorial', 5)); // 120
Swipe to see more code

🎯 Practice Exercise

Test your understanding of this topic:

Ready for the Next Module?

Continue your learning journey and master the next set of concepts.

Continue to Module 4