The class notation allows you to create an inheritance system as well. You can extend a class to another class using the extends
keyword. For example, here is a User
class:
1class User {
2 constructor(name) {
3 this.name = name;
4
5 this.firstName;
6 this.lastName;
7 }
8
9 get name() {
10 return `${this.firstName} ${this.lastName}`;
11 }
12
13 set name(name) {
14 [this.firstName, this.lastName] = name.split(" ");
15 }
16}
17
18let user = new User("John Doe");
19
20console.log(user);
1User { firstName: 'John', lastName: 'Doe' }
And then, you might need a Profile
class that extends to User
:
1class Profile extends User {
2 create() {
3 console.log("Profile created.");
4 }
5}
6
7let profile = new Profile("John Doe");
8
9console.log(Object.getPrototypeOf(profile));
1User {}
As you can see, the prototype of profile
is set to be User
. You can access the create()
method from profile
, but not user
:
1profile.create(); // -> Profile created.
2user.create(); // -> Error
1Profile created.
2/Users/. . ./index.js:29
3user.create();
4 ^
5
6TypeError: user.created is not a function
7 at Object.<anonymous> (/Users/. . ./index.js:29:6)
8 . . .
9
10Node.js v21.6.0
Technically, you don't need to know how this works internally, but if you are interested, when you use extends
to create a class, a prototype
property will be set to that class.
Recall that classes are just constructor functions, and functions are a special type of objects, which means you can assign properties to functions, as explained in the linked article above.
For example,
1class Profile extends User {
2 create() {
3 console.log("Profile created.");
4 }
5}
6
7console.log(Profile.prototype);
When an object is constructed with the new
keyword, the class that prototype
points to will automatically become the object's prototype, which is User
in this case.
Overriding a method
Consider this scenario, in the User
class, you have a create()
method in charge of creating a new user in the database. Of course, we haven't yet discussed how to work with databases using JavaScript, so for now, we'll use console.log()
as a simple replacement, just for demonstration purposes.
1class User {
2 constructor(name) {
3 this.name = name;
4
5 this.firstName;
6 this.lastName;
7 }
8
9 get name() {
10 return `${this.firstName} ${this.lastName}`;
11 }
12
13 set name(name) {
14 [this.firstName, this.lastName] = name.split(" ");
15 }
16
17 create() {
18 console.log("User created.");
19 }
20}
And in the Profile
class, you need another create()
method to add more user details, such as the bio, profile picture, and so on.
1class Profile extends User {
2 create() {
3 console.log("Profile created.");
4 }
5}
JavaScript does allow you to create the same method in both the parent and child classes, but by default, the one in the parent class will be overridden. In this case, User.create()
will be overridden by Profile.create()
.
1let profile = new Profile("John Doe");
2
3profile.create();
1Profile created.
However, in this example, you might not want to replace the parent method completely. Because in a real project, the new user row must be created in the database, before you can add extra information to it.
The former would be the job for User.create()
, and the latter would be done by Profile.create()
. So, a better solution would be letting the Profile.create()
build on top of User.create()
instead of replacing it completely.
You can access the methods in the parent class using the keyword super
. For example:
1class Profile extends User {
2 create() {
3 super.create();
4 console.log("Profile created.");
5 }
6}
7
8let profile = new Profile("John Doe");
9
10profile.create();
1User created.
2Profile created.
In this case, super.create()
will execute the User.create()
method, and then continue to the rest of the Profile.create()
method.
Overriding the constructor
You may have noticed that the inheritance system we just went over is missing a very important feature. What if you need to add more properties in the child class? At first, you might want to add a constructor()
in the child class like this:
1class User {
2 . . .
3}
4
5class Profile extends User {
6 constructor(email, name) {
7 this.email = email;
8 this.name = name;
9
10 . . .
11 }
12}
But things are a bit more complex than that. When you execute this constructor, JavaScript returns an error.
1/Users/. . ./index.js:24
2 this.email = email;
3 ^
4
5ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
6 at new Profile (/Users/. . ./index.js:24:5)
7 . . .
8
9Node.js v21.6.0
It is telling you that you must call super
before accessing this
in the child constructor, meaning this is what you have to do:
1class Profile extends User {
2 constructor(name, email) {
3 super(name);
4 this.email = email;
5 }
6
7 create() {
8 super.create();
9 console.log("Profile created.");
10 }
11}
12
13let profile = new Profile("John Doe", "me@example.com");
14
15console.log(profile);
1Profile { firstName: 'John', lastName: 'Doe', email: 'me@example.com' }
super()
executes the constructor()
in the User
class, and then continue to the rest of Profile.constructor()
.
In summary, in JavaScript classes, super()
refers to the constructor()
of the parent class, and super.create()
refers to the create()
method inside the parent class. And when extending a constructor()
, always make sure super()
is called before using the this
keyword.
The instanceof operator
JavaScript also allows you to check if an object is created by a class using the instanceof
operator. For instance, in the following example, user
is created by executing new User()
, so it is an instance of the class User
.
1class User {
2 constructor(name) {
3 this.name = name;
4
5 this.firstName;
6 this.lastName;
7 }
8
9 get name() {
10 return `${this.firstName} ${this.lastName}`;
11 }
12
13 set name(name) {
14 [this.firstName, this.lastName] = name.split(" ");
15 }
16
17 create() {
18 console.log("User created.");
19 }
20}
21
22let user = new User("John Doe");
23
24console.log(user instanceof User);
1true
If a class extends to another class, for example, Profile
extends to User
,
1class User {
2 constructor(name) {
3 this.name = name;
4
5 this.firstName;
6 this.lastName;
7 }
8
9 get name() {
10 return `${this.firstName} ${this.lastName}`;
11 }
12
13 set name(name) {
14 [this.firstName, this.lastName] = name.split(" ");
15 }
16
17 create() {
18 console.log("User created.");
19 }
20}
21
22class Profile extends User {
23 create() {
24 super.create();
25 console.log("Profile created.");
26 }
27}
The objects created by Profile
are also considered an instance of User
.
1let profile = new Profile("John Doe");
2
3console.log(profile instanceof Profile);
4console.log(profile instanceof User);
1true
2true