JavaScript is an object-oriented language. However, it’s different from many other OO languages in that it uses prototype-based inheritance instead of class-based inheritance.
Prototype-based inheritance means that objects inherit items from its prototype. A prototype is just another object, which can be inherited by other objects.
This is different from class-based inheritance in that classes are templates for creating new objects. Classes can inherit from other classes to reuse code from the class it’s inheriting from.
Old Syntax of Inheritance
Constructor Functions
Before ES6, we only have constructor functions to serve as templates to create new objects which are instances of the constructor.
For example, we can define a constructor function as follows:
function Person(name, age) {
this.name = name;
this.age = age;
}
Then we can create a new instance of Person
by writing:
let person = new Person('Joe', 10);
To inherit items from other constructor functions in a constructor function, we have to call the parent constructor function that we want to inherit from with the call
method, and then set our constructor’s prototype’s constructor
property to the parent constructor function that we want to inherit from.
For example, if we want a Employee
constructor function to inherit the properties of the Person
constructor, we can write:
function Person(name, age) {
this.name = name;
this.age = age;
}
function Employee(name, age, title) {
this.title = title;
Person.call(this, name, age);
this.__proto__.constructor = Person;
}
let employee = new Employee('Joe', 20, 'waiter');
console.log(employee);
The call
method takes the value of this
we want to set, and the rest are arguments we pass into the function that the call
method is called on.
If we look at the __proto__
property of the employee
object, which has the prototype for it, we should get that __proto__.constructor
of it should be the Person
constructor like we set it to.
The properties and the values of the employee
object should be what we passed into the Employee
constructor when we called it.
Object.create()
The Object.create()
method is another way to inherit from a prototype when we create an object.
The argument that it takes is the prototype object that we want the object returned from it to inherit from.
For example, we can use Object.create
to create an object with a prototype as follows:
const person = {
name: 'Joe',
age: 20
}
let employee = Object.create(person);
employee.title = 'waiter';
console.log(employee);
If we look at the employee
object, we’ll see that the __proto__
property will have the age
and name
properties set with values.
Setting the proto Property Directly
Setting the __proto__
property directly has been officially supported since ES6 and it’s an undocumented way to set the prototype of an object in various browsers before it like Firefox.
We can set an object to the __proto__
property directly, by writing something like:
const person = {
name: 'Joe',
age: 20
}
let employee = {
title: 'waiter'
};
employee.__proto__ = person;
console.log(employee);
We should get the exact structure of the properties and values as we did when we created an object with the Object.create()
method.
One thing we have to be careful about is that we don’t want to accidentally set it if we don’t want to change an object’s prototype. This may happen if we use JavaScript objects as maps. With ES6, we can use the Map
object for this purpose.
Object.defineProperty
We can also use the defineProperty
method to set the prototype of an object. For example, we can write:
const person = {
name: 'Joe',
age: 20
}
let employee = {
title: 'waiter'
};
Object.defineProperty(employee, '__proto__', {
value: person
});
console.log(employee.__proto__);
When we log the value of employee.__proto__
, we get back the person
object.
Note that the prototype is in the value
property of the 3rd argument of the defineProperty
method call.
Photo by Chiara Daneluzzi on Unsplash
New Class Syntax
With the release of ES6, the new class syntax is introduced. On the surface, it looks like we have class-based inheritance, but underneath the surface, it’s exactly the same as before.
The class syntax is the same as constructor functions. For example,
function Person(name, age) {
this.name = name;
this.age = age;
}
is the same as:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
We can instantiate both by writing:
const person = new Person('Joe', 10);
And we get the same object when we inspect its properties.
The class syntax also creates a clear and convenient way to do inheritance that looks like a traditional class-based inheritance.
We can create a super-class and a child class can inherit from it with the extends
keyword. For example, we can write:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
class Employee extends Person {
constructor(name, age, title) {
super(name, age);
this.title = title;
}
}
const employee = new Employee('Joe', 20, 'waiter');
In the code above, we have the extends
keyword to indicate which class Employee
inherits from. We can only inherit from one class.
The super
method is called to call the parent constructor and set its’ properties. In this case, calling super
will call the constructor
method in the Person
class.
this
refers to the class that it’s inside in each class.
This is exactly the same as what we did before:
function Person(name, age) {
this.name = name;
this.age = age;
}
function Employee(name, age, title) {
this.title = title;
Person.call(this, name, age);
this.__proto__.constructor = Person;
}
let employee = new Employee('Joe', 20, 'waiter');
console.log(employee);
The only thing is that when we inspect the employee
object, we get that the __proto__.constructor
property shows class
instead of function
.
The class syntax makes inheritance much more clear than before. It’s much needed syntactic sugar for the prototypical inheritance model that’s in JavaScript since the beginning.
Also, with the class syntax, we don’t have to call the call
method on the parent constructor object and set this.__proto__.constructor
anymore.
It’s better than using the Object.create()
or setting the __proto__
property directly. Setting the __proto__
property has its problems like accidentally setting the wrong prototype.