Introduction
Understanding the behavior of the "this" keyword in JavaScript is crucial for every developer. It often becomes a little confusing. Let's start the discussion with the very basic idea of this keyword.
As stated in the MDN documentation, "A function's this keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode."
Here, you are going to understand what is "this" in JavaScript. We will unravel the mysteries of "this" and explore its various use cases, binding rules, and best practices.
First we are going to understand "this" with a very simple example, then we will witness all the explanation and examples of the terms used in the previous paragraphs. Here goes the first example:
const person = {
name: "Alex",
profession: "teacher",
greet: function () {
console.log(`Hello, my name is ${this.name} and I am a ${this.profession}.`);
},
};
person.greet();
In this code snippet, the person
object has three properties: name
, profession
, and greet
which is a method (method is an object property that has a function value). And then we are calling the greet
method with person.greet()
. The output we get after performing this is:
Hello, my name is Alex and I am a teacher.
In this case, the this keyword refers to the person
object. So, when the method greet
is invoked using person.greet()
, this
inside the greet
method points to the person
object itself.
The this keyword allows us to access and use the properties of the object in which the method is defined. We can have multiple objects with the same method, and each object can refer to its own properties using this.
In simple terms, this refers to the context of the current object when a method is called.
Context and Execution
In JavaScript, this is a context-dependent keyword that refers to the current execution context.
Now, what is execution context?
In JavaScript, there are two main types of execution context:
Global Context: It is the default context in which code is executed outside any function. In the global context, this refers to the global object (e.g. "window" in browsers.)
Function Context: It is the context within the execution of a function. Functions in JavaScript run in a specific context, and using the this keyword we can access it. Here, the value of this is determined by the function invocation pattern.
Function Invocation
Function invocation or in simple words calling a function in JavaScript has a few different ways, they are
Simple Function Invocation: In non-strict mode of JavaScript, when a function is called or invoked, this keyword refers to the global object which is the "window" in the browser and "global" on Node.Js. But in strict mode, JavaScript sets this to undefined. (Stict and non-strict mode will be discussed in later in this article). Here is the example:
function example() {
console.log(this); // Points to the global object (window or global)
}
example();
Method Invocation: When a function is called as a method of an object, this simply refers to the object that owns the method. We can take the same example as the first one, which is:
const person = {
name: "Alex",
profession: "teacher",
greet: function () {
console.log(`Hello, my name is ${this.name} and I am a ${this.profession}.`);
},
};
person.greet();
Here, greet
is a method and inside greet
, this
keyword is referring to the object person
which owns the method greet
.
Constructor Invocation: When a function is invocated using the new keyword that is when a function is used as a constructor with the new keyword, it refers to constructor invocation.
Here, this refers to the newly created instance of the object.
Let's see an example to have a clear understanding:
function person(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
const personName = new person("Alex", "Miller");
console.log(personName.firstName);
Here, person
is a constructor function that takes two arguments (arg1
and arg2
). While calling this function with the new
keyword, it creates a new object personName
and sets the firstName
and lastName
properties on that object using the values of arg1
and arg2
. The this
keyword inside the constructor function refers to the newly created object.
Indirect Invocation: Indirect invocation, also known as function borrowing or using methods like call()
and apply()
, allows you to invoke a function with a specific context (the this
value) different from its original context. This technique is useful when you want to reuse a function but apply it to another object temporarily.
Here's an example:
function introduce() {
console.log(`My name is ${this.name} and I am a ${this.profession}.`);
}
const person1 = {
name: "Alex",
profession: "singer",
};
const person2 = {
name: "Robert",
profession: "teacher",
};
introduce.call(person1); // Output: My name is Alex and I am a singer.
introduce.call(person2); // Output: My name is Robert and I am a teacher.
In this example, the introduce()
function is defined. Instead of defining the same function for each person object, the call
method can be used to "borrow" the function and set a specific context (the person object) as this.
Similarly, the apply
method is used to achieve the same result, but it takes an array of arguments:
function greet(greeting) {
console.log(`${greeting}, ${this.name}!`);
}
const person = {
name: "Alex",
};
greet.apply(person, ["Hello"]); // Output: Hello, Alex!
Both call
and apply
methods provide a way to temporarily change the this context when invoking a function. This can be useful while reusing a function with different data contexts.
Binding
Binding the this keyword is a fundamental concept in JavaScript, as it determines the context in which a function is executed.
Default Binding: When a function is invoked in the global scope (outside any other function or object), this refers to the global object (window in browsers). However, in strict mode, this will be undefined instead of pointing to the global object.
Implicit Binding: When a function is called as a method of an object, this points to the object itself. The object to the left of the dot during the function call becomes the context of this.
Let's see an example:
const person = {
name: "Alex",
greet: function () {
console.log(`Hello, I'm ${this.name}.`);
},
};
person.greet(); // Output: Hello, I'm Alex.
Explicit Binding: Developers can explicitly set the value of this using the call, apply, or bind methods on functions.
- The
call
method immediately invokes the function with the specified context and arguments. - The
apply
method is similar to call, but it takes arguments as an array. - The
bind
method returns a new function with the specified context and, optionally, pre-set arguments(will be discussed in detail shortly).
function greet(message) {
console.log(`${message}, I'm ${this.name}.`);
}
const person = {
name: "Robert",
};
greet.call(person, "Hi"); // Output: Hi, I'm Robert.
greet.apply(person, ["Hey"]); // Output: Hey, I'm Robert.
const boundGreet = greet.bind(person, "Hello");
boundGreet(); // Output: Hello, I'm Robert.
Arrow Functions and This Keyword
The behavior of the this keyword in arrow functions is one of the key distinctions between arrow functions and regular (non-arrow) functions in JavaScript.
No Separate this Binding: Arrow functions do not have their own this binding. Unlike regular functions, which bind
this based on how they are called, arrow functions inherit the this value from their surrounding code block (lexical scoping).
Lexical this Context: The value of this in an arrow function is determined by the value of this in the nearest enclosing non-arrow function scope. In other words, it retains the this value from the containing function or scope where the arrow function is defined.
Let's understand it with an example:
const obj = {
name: "Alex",
regularGreet: function () {
console.log(`Hello, I'm ${this.name}.`);
},
arrowGreet: () => {
console.log(`Hi, I'm ${this.name}.`);
},
};
obj.regularGreet(); // Output: Hello, I'm Alex.
obj.arrowGreet(); // Output: Hi, I'm undefined.
In the regularGreet()
method, the this
value is correctly bound to the obj
object because it's a regular function, and the context is determined by how the method is invoked.
However, in the arrowGreet()
method, the this
value is undefined
because arrow functions do not have their own this binding. Instead, they inherit this from the surrounding lexical scope, which in this case is the global scope. This is why this.name
inside the arrow function resolves to undefined
.
const person = {
name: "Robert",
greet: function () {
const innerFunction = () => {
console.log(`Hi, I'm ${this.name}.`);
};
innerFunction();
},
};
person.greet(); // Output: Hi, I'm Robert.
Here, the arrow function, innerFunction()
has a lexical this context and inheriting this
from the surrounding code block.
Arrow functions are particularly useful in scenarios where someone wants to maintain the this value from the enclosing scope. They're commonly used in callback functions, such as event handlers, to retain access to the parent context without the need for additional workarounds like binding.
The bind() Method
The bind() method in JavaScript is used to create a new function that, when invoked, has its this value set to a specific value, regardless of how the function is called. This can be especially useful to ensure a specific context for a function, such as when passing a function as a callback to an event handler.
Let's have a look at an example:
const obj = {
name: "Alex",
};
function greet() {
console.log(`Hello, I'm ${this.name}.`);
}
const boundGreet = greet.bind(obj);
boundGreet(); // Output: Hello, I'm Alex.
In the example above, the greet()
function is defined, and an object obj
is also defined. Using the bind()
method, a new function boundGreet()
is created. When boundGreet()
is invoked, it maintains the this
value of the obj
object, regardless of where it's called.
The bind() method also allows to provide additional arguments that will be pre-set when the bound function is invoked.
function say(message) {
console.log(`${message}, I'm ${this.name}.`);
}
const person = {
name: "Robert",
};
const boundSay = say.bind(person, "Hello");
boundSay(); // Output: Hello, I'm Robert.
Here, the bind()
method is used to create the boundSay()
function with the context of the person
object and the message "Hello" already set. When boundSay()
is called, it logs "Hello, I'm Robert."
The bind() method doesn't modify the original function instead creates a new function with the specified context and pre-set arguments.
Strict Mode vs. Non-Strict Mode
In ECMAScript 5, the introduction of strict mode ("use strict") brought some changes to the behavior of the "this" keyword:
In strict mode, if a function is called in the global scope, the default binding of "this" is not the global object. Instead, it is set to undefined. This avoids accidentally modifying global variables unintentionally.
If a function is called without any specified context, "this" remains undefined inside the function, preventing implicit global binding.
In strict mode, when using call, apply, or bind to set "this" explicitly, if the specified "this" value is a primitive (e.g. a string or a number), it will be converted to an object. This prevents errors that could occur when attempting to access properties on primitive values.
In non-strict mode, if a function is called without any specified context, the global object will be the default value of "this".
Understanding the intricacies of the "this" keyword and being aware of strict mode can help developers write more reliable and maintainable JavaScript code.
Conclusion
The "this" keyword in JavaScript is a dynamic element that plays a crucial role in determining the execution context of functions. Its behavior can vary based on how functions are invoked and whether you're in strict or non-strict mode. Throughout this blog, we've explored the different ways "this" is bound and how it impacts your code.
While "this" might be a source of confusion initially, continuous practice and exploration will lead to mastery. "this" empowers one to create more versatile and effective scripts.
Top comments (2)
Excelent article, it really covers all scenarios to clearly understand "this". Thanks!
Thank you!