JS is an object – oriented language, object – oriented three features: encapsulation, inheritance, polymorphism. Although JS does not have polymorphism, inheritance does exist, but JS inheritance only supports implementation inheritance, which is implemented through the prototype chain. We covered the prototype chain in the last part, so this article will focus on several inheritance methods in JS.

1. Use constructors to implement inheritance

The original inheritance of this approach, which is implemented very simply, is to implement the supertype constructor inside the subtype constructor. This can be done through call and apply.

function Father() {
  this.money = '$1';
}
function Son() {
  Father.call(this);
}
var son1 = new Son();
console.log(son1.money); / / $1
var son2 = new Son();
console.log(son2.money) / / $1
Copy the code

As can be seen above, this is a complete inheritance. The son inherited the money from the father. Although it is only one dollar, it is still love!

But there’s a problem with that. Let’s see:

function Father() {
  this.money = '$1';
}
Father.prototype.makeMoney = function() {
  console.log('Hard work')}function Son() {
  Father.call(this);
}
var son1 = new Son();
console.log(son1.money); / / $1
son1.makeMoney(); //Uncaught TypeError: son1.makeMoney is not a function
Copy the code

You see, this is a big problem. Although the son inherits the money from his father, he does not have the ability to make money from his father. Although one dollar is a large sum of money, but he does not have the ability to make money, he will spend it sooner or later! This is because methods are defined in constructors, so function reuse is out of the question, and methods defined on the prototype of a parent class are invisible to subtypes, leaving all types to use the constructor pattern. Because of this problem, techniques using constructors are usually not used alone. So how do you implement methods that inherit from a parent prototype? Which brings us to another way of implementing inheritance, inheritance with a prototype chain.

2. Prototype chain implements inheritance

Prototype chain is a special existence in JS, so how to implement inheritance in the prototype chain? Does the keyword new come to mind? What did New do? Here’s a classic interview question. Simply put:

  1. Create a new object
  2. Point the _proto_ of the new object to the constructor’s prototype object
  3. Assign the scope of the constructor to the new object (that is, this points to the new object)
  4. Execute the code in the constructor (add properties to the new object)
  5. Return the new object and number two will do the job. So inheriting methods from the superclass prototype chain we can write:
function Father() {
  this.money = '$1';
}
Father.prototype.makeMoney = function() {
  console.log('Hard work')}function Son() {}
Son.prototype = new Father();
var son1 = new Son(); / / $1
console.log(son1.money);
son1.makeMoney(); // Work hard
Copy the code

Perfect. The son inherits both the dollar and the way to earn it. But what seems to be overlooked is that the father seems to have two sons. Now the father has more than just money. Let’s write them as an array.

function Father() {
  this.goods = ['$1'.'Flashlight'.'the refrigerator];
}
Father.prototype.makeMoney = function() {
  console.log('Hard work')}function Son() {}
Son.prototype = new Father();
var son1 = new Son();
var son2 = new Son();
son2.goods.push('Bicycle');
console.log(son1.goods); // ["$1", "flashlight "," refrigerator ", "bicycle "]
son1.makeMoney(); // Work hard
console.log(son2.goods); // ["$1", "flashlight "," refrigerator ", "bicycle "]
son2.makeMoney(); // Work hard
Copy the code

It seems awkward that the eldest son found out about the younger son’s inheritance of the bicycle. This is because the stereotype attribute that contains the value of the reference type is shared by all instances. This is why we use constructors to define properties. When inheritance is implemented through a stereotype, the stereotype actually becomes an instance of another stereotype, and the original instance properties become the current stereotype properties.

In this example, the goods of the superclass is an array (reference data type), and each instance of the superclass has a property of Goods. When a subclass inherits the prototype of the superclass through the prototype chain, the prototype of the subclass is an instance of the superclass, so each subclass also has the property of goods. Similar to son.prototype.goods, but because the prototype property containing the reference type is shared by all instances, when SON1 changes goods, son2 is also modified.

3. Combination inheritance

Composite inheritance is a combination of constructor inheritance and prototype chain inheritance, combining the best of both.

function Father() {
  this.goods = ['$1'.'Flashlight'.'the refrigerator];
}
Father.prototype.makeMoney = function() {
  console.log('Hard work')}function Son() {
  Father.call(this);
}
Son.prototype = new Father();
var son1 = new Son();
var son2 = new Son();
son2.goods.push('Bicycle');
console.log(son1.goods); // ['$1', 'flashlight ',' refrigerator ']
son1.makeMoney(); // Work hard
console.log(son2.goods); // ['$1', 'flashlight ',' refrigerator ', 'bicycle ']
son2.makeMoney(); // Work hard
Copy the code

Finally, he fulfilled his youngest son’s dream of inheriting a bicycle alone.

4. Optimization of composite inheritance

Composite inheritance is the most classical inheritance method in JS, but there are some disadvantages in the above mentioned. You will find that when you create an instance of a subtype, you will create two instances of the parent class. Next, we will optimize this. In the above inheritance of borrowing prototype chain, the purpose of applying new keyword is to assign the prototype of the parent class to the subclass. Then, we can directly assign the prototype of the parent class to the subclass. Optimization 1 is as follows:

function Father() {  
  this.goods = ["The $1"."Flashlight"."Refrigerator"];
}
Father.prototype.makeMoney = function () {  
  console.log("Work hard");
};
function Son() {  
  Father.call(this);
}
Son.prototype = Father.prototype;
var son1 = new Son();
var son2 = new Son();
son2.goods.push("Bicycle"); console.log(son1.goods); / / /'$1'.'Flashlight'.'the refrigerator] son1.makeMoney(); // Work hard console.log(son2.goods); / / /'$1'.'Flashlight'.'the refrigerator.'Bicycle'] son2.makeMoney(); // Work hardCopy the code

The result is no problem, and it solves the problem of calling a parent instance twice, but:

Son1. Constructor ƒ Father () {this.goods = ["$1"."Flashlight"."Refrigerator"];
}Copy the code

Although the subclass is inherited from the parent class, but its instance is also an individual ah, son 1 and son 2 are sons ah, how suddenly become Lao tze, this is definitely not. So let’s optimize again, optimize 2:

function Father() {  
  this.goods = ["The $1"."Flashlight"."Refrigerator"];
}
Father.prototype.makeMoney = function () {  
  console.log("Work hard");
};
function Son() {  
  Father.call(this);
}
Son.prototype = Object.create(Father.prototype);
Son.prototype.constuctor = Son;
var son1 = new Son();
var son2 = new Son();
son2.goods.push("Bicycle"); console.log(son1.goods); / / /'$1'.'Flashlight'.'the refrigerator] son1.makeMoney(); // Work hard console.log(son2.goods); / / /'$1'.'Flashlight'.'the refrigerator.'Bicycle'] son2.makeMoney(); // Work hardCopy the code

That’s when the optimization is done.

5. Inheritance of the original type

Originally I thought it would be over when I wrote the last one, but after seeing the story of inheritance in “Elevation” and the underlying implementation principle of ES6 inheritance, I felt it was necessary to have a good understanding of this piece of knowledge.

As Gocheng says, the guy who came up with this idea of Inheritance was Douglas Crockford, and he wrote an article in 2006 called Prototypal Inheritance in Javascript, where he implemented a new type of Inheritance, Instead of using a constructor in the proper sense, this method of inheritance creates a base object and creates a new object from an existing base object using a prototype without creating a custom type. To do this, it creates the following functions:

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

It creates a temporary constructor, assigns the object passed in to the prototype of the temporary constructor, and returns a new instance of the temporary constructor. Look at this example:

var father = {
  money: '$1'.goods: ['Flashlight'.'the refrigerator]}var son1 = object(father);
var son2 = object(father);
son2.goods.push('Bicycle');
console.log(father.goods); // [' flashlight ', 'refrigerator ', 'John',' bicycle ']
Copy the code

After all, it is inherited through a stereotype, so there are still problems with stereotype inheritance. Properties of reference types are shared, but do you feel that this method is similar to object.create()? Yes, object.create() is based on this method.

var father = {
  money: '$1'.goods: ['Flashlight'.'the refrigerator]}var son1 = Object.create(father);
var son2 = Object.create(father);
son2.goods.push('Bicycle');
console.log(father.friends); // [' flashlight ', 'refrigerator ', 'John',' bicycle ']
Copy the code

What makes object.create () different is that it takes a second argument, which is an optional value. When you set the second argument, you can specify that any property will override the property of the same name on the stereotype.

Parasitic inheritance

This way of inheritance is also proposed by Douglas Ryan crocker ford, some similar to borrow the constructor inheritance, but also he USES the idea of a temporary constructor, and the so-called parasitic thought, will be an object as a benchmark, and parasitic on the object, so you can have all of the object’s properties and methods, encapsulated into a function, Add new properties and methods to the copy of the object inside the function.

function createAnother(o) {
  var clone = object(o);
  clone.sayHi = function() {
    console.log('hi');
  };
  return clone;
}
Copy the code

But in this case, the inability to do function reuse greatly reduces efficiency.

7. Parasitic combinatorial inheritance

The above mentioned combination inheritance is the most classical inheritance, but there is an important problem that the constructor of the parent class is executed twice. Well, we can solve this problem with parasitic combinatorial inheritance.

We refer to the Elevation for an explanation of parasitic combinatorial inheritance: properties are inherited by borrowing constructors and methods are inherited by a mixed form of the prototype chain. The idea is that instead of calling a supertype constructor for a subtype stereotype, all we need is a copy of the supertype stereotype. In essence, you use parasitic inheritance to inherit the stereotype of the supertype and then assign the result to the stereotype of the subtype. As follows:

function inheritPrototype(father, son) {
  var prototype = object(father.prototype);
  prototype.constructor = son; 
  son.prototype = prototype; 
}
Copy the code

This example implements the simplest parasitic combinatorial inheritance. It takes two arguments, the constructor of the superclass and the constructor of the subclass. The first step inside the function is to create a copy of the stereotype of the parent type. The second step is to add a new copy to constructor to compensate for the default Constuctor property lost by refactoring the stereotype. The third step is to assign the new copy to the stereotype of the child type.

This ensures that the constructor of the superclass is executed only once.

function Father() {
  this.money = '$1';
}
Father.prototype.makeMoney = function() {
  console.log('Hard work');
}
function Son() {
  Father.call(this);
}
inheritPrototype(Father, Son);
Copy the code

8, ES6 inheritance class… extends…

When I talked about inheritance, I just thought it was a grammar sugar, and it was very convenient to use, but I saw the principle of its underlying implementation when I consulted the materials for this article. This is also to write the original type inheritance, parasitic inheritance, parasitic combinatorial inheritance reason.

Let’s look at THE ES6 inheritance first:

class Father {
  constructor() {
    this.money = '$1';
    this.goods = ['Flashlight'.'the refrigerator];
  }
  makeMoney = function() {
    console.log('Hard work'); }}class Son extends Father {
  constructor() {
    super()}}Copy the code

After converting to ES5 with Bable:

"use strict";

function _possibleConstructorReturn(self, call) {
  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; }function _inherits(subClass, superClass) {
  if (typeofsuperClass ! = ="function"&& superClass ! = =null) {
    throw new TypeError(
      "Super expression must either be null or a function, not " +
        typeof superClass
    );
  }
  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);
}

function _classCallCheck(instance, Constructor) {
  if(! (instanceinstanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function"); }}var Father = function Father() {
  _classCallCheck(this, Father);

  this.makeMoney = function () {
    console.log("Work hard");
  };

  this.money = "$1";
  this.goods = ["Flashlight"."Refrigerator"];
};

var Son = (function (_Father) {
  _inherits(Son, _Father);

  function Son() {
    _classCallCheck(this, Son);

    return _possibleConstructorReturn(
      this,
      (Son.__proto__ || Object.getPrototypeOf(Son)).call(this)); }return Son;
})(Father);
Copy the code

We can see that when we create a class, when we convert it from ES6 to ES5, we have an extra function called _classCallCheck, which is a check that takes two arguments, the first one is this, and the second one is the constructor that determines if this is an instance of this constructor, If it is not, it raises the Cannot call a class as a function exception.

ES6’s inheritance converts to ES5 and finds that the subclass is a self-executing function that passes the parent class as an argument. The first is the _inherits function, which also takes two arguments, the subclass constructor and the superclass constructor. If you look at the internal structure,

References:

Advanced Programming in JavaScript (3rd edition)