DEV Community

Preston Lamb
Preston Lamb

Posted on • Originally published at prestonlamb.com on

The JavaScript this Keyword

tldr;

The this keyword in JavaScript is one of the most confusing parts of the language, but understanding how it works and to what it is referring is vital to writing applications with as few errors as possible. We’ll go over a couple methods of figuring out what this is referring to so that you can figure it out in your application. There are a few methods for figuring out what this is referencing in a function, and we’ll cover them in this article.

What is this?

Before we figure out what this is referring to, let’s figure out what this is in the first place. When talking about this in JavaScript, we’re talking about the context in which a JavaScript function is run. Context in general is the circumstances that form the setting for an event, statement, or idea. So in JavaScript, it’s the circumstances in which something is done (like a function being run, for example). this in JavaScript is generally related to objects, and what object is invoking a function. this will refer to an object available inside the function that’s being executed. That object changes depending on where the function is being executed, and much of the time depends on the object that calls the function.

Let’s take the following example and look at how its context will change in two different scenarios:

function sayHello() {
  console.log(this);
  console.log(`Hello, ${this.name}!`);
}

const person = {
  name: 'Preston',
  sayHello,
};

sayHello(); // Window {}; Hello, !
person.sayHello(); // { name: "Preston", sayHello: function }; Hello, Preston!
Enter fullscreen mode Exit fullscreen mode

The context of the sayHello function changes depending on how and where it’s called. When it’s invoked by itself, its context is the global Window object. When it’s invoked from the person object, its context is the person object that invoked the function.

This concept of context is pretty natural to us in our communication with others. Suppose your friend asks you, “Hey, did you see the game last night? It was great!” You know intuitively that “it” is referring to the game. You can tell that because of the context of the conversation. But if you say to your friend, “Man, it’s crazy out there,” they don’t know exactly what you’re referring to. But as humans we’ve come to understand that that sentence just means things going on in the world are crazy. It’s more of a global statement as opposed to a specific statement like the first one. When we’re speaking of this in JavaScript, we are trying to determine what the context of the function is, just like in these two sentences.

Global Context

In our example with the sayHello function above, this prints out in two different ways, once as the Window object and once as myObj. Let’s look at the first way that it prints out, which is the global context. If a function is called and it’s not called as a method on an object, the context of the function will default to the global context. The exception here is if you are using the new keyword, which we’ll cover below. In the following example the function is called, but not called on an object. So the context is the global Window, and if there’s no name attribute on the Window, our message will print out as shown below:

function sayHello() {
  console.log(this); // Window
  console.log(`Hello, ${this.name}!`); // Hello, !
}
sayHello();
Enter fullscreen mode Exit fullscreen mode

Note: this is assuming that you are running this function in a browser. If you were running it in Node, this would refer to a global object with information related to Node.js.

The global Window context is the catch-all context in JavaScript applications. Now there is one situation when this is not true, and that’s if you’re running your application in strict mode:

'use strict';

function sayHello() {
  console.log(this); // undefined
  console.log(`Hello, ${this.name}!`); // Uncaught: TypeError: Cannot read property 'name' of undefined
}
sayHello();
Enter fullscreen mode Exit fullscreen mode

If you’re running in strict mode, and the function isn’t run in a situation where it has a specific context, then this will be undefined instead of defaulting to the Window.

Determining Context

The context in most situations in a JavaScript application are set on a function level. There are a few ways for the context, or the this object, of a function to be determined. Let’s look at the first way, when the function is a method on an object.

const person = {
  name: 'Preston',
  sayHello: function() {
    console.log(this);
    console.log(`Hello ${this.name}!`);
  },
};
person.sayHello(); // { name: 'Preston', sayHello: function }; Hello Preston!
Enter fullscreen mode Exit fullscreen mode

In this example, the sayHello function is a method on the person object. When it’s invoked, it’s run in the context of the person object. So, this refers to the object associated with calling the function. Many times you can determine what the context of a function is by looking to the left of the function name. If there’s a dot, look to the left of that. Whatever that object is, is the context of the function.

In the above example, you could also replace this with person. When you determine the context of the function, you can replace this with the related object. Instead of saying this.name, you could use person.name. Now, if you’re trying to have a function not be tied to the variable name you wouldn’t actually want to do that, but I point this out so you know this refers to the object that called the method. Hopefully that clears up what the context is a little bit.

Now, it’s important to realize that the context isn’t limited to only the top level object where it’s called. Let’s look at this example to see what I mean when I say that:

function sayHello() {
  console.log(this);
  console.log(`Hello ${this.name}!`);
}

const person = {
  name: 'Preston',
  sayHello,
  spouse: {
    name: 'Amanda',
    sayHello,
  },
};
person.sayHello(); // { name: 'Preston', sayHello: function, spouse: {} }; Hello Preston
person.spouse.sayHello(); // { name: 'Amanda', sayHello: function }; Hello Amanda
Enter fullscreen mode Exit fullscreen mode

In the above example, one of the attributes on the person object is an object itself. It also has a sayHello function. When we call the person.spouse.sayHello() function, this refers to the person.spouse object, not the person object. We can tell that in the same way we did before. We look at the sayHello() function and move left. There’s a dot, so we go left once more and the object is spouse.

I want to point out one other thing. It’s important to know that the above method of determining the context relates just to objects, but not arrays. Let’s look at what happens if we look at this in the forEach method on an array.

const numbers = [1];
numbers.forEach(function() {
  console.log(this); // Window
});
Enter fullscreen mode Exit fullscreen mode

So make sure when you are trying to determine the context that you remember to look for objects to the left of the dot, and not arrays or strings or other variables.

So we’ve looked at how we determine the context of a method on an object, now let’s look at ways that you can explicitly set the context for a function that isn’t a method on an object. Let’s look at the following example:

function sayHello() {
  console.log(this);
  console.log(`Hello, ${this.name}!`);
}

const person = {
  name: 'Preston',
};
Enter fullscreen mode Exit fullscreen mode

We still have our sayHello function, and our person object, but this time the object doesn’t have a sayHello method. We know that if we run the sayHello function without it being on an object, the context would default to the Window. But all JavaScript functions have three methods that you can use to set the context. Those three methods are call, apply, and bind. They all allow you to set the context of a function, although each in slightly different ways. We won’t go into detail of how these methods work, but it’s good to know that they exist and what they do. Let’s call the sayHello function and explicitly set the context:

sayHello.call(person); // { name: 'Preston' }; Hello Preston!
// or
sayHello.apply(person); // { name: 'Preston' }; Hello Preston!
// or
const newFn = sayHello.bind(person);
newFn(); // { name: 'Preston' }; Hello Preston!
Enter fullscreen mode Exit fullscreen mode

Again, these all have slight differences in how they work and are used, but in all 3 cases they allow you to set the context of a function before calling it. This is perfect for reusing a function that needs to know about the context in which it’s running. Just because a function is not a method on an object doesn’t mean that it can only have the global context.

Context When Using new

There is one situation when a function is called outside the context of an object and has its own context. And that’s if, in conjunction with calling the function, you use the new keyword. Let’s look at an example:

function Person() {
  this.name = 'Preston';
}
Enter fullscreen mode Exit fullscreen mode

We’ve demonstrated that calling this Person() function would result in this referring to the global Window object and in strict mode we’d get an error for trying to access an attribute of undefined. But, if we call it using the new keyword, this is not undefined and it doesn’t refer to the global Window:

const me = new Person();
console.log(me); // { name: 'Preston' }
Enter fullscreen mode Exit fullscreen mode

This is a less often used part of JavaScript, at least in my experience, but is important to know. When you’re trying to determine the context of a function, check to see if the new keyword is present.

Arrow Functions

Arrow functions were introduced to JavaScript in ES6. You’ve probably seen them being used. They’re really handy and convenient. But their context is different than a normal function’s context. The rules that we looked at above do not apply to arrow functions. Let’s look at an example.

const person = {
  name: 'Preston',
  sayHello: () => {
    console.log(this); // Window
    console.log(`Hello ${this.name}!`); // Hello !
  },
};
person.sayHello();
Enter fullscreen mode Exit fullscreen mode

At first glance, we might think that the context of the arrow function will be the person object. I mean, person is to the left of the dot, which is to the left of the function. But context in arrow functions works differently. The context in arrow functions inherits the context of the enclosing context. If there is no enclosing context, the context defaults to the global context (again, unless we’re in strict mode). In this example, the sayHello function being an arrow function means that it inherits the enclosing context. The object doesn’t have context itself, so the arrow function can’t inherit from there which means it has to inherit from the global context.

Even though arrow functions don’t have the context of the object where they’re invoked, they can still be really handy. Let’s look at an example:

const spouse = {
  name: 'Amanda',
  pets: ['Tiger', 'Midnight'],
  printPets: function printPets() {
    console.log(
      this.pets.reduce(dog => {
        return `${this.name} has ${this.pets.length} ${
          this.pets.length === 1 ? 'pet' : 'pets'
        }: ${this.pets.join(', ')}.`;
       }, ''),
     );
  },
};
spouse.printPets(); // Amanda has 2 pets: Tiger, Midnight.

const person = {
  name: 'Preston',
  pets: ['Duke', 'Max'],
  printPets: function printPets() {
    console.log(
      this.pets.reduce(function(dog) {
        return `${this.name} has ${this.pets.length} ${
          this.pets.length === 1 ? 'pet' : 'pets'
        }: ${this.pets.join(', ')}.`;
      }, ''),
    );
  },
};
person.printPets(); // cannot read property 'length' of undefined
Enter fullscreen mode Exit fullscreen mode

Each of these two objects has a printPets function. The purpose is to print out the number of the pets and their names. The printPets function is a named function, so its context is set to the object that’s calling the function, spouse and person respectively. Inside the printPets function, we use the reduce method on the pets array to create the string that we’ll print out. Inside the reduce method we access the length property of the array and use the join method.

The spouse.printPets() method works perfectly! The message Amanda has 2 pets: Tiger, Midnight is printed to the console. But the person.printPets() function call causes an error. Now why is that? The answer is in how we are using the reduce method. On the spouse object we’re using an arrow function for the reduce method. This means that the reduce method doesn’t have its own context and by default it inherits the enclosing context. That means its context is the same as the printPets function whose context refers to the spouseobject.

In contrast, the reduce method on the person object uses an anonymous function and not an arrow function. That gives the reduce method its own context. Because it’s not called invoked by an object, its context is the global context which doesn’t have a pets attribute on it, and thus we get the error.

This is an example of when using an arrow function is preferred over using a named or anonymous function.

Conclusion

The this keyword in JavaScript can be an overwhelming topic. It can be hard to understand what it is, or why it matters, or how to determine what it is. I’ve been working with JavaScript for about 6 years full time now, and am only barely starting to understand it. But once you get the hang of it it becomes easier to determine. And if you can look at a function and determine what this is referring to, you have less of a chance of introducing bugs into your code by misusing it. Hopefully this article cleared some of this up for you. If you’d like to read more, check out these articles, all of which were references for me while writing the article.

References

Top comments (0)