preface

This paper systematically introduces seven common inheritance modes in JavaScript: prototype chain inheritance, constructor inheritance, composite inheritance, primitive inheritance, parasitic inheritance, parasitic combined inheritance, extends class inheritance. It is recommended to use the browser to run the case in the article again, to help better understand.

1. Prototype chain inheritance

If you are not clear about prototype chain, you can take a look at my article. What is a prototype chain?

What is prototype chain inheritance?

Essence: Rewrite the prototype object and replace it with an instance of a new type.

Common understanding: Prototype chain inheritance is to assign the object of the constructor Super to the prototype of another constructor Sub, so that Sub retains its own attributes and methods, but also inherits the attributes and methods of Super.

Let’s look at an example where we have two constructors, Super and Sub, and Sub inherits Super through a prototype chain.

/ / parent class
function Super() {
  this.name = Valley Floor Dragon.this.getAge = function (){
    return 28; }}/ / subclass
function Sub() {
  this.country = 'China'
  this.name = 'World beaters',}// Assign the Super object to the Sub prototype
Sub.prototype = new Super();
const instance = new Sub();
// Outputs the properties and methods of Sub
console.log(
`my name is ${instance.name}, 
 my age is ${instance.getAge()},
 my country is ${instance.country}`
)
Copy the code

Sub. Prototype = new Super(); Sub = county; Inheriting both the Super property name and the method getAge()

Disadvantages of prototype chain inheritance and matters needing attention

1. When there are multiple subclass instances, reference type operations on parent class attributes will cause data tampering problems

This is the biggest problem caused by shared data in stereotype chain inheritance. Let’s start with an example

/ / parent class
function Super() {
  this.name = [Valley Floor Dragon]}/ / subclass
function Sub() {}
// Assign the Super object to the Sub prototype
Sub.prototype = new Super();
// Create two columns of the subclass
const instance1 = new Sub();
const instance2 = new Sub();
// Reference instance instance1 (push)
instance1.name.push('World beaters');
// Prints the property name of instance instance2
console.log(`my name is ${instance2.name}`)
Copy the code

In this example, the attribute name of instance instance1 will be referenced (push), and the attribute name of instance instance2 will also be changed.

Constructor should be overridden

So let’s run the case

/ / parent class
function Super() {
  this.name = [Valley Floor Dragon]}/ / subclass
function Sub() {}
// Assign the Super object to the Sub prototype
Sub.prototype = new Super();
// Create two columns of the subclass
const instance = new Sub();
// The constructor of the subclass prototype points to the superclass Super
console.log(Sub.prototype.constructor === Super);
Copy the code

Sub.prototype = new Super(); prototype-chain inheritance (sub.prototype = new Super()) ¶ So, here to perform Sub. Prototype. Constructor = = = Super result is true.

3. If a subclass has the same properties and methods as its parent class, the subclass overrides its parent class

When the constructor is initialized, if the subclass has the same attributes and methods as the parent class, the subclass takes precedence (note the distinction from Point 4), so we run the following case first

/ / parent class
function Super() {
  this.name = Valley Floor Dragon
  this.getAge = function (){
    return 28; }}/ / subclass
function Sub() {
  this.name = 'World beaters';
  this.getAge = function (){
    return 35; }}// Assign the Super object to the Sub prototype
Sub.prototype = new Super();
// Outputs the properties and methods of Sub
const instance = new Sub();
console.log(`my name is ${instance.name}, my age is ${instance.getAge()}`)
Copy the code

Here, subclass Sub and superclass Super have the same initialization property name and method getAge(), which ultimately represent the subclass’s properties and methods. So print my name is invincible, my age is 35

4. Subclass stereotypes should add attributes and methods after stereotype inheritance

This is easy to understand, as you can see from point 2. Because of the stereotype chain inheritance, the object of the parent class overrides the prototype of the child class, so you need to add attributes and methods to the prototype of the child class after the stereotype inheritance.

  • Note:Note here the number with the one above3Point to distinguish

Let’s look at an example

/ / parent class
function Super() {
  this.name = Valley Floor Dragon
}
/ / subclass
function Sub() {};
// Subclass prototype adds attribute age
Sub.prototype.name = 'World beaters';

// Assign the Super object to the Sub prototype
Sub.prototype = new Super();
// Outputs the properties and methods of Sub
const instance = new Sub();
console.log(`my name is ${instance.name}`)
Copy the code

Here is the attribute name added to the subclass (Sub) prototype before the prototype, so it will be overwritten by the superclass (Super), printing my name is the bottom dragon

Cannot pass a parameter to the parent class constructor when creating a subclass instance

This is easy to understand, and it’s clear from the previous cases, so I won’t go into it here.

Constructor inheritance

If you are not familiar with constructors, what is a constructor?

What is constructor inheritance?

Constructor inheritance is essentially calling this from Super to Sub in a subclass by call/apply.

Let’s look at an example first

/ / parent class
function Super(name) {
  this.name = Valley Floor Dragon
  this.getAge = function() {
    return 28;
  };
}
/ / subclass
function Sub() {
  // Subclass Sub by call
  Super.call(this.Valley Floor Dragon)};// Outputs the properties and methods of Sub
const instance = new Sub();
console.log(`my name is ${instance.name}, my age is ${instance.getAge()}`)
Copy the code

If you run the above example, you will print my name is The bottom dragon, and my age is 28. Obviously, there is constructor inheritance via call, so that subclass Sub inherits the property name and method getAge() from the parent class Super.

  • Note:It is also possible to change the “call” in the case to “apply”. rightcall/applyIf it’s not clear, check out my articleFully parse call and apply

Advantages and disadvantages of constructor inheritance and considerations

1. Advantages: As mentioned above, stereotype chain inheritance has the defects that multiple instances’ operations on reference types will be tampered with, and when creating a subclass instance, it cannot pass arguments to the constructor of the parent class. Constructor inheritance can avoid these two problems

Let’s change the stereotype chain inheritance case to constructor inheritance

/ / parent class
function Super(name) {
  this.name = [name]
}
/ / subclass
function Sub() {
  // Use constructor inheritance
  Super.call(this.Valley Floor Dragon)}// Create two columns of the subclass
const instance1 = new Sub();
const instance2 = new Sub();

// Reference instance instance1 (push)
instance1.name.push('World beaters');
// Prints the property name of instance instance2
console.log(`my name is ${instance2.name}`)
Copy the code

As you can see from the example, through constructor inheritance

  • Subclasses can pass arguments (The bottom of the dragon) to the parent class constructor
  • After using constructor inheritance, reference type operation is performed on instance instance1 property name (push), the property name of instance instance2 will not be modified, so it will be printed hereMy name is flying dragon

2. Note: In constructor inheritance, subclasses should add attributes and methods after constructor inheritance

This can be compared to points 3 and 4 in prototype chain inheritance. Let’s put it in context

/ / parent class
function Super(name) {
  this.name = Valley Floor Dragon;
  this.age = 28;
}
/ / subclass
function Sub() {
  // Define attributes before inheritance
  this.name = 'World beaters'
  
  // Use constructor inheritance
  Super.call(this);
  
  // Defined after inheritance
  this.age = 68;
}

// Prints the attributes name and age of subclass instance instance
const instance = new Sub();
console.log(`my name is ${instance.name}, my age is ${instance.age}`)
Copy the code
  • A subclassBefore the constructor inheritsAttributes and methods defined with the same name as the parent class are overwritten by the attributes and methods of the parent class. If the name is different from the parent class, there is no impact
  • A subclassAfter constructor inheritanceProperties and methods defined, whether or not they have the same name as the parent class, are not affected by the parent class

So, I should print my name is Valley Flying Dragon, my age is 68

3, disadvantages: the parent class prototype method, in the subclass is not visible

/ / parent class
function Super(name) {}
// Define the method getName on the Super stereotype
Super.prototype.getName = function() {
   return Valley Floor Dragon;
}

/ / subclass
function Sub() {
  // Use constructor inheritance
  Super.call(this);
}
const instance = new Sub();
console.log(`my name is ${instance.getName()}`)
Copy the code

Uncaught TypeError: instance.getName is not a function Uncaught TypeError: instance.getName is not a function

  • Note:Methods defined inside the parent constructor are accessible to subclasses

3. Combinatorial inheritance

As mentioned above, both pure prototype chain inheritance and pure constructor inheritance have defects. Therefore, in practical development, they are generally mixed, which leads to the concept of combinatorial inheritance

What is combinatorial inheritance?

Using prototype chain inheritance to inherit the properties and methods of prototype; Constructor inheritance is used to implement inheritance of instance attributes

In fact, it is the combination of prototype chain inheritance and constructor inheritance, giving play to their advantages to make up for each other’s shortcomings, so as to achieve not only function reuse, but also the effect of making each object have attributes.

/ / parent class
function Super(name) {
  this.name = Valley Floor Dragon;
  this.hobby = ['read']}// Define the superclass Super prototype method
Super.prototype.getAge = function() {
   return 28;
}

/ / subclass
function Sub() {
  // Use constructor inheritance
  Super.call(this);
  
  // Subclass-specific attributes
  this.country = 'China'
}

// Use prototype chain inheritance on subclass Sub
Sub.prototype = new Super()

const instance1 = new Sub();
const instance2 = new Sub();

// Reference type operation on instance instance1
instance1.hobby.push('investment')

console.log(
'Instance1: My name is${instance1.name}, 
 my age is ${instance1.getAge()},
 my country is ${instance1.country},
 my hobby is ${instance1.hobby}`)

console.log(
'Instance2: My name is${instance2.name}, 
 my age is ${instance2.getAge()},
 my country is ${instance2.country},
 my hobby is ${instance2.hobby}`)
Copy the code

Let’s take a look at the print result:

As you can see from the print above, by combinatorial inheritance

  • Subclass Sub inherits properties inside the Super constructorname
  • Subclass Sub can access the superclass SuperPrototype prototypeThe method on thegetAge()
  • Subclass Sub retains its own attributescountry
  • Fixed multiple instances tampering with properties with reference type operations (instance instance2 properties)hobbyNot subject to instance instance1 on superclass propertieshobbyImpact of doing a reference type operation)

4. Original type inheritance

Be careful not to be confused with the prototype chain inheritance mentioned earlier.

What is primary inheritance?

Use an empty object as an intermediary, assign an object directly to the prototype of the empty object constructor, and return the empty object as a new object, then the new object inherits the properties and methods of the original object.

// The original type of inheritance function
function create(obj){
   // Create a new constructor
   function F() {};
   // Point the prototype of the new constructor to the existing object obj
   F.prototype = obj;
   // Returns a new object that shares the properties and methods of the original object obj
   return new F()
}

function Person() {
   this.name = Valley Floor Dragon;
   this.getAge = function(){
      return 28; }}// Call the old type function to generate a new object
const instance = create(new Person())
console.log(`my name is ${instance.name}, my age is ${instance.getAge()}`)
Copy the code

The original type inheritance function create() is created to make a shallow copy of the constructor object new Person() passed in. Here print the result my name is valley flying dragon, my age is 28, it can be seen that through the original type inheritance method, the existing object can be used to create a new object with the help of the prototype, so that the new object and the existing object share attributes and methods.

  • The object.create () method was added in ES5 to normalize primitive inheritance, taking two parameters, the first being the prototype Object and the second being the property to be blended into the new Object. The principle is the same

Defects of original type inheritance and matters needing attention

With the previous prototype chain inheritance is very similar, the principle is similar, can be compared to understand. The original type inherits the shared properties and methods, but there are no instances of tampering with the reference type operation

// The original type of inheritance function
function create(obj){
   // Create a new constructor
   function F() {};
   // Point the prototype of the new constructor to the existing object obj
   F.prototype = obj;
   // Returns a new object that shares the properties and methods of the original object obj
   return new F()
}

function Person() {
   this.name = [Valley Floor Dragon];
}
// Call the old type function to generate a new object
const instance1 = create(new Person())
const instance2 = create(new Person())

// Reference the instance1 attribute name (push)
instance1.name.push('World beaters');

// Instance1 property name changed to "Valley Dragon, invincible"
console.log(
`instance1:
 my name is ${instance1.name}`
)

// The instance2 property name remains the same as' Valley Dragon '
console.log(
'Instance2: My name is${instance2.name}`
)
Copy the code

Execute the above example and print the following:

It can be seen from the results that although the original type inheritance also shares data, there is not necessarily the problem of data tampering caused by multiple instances referencing attributes in the prototype chain inheritance

Parasitic inheritance

What is parasitic inheritance?

On the basis of the original type inheritance, enhance the object, return the constructor

// The original type of inheritance function
function create(obj){
   function F() {};
   F.prototype = obj;
   return new F()
}

// Parasitic inheritance function
function createAnother(obj){
   // Create a new object using the old type inheritance function create()
   var clone = create(obj)
   // Add new attributes and methods to the object
   clone.name = Valley Floor Dragon
   clone.getAge = function(){
     return 28;
   } 
   return clone
}

function Person(){
   this.country = 'China'
}
const instance = createAnother(new Person());
console.log(
`my name is ${instance.name},
 my age is ${instance.getAge()},
 my country is ${instance.country}`
 );
Copy the code

Clone () creates a clone object that inherits the country property of the Person constructor and adds the name property and getAge() method.

Parasitic combinatorial inheritance

Parasitic inheritance, prototype inheritance, and prototype chain inheritance are all prototype-based inheritance, so they all have constructors that cannot pass parameters to the parent class. Shared data may bring data tampering problems with reference type operations, so similar to composite inheritance, we solve these problems by combining constructor inheritance.

The essence of parasitic combinatorial inheritance is to make use of the advantages of both constructor inheritance and parasitic inheritance to make up for the shortcomings of each other, that is, using the constructor inheritance to pass parameters and avoid tampering, using the parasitic inheritance to increase the prototype method

The case is being supplementedCopy the code

7. Extends Class Inheritance (recommended)

It’s ES6 that has extends inheritance, which is the most recommended method of inheritance in our current development

What is extends class inheritance?

  • Subclasses byextendsInherit the parent class
  • Both parent and child classes have their own constructorsconstructor()Constructor to get the parameters passed when the instance is created. There can only be one constructor in a class
  • In the constructor of a subclass, passsuper()To pass arguments to the parent class
// Define a parent class Person
class Person {
  // constructor
  constructor(name, age){
     this.myName = name;
     this.age = age;
  }
  // getter gets the property
  get name() {return this.myName;
  }
  / / method
  getAge() {
    return this.age; }}Extends the parent class Person through extends
class Sub extends Person {
  // The subclass constructor needs to call super() first before using "this"
  constructor(content){
    // Pass the argument to the parent class
    super(content, 28)
    this.country = 'China'
  }
  get info() {return `my name is The ${this.name},
     my age is The ${this.getAge()}, 
     my country is The ${this.country}`}}const instance = new Sub(Valley Floor Dragon);
console.log(instance.info)
Copy the code

In this case, subclass Sub inherits Person from extends. Sub has access to the attribute name and method getAge(), so, My name is Valley Flying Dragon, my age is 28, my country is China

  • Note:A subclass overrides its parent class if it has the same properties and methods as its parent class

Why do subclasses call super?

From above we know that a subclass call from the constructor() function needs to call super() first and must be called before this. Why?

  • On the one hand, throughsuper()Pass arguments to the parent constructor
  • Mainly because subclasses don’t have their ownthis, so it needs to be called firstsuperCreate this object of the parent class, and modify this with the constructor of the subclass

If super() is not called, the new instance will report an error. Run the following example to verify

// Define a parent class Person
class Person {
  // constructor
  constructor(){
     this.myName = Valley Floor Dragon;
  }
  // getter gets the property
  get name() {return this.myName; }}Extends the parent class Person through extends
class Sub extends Person {
  // The subclass constructor does not call super()
  constructor(){
     this.country = 'China'
  }
  get info() {return `my name is The ${this.name}`}}const instance = new Sub();
console.log(instance.info)
Copy the code

The error log is as follows:

  • Note:withConstructor inheritanceComparison: Constructor inheritance creates instance objects of a subclass and then adds methods of the parent class to the subclassthisCalled in the subclass constructorSuper.call(this))

More wonderful

Welcome to my blog family photo: Valley Floor Dragon’s Collection of big front-end technology blogs