SoFunction
Updated on 2025-03-06

15 minutes to gain an in-depth understanding of JS inheritance classification, principles and usage

This article comprehensively describes the classification, principles and usage of JS inheritance. Share it for your reference, as follows:

Many OO languages ​​support two inheritance methods: interface inheritance and implementation inheritance. Interface inheritance only inherits method signatures, while implementing inheritance inherits the actual method. Since functions in ECMAScript are not signed, interface inheritance cannot be implemented in JS. ECMAScript only supports implementation inheritance, and its implementation inheritance is mainly achieved by relying on prototype chains. So, what I want to say belowPrototype chain inheritanceBorrow constructor inheritanceCombination inheritancePrototype inheritanceParasitic inheritanceandParasitic combination inheritanceAll belong to realization of inheritance.

At the end, I will explain theextendSyntax uses parasitic combination inheritance.

1. Prototype chain inheritance

It is described in ECMAScriptPrototype chainand use prototype chain as the main method to implement inheritance. The basic idea is to use prototypes to allow one reference type to inherit the properties and methods of another reference type. There is a basic pattern to implement prototype chain inheritance, and its code is roughly as follows:

function SuperType(){
   = true;
}
 = function(){
  return ;
};
function SubType(){
   = false;
}
 = new SuperType();    // Knock on the blackboard!  Here's the point: inheriting SuperType = function (){
  return ;
};
var instance = new SubType();
alert(());    // true

One essence of prototype chain inheritance is to rewrite the prototype object and replace it with a new type of instance; the code to add methods to the prototype must be placed after the statement that replaces the prototype; when inheriting through the prototype chain, you cannot use object literals to create prototype methods.

After instantiation, an instance attribute will be mounted under the instance object, so it is called an instance attribute. In the above code = new SuperType();, After executing this statement, the instance properties of the original SuperTypepropertyJust mountedBelow the object. This is actually a hidden danger, and the specific reasons will be discussed later.

Every time you search for an attribute or method, if the attribute or method is not found, the search process always needs to move forward one by one to the end of the prototype chain before stopping.

All reference types inherit Object by default, and this inheritance is also implemented through the prototype chain. From this we can see that the default prototype of all functions is an instance of object, so the default prototype of the function will contain an internal pointer pointing to .

shortcoming:

  1. The main problem comes from prototypes containing reference type values. When inheritance is achieved through a prototype, the prototype will actually become an instance of another type. Therefore, the original instance attribute naturally becomes the current prototype attribute.
  2. When creating instances of subtypes, parameters cannot be passed to supertype constructors.

* Off topic: Two ways to determine the relationship between prototype and instance

  1. The first method is to useinstanceOf Operator, just use this operator to test whether a constructor has appeared in the prototype chain of the instance. If there is, it will return true; if there is no, it will return false. The following is the sample code:
    alert(instance instanceof Object);   //true
    alert(instance instanceof SuperType);  //true
    alert(instance instanceof SubType);   //true
    
    
  1. The second way is to use the isPrototypeOf() method. Similarly, as long as it is a prototype that appears in the prototype chain, it can be said to be the prototype of the instance derived from the prototype chain. The following is the sample code:
alert((instance));    //true
alert((instance));   //true
alert((instance));    //true

2. Borrow constructor inheritance

Borrowing constructor inheritance, also known as forged object or classical inheritance. The basic idea is quite simple, that is, call the supertype constructor inside the subtype constructor. The inheritance code is roughly as follows:

function SuperType(){
   = [ "red", "blue", "green"];
}
function SubType(){
  (this);    // Knock on the blackboard!  Pay attention to inheriting SuperType here}
var instance1 = new SubType();
("black");
alert();    // "red,blue,green,black"
var instance2 = new SubType();
alert();    // "red,blue,green"

By usingcall()Method (orapply()The method is also OK), we are actually calling the parent class constructor in the instance environment of the newly created subclass (in the future).

To ensure that the superclass constructor does not override the properties of the subtype, you can add the properties that should be defined in the subtype after calling the supertype constructor.

advantage:Parameters can be passed to supertype constructors in subtype constructors.

shortcoming:

  1. Methods are defined in the constructor. Every time they are instantiated, a new method object is created, so the function cannot be reused at all;
  2. Use this pattern to define custom types, methods defined in supertype prototypes are invisible to subtypes.

3. Combination inheritance

Combination inheritance, sometimes called pseudo-classical inheritance, is to use the prototype chain to implement the inheritance of prototype properties and methods, and to implement the inheritance of instance properties by borrowing constructors. The inheritance code is roughly as follows:

function SuperType(name){
   = name;
   = ["red", "blue", "green"];
}
 = function(){
  alert();
};
function SubType(name, age){
  (this, name);   // Inheritance attributes   = age;         // Inherit first, then define new custom properties}
 = new SuperType();    // Inheritance method( , "constructor", {   // Inherit first, then define new custom properties  enumerable: false,   // Declare the data attribute - constructor cannot be enumerated  value: SubType
});
 = function(){   // Inherit first, then define a new custom method  alert();
};
var instance1 = new SubType("Nicholas", 29);
("black");
alert();    // "red, blue, green, black"
();      // "Nicholas"
();       // 29
var instance2 = new SubType("Greg", 27);
alert();    // "red, blue, green"
();      // "Greg";
();       // 27

advantage:

  1. It integrates the advantages of prototype chain inheritance and borrowed constructor inheritance to avoid their shortcomings;
  2. instanceOf()andisPrototypeOf()It can also be used to identify objects created based on combination inheritance.

shortcoming:

When implementing inheritance, the supertype constructor will be called twice in any case: once when creating a subtype prototype, and the other time inside the subtype constructor. The prototype of a subtype will eventually contain all instance properties of the supertype object, but we have to override these properties when defining the subtype constructor, because it is best not to have reference type values ​​in the prototype of the subtype. But in reality, this causes memory waste.

4. Prototype inheritance

The idea of ​​prototype inheritance is to create new objects based on existing objects without having to create custom types. This will use()Method, let's first look at the principle code of this method:

function object(o){
  function F(){}
   = o;
  return new F();
}

Essentially,object()A shallow copy is performed on the objects passed into it.

ECMAScript 5 Want to pass()Methods standardize prototype inheritance. This method accepts two parameters: the first parameter is an object used as the prototype of the new object; the second parameter is optional, an object that defines additional attributes for the new object. The format of this parameter is()The second parameter format is the same. The following is the sample code for prototype inheritance:

var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = (person, {
  name: {
    value: "Greg"
  }
});
("Rob");
alert();   //"Greg"
var yetAnotherPerson = (person);
 = "Linda";
("Barbie");
alert();   //"Shelby,Court,Van,Rob,Barbie"

shortcoming:All instances always share the reference type attribute value in the source object.

5. Parasitic inheritance

Parasitic inheritance is similar to parasitic constructors and factory patterns, which creates a function that only encapsulates the inheritance process, which internally enhances the object in some way, and finally returns the object as if it really did all the work. Let’s take a look at the sample code for parasitic inheritance:

function object(o){
  function F(){}
   = o;
  return new F();
}
function createAnother(original){
  var clone = object(original);  // Create a new object by calling the function   = function(){    // Somehow enhance this object    alert("hi");
  };
  return clone;          // Return this object}

This inheritance method is actually to put the prototype inheritance into the function, enhance the object inside it, and then return it. It is equivalent to the inheritance of prototypes parasitic in functions, so it is named parasitic inheritance.

The object() function used in the previous demonstration of inheritance mode is not required; any function that can return a new object is applicable to this mode.

shortcoming:Function multiplexing cannot be achieved, and it is inefficient.

6. Parasitic combination inheritance (recommended)

Parasitic combination inheritance, that is, inheriting attributes by borrowing constructors, and inheriting methods through the mixed form of prototype chains. The basic idea behind it is: there is no need to call the supertype constructor to specify the prototype of the subtype. All we need is a copy of the supertype prototype. Essentially, it is to use parasitic inheritance to inherit the supertype prototype, and then assign the result to the subtype prototype. The following is the example code of parasitic combination inheritance:

function object(o){
  function F(){}
   = o;
  return new F();
}
function inheritPrototype(subType, superType){
  var prototype = object();    //Create an object   = subType;          //Enhanced Object   = prototype;           //Specify the object}
function SuperType(name){
   = name;
   = ["red", "blue", "green"];
}
 = function(){
  alert();
};
function SubType(name, age){
  (this, name);     // Inheritance attributes   = age;
}
inheritPrototype(SubType, SuperType);    //Inherit the prototype method = function(){
  alert();
};

advantage:

  1. Call the supertype constructor only once;
  2. Avoid creating unnecessary and unnecessary attributes on subclass prototypes, saving memory space;
  3. The prototype chain can still remain unchanged normally, which means that instanceOf and isPrototypeOf() can be used normally for object recognition.

Parasitic combination inheritance is the most ideal inheritance method.

7. Extend inheritance in ES6

Let’s take a look at the example code of how extend implements inheritance in ES6: The content explanation of this section. I read this article. If you want to know the original text, please click here~

class Child extends Parent{
  name ='qinliang';
  sex = "male";
  static hobby = "pingpong";   //static variable
  constructor(location){
    super(location);
  }
  sayHello (name){
    (name);    //super calls the parent class method  }
}

Let's take a look at the _inherit() method in the code compiled by babel:

function _inherits(subClass, superClass) {
  //SuperClass must be a function, and is not null at the same time  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  }
   = (   // Parasitic combination inheritance    superClass && ,   //The methods and attributes on the prototype have been inherited    {
      constructor: {   // And a new attribute is defined, here the constructor attribute is rewritten        value: subClass,
        enumerable: false,   // and implements the inenuity of this property        writable: true,
        configurable: true
      }
    }
  );
  if (superClass)   // Implement the inheritance of static variables in the class     ? (subClass, superClass) : subClass.__proto__ = superClass;
}

From this we can clearly see that in ES6extendSyntax, when implementing inheritance internally, parasitic combination inheritance is used.

Let's take a look at the compilation, except_inherit()Other compilation result codes outside the method:

"use strict";
var _createClass = function () {    // Create custom types using prototype mode  function defineProperties(target, props) {   // Set the data characteristics of the attributes    for (var i = 0; i < ; i++) {
      var descriptor = props[i];
       =  || false;
       = true;
      if ("value" in descriptor)
         = true;
      (target, , descriptor);
    }
  }
  return function (Constructor, protoProps, staticProps) {
    // Set the prototype properties of the Constructor into the prototype    if (protoProps) defineProperties(, protoProps);
    // Set the static type property of the Constructor    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();
var _get = function get(object, property, receiver) {  // The parent class's method will be called before calling the subclass method  // Get the method from it by default  if (object === null) object = ;
  // Get the specified method in the parent class prototype chain  var desc = (object, property);
  if (desc === undefined) {
    var parent = (object);   // Continue to get the parent type prototype up    if (parent === null) {
      return undefined;
    } else {    // Continue to get the specified method in the parent class prototype      return get(parent, property, receiver);
    }
  } else if ("value" in desc) {
    return ;   // Return the obtained value  } else {
    var getter = ;   // Get the getter method for obtaining the prototype    if (getter === undefined) {
      return undefined;
    }
    return (receiver);    // Then call the getter method and pass this object  }
};
function _classCallCheck(instance, Constructor) {    // Ensure that our instance object is of a specific type  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}
// Call the constructor of the parent class in the constructor of the subclassfunction _possibleConstructorReturn(self, call) {    // This is the subclass of the first parameter, and the second parameter is the constructor of the parent class  if (!self) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }
  return call && (typeof call === "object" || typeof call === "function") ? call : self;
}
var Child = function (_Parent) {
  _inherits(Child, _Parent);
  function Child(location) {   // static variable
    _classCallCheck(this, Child);    // Detect this pointing problem    // Call the constructor of the parent class and pass in the parameters when calling the subclass to generate this or subclass's own this    var _this = _possibleConstructorReturn(this, (Child.__proto__ || (Child)).call(this, location));
    _this.name = 'qinliang';
    _this.sex = "male";
    return _this;
  }
  _createClass(Child, [{   //Update the prototype of Child type    key: "sayHello",
    value: function sayHello(name) {
      // super calls the parent class method and passes the parameters passed in when the subclass is called to the parent class      _get(.__proto__ || (), "sayHello", this).call(this, name);
    }
  }]);
  return Child;
}(Parent);
 = "pingpong";

It can be seen from my comments_possibleConstructorReturn()Functions are actually the only time that supertype constructor is called in parasitic combination inheritance, thusInitialize the subtype constructor in the environment. From this point of view, we can be more certain that extend in ES6 uses parasitic combination inheritance.

For more information about JavaScript, you can also view the topic of this site: "JavaScript object-oriented tutorial》、《Summary of JavaScript Errors and Debugging Skills》、《Summary of JavaScript data structure and algorithm techniques》、《JavaScript traversal algorithm and skills summary"and"Summary of JavaScript mathematical operations usage

I hope this article will be helpful to everyone's JavaScript programming.