Week 8: Functions - The Power of Reusability

Learn how to define and use functions to write organized, reusable, and efficient JavaScript code.

Explore Chapter 8

Chapter 8: Writing Reusable Code with Functions

Defining Functions: Declaration vs. Expression.

Functions are fundamental building blocks in JavaScript. They are blocks of code designed to perform a particular task. Functions allow you to make your code more modular, reusable, and easier to debug.

There are several ways to define functions in JavaScript:

1. Function Declaration (Statement)

This is the classic way to define a function using the function keyword.

function greet(name) {
  console.log("Hello, " + name + "!");
}

// Calling the function
greet("Alice"); // Output: Hello, Alice!
  • Function declarations are "hoisted", meaning the JavaScript interpreter moves their definition to the top of their scope before code execution. This allows you to call a function declaration *before* it appears in the code.

2. Function Expression

A function expression defines a function as part of a larger expression, typically assigning it to a variable. The function can be named or anonymous.

// Anonymous function expression assigned to a variable
const farewell = function(name) {
  console.log("Goodbye, " + name + "!");
};

// Named function expression (less common, useful for debugging)
const add = function sum(x, y) {
  return x + y;
};

// Calling the function via the variable
farewell("Bob"); // Output: Goodbye, Bob!
let result = add(5, 3); // result = 8
console.log(result);
  • Function expressions are *not* hoisted in the same way as declarations. You cannot call a function expression before its definition in the code.

We will also cover Arrow Functions later this week, which provide a more concise syntax for function expressions.

Function Parameters and Arguments.

Parameters are the names listed in the function definition (placeholders for inputs), while arguments are the actual values passed to the function when it is called.

Passing Arguments

Arguments are passed to functions based on their position.

function displayInfo(username, age) {
  console.log(`Username: ${username}, Age: ${age}`);
}

displayInfo("Charlie", 35); // "Charlie" maps to username, 35 maps to age

Default Parameters (ES6+)

You can provide default values for parameters, which are used if an argument is not provided for that parameter during the function call.

function createMessage(text, sender = "System") { // "System" is the default
  console.log(`From ${sender}: ${text}`);
}

createMessage("File saved successfully."); // Output: From System: File saved successfully.
createMessage("Need assistance!", "User123"); // Output: From User123: Need assistance!

Rest Parameters (ES6+)

The rest parameter syntax (...parameterName) allows a function to accept an indefinite number of arguments as an array. It must be the last parameter in the function definition.

function sumAll(...numbers) { // Collects all arguments into the 'numbers' array
  let total = 0;
  for (let num of numbers) {
    total += num;
  }
  return total;
}

console.log(sumAll(1, 2, 3));       // Output: 6
console.log(sumAll(10, 20, 30, 40)); // Output: 100
console.log(sumAll(5));            // Output: 5

Returning Values from Functions.

Functions often compute a value and need to send it back to the part of the code that called them. This is done using the return statement.

The return Statement

When a return statement is executed, the function immediately stops executing and sends the specified value back to the caller.

function multiply(a, b) {
  return a * b; // Returns the product of a and b
  // Any code after the return statement in the same block is unreachable
  console.log("This won't execute");
}

let product = multiply(6, 7); // The returned value (42) is stored in 'product'
console.log(product); // Output: 42

Returning Multiple Values

While a function can only have one return statement execute, you can return multiple values by returning them within an array or an object.

// Returning multiple values using an array
function getCoordinates() {
  let x = 10;
  let y = 25;
  return [x, y]; // Return an array
}

let coords = getCoordinates();
let xPos = coords[0]; // 10
let yPos = coords[1]; // 25
console.log(`Position: x=${xPos}, y=${yPos}`);

// Returning multiple values using an object
function getUserData() {
    return { username: 'Dave', id: 102 }; // Return an object
}

let userData = getUserData();
console.log(`Username: ${userData.username}, ID: ${userData.id}`);

Returning undefined

If a function doesn't have an explicit return statement, or if it has a return; statement with no value, it implicitly returns undefined.

function logMessage(msg) {
  console.log(msg);
  // No return statement here
}

let returnValue = logMessage("Testing"); // Logs "Testing" to console
console.log(returnValue); // Output: undefined

Scope of Variables (Global, Function, Block).

Scope determines the accessibility (visibility) of variables. Where a variable is declared affects where it can be used in your code.

Global Scope

Variables declared outside of any function or block have global scope. They can be accessed from anywhere in your JavaScript code (including inside functions).

let globalVar = "I am global";

function checkGlobal() {
  console.log(globalVar); // Accessible inside the function
}

checkGlobal(); // Output: I am global
console.log(globalVar); // Also accessible outside

Relying heavily on global variables is generally discouraged as it can lead to naming conflicts and make code harder to manage.

Function Scope (var)

Variables declared using var (the older way) inside a function have function scope. They are accessible anywhere within that function, even before their declaration (due to hoisting, where their declaration, but not assignment, is moved to the top), but not outside the function.

function functionScopeTest() {
  if (true) {
    var message = "Hello from var";
  }
  console.log(message); // Accessible here - Output: Hello from var
}
functionScopeTest();
// console.log(message); // Error: message is not defined (outside function scope)

Block Scope (let and const)

Variables declared using let and const (introduced in ES6) have block scope. They are only accessible within the block ({...}) in which they are defined (e.g., inside an if statement, a for loop, or a function).

function blockScopeTest() {
  let functionLevel = "I'm function-scoped (using let)";
  if (true) {
    let blockVar = "I am block-scoped";
    const blockConst = "Me too!";
    console.log(blockVar);   // Accessible here
    console.log(blockConst); // Accessible here
    console.log(functionLevel); // Outer scope 'let' is accessible
  }
  // console.log(blockVar); // Error: blockVar is not defined here
  // console.log(blockConst); // Error: blockConst is not defined here
}
blockScopeTest();

Block scoping with let and const helps prevent errors and makes code more predictable compared to var's function scoping. It's the preferred way in modern JavaScript.

Arrow Functions (ES6+).

Arrow functions provide a more concise syntax for writing function expressions. They are especially useful for simple, inline functions.

Syntax Variations

// 1. Basic syntax with multiple parameters
const add = (x, y) => {
  return x + y;
};

// 2. If only one parameter, parentheses are optional
const square = x => {
  return x * x;
};

// 3. If the function body is a single expression,
//    curly braces and 'return' can be omitted (implicit return)
const double = num => num * 2;

// 4. No parameters requires empty parentheses
const getRandom = () => Math.random();

console.log(add(5, 4));     // Output: 9
console.log(square(6));   // Output: 36
console.log(double(10));  // Output: 20
console.log(getRandom()); // Output: (a random number between 0 and 1)

Key Differences from Regular Functions

  • Concise Syntax: Often shorter to write.
  • No arguments object: Arrow functions don't have their own arguments object (use rest parameters ...args instead).
  • this binding: Arrow functions do *not* have their own this context. They inherit this from the surrounding (lexical) scope where they were defined. This is a significant difference and often helpful in avoiding this-related confusion, especially with callbacks and methods. We'll revisit this in more detail later.
// Example showing 'this' difference (conceptual - more relevant in Week 10/OOP)
/*
let myObject = {
  value: 10,
  regularMethod: function() {
     console.log(this.value); // 'this' refers to myObject
     setTimeout(function() {
         // console.log(this.value); // 'this' here is different (often window/global), causes error or undefined
     }, 100);
  },
  arrowMethod: function() {
     console.log(this.value); // 'this' refers to myObject
     setTimeout(() => {
         console.log(this.value); // Arrow function inherits 'this' from arrowMethod scope, logs 10
     }, 100);
  }
};
myObject.arrowMethod();
*/

Arrow functions are widely used in modern JavaScript, especially with array methods like map, filter, and forEach.

Syllabus