5 simple rules to tame JavaScript's most confusing keyword
JavaScript's this
keyword is confusing at the best of times. But what if I told you that 99% of the time, five simple rules are all you need to use this
like a pro?
this
may well be the most confusing part of JavaScript you’ll run up against — it sure is for me. And what’s worse, unlike other confusing things (like the arguments
fake-array), there’s no modern substitute for it! You need this
for classes, and thus for many modern JavaScript libraries. You’re kinda stuck with it.
It’s no wonder then that I recently received an e-mail with a question about this
. In particular, the reader wanted some help understanding the below block of code from my article When to use arrow functions with React:
Within this
render
function, the call tohandleClick
is preceded bythis.
. Thethis
on line 4 refers to the component instance, so shouldn’t thethis
on line 7 point to the component instance too?
class BrokenButton extends React.Component {
render() {
return (
<button onClick={this.handleClick} style={this.state}> Set background to red
</button>
)
}
handleClick() {
this.setState({ backgroundColor: 'red' }) }
}
Firstly, thanks for the great question! This is something that most JavaScript developers have struggled with at some point — myself included. To really understand what’s going, there’s no substitute for trying things out, so later in this article I’m going to walk you through a bunch of live demos (after introducing the 5 rules that’ll help you understand this
). But I’m getting ahead of myself! Let me start by answering the question:
If a call to handleClick
is preceded by this.
, then the value of this
within the function will behave as you expect. The issue is, the above example never actually calls handleClick()
.
To put it another way: calling this.handleClick()
will change the function’s behavior, setting the value of the this
keyword within the handleClick
function. In contrast, passing this.handleClick
just passes the function as a value — without changing the behavior at all.
Confused? You’re not alone. But keep reading — we’ll get to the bottom of this together.
When working with JavaScript functions, the value of this
depends on the way in which the function was called — as opposed to the way in which the function was defined.
This can result in some truly boggling behavior. For example, try uncommenting the final two lines in the below example. Like actually try it out. Seeing is believing. I’ll wait here.
Did you give it a shot? Great! So you’ll have noticed that while calling robot.sleepTalk()
prints a classic line from Futurama… calling exactly the same function a couple lines later causes the app to die.
So what’s going on? Let’s see if you can’t figure it out yourself, using the 5 rules of this
.
this
The value of this
in a JavaScript function can vary based on how the function was called. There are far too many rules to list them all right here, but 99% of situations are covered by just three rules:
obj.someFunction()
, the value of this
within that function will be the object on which it was called.call()
or apply()
methods, the value of this
inside the function is provided as the first argument to call()
or apply()
..
), the value of this
will either be undefined
in strict mode, or the global object (e.g. window
) outside of strict mode. But you’re probably in strict mode, so it’s probably going to be undefined
.There are two important exceptions to these rules:
this
will always equal the first argument to bind
.this
within an arrow function will always equal the value of this
at the time the function was created.These rules and exceptions are a bit of a mouthful, so let’s take a look at some live examples.
this
The simplest and most obvious way for a function to get a value for this
occurs when the function is called as a method. In this case, this
is set to the object on which it was called.
Keep in mind that it doesn’t matter how a function is defined — only how it is called. This means that there are many different ways to create a counter with the same API as above:
this
on plain functionsWhen you call a function by itself in a strict-mode application, this
will be undefined
.
Obviously, it’s unlikely you’re ever going to write code like the above. But this rule can still cause you all sorts of grief, especially when this rule is applied to functions that were designed to be called as methods.
For example, say you pass a counter instance’s increment
method to a setTimeout()
function.
See how the call to increment
by setTimeout()
doesn’t behave as you’d expect? This is because internally, setTimeout()
doesn’t have access to the counter
object — it only has access to the increment
function. As a result, increment
is called by itself, and this
is undefined
for the duration of that call.
Be careful when using this
in event handlers — the code calling the event handler probably won’t set this
to what you want it to be!
this
on arrow functionsWhen using arrow functions, you’ll find that the value of this
is fixed to whatever it was when the function was defined. When the function is created at the top level, this will usually be undefined
. But where things get interesting are when you define arrow functions within other functions. In this case, the value of this
within the arrow function will reflect the value of this
within the function that created it.
One place where arrow functions come in handy is class constructors. Any arrow functions defined in a constructor will always have a value of this
that points to the class instance, no matter where they’re called from — making them perfect for event handlers.
Arrow functions are also great for event handlers in React’s class components, as they reduce the mental overhead for accessing instance methods like setState()
.
this
using bind
, call
and apply
If you need to force a specific value of this
but can’t use an arrow function, then bind, call and apply are your friends. These functions allow you to specify a function’s arguments and this
value programmatically.
I won’t go into too much detail on how these work — if you’re interested, you can follow the above links to read the details on MDN. For good measure though, here’s a demo using all three:
As bind
, call
and apply
are themselves functions… well, you can use them on each other. But please, don’t do this at home (or especially at work).
this
quizNow that you’ve seen this
in action, let’s take another look at the broken dreaming bending robot:
const bendingRobot = {
dream: "kill all humans",
sleepTalk() {
console.log(`Hey baby, wanna ${this.dream}?`)
}
}
bendingRobot.sleepTalk()
const bendingRobotSleepTalk = bendingRobot.sleepTalk
bendingRobotSleepTalk()
Now do you know why the last line throws an error, while calling bendingRobot.sleepTalk()
works as expected? Have a think about it, and then check your answer below.
this
doesn’t care about where sleepTalk
was defined — it only cares about how it was called. In the case where it’s called using robot.sleepTalk()
, the value of this
will be robot
. However, when called as a bare function, this
will be undefined
.
Let’s finish up by taking another look at the broken React component from the start of this article. Now that you’ve learned about the different ways to control the value of this
, can you fix the component so that clicking the button changes the background color?
How’d you go — were you able to make the button work?
Keep in mind that there’s no right ways to do this — I’ve provided one solution on the solution tab, but it’s by no means the only one. If you come up with another solution, I’d love to see it — you can let me know with a tweet!
this
?So now that you know all about this
, I want to finish up by discussing the best approach to using it:
In my opinion, the best use of this
is no use of this
.
At best, this
is confusing. At worst, it can be the cause of obscure and unpredictable bugs. If you have the choice, the easiest thing to do is to just avoid it. And luckily, if you’re using React, hooks make it easier than it’s ever been to avoid this
completely!
With this in mind, in a future article, I want to dive into the workings of a custom hook from one of my production codebases. And I’m looking for your help to decide which one! Here are the options:
useLatestSnapshot()
- a hook for integrating with Firebase, simplifying access to the latest Snapshot of a Firestore Document or Query.useMediaQuery()
- a hook that returns a boolean reflecting whether the browser currently matches the given media query.usePopupTrigger()
- a hook that manages the state of popup menu, independent of its presentation.If you’d like to have a say in which of these I write about, it’s easy — just send a quick email or tweet telling me which of the above options you’d like to see. Of course, I’d love to hear any of your other JavaScript questions too! I won’t promise to respond to every message, but I’m always looking for ideas for content that can help give people a leg up. I can’t wait to hear from you — and until next time, happy coding!
Tokyo, Japan