Categories
TypeScript

Introduction to JavaScript Inheritance

Spread the love

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.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *