Introduction

Functions are the building blocks of JavaScript applications. They allow you to write reusable code, organize logic, and create powerful abstractions. In this lesson, you'll master function syntax, understand scope, and learn about closures.

Learning Objectives

By the end of this lesson, you will be able to:

  • Write functions using both traditional and arrow function syntax
  • Understand the differences between function declarations and expressions
  • Master scope concepts (global, function, and block scope)
  • Create and use closures for data privacy and encapsulation
  • Work with higher-order functions and callbacks
  • Use array methods like map, filter, and reduce effectively

Prerequisites

  • Completion of Lesson 01 (JavaScript Fundamentals)
  • Understanding of variables and data types
  • Basic knowledge of arrays and objects

Estimated Time

3.5 hours (including practice exercises)

Function Declarations

The traditional way to define functions in JavaScript:

JavaScript
// Basic function declaration
function greet(name) {
  return `Hello, ${name}!`;
}

console.log(greet("Alice")); // "Hello, Alice!"

// Function with multiple parameters
function add(a, b) {
  return a + b;
}

console.log(add(5, 3)); // 8

// Function with default parameters
function createUser(name, role = "user") {
  return { name, role };
}

console.log(createUser("Bob")); 
// { name: "Bob", role: "user" }

Arrow Functions

Modern ES6 syntax for writing concise functions:

JavaScript
// Traditional function
function double(x) {
  return x * 2;
}

// Arrow function (concise)
const doubleArrow = (x) => x * 2;

// Arrow function with multiple parameters
const multiply = (a, b) => a * b;

// Arrow function with block body
const processUser = (user) => {
  const fullName = `${user.first} ${user.last}`;
  return { ...user, fullName };
};

// Single parameter (parentheses optional)
const square = x => x * x;

console.log(square(5)); // 25
Arrow Function Benefits: Arrow functions have a shorter syntax and don't bind their own this value, making them ideal for callbacks and array methods.

Scope

Scope determines where variables are accessible in your code:

Global Scope

JavaScript
// Global variable (accessible everywhere)
const globalVar = "I'm global";

function showGlobal() {
  console.log(globalVar); // Accessible
}

showGlobal(); // "I'm global"

Function Scope

JavaScript
function myFunction() {
  const localVar = "I'm local";
  console.log(localVar); // Accessible
}

myFunction();
// console.log(localVar); // Error: localVar is not defined

Block Scope

JavaScript
if (true) {
  const blockVar = "I'm in a block";
  let anotherBlockVar = "Me too";
  console.log(blockVar); // Accessible
}

// console.log(blockVar); // Error: not accessible outside block

// var doesn't respect block scope (another reason to avoid it)
if (true) {
  var oldStyle = "I leak out";
}
console.log(oldStyle); // "I leak out" (not recommended)

Closures

A closure is a function that has access to variables from its outer scope, even after the outer function has returned:

JavaScript
// Basic closure example
function createCounter() {
  let count = 0; // Private variable
  
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// count is not accessible directly
// console.log(count); // Error

Practical Closure Example

JavaScript
// Create a private data store
function createBankAccount(initialBalance) {
  let balance = initialBalance;
  
  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount <= balance) {
        balance -= amount;
        return balance;
      }
      return "Insufficient funds";
    },
    getBalance() {
      return balance;
    }
  };
}

const account = createBankAccount(100);
console.log(account.deposit(50));    // 150
console.log(account.withdraw(30));   // 120
console.log(account.getBalance());   // 120
// console.log(account.balance);     // undefined (private)

Higher-Order Functions

Functions that take other functions as arguments or return functions:

JavaScript
// Function that takes a function as argument
function repeat(n, action) {
  for (let i = 0; i < n; i++) {
    action(i);
  }
}

repeat(3, (i) => console.log(`Iteration ${i}`));
// Iteration 0
// Iteration 1
// Iteration 2

// Function that returns a function
function multiplier(factor) {
  return (number) => number * factor;
}

const double = multiplier(2);
const triple = multiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

Array Methods with Functions

JavaScript arrays have powerful methods that use functions:

JavaScript
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 that match condition
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]

// reduce - combine elements into single value
const sum = numbers.reduce((total, n) => total + n, 0);
console.log(sum); // 15

// find - get first matching element
const found = numbers.find(n => n > 3);
console.log(found); // 4

// some - check if any element matches
const hasEven = numbers.some(n => n % 2 === 0);
console.log(hasEven); // true

// every - check if all elements match
const allPositive = numbers.every(n => n > 0);
console.log(allPositive); // true

Practical Example: Task Manager

JavaScript
// Create a task manager using closures
function createTaskManager() {
  let tasks = [];
  let nextId = 1;
  
  return {
    addTask(description) {
      const task = {
        id: nextId++,
        description,
        completed: false
      };
      tasks.push(task);
      return task;
    },
    
    completeTask(id) {
      const task = tasks.find(t => t.id === id);
      if (task) {
        task.completed = true;
      }
      return task;
    },
    
    getTasks() {
      return [...tasks]; // Return copy
    },
    
    getActiveTasks() {
      return tasks.filter(t => !t.completed);
    },
    
    getCompletedTasks() {
      return tasks.filter(t => t.completed);
    }
  };
}

// Usage
const manager = createTaskManager();
manager.addTask("Learn JavaScript");
manager.addTask("Build a project");
manager.completeTask(1);

console.log(manager.getActiveTasks());
// [{ id: 2, description: "Build a project", completed: false }]

Key Takeaways

  • Functions are first-class citizens in JavaScript
  • Arrow functions provide concise syntax and lexical this binding
  • Scope determines variable accessibility (global, function, block)
  • Closures allow functions to access outer scope variables
  • Higher-order functions enable powerful abstractions
  • Array methods like map, filter, and reduce use callback functions
  • Closures are useful for creating private data and encapsulation

Practice Exercises

Exercise 1: Arrow Functions

Convert these traditional functions to arrow functions:

Task
// Convert to arrow functions
function add(a, b) {
  return a + b;
}

function isEven(num) {
  return num % 2 === 0;
}

function greetUser(name) {
  return `Welcome, ${name}!`;
}

Exercise 2: Array Methods

Use map, filter, and reduce to solve these problems:

Task
const products = [
  { name: "Laptop", price: 999, category: "electronics" },
  { name: "Phone", price: 699, category: "electronics" },
  { name: "Shirt", price: 29, category: "clothing" },
  { name: "Shoes", price: 89, category: "clothing" }
];

// 1. Get all product names
// 2. Get all electronics products
// 3. Calculate total price of all products
// 4. Get products under $100

Exercise 3: Closure Challenge

Create a counter with increment, decrement, and reset methods:

Task
// Create a function that returns an object with:
// - increment() - increases count by 1
// - decrement() - decreases count by 1
// - reset() - sets count back to 0
// - getValue() - returns current count

function createCounter() {
  // Your code here
}

const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
counter.getValue();  // 1
counter.reset();     // 0

Exercise 4: Higher-Order Functions

Create a function that returns customized greeting functions:

Task
// Create a function that takes a greeting word
// and returns a function that greets with that word

function createGreeter(greeting) {
  // Your code here
}

const sayHello = createGreeter("Hello");
const sayHi = createGreeter("Hi");

console.log(sayHello("Alice")); // "Hello, Alice!"
console.log(sayHi("Bob"));      // "Hi, Bob!"

Common Pitfalls

1. Arrow Functions and 'this'

JavaScript
// Arrow functions don't have their own 'this'
const obj = {
  name: "Object",
  regularFunc: function() {
    console.log(this.name); // "Object"
  },
  arrowFunc: () => {
    console.log(this.name); // undefined (or global)
  }
};

obj.regularFunc(); // Works as expected
obj.arrowFunc();   // Doesn't work as expected

2. Closure Memory

Warning: Closures keep references to outer variables, which can lead to memory issues if not managed properly. Be mindful when creating many closures in loops.

What's Next?

In Lesson 03, we'll explore DOM manipulation and event handling. You'll learn how to make your web pages interactive by responding to user actions and dynamically updating content.