DEV Community

Cover image for Why We Need 'this' in JavaScript and How To Know It's Value
Reed Barger
Reed Barger

Posted on • Originally published at reedbarger.com on

Why We Need 'this' in JavaScript and How To Know It's Value

As you build more and more projects using JavaScript, you’ll discover there’s no getting away from the this keyword. It’s present in virtually every context in the language. You’ll encounter it 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 it is equal to.

Note that you will likely forget what’s covered in this article here from time to time, and that’s okay. All JavaScript developers have trouble at one point or another with understanding 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, in any context, is a reference to a JavaScript object. But what makes it tricky, is that the object that this refers to can vary. It’s value varies according to how a function is called.

That’s what makes it a tricky concept—it is a dynamic characteristic that’s determined by how the function is called. For example, whether it is called 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 value. In other words, so features can be shared across objects.

Unlike many other languages, JavaScript’s this being dynamic is essential for a feature called prototypical inheritance, which enables both constructor functions and classes to work as expected. Both of these types of functions play a large role in making JavaScript apps, so this is an immensely important feature of the language.

Four rules to know out what this refers to

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

  1. in the global context
  2. as a method on an object
  3. as a constructor function or class constructor
  4. 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
Enter fullscreen mode Exit fullscreen mode

In the global context, this is set to the global object. If you’re working with JavaScript in a web browser, 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 object:

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

whatIsThis();
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

This is the same result as with an arrow function:

const whatIsThis = () => console.log(this); // undefined
whatIsThis();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Constructor 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
Enter fullscreen mode Exit fullscreen mode

Since we know that how classes under the hood 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 25
Enter fullscreen mode Exit fullscreen mode

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>
});
Enter fullscreen mode Exit fullscreen mode

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 <button>Click</button> 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.

Explicitly setting the value of this

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() and .apply()

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"
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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’}
Enter fullscreen mode Exit fullscreen mode

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

Passing 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);
Enter fullscreen mode Exit fullscreen mode

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, "New York City", "USA");
Enter fullscreen mode Exit fullscreen mode

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, ["New York City", "USA"]);
Enter fullscreen mode Exit fullscreen mode

.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();
Enter fullscreen mode Exit fullscreen mode

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(user2);

userBio();
Enter fullscreen mode Exit fullscreen mode

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

Arrow functions have no this

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
Enter fullscreen mode Exit fullscreen mode

Summary

Let’s review the four different ways of calling a function that determine its this binding:

  1. in the global context: refers to global object or undefined in strict mode / for arrow fn
  2. as a method on an object: refers to object on left side of dot when method called
  3. as a constructor function or class constructor: refers to the instance itself when called with new
  4. as a DOM event handler: refers to 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!

Become a React Developer in 5 Weeks

React is hard. You shouldn't have to figure it out yourself.

I've put everything I know about React into a single course, to help you reach your goals in record time:

Introducing: The React Bootcamp

It’s the one course I wish I had when I started learning React.

Click below to try the React Bootcamp for yourself:

Click to join the React Bootcamp
Click to get started

Top comments (1)

Collapse
 
titoasty profile image
nico-boo

Great article about this!
I'd love to hear about its counterpart: love to hate this ;)
Anyway, great explanation about a complex notion that can seem easy but is hard to grasp!