Six kinds of inheritance in JS: prototype chain, stealing constructor, combinatorial inheritance, prototype inheritance, parasitic inheritance and combinatorial parasitic inheritance

Posted by jcrensha627 on Wed, 10 Nov 2021 21:57:18 +0100

Six kinds of inheritance in graphic JS: prototype chain, stealing constructor, combinatorial inheritance, prototype inheritance, parasitic inheritance and combinatorial parasitic inheritance

inherit

There are two types of inheritance: Interface Inheritance and Implementation Inheritance. Interface Inheritance requires method signature. JS has no method signature, so the next six types of inheritance are Implementation Inheritance.

Inheritance essentially means that one object reuses the attributes and methods of another object. Note that the reuse effects of attributes and methods are different. If you understand inheritance from this point, you can understand why six kinds of inheritance occur.

Prototype Chaning

First, let's talk about the prototype chain:

function Animal(){
    this.names = ['animal', 'zoom', 'beast', 'creature']
    this.say = function(index){
        console.log(`I am ${this.names[index]}`)
    }
}

var oneAnimal = new Animal()
oneAnimal.say(1) // I am zoom

Each method has a prototype. We can mount properties or methods in the prototype chain. The prototype is shared between the method and the instance generated by the new method:

function Animal(){
}
Animal.prototype.names = ['animal', 'zoom', 'beast', 'creature']
Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}

var oneAnimal = new Animal()
oneAnimal.say(1) 
//> I am zoom

The biggest advantage of prototype chain is that instances can be reused:

var anotherAnimal = new Animal()
anotherAnimal.say(2)
//> I am beast

Next, let's talk about how the prototype chain inherits:

function Animal(){
}
Animal.prototype.names = ['animal', 'zoom', 'beast', 'creature']
Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}

function Cat(){
    this.whiskers = 8
}

// Inherit from Animal
Cat.prototype = new Animal()

var kitten = new Cat()
kitten.say(2) //> I am beast

There are two major problems in the prototype chain. One is that reference values are used mutually between instances:

var kitten = new Cat()
var oldCat = new Cat()
kitten.names.push('kitten')
oldCat.say(4) //> I am kitten

The second problem is that when creating an instance, you cannot pass parameters to the parent constructor.

Stealing constructor steaming

In order to solve the problem of reference value and the inability to pass parameters to the parent class, developers began to use a method called stealing constructor, which also became object camouflage or classic inheritance.

function Animal(myName){
    this.names = ['animal', 'zoom', 'beast', 'creature']
    this.names.push(myName)
}
Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}

function Cat(myName){
    Animal.apply(this, [myName])
}

Cat.prototype = new Animal()

var kitten = new Cat('kitten')
kitten.say(4) //> I am kitten

Stealing a constructor is a very interesting metaphor. It means that the most important purpose of using the constructor of the parent class as its own constructor is to make a copy of the attribute, that is, copy the reference value, so that you don't have to worry about modifying the reference value between instances. At the same time, when the parent constructor is referenced, the parameter is passed, which also solves the problem that the created instance cannot pass the parameter to the parent class.

var kitten = new Cat('kitten')
var oldCat = new Cat('oldCat')
kitten.say(4) //> I am kitten
oldCat.say(4) //> I am oldCat

Combination Inheritance

In the embezzlement constructor, after we inherit the parent class, if we want to add a method (or attribute) or override a method, we will naturally think of:

function Animal(myName){
    this.names = ['animal', 'zoom', 'beast', 'creature']
    this.names.push(myName)
}
Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}

function Cat(myName, age){
    Animal.apply(this, [myName])
    //---------------------------------------------------------------|
    this.age = age                                                 //|
    //---------------------------------------------------------------|
}

Cat.prototype = new Animal()

//--------------------------------------------------------------------|
Cat.prototype.sayAge = function(){                                  //|
    console.log(`My age = ${this.age}`)                             //|
}                                                                   //|
//--------------------------------------------------------------------|

var kitten = new Cat('kitten', 3)
kitten.sayAge()
//> My age = 3

Yes, this is combinatorial inheritance, also known as pseudo classical inheritance. The basic idea is to steal the constructor + add attribute + prototype chain attachment method.

Composite inheritance is more reasonable than stealing constructor to solve the problems of reference value, parameter passing and adding properties / methods. However, in fact, composite inheritance is not perfect. It also needs new Animal() parent object twice.

Prototypal Inheritance

In the previous example, we need to customize an Animal parent class first, and then define a Cat subclass to inherit it. The role of prototype inheritance is to skip the first step and use an existing class to inherit directly.

function create(fatherInstance){
    function Son(){}
    Son.prototype = fatherInstance
    return new Son()
}

This is prototype inheritance. Object.create() uses this method:

In 2006, Douglas Crockford wrote an article, "Prototypal Inheritance in JavaScript".

Parasitic Inheritance

function parasiticCreate(fatherInstance){
    var sonInstance = create(fatherInstance)
    sonInstance.sayHi = function(){
        console.log(`Hi`)
    }
    return sonInstance
}

The core implementation of parasitic inheritance is to complete inheritance + add methods to instances.

Parasitic Combination Inheritance

Base parasitic combination inheritance:

function parasiticCombination(Father, Son){
    var middle = create(Father.prototype)
    middle.contructor = Son
    Son.prototype = middle
}

Parasitic composite inheritance: steal constructor inheritance attribute + parasitic inheritance to create a new object (as a new prototype of subclass object)

function Animal(myName){
    this.names = ['animal', 'zoom', 'beast', 'creature']
    this.names.push(myName)
}

Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}

function Cat(myName, age){
    Animal.apply(this, [myName])
    this.age = age
}

parasiticCombination(Animal, Cat)

Cat.prototype.sayAge = function(){
    console.log(`My age is ${this.age}`)
}

var kitten = new Cat('kitten', 3)
var oldCat = new Cat('oldCat', 9)
kitten.say(4)
oldCat.say(4)

In contrast to composite inheritance, parasitic composite inheritance no longer uses a new Animal instance as the prototype of Cat, but generates a new object substitution through parasitic inheritance. Therefore, there is less one call to the parent class constructor and unnecessary attributes are reduced.

Parasitic composite inheritance is the implementation method of ES6 class, which is considered to be the best inheritance scheme.

Actual implementation:

function Animal(myName){
    this.names = ['animal', 'zoom', 'beast', 'creature']
    this.names.push(myName)
}

Animal.prototype.say = function(index){
    console.log(`I am ${this.names[index]}`)
}

function Cat(myName, age){
    Animal.apply(this, [myName])
    this.age = age
}

var middle = Object.create(Animal.prototype)
Cat.prototype = middle

Cat.prototype.sayAge = function(){
    console.log(`My age is ${this.age}`)
}

var kitten = new Cat('kitten', 3)
var oldCat = new Cat('oldCat', 9)
kitten.say(4)
oldCat.say(4)

After conversion:

class Animal{
    constructor(myName){
        this.names = ['animal', 'zoom', 'beast', 'creature']
        this.names.push(myName)
    }
    say(index){
        console.log(`I am ${this.names[index]}`)
    }
}

class Cat extends Animal{
    constructor(myName, age){
        super(myName) // In a derived class, you must call super(), otherwise an error will be reported
        this.age = age
    }
    sayAge(){
        console.log(`My age is ${this.age}`)
    }
}

var kitten = new Cat('kitten', 3)
var oldCat = new Cat('oldCat', 9)
kitten.say(4)
kitten.sayAge()
oldCat.say(4)
oldCat.sayAge()
//> I am kitten
//> My age is 3
//> I am oldCat
//> My age is 9

So why is ES6 inheritance the syntax sugar of the prototype chain, because several complex processes are encapsulated behind it.

summary

Topics: Javascript Front-end ECMAScript inheritance