TLDR:
The
this
in a method is the object the method was called on
Introduction
Looking up “how does this
work in javascript?” on google can return a large number of very good results. Most of them explain how this
works in great detail, but I found that although the explanation is both correct and detailed, it usually involves going over multiple complicated scenarios and explaining a different “rule” for each scenario. All these special rules can quickly become overwhelming when you’re just trying to use this
in a small part of your code.
So at the risk of brushing over the little nuances of this
, I’ll try to (over)simplify all the different interwined rules into one simple rule, something that will hopefully be a bit easier to remember.
The Rule
The very over-simplified rule of this
in JS is,
The
this
in a method is the object the method was called on
that’s about it.
An Example
Let’s jump into a simple example where we can see how this short rule can help us figure out what this
means,
let obj = {
add: function (a, b) {
console.log(this); // 1st log
console.log(a); // 2nd log
console.log(b); // 3rd log
}
}
What would this program output?
It may be tempting to say something along the lines of
“The first log will output obj
, but it’s impossible to know the 2nd and 3rd log without looking at the call site”.
This answer isn’t completely wrong, but it sort of misses an important detail. The actual answer is that just like how it isn’t possible to know what the values of a
and b
before the method is called, it isn’t possible to know the value of this
either.
The value of this
has no relation to where the function or method is declared, just like the params a
and b
, and just like a
and b
it is set when the method is called not when it is created.
now if we changed it a bit to,
let obj = {
add: function (a, b) {
console.log(this); // 1st log
console.log(a); // 2nd log
console.log(b); // 3rd log
}
}
obj.add(1, 2);
We can see that,
- The 1st log would output
obj
, since that’s the object the method is being called on, - The 2nd log would output
1
, since that’s the 1st parameter we passed - The 3rd log would output
2
, since that’s the 2nd parameter we passed
Another Example
That was a simple enough example, and something where the value of this
seems pretty intuitive, specially if you used another language like Java
or Python
before.
Now, let’s look at another example, somewhere where the value of this
might not be so intuitive
let student = {
name: 'john',
logThis: function() {
console.log(this);
}
}
let teacher = {
name: 'alan'
};
teacher.logThis = student.logThis;
student.logThis(); // 1st log
teacher.logThis(); // 2nd log
What should the logs output?
- For the 1st log,
.logThis(...)
is called on thestudent
object, so it outputsstudent
- For the 2nd log,
.logThis(...)
is called on theteacher
object, so it outputteacher
.
The most common source of confusion seems to come from this line
teacher.logThis = student.logThis;
There is an expectation that student.logThis
“belongs” to the student
object, so even if we’re assigning it to another object, it should still “remember” where it came from. But the reality is that,
Functions do not “belong” to any object nor do they know which object they were created in. They are simply “called on” objects, and the value of
this
is the object they are called on.
So although objects may have have properties that point to functions, functions themselves have no idea which object they were created in.
Now that we have the most common case covered, let’s cover a few other cases
Case 1: What if it’s called without an object?
For example
function add(a, b) {
console.log(this); // 1st log
console.log(a); // 2nd log
console.log(b); // 3rd log
}
add(1, 2);
The rule for this case is
The
this
in a method is the object the method was called on
i.e. the rules are the same.
A function in JS needs to be called on an object. If no object is specified, it’s called on a default
object.
In node
this value is an object called global
.
In the browser it’s window
.
In another runtime environment it could be something else. The important thing to remember is that all methods are called on objects, so even if you don’t specify the object to call the method on, the Runtime
will run it against a default
one.
So if we ran the above snippet in the browser
- The 1st log would be
window
, which is thedefault
object in the browser - The 2nd log would be
1
- The 3rd log would be
2
Case 2: What if it’s called with new
?
For example
function person() {
console.log(this); // 1st log
}
let p = new person();
The rule for this case is
The
this
in a method is the object the method was called on
i.e. it’s still the same.
What new
essentially does is create a “new object” i.e. {}
and then call person()
on the newly created object.
So running the above snippet,
- The 1st log would be
{}
, sinceperson()
is called on a new empty object.
Case 3: What if we use strict mode
For example,
'use strict'
function logThis() {
console.log(this);
}
logThis();
The rule for this case is
The
this
in a method is the object the method was called on
i.e. it’s always the same.
The only difference is that in strict mode
the default
object will always be undefined
i.e. it strict mode
changes the value of the default
object, but the rules for this
remain the same.
Conclusion
A fun little thing to do is to replace the code in the snippets with ES6 arrow functions (() ⇒ {}
) in the examples, and see if the outputs match (hint: it won’t). Which may make it seem like the rules for arrow functions are different. Fortunately, the basic rule is still the same, but the way in which arrow functions are called is a bit different. We can go over that in a future post.
Disclaimer:
I intentionally avoided using “execution context” and “invoking”, along with a few other terms. Although they’re the more accurate terms, they can seem confusing if you don’t already know what they are.
Further reading
“This or That” from You Don’t Know JS - Honestly, the entire book is amazing
MDN docs - A bit more technical, but a great resource nonetheless