dojo类机制实现原理分析
声明:本文为笔者原创,但首发于InfoQ中文站,详见文末声明
?
?
//此即为dojo.declare方法的定义d.declare = function(className, superclass, props){ //前面有格式化参数相关的操作,一般情况下定义类会把三个参数全传进来,分别为//类名、父类(可以为null、某个类或多个类组成的数组)和要声明类的属性及方法//定义一系列的变量供后面使用var proto, i, t, ctor, name, bases, chains, mixins = 1, parents = superclass;// 处理要声明类的父类if(opts.call(superclass) == "[object Array]"){//如果父类参数传过来的是数组,那么这里就是多继承,要用C3算法处理父类的关系 //得到的bases为数组,第一个元素能标识真正父类(即superclass参数中的第一个)//在数组中的索引,其余的数组元素是按顺序排好的继承链,后面还会介绍到C3算法bases = c3mro(superclass, className);t = bases[0];mixins = bases.length - t;superclass = bases[mixins];}else{//此分支内是对没有父类或单个父类情况的处理,不再详述} //以下为构建类的原型属性和方法if(superclass){for(i = mixins - 1;; --i){ //此处遍历所有需要mixin的类//注意此处,为什么说多个父类的情况下,只有第一个父类是真正的父类呢,因//为在第一次循环的实例化了该父类,并记在了原型链中,而其它需要mixin的//父类在后面处理时会把superclass设为一个空的构造方法,合并父类原型链//后进行实例化proto = forceNew(superclass);if(!i){//此处在完成最后一个父类后跳出循环break;}// mix in propertiest = bases[i];//得到要mixin的一个父类(t._meta ? mixOwn : mix)(proto, t.prototype);//合并原型链// chain in new constructorctor = new Function;//声明一个新的Functionctor.superclass = superclass;ctor.prototype = proto;//设置原型链//此时将superclass指向了这个新的Function,再次进入这个循环的时候,实例//化的是ctor,而不是mixin的父类superclass = proto.constructor = ctor; }}else{proto = {};}//此处将上面得到的方法(及属性)与要声明类本身所拥有的方法(及属性)进行合并safeMixin(proto, props);…………//此处收集链式调用相关的信息,后面会详述for(i = mixins - 1; i; --i){ // intentional assignmentt = bases[i]._meta;if(t && t.chains){chains = mix(chains || {}, t.chains);}}if(proto["-chains-"]){chains = mix(chains || {}, proto["-chains-"]);}//此处根据上面收集的链式调用信息和父类信息构建最终的构造方法,后文详述t = !chains || !chains.hasOwnProperty(cname);bases[0] = ctor = (chains && chains.constructor === "manual") ? simpleConstructor(bases) :(bases.length == 1 ? singleConstructor(props.constructor, t) : chainedConstructor(bases, t));//在这个构造方法中添加了许多的属性,在进行链式调用以及调用父类方法等处会用到ctor._meta = {bases: bases, hidden: props, chains: chains,parents: parents, ctor: props.constructor};ctor.superclass = superclass && superclass.prototype;ctor.extend = extend;ctor.prototype = proto;proto.constructor = ctor;// 对于dojo.declare方法声明类的实例均有以下的工具方法proto.getInherited = getInherited;proto.inherited = inherited;proto.isInstanceOf = isInstanceOf;// 此处要进行全局注册if(className){proto.declaredClass = className;d.setObject(className, ctor);}//对于链式调用父类的那些方法进行处理,实际上进行了重写,后文详述if(chains){for(name in chains){if(proto[name] && typeof chains[name] == "string" && name != cname){t = proto[name] = chain(name, bases, chains[name] === "after");t.nom = name;}}}return ctor;// Function};
?以上简单介绍了dojo声明类的整体流程,但是一些关键的细节如C3算法、链式调用在后面会继续进行介绍。
?
dojo.declare("A",null);dojo.declare("B",null);dojo.declare("C",null);dojo.declare("D",[A, B]);dojo.declare("E",[B, C]); dojo.declare("F",[A, C]); dojo.declare("G",[D, E]);?
?
function c3mro(bases, className){ //定义一系列的变量var result = [], roots = [{cls: 0, refs: []}], nameMap = {}, clsCount = 1,l = bases.length, i = 0, j, lin, base, top, proto, rec, name, refs;//在这个循环中,构建出了父类各自的依赖关系(即父类可能会依赖其它的类)for(; i < l; ++i){base = bases[i];//得到父类 ………… //在dojo声明的类中都有一个_meta属性,记录父类信息,此处能够得到包含本身在//内的继承链lin = base._meta ? base._meta.bases : [base];top = 0;for(j = lin.length - 1; j >= 0; --j){ //遍历继承链中的元素,注意,这里的处理是反向的,即从最底层的开始,一直到链的顶端proto = lin[j].prototype;if(!proto.hasOwnProperty("declaredClass")){proto.declaredClass = "uniqName_" + (counter++);}name = proto.declaredClass; // nameMap以map的方式记录了用到的类,不会重复if(!nameMap.hasOwnProperty(name)){ //每个类都会有这样一个结构,其中refs特别重要,记录了引用了依赖类nameMap[name] = {count: 0, refs: [], cls: lin[j]};++clsCount;}rec = nameMap[name];if(top && top !== rec){ //满足条件时,意味着当前的类依赖此时top引用的类,即链的前一元素rec.refs.push(top);++top.count;}top = rec;//top指向当前的类,开始下一循环}++top.count;roots[0].refs.push(top);//在一个父类处理完成后就将它放在根的引用中}//到此为止,我们建立了父类元素的依赖关系,以下要正确处理这些关系while(roots.length){top = roots.pop();//将依赖的类放入结果集中result.push(top.cls);--clsCount;// optimization: follow a single-linked chainwhile(refs = top.refs, refs.length == 1){ //若当前类依赖的是一个父类,那处理这个依赖链top = refs[0];if(!top || --top.count){//特别注意此时有一个top.count变量,是用来记录这个类被引用的次数,//如果减一之后,值还大于零,说明后面还有引用,此时不做处理,这也就是//在前面的例子中为什么不会出现G->E->C->B的原因top = 0;break;}result.push(top.cls);--clsCount;}if(top){//若依赖多个分支,则将依赖的类分别放到roots中,这段代码只有在多继承,//第一次进入时才会执行for(i = 0, l = refs.length; i < l; ++i){top = refs[i];if(!--top.count){roots.push(top);}}}}if(clsCount){//如果上面处理完成后,clsCount的值还大于1,那说明出错了err("can't build consistent linearization", className);}//构建完继承链后,要标识出真正父类在链的什么位置,就是通过返回数组的第一个元素base = bases[0];result[0] = base ?base._meta && base === result[result.length - base._meta.bases.length] ?base._meta.bases.length : 1 : 0;return result;}?
bases[0] = ctor = (chains && chains.constructor === "manual") ? simpleConstructor(bases) :(bases.length == 1 ? singleConstructor(props.constructor, t) : chainedConstructor(bases, t));
?这个方法对于理解dojo类机制很重要。从前一篇文章的介绍中,我们了解到默认情况下,如果dojo声明的类存在继承关系,那么就会自动调用父类的构造方法,且是按照继承链的顺序先调用父类的构造方法,但是从1.4版本开始,dojo提供了手动设置构造方法调用的选项。在以上的代码中涉及到dojo声明类的三个方法,如果该类没有父类,那么调用的就是singleConstructor,如果有父类的话,那么默认调用的是chainedConstructor,如果手动设置了构造方法,那么调用的就是simpleConstructor ,要启动这个选项只需在声明该类的时候添加chains的constructor声明即可。
dojo.declare("com.levinzhang.Employee", com.levinzhang.Person,{"-chains-": {constructor:"manual"},…………}?
function chainedConstructor(bases, ctorSpecial){return function(){//在此之前有一些准备工作,不详述了 //找到所有的父类,分别调用其构造方法for(i = l - 1; i >= 0; --i){f = bases[i];m = f._meta;f = m ? m.ctor : f;//得到父类的构造方法if(f){ //通过apply调用父类的方法f.apply(this, preArgs ? preArgs[i] : a);}}// 请注意在构造方法执行完毕后,会执行名为postscript的方法,而这个方法是//dojo的dijit组件实现的关键生命周期方法f = this.postscript;if(f){f.apply(this, args);}};}?
function inherited(args, a, f){………… //在此之前有一些参数的处理if(name != cname){// 不是构造方法if(cache.c !== caller){//在此之间的一些代码解决了确定调用者的问题,即确定从什么位置开始找父类}//按照顺序找父类的同名方法base = bases[++pos];if(base){proto = base.prototype;if(base._meta && proto.hasOwnProperty(name)){f = proto[name];//找到此方法了}else{ //如果没有找到对应的方法将按照继承链依次往前找opf = op[name];do{proto = base.prototype;f = proto[name];if(f && (base._meta ? proto.hasOwnProperty(name) : f !== opf)){break;}}while(base = bases[++pos]); // intentional assignment}}f = base && f || op[name];}else{//此处是处理调用父类的构造方法}if(f){ //方法找到后,执行return a === true ? f : f.apply(this, a || args);}}?
dojo.declare("com.levinzhang.Employee", com.levinzhang.Person,{"-chains-": { sayMyself: "before"},……}
if(chains){for(name in chains){if(proto[name] && typeof chains[name] == "string" && name != cname){t = proto[name] = chain(name, bases, chains[name] === "after");t.nom = name;}}}?
function chain(name, bases, reversed){return function(){var b, m, f, i = 0, step = 1;if(reversed){ //判定顺序,即“after”还是“before”,分别对应于循环的不同起点和方向i = bases.length - 1;step = -1;}for(; b = bases[i]; i += step){ //按照顺序依次查找父类m = b._meta; //找到父类中同名的方法f = (m ? m.hidden : b.prototype)[name];if(f){ //依次执行f.apply(this, arguments);}}};}?
function isInstanceOf(cls){ //得到实例对象继承链上的所有类var bases = this.constructor._meta.bases; //遍历所有的类,看是否与传进来的类相等for(var i = 0, l = bases.length; i < l; ++i){if(bases[i] === cls){return true;}}return this instanceof cls;}
if(className){proto.declaredClass = className;d.setObject(className, ctor);}
?在dojo实现类机制的过程中,有一些内部的方法,是很值得借鉴的如forceNew、safeMixin等,这些方法在实现功能的同时,保证了代码的高效执行,感兴趣的朋友可以进一步的研究。
?
???????? 探究类库的实现原理是提高自己编码水平的好办法,类似于dojo这样类库的核心代码基本上每一行都有其设计思想在里面(当然也不可以盲目崇拜),每次阅读和探索都会有所发现和心得,当然里面肯定也会有自以为是或谬误之处,在此很乐意和读到这篇文章的朋友们一起研究,欢迎批评指正。
?
张卫滨,关注企业级Java开发和RIA技术,个人博客:http://lengyun3566.iteye.com,微博:http://weibo.com/zhangweibin1981
?
参考资料:
http://docs.dojocampus.org/
http://blog.csdn.net/dojotoolkit/
http://dojotoolkit.org/
?
?
?
?
?
?
声明:?
本文已经首发于InfoQ中文站,版权所有,原文为《dojo类机制实现原理分析》,如需转载,请务必附带本声明,谢谢。?
InfoQ中文站是一个面向中高端技术人员的在线独立社区,为Java、.NET、Ruby、SOA、敏捷、架构等领域提供及时而有深度的资讯、高端技术大会如QCon 、线下技术交流活动QClub、免费迷你书下载如《架构师》等。