这是dojo.declare中的三个极度蛋疼的功能,在对多继承的实质有所了解之后,才会加深对这三个功能的认识,所以放到最后说。这里就不谈它们的实现原理了,第四章中也许会描述到= =!
如果觉得运行constructor前后缺少了些什么,那么preamble、postscript可以很好的帮助我们进行弥补。根据我时间不长的开发经验,还想不出什么情况下需要这种操作来弥补。如果在类型的定义中包含了preamble方法,那么在这个类型的构造函数被调用之前,会首先执行一次preamble。同样如果定义了postscript方法,那么该类型的构造函数被调用之后,也会自动执行一遍postscript。下面是一个简单的例子:
dojo.declare('A',null,{ preamble:function(){ console.log('A'); }, constructor:function(){ console.log('AA'); }, postscript:function(){ console.log('AAA'); } }); var a= new A(); /*输出: A AA AAA */
至于preamble和postscript方法究竟是如何被调用的,第四章中有解释,暂时不需要关注,可以认为这是Dojo提供好的机制。来个复杂一些的例子:
dojo.declare('A',null,{ preamble:function(){ console.log('A'); }, constructor:function(){ console.log('AA'); }, postscript:function(){ console.log('AAA'); } }); dojo.declare('B',A,{ preamble:function(){ console.log('B'); }, constructor:function(){ console.log('BB'); }, postscript:function(){ console.log('BBB'); } }); dojo.declare('C',B,{ preamble:function(){ console.log('C'); }, constructor:function(){ console.log('CC'); }, postscript:function(){ console.log('CCC'); } }); var c= new C(); /* 输出: C B A AA BB CC CCC */
从输出的结果来看,我们可以挖掘出一些有意思的事情。在这种拥有继承的情况下,父类中postscript方法是不会被自动调用到的。上述例子的准确函数执行顺序是:
至于为什么不会调用到A和B的postscript方法,从Dojo的源码实现上讲是因为这里所调用的父类型的constructor并没有去执行postscript方法。换个角度说,这里调用父类型的constructor函数完成的构造过程,与我们直接通过new来调用的父类型发生的构造,是两回事。归纳来说,对类型L(A)= AA1A2A3…AN使用new进行实例化时,默认的执行顺序是:
在Dojo1.4之后的版本中,preamble已经被标记为deprecated函数,不过postscript并没有被列入deprecated。chain提供了自动执行父类中函数的功能。默认情况下,只有父类的构造函数是会被自动调用的,并且总是先于子类的构造函数执行。只有在一些特殊情况下,我们会需要让其他的函数也能够像构造函数一样,自动执行,免去我们手工调用的麻烦。举例来说,如果创建的类型包含了destroy函数,该函数会进行一些垃圾回收方面的工作,我们肯定希望destroy函数完成后也会自动去执行一下父类中的destroy。
下面的例子定义了一条destroy函数组成的chain。其中的允许我们来设置函数的执行顺序,这里指定的是before顺序,也就是说子类的函数会先于父类的函数执行,所以子类的destroy先运行。
dojo.declare('A',null,{ constructor:function(){ console.log('A'); }, destroy:function(){console.log('AA');} }); dojo.declare('B',A,{ constructor:function(){ console.log('B'); }, destroy:function(){console.log('BB');} }); dojo.declare('C',B,{ "-chains-": { destroy: "before" }, constructor:function(){ console.log('C'); }, destroy:function(){console.log('CC');} }); var c= new C(); c.destroy(); /*输出: A B C CC BB AA */
有两点值得注意:
第一点是"-chains-"语句所处的位置,上例中放在了C类型的定义中。如果放在A或者B类中,执行c.destroy()的效果还是一样的。事实上,只要把chain声明放在继承链条中的任何一个类型定义里,都可以达到串连所有同名函数的效果。对于复杂的多重继承结构也是这样的,因为他们实质上最终还是一条单继承结构。
第二点是chain中允许我们声明三种类型的顺序,他们能够产生效果的对象不同。字面上,我们能够使用的是after\before\manual这三个顺序,他们分别代表了在父类函数执行之后执行、在父类函数执行之前执行、手动调用。对于非构造函数,设置manual是没有意义的,如果不是after顺序,会被一概视为before。而对于构造函数,设置before是没有意义的,因为父类的构造函数要么manual手动调用,要么一定会在子类的构造函数之前执行。
dojo.declare('A',null,{ "-chains-": { constructor: "before", //没有作用,非‘manual’即被视为‘after’ foo: "manual" //没有作用,非‘after’即被视为‘before’ }, constructor:function(){ console.log('A'); }, foo:function(){console.log('AA');} }); dojo.declare('B',A,{ constructor:function(){ console.log('B'); }, foo:function(){console.log('BB');} }); dojo.declare('C',B,{ constructor:function(){ console.log('C'); }, foo:function(){console.log('CC');} }); var c= new C(); c.destroy(); /*输出: A B C CC BB AA */
最后来看一个针对构造函数设置manual的例子。
dojo.declare('A',null,{ "-chains-": { constructor: "manual" }, constructor:function(){ console.log('A'); } }); dojo.declare('B',A,{ constructor:function(){ console.log('B'); } }); dojo.declare('C',B,{ constructor:function(){ this.inherited(arguments); //设置为manual后,只能手动调用父类函数 console.log('C'); } }); var c= new C(); /*输出: B C */
从这个例子可以看出,在设置了manual后,如果不手动去调用父类的构造函数,那么父类的构造函数是不会执行的,因此这里就不会打印A,根据第二章中的描述,手动调用可以使用inherited方法。
PS,之前我以为preamble、postscript以及chain会在dijit中被较多使用到,但根据在Dojo1.5源码中的搜索,很不幸,只有postscript在dijit中被使用过,至于preamble和chain基本上在整个Dojo的实现代码中都没有,只有在test的代码里出现过两三次。可见这些功能偏门 到什么程度。我觉得API提供的原则应该是简单易用 ,而Dojo的接口往往体现着庞大复杂精深,我想这可能也是很多web fronter不愿意花成本去学习去使用的Dojo的原因吧。其实作为开发者来说,Dojo用熟了也并没有感觉太复杂,你甚至会为它的细致周全感到震撼,但是对于初学者来说,或者是那些急于上手某个Ajax框架进行开发的人,Dojo的确不是一个好的选择。