inheritance

  1. Definition: Inheritance is the process by which a class obtains methods and attributes from another class.

  2. Js inheritance: overrides subclass prototype objects by copying the properties and methods of the parent class.

    Keeping this concept in mind, you’ll notice that inheritance in JS is all about this purpose, the difference is how they are implemented.

implementation

  1. Prototype chain inheritance
  2. Borrow constructor inheritance
  3. Combination of inheritance
    • Combinatorial inheritance optimization 1
  4. Primary inheritance
  5. Parasitic inheritance
  6. Parasitic combinatorial Inheritance (combinatorial inheritance optimization 2) – Perfect way
  7. ES6 Class extends

To prepare

To inherit, we need a parent class. Define a Person parent class:


function Person (name) {
  this.name = name || 'xuxu' // Instance base attribute (this attribute, emphasis private, not shared)
  this.like = ['eat'] // Private, not shared

  this.say = function() { // Instance reference attribute (this attribute, emphasis on reuse, needs to be shared)
    console.log('hello')
  }
}

Person.prototype.getInfo = function () { // Define methods that need to be reused and shared on the parent class prototype
  console.log(this.name + this.sex)
}

Copy the code

1. Prototype chain inheritance

  1. Core: Use an instance of a parent class as a prototype for a subclass

  2. 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.

  3. Code:

    
    function p (sex) {
      this.sex = sex
    }
    
    p.prototype = new Person()
    
    let p1 = new p('man')
    let p2 = new p('woman')
    
    // Advantages: parent methods can be reused, p1 and P2 share the same method
    console.log(p1.say === p2.say) // true
    // The prototype method also works
    console.log(p1.getInfo === p2.getInfo) // true
    console.log(p1.getInfo()) // xuxuman
    console.log(p2.getInfo()) // xuxuwoman
    
    // Disadvantage 1: The reference data type of the parent class is shared by all subclass instances
    p1.link.push('sleep')
    console.log(p1.link) // ['eat', 'sleep']
    console.log(p2.link) // ['eat', 'sleep']
    
    Copy the code
  4. Advantages: Methods on the parent class can be reused, as can methods on the prototype.

  5. Disadvantages:

    • The reference data type of the parent class is shared by all subclass instances.
    • A subclass cannot take arguments to its parent constructor when instantiated.

2. Borrow constructor inheritance

  1. Using the constructor of a parent class to enhance a child class instance is equivalent to copying an instance of the parent class to a child class (without using a stereotype).

  2. Borrowing constructor inheritance solves two problems of prototype chain inheritance: reference type sharing and parameter passing.

But the advantages of prototype chain inheritance also become its disadvantages.

  1. Code:

    
    function p(name, sex) {
      Person.call(this, name)  / / core
      this.sex = sex
    }
    
    let p1 = new p('little red'.'man')
    let p2 = new p('Ming'.'woman ')
    
    // Advantage 1: you can pass arguments to the parent constructor
    console.log(p.name, p.name) // Xiao Hong, Xiao Ming
    
    // Advantage 2: Subclass instances do not share reference attributes of the parent class constructor
    p1.like.push('sleep')
    console.log(p1.like,p2.like)// ['eat', 'sleep'] ['eat']
    
    // Disadvantage 1: Methods cannot be reused, and must be created every time an instance of a subclass is created
    console.log(p1.say === p2.say) // false (p1 and p2 say methods are independent, not shared)
    
    // Disadvantage 2: can't inherit methods from parent archetypes
    Person.prototype.walk = function () {   // Define a walk method on the parent's prototype object.
      console.log('I can walk')
    }
    p1.walk  // undefined (specify instance, cannot get method on superclass prototype)
    
    Copy the code
  2. Advantages: The exact opposite of prototype chain inheritance.

    • You can pass arguments to the superclass constructor.
    • Subclass instances do not share reference properties of the parent class constructor.
  3. Disadvantages:

    • Methods cannot be reused; Because methods must be defined in constructors, this results in the method being created every time an instance of a subclass is created.
    • You cannot inherit methods from a superclass stereotype, only instance properties and methods of the superclass.

3. Combinatorial inheritance

  1. Inheriting the attributes of the parent class and retaining the advantages of passing arguments by calling the parent constructor; Then the function reuse is realized by using the parent class instance as the prototype of the subclass.

  2. Code:

    
    function p(name, sex) {
      Person.call(this, name)  // Core for the second time
      this.sex = sex
    }
    
    p.prototype = new Person() // Core first time
    // Fix the constructor pointer
    p.prototype.constructor = p
    
    let p1 = new p('little red'.'man')
    let p2 = new p('Ming'.'woman ')
    
    // Advantage 1: you can pass arguments to the parent constructor
    console.log(p.name, p.name) // Xiao Hong, Xiao Ming
    
    // Advantage 2: Subclass instances do not share reference attributes of the parent class constructor
    p1.like.push('sleep')
    console.log(p1.like,p2.like)// ['eat', 'sleep'] ['eat']
    
    // Advantage 3: Methods on superclass prototypes can be reused
    console.log(p1.getInfo === p2.getInfo) // true
    
    Copy the code
  3. Note: Constructor = Person (prototype); / / constructor = Person (prototype); Result in p.prototype.constructor === Person. This requires pointing constructor to the subclass’s constructor P.

    Cause: Cannot determine whether the immediate constructor of a subclass instance is a subclass constructor or a superclass constructor.

  4. Advantages:

    • The advantages of preserving constructors are: 1. Arguments can be passed to the parent constructor. 2. Subclass instances do not share reference attributes of the parent class constructor.
    • The advantage of preserving the prototype chain is that the instance methods of the parent class are defined on the prototype object of the parent class, which can realize method reuse.
  5. Disadvantages: the parent constructor is called twice. There is an extra copy of the parent instance attributes.

    • First execution:Person.call(this, name)P calls Person to copy the parent instance property as the instance property of the subclass.
    • Second execution:p.prototype = new Person()Create a parent class instance as a subclass prototype.
    • After the second execution, the parent instance has another instance property, but this property will be masked by the first copy of the instance property, so it is redundant.

Combinatorial inheritance optimization 1

  1. Core: Remove the instance attributes of the parent class, so that when the constructor of the parent class is called, the instance is not initialized twice, avoiding the disadvantages of composite inheritance.

  2. Code:

    
    function p(name, sex) {
      Person.call(this, name)  / / core
      this.sex = sex
    }
    
    p.prototype = Person.prototype // The core subclass prototype and the parent class prototype are essentially the same
    // Fix the constructor pointer
    p.prototype.constructor = p
    
    let p1 = new p('little red'.'man')
    let p2 = new p('Ming'.'woman ')
    
    let p3 = new Person()
    
    // Disadvantages: when fixing the pointing of the subclass constructor, the pointing of the parent class instance will also change.
    console.log(p1.constructor)  // p
    console.log(p3.constructor) // p here is the problem (we want Person)
    
    Copy the code
  3. Advantages:

    • The superclass constructor is called only once.
    • Preserve the advantages of combinatorial inheritance:
      • The advantages of preserving constructors are: 1. Arguments can be passed to the parent constructor. 2. Subclass instances do not share reference attributes of the parent class constructor.
      • The advantage of preserving the prototype chain is that the instance methods of the parent class are defined on the prototype object of the parent class, which can realize method reuse.
  4. Disadvantages:

    • After fixing the constructor pointer, the parent instance’s constructor pointer also changes (which we don’t want).

      Prototype of a subclass is the same as the prototype of its parent class, so we changed the Construtor attribute of an instance of the subclass, making all the constructors pointing to change.

4. Original type inheritance

  1. Core: The createObj method inherited from the original type is essentially a shallow copy of the parameter object.

  2. Assign an object directly to the prototype of the empty object constructor using an empty object as a mediator.

    
    function createObj(o) {
      function F() {}
      F.prototype = o // Make the inherited object the prototype of the empty function
      return new F() // Return a new object created during new. The prototype of this object is the inherited object. The attributes of the inherited object can be obtained through the prototype chain
    }
    
    Copy the code

    Create is a mock implementation of ES5 Object.create that uses the passed Object as a prototype for the created Object.

    Today we use object.create () for Object prototype inheritance.

  3. Code:

    
    let p1 = createObj(Person)
    let p2 = createObj(Person)
    
    // Advantages: parent methods can be reused, p1 and P2 share the same method
    console.log(p1.say === p2.say) // true
    // The prototype method also works
    console.log(p1.getInfo === p2.getInfo) // true
    console.log(p1.getInfo()) // xuxuman
    console.log(p2.getInfo()) // xuxuwoman
    
    // Disadvantage 1: The reference data type of the parent class is shared by all subclass instances
    p1.link.push('sleep')
    console.log(p1.link) // ['eat', 'sleep']
    console.log(p2.link) // ['eat', 'sleep']
    
    p1.name = 'p1';
    console.log(p2.name); // xuxu
    
    Copy the code
  4. As with the inheritance of prototype chain, the value of p2.name does not change when the value of P1. name is modified, not because P1 and P2 have independent name values, but because P1. name = ‘P1’. The name value is added to P1, not because the name value on the prototype is modified.

  5. Pros: Same as prototype chain

    • Methods on the parent class can be reused, as can methods on the prototype.
  6. Cons: Same as prototype chain

    • The reference data type of the parent class is shared by all subclass instances.
    • A subclass cannot take arguments to its parent constructor when instantiated.

Parasitic inheritance

  1. Using a shallow copy of the original inheritance, create a function that simply encapsulates the inheritance process, internally doing the enhancement object in some form, and finally returning the object.

  2. Code:

    
    function createObj (o) {
      let clone = Object.create(o)
      clone.sayName = function () {
        console.log('hi')}return clone
    }
    
    Copy the code
  3. Usage scenario: A fixed way of enhancing an object.

6. Parasitic combinatorial Inheritance (Combinatorial inheritance optimization 2) — Perfect

  1. Addresses the drawback of composite inheritance calling the parent constructor twice.

  2. Inheriting properties/methods declared by prototype of the parent class by setting prototype as the prototype of subclass Prototype via the parasitic wrapper function.

  3. Code:

    
    function p(name, sex) {
      Person.call(this, name)  / / core
      this.sex = sex
    }
    
    ---------------------------------------------------
    
    // p.protoType = new Person() // Core first time
    
    // 1. The simplest way is to change the above line
    p.prototype = Object.create(Person.prototype) // Instead of using p.protoType = new Person(), the core indirectly gives p.protoType access to Person.prototype
    
    // Fix the constructor pointer
    p.prototype.constructor = p
    
    ---------------------------------------------------
    
    // 2. Parasitic inheritance: encapsulates the process of inheriting Person. Prototype from the original p.prototype object and enhances the passed object.
    function inheritPrototype(son, father) {
      const fatherFnPrototype = Object.create(father.prototype) Father. Prototype object father. Prototype is the prototype of the new object
      son.prototype = fatherFnPrototype // set father. Prototype to son. Prototype
      son.prototype.constructor = son // Correct the constructor direction
    }
    inheritPrototype(p, Person)
    
    ---------------------------------------------------
    
    let p1 = new p('little red'.'man')
    let p2 = new p('Ming'.'woman ')
    
    // Advantage 1: you can pass arguments to the parent constructor
    console.log(p.name, p.name) // Xiao Hong, Xiao Ming
    
    // Advantage 2: Subclass instances do not share reference attributes of the parent class constructor
    p1.like.push('sleep')
    console.log(p1.like,p2.like)// ['eat', 'sleep'] ['eat']
    
    // Advantage 3: Methods on superclass prototypes can be reused
    console.log(p1.getInfo === p2.getInfo) // true
    
    Copy the code
  4. The most mature inheritance method, is now the most commonly used inheritance method, many JS libraries adopt the inheritance scheme is also it.

  5. To quote the praise of parasitic combinatorial inheritance from JavaScript Advanced Programming:

    The efficiency of this approach is that it calls the Parent constructor only once, and thus avoids creating unnecessary, redundant properties on Parent. Prototype. At the same time, the prototype chain stays the same; Therefore, instanceof and isPrototypeOf can also be used normally. Parasitic combinatorial inheritance is generally considered by developers to be the ideal inheritance paradigm for reference types.

7. ES6 Class extends

  1. ES6 inheritance works the same way as parasitic combinatorial inheritance.

  2. The extends keyword is used primarily in class declarations or class expressions to create a class that is a subclass of another class. A class can have only one constructor, and SyntaxError is raised if more than one constructor is specified. If no constructor is explicitly specified, the default constructor method is added, as shown in the following example.

  3. Inheritance code:

    
    class Person {
    
      // constructor
      constructor(name) {
        this.name = name
        this.like = ['eat']}// Getter
      get say() {
        return this.getInfo()
      }
      
      // Method
      getInfo() {
        return this.name + this.like
      }
    }
    
    const pp = new Person('xuxu')
    console.log(pp.say) // xuxueat
    
    -----------------------------------------------------------------
    / / inheritance
    class p extends Person {
    
      constructor(name, sex) {
        super(name)
        
        // If a constructor exists in a subclass, you need to call super() first before using "this".
        this.sex = sex;
      }
    
      get say() {
        return this.name + this.like + this.sex
      }
    }
    
    const p1 = new p('xx'.'man')
    console.log(p1.say) // xxeatman
    
    Copy the code
  4. The core code for extends inheritance (compiled online to ES5 via Babel) :

    
    // Parasitic inheritance encapsulates the inheritance process
    function _inherits(son, father) {
    
      // Father. Prototype: Set father. Prototype to son. Prototype to inherit father. Prototype properties/methods
      son.prototype = Object.create(father && father.prototype)
      son.prototype.constructor = son // Correct the constructor direction
    
      // Set the parent class as the prototype of the subclass to inherit the static attributes of the parent class/method (father.some)
      if (father) {
        Object.setPrototypeOf
          ? Object.setPrototypeOf(son, father)
          : son.__proto__ = father
      }
    }
    
    Copy the code
    • In addition, subclasses inherit properties/methods declared by their parent through this by borrowing constructor inheritance (call), just like parasitic combinatorial inheritance.

    • The object.setProtoTypeof () method sets the Prototype of a specified Object (that is, the internal [[Prototype]] property) to another Object or null.

ES5 inheritance differs from ES6 inheritance:

  1. ES5 inheritance essentially creates an instance object of the subclass and then adds the methods of the superclass to this.

  2. ES6 inherits by creating an instance object of the parent class, this, and modifying this with the constructor of the subclass.

    Because subclasses do not have their own this object, they must first call the super() method of the superclass.

Conclusion:

  1. ES6 Class extends is an ES5 inherited syntactic sugar.

  2. JS inheritance is based on prototype chains except constructor inheritance.

  3. You can implement ES6 Class extends with parasitic composite inheritance, but there are subtle differences.

reference

  1. The various ways in which JavaScript extends inheritance and the pros and cons
  2. There are eight inheritance schemes commonly used in JavaScript
  3. An article understanding JS inheritance — prototype chains/constructors/combinations/primitives/parasitic/parasitic combinations /Class extends
  4. Js inheritance, constructor inheritance, prototype chain inheritance, combinatorial inheritance, combinatorial inheritance optimization, parasitic combinatorial inheritance)