On the principle of prototype inheritance

Keywords: Javascript

In my last article, I learned that prototypes can be used to share methods.

Inheritance literally means that the attribute methods in the parent class are passed to the subclass, and the subclass does not need to write the same logical code repeatedly, which can greatly improve the readability and simplicity of the code. It is different from the class keyword provided by es6 that we can now create a class and use extensions to inherit the parent class.

Before es6, we created classes through constructor simulation. This method is called constructor inheritance. Because prototypes can share methods, we can also use prototype chain to realize inheritance, not only inherit methods, but also inherit properties on prototypes. After all, methods and properties on prototypes can be inherited by corresponding instance objects through prototype chain, So this method is called prototype chain inheritance.

However, in general, shared methods are defined on the prototype, and attribute inheritance is generally realized through constructors. This kind of inheritance using constructors to implement attributes and prototypes to implement methods is called composite inheritance. Combinatorial inheritance is also a common question in interviews because it can examine your understanding of prototypes and prototype chains.

The implementation method of composite inheritance is briefly described below:

The inheritance of attributes is through the constructor. In the child constructor, directly call the parent constructor, and modify this in the parent constructor to this in the child constructor through the call method, so as to achieve the purpose of inheriting the attributes of the parent constructor.

Here is the sample code:

//Define the parent constructor first
function Father(name,age){
  this.name=name,
    this.age=age
}

//Define a child constructor
function Son (name,age){
  Father.call(this,name,age)
}

var son=new Son('xm','18')
console.log(son)  //Son {name: "xm", age: "18"}

Note: when using the constructor to inherit attributes, you must pay attention to the pointing of this. Calling Father(name,age) directly can not achieve the desired results. Because if you direct Father(name,age), this in the parent constructor points to father. Inheritance is the attribute of the parent constructor called in the Son constructor. At this time, the parent constructor this should point to the child constructor this, so as to realize attribute inheritance. There are three ways to change the point of this: call, apply and bind. The differences and applications of the three methods will continue to be updated (I feel that learning is like this. I constantly find my own shortcomings in learning. Just expand knowledge points to learn 2333)

Using call to change the point of this is equivalent to the code in the sub constructor is as follows:

function Son (name,age){
  //Father.call(this,name,age)
  this.name=name,
  this.age=age
}

Because at this time, the first parameter of call passes in the target object to change this. Of course, this in the child constructor points to the constructor. At this time, this in the parent constructor is changed to this in the child constructor, which is equivalent to copying the code in the parent constructor to the child constructor. When there are many attributes to inherit, using constructor inheritance shows its advantages.

The inheritance of methods is realized through the prototype object. By making the prototype object of the child constructor prototype=new Father() equal to the construction instance of a parent constructor, son.prototype points to the construction instance of the parent constructor, and the construction instance of the parent constructor can point to the prototype object of the parent constructor through -- proto, The method of the parent constructor can be accessed through the prototype chain, so as to realize inheritance.

Here is the sample code:

  function Father(name, age) {
      this.name = name,
        this.age = age
  }
  Father.prototype.work = function () {
      console.log('What a nice day today!')
  }

  function Son(name, age) {
      Father.call(this, name, age)
  }


  Son.prototype = new Father();
  
  Son.prototype.constructor = Son;// If you modify the prototype object in the form of an object, you should use the constructor to refer back to the original constructor
  var son = new Son('xm', '18')
  son.work()  //What a nice day today!

tips: note that you cannot directly make Son.prototype=Father.prototype. In this case, the child prototype object points to the parent prototype object. In this case, the child prototype object modification will modify the parent prototype object together, which does not meet the development needs.

Look at the following error example code:

  function Father(name, age) {
      this.name = name,
        this.age = age
  }
  Father.prototype.work = function () {
      console.log('What a nice day today!')
  }

  function Son(name, age) {
      Father.call(this, name, age)
  }
  Son.prototype = Father.prototype;
  Son.prototype.constructor=Son;
  Son.prototype.score=function(){
    console.log('I need an exam')
  }
  var son = new Son('xm', '18')
  console.log(Son.prototype)//{work: ƒ, score: ƒ, constructor: ƒ}
  console.log(Father.prototype)//{work: ƒ, score: ƒ, constructor: ƒ}

You can see that defining the method for the prototype of the child constructor will affect the prototype of the parent constructor. This is because if the object assignment method is adopted, the prototype reference address of the parent constructor is given to the prototype of the child constructor. Both prototypes point to the same address, but when one of them changes, the prototype of the other will also change. This does not meet the development needs. At this time, we need to make the prototype object of the child constructor prototype=new Father() equal to the construction instance of a parent constructor. At this time, son.prototype points to the construction instance of the parent constructor. The construction instance of the parent constructor can point to the prototype object of the parent construction function through -- proto -- and access the methods of the parent constructor through the prototype chain. This avoids modifying the prototype of the child constructor and affecting the prototype object of the parent constructor.

This is the principle and method of implementing simulated class inheritance by composite inheritance. But composite inheritance also has disadvantages: it calls the parent class constructor twice and generates two instances (the subclass instance shields the one on the subclass prototype). The solution is parasitic composite inheritance. If you are interested, you can find out by yourself.

Posted by shanejeffery86 on Tue, 26 Oct 2021 20:43:33 -0700