inheritance

Inheritance implementation: Explore 6 kinds of common JS inheritance methods

Consider a few questions:

  • How many ways are there to implement JS inheritance?
  • In what way is the extends keyword implemented in ES6?

Concept of inheritance

Inheritance allows subclasses to have the same methods and attributes as their parent class, such as in the example above where “car” and “van” inherit the attributes of the car, respectively, without having to define the attributes of the car in “car” again.

JS implementation of several ways of inheritance

  • Prototype chain inheritanceChild. Prototype = new parent(), void multiple new functions, shared prototype object
  • Constructor inheritance (with call)Parent.call (this), which inherits properties and methods from the instance but not from the parent stereotype
  • Combinatorial inheritance (the first two combinations)The parent. The call (this), child. The prototype = new parent (), child. The prototype. Contstrutor = child. Solved the problem of shared objects and inherited the properties and methods of prototype, shortcomings of execution 2 times, performance overhead
  • Primary inheritanceparent = Object.create(child); Basic inheritance of ordinary objects, not only can inherit attributes, like shallow copy, shared objects
  • Parasitic inheritanceparent = Object.create(child); Let's add another method
  • Parasitic combinatorial inheritance parent.call(this); child.prototype=Object.assign(parent.prototype),child.prototype.contrutor=child; var a = new child()

Child.prototype = new parent()

Prototype chain inheritance is one of the common inheritance methods, which involves the constructor, prototype and instance, there is a certain relationship between the three, that is, each constructor has a prototype object, the prototype object contains a pointer to the constructor, and the instance contains a pointer to the prototype object.

  function Parent1() {
    this.name = 'parent1';
    this.play = [1.2.3]}function Child1() {
    this.type = 'child2';
  }
  Child1.prototype = new Parent1();
  console.log(new Child1());
Copy the code

Disadvantages:

  var s1 = new Child2();
  var s2 = new Child2();
  s1.play.push(4);
  console.log(s1.play, s2.play);
Copy the code

I only changed the play property of S1, so why did S2 change too? The reason is simple, because both instances use the same stereotype object. Their memory space is shared, and when one changes, the other changes, which is a disadvantage of using prototype chain inheritance.

To solve this problem, we need to look at other inheritance methods. Let’s look at a second approach to solving the stereotype property sharing problem.

Second: Constructor inheritance (with call)

  function Parent1(){
    this.name = 'parent1';
  }

  Parent1.prototype.getName = function () {
    return this.name;
  }

  function Child1(){
    Parent1.call(this); // This refers to Child1. Call causes Child1 to inherit Parent1's methods and attributes
    this.type = 'child1'
  }

  let child = new Child1();
  console.log(child);  / / no problem
  console.log(child.getName());  / / complains

Copy the code

By executing the code above, you get something like this.

You can see that the last printed child is displayed on the console, inheriting the Parent1 attribute name in addition to the type attribute of Child1. In this way, the subclass can get the attribute values of the parent class, which solves the drawbacks of the first method of inheritance. However, the problem is that once there are methods defined by the parent class in the prototype object, the subclass cannot inherit those methods. The console execution result in this case is shown below.

Therefore, the above results show the advantages and disadvantages of the constructor implementation inheritance. It makes the reference attributes of the parent class not shared, which optimizes the disadvantages of the first method of inheritance. However, there is an obvious disadvantage that only instance properties and methods of the parent class can be inherited, not stereotype properties or methods.

The above two inheritance methods have their own advantages and disadvantages, so combining the advantages of the two, the following combination inheritance method is generated.

Third: Combinatorial inheritance (the first two combinations)

This method combines the advantages and disadvantages of the previous two inheritance methods. The combined inheritance code is as follows.

  function Parent3 () {
    this.name = 'parent3';
    this.play = [1.2.3];
  }

  Parent3.prototype.getName = function () {
    return this.name;
  }
  function Child3() {
    // Call Parent3() for the second time
    Parent3.call(this);
    this.type = 'child3';
  }

  Call Parent3() for the first time
  Child3.prototype = new Parent3();
  // Hang the constructor manually and point to your own constructor
  Child3.prototype.constructor = Child3;

  var s3 = new Child3();
  var s4 = new Child3();
  s3.play.push(4);
  console.log(s3.play, s4.play);  // Do not affect each other
  console.log(s3.getName()); // Normal output 'parent3'
  console.log(s4.getName()); // Normal output 'parent3'
Copy the code

But here’s a new problem: Parent3 (Child3, Child3, Child3); Parent3 (Child3, Child3, Child3, Child3, Child3); This is not what we want to see.

4. Primitive inheritance (Object.create)

I have to mention the object.create method in ES5, which takes two parameters: an Object to be used as a prototype for the new Object, and an Object (optional) to define additional properties for the new Object.

  let parent4 = {
    name: "parent4".friends: ["p1"."p2"."p3"].getName: function() {
      return this.name; }};let person4 = Object.create(parent4);

  person4.name = "tom";
  person4.friends.push("jerry");
  let person5 = Object.create(parent4);
  person5.friends.push("lucy");

  console.log(person4.name);
  console.log(person4.name === person4.getName());
  console.log(person5.name);
  console.log(person4.friends);
  console.log(person5.friends);
Copy the code

As you can see from the above code, the Object. Create method can inherit from ordinary objects, not only the attributes, but also the getName method.

The first result, “Tom,” is easier to understand, and person4 inherits the parent4 name attribute, but customizes it from there.

The second is the inherited getName method that checks if its name is the same as the value in the property, and the answer is true.

The third result, “parent4,” is also easier to understand. Person5 inherits the name attribute of parent4 without overwriting it, so it prints the attributes of the parent object.

The Object. Create method can be used to implement shallow copies of some objects.

Then the disadvantages of this method of inheritance are also obvious. Multiple instances of reference type attribute point to the same memory, there is the possibility of tampering. Next, let’s take a look at another inheritance method optimized on the basis of this inheritance — parasitic inheritance.

Fifth: Parasitic inheritance (Object.create and add methods)

The method of inheritance is called parasitic inheritance, in which a shallow copy of the target object is obtained using the original inheritance, and then the power of the shallow copy is enhanced by adding some methods.

Although it has the same advantages and disadvantages as original inheritance, parasitic inheritance still adds more methods to the parent class than original inheritance for common object inheritance. So let’s see how the code works.

   let parent5 = {
    name: "parent5".friends: ["p1"."p2"."p3"].getName: function() {
      return this.name; }};function clone(original) {
    let clone = Object.create(original);
    clone.getFriends = function() {
      return this.friends;
    };
    return clone;
  }

  let person5 = clone(parent5);

  console.log(person5.getName());
  console.log(person5.getFriends());
Copy the code

From the code above, we can see that person5 is an instance generated by parasitic inheritance, and not only does it have a getName method, but it also has a getFriends method at the end, as shown in the figure below.

As you can see from the final output, person5 uses the Clone method to add the getFriends method, thus adding another method to the generic object’s inheritance process, which is called parasitic inheritance.

Parasitic combinatorial inheritance addresses some of the drawbacks I mentioned in the third combinatorial inheritance approach above, namely the waste of calling the parent class’s constructor twice.

The sixth kind: parasitic combinatorial inheritance

Combined with the inheritance method mentioned in the fourth method and the Object. Create method to solve the inheritance problem of common objects, we reformed the advantages and disadvantages of the previous inheritance methods and obtained the parasitic combination inheritance method, which is also the relatively optimal inheritance method among all inheritance methods. The code is as follows.

function clone(parent, child) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.construtor = child
}

 function parent() {
    this.name = 'parent6';
    this.play = [1.2.3];
 }
	parent.prototype.getName = function () {
    return this.name;
  }
	
  function child() {
    parent.call(this) // Inherit function methods and attributes
    this.friends = 'child5'
  }

  clone(parent, child) // Inherit the parent's prototype

	child.prototype.getFriends = function() {
    return this.friends
  }

	let a = new child();

  console.log(person6);
  console.log(person6.getName());
  console.log(person6.getFriends());

Copy the code

It can be seen from this code that this parasitic combinatorial inheritance method can basically solve the shortcomings of the previous inheritance methods, better achieve the desired results of inheritance, but also reduce the number of construction, reduce the cost of performance. Let’s take a look at the execution results of the above code.

You can see the result printed by Person6, where the properties are inherited and the methods are fine, and the expected results are printed.

On the whole, parasitic combinatorial inheritance is the best inheritance among the six inheritance modes. In addition, ES6 provides the extends keyword for inheritance, so let’s take a look at the logic behind extends that implements inheritance.

The extends keyword of ES6 implements logic

Using the extends sugar in ES6, it’s easy to implement JavaScript inheritance directly with keywords, but if you want to understand how extends sugar is implemented, you have to dig into the underlying logic of extends.

Let’s take a look at how inheritance can be implemented directly using extends.

class Person {
  constructor(name) {
    this.name = name
  }

  // Prototype method
  Prototype. GetName = function() {}
  GetName () {... }
  getName = function () {
    console.log('Person:'.this.name)
  }
}

class Gamer extends Person {
  constructor(name, age) {
    // If there is a constructor in a subclass, you need to call super() first before using "this".
    super(name)
    this.age = age
  }
}
const asuna = new Gamer('Asuna'.20)
asuna.getName() // Successfully access the method of the parent class
Copy the code

Because of browser compatibility issues, if you encounter a browser that does not support ES6, you will need to use Babel to compile ES6 code into ES5, so that some browsers that do not support the new syntax can run.

So what does extends look like in the end? Let’s take a look at the translated code snippet.

function _possibleConstructorReturn (self, call) { 
		// ...
		return call && (typeof call === 'object' || typeof call === 'function')? call : self; }function _inherits (subClass, superClass) { 
    // It can be seen here

	subClass.prototype = Object.create(superClass && superClass.prototype, { 
		constructor: { 
			value: subClass, 
			enumerable: false.writable: true.configurable: true}});if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 

}

var Parent = function Parent () {
	// Verify if Parent constructs this
	_classCallCheck(this, Parent);
};

var Child = (function (_Parent) {
	_inherits(Child, _Parent);
	function Child () {
		_classCallCheck(this, Child);
		return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this.arguments));
}
	return Child;
}(Parent));

Copy the code

As can be seen from the compiled source code above, it also adopts the parasitic combination inheritance method, so it also proves that this method is a better way to solve inheritance.

This is the end of JavaScript inheritance.

conclusion

I’ve made a summary of JavaScript inheritance in the following brain map, so you can review the lecture more clearly.

Object. Create is used to divide the different inheritance methods. Finally, parasitic combinatorial inheritance is the optimal inheritance method modified by combinatorial inheritance.