JavaScript Functions

Functions are one of the most fundamental concepts in JavaScript. They allow you to group code together, reuse it, and make your programs more organized and maintainable.

What are Functions?

Functions are reusable blocks of code that perform specific tasks. They can take input (parameters), process the input, and return output (return values).

Declaring Functions

1. Function Declaration

The most common way to declare a function:

function greet(name) {
  return "Hello, " + name + "!";
}

// Calling the function
console.log(greet("Alice")); // Outputs: Hello, Alice!

2. Function Expression

Assign a function to a variable:

const greet = function(name) {
  return "Hello, " + name + "!";
};

console.log(greet("Bob")); // Outputs: Hello, Bob!

3. Arrow Functions (ES6)

Modern syntax for writing functions:

// Simple arrow function
const greet = (name) => {
  return "Hello, " + name + "!";
};

// Arrow function with implicit return
const greet = (name) => "Hello, " + name + "!";

// Arrow function with single parameter (parentheses optional)
const greet = name => "Hello, " + name + "!";

console.log(greet("Charlie")); // Outputs: Hello, Charlie!

Function Parameters

1. Basic Parameters

function add(a, b) {
  return a + b;
}

console.log(add(5, 3)); // Outputs: 8

2. Default Parameters

ES6 allows you to set default values for parameters:

function greet(name = "Guest") {
  return "Hello, " + name + "!";
}

console.log(greet()); // Outputs: Hello, Guest!
console.log(greet("Alice")); // Outputs: Hello, Alice!

3. Rest Parameters

Handle multiple parameters as an array:

function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

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

Return Values

1. Single Return Value

function multiply(a, b) {
  return a * b;
}

const result = multiply(4, 5);
console.log(result); // Outputs: 20

2. Multiple Return Values

Return an object or array to return multiple values:

function getUserInfo(id) {
  // Simulate database lookup
  if (id === 1) {
    return {
      name: "John Doe",
      age: 30,
      email: "[email protected]"
    };
  }
  return null;
}

const user = getUserInfo(1);
console.log(user.name); // Outputs: John Doe
console.log(user.age); // Outputs: 30

3. Early Returns

Exit a function early based on conditions:

function getDiscount(price, memberLevel) {
  if (!memberLevel) {
    return price; // No discount for non-members
  }
  
  if (memberLevel === "gold") {
    return price * 0.8; // 20% discount
  }
  
  if (memberLevel === "silver") {
    return price * 0.9; // 10% discount
  }
  
  return price; // No discount for other levels
}

console.log(getDiscount(100, "gold")); // Outputs: 80
console.log(getDiscount(100, "silver")); // Outputs: 90
console.log(getDiscount(100, null)); // Outputs: 100

Function Scope

1. Global and Local Scope

const globalVar = "I'm global";

function testScope() {
  const localVar = "I'm local";
  console.log(globalVar); // Can access global
  console.log(localVar);  // Can access local
}

testScope();
console.log(globalVar); // Outputs: I'm global
// console.log(localVar); // Error: localVar is not defined

2. Block Scope (let and const)

function testBlockScope() {
  if (true) {
    const blockVar = "I'm block-scoped";
    console.log(blockVar); // Outputs: I'm block-scoped
  }
  // console.log(blockVar); // Error: blockVar is not defined
}

3. Closures

Functions can remember and access variables from their outer scope:

function createCounter() {
  let count = 0;
  
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // Outputs: 1
console.log(counter()); // Outputs: 2
console.log(counter()); // Outputs: 3

Higher-Order Functions

Functions that take other functions as arguments or return functions:

1. Functions as Arguments

function calculate(operation, a, b) {
  return operation(a, b);
}

function add(x, y) {
  return x + y;
}

function multiply(x, y) {
  return x * y;
}

console.log(calculate(add, 5, 3)); // Outputs: 8
console.log(calculate(multiply, 5, 3)); // Outputs: 15

2. Functions that Return Functions

function createMultiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

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

Built-in Higher-Order Functions

1. Array Methods

const numbers = [1, 2, 3, 4, 5];

// map: Transform each element
const doubled = numbers.map(num => num * 2);
console.log(doubled); // Outputs: [2, 4, 6, 8, 10]

// filter: Keep elements that pass a test
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // Outputs: [2, 4]

// reduce: Reduce array to single value
const sum = numbers.reduce((total, num) => total + num, 0);
console.log(sum); // Outputs: 15

// forEach: Execute function for each element
numbers.forEach(num => console.log(num * 3));
// Outputs: 3, 6, 9, 12, 15

2. String Methods

const text = "Hello World";

// toUpperCase: Convert to uppercase
console.log(text.toUpperCase()); // Outputs: HELLO WORLD

// replace: Replace text
console.log(text.replace("World", "JavaScript")); // Outputs: Hello JavaScript

// split: Split string into array
console.log(text.split(" ")); // Outputs: ["Hello", "World"]

Function Methods

1. call() and apply()

Execute a function with a specific this value:

const person = {
  name: "John",
  age: 30
};

function greet(greeting) {
  return greeting + ", I'm " + this.name + " and I'm " + this.age + " years old.";
}

console.log(greet.call(person, "Hi")); // Outputs: Hi, I'm John and I'm 30 years old.
console.log(greet.apply(person, ["Hello"])); // Outputs: Hello, I'm John and I'm 30 years old.

2. bind()

Create a new function with a specific this value:

const person = {
  name: "Alice"
};

function greet() {
  return "Hello, I'm " + this.name;
}

const boundGreet = greet.bind(person);
console.log(boundGreet()); // Outputs: Hello, I'm Alice

Recursive Functions

Functions that call themselves:

// Factorial using recursion
function factorial(n) {
  if (n <= 1) {
    return 1;
  }
  return n * factorial(n - 1);
}

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

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

console.log(fibonacci(7)); // Outputs: 13

Immediately Invoked Function Expressions (IIFE)

Functions that execute immediately after being defined:

(function() {
  const message = "Hello from IIFE!";
  console.log(message);
})(); // Outputs: Hello from IIFE!

// With parameters
(function(name) {
  console.log("Hello, " + name + "!");
})("World"); // Outputs: Hello, World!

Pure Functions

Functions that always return the same output for the same input and have no side effects:

// Pure function
function add(a, b) {
  return a + b;
}

// Impure function (has side effects)
let total = 0;
function addToTotal(value) {
  total += value; // Modifies external variable
  return total;
}

// Better pure function
function addValues(a, b) {
  return a + b;
}

Function Best Practices

1. Use Descriptive Names

// Good
function calculateArea(length, width) {
  return length * width;
}

// Bad
function calc(l, w) {
  return l * w;
}

2. Keep Functions Small

// Good - Single responsibility
function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

function sanitizeInput(input) {
  return input.trim().toLowerCase();
}

// Bad - Multiple responsibilities
function processEmail(email) {
  const trimmed = email.trim().toLowerCase();
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(trimmed);
}

3. Use Default Parameters

// Good
function createUser(name, role = "user") {
  return { name, role };
}

// Avoid this pattern
function createUser(name, role) {
  if (!role) {
    role = "user";
  }
  return { name, role };
}

4. Document Your Functions

/**
 * Calculates the area of a rectangle
 * @param {number} length - The length of the rectangle
 * @param {number} width - The width of the rectangle
 * @returns {number} The area of the rectangle
 * @throws {Error} If length or width is negative
 */
function calculateArea(length, width) {
  if (length < 0 || width < 0) {
    throw new Error("Length and width must be positive");
  }
  return length * width;
}

Error Handling in Functions

function divide(a, b) {
  try {
    if (b === 0) {
      throw new Error("Cannot divide by zero");
    }
    return a / b;
  } catch (error) {
    console.error("Error:", error.message);
    return null;
  }
}

console.log(divide(10, 2)); // Outputs: 5
console.log(divide(10, 0)); // Outputs: Error: Cannot divide by zero, then null

Complete Example

// A complete example showing various function concepts

// Data validation function
function validateUser(userData) {
  const errors = [];
  
  if (!userData.name || userData.name.trim().length < 2) {
    errors.push("Name must be at least 2 characters");
  }
  
  if (!userData.email || !validateEmail(userData.email)) {
    errors.push("Valid email is required");
  }
  
  if (userData.age && (userData.age < 0 || userData.age > 150)) {
    errors.push("Age must be between 0 and 150");
  }
  
  return {
    isValid: errors.length === 0,
    errors: errors
  };
}

// Email validation helper function
function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

// User creation function with error handling
function createUser(userData) {
  const validation = validateUser(userData);
  
  if (!validation.isValid) {
    throw new Error("Validation failed: " + validation.errors.join(", "));
  }
  
  return {
    id: generateId(),
    name: userData.name.trim(),
    email: userData.email.toLowerCase(),
    age: userData.age || null,
    createdAt: new Date().toISOString()
  };
}

// ID generator function (closure)
function createIdGenerator() {
  let lastId = 0;
  
  return function() {
    lastId++;
    return "user_" + lastId;
  };
}

const generateId = createIdGenerator();

// User processing function (higher-order function)
function processUsers(users, processor) {
  return users.map(processor);
}

// Usage examples
try {
  const userData = {
    name: "John Doe",
    email: "[email protected]",
    age: 30
  };
  
  const user = createUser(userData);
  console.log("Created user:", user);
  
  // Process multiple users
  const users = [
    { name: "Alice", email: "[email protected]" },
    { name: "Bob", email: "[email protected]" }
  ];
  
  const processedUsers = processUsers(users, userData => 
    createUser({ ...userData, age: 25 })
  );
  
  console.log("Processed users:", processedUsers);
  
} catch (error) {
  console.error("Error:", error.message);
}

External Resources:

Related Tutorials:

Last updated on