Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

JavaScript Essentials - Closures

Understanding closures and their use cases

A closure is a function that retains access to variables from its enclosing scope even after the outer function has finished executing. This tutorial covers the basics of closures, how they work, and their practical use cases in JavaScript programming.

Key Points:

  • Closures allow functions to access variables from an enclosing scope even after the outer function has returned.
  • Closures are created every time a function is created, at function creation time.
  • Closures are useful for creating private variables and functions, and for functional programming.

What is a Closure?

A closure is a function that "remembers" the environment in which it was created. This means that the function has access to variables from its enclosing scope, even after the outer function has finished executing. Here is an example:


function outerFunction() {
    let outerVariable = 'I am outside!';
    
    function innerFunction() {
        console.log(outerVariable);
    }
    
    return innerFunction;
}

const closure = outerFunction();
closure(); // Output: I am outside!
                

Practical Use Cases of Closures

Closures have several practical use cases in JavaScript programming. Here are a few examples:

  • Creating private variables and functions.
  • Function currying.
  • Memoization.
  • Event handlers.
  • Factory functions.

Creating Private Variables and Functions

Closures can be used to create private variables and functions that are not accessible from outside the enclosing function. Here is an example:


function createCounter() {
    let count = 0;

    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment()); // Output: 1
console.log(counter.increment()); // Output: 2
console.log(counter.decrement()); // Output: 1
console.log(counter.getCount()); // Output: 1
                

Function Currying with Closures

Function currying is a technique of transforming a function that takes multiple arguments into a series of functions that each take a single argument. Closures make it easy to implement currying. Here is an example:


function curryFunction(a) {
    return function(b) {
        return function(c) {
            return a + b + c;
        };
    };
}

const curriedFunction = curryFunction(1)(2)(3);
console.log(curriedFunction); // Output: 6
                

Memoization with Closures

Memoization is an optimization technique that caches the results of expensive function calls and returns the cached result when the same inputs occur again. Closures are often used to implement memoization. Here is an example:


function memoize(fn) {
    const cache = {};
    
    return function(...args) {
        const key = JSON.stringify(args);
        if (cache[key]) {
            return cache[key];
        } else {
            const result = fn(...args);
            cache[key] = result;
            return result;
        }
    };
}

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

const memoizedAdd = memoize(add);
console.log(memoizedAdd(1, 2)); // Output: 3
console.log(memoizedAdd(1, 2)); // Output: 3 (cached result)
                

Event Handlers with Closures

Closures are commonly used in event handlers to retain access to variables from the enclosing scope. Here is an example:


function setupEventHandlers() {
    let count = 0;

    document.getElementById('incrementButton').addEventListener('click', function() {
        count++;
        console.log('Button clicked:', count);
    });
}

setupEventHandlers();
                

Factory Functions with Closures

Factory functions use closures to create and return objects with private variables and functions. Here is an example:


function createPerson(name, age) {
    return {
        getName: function() {
            return name;
        },
        getAge: function() {
            return age;
        },
        setAge: function(newAge) {
            age = newAge;
        }
    };
}

const person = createPerson('Alice', 25);
console.log(person.getName()); // Output: Alice
console.log(person.getAge()); // Output: 25
person.setAge(26);
console.log(person.getAge()); // Output: 26
                

Summary

In this tutorial, you learned about closures in JavaScript, including how they work and their practical use cases. You explored examples of creating private variables and functions, function currying, memoization, event handlers, and factory functions. Understanding closures is essential for writing efficient and maintainable JavaScript code.