support@90-10.dev

'this' keyword in JavaScript: A Comprehensive Guide

When writing code in JavaScript, you may have come across the this keyword - it refers to the current execution context or the value of the current object. It is a special keyword that is automatically defined in every function and can be used to reference the object that the function is a property of.

Bindings

The value of this is determined by how a function is invoked. It can either be explicitly set or implicitly determined. There are four ways to call a function in JavaScript, and each way sets the value of this differently:

Function invocation (default binding)

When a function is called as a standalone function, this points to the global object. In Node.js, the global object is called global:

function sayHello() {
  return "Hello " + this.name;
}

sayHello();  // returns: 'Hello undefined'

global.name = "Charlie"
sayHello();  // returns: 'Hello Charlie'
NB: In a web browser, the global object is the window object.

Method invocation (implicit binding)

When a function is called as a method of an object, this points to the object that owns the method.

const person = {
    name: 'Charlie',
    sayHello: function() {
        return "Hello " + this.name;
    }
}

person.sayHello();  // returns: 'Hello Charlie'

Constructor invocation (constructor binding)

When a function is called with the new keyword, this points to the newly created object.

function Person(name) {
    this.name = name;
    this.sayHello = function() {
        return "Hello " + this.name;
    }
}
const charlie = new Person('Charlie');
charlie.sayHello();  // returns: 'Hello Charlie'

Indirect invocation (explicit binding)

When a function is called using the call() or apply() method, this points to the object passed as the first argument to the method.

const person = {
    name: 'Charlie'
}

function greet(timeOfDay) {
    return `Good ${timeOfDay} ${this.name}`;
}

greet.call(person, 'Morning');  // returns: 'Good Morning Charlie'

apply works in a similar fashion with the exception of having to pass an array of arguments:

const person = {
    name: 'Charlie'
}

function greet(timeOfDay) {
    return `Good ${timeOfDay} ${this.name}`;
}

greet.apply(person, ['Morning']);  // returns: 'Good Morning Charlie'

Hard binding

It is also possible to explicitly set the value of this using the bind() method - this returns a new function with the value of this set to the object passed as the first argument.

const charlie = {
    name: 'Charlie',
    sayHello: function() {
        return "Hello " + this.name;
    }
}

charlie.sayHello();  // returns: 'Hello Charlie'

const paul = {
    name: 'Paul'
}
const sayHelloToPaul = charlie.sayHello.bind(paul);

sayHelloToPaul();   // returns: 'Hello Paul'

Hard binding can be especially useful in situations where you need to pass a function as a callback but want to ensure that the this value is always set correctly:

const btn = document.querySelector('button');

const person = {
  name: 'Charlie',
  greet: function() {
    console.log(`Hello ${this.name}!`);
  }
};

// Hard bind to person
btn.addEventListener('click', person.greet.bind(person)); 

Arrow Functions

Arrow functions do not have their own this value and instead use the this value of their lexical enclosing context. This makes them a useful alternative to traditional functions for avoiding the pitfalls of this being reset.

Arrow functions lexically bind this to the enclosing execution context:

const charlie = {
  name: 'Charlie',
  sayHello1: function() {
    function greet(timeOfDay) {
      console.log(`Good ${timeOfDay} ${this.name}`);
    }
    greet('Morning');
  },
  sayHello2: function() {
    const greet = (timeOfDay) => {
      console.log(`Good ${timeOfDay} ${this.name}`);
    }
    greet('Morning');
  }
}

charlie.sayHello1();  // prints: 'Good Morning undefined'
charlie.sayHello2();  // prints: 'Good Morning Charlie'

Callback functions

Beware of the callback reset, even when using arrow functions:

function greet(timeOfDay) {
  console.log(`Good ${timeOfDay} ${this.name}`);
}

const charlie = {
  name: 'Charlie',
  sayHello: function() {
    greet('Morning');
  }
}

charlie.sayHello();  // prints: 'Good Morning undefined'

Notice that this.name inside greet is undefined. This is where hard binding comes useful:

function greet(timeOfDay) {
  console.log(`Good ${timeOfDay} ${this.name}`);
}

const charlie = {
  name: 'Charlie',
  sayHello: function() {
    greet.bind(this)('Morning');
  }
}

charlie.sayHello();  // prints: 'Good Morning Charlie'

However, notice that bind(this) doesn't solve the issue if arrow functions are used since, for it, the this value is lexically bound, meaning it retains the this value from its surrounding scope when declared, in our case the global object:

const greet = (timeOfDay) => {
  console.log(`Good ${timeOfDay} ${this.name}`);
}

const charlie = {
  name: 'Charlie',
  sayHello: function() {
    greet.bind(this)('Morning');
  }
}

charlie.sayHello();  // prints: 'Good Morning undefined'

To solve this, the context will have to be passed in:

const greet = (context, timeOfDay) => {
  console.log(`Good ${timeOfDay} ${context.name}`);
}

const charlie = {
  name: 'Charlie',
  sayHello: function() {
    greet(this, 'Morning');
  }
}

charlie.sayHello();  // prints: 'Good Morning Charlie'

Take Away

Understanding how this works in JavaScript is crucial for writing robust and maintainable code, especially when working with complex object-oriented programs. Here are some 5 key takeaways:

  1. this refers to the current execution context, in most cases, an object or the global scope.
  2. this can be explicitly set using the .call(), .apply(), or .bind() .
  3. Arrow functions use the this value of their lexical enclosing context.
  4. Beware of this being reset, sometimes to the global scope, in callback functions. To avoid this, use explicit binding or arrow functions to maintain the this value of the original context.
  5. Avoid using this in certain situations when it makes our code harder to read and maintain, especially in larger applications.