The this
keyword is one of the most confusing topic for JavaScript beginners. In this post, I aim to break down the this
keyword in a simple and beginner-friendly manner. Please note, this is not an exhaustive coverage of the this
keyword. Instead it is aimed at complete beginners trying to get a grip on the subject. For intermediate or advanced users, I recommend this mdn doc.
Why this
keyword exist?
Maybe it will do justice to our quest if we can start with why this particular keyword has to exist in the first place.
You see, computers are dumb. It needs to be told at every instance where it can find the data required for execution. The fancy term for this is called execution context. The code might look obvious for us, but it isn't for the engine that is executing our code.
So, the best way to start understanding the this
keyword is to switch our point of view to the point of view of the engine.
From the engine's point of view, everything in JavaScript are objects. A JavaScript program is essentially a cluster of communicating objects. On the top of the hierarchy is the Window
object (or just global object in node).
When the engine is searching for values (variables) to execute, it only checks directly inside the Window
object. If the value is in any other place, for example, inside an object, then we need to explicitly tell the engine to look inside that particular object.
The way we communicate it is by using the this
keyword.
That's a very basic but overall explanation. I promise everything will start to click in its place as we start discussing the specifics.
JavaScript objects
An object in JavaScript is a container for named properties. A property is a key-value pair where the key act as the name we can use to call. The property can be a variable or a function, known as a method in JavaScript.
The following is a very basic example of a JavaScipt object. It has two variables firstName
and lastName
including the values. And a function (method) that returns the full name.
const user = {
firstName: "Aravind",
lastName: "Sanjeev",
fullName: function () {
return this.firstName + " " + this.lastName
},
}
I know I already used the this
keyword in the above example. Leave it for now, I will come back to this
later. (See what I did there?)
We can access the property values by their keys.
user.firstName // Aravind
user.lastName // Sanjeev
user.fullName() // Aravind Sanjeev
Now that we have a moderate understanding of JavaScript objects and how to access its values, let's discuss the window object.
The Window object
The Window
object is the top-most object in JavaScript. Every other object is a property of the window object.
On the JavaScript console, simply run window
to see the Window object. We can see a whole list of gibberish, it is not necessary that we understand them all. What I want you to take away is that when a JS engine is executing a code, it only checks for variables directly defined inside the window object. That is, the variable must be a property of the window object.
This is best exemplified by directly declaring a variable. On the JSConsole, run the following code.
variableName = "Value"
Please note that I didn't use var
, let
, or const
. The above variable can be called simply by its name.
variableName // Value
The same result can be achieved if we run the follow command.
window.variableName // Value
If you noticed, this is how we access the value of a property inside an object. The window initial is always implied even if we doesn't explicitly provide it.
The this
keyword
The this
keyword always refers to the parent object.
Go to jsconsole.com once again and try the below code.
console.log(this)
// Window {...}
Since we are using this
keyword in global scope, it will refer to the window object. So variables defined in the global scope can be called the same way.
variableName = "Value"
this.variableName // Value
That's because this
holds the value window
.
Now let's recall our example above.
const user = {
firstName: "Aravind",
lastName: "Sanjeev",
fullName: function () {
return this.firstName + " " + this.lastName
},
}
You can see that we are using this.firstName
and this.lastName
. Here, the this
keyword is referring to the object scope which is the user
object.
When we call the fullName()
method, the this
is replaced by user
.
user.fullName() // "Aravind Sanjeev"
// this.firstName == user.firstName
// this.secondName == user.secondName
To be more precise, we are actually checking:
window.user.firstName
window.user.secondName
Hence, the this
keyword is acting like a placeholder that is holding the value user
. Except, the placeholder is dynamic in the sense that it always inherits the scope of its parent object.
Technically, the JavaScript engine is still only checking inside the window object. However, this
keyword will force it to check inside the user
object.
If we do not provide the this
keyword, it will return a ReferenceError.
const user = {
firstName: "Aravind",
lastName: "Sanjeev",
fullName: function () {
return firstName + " " + lastName
},
}
user.fullName() // ReferenceError
This is because the JS engine is now looking for window.firstName
and window.lastName
which doesn't exist.
call(), apply(), and bind() methods
Now it is time to enter slighly more uncomfortable territory of the this
keyword. I aim to keep it as simple as possible.
call()
The call()
method is used to call a function. However, we can change the value of the this
keyword hence changing the context.
const sum = function (a, b) {
return a + b + this.n
}
const obj = {
n: 100,
}
sum.call(obj, 1, 2) // 103
In the above program, this
keyword refers to the window object. However, by utilizing the the call()
method, we managed to change the context to the obj
object.
Notice that the object we intent to change the context to is always passed as the first argument.
apply()
The apply()
method is similar to the call()
method. The difference is, we apply an array of arguments instead.
const sum = function (a, b) {
return a + b + this.n
}
const obj = {
n: 100,
}
const args = [1, 2]
sum.apply(obj, args) // 103
bind()
The bind()
is different from call()
and apply()
. It is used to bind a function to an object.
const obj = {
n: 100,
}
const sum = function (a, b) {
return a + b + this.n
}
const newSum = sum.bind(obj)
newSum(1, 2) // 103
Here we binded the sum
function by creating a new function newSum
and then binding it with obj
. Essentially, we created a separate function that now acts as a method of obj
.
Strict mode vs non-strict mode
The this
keyword behaves a little differently in strict and non-strict modes. In non-strict mode, this
inside a function defined in global scope will be the Window
object.
const func = function () {
return this
}
func() // Window
In strict mode, this
inside a function defined in global scope will be undefined.
const func = function () {
"use strict"
return this
}
func() // undefined
Inside an object, there is no change in behavior.
Arrow functions
Arrow functions does not have it's on this
binding. It instead inherits the scope of its object. Arrow functions should
- Never be used as methods (because there is no
this
binding) - May be used as functions inside methods (as it will inherit object's scope)
const obj1 = {
key: "value",
method: function () {
function normal() {
console.log(this)
}
normal()
},
}
obj1.method() // Window
// here the normal() falls out of scope and defaults to window object
const obj2 = {
key: "value",
method: function () {
const arrow = () => {
console.log(this)
}
arrow()
},
}
obj2.method() // Object
// here arrow() has the object scope
As you can see, method inside obj2
returned the object itself. That's because arrow functions automatically inherits the scope of object.
And that's about it. Hope this
was brief but useful. (See what I did there again?)