Shades of 'this' in JavaScript
Overview
Depending on where it is used,
this
behaves differently: in the global space¹, inside a regular function², within an object's method³, in an arrow function⁴, a callback function⁵, or an event handler⁶.There are nuances between strict and non-strict modes², adding a little complexity.
We can explicitly pass in a
this
context to call() or apply()⁷ methods. There's an exception withbind()
⁸ method which can set the value of a function'sthis
regardless of how it is invoked which requires us to trace its origin.
Let's break it all down and understand each point in plain and simple terms!
1. In global space
In the global space, this
takes on different identities (i.e., global object) depending on where your JavaScript is running.
In web browsers,
this
refers towindow
object,In Node.js,
this
refers to an object calledglobal
.
console.log(this); //Window{..} or global object
2. In a function
In functions, the this
keyword initially points to undefined
. Yet, it depends on the presence of strict mode.
In strict mode ("use strict"
), this
remains the same, i.e., undefined
.
"use strict";
function foo() {
console.log(this); //undefined
}
foo();
Note: If you call the function as window.foo()
, even in strict mode, 'this' will refer to the window
object.
"use strict";
function foo() {
console.log(this); // Window{..}
}
window.foo();
Now, in non-strict mode, this
refers to the global object due to default binding or what we call "this
substitution."
What is this
substitution? In non-strict mode, when the value of this
is either undefined
or null
, JavaScript replace its value with the global object!
// non-strict mode
function bar() {
console.log(this); // Window{..} or global object
}
bar();
3. In an object method
What is the difference between a function and a method?
A function is a standalone block of code that can be invoked independently, whereas a method is a function associated with an object that can be invoked using the object it belongs to.
In the case of object method, the this
refers to its owner object (Object method binding).
const employee = {
fullName : 'Robin Kataria',
getName : function() {
console.log(this.fullName);
}
};
employee.getName();
In the above example, this
is like a mirror reflecting to the employee
object itself.
4. In an arrow function
According to the MDN definition, "...and, arrow functions don't provide their own this
binding (it retains the this
value of the enclosing lexical context)."
Now, "enclosing lexical context" might sound a bit fancy, but it simply refers to the environment in which a function is defined. This environment allows a function to access variables from its outer (enclosing) scope - this is known as lexical scoping.
Let's look at two examples:
Example 1: Arrow function inside global space
const foo = () => {
console.log(this); //global object
}
Example 2: Arrow function nested inside an object
const myObject = {
myMethod : function() {
const arrowFunction = () => {
console.log(this); // myObject {myMethod: f}
};
arrowFunction();
},
};
myObject.myMethod();
In this example, this
is invoked inside arrowFunction
which is defined in an Object method myMethod
. Following the definition, this
points to its enclosing lexical context, which is the method myMethod
.
As myMethod
is an object method, this
ultimately refers to the myObject
object itself.
5. Advance case of this
keyword
Let's put together everything we've learned so far and try to guess the output of the following code:
const carOwner = {
ownerName: "Robin",
cars: ["Sedan"],
printCars: function() {
this.cars.forEach(function(car) {
console.log(`${this.ownerName} owns a ${car}`);
});
}
};
carOwner.printCars();
In line 6, when we use console.log(`${this.ownerName} owns a ${car}.`)
, the question arises: Does this
refer to the carOwner
object since it's inside a method of that object?
Instead of seeing Robin owns a Sedan
, the correct output is undefined owns a Sedan
.
The reason is that this.ownerName
is undefined because this
is referring to the global object (window), and window.carOwner
is undefined.
You might be wondering, isn't the function (car) {...}
inside the object method and why it isn't referring to the Object, as explained in Point 3?
The key is that the this
is inside a callback function, and a callback function is just a regular function. In non-strict mode, this
inside a regular callback function refers to the global object.
To address this issue, many developers prefer using Arrow Functions as callback functions. As mentioned in Point 4, arrow functions do not provide their own this
binding (they retain the this
value of the enclosing lexical context).
So, if we replace the regular callback function with an Arrow Function, we get the expected result.
const carOwner = {
ownerName: "Robin",
cars: ["Sedan"],
printCars: function() {
this.cars.forEach((car) => {
console.log(`${this.ownerName} owns a ${car}`);
//Robin owns a Sedan
});
}
};
carOwner.printCars();
For further details, refer to Youssef Zidan's blog on this
keyword.
6. Inside an event handler
<button onclick="alert(this)">Click here</button>
In this scenario, this
points directly to the HTML element that triggered the event. It refers to the button itself. When you click the button, the alert message will display 'HTMLButtonElement.'
With this
, we can explore the element's properties. For instance, this.tagName
will give the tag name of the button.
7. Explicit function binding
By using .call()
or .apply()
, we can explicitly pass in a this
context to a function. This is like providing instructions, ensuring that the function operates with a specific object.
Consider the example below, where we have two persons:
const car = {
displayInfo: function() {
return this.make + " " + this.model;
}
}
const myCar = {
make: "Hyundai",
model: "Verna",
}
Now, with the magic of call()
, we can call the car.displayInfo
method with myCar
as an argument.
// Return "Hyundai Verna":
car.displayInfo.call(myCar);
In this scenario, this
within the displayInfo
method refers to myCar
, even though displayInfo
is originally a method of car
. It's like giving a method a temporary pass to work with a different object.
8. Function Borrowing
With the bind()
method, an object can borrow a method from another object.
In the code snippet below, two objects, carOwner
and carDriver
, are defined. The carDriver
object has similar properties as carOwner
but lacks the method getOwnerFullName
.
const carOwner = {
ownerName: "Robin",
ownerSurname: "Kataria",
getOwnerFullName: function () {
return this.ownerName + " " + this.ownerSurname;
}
}
const carDriver = {
ownerName: "John",
ownerSurname: "Doe",
}
let driverFullName = carOwner.getOwnerFullName.bind(carDriver);
The last line uses the bind
method to create a new function driverFullName
that is essentially a reference to getOwnerFullName
method of carOwner
, but with carDriver
as the context (this
value). This allows driverFullName
to use the method from carOwner
with the data from carDriver
.
And, it's a wrap!
Thanks for stopping by and spending your time reading this article :)
List of references: w3schools, mdn web docs, Akshay Saini, Youssef Zidan, zcaceres
Subscribe to my newsletter
Read articles from Robin Kataria directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by