Many object-oriented languages support two types of inheritance: interface inheritance and implementation inheritance. The former inherits only method signatures; the latter inherits the actual methods. Because JavaScript functions do not have signatures, inheritance is implemented in the only way that JavaScript supports inheritance, and this is mostly done through prototype chains.

1. Prototype chain inheritance

The relationship between constructors, stereotypes, and instances: Each constructor has a stereotype object, each stereotype object contains a pointer to the constructor, and each instance contains a pointer to the stereotype object.

The core of stereotype chain inheritance: an instance of a parent class as a stereotype of a subclass.

/ / parent class
function Animal() {
  this.superValue = 'animal';
  this.colors = ['red'.'green'.'black']
}

Animal.prototype.getSuperValue = function() {
  return this.superValue;
}

/ / subclass
function Cat() {
  this.subValue = 'cat'
}

// Key: create Animal instance and assign it to cat.prototype
Prototype__proto__ = animal.prototype
Cat.prototype = new Animal();

Cat.prototype.getSubValue = function() {
  return this.subValue;
}

const instance1 = new Cat();
console.log(instance1.getSuperValue()); // animal
console.log(instance1.colors); // [ 'red', 'green', 'black' ]
instance1.colors.push('pink');
console.log(instance1.colors); // [ 'red', 'green', 'black', 'pink' ]

const insatnce2 = new Cat();
console.log(instance1.colors); // [ 'red', 'green', 'black', 'pink' ]

// The constructor property on the subclass prototype has been overridden and no longer points to Cat
console.log(Cat.prototype.constructor === Cat); // false
console.log(Cat.prototype.constructor === Animal); // true
Copy the code

Characteristics of the prototype inheritance scheme:

  • Very pure inheritance, an instance is an instance of a subclass and an instance of a parent class
  • New stereotype methods/attributes in the parent class that all subclasses can access
  • Simple and easy to implement

Disadvantages of the prototype chain inheritance scheme:

  1. Reference properties from the stereotype object are shared by all instances, and if the stereotype object has a reference type in its properties, modifying the value of the reference type in one instance will be modified in the other.
  2. The Construtor attribute on the prototype of a subtype has been rewritten
  3. Adding attributes and methods to a subclass stereotype must precede the replacement stereotype
  4. Unable to pass arguments to the superclass constructor while creating a subclass instance

2. Borrow constructor inheritance

Borrow the basic idea of constructor inheritance: call the superclass constructor inside the subclass constructor.

Essentially enhancing a subclass instance by using the parent class’s constructor is equivalent to copying an instance of the parent class to a subclass (without using a stereotype).

function Animal() {
  this.colors = ['red'.'green'.'blue']}function Cat() {
  / / Animal inheritance
  Animal.call(this); // This refers to the Cat object (the Cat constructor), which initializes the colors attribute in the Cat object
}

const instance1 = new Cat();
instance1.colors.push('black');
console.log(instance1.colors); // [ 'red', 'green', 'blue', 'black' ]

const instance2 = new Cat();
console.log(instance2.colors); // [ 'red', 'green', 'blue' ]
Copy the code

The core code is animal.call (this), which calls the Animal constructor when creating an instance of a subclass, so that each instance of Cat copies the property from Animal.

Borrow constructor inheritance features:

  • It solves the problem that subclass instances share reference attributes of parent class in prototype chain inheritance
  • When you create a subclass instance, you can pass parameters to the parent class
  • Multiple inheritance can be implemented (call multiple parent objects)

Disadvantages of borrowing constructor inheritance:

  1. Only instance properties and methods of the parent class can be inherited, not stereotype properties and methods
  2. Function reuse is not possible, each subclass has a copy of the parent class instance function, affecting performance

3. Combinatorial inheritance

Combining these two methods is called combinatorial inheritance.

Core: Inheritance of stereotype properties and methods using stereotype chains and inheritance of instance properties by borrowing constructors.

// Combinatorial inheritance
function Animal(name) {
  this.name = name;
  this.colors = ['red'.'green'.'blue']
}

Animal.prototype.sayName = function() {
  return this.name;
}

function Cat(name, age) {
  // Inherit Animal property, call Animal for the second time
  Animal.call(this, name);
  this.age = age;
}

// Call Animal for the first time
Cat.prototype = new Animal();
// Override cat. prototype's constructor property to point to its own constructor, Cat
Cat.prototype.constructor = Cat;
Cat.prototype.sayAge = function(){
  return this.age;
};

const instance1 = new Cat('doudou'.3)
instance1.colors.push('black');
console.log(instance1.colors); // [ 'red', 'green', 'blue', 'black' ]
console.log(instance1.sayName()); // doudou
console.log(instance1.sayAge()); / / 3

const instance2 = new Cat('maomao'.1);
console.log(instance2.colors); // [ 'red', 'green', 'blue' ]
console.log(instance2.sayName()); // maomao
console.log(instance2.sayAge()); / / 3
Copy the code

Disadvantages of composite inheritance:

  • The superclass constructor is called twice, once while the subclass prototype is being created and once inside the subtype constructor.

4. Original type inheritance

The basic idea of primitive inheritance is to use an empty object as an intermediary, assign an object directly to the prototype of the empty object constructor, and return an instance of the controlling object constructor.

function object(obj){
  function F(){}
  F.prototype = obj;
  return new F();
}

// object() performs a shallow copy of the object passed into it
const person = {
  name: "Nicholas".friends: ["Shelby"."Court"."Van"]};const anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

const yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

console.log(person.friends);   //"Shelby,Court,Van,Rob,Barbie"
Copy the code

Disadvantages of original type inheritance:

  1. Reference properties from the stereotype object are shared by all instances, and if the stereotype object has a reference type in its properties, modifying the value of the reference type in one instance will be modified in the other.
  2. Unable to pass parameters

In ES5, there is a method called object.create () that can replace the Object method above.

Parasitic inheritance

Core: on the basis of the original type inheritance, enhance the object

function createAnother(original){
  var clone = object(original); // Create a new object by calling object()
  clone.sayHi = function(){  // Enhance objects in some way
    console.log("hi");
  };
  return clone; // Return this object
}

onst person = {
  name: "Nicholas".friends: ["Shelby"."Court"."Van"]};const anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
Copy the code

The disadvantages of parasitic inheritance are the same as those of primitive inheritance

6. Parasitic combinatorial inheritance (best)

A parasitic combinatorial foundation inherits properties by borrowing constructors and inherits methods through a mashup of stereotype chains.

The basic idea: Use parasitic inheritance to inherit the stereotype of the parent type, and then assign the result to the stereotype of the child type.

function SuperType(name) {
  this.name = name;
  this.colors = ["red"."blue"."green"];
}

SuperType.prototype.sayName = function(){
  console.log(this.name);
};


function SubType(name, age){
  // Use the constructor to pass enhanced subclass instance attributes (support passing arguments and avoid tampering)
  SuperType.call(this, name);
  this.age = age;
}

// Point the superclass prototype to the subclass and inherit the superclass prototype method
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.constructor = SubType;

// Add a new subclass stereotype attribute
SubType.prototype.sayAge = function(){
  console.log(this.age);
}

const instance1 = new SubType("doudou".3);
const instance2 = new SubType("maomao".1);

instance1.colors.push('black')
console.log(instance1.colors) // ["red", "blue", "green", "black"]
console.log(instance2.color) // ["red", "blue", "green"]
Copy the code

The efficiency of this example is that it calls the SuperType constructor only once, and thus avoids creating unnecessary, redundant properties on subtype. prototype. At the same time, the prototype chain stays the same; Therefore, instanceof and isPrototypeOf() can also be used normally.

This is the most mature method, and it is the method that libraries implement today.

7. ES6 extends inheritance

The premise of knowledge

/* Class features * 1. All methods of a class are defined on the prototype property of the class; Class new methods can be added to the Prototype object. * 2. All methods defined inside a class are not enumerable, which is inconsistent with ES5 behavior. * 3. A class must have a constructor() method; if not defined, an empty constructor() method is added by default. * 4. The constructor() method returns the instance object (that is, this) by default and can be specified to return another object. * 5. The class must be called with new, otherwise an error will be reported. This is a major difference from normal constructors, which can be executed without new. * 6. Unlike ES5, there is no variable promotion in a class. The reason for this rule has to do with inheritance, and the subclass must be defined after its parent. * 7. In a class, the static keyword is used before a method, indicating that the method is not inherited by the instance, but is called directly by the class. * If the static method contains the this keyword, this refers to the class, not the instance; Static methods of a parent class can be inherited by subclasses; Static methods can also be called from super objects. * 8. Instance attributes can be defined at the top level of the class, as well as above this in the constructor() method. * 9. There is currently a proposal to make class private. The method is to use # to indicate * 10 before the attribute name. When a subclass inherits from its parent, new.target returns the subclass. * /Copy the code
  • ES5 inheritance is essentially creating instance objects of the subclass firstthis, and then add the methods of the parent class tothisThe above (Parent.call(this))
  • ES6 has a completely different inheritance mechanism, essentially adding the attributes and methods of the parent instance object toOn this(so you must call super()), and then modify it using the subclass constructorthis.

Classes implement inheritance mainly through the extends keyword as shown in the following example:

class SuperType {
  constructor(name) {
    this.name = name
    this.colors = ["red"."blue"."green"]}sayName() {
    return this.name
  }
}

class SubType extends SuperType {
  constructor(name, age) {
    // Inherits the attributes of the parent class
    super(name)
    this.age = age
  }
  sayAge() {
    return this.age
  }
}

const instance1 = new SubType("doudou".3)
const instance2 = new SubType("maomao".1)
console.log(instance1.sayName()) // doudou
instance1.colors.push("2")
console.log(instance1.colors) // ["red", "blue", "green", "2"]
console.log(instance2.colors) // ["red", "blue", "green"]
Copy the code

The core code of extends inheritance is as follows and is implemented in the same way as the parasitic combinational inheritance described above

function _inherits(subType, superType) {
    // Create object, create a copy of the parent class prototype
    // Enhance the object to compensate for the loss of the default constructor property by rewriting the prototype
    // Assign the newly created object to the prototype of the subclass
    subType.prototype = Object.create(superType && superType.prototype, {
        constructor: {
            value: subType,
            enumerable: false.writable: true.configurable: true}});if (superType) {
        Object.setPrototypeOf ? Object.setPrototypeOf(subType, superType) : subType.__proto__ = superType; }}Copy the code

supplement

1. A subclass must call the super method from its constructor method, otherwise an error will be reported when creating a new instance. * Reason: the subclass's this object must first be molded by the parent class's constructor to get the same instance attributes and methods as the parent class, and then to add the subclass's own instance attributes and methods. If you don't call super, your subclasses don't get this. * 2. The subclass does not define the constructor method, which is added by default. That is, any subclass has a constructor method regardless of whether the definition is displayed. * 4. The super keyword can be used either as a function or as an object. * 4.1 When super is called as a function, the constructor representing the parent class can only be used in the constructor of the subclass. If super is used elsewhere, an error will be reported. * 4.2 When super is an object, in a normal method, it points to a prototype object of the parent class; In static methods, point to the parent class. * 5. Class is the syntactic sugar of the constructor and has both the prototype and __proto__ attributes, so there are both inheritance chains. * 5.1 The __proto__ attribute of a subclass, indicating constructor inheritance, always points to the parent class (ES5 does not have this) * 5.2 The __proto__ attribute of a subclass prototype attribute, indicating method inheritance, always points to the prototype attribute of the parent class */Copy the code

Reference: juejin. Cn/post / 684490…