Demystifying Closures and Lexical Scopes in JavaScript

Published on

Introduction

JavaScript is a versatile programming language used for creating web applications, browser extensions, and server-side applications. One of the most powerful features of JavaScript is its support for closures and lexical scoping.

These features allow developers to create robust and efficient code by controlling the visibility and lifetime of variables and functions.

In this article, we will dive deep into closures and lexical scoping in JavaScript, explore their nuances, and provide practical examples to help you grasp the concepts with ease.

What are Closures and Lexical Scopes in JavaScript?

In JavaScript, a closure is a function that has access to variables in its outer (enclosing) function scope, even after the outer function has returned.

In other words, a closure "closes over" its surrounding state, preserving the values of its variables and functions.

On the other hand, lexical scoping is a mechanism in which a variable's scope is determined by its position within the source code. The scope of a variable is defined by the location of its declaration in the code, and the variables and functions declared in the outer scope are accessible within the inner scope.

Closures and lexical scoping work together to create a powerful mechanism for controlling the visibility and lifetime of variables and functions in JavaScript.

Understanding Closures and Lexical Scopes in Simple Terms

Imagine you are planning a surprise party for your friend, and you want to send out invitations. You have a list of guests and their addresses, but you don't want to reveal the addresses to anyone else.

So, you write a function that takes in the guest list and returns a new function that can be used to send out invitations to the guests.

The returned function has access to the guest list, but no one else does. This is an example of a closure, where the returned function "closes over" the guest list variable and has access to it even after the outer function has returned.

This way, you can send out the invitations without revealing the addresses to anyone else. The guest list is kept private and secure within the closure, and only the returned function can access it.

Code Examples

Let's see a code example to demonstrate how closures work in JavaScript:

function outer() {
  const x = 10

  function inner() {
    console.log(x)
  }

  return inner
}

var closure = outer()
closure() // Output: 10

In the above code, we have defined an outer function that declares a variable x and defines an inner function that logs the value of x.

The outer() function returns the inner() function, and we store it in a variable closure.

When we call closure(), it outputs the value of x, which is 10. Here, the inner() function has closed over its surrounding state, preserving the value of x even after the outer() function has returned.

Let's check another example, Consider you want to create a counter which will increment every time you call the function.

function makeCounter() {
  let count = 0

  return function getCurrentCount() {
    count = count + 1
    return count
  }
}

const getCurrentCount = makeCounter()

getCurrentCount() // 1

getCurrentCount() // 2
getCurrentCount() // 3

The makeCounter() function closes over the count variable and returns a function called getCurrentCount() which uses this lexical scoped variable count and adds one to it and returns the updated count.

Now every time you call the getCurrentCount() function, it incrementally returns the count value by remembering what was the last value of count and adding one to it.

Real-World Example

A real-world example of closures and lexical scoping in JavaScript is event listeners.

When you attach an event listener to a DOM element, it creates a closure that has access to the variables and functions declared in the outer scope.

This mechanism allows you to create efficient and flexible code that responds to user input in real-time.

Conclusion

Closures and lexical scoping are fundamental concepts in JavaScript, and mastering them is essential for creating efficient and reliable code.

By understanding the nuances of these features and practicing with code examples, you can unlock the full potential of JavaScript and take your coding skills to the next level.

Updates straight in your inbox!

A periodic update about my life, recent blog posts, TIL (Today I learned) related stuff, things I am building and more!

Share with others

Liked it?

Views

You may also like

  • javascript

    Integration Testing in JavaScript with Jest and Nock: A Beginner's Guide

    Integration testing is an essential part of the software development process, and it ensures that all the components of your application work together as expected. In this article, we'll explore the basics of integration testing in JavaScript and show you how to use Jest and Nock to write effective tests that simulate real-world scenarios.

    4 min read
  • javascript

    Unit Testing in JavaScript: How to Ensure Code Quality and Catch Bugs Early

    Unit testing is a critical aspect of software development that ensures code quality, improves maintainability, and catches bugs early. In this article, we'll explore the basics of unit testing in JavaScript, including what it is, why it's important, and how to write effective unit tests using popular testing frameworks.

    5 min read
  • javascript

    Array and Object Manipulation in JavaScript: Advanced Techniques

    Arrays and objects are fundamental data structures in JavaScript. They are used extensively in modern web development to store and manipulate data. In this blog post, we will explore advanced techniques for manipulating arrays and objects in JavaScript. We will cover topics such as flattening arrays, merging objects, filtering and mapping arrays, and much more. By the end of this post, you will have a deeper understanding of JavaScript's array and object manipulation capabilities.

    3 min read