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:
- 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
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();
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
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
. 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
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
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 <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"
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.
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);
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");
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"]);
.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(user2);
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 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
Summary
Let’s review the four different ways of calling a function that determine its this
binding:
- in the global context: refers to global object or undefined in strict mode / for arrow fn
- as a method on an object: refers to object on left side of dot when method called
- as a constructor function or class constructor: refers to the instance itself when called with
new
- 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:
Top comments (1)
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!