The mystery of “this” in JavaScript.

The mystery of “this” in JavaScript.

JavaScript, like a box of gems

If you are into JavaScript, then you cannot deny the fact that this keyword is very confusing 🤨 when you are writing code, especially when you read others’ code.

In this blog, we will understand how to use this correctly ✔️ and also the scenarios where it is most misunderstood.

In the previous blog post, I mentioned that function invocations in JavaScript can be split into 4 types :

  • function invocation
  • method invocation
  • constructor invocation
  • indirect invocation

Each function invocation defines its context differently.

The key to understanding this keyword is to understand how function invocation affects the context.

Let's become familiar with some terms before getting started :

  • Invocation - Invoking a function means running the code that makes up its body, or simply calling it.
  • Context - The value of this within function body.
  • Scope - A function's scope is the set of variables and functions it can access.

1. Function Invocation

A function invocation occurs when an expression that evaluates to a function object is followed by an open parenthesis (, a comma separated list of arguments expressions, and a close parenthesis).

Here is a simple example of function invocation:

function square(x){
    return x*x;
}
// Function invocation
square(5);

square(5) is a function invocation, square evaluates to a function object, followed by a pair of parentheses and the argument 5.

Remember the distinction between function invocation and method invocation. For example, ['a','b'].join('') is not a function invocation, but a method invocation.

1.1 this in a function invocation

In a function invocation, this refers to the global object.

The global object is determined by the execution environment. The window object is the global object in a browser.

function square(x){
    console.log(this);  // same window object
    console.log(this === window);  // true
    return x*x;
}
// Function invocation
square(5);
console.log(this , this === window); // logs window object and true

In NodeJS, this is the current module.exports object, not the global object, but inside a function, this will point to the global object, or we can use the global keyword to access it.

console.log(this);    // logs {}

module.exports.num = 5;

console.log(this);   // logs { num:5 }

function square(x){
  console.log(this,this === global);    // logs global object and true
}

console.log(global);  // logs global object

Note: In strict mode, this is undefined

function square(x){
    'use strict';
    console.log(this);  // undefined
    return x*x;
}
// Function invocation
square(5);
console.log(this , this === window); // logs window object and true

1.2 this in inner function

❗️ An easy trap with function invocation is assuming that this is the same in an inner function as in the outer function.

✔️ The context of an inner function (except the arrow function) is entirely determined by its own invocation type, not by the context of the outer function.

const car = {
  distance: 200,
  time: 2,
  speed: function(){
      console.log(this === car) // true
      console.log(this.distance / this.time) // 100
      function calculate(){
        console.log(this,this === window)  // window object and true
        console.log(this === car) // false
      return this.distance / this.time;  // error
    }
   return calculate(); // function invocation
  }
};
car.speed();

car.speed() is a method invocation on an object thus this equals car object. calculate() function is defined inside speed(), so you might expect to have this as car object inside calculate() too.

calculate() is a function invocation, not method invocation, thus here this refers to the global object window or undefined in strict mode.

Invocation of car.speed() does not produce the expected result of 200 / 2 = 100.

👍To solve the problem, calculate() function must execute with the same context as the car.speed() method, to access this.distance and this.time properties.

One solution is to use calculate.call(this) to manually set the context of inner function as same as that of outer, another much better solution is to use arrow function so that context of inner function is resolved lexically.

const car = {
  distance: 200,
  time: 2,
  speed: function(){
      console.log(this === car) // true
      const calculate = () => {
         console.log(this === car)  // car object and true
         return this.distance / this.time; 
      }
     return calculate();
    }
};
car.speed();  // 100

2. Method invocation

A method is a function stored in an object's property.

const greet =  {
  english: function(){
         console.log("Hello");
   },
  hindi: function(){
         console.log("Namaste");
   }
};

english and hindi are methods of the greet object, and we can invoke these methods using the property accessors greet.english() and greet.hindi().

2.1 this in a method invocation

In a method invocation, this refers to the object that owns the method.

const greetUser = {
    name: "Dan",
    english: function() {
        console.log(this === greetUser);   // true
        console.log("Hello " + this.name);  // logs Hello Dan
    },
    hindi: function() {
        console.log("Namaste " + this.name);
    }
};
greetUser.engilsh();   // method invocation

2.2 separating method from the object

❗️ A method can be extracted from an object into a separate variable. when the detached method is invoked, you might expect this to be the object in which the method was defined.

✔️ If the method is called without an object, then a function invocation occurs, where this is either the global object window or undefined in strict mode.

const greetUser = {
    userName: "Dan",
    dogName:"milo",
    english: function() {
        console.log(`${this.userName} loves ${this.dogName}`);
    },
    hindi: function() {
        console.log("Namaste " + this.name);
    }
};
const greet = greetUser.english;
greet();   // function invocation logs "undefined loves undefined"

When the separated method english is invoked as a function, this is the global object or undefined in strict mode (not the greetUser object), and since the global object does not contain such properties, it logs undefined.

👍To solve the context issue, we can bind the function with the object using the.bind() method.

const greet = greetUser.english.bind(greetUser);
greet();  // logs "Dan loves milo"

Conclusion

By now, I think you have a pretty good understanding of how function invocation impacts the context, and what common pitfalls to avoid.

🔜 In upcoming posts, I'll be writing about how constructor and indirect invocation impacts context as well as some common misunderstood cases.

🙏 Thank you very much for reading this article. I hope you found it useful and if you did, please do share it with your friends or anyone who may find this interesting. ✨

Did you find this article valuable?

Support Mohit Kushwaha by becoming a sponsor. Any amount is appreciated!