current position:Home>Learning notes on JavaScript advanced programming (II) - six ways of JS inheritance

Learning notes on JavaScript advanced programming (II) - six ways of JS inheritance

2021-08-26 17:43:25 stan

JavaScript Six ways of inheritance

Many object-oriented languages support Interface inheritance and Implementation inheritance Two ways of inheritance , The former only inherits the method signature , The latter inherits the actual method . Interface inheritance in ECMAScript It's impossible , Because the function has no signature . Implementing inheritance is ECMAScript The only way to support inheritance , This is mainly through Prototype chain Realized .

1. Prototype chain

1.1 Ideas

ECMAScript-262 The prototype chain is defined as ECMAScript The main way to inherit . The basic idea is this Inherit properties and methods of multiple reference types through prototypes .

Constructors 、 Relationship between prototype and instance : Each constructor has a prototype object , The prototype has a property that refers back to the constructor associated with it , And the instance has an internal pointer to the prototype .

Prototype chain : If prototype is an instance of the another type , Then the prototype itself has an internal pointer to another prototype , Correspondingly, another prototype also has a pointer to another constructor . In this way, a prototype chain is constructed between the instance and the prototype .

function SuperType () {
  this.property = true;
}

SuperType.prototype.getSuperValue = function () {
  return this.property;
}

function SubType () {
  this.subproperty = false;
}

//  Inherit  SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function () {
  return this.subproperty;
}

let instance = new SubType();
console.log(instance.getSuperValue()); // true

SubType By creating a SuperType And assign it to its own prototype SubType.prototype Realized with SuperType Inheritance , This assignment overrides Subtype The original prototype , Replace it with SuperType Example , namely SuperType All properties and methods that the instance can access will also exist in SubType.prototype, At the same time instance( Through internal [[Prototype]]) Point to SubType.prototype, and SubType.prototype( As SuperType The instance of [[Prototype]]) Point to SuperType.prototype.

 Prototype chain .png

Be careful :

  1. getSuperValue The method is still SuperType.prototype On the object , and property Property is in SubType.prototype On . because getSuperValue Is a prototype method , and property Is an instance property .SubType.prototype Now it is SuperType An example of , Therefore, it will be stored on it property attribute .
  2. because SubType.prototype Of constructor Property is overridden to point to SuperType, therefore instance.constructor Point to SuperType.
  3. When inheritance is implemented through the prototype chain , The search process continues up the prototype chain , call instance.getSuperValue Will search in turn instanceSubType.prototype and SuperType.prototype. When a property or method cannot be found , The search process never stops until the end of the prototype chain .

1.2 Expand

  1. The default prototype

    By default , All reference types inherit from Object, This is also achieved through the prototype chain . The default prototype of any function is a Object Example , That is, this instance has an internal pointer to Object.prototype. Therefore, custom types can inherit including toStringvalueOf All default methods including .

     Complete prototype chain .png

  2. Relationship between prototype and inheritance

    The relationship between prototype and inheritance can be determined in two ways :

    • instanceof The operator : If the corresponding constructor appears in the prototype chain of an instance , be instanceof Operator return true.

      console.log(instance instanceof Object); // true
      console.log(instance instanceof SuperType); // true
      console.log(instance instanceof SubType); // true
    • isPrototypeOf Method : When the prototype in the prototype chain calls this method and passes in the instance, it returns true.

      console.log(Object.prototype.isPrototypeof(instance)); // true
      console.log(SuperType.prototype.isPrototypeof(instance)); // true
      console.log(SubType.prototype.isPrototypeof(instance)); // true
  3. About the way

    • If you want to override the methods of the parent class or add methods that the parent class does not have , It must be added to the prototype after the prototype assignment .

      function SuperType () {
        this.property = true;
      }
      
      SuperType.prototype.getSuperValue = function () {
        return this.property;
      }
      
      function SubType () {
        this.subproperty = false;
      }
      
      //  Inherit  SuperType
      SubType.prototype = new SuperType();
      
      //  The new method 
      SubType.prototype.getSubValue = function () {
        return this.subproperty;
      }
      
      //  Overwrite existing methods 
      SubType.prototype.getSuperValue = function () {
        return false;
      }
      
      let instance = new SubType();
      console.log(instance.getSuperValue()); // false
    • Creating prototypes in the literal way of objects breaks the chain of prototypes , Equivalent to rewriting the prototype chain , Set the prototype to a Object Example .

      function SuperType () {
        this.property = true;
      }
      
      SuperType.prototype.getSuperValue = function () {
        return this.property;
      }
      
      function SubType () {
        this.subproperty = false;
      }
      
      //  Inherit  SuperType
      SubType.prototype = new SuperType();
      
      SubType.prototype = {
        getSubValue () {
          return this.subproperty;
        }
          
        someOtherMethod () {
          return false;
        }
      }
      
      let instance = new SubType();
      console.log(instance.getSuperValue()); // error
  4. The problem of prototype chain

    • The main problem is when the prototype contains reference values , The reference value is shared between instances , This is why attributes are usually defined in constructors rather than on prototypes .

      function SuperType () {
        this.colors = ['red', 'blue', 'green'];
      }
      
      function SubType () {}
      
      SubType.prototype = new SuperType();
      
      let instance1 = new SubType();
      instance1.colors.push('black');
      let instance1 = new SubType();
      
      console.log(instance1.colors); // 'red', 'blue', 'green', 'black'
      console.log(instance2.colors); // 'red', 'blue', 'green', 'black'

      When SubType Inheritance through prototype SuperType after ,SubType.prototype become SuperType An example of , So he got his own colors attribute , Similar to creating SubType.prototype.colors attribute , so SubType All instances of will share this colors attribute .

    • When instantiating a subtype, you cannot pass parameters to the constructor of the parent type .

2. Stealing constructors

Stealing constructors (constructor stealing) Technology is also known as object camouflage or classic inheritance .

2.1 Ideas

The basic idea is to call the parent class constructor in the subclass constructor . Because a function is a simple object that executes code in a specific context , So you can use apply and call Method executes the constructor in the context of the newly created object .

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

function SubType () {
  //  Inherit  SuperType
  SuperType.call(this);
}

let instance1 = new SubType();
instance1.colors.push('black');
let instance1 = new SubType();

console.log(instance1.colors); // 'red', 'blue', 'green', 'black'
console.log(instance2.colors); // 'red', 'blue', 'green'

SuperType The constructor is working for SubType Execute in the context of a new object created by an instance of , Equivalent to new SubType Object SuperType All initialization code in the constructor , That is, each instance will have its own colors attribute .

2.2 Expand

  1. Pass parameters

    The advantage of stealing constructors is that you can pass parameters to the parent constructor in the child constructor .

    function SuperType (name) {
      this.name = name;
    }
    
    function SubType () {
      SuperType.call(this, 'Stan');
      this.age = 24;
    }
    
    let instance = new SubType();
    console.log(instance.name); // 'Stan'
    console.log(instance.age); // 24

    stay SubType Invocation in constructor SuperType Pass in parameters when constructor , In fact, it will SubType Defined on an instance of name attribute .

    To ensure SuperType The constructor does not override SubType Properties defined , You can add additional properties to the subclass instance after calling the parent class constructor .

  2. problem

    The main disadvantage of stealing constructors is also the problem of using constructor patterns to customize types , That is, the method must be defined in the constructor , So functions can't be reused . Besides , Subclasses also cannot access methods defined on the parent prototype , So all types can only use constructor mode .

3. Combination inheritance

Composite inheritance is also called pseudo classical inheritance , It combines the advantages of prototype chain and stealing construction function tree . Combinatorial inheritance makes up for the shortcomings of prototype chain and embezzlement of constructors , yes JavaScript The most used inheritance pattern in . meanwhile , Composite inheritance is also preserved instanceof Operators, and isPrototypeOf The ability of methods to recognize synthetic objects .

3.1 Ideas

The basic idea of combinatorial inheritance is to inherit the attributes and methods on the prototype using the prototype chain , By stealing the constructor to inherit instance properties .

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

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

function SubType (name, age) {
  //  Inheritance attribute 
  SuperType.call(this, name);
  
  this.age = age;
}

//  Inheritance method 
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
  console.log(this.age);
}

let instance1 = new SubType('Stan', 24);
instance1.colors.push('black');
console.log(instance1.colors); // 'red', 'blue', 'green', 'black'
instance1.sayName(); // 'Stan'
instance1.sayAge(); // 24

let instance2 = new SubType('Greg', 20);
console.log(instance2.colors); // 'red', 'blue', 'green'
instance2.sayName(); // 'Greg'
instance2.sayAge(); // 20

3.2 Expand

Combinatorial inheritance also has efficiency problems , The most important thing is that the parent constructor will be called twice , The first is called when you create a subclass prototype , The second time is to call in the subclass constructor. . Essentially , The subclass prototype ultimately contains all the instance properties of the superclass object , Just override its prototype when the subclass constructor executes .

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

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

function SubType (name, age) {
  //  Second call 
  SuperType.call(this, name);
  
  this.age = age;
}

//  First call 
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
  console.log(this.age);
}

After the above code is executed ,SubType.prototype There will be name and colors Two attributes , Both are SuperType Instance properties for , Now it becomes SubType Prototype properties of . Calling SubType Constructor , Will also call SuperType Constructors , A new object is created on the new object name and colors Two instance properties , Two properties with the same name on the prototype will be masked .

 Combination inheritance 1.png

 Combination inheritance 2.png

The solution to the efficiency problem of combinatorial inheritance is parasitic combinatorial inheritance .

4. Original pattern inheritance

4.1 Ideas

first , Original pattern inheritance (prototypal inheritance) Is an inheritance method that does not involve constructors in the strict sense , The starting point is that information sharing between objects can be realized through prototypes even without custom types .

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

object Function by creating a temporary constructor , And assign the passed in object to the prototype of this constructor , Finally, an instance of this temporary type is returned , Implementation inheritance . Essentially object The function performs a shallow copy of the incoming object .

let person = {
  name: 'Stan',
  friends: ['xiaoming', 'xiaohong']
};

let anotherPerson = object(person);
anotherPerson.name = 'xiaobai';
anotherPerson.friends.push('xiaohei');

let yetAnotherPerson = object(person);
yetAnotherPerson.name = 'xiaohei';
yetAnotherPerson.friends.push('xiaohei');

console.log(person.friends); // 'xiaoming', 'xiaohong', 'xiaobai', 'xiaohei'

Prototype inheritance applies when you have an object and want to create a new object based on it . Pass this object to object function , Then modify the returned object appropriately .

4.2 Expand

  1. Object.create Method

    ECMAScript5 Added Object.create Method to standardize the concept of prototype inheritance .Object.create Method accepts two parameters , Objects as prototypes of new objects , And objects that define additional properties for new objects ( Optional ). When there is only the first parameter ,Object.create Method and previous object The effect of the method is the same .

    let person = {
      name: 'Stan',
      friends: ['xiaoming', 'xiaohong']
    }
    
    let anotherPerson = Object.create(person);
    anotherPerson.name = 'xiaobai';
    anotherPerson.friends.push('xiaohei');
    
    let yetAnotherPerson = Object.create(person);
    yetAnotherPerson.name = 'xiaohei';
    yetAnotherPerson.friends.push('xiaohei');
    
    console.log(person.friends); // 'xiaoming', 'xiaohong', 'xiaobai', 'xiaohei'

    Object.create The second parameter of the method is Object.defineProperties The second parameter of the method is the same , Each new attribute is described by its own descriptor .

    let person = {
      name: 'Stan',
      friends: ['xiaoming', 'xiaohong']
    }
    
    let anotherPerson = Object.create(person, {
      name: {
        value: 'Greg'
      }
    });
    
    console.log(anotherPerson.name); // 'Greg'
  2. problem

    Archetypal inheritance applies to constructors that do not need to be created separately , But there are still situations where information needs to be shared between objects . But like the prototype chain , The reference value contained in the property is always shared between related objects .

5. Parasitic inheritance

Parasitic inheritance (parasitic inheritance) It is a kind of inheritance method that is close to prototype inheritance .

5.1 Ideas

The idea of parasitic inheritance is similar to parasitic constructors and factory patterns , That is, create a function that implements inheritance , Enhance objects in some way , Finally, return this object .

function createAnother (original) {
  //  Create a new object 
  let clone = object(original);
  //  Enhance objects in some way 
  clone.sayHi = function () {
    console.log('hi');
  };
  return clone;
}

let person = {
  name: 'Stan'
}

let anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'hi'

5.2 Expand

  • Parasitic inheritance also applies to the main object of interest , Regardless of the type and constructor scenarios .
  • object Functions are not required for parasitic inheritance , You can use any function that returns a new object .
  • Adding functions to objects through parasitic inheritance can make functions difficult to reuse , Similar to the constructor pattern .

6. Parasitic combination inheritance

Parasitic composite inheritance inherits properties by stealing constructors , The inheritance method uses a hybrid chain .

The basic idea of parasitic composite inheritance is not to assign a value to the subclass prototype by calling the parent class constructor , Instead, take a copy of the parent prototype , That is, parasitic inheritance is used to inherit the parent class prototype , Then assign the returned new object to the subclass prototype .

function inheritPrototype (SubType, SuperType) {
  let prototype = object(SuperType.prototype);
  prototype.constructor = SubType;
  SubType.prototype = prototype;
}

inheritPrototype Function implements the core logic of parasitic combinatorial inheritance . The function receives two parameters: the subclass constructor and the parent constructor . Inside the function , First, create a copy of the parent class prototype ; The second is to return prototype Object settings constructor attribute , Solve the default problem caused by rewriting the prototype constructor Lost problems ; Assign the newly created object to the prototype of the subtype .

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

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

function SubType (name, age) {
  SuperType.call(this, name);
  this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function () {
  console.log(this.age);
};

When using parasitic composite inheritance , The prototype chain remains the same ,instanceof Operators, and isPrototypeOf The method is normal and effective . Parasitic composite inheritance is the best mode of reference type inheritance .

copyright notice
author[stan],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210826174248717O.html

Random recommended