What is "this" in JavaScript?

What is "this" in JavaScript?

·

6 min read

When working with JavaScript, we might encounter the use of this, which can sometimes be confusing. In this article, we will explore this keyword and understand its importance when developing JavaScript applications.

1. Keyword this

The this keyword refers to the context of a piece of code is executed. Its value depends on the context in which it appears. In the following sections, we will explore various contexts and how they affect the value of this.

2. This in global context

In global scope, this refers to the global object:

  • In browser environment, this refers to the window object. For example:

      console.log(this)
      // Window object
    
  • In server environment (Node.js), this refers to the global object.

3. This in function context

For regular functions, the value of this depends on how the function is invoked. The context of a function is determined by the object that the function is accessed on.

  • In global scope, if a function is called directly, this refers to the global object. For example:

      function logThis() {
          console.log(this)
      }
      logThis();
      // Window object (in browser)
    

    This is equivalent to calling window.logThis() in browser environment.

  • In an object method, if the function is called as a method of an object, this refers to the object itself. For example:

      function logThis() {
          console.log(this)
      }
      const obj = {
          name: 'object',
          logThis 
      }
      obj.logThis();
      // { name: 'object', logThis: f()}
    

For arrow functions, they handle this value differently from regular functions. Unlike regular functions, arrow functions do not have their own this. Instead, they inherit the this value from the parent scope at the time they are defined. For example:

const obj = {
    name: 'object',
    logThis: () => { console.log(this) }
}
obj.logThis();
// Window object

Instead of logging the obj object like in the previous example, the code logs the window object. This happens because this inside the logThis arrow function is inherited from the scope where the obj object is defined, which is the global scope.

Arrow functions create a closure over the this value of their surrounding scope. This behavior makes them particularly useful for callbacks and for preserving the context of this. Let’s examine the following example:

const objA = {
    name: 'object A',
    logThis: function() {
       const thisValue = this;
       console.log('this: ',thisValue);
       setTimeout(() => { 
            const timeoutThis = this;
            console.log('timeoutThis: ', timeoutThis) 
        },1000);
    }
}
objA.logThis();
// this: { name: 'object A', logThis: f() }
// timeoutThis:  { name: 'object A', logThis: f() }

In the code above:

  • When the objA.logThis is called, the value of thisValue is objA, as expected.

  • In the setTimeout, the callback is an arrow function, so the timeoutThis value retains the this value of its parent scope, which is the logThis function.

Now, let’s consider another example with a regular function:

const objB = {
    name: 'object B',
     logThis: function() {
       const thisValue = this;
       console.log('this: ',thisValue);
       setTimeout(function() { 
            const timeoutThis = this;
            console.log('timeoutThis: ', timeoutThis) 
        },1000);
    }
}

objB.logThis();
// this: { name: 'object B', logThis: f() }
// timeoutThis: Window object

In this example:

  • Similar to objA, when the objB.logThis method is called, the thisValue value is correctly logged as objB.

  • However, the callback inside setTimeout is a regular function. A regular function determines based on the object that calls them. Since the setTimeout is executed in global context (window.setTimeout), the this value inside the callback refers to the global object (window object), rather than the scope of logThis function.

4. This in class context

In a class, this typically refers to the instance of that class. For example:

class Animal {
    constructor(name) {
        this.name = name;
    }
    getName() {
       return this.name;
    }
}

const duck = new Animal('Donald');
duck.getName(); // 'Donald'

const cat = new Animal('Kitty');
duck.getName(); // 'Kitty'

And this can also refer to the class itself when working with static properties and methods. For example:

class Counter {
    static count = 1;
    static increase() {
        this.count++;
        return this.count;
    }
}
console.log(Counter.increase()); // 2
console.log(Counter.increase()); // 3

5. This in DOM event handlers

When using a function as an event handler, this inside that function will refers to the DOM element to which the event listener is attached. For example:

const button = document.querySelector("#clickMe");

function eventHandler() { console.log(this.id) };
button.addEventListener("click", eventHandler);

In the above example: when the button is clicked , this inside the eventHandler function refers to the button element, so the result will be:

// clickMe

However, if we use an arrow function, this will not behave as expected. For example:

const eventHandler = () => { console.log(this.id) };
button.addEventListener("click", eventHandler);

In this case, the this value refers to the window object, so the result will be:

// undefined

To ensure that this refers to the DOM elements, we should use a regular function for event handlers.

6. Changing the context of a function

JavaScript provides three methods— Function.prototype.bind, Function.prototype.apply and Function.prototype.call—that enable us to to call a function with new context (this value) or create a new function with new context based on an existing function.

  • Create new function with new context using bind method:

      // Syntax
      const newFn = fn.bind(thisArg)
    

    Example:

      // Example
      const objA = {
          name: 'Object A',
          getName: function() { return this.name }
      }
      objA.getName(); // Object A
    
      const objB = { name: 'Object B' }
      const getNameB = objA.getName.bind(objB);
      getNameB(); // Object B
    

    In this example:

    • The getName method is originally bound to objA

    • We use bind method to create a new function getNameB that has its context set to objB. When getNameB is called, it returns ‘Object B‘

  • Call a function with new context using apply or call:

      // Syntax
      fn.apply(thisArg, [arg1, arg2, ...])
      fn.call(thisArg, arg1, arg2,...)
    

    Example:

      // Example
      function getTotalIncome(salary, bonus) {
          return `${this.name}'s income: ${salary + bonus}`;
      }
      getTotalIncome() // NaN
    
      const staff = {
           name: 'Alice'
      }
      const salary = 1000;
      const bonus = 200;
      getTotalIncome.apply(staff, [salary, bonus]); // Alice's income: 1200
      getTotalIncome.call(staff, salary, bonus); // Alice's income: 1200
    

    In this example:

    • The apply and call methods are used to invoke the getTotalIncome function with a new context (this set to staff object)

    • The difference between apply and call is that the apply method expects the arguments as an array, while the call method take arguments individually.

And it’s important to note that arrow functions do not have their own this so we can not bind a new this to a arrow function with these method above. For example:

    const getId = () => { return this.id }
    getId() // undefined

    const staff = { id: 123 }
    const getNewId = getId.bind(staff);
    getNewId(); // undefined
    getId.call(staff); // undefined
    getId.apply(staff); // undefined

Alright, I believe I’ve covered the most common cases of this keyword in JavaScript in this article. I hope this guide has been helpful and that you’re no longer confused when encountering this in any JavaScript codebase.