Module 5: Functions
Functions are the fundamental building blocks of JavaScript. They allow you to write reusable, modular, and maintainable code.
1. Function Declaration
The traditional way to define a function.
function greet(name) {
return `Hello, ${name}!`;
}
console.log(greet('John')); // Hello, John!
Characteristics
- Hoisted – can be called before declaration
- Has
thisbinding - Has
argumentsobject
// Works due to hoisting
sayHi(); // Hello!
function sayHi() {
console.log('Hello!');
}
2. Function Expression
Function assigned to a variable.
const greet = function(name) {
return `Hello, ${name}!`;
};
console.log(greet('Jane')); // Hello, Jane!
Named Function Expression
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // Can reference itself
};
Function expressions are not hoisted. They must be defined before use.
sayHi(); // ❌ Error: Cannot access before initialization
const sayHi = function() {
console.log('Hello!');
};
3. Arrow Functions (ES6)
Modern, concise syntax for functions.
Basic Syntax
// Traditional
function add(a, b) {
return a + b;
}
// Arrow function
const add = (a, b) => {
return a + b;
};
// Concise (implicit return)
const add = (a, b) => a + b;
// Single parameter (parentheses optional)
const square = x => x * x;
// No parameters
const greet = () => 'Hello!';
Important Differences
// No 'this' binding (lexical this)
// No 'arguments' object
// Cannot be used as constructors
// Cannot be generators
Use arrow functions for:
- Short, simple functions
- Callbacks and array methods
- When you need lexical
this
Use regular functions for:
- Methods in objects/classes
- Functions needing
arguments - Constructor functions
4. Parameters and Arguments
4.1 Default Parameters (ES6)
function greet(name = 'Guest', greeting = 'Hello') {
return `${greeting}, ${name}!`;
}
greet(); // Hello, Guest!
greet('John'); // Hello, John!
greet('Jane', 'Hi'); // Hi, Jane!
4.2 Rest Parameters (ES6)
Collect all remaining arguments into an array.
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15
// Rest must be last parameter
function logInfo(message, ...details) {
console.log(message);
console.log(details);
}
4.3 Spread Operator
const numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // 3
// Combining arrays
const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4]
4.4 Destructuring Parameters
// Object destructuring
function createUser({ name, age, email }) {
return { name, age, email };
}
createUser({ name: 'John', age: 30, email: 'john@example.com' });
// Array destructuring
function getCoordinates([x, y]) {
return { x, y };
}
getCoordinates([10, 20]);
// With defaults
function login({ username, password, remember = false }) {
// ...
}
5. Return Statement
5.1 Explicit Return
function add(a, b) {
return a + b;
}
function getUser(id) {
const user = findUser(id);
return user;
}
5.2 Implicit Return (Arrow Functions)
const add = (a, b) => a + b;
const getUser = id => ({ id, name: 'John' }); // Object needs parentheses
5.3 Early Return
function processOrder(order) {
if (!order) return null;
if (!order.items.length) return null;
// Process order
return processedOrder;
}
5.4 Multiple Return Values
// Return object
function getUser() {
return {
name: 'John',
age: 30,
email: 'john@example.com'
};
}
// Return array (for destructuring)
function getCoordinates() {
return [10, 20];
}
const [x, y] = getCoordinates();
6. Scope
6.1 Global Scope
const globalVar = 'I am global';
function test() {
console.log(globalVar); // Accessible
}
6.2 Function Scope
function test() {
var functionScoped = 'I am function scoped';
console.log(functionScoped); // ✅ Works
}
console.log(functionScoped); // ❌ Error
6.3 Block Scope (let & const)
{
let blockScoped = 'I am block scoped';
const alsoBlockScoped = 'Me too';
console.log(blockScoped); // ✅ Works
}
console.log(blockScoped); // ❌ Error
6.4 Lexical Scope
function outer() {
const outerVar = 'outer';
function inner() {
console.log(outerVar); // ✅ Can access parent scope
}
inner();
}
7. Closures
A closure is a function that has access to variables in its outer scope, even after the outer function has returned.
Basic Closure
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
Practical Use Cases
// Private variables
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
}
return 'Insufficient funds';
},
getBalance() {
return balance;
}
};
}
const account = createBankAccount(1000);
account.deposit(500); // 1500
account.withdraw(200); // 1300
// Cannot access balance directly
Function Factory
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Closures keep references to outer variables, which can prevent garbage collection. Be mindful in loops and large-scale applications.
8. Higher-Order Functions
Functions that accept functions as parameters or return functions.
Functions as Arguments
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, console.log);
// 0
// 1
// 2
// Callback pattern
function fetchData(url, callback) {
// Simulate async operation
setTimeout(() => {
const data = { user: 'John' };
callback(data);
}, 1000);
}
fetchData('/api/user', data => {
console.log(data);
});
Functions as Return Values
function createGreeter(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const sayHello = createGreeter('Hello');
const sayHi = createGreeter('Hi');
console.log(sayHello('John')); // Hello, John!
console.log(sayHi('Jane')); // Hi, Jane!
9. Recursion
A function that calls itself.
Basic Recursion
function countdown(n) {
if (n <= 0) return; // Base case
console.log(n);
countdown(n - 1); // Recursive call
}
countdown(5); // 5, 4, 3, 2, 1
Factorial
function factorial(n) {
if (n <= 1) return 1; // Base case
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
Fibonacci
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// With memoization (optimization)
function fibonacciMemo() {
const cache = {};
return function fib(n) {
if (n in cache) return cache[n];
if (n <= 1) return n;
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
};
}
const fib = fibonacciMemo();
console.log(fib(10)); // 55
Deep recursion can cause stack overflow. Consider iteration or tail call optimization when possible.
10. Immediately Invoked Function Expression (IIFE)
Function that executes immediately after definition.
(function() {
console.log('I run immediately!');
})();
// With parameters
(function(name) {
console.log(`Hello, ${name}!`);
})('John');
// Arrow function IIFE
(() => {
console.log('Modern IIFE');
})();
// Use case: Private scope
const counter = (function() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
value: () => count
};
})();
11. Function Methods
11.1 call()
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: 'John' };
console.log(greet.call(user, 'Hello', '!')); // Hello, John!
11.2 apply()
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const user = { name: 'John' };
console.log(greet.apply(user, ['Hello', '!'])); // Hello, John!
11.3 bind()
function greet(greeting) {
return `${greeting}, ${this.name}!`;
}
const user = { name: 'John' };
const greetJohn = greet.bind(user);
console.log(greetJohn('Hello')); // Hello, John!
console.log(greetJohn('Hi')); // Hi, John!
// Partial application
const sayHello = greet.bind(user, 'Hello');
console.log(sayHello()); // Hello, John!
12. Best Practices
12.1 Keep Functions Small
// Bad: Does too much
function processUser(user) {
// Validate
// Transform
// Save to database
// Send email
// Log activity
}
// Good: Single responsibility
function validateUser(user) { /* ... */ }
function transformUser(user) { /* ... */ }
function saveUser(user) { /* ... */ }
function sendWelcomeEmail(user) { /* ... */ }
12.2 Use Descriptive Names
// Bad
function f(x) { return x * 2; }
// Good
function doubleNumber(number) { return number * 2; }
12.3 Avoid Side Effects
// Bad: Modifies external state
let total = 0;
function addToTotal(num) {
total += num;
}
// Good: Pure function
function add(a, b) {
return a + b;
}
12.4 Prefer Pure Functions
// Pure: Same input = same output, no side effects
function calculateTax(amount, rate) {
return amount * rate;
}
// Impure: Depends on external state
const taxRate = 0.1;
function calculateTax(amount) {
return amount * taxRate;
}
Summary
In this module, you learned:
- ✅ Function declarations, expressions, and arrow functions
- ✅ Parameters: default, rest, spread, destructuring
- ✅ Return statements and patterns
- ✅ Scope: global, function, block, lexical
- ✅ Closures and their practical applications
- ✅ Higher-order functions
- ✅ Recursion and memoization
- ✅ IIFE and function methods
- ✅ Best practices for clean functions
In Module 6, you'll learn about Arrays – one of the most important data structures in JavaScript.
Practice Exercises
- Create a function with default parameters
- Write a function using rest parameters to sum any number of arguments
- Implement a counter using closures
- Create a higher-order function that filters an array
- Write a recursive function to reverse a string
- Build a function factory that creates customized calculators
- Implement memoization for an expensive function
- Create a utility library using IIFE