当子类型有时候需要覆盖(与原型中覆盖属性是同样的道理,见《深入理解JavaScript中创建对象模式的演变(原型)》)超类型的某个方法,或者需要添加超类型中不存在的某个方法。这时,应当注意:给原型添加方法的代码一定要放在(用超类型的对象实例作为子类型的原型来)替换原型的语句之后。看以下代码:
function SuperType(){
this.property=true;
}
SuperType.prototype.getSuperValue=function(){
return this.property;
};
function SubType(){
this.subproperty=false;
}
SubType.prototype=new SuperType();//这一句代码即为替换的原型的语句
SubType.prototype.getSubValue=function(){
return this.subproperty;//这时在子类型中新添加的方法
}
SubType.prototype.getSuperValue=function(){
return false;//这时在子类型添加的超类型的同名方法,用于覆盖超类型中的方法,因此,最后反悔了false
}
var instance= new SubType();
console.log(instance.getSuperValue());//false
如果顺序颠倒,那么这两个新添加的方法就是无效的了,最终instance.getSuperValue()得到的结果仍然是从超类型中搜索到的,返回false。这时因为如果颠倒,那么后面添加的方法给了SubType最开始的原型,后面替换原型之后,就只能继承超类型的,而刚刚添加的方法不会被实例所共享,此时实例的[[prototype]]指向的是替换之后的原型对象而不在指向最初的添加了方法的原型对象。
还有一点需要注意的就是,在通过原型链实现继承时,不能使用对象字面量创建原型方法(这样就会再次创建一个原型对象,而不会刚刚的那个用超类型的实例替换的对象),因为这样会切断原型链,无法实现继承。
C
单独使用原型链的问题
问题1: 最主要的问题是当包含引用类型值的原型。首先,回顾以下原型模式创建对象的方法,对于包含引用类型值的原型属性会被所有的实例共享,这样改变其中一个实例,其他都会被改变,这不是我们想要的。这也正是之前关于原型的讲解中为什么要将引用类型的值定义在构造函数中而不是定义在原型对象中。对于原型链,也是同样的问题。
看以下的代码;
function SuperType(){
this.colors=["red","blue","green"];
}
function SubType(){}
SubType.prototype=new SuperType();//这时,SuperType中的this对象指向的是SubType.prototype
var instance1=new SubType();
instance1.colors.push("black");
console.log(instance1.colors);//["red", "blue", "green", "black"]
var instance2=new SubType();
console.log(instance2.colors);//["red", "blue", "green", "black"]
在SuperType构造函数中的this一定是指向由他创建的新对象的,而SubType.prototype正是这个新对象,因此SubType的原型对象便有了colors属性,由于这个属性值是数组(引用类型),因而尽管我们的本意是向instance1中添加一个“black”,但最终不可避免的影响到了instance2。而colors放在构造函数中有问题,如果放在其他的原型对象中,依然会有问题。因此,这是原型链继承的一个问题。
问题二:
在创建子类型的实例时,不能向超类型的构造函数传递参数。实际上,应该说没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
正因为单单使用原型链来实现继承出现的以上两个问题,我们在实践中很少会单独使用原型链。
第二部分:借用构造函数继承
A
为解决以上问题,人们发明了借用构造函数(又称伪造对象或经典继承),这种方法的核心思想是:在子类型构造函数的内部调用超类型构造函数。由于函数只不过是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在(将来)新创建的对象上执行构造函数。注意:这种继承方式没有用到原型链的知识,与基于原型链的继承毫无关系。代码如下:
function SuperType(){
this.colors=["red","blue","green"];
}
function SubType(){
SuperType.call(this);//在子类型构造函数的内部调用超类型构造函数
}
var instance1=new SubType();
instance1.colors.push("black");
console.log(instance1.colors);//["red", "blue", "green", "black"]
var instance2=new SubType();
console.log(instance2.colors);//["red", "blue", "green"]
首先,我们可以看到此种继承方式既完成了继承任务,又达到了我们希望达到的效果:对一个实例的值为引用类型的属性的修改不影响另一个实例的引用类型的属性值。
值得注意的是:这种继承方式与原型链的继承方式是完全不同的。看以下代码:
console.log(instance1 instanceof SubType);//true
console.log(instance1 instanceof SuperType);//false