Marcell Ciszek Druzynski

This keyword in JavaScript

Understanding the `this` keyword in JavaScript

June 27, 2024

The this keyword in JavaScript can be quite confusing. Unlike languages like Java, where this works in a more straightforward way, JavaScript requires us to understand the context in which this is used, as it doesn't always point to the object we expect.

Let's break down the this keyword to get a clear understanding of how it works. Knowing how this behaves in different scenarios is a crucial skill for any JavaScript developer.

There are five main rules that govern the behavior of this in JavaScript:

  1. Implicit Binding
  2. Explicit Binding
  3. New Binding
  4. Lexical Binding
  5. Window Binding

1. Implicit Binding

The implicit binding is perhaps the most common scenario we will encounter when writing our JavaScript applications. The simple rule here is to look to the left of the dot when the function is invoked. Whenever we see a dot (.), we can look to the left to find the object that this is referring to.

When a function is called as a method of an object, this refers to the object that the method is called on.

1let person = {
2 name: "John",
3 greet() {
4 console.log(`Hello, my name is ${this.name}`);
5 },
6};
7
8person.greet(); // 'Hello, my name is John'
1let person = {
2 name: "John",
3 greet() {
4 console.log(`Hello, my name is ${this.name}`);
5 },
6};
7
8person.greet(); // 'Hello, my name is John'

In this example, this inside greet refers to the person object because greet is called as a method of person.

2. Explicit Binding

Explicit binding means we tell the JavaScript engine exactly what this should refer to in a given context.

Consider this function using the this keyword:

1function greet() {
2 console.log(`Hi! my name is ${this.name}`);
3}
4
5greet(); // Hi! my name is
1function greet() {
2 console.log(`Hi! my name is ${this.name}`);
3}
4
5greet(); // Hi! my name is

Here, this is not set, so the output is incomplete. We can explicitly set this using call, apply, or bind.

1function greet() {
2 console.log(`Hello, my name is ${this.name}`);
3}
4
5const person = {name: "Bobby"};
6
7greet.call(person); // 'Hello, my name is Bobby'
1function greet() {
2 console.log(`Hello, my name is ${this.name}`);
3}
4
5const person = {name: "Bobby"};
6
7greet.call(person); // 'Hello, my name is Bobby'

In this example, we use the call() method to set this to the person object, making this.name refer to person.name.

The bind() and apply() methods are used similarly but in different scenarios. For instance, bind() is useful for maintaining the correct this context when working with functions in JavaScript.

1let user = {
2 name: "Alice",
3 greet: function (greeting) {
4 console.log(`${greeting}, ${this.name}!`);
5 },
6};
7
8// Works as expected
9user.greet("Hello"); // Output: Hello, Alice!
10
11// Problem: 'this' context is lost
12let greetFunction = user.greet;
13greetFunction("Hi"); // Output: Hi, undefined!
14
15// Solution: Use bind to preserve 'this' context
16let boundGreet = user.greet.bind(user);
17boundGreet("Welcome"); // Output: Welcome, Alice!
18
19// Using bind with setTimeout
20setTimeout(user.greet.bind(user, "Good morning"), 1000);
21// Output after 1 second: Good morning, Alice!
1let user = {
2 name: "Alice",
3 greet: function (greeting) {
4 console.log(`${greeting}, ${this.name}!`);
5 },
6};
7
8// Works as expected
9user.greet("Hello"); // Output: Hello, Alice!
10
11// Problem: 'this' context is lost
12let greetFunction = user.greet;
13greetFunction("Hi"); // Output: Hi, undefined!
14
15// Solution: Use bind to preserve 'this' context
16let boundGreet = user.greet.bind(user);
17boundGreet("Welcome"); // Output: Welcome, Alice!
18
19// Using bind with setTimeout
20setTimeout(user.greet.bind(user, "Good morning"), 1000);
21// Output after 1 second: Good morning, Alice!
  1. We define a user object with a name and a greet method.
  2. Calling greet directly on the user object works as expected.
  3. Assigning greet to a variable and calling it loses the this context, resulting in undefined.
  4. Using .bind(user), we create a new function with this bound to user.
  5. We show how .bind() preserves the correct this context with setTimeout().

This demonstrates how .bind() helps maintain the correct this context, especially when dealing with callbacks or event handlers. Without .bind(), functions saved in variables lose their this reference.

Using the .apply() method is similar to the call() but it takes arguments as a list.

1function greet(greeting, punctuation) {
2 console.log(`${greeting}, my name is ${this.name}${punctuation}`);
3}
4
5let person = {name: "Bobby"};
6
7greet.apply(person, ["Hello", "!"]); // 'Hello, my name is Bobby!'
1function greet(greeting, punctuation) {
2 console.log(`${greeting}, my name is ${this.name}${punctuation}`);
3}
4
5let person = {name: "Bobby"};
6
7greet.apply(person, ["Hello", "!"]); // 'Hello, my name is Bobby!'

You can see that we take in two arguments greeting and punctuation but when calling the function we can simply use the the .apply(), where the first argument is the this and the second argument is the list with the provided arguments.

By understanding and using call(), apply(), and bind(), we can explicitly set the this context in JavaScript, ensuring our functions behave as expected in different situations.

One tip that I like to use to remind myself of the difference between call() and apply() is that call() is for comma-separated arguments, while apply() is for array-like arguments where a in apply() stands for array.

3. New Binding

When a function is used as a constructor with the new keyword, this refers to the newly created instance.

1function Person(name) {
2 this.name = name;
3}
4
5const person1 = new Person("Alice");
6console.log(person1.name); // 'Alice'
1function Person(name) {
2 this.name = name;
3}
4
5const person1 = new Person("Alice");
6console.log(person1.name); // 'Alice'

Here we create a new instance of the Person constructor function using the new keyword. Inside the Person function, this refers to the new instance being created, allowing us to set properties on it.

When we use the new keyword, JavaScript creates a new object and sets this to that object. This is known as new binding.

4. Lexical Binding

Arrow functions use lexical scoping for this, which means this keyword is determined by the surrounding context. We often take advantage of using the arrow functions to avoid the confusion of the this keyword. Since arrow functions don't have their own this context, they inherit the this value from the enclosing function.

1const obj = {
2 name: "Bob",
3 greet: () => {
4 console.log(`Hello, my name is ${this.name}`);
5 },
6};
7
8obj.greet(); // 'Hello, my name is undefined'
1const obj = {
2 name: "Bob",
3 greet: () => {
4 console.log(`Hello, my name is ${this.name}`);
5 },
6};
7
8obj.greet(); // 'Hello, my name is undefined'

Here we can see that we get undefined because the arrow function doesn't have its own this context and it inherits the this value from the global context.

But to really see where arrow functions shine, let's look at the following example:

1const obj = {
2 name: "Bob",
3 greet: function () {
4 setTimeout(() => {
5 console.log(`Hello, my name is ${this.name}`);
6 }, 1000);
7 },
8};
9
10obj.greet(); // 'Hello, my name is Bob'
1const obj = {
2 name: "Bob",
3 greet: function () {
4 setTimeout(() => {
5 console.log(`Hello, my name is ${this.name}`);
6 }, 1000);
7 },
8};
9
10obj.greet(); // 'Hello, my name is Bob'

Now we can see that the arrow function inside the setTimeout function is able to access the this value from the greet function. Since the greet function is a regular function and has its own this context, the arrow function inside it can access the this value from the greet function.

This is a common pattern in JavaScript, where we use arrow functions to maintain the correct this context in nested functions.

5. Window Binding

If none of the above rules apply, this defaults to the global object, which is window in the browser and global in Node.js. What I mean by none of the above rules apply is when we have a function that is not a method of an object, not a constructor function, and not an arrow function.

1function greet() {
2 console.log(`Hello, my name is ${this.name}`);
3}
4
5greet(); // 'Hello, my name is undefined'
1function greet() {
2 console.log(`Hello, my name is ${this.name}`);
3}
4
5greet(); // 'Hello, my name is undefined'

As we know the previous rules it prints out nothing unexpected because the this value is not set and it defaults to the global object.

But if we are in strict mode, the this value will be undefined instead of the global object.

1function greet() {
2 "use strict";
3 console.log(`Hello, my name is ${this.name}`);
4}
5
6greet(); // TypeError: Cannot read property 'name' of undefined
1function greet() {
2 "use strict";
3 console.log(`Hello, my name is ${this.name}`);
4}
5
6greet(); // TypeError: Cannot read property 'name' of undefined

In strict mode, this is undefined when the function is not called as a method of an object.

But if we set the window object to the this value, we can access the name property.

1function greet() {
2 console.log(`Hello, my name is ${this.name}`);
3}
4
5window.name = "Alice";
6
7greet(); // 'Hello, my name is Alice'
1function greet() {
2 console.log(`Hello, my name is ${this.name}`);
3}
4
5window.name = "Alice";
6
7greet(); // 'Hello, my name is Alice'

Now we can see that we are able to access the name property from the window object. Since the greeet() function is not a method of an object, it defaults to the global object where looks at the window object.

Conclusion

Understanding the this keyword in JavaScript is crucial for writing clean and maintainable code. By following the rules of implicit, explicit, new, lexical, and window binding, we can ensure that this behaves as expected in different scenarios.

Remember to always consider the context in which this is used and apply the appropriate binding to avoid unexpected behavior in your JavaScript applications.

I hope this article has helped you understand the this keyword in JavaScript better.

Happy coding!

Referemces