JavaScript Functions
Learn how to create and use functions to organize your code into reusable, modular pieces
What are Functions?
Functions are one of the fundamental building blocks in JavaScript. A function is a reusable block of code designed to perform a particular task. Functions help organize your code, make it reusable, and keep your program modular.
Function Declaration
The most common way to define a function is with a function declaration, which consists of the function
keyword, followed by:
- The name of the function
- A list of parameters enclosed in parentheses (optional)
- The function body enclosed in curly braces
1// Basic function declaration
2function greet() {
3console.log("Hello, world!");
4}
56// Function with parameters
7function greetPerson(name) {
8console.log("Hello, " + name + "!");
9}
1011// Function with return value
12function sum(a, b) {
13return a + b;
14}
1516// Calling functions
17greet(); // Output: Hello, world!
18greetPerson("Alice"); // Output: Hello, Alice!
19let result = sum(5, 3); // result = 8
20console.log(result); // Output: 8
Function Expressions
Another way to define a function is with a function expression. In this case, the function can be anonymous (without a name) or it can have a name.
1// Anonymous function expression
2const greet = function() {
3console.log("Hello, world!");
4};
56// Named function expression
7const sum = function addNumbers(a, b) {
8return a + b;
9};
1011// Calling function expressions
12greet(); // Output: Hello, world!
13console.log(sum(5, 3)); // Output: 8
1415// The function name in a named function expression is only visible inside the function
16// console.log(addNumbers); // Error: addNumbers is not defined
Key Difference
Function declarations are hoisted (moved to the top of their scope), which means you can call them before they appear in your code. Function expressions are not hoisted in the same way.
Arrow functions were introduced in ES6 (ECMAScript 2015) and provide a more concise syntax for writing functions. They are especially useful for short, simple functions and for functions that need to preserve the lexical this
value.
1// Basic arrow function
2const greet = () => {
3console.log("Hello, world!");
4};
56// Arrow function with one parameter (parentheses optional)
7const greetPerson = name => {
8console.log("Hello, " + name + "!");
9};
1011// Arrow function with multiple parameters
12const sum = (a, b) => {
13return a + b;
14};
1516// Arrow function with implicit return (no curly braces)
17const multiply = (a, b) => a * b;
1819// Calling arrow functions
20greet(); // Output: Hello, world!
21greetPerson("Bob"); // Output: Hello, Bob!
22console.log(sum(5, 3)); // Output: 8
23console.log(multiply(4, 2)); // Output: 8
Arrow Functions and Lexical this
One of the main advantages of arrow functions is that they don't have their own this
binding. Instead, they inherit this
from the surrounding code (lexical scoping).
1// Regular function vs Arrow function with 'this'
23// With regular function
4const person = {
5name: "Alice",
6regularFunction: function() {
7console.log(this.name); // 'this' refers to the person object
8},
9arrowFunction: () => {
10console.log(this.name); // 'this' refers to the outer scope (likely window or undefined)
11},
12delayedRegular: function() {
13setTimeout(function() {
14console.log(this.name); // 'this' is lost, refers to window or undefined
15}, 100);
16},
17delayedArrow: function() {
18setTimeout(() => {
19console.log(this.name); // 'this' is preserved, refers to person
20}, 100);
21}
22};
2324person.regularFunction(); // Output: Alice
25person.arrowFunction(); // Output: undefined (or depends on global context)
26person.delayedRegular(); // Output: undefined (or depends on global context)
27person.delayedArrow(); // Output: Alice
When to Use Arrow Functions
Use arrow functions when you want to preserve the lexical this
, such as in callbacks or event handlers. Avoid using arrow functions for object methods, constructors, or when you need access to arguments
object.
Basic Parameters
Parameters are the names listed in the function definition, while arguments are the actual values passed to the function when it is called.
1// Function with two parameters
2function add(x, y) {
3return x + y;
4}
56// Calling with two arguments
7console.log(add(5, 3)); // Output: 8
89// What happens with missing arguments?
10console.log(add(5)); // Output: NaN (5 + undefined = NaN)
1112// What happens with extra arguments?
13console.log(add(5, 3, 2)); // Output: 8 (extra arguments are ignored)
Default Parameters
ES6 introduced default parameters, which allow you to specify default values for parameters if no argument is provided or if undefined
is passed.
1// Function with default parameters
2function greet(name = "Guest", greeting = "Hello") {
3return `${greeting}, ${name}!`;
4}
56console.log(greet()); // Output: Hello, Guest!
7console.log(greet("Alice")); // Output: Hello, Alice!
8console.log(greet("Bob", "Hi")); // Output: Hi, Bob!
9console.log(greet(undefined, "Hey")); // Output: Hey, Guest!
1011// Default parameters can use previous parameters
12function createUser(name, role = "user", id = generateId(name)) {
13function generateId(name) {
14return name.toLowerCase().replace(/\s/g, "") + Date.now();
15}
16return { name, role, id };
17}
1819console.log(createUser("John Doe"));
20// Output: { name: "John Doe", role: "user", id: "johndoe1234567890" }
Rest Parameters
The rest parameter syntax allows a function to accept an indefinite number of arguments as an array. It's denoted by three dots (...
) followed by a parameter name.
1// Function with rest parameter
2function sum(...numbers) {
3return numbers.reduce((total, num) => total + num, 0);
4}
56console.log(sum(1, 2)); // Output: 3
7console.log(sum(1, 2, 3, 4, 5)); // Output: 15
8console.log(sum()); // Output: 0
910// Rest parameter must be the last parameter
11function logDetails(name, ...details) {
12console.log(`Name: ${name}`);
13console.log(`Details: ${details.join(", ")}`);
14}
1516logDetails("Alice", "Developer", "New York", "30");
17// Output:
18// Name: Alice
19// Details: Developer, New York, 30
The Arguments Object
In non-arrow functions, you can access all arguments passed to a function using the arguments
object, which is an array-like object containing all arguments.
1// Using the arguments object
2function logAll() {
3for (let i = 0; i < arguments.length; i++) {
4console.log(`Argument ${i}: ${arguments[i]}`);
5}
6}
78logAll("a", "b", "c");
9// Output:
10// Argument 0: a
11// Argument 1: b
12// Argument 2: c
1314// Note: arguments is not available in arrow functions
15const arrowLogAll = () => {
16console.log(arguments); // Error or unexpected behavior
17};
1819// Use rest parameters instead for arrow functions
20const betterArrowLogAll = (...args) => {
21args.forEach((arg, i) => {
22console.log(`Argument ${i}: ${arg}`);
23});
24};
Parameter Destructuring
You can use object and array destructuring in function parameters to extract values from objects and arrays passed as arguments.
1// Object destructuring in parameters
2function displayPerson({ name, age, job = "Unknown" }) {
3console.log(`Name: ${name}, Age: ${age}, Job: ${job}`);
4}
56const person = { name: "Alice", age: 30, location: "New York" };
7displayPerson(person);
8// Output: Name: Alice, Age: 30, Job: Unknown
910// Array destructuring in parameters
11function displayCoordinates([x, y, z = 0]) {
12console.log(`X: ${x}, Y: ${y}, Z: ${z}`);
13}
1415displayCoordinates([10, 20]);
16// Output: X: 10, Y: 20, Z: 0
Variable Scope
Scope determines the accessibility of variables in your code. JavaScript has three types of scope: global scope, function scope, and block scope (introduced with let
and const
in ES6).
1// Global scope
2let globalVar = "I'm global";
34function exampleFunction() {
5// Function scope
6let functionVar = "I'm function-scoped";
7
8console.log(globalVar); // Accessible: Output: I'm global
9console.log(functionVar); // Accessible: Output: I'm function-scoped
10
11if (true) {
12// Block scope
13let blockVar = "I'm block-scoped";
14var functionScopedVar = "I'm function-scoped too";
15
16console.log(blockVar); // Accessible: Output: I'm block-scoped
17}
18
19// console.log(blockVar); // Error: blockVar is not defined
20console.log(functionScopedVar); // Accessible: Output: I'm function-scoped too
21}
2223exampleFunction();
24console.log(globalVar); // Accessible: Output: I'm global
25// console.log(functionVar); // Error: functionVar is not defined
Scope Rules
var
declarations are function-scoped (visible throughout the function).let
and const
declarations are block-scoped (visible only within the block).
Variables declared without var
, let
, or const
become global variables (not recommended).
Closures
A closure is a function that has access to variables from its outer (enclosing) function's scope, even after the outer function has returned. Closures are one of the most powerful features in JavaScript.
1// Basic closure example
2function createGreeter(greeting) {
3// The inner function is a closure that "closes over" the greeting variable
4return function(name) {
5return `${greeting}, ${name}!`;
6};
7}
89const sayHello = createGreeter("Hello");
10const sayHi = createGreeter("Hi");
1112console.log(sayHello("Alice")); // Output: Hello, Alice!
13console.log(sayHi("Bob")); // Output: Hi, Bob!
1415// Practical closure example: counter
16function createCounter() {
17let count = 0; // Private variable
18
19return {
20increment() {
21count++;
22return count;
23},
24decrement() {
25count--;
26return count;
27},
28getCount() {
29return count;
30}
31};
32}
3334const counter = createCounter();
35console.log(counter.increment()); // Output: 1
36console.log(counter.increment()); // Output: 2
37console.log(counter.getCount()); // Output: 2
38console.log(counter.decrement()); // Output: 1
3940// The count variable is not directly accessible
41// console.log(counter.count); // Output: undefined
Common Closure Use Cases
1// Data privacy / encapsulation
2function createBankAccount(initialBalance) {
3let balance = initialBalance;
4
5return {
6deposit(amount) {
7if (amount > 0) {
8balance += amount;
9return `Deposited ${amount}. New balance: ${balance}`;
10}
11return "Invalid deposit amount";
12},
13withdraw(amount) {
14if (amount > 0 && amount <= balance) {
15balance -= amount;
16return `Withdrew ${amount}. New balance: ${balance}`;
17}
18return "Invalid withdrawal amount";
19},
20getBalance() {
21return `Current balance: ${balance}`;
22}
23};
24}
2526const account = createBankAccount(100);
27console.log(account.getBalance()); // Output: Current balance: 100
28console.log(account.deposit(50)); // Output: Deposited 50. New balance: 150
29console.log(account.withdraw(30)); // Output: Withdrew 30. New balance: 120
30console.log(account.withdraw(200)); // Output: Invalid withdrawal amount
3132// Function factory
33function createMultiplier(factor) {
34return function(number) {
35return number * factor;
36};
37}
3839const double = createMultiplier(2);
40const triple = createMultiplier(3);
4142console.log(double(5)); // Output: 10
43console.log(triple(5)); // Output: 15
Closure Gotchas
Be careful with closures in loops. Variables captured by closures refer to their final values, not the value at the time the closure was created. Use let instead of var in loops, or create a new scope for each iteration.
Higher-Order Functions
Higher-order functions are functions that take other functions as arguments or return functions as their result. They are a powerful concept in functional programming.
1// Function that takes a function as an argument
2function executeOperation(operation, a, b) {
3return operation(a, b);
4}
56// Functions to pass as arguments
7function add(x, y) {
8return x + y;
9}
1011function multiply(x, y) {
12return x * y;
13}
1415console.log(executeOperation(add, 5, 3)); // Output: 8
16console.log(executeOperation(multiply, 5, 3)); // Output: 15
1718// Using arrow functions
19console.log(executeOperation((x, y) => x - y, 5, 3)); // Output: 2
2021// Array methods that are higher-order functions
22const numbers = [1, 2, 3, 4, 5];
2324// map: transforms each element
25const doubled = numbers.map(num => num * 2);
26console.log(doubled); // Output: [2, 4, 6, 8, 10]
2728// filter: selects elements that match a condition
29const evenNumbers = numbers.filter(num => num % 2 === 0);
30console.log(evenNumbers); // Output: [2, 4]
3132// reduce: accumulates values
33const sum = numbers.reduce((total, num) => total + num, 0);
34console.log(sum); // Output: 15
Immediately Invoked Function Expressions (IIFE)
An IIFE is a function that is executed immediately after it is created. It's a common pattern used to create a private scope for variables.
1// Basic IIFE syntax
2(function() {
3console.log("This function is executed immediately");
4})();
56// IIFE with parameters
7(function(name) {
8console.log("Hello, " + name);
9})("Alice");
1011// IIFE with arrow function
12(() => {
13console.log("Arrow function IIFE");
14})();
1516// IIFE to create private scope
17const counter = (function() {
18let count = 0; // Private variable
19
20return {
21increment() {
22return ++count;
23},
24decrement() {
25return --count;
26},
27value() {
28return count;
29}
30};
31})();
3233console.log(counter.increment()); // Output: 1
34console.log(counter.increment()); // Output: 2
35console.log(counter.value()); // Output: 2
Function Binding and this
JavaScript provides methods to control what this
refers to when a function is called:bind()
, call()
, and apply()
.
1// The 'this' problem
2const person = {
3name: "Alice",
4greet() {
5console.log(`Hello, my name is ${this.name}`);
6}
7};
89person.greet(); // Output: Hello, my name is Alice
1011// Losing 'this' context
12const greetFunction = person.greet;
13// greetFunction(); // Output: Hello, my name is undefined
1415// Solution 1: bind()
16const boundGreet = person.greet.bind(person);
17boundGreet(); // Output: Hello, my name is Alice
1819// bind() with arguments
20function multiply(a, b) {
21return a * b;
22}
2324const double = multiply.bind(null, 2);
25console.log(double(5)); // Output: 10
2627// Solution 2: call()
28greetFunction.call(person); // Output: Hello, my name is Alice
2930// call() with arguments
31function introduce(greeting) {
32console.log(`${greeting}, my name is ${this.name}`);
33}
3435introduce.call(person, "Hi"); // Output: Hi, my name is Alice
3637// Solution 3: apply()
38introduce.apply(person, ["Hello"]); // Output: Hello, my name is Alice
Function Composition and Currying
Function composition and currying are advanced functional programming techniques that help create more modular and reusable code.
1// Function composition
2function addOne(x) {
3return x + 1;
4}
56function double(x) {
7return x * 2;
8}
910// Manual composition
11function addOneThenDouble(x) {
12return double(addOne(x));
13}
1415console.log(addOneThenDouble(3)); // Output: 8
1617// Composition helper
18function compose(f, g) {
19return function(x) {
20return f(g(x));
21};
22}
2324const addOneThenDouble2 = compose(double, addOne);
25console.log(addOneThenDouble2(3)); // Output: 8
2627// Currying
28function curry(fn) {
29return function curried(...args) {
30if (args.length >= fn.length) {
31return fn.apply(this, args);
32} else {
33return function(...moreArgs) {
34return curried.apply(this, args.concat(moreArgs));
35};
36}
37};
38}
3940function add(a, b, c) {
41return a + b + c;
42}
4344const curriedAdd = curry(add);
4546console.log(curriedAdd(1)(2)(3)); // Output: 6
47console.log(curriedAdd(1, 2)(3)); // Output: 6
48console.log(curriedAdd(1)(2, 3)); // Output: 6
49console.log(curriedAdd(1, 2, 3)); // Output: 6