DEV Community

avinash-repo
avinash-repo

Posted on

Lexical scoping with arrow

Certainly, let's break down the concepts of arrow functions, this, and lexical scoping with a simple example in layman's terms.
https://onecompiler.com/javascript/423f2mcav

Imagine you have a person object:

const person = {
  name: "John",
  sayHi: function() {
    console.log(`Hi, I'm ${this.name}`);
  },
};
Enter fullscreen mode Exit fullscreen mode
  1. Using Regular Function:
   setTimeout(person.sayHi, 1000);
Enter fullscreen mode Exit fullscreen mode

If you run this code, you might expect it to print "Hi, I'm John" after one second. However, it will likely result in an error or print "Hi, I'm undefined." This happens because this inside the sayHi function does not refer to the person object. Regular functions have their own this context, and in this case, it loses its connection to person.

  1. Using Arrow Function:
   setTimeout(() => person.sayHi(), 1000);
Enter fullscreen mode Exit fullscreen mode

By using an arrow function, we maintain the lexical scope. The arrow function does not have its own this, so it borrows this from the surrounding scope, which is the person object in this case. Consequently, it prints "Hi, I'm John" after one second as expected.

In simple terms:

  • this: Think of this as a reference to the current object. In regular functions, it can change unexpectedly, causing confusion. Arrow functions, on the other hand, capture this from their surrounding scope.

  • Lexical Scoping: Lexical scoping means that a function uses the variable scope from where it was defined, not where it's called. Arrow functions exhibit lexical scoping, maintaining a direct link to the surrounding scope.

So, in essence, using arrow functions helps to avoid confusion with this and ensures that your functions maintain a clear and consistent connection to the intended objects or variables.

Certainly, let's explore more examples to illustrate the concept of lexical scoping.

  1. Nested Functions:
   function outerFunction() {
     const outerVariable = "I'm from outer function";

     function innerFunction() {
       console.log(outerVariable);
     }

     innerFunction();
   }

   outerFunction(); // Output: I'm from outer function
Enter fullscreen mode Exit fullscreen mode

In this example, innerFunction has access to the outerVariable due to lexical scoping. The inner function "remembers" the scope in which it was defined, allowing it to access variables from its enclosing function.

  1. Callbacks:
   function outerFunction(callback) {
     const message = "Hello from outer function";
     callback();
   }

   function callbackFunction() {
     console.log(message); // ReferenceError: message is not defined
   }

   outerFunction(callbackFunction);
Enter fullscreen mode Exit fullscreen mode

The callbackFunction cannot access the message variable from outerFunction because it is not lexically scoped within the same function. Lexical scoping ensures that the inner function can only access its own scope and the scopes of its ancestors during its definition.

  1. Closure:
   function createCounter() {
     let count = 0;

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

     return increment;
   }

   const counter = createCounter();
   counter(); // Output: 1
   counter(); // Output: 2
Enter fullscreen mode Exit fullscreen mode

The increment function, returned by createCounter, forms a closure. It retains access to the count variable, demonstrating lexical scoping. Each time counter is called, it increments and logs the count, maintaining its own encapsulated state.

In these examples, lexical scoping ensures that inner functions have access to variables from their containing functions, promoting encapsulation and maintaining a clear and predictable scope chain. It contributes to more robust and maintainable code by controlling the visibility and accessibility of variables.

Certainly, let's delve into layman terms to explain Closure, Callbacks, and Nested Functions in ES6 with simple examples.

  1. Closure:
   function createMultiplier(factor) {
     return function (number) {
       return number * factor;
     };
   }

   const double = createMultiplier(2);
   const triple = createMultiplier(3);

   console.log(double(5)); // Output: 10
   console.log(triple(5)); // Output: 15
Enter fullscreen mode Exit fullscreen mode

Here, createMultiplier returns a function. The returned function forms a closure, capturing the factor variable from its outer scope. When double(5) is called, it multiplies 5 by the captured factor of 2, resulting in 10. This demonstrates closure by preserving the lexical scope even after the outer function has finished executing.

  1. Callbacks:
   function fetchData(url, callback) {
     // Simulating an asynchronous operation
     setTimeout(() => {
       const data = { name: "John", age: 30 };
       callback(data);
     }, 1000);
   }

   function processUserData(userData) {
     console.log(`User Name: ${userData.name}, Age: ${userData.age}`);
   }

   fetchData("https://example.com/api/user", processUserData);
Enter fullscreen mode Exit fullscreen mode

In this example, fetchData simulates fetching data from an API. It takes a URL and a callback function as parameters. After fetching data, it invokes the provided callback (processUserData), passing the retrieved data. This is a common pattern in asynchronous programming, where functions accept callbacks to execute once an operation is complete.

  1. Nested Functions:
   function outerFunction() {
     const outerVariable = "I'm from outer function";

     function innerFunction() {
       console.log(outerVariable);
     }

     innerFunction();
   }

   outerFunction(); // Output: I'm from outer function
Enter fullscreen mode Exit fullscreen mode

Here, innerFunction is nested within outerFunction. It has access to the variables of its containing function due to lexical scoping. When innerFunction is called, it can log and access the outerVariable from its enclosing scope.

In summary:

  • Closure: Involves a function retaining access to variables from its outer scope even after the outer function has finished executing.

  • Callbacks: Refer to functions passed as arguments to other functions, commonly used in asynchronous operations to execute code after a specific task is complete.

  • Nested Functions: Occur when a function is defined inside another function, allowing the inner function to access variables from the outer function's scope. This demonstrates lexical scoping.

Top comments (0)