继承
继承的主要好处表现在代码的重用方面,通过建立类之间的继承关系,有些方法我们只需要定义一次就可以了。同样,如果需要修改这些方法或排查其中的错误,那么由于其定义只出现在一个位置,所以非常有利于节省时间和精力。MooTools实现类的继承有两种模式:原型式继承和多亲继承,原型式继承由内建的Extends Mutator来实现,多亲继承由Implements Mutator或implement方法来实现。
原型式继承这里就不多讲了,着重讲一下多亲继承。在JavaScript里,因为一个对象只能拥有一个原型对象,所以不允许子类继承多个超类,不过我们可以利用多个掺元类(minix class)或掺元对象对一个类进行扩充,这样类的实例就可以拥有mixin类(对象)中的方法、属性,所以这实际上实现了多继承的效果。通常mixin类(对象)包含一些通用的方法,大家可以看看MooTools里Class.Extras模块中三个mixin类的定义(Chain、Events、Options)。
这里还需要注意的的一点是,在派生具有私有成员的的类或implement mixin类(对象)时,因为在父类(或mixin)中访问这些私有变量的方法是公有的,所以他们也会被遗传下来。所以子类可以间接访问父类(或mixin)的私有成员,但子类自身的实例方法都不能直接访问这些私有属性,而且你也不能在子类中添加能够直接访问他们的方法(作用域变量,你想访问也访问不了啦,呵呵)。
首先我们先建立一个基类:
var Animal = new Class({ initialize: function(age){ this.age = age; } });
使用Extends Mutator派生一个子类:
var Cat = new Class({ Extends: Animal, initialize: function(name,age){ this.parent(age); //callsinitalizemethodofAnimalclass this.name = name; } }); var cat = new Cat('Micia',20); console.log(cat.name); //'Micia' console.log(cat.age);//20
利用Implements Mutator扩充一个类,首先建立一个mixin类:
var Mixin = new Class({ getName: function(){ return this.name; }, setName: function(value){ this.name = value } }); var Cat = new Class({ Extends: Animal, Implements: Mixin, initialize: function(name,age){ this.parent(age); //callsinitalizemethodofAnimalclass this.name = name; } }); var cat = new Cat('Micia',20); console.log(cat.name); //'Micia' console.log(cat.age); //20 cat.setName('Dog'); console.log(cat.getName()); //'Dog'
使用implement方法扩充一个类,首先家里一个mixin对象:
//mixin对象存储一些通用方法,可以被不同的类implement var objMixin = (function(){ var counter = 0; return { init: function(){ counter + = 1; }, getCounter: function(){ return counter; }, getAge: function(){ return this.age; }, setAge: function(value){ this.age = value; } }; })(); var Cat = new Class({ Extends: Animal, Implements: Mixin, initialize: function(name,age){ this.parent(age); //callsinitalizemethodofAnimalclass this.name = name; } }); Cat.implement(objMixin); var Dog = new Class({ Extends: Animal, Implements: Mixin, initialize: function(name,age){ this.parent(age); //callsinitalizemethodofAnimalclass this.name = name; } }); Dog.implement(objMixin); var cat = new Cat('Micia',20); console.log(cat.name); //'Micia' console.log(cat.age); //20 cat.setName('汤姆'); console.log(cat.getName()); //'汤姆' cat.setAge(12); console.log(cat.getAge()); //12 //对mixin对象的私有属性进行操作 cat.init(); console.log(cat.getCounter());//1 var dog = new Dog('小狗',6); console.log(dog.name); //'小狗' console.log(dog.age); //6 dog.setName('布鲁托'); console.log(dog.getName());//'布鲁托' dog.setAge(8); console.log(cat.getAge());//8 //对mixin对象的私有属性进行操作 dog.init(); console.log(dog.getCounter()); //2 console.log(cat.getCounter());//2
大家都看明白了吧,呵呵,不过通过上面的代码我们引申出另外一个问题,注意上面的Cat类的设计,我们首先设计了Extends,然后是Implements,再就是Cat类本身的方法属性,MooTools内部对Class构造函数解析时是按照我们设计时的顺序解析的吗?答案是按照我们设计时的顺序解释的。简单来讲MooTools通过for-in对对象进行枚举来遍历每个成员进行解释的,等等......那个ECMAScript最新版对for-in语句的遍历机制又做了调整,属性遍历的顺序是没有被规定的,也就是说随机的,那么MooTools是怎样保证按顺序解释的呢?先看下面这段代码:
var obj = { Waa: "Waa", aa: 'aa', 68: '68', 15: '15', tt: 'tt', '-7': '-7', _: "___", online: true }; for(var k in obj){ console.log(k); }
把它放在各个浏览器都执行一遍,你会发现IE、火狐、Safari浏览器的JavaScript解析引擎遵循的是较老的ECMA-262第三版规范,属性遍历顺序由属性构建的顺序决定,而Chrome、Opera中使用 for-in 语句遍历对象属性时会遵循一个规律,它们会先提取所有 key的 parseFloat 值为非负整数的属性, 然后根据数字顺序对属性排序首先遍历出来,然后按照对象定义的顺序遍历余下的所有属性。其它浏览器则完全按照对象定义的顺序遍历属性。
这下明白了吧,只要你为类设计的方法、属性还有Mutator的名称不为数字就可以了(当然如果你非要有这样的嗜好,我也只能@#%$......)。请看下面的代码:
var Super = new Class({ log: function(){ console.log('Super'); } }); var Mixin = new Class({ log: function(){ console.log('Mixin'); } }); var Sub = new Class({ Extends: Super, Implements: Mixin }); var obj = new Sub(); obj.log();//?
在这里obj.log()会返回什么呢?对了是'Maxin',这里Sub类首先继承了Super类,Sub的原型实际就是Super类的一个实例,Super的log方法也就是成了Sub的原型上的一个方法,然后执行Implements Mutator 为Sub类的原型扩展了一个Mixin类的实例上的方法,这时Mixin类实例上的log方法就覆盖了Sub类原型上原来的log方法(继承自Super类)。
如果把Extends、Implements的顺序颠倒一下:
var Sub = new Class({ Implements: Mixin, Extends: Super }); var obj = new Sub(); obj.log(); //?
这时obj.log()会返回什么呢?还是'Maxin'吗?其实这里返回的是'Super',Why?前面我们介绍了MooTools对Class构造函数解析时是按照我们设计的顺序解析的,所以在这里首先执行的是Implements Mutator,它首先为Sub类的原型扩展了一个Mixin类的实例上的log方法,然后才是对超类Super的继承,因为在JavaScrpt里每个对象只有一个原型,原型式继承的原理就是超类的一个实例赋予子类的原型,子类原来的原型这时会被超类的实例替换掉,所以这是Sub类原型的引用已经指向了超类的实例,而他自己的原型对象这时被消除了,所以之前从Mixin类得来的那个log方法,对不起跟着一起魂飞湮灭了,所以这里返回的是'Super'。
当然如果你嫌不过瘾,那就在为Sub类添加一个log方法:
var Sub = new Class({ Implements: Mixin, Extends: Super, log: function(){ console.log('sub'); } }); var obj = new Sub(); obj.log(); //?
你可以把Sub类的Implements、Extends、log来回颠倒一下看看效果,呵呵,再用implement方法在扩展一个试试:
var objMixin = { log: function(){ console.log('objMixin'); } }; var Sub = new Class({ Implements: Mixin, Extends: Super, log: function(){ console.log('sub'); } }); Sub.implement(objMixin); var obj = new Sub(); obj.log(); //?
呵呵,别晕掉,一切都是为了把问题搞的跟明白不是......
最后不要忘记两个重要的方法:parent()和protect(),这里就不多说了,在前面的Class源码分析里有详细介绍。