Master the Building Blocks of JavaScript
Functions are reusable blocks of code that perform a specific task. They're the fundamental building blocks of JavaScript programs, allowing you to organize code, avoid repetition, and create abstractions.
// A simple function
function greet() {
console.log('Hello, World!');
}
greet(); // 'Hello, World!'
Function declarations define named functions using the function keyword:
function add(a, b) {
return a + b;
}
console.log(add(5, 3)); // 8
// Function declarations are hoisted
sayHello(); // Works!
function sayHello() {
console.log('Hello!');
}
Key characteristics:
Function expressions define functions as part of an expression, typically by assigning them to variables:
const multiply = function(a, b) {
return a * b;
};
console.log(multiply(4, 5)); // 20
// Function expressions are NOT hoisted
// subtract(); // Error: Cannot access before initialization
const subtract = function(a, b) {
return a - b;
};
Key characteristics:
Arrow functions provide a concise syntax and have special behavior with this:
// Traditional function expression
const square1 = function(x) {
return x * x;
};
// Arrow function - full syntax
const square2 = (x) => {
return x * x;
};
// Arrow function - concise syntax (implicit return)
const square3 = x => x * x;
console.log(square3(5)); // 25
// Multiple parameters need parentheses
const add = (a, b) => a + b;
// No parameters need empty parentheses
const random = () => Math.random();
// Returning objects requires parentheses
const makePerson = (name, age) => ({ name, age });
console.log(makePerson('Alice', 30)); // { name: 'Alice', age: 30 }
Arrow function rules:
this bindingParameters are variables in the function definition. Arguments are the actual values passed when calling the function:
// Parameters: name, age
function introduce(name, age) {
console.log(`Hi, I'm ${name} and I'm ${age} years old.`);
}
// Arguments: 'Alice', 30
introduce('Alice', 30); // Hi, I'm Alice and I'm 30 years old.
// Too few arguments - undefined
introduce('Bob'); // Hi, I'm Bob and I'm undefined years old.
// Too many arguments - extras ignored
introduce('Charlie', 25, 'extra'); // Hi, I'm Charlie and I'm 25 years old.
function greet(name = 'Guest', greeting = 'Hello') {
console.log(`${greeting}, ${name}!`);
}
greet(); // Hello, Guest!
greet('Alice'); // Hello, Alice!
greet('Bob', 'Hi'); // Hi, Bob!
// Default can be any expression
function createArray(length = 5, fill = 0) {
return new Array(length).fill(fill);
}
console.log(createArray()); // [0, 0, 0, 0, 0]
console.log(createArray(3, 1)); // [1, 1, 1]
Rest parameters collect remaining arguments into an array:
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
// Rest parameter must be last
function logInfo(first, second, ...rest) {
console.log('First:', first);
console.log('Second:', second);
console.log('Rest:', rest);
}
logInfo('a', 'b', 'c', 'd', 'e');
// First: a
// Second: b
// Rest: ['c', 'd', 'e']
Functions can return values using the return statement:
function multiply(a, b) {
return a * b;
}
const result = multiply(5, 3);
console.log(result); // 15
// Without return, functions return undefined
function noReturn() {
console.log('This function returns nothing');
}
console.log(noReturn()); // undefined
// Early return
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
Functions that take other functions as arguments or return functions:
// Function that takes a function as argument
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, console.log);
// 0
// 1
// 2
// Function that returns a function
function multiplyBy(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Functions passed as arguments to be executed later:
// Array methods use callbacks
const numbers = [1, 2, 3, 4, 5];
// map - transform each element
const doubled = numbers.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - select elements
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]
// reduce - combine elements
const sum = numbers.reduce((total, n) => total + n, 0);
console.log(sum); // 15
// forEach - perform action on each
numbers.forEach(n => console.log(n * n));
// 1, 4, 9, 16, 25
// Custom callback example
function processArray(arr, callback) {
const result = [];
for (let item of arr) {
result.push(callback(item));
}
return result;
}
const squared = processArray([1, 2, 3], x => x * x);
console.log(squared); // [1, 4, 9]
Functions that execute immediately after being defined:
// Basic IIFE
(function() {
console.log('This runs immediately!');
})();
// IIFE with parameters
(function(name) {
console.log(`Hello, ${name}!`);
})('Alice');
// IIFE with return value
const result = (function() {
const x = 10;
const y = 20;
return x + y;
})();
console.log(result); // 30
// Arrow function IIFE
(() => {
console.log('Arrow IIFE!');
})();
// Use case: Creating private scope
const counter = (function() {
let count = 0; // Private variable
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
})();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2
// console.log(counter.count); // undefined - private!
Variables declared inside a function are local to that function:
function outer() {
const outerVar = 'I am outer';
function inner() {
const innerVar = 'I am inner';
console.log(outerVar); // Can access outer scope
console.log(innerVar); // Can access own scope
}
inner();
// console.log(innerVar); // Error: innerVar not defined
}
outer();
// Closures - inner functions remember outer scope
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter1 = makeCounter();
const counter2 = makeCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 - separate closure
// Bad
function f(x, y) {
return x + y;
}
// Good
function calculateTotal(price, tax) {
return price + tax;
}
// Bad - does too much
function processUser(user) {
validateUser(user);
saveToDatabase(user);
sendEmail(user);
logActivity(user);
}
// Good - single responsibility
function validateUser(user) { /* ... */ }
function saveUser(user) { /* ... */ }
function notifyUser(user) { /* ... */ }
// Verbose
numbers.map(function(n) {
return n * 2;
});
// Concise
numbers.map(n => n * 2);
// Bad
function greet(name) {
name = name || 'Guest';
console.log(`Hello, ${name}!`);
}
// Good
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
// Bad - nested
function processValue(value) {
if (value) {
if (value > 0) {
if (value < 100) {
return value * 2;
}
}
}
return 0;
}
// Good - early returns
function processValue(value) {
if (!value) return 0;
if (value <= 0) return 0;
if (value >= 100) return 0;
return value * 2;
}
// Bad - arguments object
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
// Good - rest parameters
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
function createGreeter(greeting) {
return function(name) {
return `${greeting}, ${name}!`;
};
}
const sayHello = createGreeter('Hello');
const sayHi = createGreeter('Hi');
console.log(sayHello('Alice')); // Hello, Alice!
console.log(sayHi('Bob')); // Hi, Bob!
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
const expensiveCalculation = memoize((n) => {
console.log('Calculating...');
return n * n;
});
console.log(expensiveCalculation(5)); // Calculating... 25
console.log(expensiveCalculation(5)); // 25 (cached)
function partial(fn, ...fixedArgs) {
return function(...remainingArgs) {
return fn(...fixedArgs, ...remainingArgs);
};
}
function greet(greeting, name) {
return `${greeting}, ${name}!`;
}
const sayHello = partial(greet, 'Hello');
console.log(sayHello('Alice')); // Hello, Alice!
console.log(sayHello('Bob')); // Hello, Bob!
this