Functions & Arrow Functions
Functions are the primary building blocks of JavaScript applications. They allow you to encapsulate logic, reuse code, and create modular execution blocks. Modern JavaScript provides multiple ways to declare and invoke functions, each with distinct scoping and binding behaviors.
Function Declarations vs. Function Expressions
JavaScript provides two traditional ways to define functions: Declarations and Expressions.
Function Definitions
├── Declaration -> Hoisted entirely (can be called before definition)
└── Expression -> Variable assigned (subject to variable hoisting rules)
| Feature | Function Declaration | Function Expression |
|---|---|---|
| Syntax | function calc() {} | const calc = function() {}; |
| Hoisting | Hoisted with full body | Variable hoisted (uninitialized/undefined) |
| Named Option | Always named | Anonymous or named |
| Primary Use | Top-level module utilities | Callbacks, conditional assignments |
// Function Declaration (Hoisted)
greet("Alice"); // Works!
function greet(name) {
return `Hello, ${name}!`;
}
// Function Expression (Not Hoisted)
// calculateTotal(10, 2); // ReferenceError: Cannot access 'calculateTotal' before initialization
const calculateTotal = function (price, tax) {
return price + price * tax;
};
Arrow Functions (ES6+)
Arrow functions offer a concise syntax for writing function expressions and introduce lexical this binding.
Basic Syntax & Implicit Returns
When an arrow function consists of a single expression, you can omit the curly braces {} and the return keyword for an implicit return:
const multiply = (a, b) => {
return a * b;
};
// Concise Arrow Function with Implicit Return
const add = (a, b) => a + b;
// Implicitly Returning an Object (Wrap in parentheses!)
const createUser = (id, username) => ({ id, username });
The this Keyword: Standard vs. Arrow Functions
The critical architectural difference between standard functions and arrow functions lies in how they handle this.
- Standard Functions: Define
thisdynamically based on how the function is invoked. - Arrow Functions: Do not have their own
this. They inheritthislexically from the surrounding outer scope.
const counter = {
count: 0,
// Standard Function Method
startTimerStandard() {
setTimeout(function () {
// 'this' refers to the global object/undefined in strict mode
console.log("Standard Timer:", this.count); // NaN or Error
}, 100);
},
// Arrow Function Method
startTimerArrow() {
setTimeout(() => {
// 'this' lexically inherits from startTimerArrow ('counter' object)
console.log("Arrow Timer:", ++this.count); // 1
}, 100);
}
};
Modern Parameter Handling
1. Default Parameters
Provide fallback values when arguments are missing or undefined.
function sendNotification(message, priority = "Normal", retryAttempts = 3) {
return `Sending "${message}" [Priority: ${priority}, Retries: ${retryAttempts}]`;
}
sendNotification("System Update");
// Output: "Sending \"System Update\" [Priority: Normal, Retries: 3]"
2. Rest Parameters (...)
Gather an arbitrary number of trailing arguments into a single array.
const sumAll = (...numbers) => {
return numbers.reduce((total, num) => total + num, 0);
};
console.log(sumAll(10, 20, 30, 40)); // 100
Interactive Playground: Functions in Action
Test implicit returns, rest parameters, and arrow function behaviors live:
Best Practices
- Use Arrow Functions for Callbacks: Ideal for inline array methods (
map,filter,reduce) and event handlers where lexicalthisis desired. - Avoid Arrow Functions for Object Methods: Don't use arrow functions as primary object methods if you need access to the object's instance via
this. - Keep Implicit Returns Readable: If an implicit return expression stretches beyond a single line, wrap it with explicit braces
{ return ... }.
Knowledge Check
Exercise Requirements:
- Refactor the
processOrdersfunction to use concise arrow functions and array methods. - Calculate the total value of completed orders using rest parameters or array reduction.
const rawOrders = [
{ id: 1, amount: 100, status: "completed" },
{ id: 2, amount: 50, status: "pending" },
{ id: 3, amount: 200, status: "completed" },
];
// Refactored Arrow Pipeline
const getCompletedTotal = (orders) =>
orders
.filter((order) => order.status === "completed")
.reduce((total, order) => total + order.amount, 0);
console.log("Total Completed:", getCompletedTotal(rawOrders)); // 300
Now that you have mastered function declarations and scoping behaviors, proceed to Operators and Control Flow!