DEV Community

Bo Louie
Bo Louie

Posted on • Edited on

.this keyword in JavaScript

My goal in writing this post is to promote a better understanding of this keyword by sharing aspects of this that I discovered while learning JavaScript. Skip the specifics and jump to the Cliffs Notes if you'd prefer.

Contents

Why this exists

In JavaScript, the this keyword is a means to refer to the object or context that the code is currently executing within. In object-oriented programming, this keyword makes it possible to reference the current object in callback functions and event handlers, allowing access to and manipulation of the current object's properties and methods. Additionally, this enables programmers to build methods that preserve the context of the object they were called on while being reused at different instances.

The this keyword can also be used to develop closures, inner functions that have access to the variables and information in their containing functions. It serves as a method to refer to the object whose property the function is, or to the object which is carrying out the current function.

How this works

In JavaScript, the value of this keyword within a function is typically determined by how the function is invoked, not by the location of where the function was declared. Except for arrow functions, which have a different syntax and behaviour than regular functions.

The this keyword can be passed to a function as an argument, or it can be implicitly set depending on the execution context.

The call(), apply(), and bind() methods can also be used to explicitly set the value of this when a function is called. Each method can be used to call a function and modify how it is invoked, but they work in slightly different ways.

A few of the common factors that dictate how the this keyword is determined in different contexts will be discussed next. Then I will discuss the function methods in greater detail.

What defines this

The context of the code that is presently running determines what the this keyword represents. The call-site (the place in your code where a function is called) of the function is what determines the default behaviour of the this keyword.

The following are often the most important factors to consider when defining this keyword as a JavaScript programmer:

1st Factor

In the global scope, by default, this refers to the global object, also known as the window in the browser or global in Node.js.

2nd Factor

In a regular function call, this refers to the global object.

function myFunc() {
  console.log(this);
}
myFunc(); // logs the global object (window or global)
Enter fullscreen mode Exit fullscreen mode

3rd Factor

In a method call, this refers to the object on which the method is defined.

let object = {
  myMethod: function () {
    console.log(this);
  }
};
object.myMethod(); // logs object
Enter fullscreen mode Exit fullscreen mode

4th Factor

In a constructor function, this refers to the new object being constructed.

function MyConstructor() {
  this.myProp = 'hello';
  console.log(this);
}
let myObj = new MyConstructor(); // logs the new object, with a myProp property
Enter fullscreen mode Exit fullscreen mode

5th Factor

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

// First Object
let object = {
  myProp: 'Netflix',
  getMethod: function() {
    return this.myProp;
  },
};
// Second Object
let otherObject = {
  myProp: 'Disney'
};
object.getMethod(); // logs 'Netflix'
otherObject.getMethod (); // logs 'Disney'
console.log(object.getMethod()); // logs 'Netflix'
otherObject.getMethod = object.getMethod // logs f() { return this.myProp }
Enter fullscreen mode Exit fullscreen mode

In the above code, the first object is called object that has a myProp property with a value of Netflix, and a getMethod method that returns the value of the myProp property using this.myProp.

When the object.getMethod() method is called, this inside the method refers to the object on which the method is defined, so this.myProp evaluates to object.myProp which is Netflix. Alternatively, getMethod() can be called on the second object called otherObject to return it's myProp property as Disney.

Clear as mud, right!?

The this keyword is dynamic and its value is determined implicitly by the execution context of how the function is called.

However, for arrow functions, the value of this is not dynamic, it's determined by the scope where the arrow function was defined. Instead of having its own this keyword, the arrow function must access this from the parent scope.

I plan on posting about arrow functions soon to better understand their atypical nature, so stay tuned!

Function methods make JavaScript more predictable by enabling the explicit definition of the value of the this keyword. The call(), apply(), and bind() methods allow you to explicitly specify the value of this when a function is called. These methods modify how functions get invoked, but they work in slightly different ways.

The call() and apply() methods invoke the function immediately, while the bind() method returns a new function with this bound to a specified value. This new function can be called later.

Let's take a closer look!

Function Methods

The following Booking Flights code snippet is from Jonas Schmedtmann's The Complete JavaScript Course 2023, Section 10: A Closer Look at Functions, available on Udemy. It refers to booking flights with the second-largest European airline group and has one main object that I'll now discuss in greater detail on function methods.

// Booking Flights
// Main Object
const lufthansa = {
    airline: 'Lufthansa',
    iataCode: 'LH',
    bookings: [],
    // book: function() {}
    book(flightNum, name) {
      console.log(
        `${name} booked a seat on ${this.airline} flight ${this.iataCode}${flightNum}`
      );
      this.bookings.push({ flight: `${this.iataCode}${flightNum}`, name });
    },
};
lufthansa.book(239, 'Jonas Schmedtmann');
lufthansa.book(635, 'John Smith');

const eurowings = {
    airline: 'Eurowings',
    iataCode: 'EW',
    bookings: [],
};
const book = lufthansa.book
book(23, 'Sarah Williams') // ERROR

const swiss = {
    airlines: 'Swiss Air Lines',
    iataCode: 'Lx',
    bookings: [],
};

Enter fullscreen mode Exit fullscreen mode

If you run this code, you will get the following output:

Jonas Schmedtmann booked a seat on Lufthansa flight LH239
John Smith booked a seat on Lufthansa flight LH635
Enter fullscreen mode Exit fullscreen mode

The book method of the lufthansa object is called with the correct value for this (i.e. this refers to the lufthansa object), so the bookings are added to the lufthansa object's bookings array.

However, when the book function is assigned to the book variable and then called with the book(23, 'Sarah Williams'), the value of this is determined by the way in which the function is called, and in this case it is called as a standalone function, not as a method of an object.

As a result, the value of this is the global object (in a web browser, this is the window object). This means that the airline and iataCode properties of this object will not be defined, and you will see an error message
logged to the console similar to the following:

Uncaught TypeError: Cannot read property 'airline' of undefined
Enter fullscreen mode Exit fullscreen mode

To fix this error, we need to use the call() method on the book function to properly set the value of this keyword to the eurowings object. As a result, we get the booking added to the eurowings object's bookings array.

Call Method

Using the call() method effectively specifies the value of this when calling the book function, like below:

// Call Method
book.call(eurowings, 23, 'Sarah Williams');
console.log(eurowings); // logs Sarah Williams booked a seat on Eurowings flight EW23

book.call(lufthansa, 239, 'Mary Cooper');
console.log(lufthansa); // logs Mary Cooper booked a seat on Lufthansa flight LH239 
Enter fullscreen mode Exit fullscreen mode

In the above code, the book function is defined as a method of the lufthansa object, but the call() method on the book function can set the value of this to the eurowings object as well.

The call() method allows you to invoke a function and specify the value of this as well as any arguments that should be passed to the function when it is called, regardless of where the function is defined.

The value of the this keyword is determined dynamically based on how a function is invoked. It is also possible to explicitly set the value of the this keyword within a function by using the call() method.

The call() method allows you to specify the value of this explicitly, rather than relying on the default behavior of the this keyword. This can be useful when you want to reuse a function that was defined in one context, but use it in a different context where the value of this needs to be different.

Apply Method

The apply() method allows you to call a function with a specified value for this, and an array of arguments. This can be useful when calling a function with a variable number of arguments.

For example, where flightData is set to an array:

// Apply Method
const swiss = {
  airline: 'Swiss',
  iataCode: 'LX',
  bookings: [],
  book: function(flightNum, name) {
    console.log(`${name} booked a seat on ${this.airline} flight${this.iataCode}${flightNum}`
  );
  this.bookings.push({ flight: `${this.iataCode}${flightNum}`, name });
},
};
// Array as an argument
const.flightData = [583, 'Carlos Santana'];
book.apply(swiss, flightData);
console.log(swiss);
Enter fullscreen mode Exit fullscreen mode

The following is logged to the console:

Carlos Santana booked a seat on Swiss flight LX583 { airline: 'Swiss', iataCode: 'LX', bookings: [{ flight: 'LX583', name: 'Carlos Santana' }], book: [Function]}
Enter fullscreen mode Exit fullscreen mode

In the above code, the apply() method is called on the book function, and it is passed two arguments: the value of this (the swiss object) and an array of arguments (flightData). The booking is then added to the swiss objects bookings array.

The apply() method is less commonly used in modern JavaScript because there is a better way to accomplish the same task.

I'm referring to the spread operator (...), which allows you to spread an array of values into individual arguments when calling a function. This can be a more concise and convenient way of calling a function with an array of arguments in comparison to the apply() method.

For example, instead of using the apply() method as shown in the previous example, you could use the spread operator like so:

// Spread Operator
const flightData = [583, 'Carlos Santana'];
book.call(swiss, ...flightData);
console.log(swiss); // logs Carlos Santana booked a seat on Swiss flight LX583
Enter fullscreen mode Exit fullscreen mode

Invoking the book function, by spreading the flightData into separate arguments with the spread operator effectively adds Carlos Santana to the swiss objects bookings array!

Because the spread operator is a relatively new addition to JavaScript, it may not be supported in all environments. It is, however, widely supported in modern browsers and JavaScript runtimes, making it a convenient and concise method of calling a function with an array of arguments.

Bind Method

The bind() method creates a new function that, when called, has its this keyword set to the provided value, that you specify. This new function can be called later.

By calling the bind() method on the book function and pass different values for this, you can create new functions with fixed values for this. For example:

// Bind Method
const bookEW = book.bind(eurowings);
const bookLH = book.bind(lufthansa);
const bookLX = book.bind(swiss);
Enter fullscreen mode Exit fullscreen mode

The bind() method returns a new function with the value of this set to the specified value.

In the above example, the bind() method is called on the book function three times, each time with a different value for this, resulting in three new functions:

-bookEW, which has this fixed to the eurowings object
-bookLH, which has this fixed to the lufthansa object
-bookLX, which has this fixed to the swiss object.

These functions are identical to the book function, except the value of this is fixed to the value that was passed to bind(). You can then call these functions at a later time, and the value of this will be fixed to the value that you specified when you created the function with bind().

The following gets logged to the console:

bookEW(23, 'Steven Williams'); // Adds a booking to the eurowings object's bookings array
bookLH(654, 'Alice Smith'); // Adds a booking to the lufthansa object's booking array
bookLX(583, 'Bob Johnson'); // Adds a booking to the swiss object's bookings array
Enter fullscreen mode Exit fullscreen mode

This can be useful when you want to reuse a function in different contexts, but you want the value of this to be different in each context. The bind method allows you to create new functions with fixed values for this, which can be called at a later time. For example:

// Calling a single function for additional flight bookings
const bookEW23 = book.bind(eurowings, 23);
bookEW23('Jonas Schmedtmann');
bookEW23('Martha Stewart');
Enter fullscreen mode Exit fullscreen mode

The bind() method in this example, was used to create new functions with the this value set to either lufthansa or eurowings, depending on which object is passed as an argument to bind().

The bookEW function is created with the this value set to eurowings, so when it is called with (23, 'Steven Williams'), it logs the message: "Steven Williams booked a seat on Eurowings flight EW23".

The bookEW23 function is also created with the this value set to eurowings, but it is already partially applied with the first argument set to 23. When it is called with a single argument 'Jonas Schmedtmann', it logs the message: "Jonas Schmedtmann booked a seat on Eurowings flight EW23".

When you create a new function using the bind() method and partially apply some of its arguments, the resulting function will only have placeholders for the remaining arguments.

Partial application refers to the process of creating a new function by fixing some of the arguments of an existing function. The new function is then called with the remaining arguments.

For example, in the case of the bookEW23 function, the flightNum argument is already set to 23, so when you call bookEW23 function, you only need to provide the name argument. The resulting function call will look like this: bookEW23(name).

You can also use the bind() method to set the value of multiple arguments at once. For example:

const bookEW23Martha = book.bind(eurowings, 23, 'Martha');
bookEW23Martha(); // logs Martha booked a seat on Eurowings flight EW23
Enter fullscreen mode Exit fullscreen mode

Functional programmers often use partial application to code reusable, configurable functions that are simple to adapt for particular use cases. The bind() method is one way to implement partial application in JavaScript, but there are other ways as well, such as using higher-order functions or creating a function that returns a partially applied function.

Cliffs Notes

Back to Contents

The call(), apply(), and bind() methods are all ways to specify the value of this keyword when calling a function.

The call() method allows you to call a function with a specified value for this, and pass any number of arguments to the function as separate parameters.

The apply() method allows you to call a function with a specified value for this, and pass any number of arguments to the function as an array.

The bind() method allows you to create a new function with a specified value for this, and optionally set some or all of the arguments to the new function. The new function can then be called at a later time, with the remaining arguments passed when the function is called.

These methods can be useful when you want to reuse a function in different contexts, but you want the value of this or the arguments to be different in each context. They allow you to specify the value of this and the arguments explicitly, rather than relying on the default behavior of the this keyword and the arguments.

Thanks for reading and hopefully this was helpful! I welcome constructive feedback, and if you find an error, please kindly let me know. Disclaimer: This post was produced with the help of artificial intelligence.

Top comments (0)