Mastering this
in JavaScript
JavaScript developers—new and experienced—constantly rate the variable this
as a major pain of the language. The this
variable, which represents a function’s context, plays an important role in everyday JavaScript programming, so understanding how it works is critical. This blog post gives an overview of this
and its many quirks and forms. We will lay out the four ways to set a function’s this
value, as well as discuss some common use cases and pitfalls.
What is Context?
But first, what is context in the first place? Think of it like this: if a function is a sentence, the context is the sentence’s subject. It represents who or what the function is about. If a function is creating a new user, the context would be the user instance. The way we access a function’s context is through the variable this
.
It seems simple, so why is it so hard to understand? Developers routinely get tripped up by this
, and that’s largely due to the fact that we’re used to lexical scoping. With lexical scoping, we can see the value of a variable by finding its assignment in earlier code. So when we want to know what usersCount
holds, we can just scroll up and find where that variable is set.
Here’s a visual example:
That’s lexical scoping.
The this
variable, however, uses dynamic scoping. We have much less exposure to dynamic scoping than we do to lexical scoping, so naturally it’s more difficult to grasp. With dynamic scoping, variables are set depending on how the host function is called. So the value of this
is any given function depends entirely on how and where that function was called in the first place.
Intuition and this
Before getting into the nitty gritty, let’s consider what we already know about this
. Most anyone who’s written jQuery code has seen an event binding like this:
$('button').on('click', function() {
$(this).addClass('was_clicked');
// $(this) refers to the button the was clicked
});
When that button is clicked, the callback function runs. We access the $(‘button’)
that was clicked inside the callback with $(this)
. It makes intuitive sense that this
would represent the button, because that is the natural subject of the callback function.
The Four Ways to Set a Function’s this
Value
Without further ado, here are the 4 ways to set a function’s this
value:
- Call a method on an object, like
object.method()
- Pass
this
in with.call()
and.apply()
- Use
new
to makethis
start out empty - Manually bind a
this
value with.bind()
If none of the above are used, this
is the global scope object (i.e. window
in a browser). This is a very bad thing and we should always avoid it by using one of the four rules.
Now, let’s dive into each method.
Call a Method On an Object (Like object.method()
)
When you call a method on an object, the value of this
inside that method will be the object it was called on. So calling user.getName()
will set getName’s this
value to user
. This can be tricky because you don’t actively set the value of this
, but it gets populated nonetheless.
Pass this
in With .call()
and .apply()
Let’s talk first about .call()
. In JavaScript functions are just objects, and they can have properties and methods like any other object. One of the methods built into every function is .call()
. This accepts an argument for the host function’s this
value. .call()
executes your host function, setting its this
value to whatever argument you supplied:
The near-identical twin of .call()
is .apply()
. The only difference here is how you pass arguments to the underlying function. From the example above, imagine that user.alertName()
accepts an argument. With .call()
, all arguments are passed in inside a single array. With .apply()
, the arguments are passed in as comma-separated values.
The difference between .call()
and .apply()
are small, but it’s good to know they are there.
Use new
to Make this
Start Out Empty
If you want your function to start out with a blank slate, you can set this
as an empty object by calling that function using new
. Then, inside the function, you can build up that object however you like by attaching values to this
.
Functions invoked using new
automatically return this
:
function User(handle) {
this.handle = handle;
}
var andrew = new User("@AndrewWK");
// andrew === {handle: "AndrewWK"}
Manually Bind a this
Value With .bind()
The .bind()
function is much like .call()
in that you pass in your this
value as the first argument to each function. The difference is that .bind()
returns a function with your this
value set, whereas .call()
executes a function with your this
value set. Calling .bind()
does not run your function, it simply sets up a function with the correct context.
var alertName = function() {
alert(this.handle);
}
var boundAlertName = alertName.bind({handle: "@AndrewWK"});
boundAlertName()
// alerts "@AndrewWK"
This is useful anywhere you use callback functions. By their very nature, callback functions must be passed around without executing immediately. But setting a this
value with .call()
also executes that function, defeating the purpose of a callback.
Managing this
in a callback
And that’s it: those are the four ways to set a function’s this
value. Those four rules are not too hard to remember, but there is a common pitfall you should know about. I’m talking about callbacks. It’s easy to be writing a callback and not even know it, like in setTimeout
:
setTimeout(function() {
$(‘button’).addClass(‘red’);
}, 1000);
The setTimeout
function accepts a callback, but since it’s not using one of the four rules for setting context, this
defaults to the global window
object. That’s fine in the example above, but becomes a bug in code like this:
$('button').on('click', function() {
setTimeout(function() {
// Uh oh! `this` is the global object!
$(this).addClass('clicked');
}, 1000);
});
We’re expecting $(this)
to refer to the button that was clicked, but it doesn’t, since this
defaults to the global window
object. One way to solve this issue is to store our desired value of this
in a local variable and then simply use that variable in a child scope:
$('button').on('click', function() {
var _this = this;
setTimeout(function() {
$(_this).addClass('clicked'); // All better
}, 1000);
});
Of course there are many ways to accomplish the same thing. You could use .bind()
, .call()
, or a number of other options. Choose what works best for each individual situation.
In Review
The this
variable is a critical tool that enables important concepts in JavaScript programming: modules, browser-based MVCs, functional programming methods, and more.
Though it may feel hard to master, the four rules listed above are the only ways to assign the value of this
, and remembering them will make you a powerful JavaScript developer.
P.S. What did you think of this post? Are there any other JavaScript topics you’d like to see us cover? Let us know by throwing us a comment below.
Share your thoughts with @engineyard on Twitter
OR
Talk about it on reddit