Based on eight Common JavaScript Inheritance Schemes, this article details the principle analysis and code comments, starting with the prototype chain and working down to the extends of ES6.

Prototype chain inheritance

This is what you all know:

// The parent class has two instance attributes
function Parent(name) {
  this.name = name
  this.relation = ['grandpa'.'grandma']}// A prototype method
Parent.prototype.say = function () {/ *... * /}
// Subclasses have no instance attributes or methods
function Child() {}/ / inheritance
p = new Parent('father')
Child.prototype = p

c1 = new Child()
c2 = new Child()
// Call attributes and methods on subclass instances if they are not defined on subclass instances
// The parent instance and the prototype are searched through the prototype chain
// Call the parent's prototype method say() and instance properties name, relation
c1.say()
console.log(c1.name, c2.relation)
// In this case
// When modifying the instance properties of the parent class directly
// Or modify the attributes of the parent class instance through the subclass instance
p.name = 'mother'
c1.relation.push('grandson')
// All instances of subclasses that inherit from the parent class are affected
console.log(c1.name, c2.relation)
Copy the code

Disadvantages of prototype chain inheritance:

  • When you modify a property on a parent instance, the properties of all objects on the stereotype chain may be affected
  • A subclass instance can directly modify its inherited parent instance
  • When a subclass constructor is called, arguments cannot be passed to the parent class constructor

This is just a constructor, not a real class, but we’ll use that

In practice, inheritance is rarely implemented directly with prototype chains.

Borrow constructor inheritance

constructor stealing

Simply put, you call the superclass constructor using apply or call in the subclass constructor.

Call (this) from the subclass constructor and change the context to that of the subclass. This is a copy of the attributes of the subclass instance.

// The parent class has an instance attribute
function Parent(name) {
  this.name = name
}
// Subclasses have no instance attributes themselves, but borrow the instance of the parent class
function Child(name) {
  Parent.call(this, name)
}
c = new Child('child')
// Subclass c has its own name attribute
console.log(c)
Copy the code

When you use stereotype chain inheritance, if you access a property of a subclass instance and the subclass instance doesn’t have that property, then you look in the stereotype chain of the subclass instance, and if you find that the parent class has that property, then you get the value of the parent class, or the value of the stereotype chain. Similarly, if you modify it, it is also on the modified prototype chain.

By borrowing the constructor, the subclass instance has the property itself, so it doesn’t need to look up the stereotype chain.

In this way:

  • Arguments can be passed to the superclass constructor
  • Access to andThe parent class instanceProperties and methods with the same name, but these have been copied tocOn my own, noc.__proto__, so the modification does not affect other subclass instances
  • Because stereotype chains are not used, subclass instances cannot access properties and methods on superclass stereotype objects

Rarely used in practice.

It should be noted here that when inheritance is implemented, it is mainly for the following two parts:

  • Instance properties and methods on the parent instance
  • Properties and methods on superclass prototype objects

What I Talk about when I Talk about Succession

Combination of inheritance

That’s the stereotype chain inheritance + borrowing constructor.

Using the constructor inheritance method, subclass instances can’t access properties and methods on the parent stereotype object because there is no stereotype chain, so simply add the stereotype chain.

This is how combinatorial inheritance works:

  • useBorrowing constructorTo copy an instance of the parent classpProperty to the subclass instancec
  • Using the prototype chain method, the subclass instance is attached to the prototype chain, so that the subclass instance can also access the properties and methods on the parent class prototype object

Implementation:

// The parent class has several instance attributes
function Parent(name) {
  // Parent instance attributes
  this.first_name = name
  this.last_name = 'vue'
  this.version = '2.x'
}
// A prototype method
Parent.prototype.say = function () {
  console.log(`I am The ${this.last_name} The ${this.first_name}, version:The ${this.version}`)
}
f = new Parent('core')

/ / subclass
// 1. Borrow the constructor
function Child(name, version) {
  Parent.call(this, name)
  // Note that the parent constructor is called before defining the subclass instance's own properties
  // Otherwise subclass instance attributes are overwritten by parent instance attributes of the same name
  this.version = version
}
// 2. Prototype chain
Child.prototype = f
// Modify the constructor of the prototype object
Child.prototype.constructor = Child

// Create two new instances
c1 = new Child('router'.'3.x')
c2 = new Child('x'.'4.x')
print()
// Modify the parent instance, has no effect on the subclass instance
f.last_name = 'react'
print()
// Modifying one subclass instance has no effect on other subclass instances
c1.version = '5.x'
print()

function print() {
  console.log(f)
  console.log(c1)
  console.log(c2)
  c1.say()
}
Copy the code

This is common inheritance in practice.

There is a catch: a subclass instance will hold two copies of the parent instance’s data. Because of the prototype chain.

It’s a prototype chain. I added a prototype chain.

One copy of Parent. Call (this) is copied to the subclass instance C, and one copy of the Parent instance’s original data, located on C. __proto__. Although redundant, but the use of the effect does not have much impact. There is also a solution, which is parasitic combinatorial inheritance.

Primary inheritance

Create an instance of a subclass that inherits from the parent class, except that the subclass instance is empty and has no property methods.

In other words, create an empty object and attach it to another object’s prototype chain.

We encapsulate this inherited logic in the createObject function as follows:

function createObject(p){
  // Equivalent to the subclass constructor, empty
  function F() {}
  // Attach to the prototype chain of the parent class instance, which is actually the prototype chain inheritance
  F.prototype = p
  return new F()
}

function Parent() {
  this.name = 'Nicholas'
  this.friends = ["Shelby"."Court"."Van"]}var p = new Parent()

// get an empty object c that inherits p
var c = createObject(p)
Copy the code

The createObject() function above is just Object.create(), or object.create () is a more formal implementation of primitive inheritance.

Here is the polyfill of object.create () provided by MDN.

So far, it doesn’t feel very different from prototype chain inheritance. True, but its significance will come later in parasitic combinatorial inheritance.

Parasitic inheritance

It is an enhanced version of the original type inheritance.

CreateObjectPlus () encapsulates the createObjectPlus() function, generates the subclass instance, adds some attributes or methods, and returns:

function createObjectPlus(p){
  // Using the createObject function above, an instance of the subclass is generated
  var c = createObject(p)
  // Add a few attributes or methods to the subclass instance
  c.sayHi = function(){
    console.log("hi")}/ / return
  return c
}
Copy the code

boring

Parasitic combinatorial inheritance

That’s borrowing constructor inheritance + parasitic inheritance.

At the end of the section on borrowing constructors, we conclude with two points:

  • Properties and methods on the parent class instance
  • Properties and methods on superclass prototype objects

Composite inheritance does both of these things, but one drawback mentioned is that there are two copies of the parent instance’s data.

Of the two copies, the one copied to the subclass instance C via parent.call (this) is really needed, while the one on C.__proto__ is redundant and a side effect of the prototype chain.

To optimize this, add the subclass instance to the parent instance’s stereotype chain without having the parent instance’s properties and methods on the stereotype chain.

Two approaches can be thought of:

  • Create a parent class instance with no instance attributes
  • Let the subclass instance bypass the parent class instance and inherit the parent class’s prototype object directly

Now, remember what the effect of primitive inheritance/parasitic inheritance is, creating an instance of a subclass with no attributes? In this case, you can get an empty instance of the parent class by simply applying it to the parent class’s prototype object.

Next modify the composite inheritance code to use createObject() (or object.create ()) instead of using the new keyword when creating the parent class instance:

function Parent(name) {
  this.name = name
  this.age = 40
  this.relation = ['grandma'.'grandpa']
}
Parent.prototype.say = function () {
  console.log(this.name)
}

/ / inheritance
// Borrow the constructor
function Child(name) {
  Parent.call(this, name)
}
// Create a parent instance without instance attributes
p = Object.create(Parent.prototype)
// Establish a relationship between the subclass constructor Child and its prototype object (instance of the parent class)
p.constructor = Child
Child.prototype = p
Copy the code

This is the most mature approach, and ES6’s extends implementation is pretty much the same as parasitic composite inheritance.

Another method mentioned above is to have a subclass instance bypass the parent class instance and directly inherit the parent class’s prototype object. The problem with this approach is that subclass instance C actually becomes a sibling of superclass instance P, rather than an inherited relationship.

ES6 extends

As mentioned earlier, the extends core code of ES6 is basically the same as parasitic composite inheritance. So let’s look at the following code, which is a partial implementation of compiling extends into ES5 syntax using Babel:

Try it out for yourself in Babel’s online editor

function _inherits(subClass, superClass) {
  if (typeofsuperClass ! = ="function"&& superClass ! = =null) {
    throw new TypeError("Super expression must either be null or a function")}// This is parasitic inheritance, which allows the subclass instance to access properties and methods on the superclass prototype object
  // Create a parent class instance with no instance attributes, add a constructor attribute, and assign it to the subclass's prototype object
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      writable: true.configurable: true}})// In the case of parasitic combinatorial inheritance, you also need to make the instance attributes of the parent class have a copy on the subclass
  // We need to borrow the constructor, but it doesn't look like the previous constructor.
  if (superClass) _setPrototypeOf(subClass, superClass)
}
function _setPrototypeOf(subClass, superClass) {
  // Check whether the current environment has object. setPrototypeOf methods, if not, implement one
  _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(subClass, superClass) {
    // Set the subclass's __proto__ to its parent class
    subClass.__proto__ = superClass
    return subClass
  }
  return _setPrototypeOf(subClass, superClass)
}
Copy the code

As mentioned earlier, there are two parts to inheritance: those on the parent prototype object and those on the parent instance. Parasitic inheritance already implements the former, so the _setPrototypeOf() function presumably implements the latter.

But I don’t think this is like the Parent. Call (this) constructor.

Moving on to the rest of the extends parsed by Babel, there’s this:

// ...
_inherits(subClass, superClass); Subclass.__proto__ = superClass
function subClass() {
  _classCallCheck(this, subClass);
  / / a
  // Here the superClass is fetched with _getPrototypeOf, and then apply is applied
  return _possibleConstructorReturn(this, _getPrototypeOf(subClass).apply(this.arguments));
}
// ...
Copy the code

Suffice it to say that the implementation of extends is indeed pretty much the same as parasitic composite inheritance.

Mixin inheritance

In plain English, the properties of one object are copied to another object.

For example, use object. assign(target, source). This method copies the values of all enumerable properties from one or more source objects to the target object and returns the target object.

It is a shallow copy.

Add attributes of the parent class to a subclass instance by borrowing constructors, and add attributes of the parent class prototype object to a subclass instance by mixing:

function Mother() {
  this.a = 'mom'
}
Mother.prototype.comfort = function () {
  console.log("that's ok")}function Father() {
  this.b = 'dad'
}
Father.prototype.hit = function () {
  console.log("you bastard!")}function Me() {
  // Use constructor to get a and b instance attributes
  Mother.call(this)
  Father.call(this)}// Create an instance of Mother with no instance attributes
m = Object.create(Mother.prototype)
// Modify the prototype object for Me, now Me is on the prototype chain of the Mother instance
Me.prototype = m
// Modify the constructor
Me.prototype.constructor = Me
// Copy the Father attribute method to m
// Now, although the Me instance is not on the Father instance's prototype chain
// But you can also access the attribute methods on Father. Prototype
Object.assign(Me.prototype, Father.prototype)

me = new Me()
console.log(me)
Copy the code

In fact, since both instances of the parent class and the prototype object of the parent class are objects, it is also possible to use mixin directly when adding attributes of the parent class instance to a subclass instance. The above code can be modified to:

/** * Father Mother Me constructor */
// Skip object. create and put it directly in object. assign
m = Object.assign({}, Mother.prototype, Father.prototype)
Me.prototype = m

me = new Me()
console.log(me)
Copy the code

Make an advertisement

My other articles:

“Super detailed 10 kinds of sorting algorithm principle and JS implementation” “free for the website to add SSL certificate” “detailed new/bind/apply/call simulation implementation”