Bytes

Understanding Scopes & Closures in JavaScript

Module - 6 Functional Programming in JavaScript - Intro to Functions
Understanding Scopes & Closures in JavaScript

Introduction

In JavaScript, a scope is the area of code where a particular variable can be accessed. A closure, on the other hand, is created when a function is defined inside another function and the inner function has access to the outer function's variables and parameters. Closures allow for more advanced programming techniques such as creating private variables and functions, and can also be used to preserve state in asynchronous code.

Understanding with the help of a story

Imagine you are a chef in a large kitchen with many different stations for preparing food. Each station has its own set of tools and ingredients, and only the chefs working at that station can access them.

In JavaScript, scope is a bit like the stations in a kitchen. Each function you write creates its own scope, which is like a separate station with its own set of variables and functions. The variables and functions in one scope are not accessible from another scope, just like the tools and ingredients at one station in the kitchen are not accessible from another station.

Now, imagine that you are preparing a special dish that requires some rare and expensive ingredients. You don't want other chefs to use up these ingredients, so you hide them away in a locked cabinet at your station. This cabinet represents a closure in JavaScript.

A closure is created when a function "closes over" its surrounding state. In other words, it creates a snapshot of the variables and functions that were in scope at the time the function was defined. This snapshot is stored in memory and can be accessed later, even if the original scope is no longer in use.

In our kitchen analogy, the locked cabinet represents the closure that you created to protect your rare and expensive ingredients. Even if another chef were to move to your station and start using the tools and ingredients there, they would not be able to access the contents of the locked cabinet.

What exactly is Scope?

When writing code in JavaScript, it's important to understand the concept of scope. Scope refers to the set of variables, functions, and objects that are accessible from a particular part of your code. In other words, it defines the visibility of variables and functions in your program.

In JavaScript, there are two main types of scope: global scope and local scope. Global scope refers to variables and functions that are accessible throughout the entire program, while local scope refers to variables and functions that are accessible only within a particular block of code, such as a function.

Global Scope

Global scope is the outermost scope in JavaScript. Variables and functions that are declared outside of any function or block of code have global scope, which means they are accessible from anywhere in the program. Here's an example:

// Global variable
var message = "Hello, world!";

function greet() {
  // Access global variable
  console.log(message);
}

greet(); // Output: "Hello, world!"

In this example, the variable message is declared outside of any function, which gives it global scope. The function greet() is able to access the message variable because it has global scope.

Local Scope

Local scope, also known as function scope, refers to variables and functions that are declared inside a function or block of code. These variables and functions are only accessible within that function or block of code. Here's an example:

function calculateTax(price) {
  // Local variable
  var taxRate = 0.08;

  // Local function
  function applyTax() {
    return price * taxRate;
  }

  return applyTax();
}

console.log(calculateTax(10)); // Output: 0.8

In this example, the variable taxRate and the function applyTax() are declared inside the calculateTax() function, which gives them local scope. They are not accessible from outside the function.

Block Scope

In JavaScript, variables declared using the let and const keywords have block scope, which means they are only accessible within the block of code in which they are declared. Here's an example:

if (true) {
  // Block-scoped variable
  let message = "Hello, world!";
  console.log(message); // Output: "Hello, world!"
}

console.log(message); // ReferenceError: message is not defined

In this example, the variable message is declared inside a block of code (the if statement), which gives it block scope. It is not accessible from outside the block.

Nested Scope

JavaScript allows you to nest functions inside other functions. When you do this, the nested function has access to variables and functions in the outer function's scope. Here's an example:

function outer() {
  var message = "Hello, world!";

  function inner() {
    console.log(message);
  }

  inner();
}

outer(); // Output: "Hello, world!"

In this example, the function inner() is defined inside the outer() function, which gives it access to the message variable in outer()'s scope.

What are Closures?

Closures, on the other hand, are a way to preserve the scope of a function even after it has finished executing. When you create a closure, you essentially create a snapshot of the variables and functions that were in scope at the time the closure was created. This allows you to access those variables and functions later, even if they would have gone out of scope otherwise.

How do closures work in JavaScript?

In JavaScript, functions are treated as first-class citizens, which means they can be passed around as arguments, returned as values from other functions, and assigned to variables. Closures allow us to create functions with persistent states, enabling us to write more concise and modular code.

In JavaScript, every function creates a new scope, which includes its own variables and function declarations. When a function is defined inside another function, the inner function can access the variables and functions of the outer function. When the outer function is called and returns, the inner function still has access to the variables of the outer function because it creates a closure.

Here is an example of a closure in JavaScript:

function createCounter() {
  let count = 0;

  function increment() {
    count++;
    console.log(count);
  }

  return increment;
}

const counter = createCounter();
counter(); // Output: 1
counter(); // Output: 2
counter(); // Output: 3

In the example above, the createCounter function returns an inner function called increment. The count variable is defined in the outer function createCounter and is accessible by the increment function. When we call createCounter, it returns the increment function, which we assign to the counter variable. We can call the counter function multiple times, and it will increment the count variable and log its value to the console.

Benefits of using closures in JavaScript

Closures have several benefits in JavaScript. Here are some of the advantages of using closures:

  • Closures provide a way to encapsulate variables and functions, preventing them from being accessed by other parts of the program. This helps to avoid naming conflicts and improves the overall maintainability of the code.
  • Closures can be used to create private variables and functions that are not accessible from the outside. This allows us to hide implementation details and provide a clean and simple interface to the user.
  • Closures enable us to create functions with persistent states, which can be useful in situations where we need to maintain state across multiple function calls.
  • Closures allow us to create higher-order functions, which are functions that take other functions as arguments or return functions as values. Higher-order functions are an important feature of functional programming and can be used to write more concise and expressive code.

Examples of closures in JavaScript

Here are some examples of closures in JavaScript:

Example 1: Using closures to create private variables

function createPerson(name, age) {
  let _name = name;
  let _age = age;

  function getName() {
    return _name;
  }

  function getAge() {
    return _age;
  }

  return {
    getName,
    getAge
  };
}

const person = createPerson('John', 30);
console.log(person.getName()); // Output: 'John'
console.log(person.getAge()); // Output: 30
console.log(person._name); // Output: undefined

In the example above, we create a createPerson function that takes two arguments, name and age. Inside the createPerson function, we define two private variables _name and _age. We also define two inner functions getName and getAge that return the values of _name and _age, respectively. Finally, we return an object that contains these two functions.

When we call createPerson and pass it the arguments 'John' and 30, it returns an object with two methods, getName and getAge. We can call these methods to get the values of _name and _age, but we cannot access these variables directly from outside the function.

Example 2: Using closures to create a memoized function

function memoize(fn) {
  const cache = {};

  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      console.log('Cache hit');
      return cache[key];
    } else {
      console.log('Cache miss');
      const result = fn(...args);
      cache[key] = result;
      return result;
    }
  };
}

function add(a, b) {
  console.log('Calculating sum...');
  return a + b;
}

const memoizedAdd = memoize(add);
console.log(memoizedAdd(2, 3)); // Output: 'Calculating sum...' '5'
console.log(memoizedAdd(2, 3)); // Output: 'Cache hit' '5'
console.log(memoizedAdd(4, 5)); // Output: 'Calculating sum...' '9'
console.log(memoizedAdd(4, 5)); // Output: 'Cache hit' '9'

In the example above, we define a memoize function that takes a function fn as an argument. Inside the memoize function, we define an object called cache that will store the results of previous function calls. We then return an anonymous function that takes any number of arguments using the rest parameter syntax (...args).

In the anonymous function, we convert the arguments to a string using JSON.stringify and use it as a key to check if we have already calculated the result. If the result is in the cache, we return it without calling the original function. Otherwise, we call the original function and store the result in the cache before returning it.

We can use the memoize function to wrap any function that takes a fixed number of arguments. The memoized function will only calculate the result once for a given set of arguments and return the cached result for subsequent calls.

Conclusion

Scopes and closures are fundamental concepts in JavaScript that enable developers to write robust and efficient code. Scopes define the visibility and accessibility of variables, functions, and objects within a program, while closures allow functions to retain access to variables that were defined in their outer lexical environment. This helps prevent naming collisions and enables developers to create modular, reusable code. Additionally, closures are particularly useful for creating private variables and methods, which can help improve code security and prevent unintended modification of variables. Therefore, understanding scopes and closures is essential for writing clean, maintainable, and efficient JavaScript code.

Recommended Courses
Certification in Full Stack Web Development
Course
20,000 people are doing this course
Become a job-ready Full Stack Web Developer in 30 weeks. Join the largest tech community in India. Pay only after you get a job above 5 LPA.
Masters in Computer Science: Software Engineering
Course
20,000 people are doing this course
Join India's only Pay after placement Master's degree in Computer Science. Get an assured job of 5 LPA and above. Accredited by ECTS and globally recognised in EU, US, Canada and 60+ countries.

AlmaBetter’s curriculum is the best curriculum available online. AlmaBetter’s program is engaging, comprehensive, and student-centered. If you are honestly interested in Data Science, you cannot ask for a better platform than AlmaBetter.

avatar
Kamya Malhotra
Statistical Analyst
Fast forward your career in tech with AlmaBetter

Vikash SrivastavaCo-founder & CPTO AlmaBetter

Vikas CTO

Related Tutorials to watch

Top Articles toRead

AlmaBetter
Made with heartin Bengaluru, India
  • Official Address
  • 4th floor, 133/2, Janardhan Towers, Residency Road, Bengaluru, Karnataka, 560025
  • Communication Address
  • 4th floor, 315 Work Avenue, Siddhivinayak Tower, 152, 1st Cross Rd., 1st Block, Koramangala, Bengaluru, Karnataka, 560034
  • Follow Us
  • facebookinstagramlinkedintwitteryoutubetelegram

© 2023 AlmaBetter