JavaScript Closures: A Detailed Guide

JavaScript Closures: A Detailed Guide

Closures

A closure is a combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. The closure is a function that allows you to access the parent function scope, even though it’s been removed from the execution context stack.

To fully understand the concept of closures, you need to understand variable accessibility, blocks, and lexical environments in JavaScript.

Code Blocks in JavaScript

A block of code in JavaScript refers to code written inside of curly braces.

When you declare a variable inside a block of code {…} in JavaScript, you can only access the variable inside that block of code.

For instance, let’s try to access a variable declared inside a block of code from outside:

{
  let msg = "Hello";
  console.log(msg); // Hello
}
console.log(msg); // Error: msg is not defined

This applies to any blocks of code, such as if statements, for and while loops, functions, and so on.

For instance:

if (1 == 1) {
  let msg = "Hello";
  console.log(msg); // Hello
}
console.log(msg); // Error: msg is not defined

This is a great feature because it lets you create block-specific local variables. This makes it possible to write isolated code without having to worry about the variable names or the surroundings.

A block of code is like an isolated space of code.

Next, let’s talk about nested functions.

Nested Functions in JavaScript

A nested function means a function inside a function.

For example:

function outer() {
   const a = "Hello ";
   function inner() {
     const b = "World"; 
     console.log(a+b);
   }
}

If we talk about code blocks, a nested function is a block of code inside a block of code.

Next, let’s see what happens behind the scenes when we create a new block of code.

Lexical Environment

Every time the JavaScript engine creates an execution context to execute the function or global code, it also creates a new lexical environment to store the variable defined in that function during the execution of that function.

A lexical Environment is a data structure that holds an identifier-variable mapping. (here identifier refers to the name of variables/functions, and the variable is the reference to the actual object [including function type object] or primitive value).

A Lexical Environment has two components:

  • Environment record: is the actual place where the variable and function declarations are stored.
  • Reference to the outer environment: means it has access to its outer (parent) lexical environment.

A lexical environment conceptually looks like this:

lexicalEnvironment = {
  environmentRecord: {
    <identifier> : <value>,
    <identifier> : <value>
  }
  outer: < Reference to the parent lexical environment>
}

let's try to understand this using a simple example:

let language = 'JS';
function a() {
  let b = 25;  
  console.log('Inside function a()');
}
a();
console.log('Inside global execution context');

Variables and Lexical Environments

Each JavaScript script has a global lexical environment.

All the variables (not surrounded by a block of code) belong to this global lexical environment.

Functions and Lexical Environment

As stated earlier, any new block of code creates its own lexical environment.

For instance, a function call creates a new inner lexical environment.

The function’s lexical environment stores all the variables in the function. It then references the global environment.

Searching Values in the Lexical Environment

When you access JavaScript variables they are searched for in the lexical environments.

If a variable with a specific name is not found, the search continues to the outer lexical environment. This process continues all the way up to the global lexical environment.

If the value is not found in the global environment, an error will occur.

Closures and Lexical Environments in JavaScript

All JavaScript functions store a hidden reference to the lexical environment where they were created in.

This info is stored in the [[Scopes]] property of the function.

When you have a function inside a function, the inner function has access to the outer function’s variables even after the outer function has finished execution.

This is possible because the function has access to the lexical environment it was created in.

This feature is called closure.

A closure is a JavaScript function’s ability to access the lexical environment it was created in.

This time, let’s inspect the closure in the JS console to better understand how it works.

Feel free to copy-paste the following code into the JavaScript console:

function createCounter() {
   let counter = 0;
   function increment() {
     counter++;
     console.log(counter);
   }
   return increment;
}
let add = createCounter();
// Inspect the add constant
console.dir(add);

This piece of code executes the outer function createCounter(). It returns the inner closure function increment(). The closure is then stored into a variable called add.

Conclusion

Today you learned how closures work in JavaScript.

To put it short, when you have a function inside another function, a closure is automatically created.

A closure means a function has access to the scope of the outer function. This applies even when the outer scope is destroyed.

scopes-in-javascript.png A closure function can thus use variables from the function that surrounds it even after the surrounding function has finished execution.

Behind the scenes, this all boils down to lexical environments.

Each block of JavaScript code has a lexical environment.

A lexical environment stores variables and properties of the block code.

When you create a nested function, the inner function saves a reference to the lexical environment of the outer function.

The inner function can then use it even after the outer function has finished execution.

Thanks for reading.

Happy coding!

Did you find this article valuable?

Support Chhakuli Zingare by becoming a sponsor. Any amount is appreciated!