This article has participated in the third phase of the “High production” track at the Digger Creators Camp. For more details, check out the third phase of the Digger Project | Creators Camp to “write” personal impact

Preface πŸ“–

In front of the interview, inheritance is a very common question, the interviewer will always ask you in a different way. For example, what types of inheritance do you know? What are the differences between these types of inheritance? What are the pros and cons of these types of inheritance? What are the characteristics of each?

One stroke is fatal, like when I first encountered this problem on Monday, I only remember that there are several ways of inheritance, but I can’t say why, the knowledge is still too floating surface.

Therefore, write this article to summarize the knowledge of various inheritance methods. Let’s learn about ba ~✨

πŸ“ 1. Basic knowledge preparation

1. Definition of inheritance

To quote a phrase from the Javascript high-level language programming:

Inheritance is one of the most discussed topics in object-oriented programming (object-oriented Language). Many OO languages support two types of inheritance: interface inheritance and implementation inheritance. The former can only inherit the method signature, while the latter can inherit the actual method.

Interface inheritance, however, is unlikely to exist in ECMAScript because functions are not signed.

As a result, implementation inheritance is the only method of inheritance that ECMAScript supports, and this is done primarily through prototype chains.

2. The way of inheritance

So with that definition in mind, let’s look at how many ways inheritance can be done. See the picture below πŸ‘‡ for details

πŸ“š two, six common inheritance methods

1. The prototype chain inherits πŸ’‘

(1) Relationships between constructors, prototypes, and instances

There is a relationship between a constructor, a stereotype, and an instance: each constructor has a stereotype object, the stereotype has a property that refers back to the constructor, and the instance has an internal pointer to the stereotype.

(2) Basic ideas

The basic idea of stereotype chain inheritance is to inherit properties and methods of multiple reference types through stereotypes. Its core is to treat instances of the parent class as stereotypes of the child class.

(3) Implement prototype chain inheritance

This may seem a little abstract, but let’s try prototype chain inheritance with an example. The specific code is as follows:

// Superclass function
function SuperType() {
    // The parent class defines the properties
    this.property = true;
}

// The superclass defines the method
SuperType.prototype.getSuperValue = function() {
    return this.property;
}

// Subclass functions
function SubType() {
    this.subproperty = false;
}

/** * Key points: * create an instance of SuperType * and assign that instance to subtype.prototype */
SubType.prototype = new SuperType();
// Subclasses define new methods
SubType.prototype.getSubValue = function() {
    return this.subproperty;
}

// Create an instance of a subclass
let instance = new SubType();
// The subclass calls the method of the superclass
console.log(instance.getSuperValue()); // true
Copy the code

(4) Illustration

Based on the above code, let’s use a graph to represent the inheritance relationship of this code. As shown in the figure below:

You’re going to go to the top right corner of the image, and we’re going to go from top right to bottom left.

First, we create an instance of the constructor SuperType and use it as a prototype for the subclass superType.prototype. After that, the instance is created with a content pointer to the superclass’s supertype.prototype. When you’re done, you come to step three. There is a property on the stereotype that refers back to the constructor SuperType. Fourth, for constructors, each constructor has its own prototype, so it refers back to superType.prototype.

According to the above description, we look at the relationship between (1) and (2) and the basic idea, is it much clearer?

Similarly, the following steps (5 ~ 8) are the same as the above steps, you can understand the corresponding, not detailed here.

(5) Break the prototype chain

Another important thing to understand is that creating a prototype method in the form of object literals breaks the previous prototype chain, because it is essentially overwriting the prototype chain. As the following example shows:

function SuperType() {
    this.property = true;
}

SuperType.prototype.getSuperValue = function() {
    return this.property;
};

function SubType() {
    this.subproperty = false;
}

/ / inherit the SuperType
SubType.prototpe = new SuperType();
// Adding new methods via object literals will invalidate the previous line
SubType.prototype = {
    getSubValue() {
        return this.subproperty;
    },
    someOtherMoethod(){
        return false; }}let instance = new SubType();
console.log(instance.getSuperValue()); 
// TypeError: instance.getSuperValue is not a function
Copy the code

In the above code, the prototype of the subclass SubType is overridden by an object literal after being assigned an instance of SuperType. The overridden stereotype is actually an instance of Object, not an instance of SuperType, so the stereotype chain is broken by assigning it to an Object literal. SubType and SuperType no longer have anything to do with each other.

(6) Advantages and disadvantages

1) Advantages:

  • Methods of a superclass can be reused.

2) Disadvantages:

  • All reference type data of the parent class will be shared by all subclasses, that is, if the reference type data of one subclass changes, the other subclasses will also be affected.
  • An instance of a subtype cannot pass arguments to a constructor of a parent type. The reason for this is that, once the parameters are passed, the same parameters will be overwritten due to the first problem.

2. Steal the constructor from πŸ’‘

(1) Basic ideas

For the above two problems of prototype chain inheritance, the prototype chain will not be used alone. Therefore, to solve the problem caused by the inclusion of reference values in prototypes, we introduced a new inheritance method called “misappropriating constructors.” This technique is also known as object masquerade or classical inheritance.

The basic idea is to use the constructor of the parent class to enhance the instance of the child class, equivalent to assigning the instance of the parent class to the child class (without using the stereotype).

(2) Use mode

Functions are simple objects that execute code in a specific context, so you can use the Apply () and call() methods to execute the constructor in the context of the newly created object.

(3) Implement prototype chain inheritance

Let’s use an example to implement prototype chain inheritance. The specific code is as follows:

function SuperType(name) {
    this.name = name;
    this.colors = ["red"."blue"."green"];
}

function SubType(name, age) {
    // Inherit SuperType and pass the arguments. This is the core
    SuperType.call(this.'monday');
    // Instance properties
    this.age = age;
}

let instance1 = new SubType("monday".18);
instance1.colors.push("gray");
console.log(instance1.name); // monday
console.log(instance1.age); / / 18
console.log(instance1.colors); // [ 'red', 'blue', 'green', 'gray' ]

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

As you can see, in the example above, the SuperType constructor is borrowed by using the call() method, so that every instance of SubType is created with a copy of the properties in the SuperType.

Also, an advantage of misappropriating constructors over the prototype chain is that arguments can be passed from the subclass constructor to the superclass constructor. As you can see, when we use superType.call (), we can pass arguments directly to the SuperType.

(4) Advantages and disadvantages

1) Advantages:

  • The constructor of a subclass may pass arguments to the constructor of a superclass.

2) Disadvantages:

  • Only the instance properties and methods of the parent class can be inherited, and the prototype properties and methods of the parent class cannot be inherited.
  • Reuse is not possible. Every new subclass created will produce a copy of the function of the instance of the parent class, which greatly affects performance.

3. Combination inheritance πŸ’‘

(1) Basic ideas

Because of the pitfalls of both the prototype chain and the misappropriated constructor inheritance approach, they are largely unusable. To do this, we have introduced a new type of inheritance, composite inheritance. Composite inheritance, also known as pseudo-classical inheritance, combines the best of both archetype chains and misappropriated constructors.

The basic idea is to use the stereotype chain to inherit the properties and methods on the stereotype, and then to inherit the properties on the instance by stealing the constructor.

In this way, functions can be reused by defining methods on the prototype, while ensuring that each instance has its own properties.

(2) Implement composite inheritance

Let’s use an example to implement composite inheritance. The specific code is as follows:

function SuperType(name) {
    this.name = name;
    this.colors = ['red'.'blue'.'green'];
}

SuperType.prototype.sayName = function() {
    console.log(this.name);
}

function SubType(name, age) {
    // Inherit attributes β†’ borrow the constructor to inherit attributes on the instance
    SuperType.call(this, name);
    this.age = age;
}

// Inherit methods β†’ inherit attributes and methods from stereotypes
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    console.log(this.age);
}

let instance1 = new SubType('Monday'.18);
instance1.colors.push('gray');
console.log(instance1.colors); // [ 'red', 'blue', 'green', 'gray' ]
instance1.sayName(); // Monday
instance1.sayAge(); / / 18

let instance2 = new SubType('Tuesday'.24);
console.log(instance2.colors); // [ 'red', 'blue', 'green' ]
instance2.sayName(); // Tuesday
instance2.sayAge(); / / 24
Copy the code

As you can see, in the code above, we inherit from the SuperType instance by stealing the constructor. At the same time, through the way of prototype, also successfully inherited SuperType prototype properties and methods.

(3) Legend

Let’s use a graph to show the above results. The details are as follows:

(4) Advantages and disadvantages

1) Advantages:

  • Avoid the defects of prototype chain and embepper constructor inheritance and implement inheritance to instances and prototypes.
  • Composite inheritance isjavascriptA common inheritance method, and retainedinstanceofOperators, andisPrototypeOf()methodsRecognition of synthetic objectsAbility.

2) Disadvantages:

  • The instance properties and methods of the parent class, which reside both in the instance of the child class and in the prototype of the child class, will consume more memory.
  • So, when you create an instance object using a subclass, there will be two copies of the same properties and methods in its prototype.

4. The original type inheritance πŸ’‘

(1) Basic ideas

The basic idea is to assign an object directly to the prototype of the constructor. The following code looks like this:

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

The above code was written in a 2006 article by Douglas Crockford. This article introduces an inheritance approach that does not strictly involve constructors, and its starting point is that prototypes can be used to share information between objects, even if there are no custom types.

In the above code, object() executes once on the object passed in, and points the prototype of F directly to the object passed in.

(2) Realize the original type inheritance

Let’s use an example to implement the old type inheritance. The specific code is as follows:

function object(obj){
    function F(){}
    F.prototype = obj;
    return new F();
  }

let person = {
    name: 'Monday'.friends: ['January'.'February'.'March']};let otherPerson = object(person);
otherPerson.name = 'Friday';
otherPerson.friends.push('April');

let anotherPerson = object(person);
anotherPerson.name = 'Sunday';
anotherPerson.friends.push('May');

console.log(person.friends); // [ 'January', 'February', 'March', 'April', 'May' ]
Copy the code

And as you can see, eventually all the friends are copied onto the Person object. However, this approach is rarely used and is similar to the prototype chain inheritance pattern, so it is not usually used alone.

(3) Advantages and disadvantages

1) Advantages:

  • Suitable for situations where you do not need to create a separate constructor, but you still need to share information between objects.

2) Disadvantages:

  • When you inherit reference type data from multiple instances, the points are the same, so there is the possibility of tampering.
  • Unable to pass parameter.
  • ES5Already exists inObject.create()Can replace the aboveobjectMethods.

5. Parasitic inheritance πŸ’‘

(1) Basic ideas

The basic idea of parasitic inheritance is to enhance an object in a way that returns the object on top of the original type inheritance above.

(2) Implementation of parasitic inheritance

Let’s use an example to implement parasitic inheritance, the specific code is as follows:

/ / the object function
function object(obj){
  function F(){}
  F.prototype = obj;
  return new F();
}

// The main function of the constructor is to add properties and methods to enhance the function
function createAnother(original) {
  // Create a new object by calling a function
  let clone = object(original);
  // Enhance the object in some way
  clone.sayHello = function() {
    console.log('hello');
  }
  // Return the object
  return clone; 
}

let person = {
  name: 'Monday'.friends: ['January'.'February'.'March']};let anotherPerson = createAnother(person);
anotherPerson.sayHello(); // hello

Copy the code

As you can see, by creating a new constructor createAnother, we enhance the contents of the object. And then return that object for us to use.

(3) Advantages and disadvantages

1) Advantages:

  • Suitable for scenarios where you only care about the object itself, not the data types and constructors.

2) Disadvantages:

  • Adding a function to an object makes the function difficult to reuse, similar to the misappropriated constructor inheritance in 2 above.
  • When you inherit reference type data from multiple instances, the points are the same, so there is the possibility of tampering.
  • Unable to pass parameter.

6. Parasitic combination inheritance πŸ’‘

(1) Basic ideas

The basic idea of parasitic composite inheritance is to combine embezzled constructors and parasitic patterns to implement inheritance.

(2) Implementation of parasitic composite inheritance

Let’s use an example to illustrate parasitic composite inheritance. The specific code is as follows:

function inheritPrototype(subType, superType) {
    // Create an object
    let prototype = Object.create(superType.prototype);
    // Enhance the object
    prototype.constructor = subType;
    // Specify an object
    subType.prototype = prototype;
}

// The parent class initializes the instance properties and the stereotype properties
function SuperType(name) {
    this.name = name;
    this.friends = ['January'.'February'.'March'];
}
SuperType.prototype.sayName = function() {
    console.log(this.name);
}

// The borrowing constructor passes parameters to the subclass instance attributes (supports parameter passing and avoids tampering)
function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}

// Use the parasitic inheritance feature to point the parent class prototype to the child class
inheritPrototype(SubType, SuperType);

// Add prototype properties for subclasses
SubType.prototype.sayAge = function() {
    console.log(this.age);
}

let instance1 = new SubType('Ida'.18);
let instance2 = new SubType('Hunter'.23);

instance1.friends.push('April');
console.log(instance1.friends); // [ 'January', 'February', 'March', 'April' ]
instance1.friends.push('May');
console.log(instance2.friends); // [ 'January', 'February', 'March' ]
Copy the code

As you can see, in the case of parasitic composite inheritance, it borrows the inheritPrototype constructor to pass the instance properties of the subclass, and uses the inheritPrototype constructor to refer the parent class’s prototype to the subclass, so that the subclass inherits from the parent.

At the same time, subclasses can also add their own desired prototype attributes on their prototypes, to achieve the effect of inheriting their own prototype methods.

(3) Legend

Let’s show the results again with a graph. The details are as follows:

(4) Advantages and disadvantages

1) Advantages:

  • Parasitic composite inheritance can be the best form of reference type inheritance.
  • It almost avoids all the defects in the inheritance method mentioned above, and is also the most efficient and widely used one.

2) Disadvantages:

  • The implementation process is relatively complicated, with the father-son relationship to avoid jumping into the vortex of the prototype. It’s not a bad thing, because it’s worth it!

πŸ—žοΈ 3. Class inheritance

1. Basic concepts

We’ve talked about various types of inheritance, but none of them seem to escape the closed loop of the prototype chain. In 2015, ES6 came along and solved that problem. ES6 classes can be inherited through the extends keyword, which is much cleaner and easier than ES5’s method of inheriting by modifying the prototype chain.

Let’s first look at how class implements inheritance with an example. The specific code is as follows:

class Point{
    constructor(x, y) {
        this.x = x;
        this.y = y; }}class ColorPoint extends Point {
    constructor(x, y, color) {
        Constructor (x, y) = constructor(x, y);
        // Only the super method can return an instance of the superclass
        super(x, y); 
        this.color = color;
    }
    
    toString() {
        return this.color + ' ' + super.toString() {
            // Call toString() of the parent class}}}Copy the code

As you can see, the extends keyword allows you to inherit the properties and methods of the parent class, which seems to be much more convenient than the parasitic composite inheritance above.

Now, let’s move on to other uses.

2. Object.getPrototypeOf()

In ES6, the object. getPrototypeof method can be used to obtain a superclass from a subclass. Such as:

Object.getPrototypeof(ColorPoint) === Point
// true
Copy the code

Therefore, you can use this method to determine whether a class inherits from another class.

3. The super keyword

We saw the super keyword above, which is primarily used to return instances of the parent class. So in real life, super can be used either as a function or as an object. Let’s talk about these two cases.

(1) As a function

In the first case, when super is called as a function, it represents the constructor of the superclass. ES6 requires that the constructor of a subclass must execute the super function once. Here’s an example:

class A {}

class B extends A {
    constructor() {
        super();
    }
}
Copy the code

In the above code, super() means calling the constructor of superclass A, but returns an instance of subclass B, where this inside super refers to B. Therefore, the super () is equivalent to Amy polumbo rototype. Constructor. Call (this).


Note that as a function, super() can only be used in the constructor of a subclass. If used elsewhere, an error will be reported. Such as:

class A {}

class B extends A {
	m() {
		super(a);/ / an error}}Copy the code

As you can see, in this case, super() is a syntactic error when used in m methods of class B.

(2) As an object

In the second case, when super is an object, it points to the prototype object of the superclass in a normal method and to the superclass in a static method. Let’s look at both cases.

1) In ordinary methods

Let’s look at the following code:

class A {
	p() {
		return 2; }}class B extends A {
	constructor() {
		super(a);console.log(super.p()); / / 2}}let b = new B();
Copy the code

In the above code, super.p() in subclass B uses super as an object. As described above, super points to the prototype object of the superclass in a normal method, so super.p() here equals a.protoType.p (). So the final output is 2.


Continuing, since super points to the prototype object of the superclass, methods or properties defined on the instance of the superclass cannot be called through super. Such as:

class A {
	constructor() {
		this.p = 2; }}class B extends A {
	get m() {
		return super.p; // Is defined on a normal method, so super points to the prototype object of the superclass}}let b = new B();
b.m // undefined
Copy the code

In the above code, super is called in a normal method, so that means super points to the prototype object of the superclass. B.m, on the other hand, wants to point directly to the property of the instance of the parent class A.


We can modify the code to define the properties on the prototype object of the parent class so that super can find the specific properties. The details are as follows:

class A {}
A.prototype.x = 2;

class B extends A {
	constructor() {
		super(a);console.log(super.x); / / 2}}let b = new B();
Copy the code

As you can see, the property x is now defined on a.prototype, so super.x can take its value.

2) In static methods

We talked about calling super as an object in a normal method, now let’s look at what a call to a static method looks like.

One of the first things we talked about is that when super is called in a static method, it refers to its parent class. Let’s look at some code:

class Parent {
	static myMethod(msg) {
		console.log('static', msg);
	}
	
	myMethod(msg) {
		console.log('instance', msg); }}class Child extends Parent {
	static myMethod(msg) {
		super.myMethod(msg);
	}
	
	myMethods(msg) {
		super.myMethods(msg);
	}
}

Child.myMethod(1); // static 1
let child = new Child();
child.myMethod(2); // instance 2
Copy the code

As you can see, when calling directly with Child.myMethod(1), it means calling the static method directly. When super is called in a static method, it refers to a static method in its parent class, so print static 1.

Moving on, the following child instantiates an object as new, and then calls the instance. So, the normal method is called, so instance 1 is printed.


One important point to note when using super is that you must explicitly specify whether to use it as a function or as an object, otherwise an error will be reported. Such as:

class A {}
class B extends A {
	constructor() {
		super(a);console.log(super); / / an error}}Copy the code

As you can see, if you refer to it as a function or as an object, it is impossible to tell whether it is a function or an object. When the JS engine parses the code, it will report an error.

Then we need to judge our results by clearly indicating the data type of super. Such as:

class A {}
class B extends A {
	constructor() {
		super(a);// object.valueof () returns the original valueOf the object
		console.log(super.valueOf() instanceof B); // true}}Copy the code

In the above code, we use object.valueof () to indicate that super is an object, and then the JS engine recognizes it and finally prints it successfully.

4. The prototype attribute and the __ proto __ attribute of the class

(1) Inheritance chain of classes

In most browser ES5 implementations, each object has a __proto__ property that points to the corresponding constructor’s prototype property.

Class, as the syntax sugar of the constructor, has both the prototype property and __proto__ property, so there are two inheritance chains. Respectively is:

  • A subclass__proto__Property represents inheritance from the constructor and always points to the superclass.
  • A subclassprototypeProperties of the__proto__Properties represent method inheritance, always pointing to the superclassprototypeProperties.

Let’s use a code to demonstrate:

class A {}class B extends A {

}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
Copy the code

In the above code, the __proto__ property of subclass B always points to the parent class A, and the __proto__ property of the prototype property of subclass B points to the prototype property of the parent class A.

The two prototype chains can be understood as:

  • whenAs an objectWhen a subclassBThe prototype (__proto__Property is the parent classA οΌ›
  • whenAs a constructorWhen a subclassBThe prototype (prototypeProperty is an instance of the parent class.

(2) Inheritance in special cases

Three special cases of inheritance are discussed below. The details are as follows:

In the first case, subclasses inherit from the Object class. Let’s start with some code:

class A extends Object {
    
}

A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
Copy the code

In this case, A is simply A copy of the constructor Object, and an instance of A is an instance of Object.

Second case: there is no inheritance. Let’s start with some code:

class A {

}

A.__proto__ === Function.prototype // true
B.prototype.__proto__ === Object.prototype // true
Copy the code

In this case, A, as A base class, has no inheritance. Since class is the syntactic sugar of the constructor, we can say that A is an ordinary function. A: Function. Prototype

Note that A returns an empty Object (i.e. an Object instance), so a. prototype.__proto__ refers to the prototype property of the constructor Object.

Third case: subclass inherits null. Let’s start with some code:

class A extends null {
	
}

A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true
Copy the code

This case is very similar to the second case. A is also A normal Function, so it inherits directly from function.prototype.

Note that the object returned by A does not inherit any methods, so its __proto__ refers to function. prototype, which actually executes the following code:

class C extends null {
	constructor() {
		return Object.create(null); }}Copy the code

(3) The __ proto __ attribute of the instance

For a subclass, the __proto__ attribute of the __proto__ attribute of the example always points to the __proto__ attribute of the parent instance. That is, the archetype of a subclass is the archetype of the superclass.

Let’s look at a code that looks like this:

let p1 = new Point(2.3);
let p2 = new ColorPoint(2.3.'green');

p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true
Copy the code

In the above code, the subclass ColorPoint inherits Point, so that the prototype of the former prototype is the prototype of the latter.

Thus, we can modify the behavior of a superclass instance by using the __proto__.__proto__ attribute of a subclass instance. The details are as follows:

p2.__proto__.__proto__.printName = function() {
	console.log('Monday');
};

p1.printName(); // 'Monday'
Copy the code

As you can see, Point instance P1 is affected by adding methods to the Point class on instance P2 of ColorPoint.

πŸ“” Examine the past and know the new

Finally, let’s review the whole article with a mind map. See the picture below πŸ‘‡ for details

πŸ“‘ V. Conclusion

Above we talked about the 6 inheritance methods and class inheritance, in today’s development scenarios, is basically parasitic combination inheritance and class inheritance used more. I believe that through the above understanding, we have a new understanding of javascript inheritance.

This is the end of js inheritance! Hope to be helpful to everyone!

If the article is wrong or have not understood the place, welcome little friends comments section ~πŸ’¬

🐣 Easter Eggs One More Thing

(: References

Books πŸ‘‰ES6 Books introduction to ES6 Standards

Books πŸ‘‰ The Little Red Book advanced Programming in JavaScript 4th edition

πŸ‘‰ “javascript advanced programming” note: inheritance

(: External chapter

  • Follow the public account Monday research, the first time to pay attention to quality articles, more selected columns for you to unlock ~
  • If this article is useful to you, make sure you leave footprints before you go
  • That’s all for this article! See you next time! πŸ‘‹ πŸ‘‹ πŸ‘‹