One of the characteristics of object-oriented is inheritance. Most object-oriented programming languages support two types of inheritance: interface inheritance and implementation inheritance. Interface inheritance only inherits method signatures, while implementation inheritance inherits the actual methods. Since functions have no signatures in JavaScript, interface inheritance cannot be implemented. In JavaScript, inheritance is mainly implemented through the prototype chain.
Implementing inheritance based on the prototype chain
The basic idea of implementing inheritance based on the prototype chain is to use the prototype to let one reference type inherit the properties and methods of another reference type. Previously we have introduced the relationship between prototypes, constructors and object instances, and analyzed their memory model structures in detail. We use the following example to analyze how JavaScript implements inheritance based on the prototype chain.
// 创建父类 function Parent(){ this.parentValue = "Parent"; } // 在父类的原型中添加方法 Parent.prototype.showParentValue = function(){ alert(this.parentValue); } // 创建子类 function Child(){ this.childValue ="Child"; } // 实现继承,让子类Child的原型链指向Parent对象 Child.prototype = new Parent(); // 在子类的原型中添加方法 Child.prototype.showChildValue = function(){ alert(this.childValue); } // 创建子类对象 var c = new Child(); // 子类对象调用继承自父类的方法 c.showParentValue(); // 子类对象调用自己的方法 c.showChildValue();
In the above code, we first create a parent class Parent and add the showParentValue method to its prototype.
Then we created a subclass Child and implemented inheritance by letting the prototype chain of the subclass point to the parent class.
/** 实现继承的关键代码 **/Child.prototype = new Parent();
Then add the showChildValue method in the prototype chain of the subclass. Then a subclass object is created. At this time, the subclass object c can call its own methods or methods inherited from the parent class.
After executing the above code, the memory model diagram of the subclass and parent class is as shown below:
From the above picture we can see, When creating a subclass, the prototype attribute of the subclass points to the prototype object of the subclass. When we make the prototype of the subclass point to the parent class object through the Child.prototype = new Parent(); statement, we actually rewrite the prototype object of the subclass. At this time, there will be a _proto_ attribute in the prototype object of the subclass pointing to the prototype object of the parent class. The original subclass prototype object (red area in the picture) is actually useless.
The methods we add in the subclass prototype will be added to the new subclass prototype. At the same time, the attributes in the parent class will also be written to the new subclass prototype.
After we create the subclass object c, we call the showParentValue method of the parent class through the object c. If object c does not find this method in its own space, it will search in the prototype of the subclass through the _proto_ attribute. It also does not find this method, and then it will go to the prototype of the parent class through the _proto_ attribute in the prototype. to search, at this time, the showParentValue method is found and executed correctly.
The above process is the process of implementing inheritance based on the prototype chain.
Complete memory model based on prototype chain to implement inheritance
In the above memory model based on prototype chain to implement inheritance, we actually describe one less object: Object. All reference types inherit from Object, and this inheritance is also implemented through the prototype chain. Therefore, the complete memory model based on the prototype chain to implement inheritance is as shown below:
The default prototype of all functions is Object, so the default prototype will contain an internal pointer pointing to Object.prototype. There are built-in hasOwnProperty, isPrototypeOf, propertyEmunerable, toLocaleString, toString and valueOf methods in Object.prototype, so our custom types will inherit these methods.
Precautions when using the prototype chain for inheritance
When using the prototype chain for inheritance, please pay attention to the following issues:
1. After setting the prototype chain, reassign the prototype chain.
2. Methods must be added or overridden after assignment in the prototype chain.
For the first point, let’s look at the following example:
function Parent(){ this.parentValue = "Parent"; } Parent.prototype.showParentValue = function(){ alert(this.parentValue); } function Child(){ this.childValue ="Child"; } //实现继承,让Child的原型链指向Parent对象 Child.prototype = new Parent(); //下面的操作重写了Child的原型 Child.prototype = { showChildValue:function(){ alert(this.value); } }
在上面的代码中,我们分别创建了一个父类和一个子类,并让子类的原型指向父类对象,实现继承。但是在这之后,我们又通过Child.prototype = {...}的方式重写了子类的原型,在原型的重写一文中我们已经讲过,重写原型实际上是使子类的原型重新指向一个新的子类原型,这个新的子类原型和父类之间并没有任何的关联关系,所以子类和父类之间此时不再存在继承关系。
对于第二点也很好理解,也来看一个例子:
function Parent(){ this.parentValue = "Parent"; } Parent.prototype.showParentValue = function(){ alert(this.parentValue); } function Child(){ this.childValue ="Child"; } //在实现继承之前为子类在原型中添加方法 Child.prototype.showChildValue = function(){ alert(this.childValue); } //实现继承,让Child的原型链指向Parent对象 Child.prototype = new Parent();
在上面的代码中,我们分别创建了父类和子类。在创建子类之后马上为子类在原型中添加一个方法,然后才让Child的原型链指向Parent对象,实现继承。
这样做的后果是实现继承之后,子类指向的是新的子类原型,而前面添加的方法是放置在原来的原型中的(内存模型图中的红色区域),所以在实现继承之后,子类对象将不再拥有这个方法,因为原来的原型现在已经没有作用了。
方法的覆盖及原型链继承的缺点
如果我们需要实现子类的方法来覆盖父类的方法,只需要在子类的原型中添加与父类同名的方法即可。
/** 覆盖父类中的showParentValue方法 **/ Child.prototype.showParentValue = function(){ alert("Override Parent method"); }
原型链虽然是否强大,可以实现继承,但是原型链也存在一些缺点。原型链继承的缺点主要有:
1、使用原型链进行继承最大的缺点是无法从子类中调用父类的构造函数,这样就没有办法把子类中的属性赋值到父类中。
2、如果父类中存在引用类型的属性,此时这个引用类型会添加到子类的原型中,当第一个对象修改这个引用之后,其它对象的引用同样会被修改。
原型链和原型在处理引用类型的值的时候存在同样的问题。我们在介绍原型的时候曾经举过一个使用引用类型的例子。在使用原型链时同样会有这个问题。来看下面的例子:
// 创建父类 function Parent(){ this.parentValue = "Parent"; //引用类型的属性 this.friends = ['Leon','Ada']; } // 在父类的原型中添加方法 Parent.prototype.showParentValue = function(){ console.info(this.name+"["+this.friends+"]"); } // 创建子类 function Child(){ this.childValue ="Child"; } // 实现继承,让子类Child的原型链指向Parent对象 Child.prototype = new Parent(); // 在子类的原型中添加方法 Child.prototype.showChildValue = function(){ console.info(this.name+"["+this.friends+"]"); } // 创建子类对象 var person1 = new Child(); person1.name = "Jack"; person1.friends.push('Tom'); var person2 = new Child(); person2.name = "John"; console.info(person2.friends);
在上面的代码中,在父类中有一个引用类型的数组对象属性friends,在子类实现继承之后,子类对象person1为它的friends添加了一个新的朋友,此时,新的朋友是被添加到父类的原型中的,所以在这之后创建的所有新的对象都会有一个新的朋友“Tom”。这就是引用类型属性存在的问题。
以上就是JavaScript面向对象-基于原型链实现继承的内容,更多相关内容请关注PHP中文网(www.php.cn)!