All about `this` in JavaScript

All about `this` in JavaScript

What is this?

It depends on the situation. Let’s look at a few common scenarios.

The global this

Outside of a function, this references the global object. In a browser environment, this is typically the window object. In this scenario, you can set properties on the global object by referencing it with this:

this.name = 'Joe';

console.log(window.name); // Joe
console.log(name); // Joe
console.log(this.name); // Joe

this inside a function called on an object

Suppose we have the following object:

const joe = {
  name: 'Joe',
  sayHello() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

If we call the sayHello function on the joe object like this:

joe.sayHello(); // prints 'Hello, I'm Joe'

Then this inside the sayHello function refers to the joe object.

When you call a function on an object using dot notation like we have done above, you could say that this refers to the object before the dot. This is sometimes called the receiver.

If, however, we save a reference to the sayHello function and call it via the reference, we receive a different result:

const greet = joe.sayHello;
greet(); // prints "Hello, I'm undefined"

What happened? When there is no explicit receiver in a function call, this refers to the global object. If nothing else has set a name property on the window object, this will print Hello, I'm undefined.

If some other code already set a name property on the window object, it will print that instead. Consider the following code:

name = 'Bob';

const joe = {
  name: 'Joe',
  sayHello() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

joe.sayHello(); // prints "Hello, I'm Joe"

const greet = joe.sayHello;
greet(); // prints "Hello, I'm Bob"

const ben = {
  name: 'Ben',
  sayHello: joe.sayHello
};
ben.sayHello(); // prints "Hello, I'm Ben"

this inside an event listener

Another common scenario is an event listener. When an event listener is added, a callback function is specified to handle the event. When this callback is called, this refers to the object that the event listener was added to.

document.querySelector('button.myButton').addEventListener('click', function() {
  this.style.background = 'red';
});

Here we added a click listener to a button. When the button is clicked and the callback function is executed, this refers to the button.

this inside a callback

There are several useful functions on Array.prototype such as forEach, map, reduce, etc. Each of these takes a callback function as an argument.

Inside the callback passed to these functions, this refers again to the global object.

const arr = [1, 2, 3];
arr.forEach(function(item) {
  console.log(this);
});

When the above code is run in a browser, it will print the window object to the console three times.

Consider the following code:

name = 'Bob'; 

const joe = {
  name: 'Joe',
  greet(people) {
    people.forEach(function(person) {
      console.log(`Hello ${person}, I'm ${this.name}`);
    });
  }
};

joe.greet(['Liz', 'Ben']);

The above code will produce the following output:

Hello Liz, I'm Bob
Hello Ben, I'm Bob

Even though the greet function has a this value of the joe object, inside the callback to forEach the value of this.name is Bob, which was set on the window object.

How can we change this code so that the greet function prints Joe instead of Bob?

One way is to save a reference to this and reference that from inside the callback:

name = 'Bob';

const joe = {
  name: 'Joe',
  greet(people) {
        const self = this;
        people.forEach(function(person) {
          console.log(`Hello ${person}, I'm ${self.name}`);
      });
  }
};

joe.greet(['Liz', 'Ben']);

When we run this, it works as intended:

Hello Liz, I'm Joe
Hello Ben, I'm Joe

Why does this work? Because a function inherits the surrounding scope (thanks, closure), the value of self can be accessed from within the callback function.

This is generally frowned upon these days, as there are better ways to accomplish this, as discussed in the next section.

Changing the value of this

Using an arrow function

The easiest way to accomplish what the previous code sample does is to use an arrow function instead of the function() { ... } syntax.

An arrow function does not get its own this; rather, it inherits the this of its enclosing scope. We can rewrite the previous example using arrow functions:

name = 'Bob';

const joe = {
    name: 'Joe',
    greet(people) {
        people.forEach(person => console.log(`Hello ${person}, I'm ${this.name}`));
    }
};

joe.greet(['Liz', 'Ben']);

The output is the same as before:

Hello Liz, I'm Joe
Hello Ben, I'm Joe

The value of this inside the arrow callback function is the joe object.

Use Function.prototype.bind

There are several handy functions on the prototype of Function. One of these is bind. With this function you can change what this refers to in a given function.

const joe = {
  name: 'Joe',
  sayHello() {
    console.log(`Hello, I'm ${this.name}`);
  }
}

const greet = joe.sayHello;
greet();

As we have already seen, the above code will not print Hello, I'm Joe because we are calling the sayHello function without an explicit receiver. However, we can fix this by calling bind:

const joe = {
  name: 'Joe',
  sayHello() {
    console.log(`Hello, I'm ${this.name}`);
    }
}

const greet = joe.sayHello.bind(joe);
greet(); // prints "Hello, I'm Joe"

Here's what bind does: Calling bind on a function like we did above returns a new function whose this value is bound to the first argument passed to bind.

joe.sayHello is a reference to the sayHello function. We then call bind(joe) on that function, which returns a new function where this is bound to the joe object. So our code works as intended.

bind can actually take more than one argument. That's beyond the scope of this post, but essentially it allows you to do partial application of functions.

Use Function.prototype.call or Function.prototype.apply

Two other useful functions on the Function prototype are call and apply. They both have the same end result, they just approach it slightly differently, as we will see in a moment.

const joe = {
  name: 'Joe',
  greet(person) {
    console.log(`Hello ${person}, I'm ${this.name}`);
    }
}

const greet = joe.greet;

greet('Ben'); // prints "Hello Ben, I'm undefined"

greet.call(joe, 'Ben'); // prints "Hello Ben, I'm Joe"
greet.apply(joe, ['Ben']); // prints "Hello Ben, I'm Joe"

As you can see, call and apply both accomplish what we want. But you might notice there's a slight difference in how they're used.

First, what do they have in common? call and apply both invoke a function with the first argument bound as the this value. So in the above example, when we call call and apply on the function, the joe object is bound to this.

This is similar to bind as shown above, but with one key difference. bind returns a new function that will always have the specified this value for every invocation. In contrast, call and apply operate on the original function, and their effects apply only to that single invocation.

Now, back to call and apply. What is the difference? The difference is how we specify the arguments to the function call. Function.prototype.call takes a variable number of arguments. Each of these arguments are passed, in order, as arguments to the original function.

Function.prototype.apply takes two arguments. The first, as we've seen, is the this value to use. The second argument is an array of the argument values to be passed to the function call. The difference is more apparent with a function call using multiple arguments. Consider the difference between these:

// These both call the greet function with joe as the this value, and three arguments: 'Ben', 'Liz', and 'Bob'
greet.call(joe, 'Ben', 'Liz', 'Bob');
greet.apply(joe, ['Ben', 'Liz', 'Bob]);

Other ways

There are yet other ways to affect the value of this in a function call. One example is Array.prototype.forEach. As we saw earlier, forEach takes a callback function as its argument. However, it also takes an optional second argument. If specified, this argument will become the value of this in the callback function:

const joe = {
  name: 'Joe',
  greet(people) {
    people.forEach(function(person) {
      console.log(`Hello ${person}, I'm ${this.name}`);
    }, this);
  }
}

joe.greet(['Liz', 'Ben']);

Notice in the forEach call that this was passed as the second argument after the callback. As long as this function is invoked like this: joe.greet(...), then the callback function will have the correct this value set.

Summary

The rules of this in JavaScript can be a little tricky for beginners, but hopefully this post has helped clear up some confusion.

As we have seen, there are several ways to affect what the this value is during a function call. If you are using ES2015+, the easiest way is to just use an arrow function. If you can't use arrow functions, there are still several tools at your disposal such as bind, call, and apply.