Previous
As we all know, inheritance is a common feature of all OO languages. In JavaScript, its inheritance mechanism is very different from other OO languages. Although ES6 provides us with the same syntax sugar as object-oriented inheritance, its bottom layer is still a constructor, so it is very important to understand the underlying principle of inheritance. So let's discuss the inheritance mechanism in JavaScript today.
Prototype and prototype chain
In order to understand inheritance, we must understand the prototype and prototype chain in JavaScript. I discussed the prototype in the previous article, and interested partners can have a look at it~
Understanding prototype and prototype chain
inherit
In JavaScript, there are six main common inheritance methods. Next, I will analyze each inheritance method and summarize their advantages and disadvantages.
1. Prototype chain inheritance
The concept of prototype chain inheritance
In JavaScript, implementation inheritance mainly depends on prototype chain. The basic idea is to use prototypes to let one reference type inherit the properties and methods of another reference type.
Let's briefly review the relationship among constructors, prototypes and instances: each constructor has a prototype object prototype, the prototype object contains a pointer to the constructor, and the instance contains an internal pointer to the prototype object__
What happens if we make the prototype object equal to an instance of another type? Let's look at the following code.
function Father() { this.name = 'zhang'; } Father.prototype.sayName = function() { console.log(this.name); } function Son() { this.age = 18; } // Inherited Father Son.prototype = new Father(); Son.prototype.sayAge = function() { console.log(this.age); } const xiaoming = new Son(); console.log(xiaoming.sayName()) // 'zhang'
In the above code, Son inherits the other, and inherits by creating an instance of the other and pointing Son.prototype to the other instance out of new. The essence of the implementation is to rewrite the prototype object, waiting for an instance of a new type, that is to say, all the attributes and methods that originally existed in the Father constructor also exist in Son.prototype.
It can be seen from the above figure that instead of using the prototype provided by Son by default, we have replaced it with a new prototype. This prototype is an instance of Father, and there is a pointer inside it to the prototype of Father. Because the prototype of Son has been rewritten, the constructor property of xiaoming instance now points to the parent. In a word, the conclusion is that Son inherits the other, while the other inherits the Object. When calling the xiaoming.toString() method, it actually calls the toString method in Object.prototype.
Note: the code to add methods to the subclass prototype must be placed after the statement to replace the prototype
We also need to remind you that when using prototype chain inheritance, we must not create prototype methods with object literals, because this will rewrite the prototype chain. Let's look at the following code.
function Father() { this.name = 'zhang'; } Father.prototype.sayName = function() { console.log(this.name); } function Son() { this.age = 18; } // Inherited Father Son.prototype = new Father(); Son.prototype = { sayAge: function() { console.log(this.age) } } const xiaoming = new Son(); console.log(xiaoming.sayName()) // 'error reporting'
Using object literals to create prototype methods will cut off the inheritance relationship between Father and Son~
Advantages of prototype chain inheritance
An instance object of a subtype has all the properties and methods of a supertype.
Disadvantages of prototype chain inheritance
As I mentioned in the previous article, prototype properties containing reference type values are shared by all instances. When inheritance is implemented through a prototype, the prototype will actually become an instance of another type, and the original instance property will naturally become the current prototype property.
function Father() { this.cars = ['Benz', 'bmw', 'Lamborghini']; } Father.prototype.sayName = function() { console.log(this.name); } function Son() { this.age = 18; } // Inherited Father Son.prototype = new Father(); const xiaoming = new Son(); xiaoming.cars.push('Wuling Hongguang'); console.log(xiaoming.cars); //"Benz, BMW, Lamborghini, Wuling Hongguang" const xiaohong = new Son(); console.log(xiaohong.cars); //"Benz, BMW, Lamborghini, Wuling Hongguang"
It can be found from the above code that when the attribute in Father is of reference type, of course, each instance of Father will have its own array cars attribute. When Son inherits the other, Son.prototype becomes an instance of the other. As a result, xiaoming and xiaohong share a cars attribute, which we don't want to appear in inheritance.
The second problem is that when you create an instance of Son, you cannot pass parameters to the constructor of Father, that is to say, there is no way to pass parameters to the super type constructor without affecting all the object instances.
The second way I'm going to inherit is constructor inheritance, which can solve the problem of containing reference type values.
2. Constructor inheritance
The concept of constructor inheritance
The basic idea of implementing constructor inheritance is quite simple, that is to call the supertype constructor inside the subtype constructor.
Let's look at the following code:
function Father() { this.cars = ['Benz', 'bmw', 'Lamborghini']; } function Son() { // Inherit from Father Father.call(this); } const xiaoming = new Son(); xiaoming.cars.push('Wuling Hongguang'); console.log(xiaoming.cars); //"Benz, BMW, Lamborghini, Wuling Hongguang" const xiaohong = new Son(); console.log(xiaohong.cars); //"Benz, BMW, Lamborghini"
By using the call() method (or the apply() method), the parent constructor is called while creating the xiaoming instance, so that all the object initialization code defined by the parent constructor will be executed on the instance object of Son, so xiaoming and xiaohong have their own cars properties.
Another advantage of constructor inheritance is that it can pass parameters to a supertype constructor. Let's look at the following code.
function Father(name) { this.name = name; } function Son(name, age) { Father(this, name); this.age = age; } const xiaoming = new Son('Xiaoming', 19); console.log(xiaoming.name); //'Xiaoming' console.log(xiaoming.age); //19
We create an instance of xiaoming and pass two parameters, name and age. The name parameter passes parameters to name in the Father constructor by calling the Father constructor. Therefore, the instance of xiaoming has two instance properties, name and age.
Advantages of constructor inheritance
You can pass parameters to a supertype constructor in a subtype constructor; objects created by a subtype constructor have their own properties and methods (reference types)
Disadvantages of constructor inheritance
Obviously, when methods are defined in constructors, function reuse is impossible, so constructor inheritance is rarely used alone. The inheritance method introduced next is the combination of prototype chain and constructor, which is called combination inheritance.
3. Combination inheritance
The concept of combinatorial inheritance
The basic idea of combinatorial inheritance is to use prototype chain to inherit prototype properties and methods, and to use constructor to inherit instance properties.
The advantage of using combinatorial inheritance is that the function reuse is realized by defining methods on the prototype, and each instance can be guaranteed to have its own properties. Let's look at the following code.
function Father(name) { this.name = name; this.cars = ['Benz', 'bmw', 'Lamborghini']; } Father.prototype.sayName = function() { console.log(this.name); } function Son(name, age) { // Inheritance properties Father.call(this, name); //Second call to Father() this.age = age; } // Inheritance method Son.prototype = new Father(); //First call to Father() Son.prototype.constructor = Son; Son.prototype.sayAge = function() { console.log(this.age); } const xiaoming = new Son('xiaoming', 18); xiaoming.cars.push('Wuling Hongguang'); console.log(xiaoming.cars); //"Benz, BMW, Lamborghini, Wuling Hongguang" xiaoming.sayName(); //'xiaoming' xiaoming.sayAge(); //18 const xiaohong = new Son('xiaohong', 20); console.log(xiaohong.cars); //"Benz, BMW, Lamborghini" xiaohong.sayName(); //'xiaohong' xiaohong.sayAge(); //20 console.log(xiaoming instanceof Son) //true console.log(xiaoming instanceof Father) //true console.log(xiaoming instanceof Object) //true
Advantages of combining inheritance
Combined inheritance avoids the defects of prototype chain inheritance and constructor inheritance, and integrates their advantages, becoming the most commonly used inheritance mode in JavaScript.
Disadvantages of combinatorial inheritance
The biggest problem with combinatorial inheritance is that in any case, the supertype constructor is called twice. One is when creating a subtype prototype, the other is inside the subtype constructor.
4. Prototype inheritance
The concept of prototype inheritance
Prototype inheritance is to create new objects based on existing objects with the help of prototype.
Let's look at the following code.
function object(o) { function F() {} F.prototype = o; return new F(); } const person = { name: 'zhangsan', cars: ['Benz', 'bmw', 'Lamborghini'] } const anotherPerson = object(person); anotherPerson.name = 'lisi'; anotherPerson.cars.push('Wuling Hongguang'); console.log(anotherPerson.name); //'lisi' console.log(anotherPerson.cars); //"Benz, BMW, Lamborghini, Wuling Hongguang" const yetAnotherPerson = object(person); yetAnotherPerson.name = 'wangwu'; console.log(yetAnotherPerson.name); //'wangwu' console.log(yetAnotherPerson.cars); //"Benz, BMW, Lamborghini, Wuling Hongguang"
object() is actually a shallow copy of an object. The premise of prototype inheritance is that you must have one object as the basis of another.
ES5 added the Object.create() method, which normalizes prototype inheritance. I will not introduce this method here. For interested partners, please refer to the MDN documentation Object.create()
Prototype inheritance advantages
If you want to keep only one object similar to another, prototype inheritance can be fully competent.
Disadvantages of prototype inheritance
We believe that you have seen the shortcomings of prototype inheritance. The attributes that contain reference type values always share the corresponding values, just like using prototype chain inheritance.
5. Parasitic inheritance
The concept of parasitic inheritance
Parasitic inheritance is closely related to prototype inheritance, that is, to create a function that only encapsulates the inheritance process. The function enhances the object internally in some way, and finally returns the object.
Let's take a look at the code below.
function createAnother(original) { const clone = Object.create(original); clone.sayHi = function() { console.log('hi'); } return clone; } const person = { name: 'zhangsan', cars: ['Benz', 'bmw', 'Lamborghini'] } const anotherPerson = createAnother(person); anotherPerson.sayHi(); //'hi' const yetAnotherPerson = createAnother(person); yetAnotherPerson.sayHi(); //'hi' console.log(anotherPerson.sayHi == yetAnotherPerson.sayHi) //false
In this example, we encapsulate a function of createnote, which takes a parameter, that is, it will be the basic object of the new object. We can see that the other person and yetAnotherPerson have their own sayHi methods.
Parasitic inheritance is also a useful pattern when you focus on objects rather than custom types and constructors.
Advantages of parasitic inheritance
Inherited objects all have their own properties and methods (reference types).
Disadvantages of parasitic inheritance
Using parasitic inheritance to add functions to objects can reduce efficiency due to the inability to reuse functions, similar to the constructor inheritance pattern.
6. Parasitic combinatorial inheritance
The concept of parasitic combinatorial inheritance
The so-called parasitic combinatorial inheritance is to inherit the attributes through the constructor and inherit the methods through the mixed form of the prototype chain. The basic idea behind it is that we don't need to call the supertype constructor to specify the prototype of the subtype. All we need is a copy of the supertype prototype.
In essence, parasitic inheritance is used to inherit the prototype of a supertype, and then assign the result to the prototype of a subtype. Let's look at the following code.
function inheritPrototype(Son, Father) { const prototype = Object.create(Father.prototype); prototype.constructor = Son; Son.prototype = prototype; } function Father(name) { this.name = name; this.cars = ['Benz', 'bmw', 'Lamborghini']; } Father.prototype.sayName = function() { console.log(this.name); } function Son(name, age) { Father.call(this, name); //Call Father this.age = age; } inheritPrototype(Son, Father); Son.prototype.sayAge = function() { console.log(this.age); }
The efficiency of this example is that it only calls the family constructor once, and therefore avoids creating unnecessary and redundant properties on the Son.prototype.
Advantages of parasitic combinatorial inheritance
Parasitic combinatorial inheritance only calls the supertype constructor once, which is generally considered as the best inheritance paradigm for reference types by developers.
Parasitic combinatorial inheritance has no disadvantages
summary
There is still a long way to go for front-end learning. This article is just the tip of the iceberg. I hope this article written by front-end cc can bring new knowledge expansion to kids. I hope that front-end cc and all front-end kids can grow together in front-end career, and rush to duck!