The puzzle behind "this" keyword

The puzzle behind "this" keyword

Why we need "this" for?

The keyword "this" allows a function to be reused. It informs the function of the context in which it should run. As a result, the "this" keyword in javascript always refers to an object. So we might communicate the function which object should be the focus object by utilising the "this" keyword.

How to find which object is "this" referencing to?

We need to look at where the function is called to figure out what the "this" keyword is referring to. There are many bindings that aid in determining what "this" referring to.

  1. Implicit binding
  2. Explicit binding
  3. Lexical binding
  4. new binding
  5. Window binding

1. Implicit binding

const webDev={
  language: "javascript",
  print: function(){
    console.log(`${this.language} is widely used in web development`);
  }
}

webDev.print();

In the above code snippet , we could tell the referential object of "this" keyword by looking at left of the dot operator.Here, the "this" keyword is referencing to the webDev object where the function print is a method the object. So , as if, inside the print function , the "this" is interpreted as webDev by the javascript engine.

const webDev={
  language: "javascript",
  print: function(){
    console.log(`${webDev.language} is widely used in web development`);
  }
}

This kind of binding is known as Implicit binding

2. Explicit binding

What if we need to use a function of another object and set the context of that function to the object we wish to use currently like in below snippet

const techStackOne={
   frontEnd:"react",
   backEnd:"node js",
   print:function(){
     console.log(`The tech stack used is ${this.frontEnd} and ${this.backEnd}`);
   }
}
const techStackTwo={
   frontEnd:"angular",
   backEnd:"spring boot",
}

We can achieve it by using call method. call is a method accessible on all javascript functions that helps to set the context in which the function should execute.

techStackOne.print.call(techStackTwo);
//output: The tech stack used is angular and spring boot

If you wish to pass arguments to the function using call method ,then you need to pass the context as the first argument and other arguments one by one.

const company={
   name:"Netflix"
}
function print(cityOne,cityTwo){
   console.log(`${this.name} has branches at ${cityOne} and ${cityTwo}`);
}
print.call(company,"San Francisco","Singapore");
//output:Netflix has branches at San Francisco and Singapore

Instead of passing arguments one by one, the user could pass an array of arguments by using a method called apply.

const company={
   name:"Netflix"
}
function print(cityOne,cityTwo){
   console.log(`${this.name} has branches at ${cityOne} and ${cityTwo}`);
}
print.apply(company,["San Francisco","Singapore"]);
//output:Netflix has branches at San Francisco and Singapore

Finally, there is the bind method, which is used for explicit binding.Rather than executing the bound function the bind method returns a new function which could be executed later.

const company={
   name:"Netflix"
}
function print(cityOne,cityTwo){
   console.log(`${this.name} has branches at ${cityOne} and ${cityTwo}`);
}
const newPrint = print.bind(company,"San Francisco","Singapore");
newPrint();
//output:Netflix has branches at San Francisco and Singapore

3. Lexical binding

In this binding , the object referred by "this" keyword is determined lexically. In the below snippet the output is undefined because "this" refers to window object.

const incrementCount = {
    counter: 0, 
    start:function(){
         function currentCount () {
            console.log(this.currentCount);
        }
       setTimeout(function(){
          this.counter++;
          currentCount();
       },1000);
    }
};
incrementCount.start();
//output:undefined

this is how the execution context looks like for the above example

Untitled-2022-02-14-2142.png

Since "this" is referencing to window object we're getting the output as undefined. We could solve the issue by using arrow functions. Arrow functions do not have their own "this" rather the "this" is resolved lexically that is the javascript engine will look the enclosing scope in which the arrow function is called.

const incrementCount = {
  counter: 0,
  start: function () {
    const currentCount = () => {
      console.log(this.counter);
    };
    setTimeout(() => {
      this.counter++;
      currentCount();
    }, 1000);
  },
};
incrementCount.start();
//output:1

Untitled-2022-02-14-2142.png

Since the start function is the enclosing scope of the arrow function, "this" refers to "incrementCount".

4. new binding

When a function is invoked with "new" keyword , the javascript engine creates a new object and makes "this" refer to the new object.The object's [[proto]] property will refer to the constructor function's prototype property.The invoked function returns the new object.

function user(name,city,pincode){
   this.name = name;
   this.city = city;
   this.pincode = pincode;
}

5. window binding

When a function is invoked as a regular function without dot notation or using bind methods then the "this" keyword inside the function would always refer to the window.

function print(){
 console.log(this.name);
}
//output:undefined

Summary

  • "this" keyword allows a function to be used on different context by binding it.
  • in implicit binding the thumb rule is to look at the left of the dot operator of the function that is being invoked to determine "this".
  • a function could be explicitly bound by any of the following methods
    1. call
    2. apply
    3. bind
  • arrow functions don't have their own "this", rather "this" is resolved lexically by looking at the enclosing scope.This is called lexical binding.
  • when a function is invoked using new keyword, then "this" refers to the newly created object.
  • when a function is invoked as a regular function , then "this" refers to the window object.