1. Introduction
The this
keyword is one of the most perplexing concepts in JavaScript. The confusion arises from the fact that its value is determined differently depending on the context in which it is used. The this
keyword can be used in different contexts: inside functions, global scope, inside modules, etc.
2. Function context
The this
keyword is mostly used inside functions to refer to the object using which the function was invoked. When a function is called as a “method” using an object, the this keyword refers to the object that invoked the function. The value of this inside a function depends on how that function is called. Consider the following code example:
const student = { id: 123, name: "John", email: "john@email.com", printInfo: function () { console.log(`${this.id} - ${this.name} - ${this.email}`); }, }; student.printInfo(); // 123 - John - john@email.com
The this
inside that function refers to the object using which the function was invoked. So in our code example, this
inside the printInfo
refers to the student
object. As the student
object has the three properties that are accessed using the this
keyword inside the printInfo
function, their values are logged to the console.
function printName() { console.log("The name is: " + this.fullName); } printName(); //The name is: undefined
What does this refer to inside the printName
function?
The answer to the question above depends on whether our code is executed in strict mode. If non-strict mode, this inside a function, when not invoked as a method, refers to the global object, which in the case of browsers is the window
object. However, the value of this
inside a function is undefined
in strict mode when not invoked as a method.
3. Global context
In the global scope, the value of this
varies depending on the environment where the JavaScript code is executed. JavaScript code runs in various environments, such as browsers or NodeJS. The value of this
in the global scope differs depending on the environment. In browsers, this
refers to the window
object when in the global scope.
In NodeJS, the value of this
varies depending on whether you’re using ECMAScript modules or CommonJS modules. In ECMAScript modules, this
is undefined
at the top level due to the code being executed in strict mode. In CommonJS modules, at the top level, this
refers to the module.exports
object.
Inside web workers, the value of this
at the top level refers to the global scope of the web worker, which is different from the global scope containing the window object in the browser. Code inside a web worker is executed in its own separate context with its own global scope.
4. Constructor function context
When a function is invoked as a constructor function using the new
keyword, the this keyword inside the constructor function refers to the newly created object. The new
keyword creates a new object and sets the newly created object as the value of this
. As a result, we can use this
inside a constructor function to add properties to the newly created object.
function Recipe(name, ingredients) { this.name = name; this.ingredients = ingredients; }
The function above, when invoked as a constructor function, will add two properties: name
and ingredients
to the newly created object.
5. Class context
Code inside a class in JavaScript is executed in strict mode. As a result, the value of this inside methods is either undefined if not invoked on an object or the class instance itself, which is used to invoke the method.
class Shape { constructor(color) { this.color = color; } printColor() { console.log(this.color); } } const circle = new Shape("Red"); const printColorFn = circle.printColor; printColorFn(); // Error: this is undefined
The code example above throws an error because we have invoked the printColor
method as a “function”. As mentioned earlier, code inside a class executes in strict mode, so, as within functions in strict mode, this
inside methods is undefined
.
6. DOM event handler context
What happens with callback functions that we don’t explicitly invoke? For example, consider DOM event handlers—callbacks that JavaScript calls on our behalf when a click event occurs. In such scenarios, what does the value of this
represent?
The event listener callback is executed with this bound to the HTML element that triggered the event. For instance, consider the following example:
<button>Submit</button> <script> const btn = document.querySelector("button"); class FormHandler { constructor(submitBtn) { submitBtn.addEventListener("click", this.submitForm); } submitForm() { console.log("form submitted"); console.log(this); } } new FormHandler(btn); </script>
When this method is called as an event listener callback, the value of this
refers to the button element that triggered the event, rather than the instance of the class as one might typically anticipate.
7. Arrow functions
Another way to solve the problem shown above is to use an arrow function. Let’s change the code example above to use an arrow function:
Unlike regular functions, which get their own value of this
when they are invoked, arrow functions don’t get their own this
value; instead, the value of this
inside an arrow function is taken from the surrounding context.
JavaScript also offers mechanisms to explicitly define the value of this as per our requirements. We can use any of the following three built-in methods to explicitly set the value of this:
- Function.prototype.call()
- Function.prototype.apply()
- Function.prototype.bind()
8. Borrowing methods
Consider an object with methods that could be beneficial for other objects too. How can we make those methods available to other objects? One approach is to replicate the method definitions for each object that needs them. However, duplicating code isn’t ideal. Is there a way to prevent duplication while reusing the same methods effectively?
const john = { name: "John", sayHello() { console.log("Hello, I am " + this.name); }, }; const sarah = { name: "Sarah", }; // borrow method from john const sayHello = john.sayHello; sayHello.call(sarah); // Hello, I am Sarah
We can explicitly set the value of this within a function and apply it to other objects using any of the three methods discussed earlier. This approach helps us avoid code duplication and promotes reuse.
You might wonder: why not create a constructor to generate objects and define shared methods on the constructor’s prototype property? That’s a valid point—using a constructor is the ideal approach when creating similar objects. However, explicitly setting the value of this provides a flexible way to share code between unrelated objects. It’s a useful option that can be applied in the right context.
9. Chain constructor calls
Prior to the introduction of classes in JavaScript, the conventional method for inheriting from another constructor function involved manually setting the prototype chain and reusing the inherited constructor to assign common properties to the new object. The example below demonstrates how an existing constructor can be leveraged to initialize properties in a newly created object:
function Employee(name, age, id) { this.name = name; this.age = age; this.id = id; } function BankEmployee(name, age, id, bankName) { // delegate the responsibility of adding // "name", "age", and "id" properties to // the Person constructor Employee.call(this, name, age, id); this.bankName = bankName; }
In simpler terms, the Employee
constructor is called within the BankEmployee
constructor, with this
explicitly bound to the newly created BankEmployee
instance. This ensures that any properties assigned to this
within the Employee
constructor are actually applied to the BankEmployee
object. This approach allows us to utilize the existing constructor function, minimizing code duplication.
10. globalThis
globalThis
is a standard global object in JavaScript that provides a consistent way to access the global context, regardless of the environment (browser, Node.js, or others). In different environments, the global object has various names:
- In browsers, it’s typically
window
orself
. - In Node.js, it’s global.
With globalThis
, you can access the global object without worrying about the environment. For example:
console.log(globalThis); // Outputs the global object in any environment
It was introduced in ECMAScript 2020. Provides a unified way to access the global scope, eliminating the need to check for window
, global
, or other environment-specific global objects. Useful in cross-platform or universal JavaScript code.
if (typeof window !== "undefined" && window.secretProperty) { // execute code for browser } else if (typeof global !== "undefined" && global.secretProperty) { // execute code for nodejs }
With globalThis
, we can simplify the above code as shown below:
if (globalThis.secretProperty) { // execute code }