Inheritance is an important part of object-oriented programming. Using inheritance can better reuse the previously developed code, shorten the development cycle, improve the development efficiency.

The concept of inheritance

Here’s a classic example of inheritance.

Hypothesis defines the class of a car, the car color, brand, style, tires, etc., these static style attributes of a class known as the car, the car can run, run this movement is known as the automobile class methods, according to the car class can derive again “cars” and “truck”, add a trunk attribute to cars, Add a large container to the van so that the two vehicles belong to different classes, but they both inherit from the car class.

As can be seen from the above example, both cars and trucks are inherited from the car class.

What are the advantages of inheritance?

By inheriting the attributes and methods of the parent class, a subclass acquires the same attributes and methods as its parent class, so it does not need to redefine the attributes and methods that are common to the parent class. When a subclass inherits its parent class, it can also redefine the attributes and methods of the parent class, so that the methods overridden by the subclass will override the methods in the parent class. This is called method rewriting. This allows a subclass to have different attributes or methods than its parent class.

Next, we’ll explain inheritance by looking at the way inheritance is implemented in ES5 and ES6.

Several ways in which JavaScript implements inheritance

1. Prototype chain inheritance

The principle of

How it works: Each constructor has a prototype object, which in turn contains a pointer to the constructor, and the instantiation object contains a pointer to the prototype object. Let the pointer to the prototype object of the subclass constructor and the pointer to the prototype object of the superclass constructor instance implement inheritance.

In simple terms, the parent class instance object has proto, which is an object called prototype, and the subclass constructor has the prototype property, which is also an object, also called prototype, because the methods in the prototype are accessible to each other. So by having the stereotype of the subclass constructor point to the stereotype of the parent class instance object, you achieve a chain of stereotype inheritance.

Implementation code:

function Parent() {
  this.name = 'Tom';
  this.data = [1.2.3];
}
function Child() {
  this.age = 18;
}
Child.prototype = new Parent();
let child = new Child();
console.log(child);   

// Printable output
/* Child age: 18 __proto__: Parent data: (3) [1, 2, 3] name: "Tom" __proto__: Object */
Copy the code

From the above code and output, you can see that the Child constructor inherits the Parent constructor from the prototype and has its attributes.

disadvantages

let child1 = new Child();
let child2 = new Child();
child1.data.push(4);
console.log(child1.data);
console.log(child2.data);

// Printable output
/* [1, 2, 3, 4] */
Copy the code

As you can see, only the data array properties of child1 are changed, but the instance objects of Child2 are also changed. This is because both instances use the same stereotype object. Their memory is shared, so when one object changes, the other changes with it, which is a big disadvantage of archetypal chain inheritance.

Here’s an inheritance approach that avoids the stereotype attribute sharing problem.

2. Constructor inheritance (call, apply)

The principle of

Make the subclass constructor point directly to the parent constructor by calling call or apply in the subclass.

Implementation code:

function Parent() {
  this.name = 'Tom';
  this.data = [1.2.3];
}
Parent.prototype.say = function() {
  console.log(123);
}
function Child() {
  Parent.call(this);
  this.age = 18;
}
let child1 = new Child();
let child2 = new Child();
child1.data.push(4);
console.log(child1);
console.log(child2);

// The console prints output
/* child1: Child {name: "Tom", data: Array(4), age: 18} age: 18 data: (4) [1, 2, 3, 4] name: "Tom" __proto__: Object child2: Child {name: "Tom", data: Array(3), age: 18} age: 18 data: (3) [1, 2, 3] name: "Tom" __proto__: Object */
Copy the code

From the printable output, it can be seen that the Child directly obtains the attributes of the Parent through the call method, thus realizing the inheritance of attributes. We also change the value of the data property that is instantiated to child1. The value of the property in Child2 does not change with child1.

disadvantages

The say method on Parent’s prototype is not inherited because the child1 prototype generated by the Child instantiation object still points to itself. There is no prototype chain that inherits Parent.

Therefore, we combine the above two methods to implement inheritance, and let’s continue!

3. Combinatorial inheritance (a combination of the above two inheritance styles)

function Parent () {
  this.name = 'parent'
  this.num = [1.2.3]
}

Parent.prototype.getName = function () {
  return this.name
}

function Child () {
  Parent.call(this)
  this.age = 18
}

Child.prototype = new Parent()
Child.prototype.constructor = Child

const c1 = new Child()
const c2 = new Child()

c1.num.push(4)

console.log(c1.num);  // -> [1, 2, 3, 4]
console.log(c2.num);  // -> [1, 2, 3]

console.log(c1.getName());  // -> parent
console.log(c2.getName());  // -> parent
Copy the code

From the above results, we can see that there are no data sharing problems with attributes through call inheritance, and methods can be used normally on the prototype inheritance.

4. Original type inheritance (Object.create)

Object.create This method takes two arguments: an Object to be used as a prototype for the new Object and, optionally, an Object to define additional properties for the new Object.

const parent = {
  name: 'tom'.age: 18.num: [1.2.3].getName: function() {
    return this.name; }}const c1 = Object.create(parent)

console.log(c1.name);  // tom
c1.num.push(4)
console.log(c1.num);   // [1, 2, 3, 4]

const c2 = Object.create(parent)
console.log(c2.num);   // [1, 2, 3, 4]

console.log(c2.getName()); // tom
Copy the code

As you can see from the above results, Object.create implements Object inheritance. Similar to shallow copy, reference type data is also shared, but method inheritance can be implemented.

Parasitic combinatorial inheritance

The previous approach of using stereotype chain inheritance is wasteful in calling the superclass constructor.

The next approach uses object.create to minimize constructor calls to achieve optimal inheritance.

function clone(parent, child) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
}

function Parent() {
  this.name = 'tom'.this.num = [1.2.3]
}

Parent.prototype.getName = function () {
  return this.name
}

function Child() {
  Parent.call(this)
}

clone(Parent, Child)

const c1 = new Child()
const c2 = new Child()

c1.num.push(4)
console.log(c1.num);  // [1, 2, 3, 4]
console.log(c2.num);  // [1, 2, 3]

console.log(c1.getName());  // tom
Copy the code

Object.createThe reason method inheritance is not shared is because during a function call, who calls the function, the function ofthisIt’s going to point to somebody so there’s no universal result.

6. ES6 extends implementation inheritance

class Parent {
  constructor(name) {
    this.name = name
  }
  getName() {
    return this.name
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name)
    this.age = age
  }
}

const child = new Child('tom'.18)

console.log(child);  // { name: 'tom', age: 18 }
Copy the code

ES6 extends inheritance is also a parasitic combinational inheritance.