var extend = function(sb, sp, sbm) {
var F = function() {
},sbp,spp = sp.prototype;
F.prototype = spp;
//用干净函数嫁接得到子类原型
sbp = sb.prototype = new F();
sbp.constructor = sb; //然后指定一个constructor指回子类
//把sbm的上的属性拷贝到子类的原型上
for (var p in sbm) {
sbp[p] = sbm[p];
}
};
那么完成Space继承Plane的代码如下:
extend(Space, Plane, {
XYZ : function() {
alert(this.x * this.y * this.z);
}
});
var spaceObject = new Space(2, 3, 4);
spaceObject.XY();//成功调用超类方法
spaceObject.XYZ();
OK,到了这里,我们基本上就完成任务了,完全从java的方向搞定的。我们现在利用js的特性来优化,让使用extend更加简单。
我们说在java中必须写两个类,每个类都写自己的字段 ,构造函数,方法等,在我们实现的extend函数中也确实把子类,父类都传递了进来,但是我们多了一个参数,那就是子类的方法集合即sbm,第一个参数sb本身也是函数,那是不是可以将这个函数也放进sbm传进来呢?这样extend就变为两个参数,即extend(sp,sbm),现在extend返回一个函数,返回的这个函数就是sp的子类,这是完全可行的,我们叫做extend2吧。
var extend2 = function(sp, sbm) {
var sb = sbm.constructor;
//如果说没有显式的构造函数,那么子类就是直接调用超类构造函数
if (sb == Object) {
sb = function() {
sp.apply(this, arguments);
};
}
extend(sb, sp, sbm);
return sb;
}
我们说要把子类的构造函数放到sbm上,放上去的key叫做constructor,就表示构造器,js中每一个对象都一个constructor属性,它指向构造了这个对象构造函数。sbm本来是个Object对象,它的constructor就指向Object,这个constructor是在sbm关联的那个原型上的,现在我们在sbm上设置某个子类的构造函数,这个时候表示sbm有个自己的constructor。
现在我们在extend2中要做的事情就是提取出构造函数,然后还原为三个参数去调用之前的extend,在java中我们的子类是可以不用构造器的,只要父类也有默认的构造器,那么在这里一样,sbm可能不包含constructor,那么我们需要做一个函数,它调用父类的构造函数,在java中这种情况过程是自动的。所以当sbm.constructor为Object的时候表示sbm没有指定构造函数,这个时候将
sb = function() {
sp.apply(this, arguments);
};
即调用父类构造函数。这样将sb,sp,sbm传递给extend就可以了。
这个时候我们新的继承语法如下:
var NewSpace = extend2(Plane, {
constructor : function(x, y, z) {
Plane.call(this, x, y);
this.z = z;
},
XYZ : function() {
alert(this.x * this.y * this.z);
}
});
var newObject = new NewSpace(3, 4, 5);
newObject.XY();
newObject.XYZ();
到了这里其实已经差不多了,但是细心的读者会发现,我们在extend中会把sbm的所有属性拷贝到子类的原型上,这里岂不是就要把constructor也拷贝到原型上?如果sbm包含了这个constructor其实就无所谓,因为子类的原型的constructor本来就是需要指向这个构造函数的,但是sbm上没有constructor那岂不是要把Object拷贝到子类原型上,答案是不会的,我们在拷贝的时候用的for in循环是迭代不出默认的那个constructor的。
现在我们来看看Ext.extend,应该完全没有问题了。我们用了两个方法extend,extend2,Ext把它合并为了一个方法Ext.extend,所以它会判断传进来的参数然后进行变换,这样Ext.extend就支持两个参数和三个参数进行调用。对于前面用到拷贝属性,Ext做了一个工具函数叫做Ext.apply,对于将一个对象的属性拷贝到一个类的原型上,Ext做了一个工具类叫做Ext.override。
Ext.extend = function() {
// inline overrides 把传入的对象属性复制到到this中
var io = function(o) {
for (var m in o) {
this[m] = o[m];
}
};
//oc其实就是Object函数
var oc = Object.prototype.constructor;
return function(sb, sp, overrides) {
//如果第二个参数是个对象而不是类,那么是用两个参数调用的,第一个参数是父类,第二个参数是对象
if (typeof sp == 'object') {
overrides = sp; //将第三个参数换为对象
sp = sb; //把第一个参数赋值第二个当成父类
sb = overrides.constructor != oc overrides.constructor : function() {
sp.apply(this, arguments);