Home > Article > Web Front-end > Let’s talk about javascript prototypal inheritance_javascript skills
In the true sense, Javascript is not an object-oriented language and does not provide a traditional inheritance method. However, it provides a prototype inheritance method that uses the prototype properties provided by itself to achieve inheritance.
Prototypes and Prototype Chains
Before we talk about prototypal inheritance, we still need to talk about prototypes and prototype chains. After all, this is the basis for realizing prototypal 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, and the prototype of the function is an object, so this object will also have a __proto__ points to its own prototype, and goes deeper layer by layer until it reaches the prototype of the Object object, thus forming a prototype chain. The picture below explains the relationship between prototype and prototype chain 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. What needs to be pointed out here is that it is the __proto__ attribute of each object that actually forms the prototype chain, not the prototype attribute of the function, which is very important.
Prototypal inheritance
Basic Mode
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 simplest way to implement prototypal inheritance. Directly assign the object of the parent class to the prototype of the constructor of the subclass, so that the object of the subclass can access the attributes in the prototype of the parent class and 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. At the same time, the disadvantages are also obvious. If the subclass needs to do the same initialization action as in the parent class constructor, then it must be done in the subclass In the constructor, repeat the operation in the parent class:
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 case, only the name attribute needs to be initialized. If the initialization work continues to increase, this method is very inconvenient. Therefore, there is an improved method as follows.
Borrow constructor
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 calling the constructor of the parent class via apply in the subclass constructor. In this way, no matter how much initialization work is done in the parent class, the subclass can also perform the same initialization work. But there is still a problem with the above implementation. The parent class constructor is executed twice, once in the subclass constructor and once when assigning the subclass prototype. This is very redundant, so we still need to make an improvement. :
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's constructor once in the child class constructor, and at the same time, we can inherit the attributes in the parent class's prototype. This is also more in line with the original intention of the prototype, which is to put the content that needs to be reused in In the prototype, we only inherit the reusable content in the prototype. The prototype of the above method is as follows:
Temporary Constructor Pattern (Holy Grail Pattern)
The last improved version that borrows the constructor pattern above still has problems. It directly assigns the prototype of the parent class to the prototype of the subclass. This will cause a problem, that is, if the prototype of the subclass is modified, then this Modifications will also affect the prototype of the parent class, and thus the parent class object. This is definitely not what everyone wants to see. To solve this problem, there is the temporary constructor pattern.
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 subclass prototype, the connection between the subclass prototype and the parent class prototype is cut off, so that when the subclass prototype is modified It will not affect the parent class prototype.
My method
The Holy Grail mode is over in "Javascript Mode", but no matter which of the above methods, there is a problem that is not easy to find. You can see that I added an obj object literal attribute to the prototype attribute of 'Parent', but it has never been used. Let’s take a look at the following situation based on the Holy Grail model:
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 situation, when I modify the child object obj.a, obj.a in the prototype of the parent class will also be modified, which causes the same problem as the shared prototype. This situation occurs because when accessing child.obj.a, we will follow the prototype chain to 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:
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 issue here. When an object accesses the properties in the prototype, the properties in the prototype are read-only for the object. That is to say, the child object can read the obj object, but cannot modify the obj object in the prototype. Reference, so when the child modifies 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 class 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 the modification of obj.a by the child will affect The value of Parent.prototype.obj.a, which in turn affects the object of the parent class. The inheritance method of $scope nesting in AngularJS is implemented by prototypal inheritance in Javascript.
According to the above description, as long as the prototype accessed in the subclass object is the same object as the parent class prototype, then the above situation will occur, so we can copy the parent class prototype and then assign it to the subclass prototype, so When a subclass modifies the properties in the prototype, it only modifies a copy of the parent class prototype and does not affect the parent class prototype. The specific implementation is as follows:
Parent.apply(this,arguments) ;
} ;
Child.prototype = deepClone(Parent.prototype) ;
var parent = new Parent('parent') ;
console.log(parent.obj.a); //1
child.obj.a = '2' ;
console.log(child.obj.a); //2
console.log(parent.obj.a); //1
var extend = function(Parent,Child){
Child = Child || function(){} ;
if(Parent === undefined)
return Child ;
//借用父类构造函数
Child = function(){
Parent.apply(this,argument) ;
} ;
//通过深拷贝继承父类原型
Child.prototype = deepClone(Parent.prototype) ;
//重置constructor属性
Child.prototype.constructor = Child ;
} ;
总结
说了这么多,其实Javascript中实现继承是十分灵活多样的,并没有一种最好的方法,需要根据不同的需求实现不同方式的继承,最重要的是要理解Javascript中实现继承的原理,也就是原型和原型链的问题,只要理解了这些,自己实现继承就可以游刃有余。