What is a Method in JavaScript

As we've discussed before, it is possible for you to put a function inside an object, which is often referred to as a method.

javascript
1let user = {
2  firstName: "John",
3  lastName: "Doe",
4  printUserName: function () {
5    console.log("John Doe");
6  },
7};
8
9user.printUserName();

There is a shorter syntax for writing methods. It allows you to omit the function keyword.

javascript
1let user = {
2  firstName: "John",
3  lastName: "Doe",
4  printUserName() {
5    console.log("John Doe");
6  },
7};
8
9user.printUserName();
text
1John Doe

In this case, the printUserName() function will print the user's name to the console.

However, notice that in this example, we hardcoded the user name. That is not a good practice. If you want to change the user's name, you'll also have to change the corresponding printUserName() method.

A better way to code is to use the properties firstName and lastName inside the printUserName() method.

javascript
1let user = {
2  firstName: "John",
3  lastName: "Doe",
4  printUserName: function () {
5    console.log(`${user.firstName} ${user.lastName}`);
6  },
7};
8
9user.printUserName();
text
1John Doe

This way, when you change the firstName and lastName properties, the alterations will be reflected in the printUserName() method as well.

javascript
1let user = {
2  firstName: "John",
3  lastName: "Doe",
4  printUserName: function () {
5    console.log(`${user.firstName} ${user.lastName}`);
6  },
7};
8
9user.printUserName();
10
11user.firstName = "Jane";
12
13user.printUserName();
text
1John Doe
2Jane Doe

The "this" keyword

Methods are often used to deal with the properties stored in the same object. As a result, there exists a this keyword that allows you to access the current instance of the object from within the method.

javascript
1let user = {
2  firstName: "John",
3  lastName: "Doe",
4  printUserName: function () {
5    console.log(`${this.firstName} ${this.lastName}`);
6  },
7};
8
9user.printUserName();
text
1John Doe

It is recommended to use this instead of hardcoding the object reference like before, because sometimes you might need to reuse the same method in different objects.

javascript
1let user = { firstName: "John", lastName: "Doe" };
2let anotherUser = { firstName: "Jane", lastName: "Doe" };
3
4function printUserName() {
5  console.log(`${user.firstName} ${user.lastName}`);
6}
7
8// The same method will be placed in two objects, user and anotherUser
9user.printUserName = printUserName;
10anotherUser.printUserName = printUserName;
11
12user.printUserName(); // Returns "John Doe"
13anotherUser.printUserName(); // Also returns "John Doe"
text
1John Doe
2John Doe

In this example, anotherUser.printUserName() also returns John Doe because the printUserName() method is hardcoded to reference the user object.

When using the this keyword, it automatically references the current object that the method is in.

javascript
1let user = { firstName: "John", lastName: "Doe" };
2let anotherUser = { firstName: "Jane", lastName: "Doe" };
3
4function printUserName() {
5  console.log(`${this.firstName} ${this.lastName}`);
6}
7
8user.printUserName = printUserName;
9anotherUser.printUserName = printUserName;
10
11user.printUserName(); // Returns "John Doe"
12anotherUser.printUserName(); // Returns "Jane Doe"
text
1John Doe
2Jane Doe

"this" outside of an object

Take a closer look at our previous example, and notice that we defined the function printUserName() first, and then placed it inside objects, which means you are allowed to define a function with a this keyword outside of an object. This behavior is not allowed in most other programming languages.

javascript
1function printUserName() {
2  console.log(`${this.firstName} ${this.lastName}`); // <- This is not allowed in most other programming languages
3}
4
5user.printUserName = printUserName;
6anotherUser.printUserName = printUserName;

You can even call the function directly. When that happens, this will point to undefined in strict mode.

javascript
1"use strict";
2
3function example() {
4  console.log(this);
5}
6
7example();
text
1undefined
The strict mode is activated by placing "use strict" at the beginning of the .js file.
For a long time, JavaScript evolved without compatibility issues with older software. But that changed in 2009 when a new standard was released. Some of the existing features were modified, and to avoid issues with older software, these new features were turned off by default, but they can be activated with "use strict".
Nowadays, it is recommended to always use strict mode, which has become the modern-day standard.

In non-strict mode, this points to the global object, which is a built-in object that holds the variables that should be available everywhere. However, this is a historical error that has been fixed in the modern strict mode.

Trying to pull a property out of this when this is not bound to an object will cause an error.

javascript
1"use strict";
2
3function example() {
4  console.log(this.name);
5}
6
7example();
text
1/Users/. . ./index.js:4
2  console.log(this.name);
3                   ^
4
5TypeError: Cannot read properties of undefined (reading 'name')
6    at example (/Users/. . ./index.js:4:20)
7    . . .
8
9Node.js v21.6.0

Arrow functions have no "this"

Arrow functions are a special case when it comes to the this keyword. They do not have a this. They can only borrow a this keyword from the parent environment. For example:

javascript
1let user = {
2  firstName: "John",
3  lastName: "Doe",
4  printUserName: () => {
5    console.log(`${this.firstName} ${this.lastName}`);
6  },
7};
8
9user.printUserName();
text
1undefined undefined

Both this.firstName and this.lastName return undefined. But when we place the arrow function inside a "normal" function, it can borrow a this from that "normal" function.

javascript
1let user = {
2  firstName: "John",
3  lastName: "Doe",
4  printUserName: function () {
5    return () => {
6      console.log(`${this.firstName} ${this.lastName}`);
7    };
8  },
9};
10
11let getUserName = user.printUserName();
12
13getUserName();
text
1John Doe

This is not a mistake. It is a feature.

Arrow functions are intended to be smaller, simpler helper functions for other normal, standard functions. For example,

javascript
1let team = {
2  name: "MyTeam",
3  students: ["John", "Pete", "Alice"],
4  printStudents: function () {
5    this.students.forEach((student) => {
6      console.log(`${this.name}: ${student}`);
7    });
8  },
9};
10
11team.printStudents();
text
1MyTeam: John
2MyTeam: Pete
3MyTeam: Alice

In line 6, the this keyword is borrowed from the outer context, which is the printStudents() method.

If you rewrite the inner function into a standard function, this.name would return undefined, because this new this belongs to the inner function, which is not bound to an object.

javascript
1let team = {
2  name: "MyTeam",
3  students: ["John", "Pete", "Alice"],
4  printStudents: function () {
5    this.students.forEach(function (student) {
6      console.log(`${this.name}: ${student}`);
7    });
8  },
9};
10
11team.printStudents();
text
1undefined: John
2undefined: Pete
3undefined: Alice

"this" is context dependent

When a method is passed to a function as an argument, there is a common problem called losing this. For example,

javascript
1let user = {
2  name: "John",
3  hi() {
4    console.log(`Hello, ${this.name}!`);
5  },
6};
7
8user.hi();
text
1Hello, John!

As expected, the method hi() works perfectly, but if we have another function example():

javascript
1function example(func) {
2  func();
3}

This particular function requires a function parameter named func(), which will be executed when example() is called.

When we pass hi() to example(), this happens:

javascript
1function example(func) {
2  func();
3}
4
5example(user.hi);
text
1Hello, undefined!

This is because when we call hi() directly, the function is executed directly within the context of user, and this will refer to the object user.

But when you pass hi() to the example() function as an argument, essentially example() becomes this:

javascript
1function example() {
2  function hi() {
3    console.log(`Hello, ${this.name}!`);
4  }
5
6  hi();
7}
8
9example();
text
1Hello, undefined!

And the this keyword points to the context of example(), which is undefined, so this.name will return undefined.

This is caused by the fact that this is context dependent in JavaScript. It is not always bound to an object.

But passing a method to another function as an argument is something we often do.

For example, sometimes, instead of executing a function immediately, you might need to schedule it until a later time. In this case, you'll need to pass the method to the setTimeout() function, which is a built-in function scheduler in JavaScript.

But if you pass the method to setTimeout(), the same issue will occur:

javascript
1setTimeout(user.hi, 1000);

This will delay the execution of user.hi() until 1000ms later.

text
1Hello, undefined!

Using a function wrapper

To fix this problem, you can either pass the method with a function wrapper:

javascript
1function example(func) {
2  func();
3}
4
5example(function () {
6  user.hi();
7});
text
1Hello, John!

In this case, the function that is passed as the argument is:

javascript
1function () {
2  user.hi();
3}

Notice that this time, the inner method hi() is not passed as an argument. It will be executed directly, because we are using user.hi(), not user.hi. This means hi() has the context user when it is executed, and this time, this.name will return a proper result.

A shorter and more elegant syntax would be using the arrow function:

javascript
1example(() => {
2  user.hi();
3});
text
1Hello, John!

Using bind()

Another solution would be using the bind() function.

javascript
1let user = {
2  name: "John",
3  hi() {
4    console.log(`Hello, ${this.name}!`);
5  },
6};
7
8function example(func) {
9  func();
10}
11
12example(user.hi.bind(user));
text
1Hello, John!

Instead of passing user.hi directly, you may use the bind() function to manually bind the context of hi() to the user object.