Reference books: JavaScript Advanced Programming 3rd and 4th editions

What is the prototype chain?

First, we need to know what a prototype is. According to the Little Red Book: Each constructor has a prototype object, which contains a pointer to the constructor, and an instance of the constructor has a pointer to the prototype object (__proto__). When the constructor FA is an instance of another constructor FB, we can use fa.prototype. __proto__ (note: The pointer to __proto__ is actually not accessible.) Find the FB prototype object and get the method on the FB prototype object. This is the core idea of prototype chain inheritance.

function SubClass() {}

function SuperClass() {}

SuperClass.prototype.getName = function() {
    console.log('I'm a prototype method of SuperClass')
}

SubClass.prototype = new SuperClass()

let sub = new SubClass()

sub.getName() // Print out the I am SuperClass prototype method
SubClass.prototype.__proto__.getName()  // Print out the I am SuperClass prototype method

console.log(SubClass.prototype.__proto__ === SuperClass.prototype) // true
console.log(sub.__proto__.__proto__ === SuperClass.prototype) // true
Copy the code

Object based source: default prototype

We all know that all reference types in Javascript inherit from Object, and this inheritance is also implemented through the prototype chain. The default prototype for all functions is an instance of Object, so the default prototype contains a pointer to Object.prototype (__proto__).

Follow the example above:

// 1, determine the existence of the prototype chain by instanceof method
console.log(sub instanceof SubClass) // true
console.log(sub instanceof SuperClass) // true
console.log(sub instanceof Object) // true

// 2, using isPrototypeOf
console.log(SubClass.prototype.isPrototypeOf(sub)) // true
console.log(SuperClass.prototype.isPrototypeOf(sub)) // true
console.log(Object.prototype.isPrototypeOf(sub)) // true
Copy the code

The prototype chain problem

Stereotype chains are a convenient way to implement inheritance, but there is a problem: reference values defined on stereotypes are shared by all instances, which is why attributes are usually defined in constructors rather than stereotypes. When we use stereotype inheritance, the stereotype usually becomes an instance of another constructor, and properties on the other constructor become properties on the stereotype, which are naturally shared by all instances. In addition, there is no way to pass arguments to the parent type’s constructor when a subtype is instantiated, which, combined with the reference value problem mentioned earlier, means that the prototype chain is rarely used alone:

function SuperClass() {
    this.name = ['super']}function SubClass() {}
SubClass.prototype = new SuperClass()
Subclass.prorotype. name = ['super'] */ subclass.prorotype. name = ['super'] */
let sub1 = new SubClass()
fb1.name.push('sub')
let sub2 = new SubClass()
console.log(sub2.name) // ['super', 'sub'] are shared because their name attribute is on the prototype, but obviously we don't want that

Copy the code

Embezzling constructors (classical inheritance)

To solve the problem of stereotype inheritance, a technique called “stolen constructors” became popular. The idea is simple: call the superclass constructor in a subclass, and use the Call, apply method to execute the superclass constructor in the context of the newly created object. (ES6 provides a class that calls the super method in the subclass constructor. The super method is the parent class constructor.)

function SuperClass(name) {
    this.name = name
    this.color = 'red'
}

function SubClass(name, age) {
    // Pass name as an argument
    SuperClass.call(this, name)
    
    this.age = age
}

const sub = new SubClass('sub'.18)
console.log(sub) // SubClass { name: 'sub', color: 'red', age: 18 }
Copy the code

The problem is that methods must be defined in constructors, so functions cannot be reused, and instances of subclasses cannot access methods on superclass prototypes, so stolen constructor inheritance is generally not used in isolation.

Combination of inheritance

Composite inheritance combines the advantages of prototype chain inheritance and embeded constructor inheritance. The idea is to use the stereotype chain to inherit the properties and methods on the stereotype, and then to inherit the instance properties by stealing constructors, which can both achieve method reuse and allow each instance to have its own properties:

    function SuperClass(name) {
        this.name = name
        this.color = 'red'
    }
    
    SuperClass.prototype.sayName = function() {
        console.log(this.name)
    }
    
    function SubClass(name, age) {
        // Inherit attributes
        SuperClass.call(this, name)
        this.age = age
    }
    
    SubClass.prototype = new SuperClass()
    const sub = new SubClass('sub'.18) 
    console.log(sub) // SuperClass { name: 'sub', color: 'red', age: 18 }
    sub.sayName() // sub
    
Copy the code

Composite inheritance makes up for the shortcomings of prototype inheritance and stolen constructors, and is the most commonly used inheritance method in JavaScript. Combinatorial inheritance also preserves the ability to recognize instanceof and isProtoetpeOf() methods.

Primary inheritance

First observe the following function:

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

The object function creates a temporary constructor, uses the passed object as the prototype of the constructor, and returns an instance of the constructor. This is actually a shallow copy of the object passed in:

let originObj = {
    name:'origin'.color: ['red'.'yellow']}let newObj = object(originObj)
newObj.name = 'new'
newObj.color.push('blue')
console.log(originObj.color) // [ 'red', 'yellow', 'blue' ]
Copy the code

We use prototype inheritance to get a new object whose __proto__ actually refers to the original object. There are no constructors involved in this process, which means that properties are shared between the two objects, essentially cloning the original object. Primitive inheritance applies when an object inherits an object without the involvement of a constructor.

ECMAScript5 adds the object.create () method to normalize the idea of type inheritance. This method takes two parameters: the object to be the prototype for the new object and, optionally, the object to be added with additional attributes. With only one argument, the object.create () method has the same effect as the create() method here.

Keep in mind that reference values contained in the original type inheritance attributes are still shared between related objects, just as with stereotype chain inheritance.

Parasitic inheritance

Parasitic inheritance is similar to primary inheritance. The idea is to enhance an object in a specific way through a function that implements inheritance, and then return the object. The basic parasitic inheritance pattern is as follows:

    function createPerson(origin) {
    let clone = Object.create(origin)
    clone.sayName = function() {
        console.log(this.name)
    }
    return clone
    }

    let person = {
        name: 'Joe'
    }

    let another = createPerson(person)

    another.sayName() / / zhang SAN
Copy the code

Object. Create in the code above is not required, and any function that returns a new Object can be used. Parasitic inheritance is suitable for scenarios where objects are the focus, not types and constructors.

Note that, like the constructor pattern, parasitic inheritance makes functions hard to eat.

Parasitic combinatorial inheritance

Composite inheritance actually has a disadvantage: the superclass constructor is called twice, once in the subclass constructor and once when assigned to the subclass stereotype. To solve this problem, combinatorial parasitic inheritance comes into being. Combinatorial parasitic inheritance inherits properties using stolen constructors, but uses a hybrid prototype chain integration approach. The basic idea is that instead of assigning a value to a subclass by calling the parent constructor, you get a copy of the parent stereotype (the idea of parasitic inheritance) and assign that copy to the subtype stereotype, and then the subtype stereotype business will have methods on the parent stereotype.

    function inheritprototype(subClass, superClass) {
        let prototype = Object.create(superClass.prototype)
        prototype.constructor = subClass // To solve the problem of overriding the prototype causing the default constructor to be lost
        subClass.prototype = prototype
    }
    
    function SuperClass(name) {
        this.name = name
        this.color = 'red'
    }
    
    SuperClass.prototype.sayName = function() {
        console.log(this.name)
    }
    
    function SubClass(name, age) {
        // Inherit attributes
        SuperClass.call(this, name)
        this.age = age
    }
    
    inheritprototype(SubClass, SuperClass)
    
    let sub = new SubClass('Joe'.18)
    sub.sayName() / / zhang SAN
    console.log(sub.__proto__) // SubClass { constructor: [Function: SubClass] }
Copy the code