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.
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.
1let user = {
2 firstName: "John",
3 lastName: "Doe",
4 printUserName() {
5 console.log("John Doe");
6 },
7};
8
9user.printUserName();
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.
1let user = {
2 firstName: "John",
3 lastName: "Doe",
4 printUserName: function () {
5 console.log(`${user.firstName} ${user.lastName}`);
6 },
7};
8
9user.printUserName();
1John Doe
This way, when you change the firstName
and lastName
properties, the alterations will be reflected in the printUserName()
method as well.
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();
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.
1let user = {
2 firstName: "John",
3 lastName: "Doe",
4 printUserName: function () {
5 console.log(`${this.firstName} ${this.lastName}`);
6 },
7};
8
9user.printUserName();
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.
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"
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.
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"
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.
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.
1"use strict";
2
3function example() {
4 console.log(this);
5}
6
7example();
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.
1"use strict";
2
3function example() {
4 console.log(this.name);
5}
6
7example();
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:
1let user = {
2 firstName: "John",
3 lastName: "Doe",
4 printUserName: () => {
5 console.log(`${this.firstName} ${this.lastName}`);
6 },
7};
8
9user.printUserName();
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.
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();
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,
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();
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.
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();
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,
1let user = {
2 name: "John",
3 hi() {
4 console.log(`Hello, ${this.name}!`);
5 },
6};
7
8user.hi();
1Hello, John!
As expected, the method hi()
works perfectly, but if we have another function example()
:
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:
1function example(func) {
2 func();
3}
4
5example(user.hi);
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:
1function example() {
2 function hi() {
3 console.log(`Hello, ${this.name}!`);
4 }
5
6 hi();
7}
8
9example();
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:
1setTimeout(user.hi, 1000);
This will delay the execution of user.hi()
until 1000ms later.
1Hello, undefined!
Using a function wrapper
To fix this problem, you can either pass the method with a function wrapper:
1function example(func) {
2 func();
3}
4
5example(function () {
6 user.hi();
7});
1Hello, John!
In this case, the function that is passed as the argument is:
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:
1example(() => {
2 user.hi();
3});
1Hello, John!
Using bind()
Another solution would be using the bind()
function.
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));
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.