Page Content

Posts

How JavaScript Closures Control Variable Lifespans and State

JavaScript Closures

Closures are a basic idea in JavaScript that results from the way the language handles lexical scope. A closure is essentially a function that, even when it is operating outside of the scope in which it was initially declared, remembers and has access to its outer variables, or its lexical scope.

Revisiting Scope

The area of your program’s source code where a variable is defined and available is called its scope. It specifies the locations for looking up and using identifiers (variables, functions). Once declared, variables that are defined outside of any function, block, or module are regarded as global and are accessible from anyplace in the program.

Generally speaking, variables specified inside a function are local to that function. Local variables are also function parameters. These local variables can be accessed by code inside the function, but typically not by code outside of it. Variables generated inside a function were traditionally considered to be destroyed upon the function’s termination.

Prior to ES6, variables were primarily declared using the var keyword. Function scope refers to the fact that variables declared with var inside a function remain visible throughout the function body, independent of block structure. Even if a variable is defined inside a function, it becomes global when it is declared without a keyword.

Block scope was introduced with ES6 by Let and Const. When let and const are used to declare variables, they are limited to the block ({}) in which they are defined, including any nested blocks. This indicates that they can only be accessed within that particular block.

It is possible to layer scopes. A new scope layer is created by a block or function defined inside another. While outer scopes are unable to access variables declared solely within inner scopes, inner scopes are able to “look out” and access variables within their containing (outer) scopes. In the event that a variable name appears in both the inner and outer scopes, the inner variable is given priority, therefore “masking” the outside variable inside the inner scope.

Lexical scoping is used in JavaScript. Accordingly, the location of a variable’s definition in the source code rather than the function that calls it determines its scope. This idea is essential to the operation of closures.

Deep Dive into Closures

When a function is defined inside another function and then is made available for execution once the outer function has completed running, closures become especially apparent and helpful.

Consider this example:

function createCounter() {
let count = 0; // ‘count’ is a local variable in createCounter’s scope
// The inner function is defined inside createCounter’s scope
return function() { // This is the closure
count++; // It accesses and modifies ‘count’ from the outer scope
console.log(count);
};
}
let myCounter = createCounter(); // createCounter() is called, returns the inner function
myCounter(); // Output: 1 – The returned function is called outside createCounter()
myCounter(); // Output: 2 – It still has access to the same ‘count’ variable
myCounter(); // Output: 3

This code calls the createCounter() function, which returns an inner, anonymous function after defining a local variable count. Because the returned inner function (the closure) keeps a reference to the local scope, including the count variable, createCounter()’s execution is not terminated. The inner function accesses and changes the count variable from the environment in which it was generated when myCounter() is invoked later. A closure’s scope is “live”; inner functions have access to the variables in the outer scope rather than merely receiving a static copy of values.

Closures make effective methods possible:

  • Maintaining Private State: They permit variables (such as count in the example) to be inaccessible from the outside and private to a function or group of functions. By doing this, the state is shielded against unintentional alteration.
  • Module Pattern: The core of the module pattern is closures, which are used to encapsulate variables and functions and provide only a public interface while maintaining the privacy of other components.
  • Callbacks and Asynchronous Code: Callbacks, like those in event handlers or setTimeout, are commonly used with closures. Even when the callback function is performed later, it still has access to the variables from the scope in which it was defined.
  • Creating Customized Functions: As demonstrated in the multiplier example, where the returned function “remembers” the factor, closures are able to produce functions that are customised with particular data from their creation environment.
  • Handling Loop Issues with var: A classic example of closure pitfalls involved loops using var. Because var has function scope, all inner functions created within the loop’s function shared the same i variable. By the time the inner functions executed, the loop had finished, and i had its final value. Using an Immediately Invoked Function Expression (IIFE) or block-scoped let/const creates a new scope for each iteration, capturing the correct value.

The secret to comprehending closures is to comprehend how functions preserve a reference to their surrounding scope and how this reference endures. Closures are a key component of JavaScript’s design, providing flexibility and control over variable access and state management, despite their initial confusion, particularly with regard to the lifespan of local variables.  

Index