instance1和instance2都不是SuperType的实例。这里的继承只是表面上的继承。我们可以分析一下这个继承的过程:首先声明了两个构造函数,然后执行var instance1=new SubType();即通过new调用了构造函数SubType,既然调用了SubType构造函数,此时便进入了SubType执行环境,该环境中又调用了SuperType()函数(注意:这里未使用new,故此时应当把SuperType函数当作一般函数来处理),又因为SubType()中this是指向instance1(SubType是构造函数啊!)的,所以,接下来就会在instance1对象上调用普通函数SuperType,因为这个普通函数在instance1上被调用,因此,SuperType中的this又指向了Instance1,这是,instance1对象便添加了属性值为应用类型的colors属性,instance2同理。
这解决了原型链继承中的第一个问题。
B
相对于原型链而言,借用构造函数有一个很大的优势,即可以在子类型构造函数中向超类型构造函数传递参数。如下所示:
function SuperType(name){
this.name=name;
}
function SubType(){
SuperType.call(this,"zzw");
this.age=21;
}
var instance1=new SubType();
console.log(instance1.name);//zzw
console.log(instance1.age);//21
其中SuperType.call(this,"zzw");又可以写做SuperType.apply(this,["zzw"]);(关于这一部分知识点可以看《JavaScript函数之美~ 》第三部分)。
言归正传,让我们先分析函数时如何执行的:首先声明了两个构造函数,然后通过new操作符调用了Subtype构造函数,随即进入Subtype构造函数的执行环境,执行语句SuperType.call(this.zzw);,随即进入了普通函数(同样地,只要没有使用new操作符,它就是一般函数)的执行环境并传递了参数,且使用了call方法,说明在instance1对象上调用普通函数SuperType,因为在对象上调用的,所以SuperType函数中的this指向instance1,并最终获得了name属性。SuperType函数执行环境中的代码执行完毕之后,执行环境又回到了SubType构造函数,这时,instance对象又获得了属性值为21的age属性。
ok!借用构造函数继承又解决了原型链继承的第二个问题。
然而,借用构造函数就没有缺点吗?答案是有!因为仅仅使用借用构造函数,就无法避免构造函数模式的问题--方法在构造函数中定义(而导致浪费)。而且,我们说这种方式与原型链不同,因此在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。
考虑到上述问题,借用构造函数的技术也是很少单独使用的。
第三部分:组合继承(伪经典继承)
与创建对象时,我们将自定义构造函数模式和原型模式组合一样,这种继承方式即将原型链和借用构造函数的技术组合到一起,从而发挥两者之长。主要思想是:使用原型链实现对原型属性(即希望让各个实例共享的属性)和方法(对于借用构造函数,继承方法显然是不合适的)的继承,而通过借用构造函数来实现对实例属性(即不希望共享的属性,之前方法是通过实例属性覆盖原型属性)的继承。这样,既通过在原型上定义方法实现了函数复用(即只创建一次方法,被多次使用,如果将函数定义在构造函数中,创建一个实例,就会同时创建一个相同的方法,无法复用,影响性能),又能够保证每个实例都有自己的属性(因为借用构造函数可以传递参数啊!把实例属性通过借用构造函数实现,就不用去覆盖了)。
下面来看这样一个例子:
function SuperType(name,age){
this.name=name;//实例属性使用借用构造函数模式 this.age=age;//实例属性使用借用构造函数模式
this.colors=["red","blue","green"];//这个数组虽然会同时被原型链和借用构造函数添加使用,但最后根据原型链的搜索机制,是按照借用构造函数模式实现的。
}
SuperType.prototype.sayName=function(){
console.log(this.name);//实现同样效果的方法使用原型链模式
};
function SubType(name,age){
SuperType.call(this,name,age);//借用构造函数模式的有点就是可以向子类型构造函数中的超类型构造函数传递参数,这里this的用法很重要
};
SubType.prototype=new SuperType();//使用SuperType的实例来替换为SubType的原型对象
SubType.prototype.constructor=SubType;// 这句代码即将SubType的原型对象的constructor属性指向SubType,但这一句去掉也不会影响结果。
SubType.prototype.sayAge=function(){
console.log(this.age);//在原型对象中定义方法,可以使得该方法实现复用,增强性能
};
var instance1=new SubType("zzw",21);
instance1.colors.push("black");
console.log(instance1.colors);//["red", "blue", "green", "black"]
instance1.sayName();//zzw
instance1.sayAge();//21
var instance2=new SubType("ht",18);
console.log(instance2.colors);//["red", "blue", "green"]
instance2.sayName();//ht
instance2.sayAge();//18