preface
In object-oriented programming, inheritance is a good method to reuse the previous development code, shorten the development cycle and improve the efficiency of development. It allows us to use methods or properties of the original object using inheritance. In a language as flexible as JavaScript, there are six common methods of inheritance, and we’ll go through them one by one.
Prototype chain inheritance
Prototype chain is one of the common inheritance methods, which involves constructors, prototypes and instances. There is a certain relationship among the three, as shown in the following figure.
Each constructor has a prototype object, which in turn contains a pointer to the constructor and an instance that has a pointer to the prototype (__proto__).
The code to implement prototype chain inheritance is simple
function Person(){
this.name = 'parent'
this.play = [1.2.3]
}
function Child() {
this.type = 'children'
}
Child.prototype = new Person() / / *
console.log(new Child())
Copy the code
The core code is that child.prototype = new Person() points the Child’s prototype pointer to an instance of Person. This allows instances of Child to access the attributes and methods of the Person constructor.
var child1 = new Child()
var child2 = new Child()
console.log(child1.name) // parent
child2.play.push('coding')
console.log(child1.play) // [1, 2, 3, 'coding'] *
Copy the code
However, a disadvantage of stereotype chain inheritance is that both instances (child1 and child2) access the same stereotype object and therefore share the same data, which leads to the risk of data tampering.
To solve this problem, let’s move on to the second inheritance method.
Constructor inheritance (borrowing from call)
The reason for the shared properties on the above stereotype is that all Child instances access the same stereotype, so every time I create a new Child, I give it a new stereotype, so this method of borrowing constructor inheritance comes into being.
function Parent(){
this.name = 'parent'
this.play = ['play game'.'coding']
}
// define a method on the superclass prototype
Parent.prototype.getName = function(){
return this.name
}
function Child(){
Parent.call(this) // Call borrows Parent's constructor so it can "steal" properties and methods defined in the Parent constructor
this.type = 'child' // Attributes of the subclass's constructor
}
let child = new Child()
console.log(child.name) // parent
console.log(child.getName()) // ?
Copy the code
Because the Child constructor “borrows the code from the Parent constructor” via the call method, its prototype also has the name and play attributes defined by Parent. This is inheritance, which means that a subclass mimics its parent class by declaring these variables or methods. Although a subclass has the attributes and methods of its parent class, it does not have access to the attributes and methods of its parent class. Child. getName is not a function child.getName is not a function
This is a little fun. The first type of inheritance (stereotype chain inheritance) allows access to properties and methods on the parent stereotype, but the properties and methods on the parent constructor are shared by the subclass. In the second (constructor inheritance), you can have the properties and methods of a separate parent constructor, but cannot access the properties and methods on the parent prototype object.
Why don’t we combine the best of both and take the best of each? Name I want to good, called combination inheritance (no trouble, this way really called this name).
Combinatorial inheritance (stereotype chain inheritance + constructor inheritance)
Stealing your stats and inheriting your dad’s (Fog)
function Parent(){
this.name = 'parent'
this.play = ['play game'.'coding']
}
Parent.prototype.getName = function(){
return this.name
}
function Child(){
Parent.call(this) // borrow constructor #2
this.type = 'child'
}
Child.prototype = new Parent(); // The prototype of the child constructor points to the parent instance #1
// Then the constructor pointer on the prototype points back to its own constructor
Child.prototype.constructor = Child
const child1 = new Child()
const child2 = new Child()
child2.play.push('daydream')
console.log(child1.play === child2.play) // Do not affect each other
child1.getName()
child2.getName() // Normal output
Copy the code
As you can see, the two big problems that plagued us are now resolved. Subclasses not only have exclusive access to the attributes on the parent constructor, but they also have access to the attributes and methods on the parent prototype. The problem, however, is that the Parent constructor is called too many times. As you can see, inheriting from Child (#1) and calling via call (#2), the Parent executes twice in total, and one more constructor execution means one more performance overhead. If Parent’s constructor code is large, each inheritance can be a significant performance cost. Is there any way to avoid making one more call?
😏
There is definitely a solution, and there is one ultimate trick that can be solved, which is also known as the extends sugar in ES6. What is that, here first do not say, sell to keep in suspense, continue to say a little bit of pre-knowledge of the big move.
The above several inheritance methods, more or less have such advantages or disadvantages, it seems that the efforts in the constructor may have found a good solution. Let’s take a different approach and forget about object constructors. What if I just want to inherit properties or methods of an object (instance)?
The Object. Create method in ES5 takes two arguments: Proto will pass the prototype of the newly created Object Object (this cool) and propertiesObject optional description of Object attribute types, this parameter is the Object. The getOwnPropertyDescriptors returned results. For those unfamiliar, go to MDN for a detailed description and use case of the CREATE method
Object.create()
Let’s take a look at some code and see how ordinary objects are inherited. Okay
Primary inheritance
const parent = {
name: 'william'
play: ['coding'.'play game']
getName(){
return this.name
}
}
const child1 = Object.create(parent)
const child2 = Object.create(parent)
child2.name = 'skye'
console.log(child1.name) // william
console.log(child2.name) // skye
child2.play.push('daydream')
console.log(child1.play) // ['coding', 'play game', 'daydream']
console.log(child2.play) // ['coding', 'play game', 'daydream']
Copy the code
Object. Create inherits properties and methods from ordinary objects (instances). This is not black magic. That’s just generating a prototype __proto__ pointer to the object passed in. The generated object can then access the properties and methods of the prototype object through the prototype chain.
console.log(child1.__proto__ === child2.__proto__ == parent) // true
Copy the code
With the first inheritance method we just discussed, stereotype chain inheritance, instances that use the same stereotype object risk tampering with the stereotype data. The original type inheritance is similar to the prototype chain, so it is inevitable to have such disadvantages.
Let’s move on to an inheritance approach that enhances some of the functionality of the original inheritance
Parasitic inheritance
The advantages and disadvantages of this inheritance are the same as those of the original inheritance, but some methods and attributes are added through the factory pattern to the objects derived from the original inheritance
Just look at the code
const parent = {
name: 'william'.
play: ['coding'.'reading'.'play game'].
getName(){
return this.name
}
}
function ChildFactory(origin){
let child = Object.create(origin)
child.getPlay = function(){
return this.play
}
return child
}
let child = ChildFactory(parent)
console.log(child.getName())
console.log(child.getPlay())
Copy the code
This kind of inheritance is not what to say, can be regarded as an enhancement of the original type of inheritance.
Now that we’ve covered all the basics, it’s time to unlock the ultimate Trick. In the third type of combinatorial inheritance above, there is some performance overhead because the method of the superclass constructor is called twice. So let’s start analyzing it. The first overhead is when the call method is inherited, and the second overhead is when the prototype of the child constructor points to the call of the parent constructor. Instead of using new Parent() to generate an instance of the Parent constructor, we can use object.create to inherit the prototype of the Parent class, saving the overhead of the next call to the Parent constructor
function Parent(){
this.name = 'william'.
this.play = ['coding'.'reading']
}
Parent.prototype.getName = function(){
return this.name
}
function Child(){
// combinatorial inheritance, using call to inherit parent constructor properties
Parent.call(this)
this.type = 'child'
}
// Using object.create to inherit the prototype saves the overhead of the next call to the parent constructor
Child.prototype = Object.create(Parent.protype)
Child.prototype.constroctor = Child
Child.prototype.getPlay = function() {
return this.name
}
const child = Child()
console.log(child.name) // william
console.log(child.getName()) // william
console.log(child.getPlay()) // ['coding', 'reading']
Copy the code
Obviously, parasitic combination inheritance solves the pain points in the above five schemes, and can be regarded as a better implementation of the inheritance effect we want. Of course, there are some downsides: it’s too long to write a lot of code for an inheritance, all sorts of prototypes, and you have to manually point the constructor pointer back to the constructor…
Wait, The Times are developing, and JS grammatical norms are becoming more and more complete. Just after ES6, not only did we add the class keyword and a way to define classes without having to write smelly, long, split constructors, but parasitic combinative inheritance also had its syntactic equivalent, extends. Let’s take a look at es6 object-oriented inheritance
// ES5's split declaration of "classes" and property methods on their prototypes
function Parent(name){ // This is equivalent to a constructor
this.name = name
}
// That's not all, the properties of the prototype need to be written in another place
Parent.prototype = {
getName(){
this.name
},
foo: 'bar'
}
// The constructor on the prototype object is only the constructor
Parent.prototype.constructor = Parent
// Then inherit the parasitic combination... Write a bunch of code (omitted)
// With es6
class Parent{
constructor(name) { // the constructor takes the parameters used in the construction
this.name = name
}
// All methods defined here are prototype methods
getName(){
return this.name
}
}
// Subclass inheritance is also convenient
class Child extends Parent{
constructor(name, age){
// If there is a constructor in the subclass, we must call super() before using this.
super(name) // The constructor equivalent of borrowing the parent class constructor is similar to the call inheritance method in constructor inheritance
this.age = age
}
}
const child = new Child('william'.18)
child.getName() // william
Copy the code
conclusion
After walking through this encounter, it is found that in fact the js several commonly used inheritance way is not complex, are on the basis of the original scheme, in order to avoid abuses to take some “upgrade”. I also arrived in these days, the system of sorting out and learning the inheritance of these methods began to realize later. Day-to-day development work rarely involves this, so the systematic learning of inheritance is often neglected. But in the front end, some of the best libraries and design patterns make a lot of use of concepts like inheritance. If you want to further improve your coding ability, it is best to lay down these basic concepts before reading source code or learning design patterns, and slowly and steadily improve your programming ability.