Advanced JavaScript

Master advanced JavaScript concepts including design patterns, performance optimization, and modern development practices.

advanced JavaScript 7 hours

Chapter 2: Closures and Scope Mastery

Chapter 2 of 15

Chapter 2: Closures and Scope Mastery

2.1 Understanding Closures

A closure is a function that has access to variables in its outer (enclosing) lexical scope, even after the outer function has returned. Closures are fundamental to JavaScript and enable powerful programming patterns.

function outer() {
    let count = 0;
    return function inner() {
        count++;
        return count;
    };
}

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

// Each call to outer() creates a new closure with its own count variable
const counter2 = outer();
console.log(counter2()); // 1 (independent closure)

How Closures Work:

  • When a function is defined inside another function, it captures the outer function's variables
  • Even after the outer function returns, the inner function retains access to those variables
  • Each closure has its own independent set of captured variables

Practical Example - Module Pattern:

const calculator = (function() {
    let result = 0;
    
    return {
        add: function(x) {
            result += x;
            return this;
        },
        subtract: function(x) {
            result -= x;
            return this;
        },
        multiply: function(x) {
            result *= x;
            return this;
        },
        getResult: function() {
            return result;
        },
        reset: function() {
            result = 0;
            return this;
        }
    };
})();

calculator.add(10).multiply(2).subtract(5);
console.log(calculator.getResult()); // 15

2.2 Scope Chain

The scope chain is the mechanism JavaScript uses to resolve variable names. When a variable is referenced, JavaScript looks up the scope chain to find it.

Scope Chain Resolution:

  1. JavaScript first looks in the current function's scope
  2. If not found, it looks in the outer function's scope
  3. Continues up the chain until it reaches the global scope
  4. If not found anywhere, throws ReferenceError
let globalVar = 'global';

function outer() {
    let outerVar = 'outer';
    
    function middle() {
        let middleVar = 'middle';
        
        function inner() {
            let innerVar = 'inner';
            
            // Can access all variables from outer scopes
            console.log(innerVar);   // "inner" - from current scope
            console.log(middleVar);  // "middle" - from middle scope
            console.log(outerVar);   // "outer" - from outer scope
            console.log(globalVar); // "global" - from global scope
        }
        
        inner();
    }
    
    middle();
}

outer();

Block Scope (let and const):

function demonstrateBlockScope() {
    if (true) {
        var functionScoped = 'I am function scoped';
        let blockScoped = 'I am block scoped';
        const alsoBlockScoped = 'I am also block scoped';
    }
    
    console.log(functionScoped);  // Works - var is function scoped
    console.log(blockScoped);     // ReferenceError - let is block scoped
    console.log(alsoBlockScoped); // ReferenceError - const is block scoped
}

2.3 Lexical Scoping

JavaScript uses lexical (static) scoping, meaning the scope is determined by where the function is declared, not where it's called.

let name = 'Global';

function outer() {
    let name = 'Outer';
    
    function inner() {
        console.log(name); // "Outer" - uses name from outer scope
    }
    
    return inner;
}

const innerFunc = outer();
innerFunc(); // Still prints "Outer" - lexical scoping

2.4 Common Closure Patterns

1. Data Privacy:

function createBankAccount(initialBalance) {
    let balance = initialBalance; // Private variable
    
    return {
        deposit: function(amount) {
            balance += amount;
            return balance;
        },
        withdraw: function(amount) {
            if (amount <= balance) {
                balance -= amount;
                return balance;
            }
            return 'Insufficient funds';
        },
        getBalance: function() {
            return balance;
        }
    };
}

const account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
account.deposit(500);
console.log(account.getBalance()); // 1500
// balance is not directly accessible - it's private

2. Function Factories:

function createMultiplier(multiplier) {
    return function(number) {
        return number * multiplier;
    };
}

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

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

3. Event Handlers:

function setupButton(buttonId, message) {
    const button = document.getElementById(buttonId);
    
    button.addEventListener('click', function() {
        alert(message); // Closure captures message variable
    });
}

setupButton('btn1', 'Hello from Button 1');
setupButton('btn2', 'Hello from Button 2');