dojo类机制实现原理分析.docx

上传人:b****6 文档编号:7415254 上传时间:2023-01-23 格式:DOCX 页数:14 大小:23.79KB
下载 相关 举报
dojo类机制实现原理分析.docx_第1页
第1页 / 共14页
dojo类机制实现原理分析.docx_第2页
第2页 / 共14页
dojo类机制实现原理分析.docx_第3页
第3页 / 共14页
dojo类机制实现原理分析.docx_第4页
第4页 / 共14页
dojo类机制实现原理分析.docx_第5页
第5页 / 共14页
点击查看更多>>
下载资源
资源描述

dojo类机制实现原理分析.docx

《dojo类机制实现原理分析.docx》由会员分享,可在线阅读,更多相关《dojo类机制实现原理分析.docx(14页珍藏版)》请在冰豆网上搜索。

dojo类机制实现原理分析.docx

dojo类机制实现原理分析

前段时间曾经在InfoQ中文站上发表文章,介绍了dojo类机制的基本用法。

有些朋友在读后希望能够更深入了解这部分的内容,本文将会介绍dojo类机制幕后的知识,其中会涉及到dojo类机制的实现原理并对一些关键方法进行源码分析,当然在此之前希望您能够对JavaScript和dojo的使用有些基本的了解。

dojo的类机制支持类声明、继承、调用父类方法等功能。

dojo在底层实现上是通过操作原型链来实现其类机制的,而在实现继承时采用类式继承的方式。

值得一提的是,dojo的类机制允许进行多继承(注意,只有父类列表中的第一个作为真正的父类,其它的都是将其属性以mixin的方法加入到子类的原型链中),为解决多重继承时类方法的顺序问题,dojo用JavaScript实现了Python和其它多继承语言所支持的C3父类线性化算法,以实现线性的继承关系,想了解更多该算法的知识,可参考这里,我们在后面的分析中将会简单讲解dojo对此算法的实现。

1.dojo类声明概览

dojo类声明相关的代码位于“/dojo/_base/declare.js”文件中,定义类是通过dojo.declare方法来实现的。

关于这个方法的基本用法,已经在dojo类机制简介这篇文章中进行了阐述,现在我们看一下它的实现原理(在这部分的代码分析中,会在整体上介绍dojo如何声明类,后文会对里面的重要细节内容进行介绍):

[javascript]viewplaincopyprint?

1.//此即为dojo.declare方法的定义

2.d.declare=function(className,superclass,props){

3.

4.//前面有格式化参数相关的操作,一般情况下定义类会把三个参数全传进来,分别为

5.//类名、父类(可以为null、某个类或多个类组成的数组)和要声明类的属性及方法

6.

7.//定义一系列的变量供后面使用

8.varproto,i,t,ctor,name,bases,chains,mixins=1,parents=superclass;

9.

10.//处理要声明类的父类

11.if(opts.call(superclass)=="[objectArray]"){

12.//如果父类参数传过来的是数组,那么这里就是多继承,要用C3算法处理父类的关系

13.//得到的bases为数组,第一个元素能标识真正父类(即superclass参数中的第一个)//在数组中的索引,其余的数组元素是按顺序排好的继承链,后面还会介绍到C3算法

14.bases=c3mro(superclass,className);

15.t=bases[0];

16.mixins=bases.length-t;

17.superclass=bases[mixins];

18.}else{

19.//此分支内是对没有父类或单个父类情况的处理,不再详述

20.}

21.//以下为构建类的原型属性和方法

22.if(superclass){

23.for(i=mixins-1;;--i){

24.//此处遍历所有需要mixin的类

25.//注意此处,为什么说多个父类的情况下,只有第一个父类是真正的父类呢,因//为在第一次循环的实例化了该父类,并记在了原型链中,而其它需要mixin的//父类在后面处理时会把superclass设为一个空的构造方法,合并父类原型链//后进行实例化

26.proto=forceNew(superclass);

27.if(!

i){

28.//此处在完成最后一个父类后跳出循环

29.break;

30.}

31.//mixinproperties

32.t=bases[i];//得到要mixin的一个父类

33.(t._meta?

mixOwn:

mix)(proto,t.prototype);//合并原型链

34.//chaininnewconstructor

35.ctor=newFunction;//声明一个新的Function

36.ctor.superclass=superclass;

37.ctor.prototype=proto;//设置原型链

38.//此时将superclass指向了这个新的Function,再次进入这个循环的时候,实例//化的是ctor,而不是mixin的父类

39.superclass=proto.constructor=ctor;

40.}

41.}else{

42.proto={};

43.}

44.//此处将上面得到的方法(及属性)与要声明类本身所拥有的方法(及属性)进行合并

45.safeMixin(proto,props);

46.

47.…………

48.//此处收集链式调用相关的信息,后面会详述

49.for(i=mixins-1;i;--i){//intentionalassignment

50.t=bases[i]._meta;

51.if(t&&t.chains){

52.chains=mix(chains||{},t.chains);

53.}

54.}

55.if(proto["-chains-"]){

56.chains=mix(chains||{},proto["-chains-"]);

57.}

58.

59.//此处根据上面收集的链式调用信息和父类信息构建最终的构造方法,后文详述

60.t=!

chains||!

chains.hasOwnProperty(cname);

61.bases[0]=ctor=(chains&&chains.constructor==="manual")?

simpleConstructor(bases):

62.(bases.length==1?

singleConstructor(props.constructor,t):

chainedConstructor(bases,t));

63.

64.//在这个构造方法中添加了许多的属性,在进行链式调用以及调用父类方法等处会用到

65.ctor._meta={bases:

bases,hidden:

props,chains:

chains,

66.parents:

parents,ctor:

props.constructor};

67.ctor.superclass=superclass&&superclass.prototype;

68.ctor.extend=extend;

69.ctor.prototype=proto;

70.proto.constructor=ctor;

71.

72.//对于dojo.declare方法声明类的实例均有以下的工具方法

73.proto.getInherited=getInherited;

74.proto.inherited=inherited;

75.proto.isInstanceOf=isInstanceOf;

76.

77.//此处要进行全局注册

78.if(className){

79.proto.declaredClass=className;

80.d.setObject(className,ctor);

81.}

82.

83.//对于链式调用父类的那些方法进行处理,实际上进行了重写,后文详述

84.if(chains){

85.for(nameinchains){

86.if(proto[name]&&typeofchains[name]=="string"&&name!

=cname){

87.t=proto[name]=chain(name,bases,chains[name]==="after");

88.t.nom=name;

89.}

90.}

91.}

92.returnctor;//Function

93.};

 

以上简单介绍了dojo声明类的整体流程,但是一些关键的细节如C3算法、链式调用在后面会继续进行介绍。

2.C3算法的实现

通过以前的文章和上面的分析,我们知道dojo的类声明支持多继承。

在处理多继承时,不得不面对的就是继承链如何构造,比较现实的问题是如果多个父类都拥有同名的方法,那么在调用父类方法时,要按照什么规则确定调用哪个父类的呢?

在解决这个问题上dojo实现了C3父类线性化的方法,对多个父类进行合理的排序,从而完美解决了这个问题。

为了了解继承链的相关知识,我们看一个简单的例子:

[javascript]viewplaincopyprint?

1.dojo.declare("A",null);

2.dojo.declare("B",null);

3.dojo.declare("C",null);

4.dojo.declare("D",[A,B]);

5.dojo.declare("E",[B,C]);

6.dojo.declare("F",[A,C]);

7.dojo.declare("G",[D,E]);

以上的代码中,声明了几个类,通过C3算法得到G的继承顺序应该是这样G->E->C->D->B->A的,只有按照这样的顺序才能保证类定义和依赖是正确的。

那我们看一下这个C3算法是如何实现的呢:

[javascript]viewplaincopyprint?

1.functionc3mro(bases,className){

2.//定义一系列的变量

3.varresult=[],roots=[{cls:

0,refs:

[]}],nameMap={},clsCount=1,

4.l=bases.length,i=0,j,lin,base,top,proto,rec,name,refs;

5.

6.//在这个循环中,构建出了父类各自的依赖关系(即父类可能会依赖其它的类)

7.for(;i

8.base=bases[i];//得到父类

9.…………

10.//在dojo声明的类中都有一个_meta属性,记录父类信息,此处能够得到包含本身在//内的继承链

11.lin=base._meta?

base._meta.bases:

[base];

12.top=0;

13.for(j=lin.length-1;j>=0;--j){

14.//遍历继承链中的元素,注意,这里的处理是反向的,即从最底层的开始,一直到链的顶端

15.proto=lin[j].prototype;

16.if(!

proto.hasOwnProperty("declaredClass")){

17.proto.declaredClass="uniqName_"+(counter++);

18.}

19.name=proto.declaredClass;

20.//nameMap以map的方式记录了用到的类,不会重复

21.if(!

nameMap.hasOwnProperty(name)){

22.//每个类都会有这样一个结构,其中refs特别重要,记录了引用了依赖类

23.nameMap[name]={count:

0,refs:

[],cls:

lin[j]};

24.++clsCount;

25.}

26.rec=nameMap[name];

27.if(top&&top!

==rec){

28.//满足条件时,意味着当前的类依赖此时top引用的类,即链的前一元素

29.rec.refs.push(top);

30.++top.count;

31.}

32.top=rec;//top指向当前的类,开始下一循环

33.}

34.++top.count;

35.roots[0].refs.push(top);//在一个父类处理完成后就将它放在根的引用中

36.}

37.//到此为止,我们建立了父类元素的依赖关系,以下要正确处理这些关系

38.while(roots.length){

39.top=roots.pop();

40.//将依赖的类放入结果集中

41.result.push(top.cls);

42.--clsCount;

43.//optimization:

followasingle-linkedchain

44.while(refs=top.refs,refs.length==1){

45.//若当前类依赖的是一个父类,那处理这个依赖链

46.top=refs[0];

47.if(!

top||--top.count){

48.//特别注意此时有一个top.count变量,是用来记录这个类被引用的次数,//如果减一之后,值还大于零,说明后面还有引用,此时不做处理,这也就是//在前面的例子中为什么不会出现G->E->C->B的原因

49.top=0;

50.break;

51.}

52.result.push(top.cls);

53.--clsCount;

54.}

55.if(top){

56.//若依赖多个分支,则将依赖的类分别放到roots中,这段代码只有在多继承,//第一次进入时才会执行

57.for(i=0,l=refs.length;i

58.top=refs[i];

59.if(!

--top.count){

60.roots.push(top);

61.}

62.}

63.}

64.}

65.if(clsCount){//如果上面处理完成后,clsCount的值还大于1,那说明出错了

66.err("can'tbuildconsistentlinearization",className);

67.}

68.

69.//构建完继承链后,要标识出真正父类在链的什么位置,就是通过返回数组的第一个元素

70.base=bases[0];

71.result[0]=base?

72.base._meta&&base===result[result.length-base._meta.bases.length]?

73.base._meta.bases.length:

1:

0;

74.

75.returnresult;

76.}

通过以上的分析,我们可以看到,这个算法实现起来相当复杂,如果朋友们对其感兴趣,建议按照上文的例子,自己加断点进行调试分析。

dojo的作者使用了不到100行的代码实现了这样强大的功能,里面有很多值得借鉴的设计思想。

3.链式构造器的实现

在第一部分代码分析中我们曾经看到过定义构造函数的代码,如下:

[javascript]viewplaincopyprint?

1.bases[0]=ctor=(chains&&chains.constructor==="manual")?

simpleConstructor(bases):

2.(bases.length==1?

singleConstructor(props.constructor,t):

chainedConstructor(bases,t));

这个方法对于理解dojo类机制很重要。

从前一篇文章的介绍中,我们了解到默认情况下,如果dojo声明的类存在继承关系,那么就会自动调用父类的构造方法,且是按照继承链的顺序先调用父类的构造方法,但是从1.4版本开始,dojo提供了手动设置构造方法调用的选项。

在以上的代码中涉及到dojo声明类的三个方法,如果该类没有父类,那么调用的就是singleConstructor,如果有父类的话,那么默认调用的是chainedConstructor,如果手动设置了构造方法,那么调用的就是simpleConstructor,要启动这个选项只需在声明该类的时候添加chains的constructor声明即可。

比方说,我们在定义继承自com.levinzhang.Person的com.levinzhang.Employee类时,可以这样做:

[javascript]viewplaincopyprint?

1.dojo.declare("com.levinzhang.Employee",com.levinzhang.Person,{

2."-chains-":

{

3.constructor:

"manual"

4.},

5.…………

6.}

添加以上代码后,在构造com.levinzhang.Employee实例时,就不会再调用所有父类的构造方法了,但是此时我们可以使用inherited方法显式的调用父类方法。

限于篇幅,以上的三个方法不全部介绍,只介绍chainedConstructor的核心实现:

[javascript]viewplaincopyprint?

1.functionchainedConstructor(bases,ctorSpecial){

2.returnfunction(){

3.//在此之前有一些准备工作,不详述了

4.//找到所有的父类,分别调用其构造方法

5.for(i=l-1;i>=0;--i){

6.f=bases[i];

7.m=f._meta;

8.f=m?

m.ctor:

f;//得到父类的构造方法

9.if(f){

10.//通过apply调用父类的方法

11.f.apply(this,preArgs?

preArgs[i]:

a);

12.}

13.}

14.//请注意在构造方法执行完毕后,会执行名为postscript的方法,而这个方法是//dojo的dijit组件实现的关键生命周期方法

15.f=this.postscript;

16.if(f){

17.f.apply(this,args);

18.}

19.};

20.}

4.调用父类方法的实现

在声明dojo类的时候,如果想调用父类的方法一般都是通过使用inherited方法来实现,但从1.4版本开始,dojo支持链式调用所有父类的方法,并引入了一些AOP的概念。

我们将会分别介绍这两种方式。

1)通过inherited方式调用父类方法

在上一篇文章中,我们曾经介绍过,通过在类中使用inherited就可以调用到。

这里我们要深入inherited的内部,看一下其实现原理。

因为inherited支持调用父类的一般方法和构造方法,两者略有不同,我们关注调用一般方法的过程。

[javascript]viewplaincopyprint?

1.functioninherited(args,a,f){

2.…………

3.//在此之前有一些参数的处理

4.if(name!

=cname){

5.//不是构造方法

6.if(cache.c!

==caller){

7.//在此之间的一些代码解决了确定调用者的问题,即确定从什么位置开始找父类

8.}

9.//按照顺序找父类的同名方法

10.base=bases[++pos];

11.if(base){

12.proto=base.prototype;

13.if(base._meta&&proto.hasOwnProperty(name)){

14.f=proto[name];//找到此方法了

15.}else{

16.//如果没有找到对应的方法将按照继承链依次往前找

17.opf=op[name];

18.do{

19.proto=base.prototype;

20.f=proto[name];

21.if(f&&(base._meta?

proto.hasOwnProperty(name):

f!

==opf)){

22.break;

23.}

24.}while(base=bases[++pos]);//intentionalassignment

25.}

26.}

27.f=base&&f||op[name];

28.}else{

29.//此处是处理调用父类的构造方法

30.}

31.if(f){

32.//方法找到后,执行

33.returna===true?

f:

f.apply(this,a||args);

34.}

35.}

 

2)链式调用父类方法

这是从dojo1.4版本新加入的功能。

如果在执行某个方法时,也想按照一定的顺序执行父类的方法,只需在定义类时,在-chains-属性中加以声明即可。

[javascript]viewplaincopyprint?

1.dojo.declare("com.levinzhang.Employee",com.levinzhang.Person,{

2."-chains-":

{

3.sayMyself:

"before"

4.},

5.……

6.}

添加了以上声明后,意味着Employee及其所有的子类,在调用sayMyself方法时,都会先调用本身的同名方法,然后再按照继承链依次调用所有父类的同名方法,我们还可以将值“before”替换为“after”,其执行顺序将会相反。

在-chains-属性中声明的方法,在类定义时,会进行特殊处理,正如我们在第一章中看到的那样:

[javascript]viewplaincopyprint?

1.if(chains){

2.for(nameinchains){

3.if(proto[name]&&typeofchains[name]=="string"&&name!

=cname){

4.t=prot

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1