purpose

Inheritance is an important part of JavaScript.

Whether on the basis of the integrity of the investigation, or in the usual work development and source code reading, inheritance will be reflected, this article is also to comb and take you to deeply understand the common realization of these kinds of inheritance, to solve the embarrassment of rote memorization code.

The concept of inheritance

Baidu Baike’s concept of inheritance:

Inheritance is a concept of object-oriented software technology. It is the three basic characteristics of object-oriented, together with polymorphism and encapsulation. Inheritance can cause subclasses to have attributes and methods of their parent class, redefine, append attributes and methods, and so on.

Here we do not do in-depth study, take a we are easier to understand 🌰 to tell about: son inherited father’s business.

Do not know everyone has seen the kind of ancient drama, usually the old emperor has laid a piece of land before passing away, and the little emperor can inherit this good land after his father emperor rises immortal as long as, and the task of the little emperor, is to go up in this foundation to open xinjiang to expand earth!

In fact, mapping to our program is also the same, our subclass can take some of its existing attributes and methods from the parent class to use, and can define their own attributes and methods on this basis.

JS in the implementation of inheritance

Js inheritance is mainly achieved through the prototype chain, so here we need to know some knowledge about the prototype.

/** * constructor *@param The name name *@param The age age * * /
function Person(name, age) {
  this.name = name || 'nothing';
  this.age = age || 18;
}

Person.prototype.say = function(){
    console.log('What are you looking at?');
}
// Construct the instance object
const xiaoMing = new Person('xiaoMing'.'18');
Copy the code

Let’s take a quick look at the relationship between constructors and instance objects using the above example:

  1. The constructortheprototypeProperty refers to the prototype object, and the prototypecontructorPoints to its constructor
  2. Created by the constructorInstance objectsWho has a__proto__Attributes also point to stereotypes

Conclusion:

Constructor. Prototype === instance object.__proto__

When we access a property or method on an object, there is a procedure like this:

  1. Finds whether the object itself has the property or method, and returns the corresponding value if it does
  2. If not, the object will be searched on the corresponding prototype object (that is, the space pointed to by the __proto__ attribute in the figure above). If not, the prototype object will be searched on the prototype object. This search process is completed by the prototype chain, and this search relationship constructed by the prototype chain can be understood as inheritance

Prototype chain inheritance

Ok, so that’s what this is all about, so let’s move on.

At this point, if we want to use Person as a superclass constructor and create a subclass Son by inheriting it, what should we do?

As shown above, if we want to inherit Son from Person, we simply point son.prototype to the new Person() instance created by the parent constructor. This is called prototype chain inheritance

/** * parent constructor *@param The name name *@param The age age * * /
function Person(name, age) {
  this.name = name || 'nothing';
  this.age = age || 18;
  this.share = [1.2.3];
}

Person.prototype.say = function(){
    console.log('What are you looking at?');
}

/** * subclass constructor **/
function Son() {
  this.sex = 'man'
}

// Implement prototype chain inheritance
Son.prototype = new Person();

// Build an instance object of the subclass
const liLei = new Son();
Copy the code

Let’s examine the pros and cons of inheritance implemented this way:

Advantages – the idea is simple and clear, the code is also very simple

disadvantages

  1. If the parent class has a reference value attribute, the value of the reference attribute is shared between subclass instances
// omit the above code

// Build two instances of subclasses
const liLei = new Son();
const meiMei = new Son();

// Assign the share attribute to the first instance
liLei.share.push(4);

console.log(liLei.share) / / [1, 2, 3, 4]

console.log(meiMei.share) / / [1, 2, 3, 4]
Copy the code

The reason for this is very simple,Reference values refer to the same memory space, so the share attribute between subclasses uses this space, so when A operates this space, the value obtained by B will also change accordingly.

  1. There is no way to pass arguments to a superclass function when creating a subclass instance

As we can see from the above example, if we want to pass arguments to the parent constructor, we can do so only at the point where we implement prototype chain inheritance

Cannot pass arguments to the parent class when we create a subclass instance.

So is there a way around the downside of this inheritance? The answer is yes, which leads to the following way of inheritance: ↓

Borrow constructor inheritance

Borrowing constructors is also known as classical inheritance.

Just look at the code

/** * parent constructor *@param The name name *@param The age age * * /
function Person(name, age) {
  this.name = name || 'nothing';
  this.age = age || 18;
  this.share = [1.2.3];
}

Person.prototype.say = function(){
    console.log('What are you looking at?');
}

/** * subclass constructor **/
function Son(name) {
  // Add attributes from the parent class to the parent class
  Person.call(this,name)
  
  
  this.sex = 'man'
}


// Build an instance object of the subclass
const liLei = new Son('liLei');
const meiMei = new Son('meiMei');

liLei.share.push(4);

console.log(liLei.share); / / [1, 2, 3, 4]
console.log(meiMei.share);/ / [1, 2, 3]
Copy the code

The key to this kind of inheritance is how to implement borrowing. We know from the above that we are using the call method to implement this

  // Add attributes from the parent class to the parent class
  Person.call(this,name)
Copy the code

Instead of going into the details of what call does and how it works, let’s focus on inheritance. After executing this line of code, the subclass constructor Son looks like this:

function Son(name) {
 /** === this is the borrowed property or method === */
  this.name = name || 'nothing';
  this.age = age || 18;
  this.share = [1.2.3];
 / * * = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * /
  
  this.sex = 'man'
}
Copy the code

Advantages in a word make up for the shortcomings of prototype chain inheritance.

  1. – Subclass instances do not share reference-valued properties (stealing other people’s property is their own property, so they do not share it with others)
  2. You can pass in parameters when creating a subclass instance

We only stole the attributes and methods from the parent constructor, but we did not steal the methods from its relative, the prototype, so we will get an error when we try to access lilei.say () :

Unable to access properties and methods on superclass prototype objects

Is there a way around that?

If you dare to ask, that means there must be. Let’s keep going!

Combination of inheritance

All you need to say about combinatorial inheritance is:

Combinatorial inheritance = stereotype chain inheritance + borrowed constructor inheritance

So we simply combine the two key lines of code that implement inheritance above and we’re done

  // Implement prototype chain inheritance
  Son.prototype = new Person();
Copy the code
  // Add attributes from the parent class to the parent class
  Person.call(this,name)
Copy the code

The complete code is as follows:

/** * parent constructor *@param The name name *@param The age age * * /
function Person(name, age) {
  this.name = name || 'nothing';
  this.age = age || 18;
  this.share = [1.2.3];
}

Person.prototype.say = function(){
    console.log('What are you looking at?');
}

/** * subclass constructor **/
function Son(name) {
  // Add attributes from the parent class to the parent class
  Person.call(this,name)
  this.sex = 'man'
}

// Implement prototype chain inheritance
Son.prototype = new Person();
// Because we did not have the constructor attribute on the instance object in our previous implementation
// To implement inheritance more rigor, we need to add the pointer to the constructor on the prototype object
Son.prototype.constructon = Son;

// Build an instance object of the subclass
const liLei = new Son('liLei');
const meiMei = new Son('meiMei');

liLei.share.push(4);
// Access the methods on the prototype
liLei.say(); / / what you see
console.log(liLei.share); / / [1, 2, 3, 4]
console.log(meiMei.share);/ / [1, 2, 3]
Copy the code

advantages

  1. Instances of subclasses have access to properties and methods on superclass prototypes
  2. Reference value attributes are not shared between instances of subclasses

disadvantages

Although composite inheritance fills in the shortcomings of stereotype chain inheritance and borrowed constructor inheritance, it still has this disadvantage:

The superclass constructor was called twice, causing unnecessary runs and generating redundant attributes, resulting in a waste of memory

  1. First call:Son.prototype = new Person(), the properties from the parent class are mounted to the subclass prototype
  2. Second call:Person.call(this,name)“, stealing attributes from its parent and putting them on top of itself

So is there a way around this shortcoming?

Of course there are, but before we get there, we need to look at two other types of inheritance.

Primary inheritance

Prototypal Inheritance was proposed by Douglas Crockford in an article called “Prototypal Inheritance in JavaScript”, and at the end of the article he gave a function:

function object(o){
    // Create a temporary constructor
    function F(){};
    // Point the prototype of the temporary constructor to the passed object O
    F.prototype = o;
    // Return an instance object
    return new F();
}
Copy the code

The idea was to share information between objects through prototypes, even without using custom types. My understanding of this sentence is: he USES the above, create a temporary constructor to implementation inheritance, then the implementation inheritance of instance objects directly back out, don’t we go to the definition of the subclass again, by this function can be got directly inherited objects (personal understanding, welcome to discuss the correct)

This function is the basic implementation of object.create () in ES5.

The specific usage is as follows

const obj = {
    name: 'fire'.age: '22'.share: [1.2.3.4]}const newObj = object(obj);
newObj.share.push(5);
console.log(newObj.name); // fire

const newObj2 = object(obj);
// Reference values are shared as with stereotype chain inheritance
console.log(newObj2.share); / / [1, 2, 3, 4, 5]
Copy the code

disadvantages

  1. Reference values are shared as with stereotype chain inheritance

Parasitic inheritance

This type of inheritance can be considered to enhance the original type inheritance

We look at the code, we dissect it through the code

function objectPro(o){
    // Get the instance object of the subclass that implements inheritance
    const newObj = object(o);
    // Add methods and attributes to objects that have already been inherited
    // Easy to use directly, like factory mode operation
    newObj.say = function(){
        console.log('What are you looking at?')}// Return the enhanced object
    return newObj;
}
Copy the code

In fact, I understand the enhancement here is to create objects that have some properties and methods, but the parent class does not, so it needs to be added after the completion of inheritance, so that the newly created instance object can use it directly.

const obj = {
    name: 'fire'.age: '22'.share: [1.2.3.4]
    // There is no say method on this object
}

// But all objects we create here need this method
// So we need parasitic inheritance to implement the enhanced functionality
const veryNewObj = objectPro(obj);
const veryNewObj1 = objectPro(obj);
const veryNewObj2 = objectPro(obj);

veryNewObj.say();
veryNewObj1.say();
veryNewObj2.say();

veryNewObj.share.push(5);console.log(veryNewObj.share); / / [1, 2, 3, 4, 5]
console.log(veryNewObj1.share); / / [1, 2, 3, 4, 5]

Copy the code

advantages

  1. Enhanced original type inheritance to add custom attributes and methods

disadvantages

  1. Shared reference values are still not resolved

The disadvantage of combinatorial inheritance is that the parent constructor is called twice, so the following inheritance method can help us solve this problem. Let’s take a look

Parasitic combinatorial inheritance

We can see from the name that this inheritance is

Parasitic combination is inheritance = parasitic inheritance + combinatorial inheritance (prototype-chain inheritance + borrowed constructor inheritance)

/** this method implements inheritance from the prototype object **/
function nbPlus(parent, child){
    // Build a new object from the parent constructor's prototype object (parasitic inheritance)
    // This fixes the problem of calling the parent class twice
    const newPrototype = objcet(parent.prototype);
    // Enhance the new object by adding the constructor attribute to point to the subclass constructor
    newPrototype.constructor = child;
    // Implement the new prototype of the subclass constructor (prototype chain inheritance)
    child.prototype = newPrototype;
}
/** * parent constructor *@param The name name *@param The age age * * /
function Person(name, age) {
  this.name = name || 'nothing';
  this.age = age || 18;
  this.share = [1.2.3];
}

Person.prototype.say = function(){
    console.log('What are you looking at?');
}

/** * subclass constructor **/
function Son(name) {
  // Implement attributes and methods borrowing from the parent class itself (borrowing constructor inheritance)
  Person.call(this,name);
  
  this.sex = 'man';
}


// The usage mode
nbPlus(Person, Son);

// Build an instance object of the subclass
const liLei = new Son('liLei');
const meiMei = new Son('meiMei');

liLei.share.push(4);

console.log(liLei.share); / / [1, 2, 3, 4]
console.log(meiMei.share);/ / [1, 2, 3]
Copy the code

As you can see from the above code, the nbPlus method uses the prototype property of the constructor directly, rather than the new Person method, which is used by combinatorial inheritance. And based on this prototype to create a new object, as a subclass of the new prototype, this can not only avoid the problem of repeated execution, but also can avoid the problem of reference value sharing.

conclusion

Ok, that’s all for this article on inheritance

The follow-up will continue to put the js foundation of some things to sort out the output, if you feel useful, welcome to focus on praise ~

All my articles are sent with my public number: in addition to technology and life, welcome everyone scan code attention!

Sharing technology and my life with cats