Functions Advanced - Exercise 1
A callback function is a function that is passed as an argument to another function and is executed later. Callbacks are commonly used for handling asynchronous operations like events, timers, and API requests.
// A callback function example
function greet(name, callback) {
console.log('Hello, ' + name);
callback();
}
function sayGoodbye() {
console.log('Goodbye!');
}
// Pass sayGoodbye as a callback
greet('Alice', sayGoodbye);
// Output:
// Hello, Alice
// Goodbye!
You pass a function as an argument by using the function name without parentheses. When you include parentheses, the function executes immediately instead of being passed as a reference.
function processNumber(num, operation) {
return operation(num);
}
function double(x) {
return x * 2;
}
function square(x) {
return x * x;
}
// Pass functions without parentheses
console.log(processNumber(5, double)); // 10
console.log(processNumber(5, square)); // 25
// You can also pass anonymous functions
console.log(processNumber(5, function(x) { return x + 10; })); // 15
A higher order function is a function that either takes one or more functions as arguments, returns a function, or both. Common examples include map, filter, and reduce array methods.
// Higher order function that takes a function as argument
function repeat(action, times) {
for (let i = 0; i < times; i++) {
action(i);
}
}
repeat(console.log, 3); // Logs: 0, 1, 2
// Higher order function that returns a function
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const triple = multiplier(3);
console.log(triple(5)); // 15
A function declaration uses the function keyword followed by a name and is hoisted to the top. A function expression assigns a function to a variable and is not hoisted, so it cannot be called before it is defined.
// Function Declaration (hoisted)
sayHello(); // Works! Outputs: Hello!
function sayHello() {
console.log('Hello!');
}
// Function Expression (not hoisted)
// sayGoodbye(); // Error! Cannot access before initialization
const sayGoodbye = function() {
console.log('Goodbye!');
};
sayGoodbye(); // Works after definition
An arrow function is a shorter syntax for writing functions using the => symbol. Arrow functions do not have their own this binding and cannot be used as constructors. They are ideal for short, simple functions.
// Regular function
const add = function(a, b) {
return a + b;
};
// Arrow function
const addArrow = (a, b) => {
return a + b;
};
// Arrow function with implicit return (single expression)
const addShort = (a, b) => a + b;
// Arrow function with one parameter (no parentheses needed)
const double = x => x * 2;
// Arrow function with no parameters
const greet = () => 'Hello!';
console.log(addShort(2, 3)); // 5
console.log(double(4)); // 8
console.log(greet()); // Hello!
The return statement stops function execution and sends a value back to the caller. If no return statement is used or return has no value, the function returns undefined. A function can only return one value.
// Function with return value
function multiply(a, b) {
return a * b;
console.log('This never runs'); // Code after return is ignored
}
const result = multiply(4, 5);
console.log(result); // 20
// Function without return (returns undefined)
function logMessage(msg) {
console.log(msg);
}
const output = logMessage('Hi');
console.log(output); // undefined
// Early return for validation
function divide(a, b) {
if (b === 0) {
return 'Cannot divide by zero';
}
return a / b;
}
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // Cannot divide by zero
Default parameters allow you to set default values for function parameters. If no argument is passed or undefined is passed, the default value is used instead. This helps avoid undefined values in your functions.
// Function with default parameters
function greet(name = 'Guest', greeting = 'Hello') {
return greeting + ', ' + name + '!';
}
console.log(greet()); // Hello, Guest!
console.log(greet('Alice')); // Hello, Alice!
console.log(greet('Bob', 'Hi')); // Hi, Bob!
console.log(greet(undefined, 'Hey')); // Hey, Guest!
// Default parameter can use previous parameters
function createUser(name, role = 'user', id = name + '_' + role) {
return { name, role, id };
}
console.log(createUser('john'));
// { name: 'john', role: 'user', id: 'john_user' }
The rest parameter uses three dots (...) before a parameter name to collect all remaining arguments into an array. It must be the last parameter in the function definition and there can only be one rest parameter.
// Rest parameter collects remaining arguments
function sum(...numbers) {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4, 5)); // 15
// Rest parameter with other parameters
function introduce(greeting, ...names) {
return greeting + ' ' + names.join(', ');
}
console.log(introduce('Hello', 'Alice', 'Bob', 'Charlie'));
// Hello Alice, Bob, Charlie
// Rest parameter is always an array
function showType(...args) {
console.log(Array.isArray(args)); // true
console.log(args);
}
showType(1, 'hello', true); // [1, 'hello', true]
The spread operator uses three dots (...) to expand an array or iterable into individual arguments when calling a function. It is the opposite of the rest parameter and is useful when you have values in an array that you want to pass as separate arguments.
// Spread operator expands array into arguments
function add(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
console.log(add(...numbers)); // 6
// Without spread, you would need to do this
console.log(add(numbers[0], numbers[1], numbers[2])); // 6
// Using spread with Math functions
const scores = [85, 92, 78, 95, 88];
console.log(Math.max(...scores)); // 95
console.log(Math.min(...scores)); // 78
// Combining arrays and individual values
function greetAll(...names) {
return 'Hello ' + names.join(', ');
}
const friends = ['Alice', 'Bob'];
console.log(greetAll('Sam', ...friends, 'Eve'));
// Hello Sam, Alice, Bob, Eve
Function hoisting is a JavaScript behavior where function declarations are moved to the top of their scope during compilation. This allows you to call a function before it appears in the code. Function expressions are not hoisted.
// Function declaration is hoisted
console.log(sayHello()); // Hello!
function sayHello() {
return 'Hello!';
}
// Function expression is NOT hoisted
// console.log(sayBye()); // Error: Cannot access before initialization
const sayBye = function() {
return 'Bye!';
};
console.log(sayBye()); // Bye!
// Arrow functions are also NOT hoisted
// console.log(greet()); // Error: Cannot access before initialization
const greet = () => 'Hi there!';
console.log(greet()); // Hi there!
An anonymous function is a function without a name. They are often used as arguments to other functions or assigned to variables. Arrow functions are a common way to write anonymous functions in modern JavaScript.
// Anonymous function assigned to a variable
const greet = function() {
return 'Hello!';
};
// Anonymous arrow function
const add = (a, b) => a + b;
// Anonymous function as callback
setTimeout(function() {
console.log('This runs after 1 second');
}, 1000);
// Anonymous arrow function as callback
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function(num) {
return num * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]
// Shorter with arrow function
const tripled = numbers.map(num => num * 3);
console.log(tripled); // [3, 6, 9, 12, 15]
An IIFE is a function that runs immediately after it is defined. It is wrapped in parentheses to make it an expression, followed by another set of parentheses to invoke it. IIFEs are useful for creating private scopes and avoiding global variable pollution.
// Basic IIFE syntax
(function() {
console.log('I run immediately!');
})();
// IIFE with arrow function
(() => {
console.log('Arrow IIFE!');
})();
// IIFE with parameters
(function(name) {
console.log('Hello, ' + name);
})('Alice'); // Hello, Alice
// IIFE for private scope
const counter = (function() {
let count = 0; // Private variable
return {
increment: function() { count++; },
decrement: function() { count--; },
getCount: function() { return count; }
};
})();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
// console.log(count); // Error: count is not defined