JavaScript 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 code, make it reusable, and improve maintainability.

Function Declarations

The most common way to define a function is using a function declaration:

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

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

Function declarations are hoisted, meaning they can be called before they are defined in the code.

Function Expressions

Another way to define a function is using a function expression:

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

// Named function expression
const sayHello = function sayHelloFunc(name) {
  return "Hello, " + name + "!";
};

// Calling the functions
console.log(greet("John"));      // Outputs: "Hello, John!"
console.log(sayHello("Jane"));   // Outputs: "Hello, Jane!"

Unlike function declarations, function expressions are not hoisted. They must be defined before they are called.

Arrow Functions (ES6)

Arrow functions provide a more concise syntax for writing functions and lexically bind the this value:

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

// Simplified return (implicit return)
const greetSimple = name => "Hello, " + name + "!";

// Arrow function with no parameters
const sayHi = () => "Hi there!";

// Arrow function with multiple parameters
const introduce = (name, age) => `I'm ${name} and I'm ${age} years old.`;

// Calling the functions
console.log(greet("John"));              // Outputs: "Hello, John!"
console.log(greetSimple("Jane"));        // Outputs: "Hello, Jane!"
console.log(sayHi());                    // Outputs: "Hi there!"
console.log(introduce("Alice", 30));     // Outputs: "I'm Alice and I'm 30 years old."

Note: Arrow functions don't have their own this context. They inherit this from the enclosing scope, which can be useful in certain situations like callbacks and methods.

Function Parameters

Default Parameters (ES6)

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

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

Rest Parameters (ES6)

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

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

The arguments Object

function oldSchoolSum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

console.log(oldSchoolSum(1, 2, 3, 4)); // Outputs: 10

Warning: The arguments object is not available in arrow functions. Use rest parameters instead for modern code.

Return Values

Functions can return values using the return statement:

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

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

// Early return
function checkAge(age) {
  if (age < 18) {
    return "Too young";
  }
  return "Old enough";
}

// Function with no return statement (or just 'return;')
// implicitly returns undefined
function doSomething() {
  console.log("Doing something...");
  // No return statement
}

const value = doSomething();
console.log(value);  // Outputs: undefined

Function Scope and Closures

Lexical Scope

function outer() {
  const outerVar = "I'm from outer function";
  
  function inner() {
    const innerVar = "I'm from inner function";
    console.log(outerVar);  // Can access outerVar
  }
  
  inner();
  // console.log(innerVar);  // Error: innerVar is not defined
}

outer();

Closures

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

// Each counter is independent
const counter2 = createCounter();
console.log(counter2());  // Outputs: 1

Tip: Closures are powerful for creating private variables and maintaining state between function calls.

Immediately Invoked Function Expressions (IIFE)

An IIFE is a function that runs as soon as it is defined:

(function() {
  console.log("This function runs immediately!");
})();

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

// Arrow function IIFE
(() => {
  console.log("Arrow function IIFE");
})();

IIFEs are useful for creating private scopes and avoiding polluting the global namespace.

Higher-Order Functions

Higher-order functions are functions that take other functions as arguments or return functions:

// Function that takes a function as an argument
function executeOperation(operation, a, b) {
  return operation(a, b);
}

// Functions to pass as arguments
function add(a, b) {
  return a + b;
}

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

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

// Function that returns a function
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

Common Array Methods with Function Arguments

Many array methods in JavaScript take functions as arguments:

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

// map - creates a new array by transforming each element
const doubled = numbers.map(num => num * 2);
console.log(doubled);  // Outputs: [2, 4, 6, 8, 10]

// filter - creates a new array with elements that pass a test
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers);  // Outputs: [2, 4]

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

// forEach - executes a function for each element
numbers.forEach(num => console.log(num));  // Outputs each number

Interactive Example: Function Calculator

Select an operation to perform a calculation.

Best Practices for Functions

  • Single Responsibility: Each function should do one thing and do it well.
  • Descriptive Names: Use clear, descriptive names that indicate what the function does.
  • Keep Functions Small: Aim for functions that are short and focused.
  • Limit Parameters: Try to keep the number of parameters to a minimum (ideally 3 or fewer).
  • Use Default Parameters: Provide default values for parameters when appropriate.
  • Return Early: Use early returns to avoid deep nesting and improve readability.
  • Avoid Side Effects: Functions should ideally not modify variables outside their scope.
  • Consistent Return Types: A function should return consistent types of values.

Next Steps

Now that you understand JavaScript functions, you can explore:

  • Object-oriented programming with JavaScript
  • Functional programming concepts
  • Asynchronous functions with Promises and async/await
  • Advanced patterns like currying and composition