In the true sense, Javascript is not an object-oriented language and does not provide traditional inheritance methods, but it provides a way of prototype inheritance, using the prototype properties it provides to achieve inheritance.
Prototype and prototype chain
Before talking about prototype inheritance, we should talk about prototypes and prototype chains first. After all, this is the basis for realizing prototype inheritance.
In Javascript, each function has a prototype attribute prototype pointing to its own prototype, and the object created by this function also has a __proto__ attribute pointing to this prototype. The prototype of the function is an object, so this object will also have a __proto__ pointing to its own prototype, so that it goes deeper layer by layer until the prototype of the Object object, thus forming a prototype chain. The following figure explains the relationship between prototypes and prototype chains in Javascript very well.
Each function is an object created by the Function function, so each function also has a __proto__ attribute pointing to the prototype of the Function function. It should be pointed out here that what really forms the prototype chain is the __proto__ attribute of each object, not the prototype attribute of the function, which is very important.
Prototype inheritance
Basic mode
The code copy is as follows:
var Parent = function(){
this.name = 'parent' ;
} ;
Parent.prototype.getName = function(){
return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(){
this.name = 'child' ;
} ;
Child.prototype = new Parent();
var parent = new Parent();
var child = new Child() ;
console.log(parent.getName()); //parent
console.log(child.getName()); //child
This is the easiest way to implement prototype inheritance, directly assigning the object of the parent class to the prototype of the subclass constructor, so that the objects of the subclass can access the properties in the parent class and the prototype of the parent class constructor. The prototype inheritance diagram of this method is as follows:
The advantages of this method are obvious, the implementation is very simple and does not require any special operations; the disadvantages are also obvious. If the subclass needs to perform the same initialization actions as in the parent class constructor, then you have to repeat the operations in the parent class in the subclass constructor:
The code copy is as follows:
var Parent = function(name){
this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
this.name = name || 'child' ;
} ;
Child.prototype = new Parent();
var parent = new Parent('myParent');
var child = new Child('myChild');
console.log(parent.getName()); //myParent
console.log(child.getName()); //myChild
In the above situation, it only requires the name attribute to be initialized. If the initialization work continues to increase, this method is very inconvenient. Therefore, there is a way to improve the following.
Borrow constructor
The code copy is as follows:
var Parent = function(name){
this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
Parent.apply(this,arguments);
} ;
Child.prototype = new Parent();
var parent = new Parent('myParent');
var child = new Child('myChild');
console.log(parent.getName()); //myParent
console.log(child.getName()); //myChild
The above method performs the same initialization work by applying the call to the parent class constructor in the subclass constructor, so that no matter how much initialization work is done in the parent class, the subclass can perform the same initialization work. However, there is another problem with the above implementation. The parent class constructor was executed twice, once in the subclass constructor, and once in the subclass prototype, this is a lot of redundant, so we need to make an improvement:
The code copy is as follows:
var Parent = function(name){
this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
Parent.apply(this,arguments);
} ;
Child.prototype = Parent.prototype;
var parent = new Parent('myParent');
var child = new Child('myChild');
console.log(parent.getName()); //myParent
console.log(child.getName()); //myChild
In this way, we only need to execute the parent class constructor once in the subclass constructor, and at the same time we can inherit the properties in the parent class prototype. This is more in line with the original intention of the prototype, which is to put the content that needs to be reused in the prototype, and we only inherit the reusable content in the prototype. The prototype diagram of the above method is as follows:
Temporary constructor mode (Holy Grail mode)
There is still a problem with the version that borrowed the constructor pattern above. It directly assigns the prototype of the parent class to the prototype of the subclass, which will cause a problem, that is, if the prototype of the subclass is modified, the modification will also affect the prototype of the parent class, and then affect the parent class object. This is definitely not what everyone hopes to see. To solve this problem, a temporary constructor pattern is available.
The code copy is as follows:
var Parent = function(name){
this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
Parent.apply(this,arguments);
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype;
Child.prototype = new F();
var parent = new Parent('myParent');
var child = new Child('myChild');
console.log(parent.getName()); //myParent
console.log(child.getName()); //myChild
The prototype inheritance diagram of this method is as follows:
It is easy to see that by adding a temporary constructor F between the parent class prototype and the child class prototype, the connection between the child class prototype and the parent class prototype is cut off, so that the parent class prototype will not be affected when the child class prototype is modified.
My method
The Holy Grail mode ends in "Javascript Mode", but no matter which method above, there is a problem that is not easy to be discovered. You can see that I added an obj object literal attribute to the prototype property of 'Parent', but it has never been useful. Let’s take a look at the following situation based on the Holy Grail mode:
The code copy is as follows:
var Parent = function(name){
this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
Parent.apply(this,arguments);
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype;
Child.prototype = new F();
var parent = new Parent('myParent');
var child = new Child('myChild');
console.log(child.obj.a); //1
console.log(parent.obj.a); //1
child.obj.a = 2 ;
console.log(child.obj.a); //2
console.log(parent.obj.a); //2
In the above case, when I modify the child object obj.a, the obj.a in the parent class prototype will also be modified, which will cause the same problem as the shared prototype. This happens because when accessing child.obj.a, we will follow the prototype chain and find the prototype of the parent class, then find the obj attribute, and then modify obj.a. Let's take a look at the following situation:
The code copy is as follows:
var Parent = function(name){
this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
return this.name ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){
Parent.apply(this,arguments);
} ;
var F = new Function(){} ;
F.prototype = Parent.prototype;
Child.prototype = new F();
var parent = new Parent('myParent');
var child = new Child('myChild');
console.log(child.obj.a); //1
console.log(parent.obj.a); //1
child.obj.a = 2 ;
console.log(child.obj.a); //2
console.log(parent.obj.a); //2
There is a key problem here. When an object accesses properties in the prototype, the properties in the prototype are read-only to the object. That is to say, the child object can read the obj object, but the obj object reference in the prototype cannot be modified. Therefore, when child modifys obj, it will not affect the obj in the prototype. It just adds an obj attribute to its own object, overwriting the obj attribute in the parent prototype. When the child object modifies obj.a, it first reads the reference to obj in the prototype. At this time, child.obj and Parent.prototype.obj point to the same object, so child's modification of obj.a will affect the value of Parent.prototype.obj.a, and thus affect the object of the parent class. The inheritance method of $scope nesting in AngularJS is implemented by modeling prototype inheritance in Javasript.
According to the above description, as long as the prototype accessed in the subclass object is the same as the parent class prototype, the above situation will occur. Therefore, we can copy the parent class prototype and then assign it to the subclass prototype. In this way, when the subclass modifys the properties in the prototype, it only modifies a copy of the parent class prototype, and will not affect the parent class prototype. The specific implementation is as follows:
The code copy is as follows:
var deepClone = function(source,target){
source = source || {} ;
var toStr = Object.prototype.toString ,
arrStr = '[object array]' ;
for(var i in source){
if(source.hasOwnProperty(i)){
var item = source[i] ;
if(typeof item === 'object'){
target[i] = (toStr.apply(item).toLowerCase() === arrStr) : [] ? {} ;
deepClone(item,target[i]);
}else{
deepClone(item,target[i]);
}
}
}
return target ;
} ;
var Parent = function(name){
this.name = name || 'parent' ;
} ;
Parent.prototype.getName = function(){
return this.name ;
} ;
Parent.prototype.obj = {a : '1'} ;
var Child = function(name){
Parent.apply(this,arguments);
} ;
Child.prototype = deepClone(Parent.prototype);
var child = new Child('child');
var parent = new Parent('parent') ;
console.log(child.obj.a); //1
console.log(parent.obj.a); //1
child.obj.a = '2' ;
console.log(child.obj.a); //2
console.log(parent.obj.a); //1
Based on all the above considerations, the specific implementation of Javascript inheritance is as follows. Only when Child and Parent are functions are considered:
The code copy is as follows:
var deepClone = function(source,target){
source = source || {} ;
var toStr = Object.prototype.toString ,
arrStr = '[object array]' ;
for(var i in source){
if(source.hasOwnProperty(i)){
var item = source[i] ;
if(typeof item === 'object'){
target[i] = (toStr.apply(item).toLowerCase() === arrStr) : [] ? {} ;
deepClone(item,target[i]);
}else{
deepClone(item,target[i]);
}
}
}
return target ;
} ;
var extend = function(Parent,Child){
Child = Child || function(){} ;
if(Parent === undefined)
return Child ;
//Borrow parent class constructor
Child = function(){
Parent.apply(this,argument) ;
} ;
//Inherit the parent class prototype through deep copy
Child.prototype = deepClone(Parent.prototype);
//Reset constructor attribute
Child.prototype.constructor = Child;
} ;
Summarize
Having said so much, in fact, implementing inheritance in Javascript is very flexible and diverse, and there is no best method. Different methods of inheritance need to be implemented according to different needs. The most important thing is to understand the principle of implementing inheritance in Javascript, that is, the problem of prototypes and prototype chains. As long as you understand these, you can easily implement inheritance by yourself.