You encounter the this keyword everywhere in JS

Through the entire course as well as our projects, you've learned that there’s no getting away from the this keyword. It’s present in virtually every context in the language. In particular, we've seen it used when you're:

  • Using methods of regular objects
  • Referencing values within classes
  • Trying to access an element or event in the DOM

this may have felt like a confusing part of the language or at least one that you don't quite understand as you should. This lesson is here to serve as your guide to grasping the this keyword once and for all, what it means in different contexts and how you can manually set what is equal to. Note that you will forget what we cover here from time to time. All JavaScript developers have trouble with this, so don't hesitate to come back to this lesson if you need a refresher.

this is a reference to an object

What is ‘this’? Let's try to get at the simplest definition of this possible:

Simply put, this is a reference to an object. But what makes it tricky, as you’ve already encountered is that the object that this refers to can vary. In other words, it's value is implicitly set according to how its called:

this is not a fixed characteristic of a function based on the function's definition, but rather a dynamic characteristic that's determined by how the function is called, for example as an arrow function or function declaration, as a normal function or as a method, as a function constructor or as a class, or within a callback function.

Why this?

I think a big part of why developers don't fully grasp this is because they don't understand why we need it at all.

One of the main reasons this dynamically changes based on how the function is called is so that method calls on objects which delegate through the prototype chain still maintain the expected this.

Unlike many other languages, JS's this being dynamic is essential for prototypical inheritance, so for both constructor functions and classes, to work as expected! As we've seen already, classes are a big part of making JavaScript apps, so this is an immensely important feature of the language.

4 Rules to Figure out What this Refers To

There are four main contexts where this is dynamically given a different value:

  • in the global context
  • as a method on an object
  • as a constructor function or class constructor
  • as a DOM event handler

Let's go through each of these contexts one by one:

Global Context

Within an individual script, you can figure out what this is equal by console logging this.

Try it right now and see what you get.

console.log(this); // window

Within this context, the global context, this is set to the global object. If you’re working with JavaScript the client, as we are, this is the window object. Again, as we mentioned, this always refers to an object.

However, you know that functions have their own context as well. What about for them?

For function declarations, it will still refer to the window:

function whatIsThis() {
  console.log(this); // window
}

whatIsThis();

However this behavior changes when we are in strict mode. If we put the function in strict mode, we get undefined:

function whatIsThis() {
  "use strict";

  console.log(this); // undefined
}

whatIsThis();

This is the same result as with an arrow function:

const whatIsThis = () => console.log(this); // undefined
whatIsThis();

Now why is it an improvement for this to be undefined when working with functions, both with function declarations in strict mode and arrow functions, instead of the global object, window? Take a minute and think why this is better.

The reason is that if this refers to the global object, it is very easy to add values onto it by directly mutating the object:

function whatIsThis() {
  // "use strict";

  // console.log(this); // undefined
  this.something = 2;
  console.log(window.something);
}

whatIsThis(); // 2

As we covered in the variables section, we never want data that is scoped to a function to be able to leak out into the outer scope. That contradicts the purpose of having data scoped to a function altogether.

Object Method

When we have a function on an object, we have a method. A method uses this to refer to the properties of the object. So if we have a user object with some data, any method can use this confidently, knowing it will refer to data on the object itself.

const user = {
  first: "Reed",
  last: "Barger",
  greetUser() {
    console.log(`Hi, ${this.first} ${this.last}`);
  },
};

user.greetUser(); // Hi, Reed Barger

But what if that object is then nested inside another object? For example if we put user in an object called userInfo with some other stuff?

const userInfo = {
  job: "Programmer",
  user: {
    first: "Reed",
    last: "Barger",
    greetUser() {
      console.log(`Hi, ${this.first} ${this.last}`);
    },
  },
};

userInfo.personalInfo.greetUser(); // Hi, Reed Barger

The example still works. Why does it work? For any method, this refers to the object is on, or another way of thinking about it, the object that is the immediate left side of the dot when calling a method. So in this case, when calling greetUser, the object personalInfo is on the immediately left side of the dot. So that's what this is.

If however, we tried to use this to get data from the userInfo object:

const userInfo = {
  job: "Programmer",
  user: {
    first: "Reed",
    last: "Barger",
    greetUser() {
      console.log(`Hi, ${this.first} ${this.last}, ${this.job}`);
    },
  },
};

userInfo.personalInfo.greetUser(); // Hi, Reed Barger, undefined

We see that this doesn't refer to userInfo. So look on the immediate left hand side of the dot when calling a method and you'll know what this is.

Contructor functions / classes

When you use the new keyword, it creates an instance of a class or constructor function, depending on which you're using. When a class is instantiated with new, the this keyword is bound to that instance, so we can use this in any of our class methods with confidence knowing that we can reference our instance properties, such as in this example, first and age:

class User {
  constructor(first, age) {
    this.first = first;
    this.age = age;
  }
  getAge() {
    console.log(`${this.first} age is ${this.age}`);
  }
}

const bob = new User("Bob", 24);
bob.getAge(); // Bob's age is 24

Since we understand classes and that how they work is based off of constructor functions and prototypical inheritance, we know the same rule will apply to constructor functions as well:

function User(first, age) {
  this.first = first;
  this.age = age;
}

User.prototype.getAge = function () {
  console.log(`${this.first}'s age is ${this.age}`);
};

const jane = new User("Jane", 25);
jane.getAge(); // Jane's age is

DOM Event Handler

In the browser, there is a special this context for event handlers. In an event handler called by addEventListener, this will refer to event.currentTarget. More often than not, developers will simply use event.target or event.currentTarget as needed to access elements in the DOM, but since the this reference changes in this context, it is important to know.

In the following example, we'll create a button, add text to it, and append it to the DOM. When we log the value of this within the event handler, it will print the target.

const button = document.createElement("button");
button.textContent = "Click";
document.body.appendChild(button);

button.addEventListener("click", function (event) {
  console.log(this); // <button>Click me</button>
});

Once you paste this into your browser, you will see a button appended to the page that says "Click". If you click the button, you will see appear in your console, as clicking the button logs the element, which is the button itself. Therefore, as you can see, this refers to the targeted element, which is the element we added an event listener to.

Controlling the value of this: .call(), .bind(), .apply()

In all of the previous examples, the value of this was determined by its context—whether it is global, in an object, in a constructed function or class, or on a DOM event handler. However, using the functions call, apply, or bind, you can explicitly determine what this should refer to.

.call()

Call and apply are pretty similar—they all you to call a function on a certain context. Again, this refers to an object. For example, say we have an object whose values we want to use for a function:

const user = {
  name: "Reed",
  title: "Programmer",
};

function printUser() {
  console.log(`${this.first} is a ${this.title}.`);
}

printUser(); // "undefined is a undefined"

At this point, the function and object have no connection. But using call or apply, we can call the function like it was a method on the object:

printUser.call(user);
// or:
printUser.apply(user);

We can see how call and apply set the this context with the following code, again using our whatIsThis function:

function whatIsThis() {
  console.log(this);
}

whatIsThis.call({ first: "Reed" }); // { first: ‘Reed’}

In this case, this actually becomes the object passed as an argument.

Additional arguments to .call() and .apply()

But what if you want to use a function that requires parameters to work? Such as this:

const user = {
  name: "Reed",
  title: "Programmer",
};

function printBio(city, country) {
  console.log(`${this.name} works as a ${this.title} in ${city}, ${country}.`);
}

printBio.call(user);

[Run code]

If you try to use call as before, you see we’re setting the this context for the function, but we need to pass arguments with call as well.

We can do so by providing those arguments after the this argument, separated by commas:

printBio.call(user, "London", "England");

This is where apply differs, however. The only difference between call and apply is that it takes the additional arguments in the form of an array:

printBio.apply(user, ["London", "England"]);

.bind()

Both call and apply are one-time use methods—if you call the method with the this context it will have it, but the original function will remain unchanged.

Sometimes, you might need to use a method over and over with the this context of another object, and in that case you could use the bind method to create a brand new function with an explicitly bound this.

const userBio = printBio.bind(user);

userBio();

In this example, every time you call userBio, it will always return the original this value bound to it. Attempting to bind a new this context to it will fail, so you can always trust a bound function to return the this value you expect.

const userBio = printBio.bind(user);

userBio();

const user2 = {
  name: "Doug",
  title: "Entrepreneur",
};

userBio.bind(book2);

userBio();

Although this example tries to bind userBio once again, it retains the original this context from the first time it was bound.

Arrow functions

Arrow functions do not have their own this binding. Instead, they go up to the next execution context.

const user = {
  first: "Bob",
  fn() {
    console.log(this.first);
  },
  arrowFn: () => {
    console.log(this.first);
  },
};

user.fn(); // ‘Bob’
user.arrowFn(); // undefined

Review

So let's review the 4 different ways of calling a function that determine its this binding:

  • in the global context -> global object or undefined in strict mode / for arrow fn
  • as a method on an object -> object on left side of dot when method called
  • as a constructor function or class constructor -> the instance itself when called with new
  • as a DOM event handler -> the element itself

When in the global scope or context, this is the global object, usually window, in non-strict mode, and undefined for strict mode and arrow functions.

For a method on an object, which is what this was largely designed to help with, when calling it look to the immediate left hand side of the dot. That's the object this is bound to.

For a constructor on functions or classes, using new will automatically bind this to the created instance, so all methods added to the prototype can use those instance properties.

And finally for a normal function, not an arrow function, pass to a DOM event handler (addEventListener), this refers to the DOM element itself

Just follow these rules and you'll always be able to demystify what this is!