TLDR:
The
thisin 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
thisin 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 thestudentobject, so it outputsstudent - For the 2nd log,
.logThis(...)is called on theteacherobject, 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
thisis 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
thisin 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 thedefaultobject 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
thisin 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
thisin 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