复制继承是最原始的方法,其设计思路:利用for in语句遍历对象成员,然后逐一将其复制给另一个对象,通过这种“蚂蚁搬家”的方式来实现继承关系。例如,在下面的示例中,先定义一个F类,它包含4个成员,然后将其实例化并把它的所有属性和方法都复制给一个空对象o,这样对象o就拥有了F类的所有属性和方法。
function F(x,y){//构造函数F
this.x=x;
this.y=y;
this.add=function{
return this.x+this.y;
}
}
F.prototype.mul=function{
return this.x*this.y;
}
var f=new F(2,3)
var o={}
for(var i in f){//遍历构造函数的实例,把它的所有成员都赋值给对象o
o[i]=f[i];
}
alert(o.x);//2
alert(o.y);//3
alert(o.add);//5
alert(o.mul);//6
对于该复制继承法,可以将其封装,使其具有较大的灵活性。
Function.prototype.extend=function(o){//为Function扩展复制继承的方法
for(var i in o){
this.constructor.prototype[i]=o[i];//把参数对象成员复制给当前对象的构造函数原型对象
}
}
上面的封装函数通过原型对象为Function核心对象扩展一个方法,该方法能够把指定的参数对象完全复制给当前对象的构造函数的原型对象。this关键字指向的是当前实例对象,而不是构造函数本身,所以要为其扩展原型成员,就必须使用constructor属性来指向它的构造器,然后通过prototype属性指向构造函数的原型对象。
接下来,新建一个空的构造函数,并为其调用extend方法,把传递进来的F类的实例对象完全复制为原型对象成员。注意,此时就不能够定义对象直接量,因为extend方法只能为构造函数复制继承:
var o=function{};
o.extend(new F(2,3));
复制继承法也不是真正的继承,它是通过反射机制复制类对象的所有可枚举属性和方法来模拟继承。这种方法能够实现模拟多继承。不过,它的缺点也很明显:
❑由于是反射机制,复制继承法不能继承非枚举类型的方法,系统核心对象的只读方法和属性也是无法继承的。
❑通过反射机制来复制对象成员的执行效率会非常差。对象结构越庞大,这种低效就表现得越明显。
❑如果当前类型包含同名成员,那么这些成员可能会被父类的动态复制所覆盖。
❑在多重继承的情况下,复制继承不能够清晰地描述父类与子类的相关性。
❑只有在类被实例化后,才能够实现遍历成员和复制成员,因此它不能够灵活支持动态参数。
❑由于复制继承法仅是简单地引用赋值,如果父类的成员值包含引用类型,那么用复制继承法继承后,与原型继承法一样副作用很多。
还可以对复制继承法进行适当优化,通过对象克隆方式来实现,这样就可以避免一个个复制对象成员所带来的低效率,具体方法如下:
首先,为Function对象扩展一个方法,该方法能够把参数对象赋值给一个空构造函数的原型对象,然后实例化构造函数并返回实例对象,这样该对象就拥有构造函数包含的所有成员,例如:
Function.prototype.clone=function(o){//对象克隆方法
function Temp{};//新建空构造函数
Temp.prototype=o;//把参数对象赋值给该构造函数的原型对象
return new Temp;//实例化后的对象
}
然后,调用该方法来克隆对象。克隆方法返回的是一个空对象,不过它存储了指向给定对象的原型对象指针,这样就可以利用原型链来访问这些变量,从而在不同对象之间实现继承关系。例如:
var o=Function.clone(new F(2,3));//调用Function对象的克隆方法
alert(o.x);//2
alert(o.y);//3
alert(o.add);//5
alert(o.mul);//6