Scope chain,  Lexical scope & this keyword.

Scope chain, Lexical scope & this keyword.

If you are not aware of how call stack works, you should definitely check out this article before moving ahead. In this article, I have broken down what exactly happens during the creation phase of the execution context. Each Execution context has three components-

  1. Variable environment

  2. Scope chain

  3. this keyword binding

Variable environment

It stores the variables and function declarations defined within that Execution Context. In global EC, Each global variable is defined with var initialized with a special value undefined while variables defined with let or const remains uninitialized while the actual copy of the function body is stored inside the memory space for function declarations.
This means that the JS engine is aware of all the variables and functions that are inside the file and does not give this error ReferenceError: variable/function is not defined if one accesses them before their declaration. This is known as Hoisting and you can read more about it in depth here.

Scope Chain

Ever wonder how a piece of JS code is accessible to other parts of the codebase? Each execution context creates its own scope & it is the space or environment in which a certain variable/function can be accessed without any error. Global EC creates global scope while function EC creates block/function scope.

Global Scope

All variables and functions declared in top-level code(the piece of code that is not inside any block/function scope) have global scope.

Function scope

All variables and functions declared in a function can be accessed inside that block only and given reference error if used outside of their scope.

in ES6, all functions are also block scoped(in strict mode).

Block scope

This concept was introduced in ES6. The variables declared with let or const keyword inside a block(bounded by curly braces {} like an if-else statement, switch statement, for loop, etc.) is accessible inside that block only. Note:- If inside a curly braces block, a variable is declared using var a keyword, it can be accessible outside this scope too. However, if a variable is declared inside the function block using var a keyword, it cannot be accessed outside that function. That is why, var variables are function-scoped.

To avoid confusion, avoid declaring variables using var keyword. To summarize, let, const variables and functions inside a block are block-scoped.

lexical scope

When EC is created, we also get a reference to the lexical env of its parent. It means, that Javascript first looks for a variable in local scope, then it will look up in its parent scope, and so on till the global scope.

For global EC, lexical scope = global scope.

function parent(){ // lexical scope = local env + global scope
    let b = 5;
    function child(){ // lexical scope = local env + scope of parent fn
        console.log(b);
    }
    child();
}
parent();

lexical env = local scope + lexical env of parent

Scope chain

JavaScript follows lexical scoping. It describes how nested(child) functions have access to the variables defined in their parent's scope. Lexical in general means in a hierarchy or a sequence. So all scopes have access to variables/functions from all parent scopes including the global scope. This is known as scope chaining. The scope chain is the order in which functions are written in the code. It has nothing to do with the order in which functions are called i.e. order of EC in the call stack.

Function along with its lexical scope forms a closure.

Closure scope

If any function is created, that function will continue to remember all the variables that were present at the time when the function was created. In other words, any function has always access to the variable environment of the EC in which the function was created even after that EC is executed i.e. popped from the call stack. This concept is called closure. In JavaScript, closures are created every time a function is created, at function creation time. Thanks to the closure, a function does not lose connection to the variables that existed at the function's creation time.

The closure has priority over the scope chain.

It means while executing a function, JS will first look into the scope of that function. If a certain variable is not found in the block scope of this function, JS will immediately look into the closure(if any) first. If it does not find that variable in closure too, then JS will look it up in the scope chain.

Scope chain = look in local scope -> then in closure -> then in the parent scope -> global scope

this keyword in JS

It is a special variable that is created for every execution context. It will always take the value of the owner of the function in which it is used.

This points to the owner of the function. Note that the value of this is not static. It depends on how the function is called.

You can refer this blog for deeper analysis of this keyword usage for various use cases including call, bind, apply, prototype .

this inside a function using object methods

Inside an object method, it will point to the object that is calling the method.

const myData = {
      name: 'krishna saini`,
      year: '1992',
      calcAge: function(){
              return 2022 - this.year;  // here this will points to myData object
            // this.year = myData.year = 1992
              }
      }

this Inside the simple function call

Inside function declaration or function expression, this will point to the global object in non-strict mode else will point to undefined in strict mode.

"use strict";

function sayHello() {
  console.log(this); // Logs undefined
}

sayHello();
function sayHello() {
  console.log(this); // Logs the window object
}

sayHello();

this in global scope

It will point to a window object.

this inside arrow functions

Arrow functions differ in their handling of this: they inherit this from the parent scope at the time they are defined.

arrow functions do not have their own this keyword.

For example,

const arrowFunction = () => {
      console.log(this); // points to this of parent -> log window object
};
arrowFunction();
const myObject = {
  name: "My Object",
  regularFunction: function() {
    console.log("krishna here ", this); // Refers to myObject
    const arrowFunction = () => {
      console.log("krishna",this); // Also refers to myObject (inherits from parent)
    };
    arrowFunction();
  },
};

myObject.regularFunction();

this inside an object method

  • using arrow function

  • using regular functions

      const myData = {
            name: 'krishna saini',
            year: '1992',
           calcAge2: () => {console.log(this)}, // this will point to parent's this -> global object
           calcAge: function(){
                   return 2022 - this.year;  // here this will points to myData object
                }
            }
    

use a regular function (not an arrow function) for object method because arrow functions inherit the this context from their surrounding scope, which can lead to unexpected behavior. Regular functions, on the other hand, create their own this context, which is bound to the object they're called on.

const obj4 = {
  name: "obj4",
  getThis() {
    return this; // point to obj4
  },
};

this inside event listener

Here, it will point to the DOM element that the handler is attached to.

That's it, folks! That's all about scope chain and closures. Follow me for more in-depth articles. Leave your feedback in the comment section.