Inheritance is a very important feature of object-oriented programming. Many object-oriented languages support two types of inheritance: interface inheritance and implementation inheritance. Interface inheritance inherits only method signatures, whereas implementation inheritance inherits the actual methods. There is no interface inheritance in ES, because functions have no method signature, implementation inheritance is the only inheritance in ES, and this is mainly implemented through prototypes.

Prototype chain inheritance

Also known as the Prototype pattern

function Super (){
    this.name = 'Super'
}
Super.prototype.getName = function(){
    return this.name
}

function Sub (){
    this.name = 'Sub'
}
// let Sub inherit Super
Sub.prototype = new Super()
// Keep the inheritance chain correct
Sub.prototype.constructor = Sub

let instance = new Sub()
console.log(instance.getName()); //Sub
Copy the code

This approach to inheritance is convenient, but also problematic.

  • The first problem is the stereotype mechanism itself, because stereotypes are shared by all instances, which means that modifications to attributes referenced on the stereotype are made by instance and reflected in each instance.
  • Another problem is that when inheritance is implemented using stereotypes, the prototype of the child constructor actually becomes an instance of the parent constructor. This means that an instance property becomes a stereotype property. Let’s take an example:
function Super (){
    this.colors = ['red'.'blue'.'green']}function Sub (){}
// let Sub inherit Super
Sub.prototype = new Super()

let instance1 = new Sub()
instance1.colors.push('black')
console.log(instance1.colors);  //[ 'red', 'blue', 'green', 'black' ]

let instance2 = new Sub()
console.log(instance2.colors);  //[ 'red', 'blue', 'green', 'black' ]
Copy the code

In this example colors is an instance of Super, but sub. prototype becomes an instance of Super when Sub inherits Super. Colors becomes a stereotype property shared by Sub instances.

  • Another problem is that child constructors cannot pass arguments to parent constructors when instantiated.

This has led to the fact that prototype chain inheritance is rarely used alone

Embezzled constructors

To solve the problem of which archetypes contain reference values, a method called constructor stealing has emerged (also known as constructor binding, object spoofing, classical inheritance, etc.). The method is as simple as executing the parent constructor inside the child constructor via call/apply. Look at the following example:

function Super (){
    this.colors = ['red'.'blue'.'green']}function Sub (){
    / / Super inheritance
    Super.call(this)}let instance1 = new Sub()
instance1.colors.push('black')
console.log(instance1.colors);  //[ 'red', 'blue', 'green', 'black' ]

let instance2 = new Sub()
console.log(instance2.colors);  //[ 'red', 'blue', 'green' ]
Copy the code

This means that the code in the Super function is executed on the Sub instance object, giving each instance of Sub its colors attribute. Another advantage of a stolen constructor is that you can pass arguments to the parent constructor.

function Super (name){
    this.name = name 
}

function Sub (name){
    / / Super inheritance
    Super.call(this, name)
    //Sub instance attributes
    this.age = 15
}

let instance1 = new Sub('Ming')
console.log(instance1.name);   / / xiao Ming
console.log(instance1.age);   / / 15
Copy the code

One caveat here is that it is best to call the parent constructor first and then add the child constructor attributes. To avoid overwriting the attribute

Combination of inheritance

Composite inheritance combines the best of both archetypal chain inheritance and embeded constructors. The basic idea is to use stereotype chains to inherit properties and methods on stereotypes, and to inherit instance properties by stealing constructors. Look at the following example:

function Super (name){
    this.name = name
    this.colors =  ["red"."blue"."green"]
}
Super.prototype.sayName = function(){
    console.log(this.name);
}

function Sub (name, age){
    / / Super inheritance
    Super.call(this, name) 
    this.age = age
}
Sub.prototype = new Super()
Sub.prototype.constructor = Sub
Sub.prototype.sayAge = function(){
    console.log(this.age);
}

let instance1 = new Sub('Ming'.15)
instance1.colors.push('black')
console.log(instance1.colors);   //[ 'red', 'blue', 'green', 'black' ]
instance1.sayName() / / xiao Ming
instance1.sayAge()  / / 15

let instance2 = new Sub('wang'.18)
instance2.sayName() / / wang
instance2.sayAge()  / / 18

Copy the code

Composite inheritance makes up for the deficiency of prototype chain inheritance and embeded constructors, and is the most used inheritance pattern in JavaScript. In addition, combinatorial inheritance also retains the recognition capability of instanceof and isPrototypeOf methods.

I don’t know if you’ve found two drawbacks to combinatorial inheritance.

The instance property of the parent constructor will still exist on the child constructor’s prototype, although the same instance property of the parent constructor will be overridden when accessed

Primary inheritance

Primitive inheritance is not strictly constructor inheritance. This can be done with a function:

function create (o){
    function F(){}
    F.prototype = o
    F.prototype.constructor = F
    return new F()
}
Copy the code

This function creates a temporary constructor, takes the passed object as the prototype of the constructor, and returns an instance of it. Look at the following example:


function create (o){
    function F(){}
    F.prototype = o
    return new F()
}
let Person = {
    name:'wang'.friends: ['Joe'.'bill']}let p1 = create(Person)
p1.name = 'zhang'
p1.friends.push('zhang')
console.log(p1.name);   / / zhang
console.log(p1.friends);    //[' Zhang SAN ', 'Li Si ',' Xiao Zhang ']

let p2 = create(Person)
p2.friends.push('xiao li')
console.log(p2.name);   / / wang
console.log(p2.friends);    //[' Zhang SAN ', 'Li Si ',' Xiao Zhang ', 'Xiao Li']
Copy the code

Here Person is shared as a prototype for instances P1 and P2.

ECMAScript5 provides a canonical method for primitive inheritance, objece.create (). This method returns a new object and takes two parameters: the object to be the prototype for the new object, and the object to define additional properties for the new object. Objece.create() has the same effect as the create method above when there is only one argument:

let Person = {
    name:'wang'.friends: ['Joe'.'bill']}let p1 = Object.create(Person)
p1.name = 'zhang'
p1.friends.push('zhang')
console.log(p1.name);   / / zhang
console.log(p1.friends);    //[' Zhang SAN ', 'Li Si ',' Xiao Zhang ']

let p2 = Object.create(Person)
p2.friends.push('xiao li')
console.log(p2.name);   / / wang
console.log(p2.friends);    //[' Zhang SAN ', 'Li Si ',' Xiao Zhang ', 'Xiao Li']
Copy the code

Object. Create () : Object. Create ()

let p1 = {}
Object.setPrototypeOf(p1, Person)
Copy the code

Object P1’s internal property [[portoType]] is set to Person. You can verify this:

let Person = {
    name:'wang'.friends: ['Joe'.'bill']}let p1 = Object.create(Person)

console.log(Object.getPrototypeOf(p1) === Person);  //true
console.log(p1.__proto__ === Person);  //true
Copy the code

The second argument to Object.create() is the same as the second argument to Object.defineProperties(). Adding attributes in this way obscures the attributes on the prototype object (that is, the first argument) :

let Person = {
    name:'wang'.friends: ['Joe'.'bill']}let p1 = Object.create(Person, {
    name: {value:'zhang'.enumerable:true.writable:true.configurable:true}})console.log(p1.name);   / / zhang
Copy the code

Original type combination inheritance

Remember the last two disadvantages of combinatorial inheritance mentioned above? In fact, these two deficiencies can be made up by combining the original type:

function Super (name){
    this.name = name
    this.colors =  ["red"."blue"."green"]
}
Super.prototype.sayName = function(){
    console.log(this.name);
}

function Sub (name, age){
    Super.call(this, name)
    this.age = age
}
// sub.prototype = new Super() replace this line with the following line
Sub.prototype = Object.create(Super.prototype)

Sub.prototype.constructor = Sub
Sub.prototype.sayAge = function(){
    console.log(this.age);
}
Copy the code

The Super constructor is called only once, and it avoids unnecessary attributes on sub.Prototype, so this is more efficient. Arguably the best model for inheritance.

conclusion

The future of object orientation and inheritance should use ES6 classes first, because Class already avoids all of the above shortcomings natively, although its implementation is still based on prototype.