Closures - Inner and Outer Functions in JavaScript

A function defined inside of and returned by an enclosing function retains access to its lexical scope and can therefore read and write the variables defined inside the outer function. Functions used in this way are called closures, and this is a technique that is worth understanding.

Closures are an incredibly powerful concept in JavaScript and many other programming languages. A closure is a function that has access to its own scope, the outer function’s scope, and the global scope.

Here’s an example:

1
2
3
4
5
6
7
8
9
function outerFunction(outerVariable) {
    return function innerFunction(innerVariable) {
        console.log('outerVariable:', outerVariable);
        console.log('innerVariable:', innerVariable);
    }
}

const newFunction = outerFunction('outside');
newFunction('inside');  // Logs: outerVariable: outside, innerVariable: inside

In this example, innerFunction is a closure that is defined inside outerFunction and has access to outerFunction’s scope. This means innerFunction can access outerVariable, even after outerFunction has finished execution.

When outerFunction is called with the argument 'outside', it returns innerFunction. This returned function is then stored in newFunction. When newFunction (which is really innerFunction under the hood) is called with 'inside', it can still access 'outside' because it closed over the variables in its lexical scope.

This behavior allows you to use patterns like factory functions, where a function creates and returns another function with specific behavior, or private variables, where a variable is inaccessible from outside the function except through a closure.

One common practical use of closures is for event handlers or callbacks that need to access certain variables from the scope where they were created.

Here’s an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function createHandler(value) {
    return function handleEvent() {
        console.log('Handling event for:', value);
        // Even if 'value' is out of scope by the time this handler is called,
        // we can still access it because of closure.
    };
}

let handler = createHandler('button1');
// ... Later in code ...
handler();  // Logs: 'Handling event for: button1'

This ability to ‘remember’ the environment in which they were created makes closures a core aspect of JavaScript and functional programming in general.