this
is one of the most common JS keywords. You see them everywhere, but it can be hard to tell what this
is.
I will cover 3 scenarios where this
may be used: globally, inside a regular function, and inside arrow function. This should cover most usage.
- Global
this
this
inside a regular functionthis
inside arrow function
Let's start by looking at some examples!
Btw, I will be doing this inside browser (chrome) console, not node module. I will also assume strict mode is not used.
Global this
If we just type this
in our browser console, it will refer to window/ global object.
this // Window {...}
var helloVar = 'helloVar'
this.helloVar // helloVar
window.helloWindow = 'helloWindow'
this.helloWindow // 'helloWindow'
const helloConst = 'helloConst'
this.helloConst // undefined
let helloLet = 'helloLet'
this.helloLet // undefined
You see that let
and const
can't be called through this
. They are not stored inside "object environment record", but inside "declarative environment records". Explaining this will be outside of this article's scope. Here's a link if you're interested.
this
inside a regular function
Let's start by an example:
const obj = {
breakfast: 'donut',
wutBreakfast: function() {console.log(`I had ${this.breakfast} this morning!`)}
}
window.breakfast = 'waffles';
obj.wutBreakfast() // I had donut this morning!
Here we observe that this
inside this.breakfast
refers to the object itself. Look at where the function call is when calling obj.wutBreakfast()
. Ask yourself: "Is there an object to the left of my function call?" That object is where your this
refers to.
What if there is no object to the left of function call? If you are calling a function without an object to the left of function call, you can assume it is the global object. In this case, the Window
object.
Let's look at the next example:
function sayBrunch(){
console.log(`I had ${this.brunch} for brunch!`)
}
sayBrunch() // I had undefined for brunch
We haven't defined anything for brunch yet, so it returns undefined. Let's define it inside window object
window.brunch = 'oatmeal'
function sayBrunch(){
console.log(`I had ${this.brunch} for brunch!`)
}
sayBrunch() // I had oatmeal for brunch!
Let's do few more examples to build your intuition:
window.dinner = 'pizza'
const foodObj = {
dinner: 'spaghetti',
sayDinner: function(){
console.log(`I had ${this.dinner} for dinner!`)
}
}
foodObj.sayDinner() // what does it return?
Another one, with a little twist. We defined a window appetizer string and a mealObj.appetizer string. We call sayAppetizers from two different objects. What do you think each will return?
window.appetizer = 'chocolate';
function sayAppetizer(){
console.log(`I had ${this.appetizer} for appetizer!`)
}
const mealObj = {
appetizer: 'ice cream',
sayAppetizer: sayAppetizer
}
mealObj.sayAppetizer() // what does it return?
sayAppetizer() // what does it return?
Just remember, this
inside regular JS function refers to the object immediately to the left where the function is called. If there is no object, assume it is a window object.
With this in mind, even if we have obj1.obj2.obj3.someFunc()
, we know that this
inside someFunc()
will refer to obj3
because it is the closest object to where function is called.
this
inside arrow function
This behaves differently inside an arrow function. There are three things you need to keep in mind the whole time:
- Only regular function and global function can have
this
. - Arrow function does not have
this
on its own - When
this
is referred to inside an arrow function, it will look up the scope to find this value. It behaves like lexical scope.
Let's look at first example:
let myObj = {
breakfast: 'taco',
sayBreakfast: () => {
console.log(`I had ${this.breakfast} for breakfast`)
}
}
window.breakfast = 'pizza'
myObj.sayBreakfast() // pizza
Let's see if this makes sense while keeping the 3 rules above in mind:
when we call myObj.sayBreakfast(), it looks up to myObj, but since myObj does not have this
(rule #2), it will look one more up, the global/ window object (rule #1). It saw that global/window has this.breakfast = 'pizza'
, so it prints pizza.
Now add a regular function to object:
let myObj = {
breakfast: 'taco',
sayBreakfast: () => {
console.log(`I had ${this.breakfast} for breakfast`)
},
sayRegBreakfast: function() {
console.log(`I had ${this.breakfast} and it was yummy`)
}
}
window.breakfast = 'pizza'
myObj.sayBreakfast() // pizza
myObj.sayRegBreakfast() // taco
You'll see that using regular function gives "taco" and arrow gives "pizza".
Let's call an arrow function from global object scope. We should expect it to have this
from global scope. Is it true?
window.secondBreakfast = 'eggs';
const saySecondBreakfast = () => {
console.log(`I had ${this.secondBreakfast} for second breakfast!`)
}
saySecondBreakfast() // eggs
I was in disbelief when I see this either, so let's prove it further. The example below is from getify archive:
function foo() {
return function() {
return function() {
return function() {
console.log("Id: ", this.id);
}
}
}
}
foo.call( { id: 42} )()()() // undefined
vs
function foo2() {
return () => {
return () => {
return () => {
console.log("id:", this.id);
};
};
};
}
foo2.call( { id: 42 } )()()() // 42
(Btw, call assigns this
to function we are calling - foo/ foo2 itself - with the argument object we pass)
Remember that only arrow function looks up lexically; the first example looks for this
inside the third nested function and found nothing, so it returns undefined.
While foo2, finding no this
inside third nested function, lexically looks up for next available reg/ global function's this
. It found foo2's this
(from foo2.call({id: 42})
) first (remember rule #1), so it prints 42.
If there had been a regular function on the second example earlier, it wouldn't have found it, like:
function foo3() {
return () => {
return function() { // this is regular function now
return () => {
console.log("id:", this.id);
};
};
};
}
foo3.call({id:101})()()() // undefined
But if we gave this
to where the return function() {...})
is, it would have found it. Because when arrow function lexically looks up and found the first regular function, that function is given this
value of 101.
function foo3() {
return () => {
return function() {
return () => {
console.log("id:", this.id);
};
};
};
}
foo3()().call({id: 101})() // 101
So that's it folks! This is definitely only the tip of iceberg, but this
should be enough to get you started - pun intended 😁.
Let me know if you have questions/ found mistakes - thanks for reading and happy codin'!!