接着第四章....
(二)?? getInherited和inherited方法
这两个方法是对外提供的扩展接口,可以用declare出来的类型的实例调用。它们的用途是返回父类中曾经出现的方法,或者对其进行调用。具体的用法在第二章中已经描述,这里不再举例说明。
1. getInherited方法主要用来返回父类中的方法;
2. inherited方法主要用来调用父类中的方法;
首先来看getInherited方法,该方法的实现比较简单:
//getInherited的实现,调用了inherited function getInherited(name, args){ //如果传入name,则返回指定name的方法 if(typeof name == "string"){ return this.inherited(name, args, true); } //否则返回的是父类中的同名方法 return this.inherited(name, true); }
上面的实现中可以看出,在getInherited中其实还是调用inherited来做进一步处理,只不过将inherited的最后一个参数设置为true,在inherited里会判断该参数,为true就是返回函数,不为true则是调用函数。
?
接下来看inherited方法,可以分成个3步骤依次来看inherited具体的实现细节。第一步 依然是crack arguments,这是由于dojo提供的API的灵活性所致,就要求这样也能调用,那样也能调用,很蛋疼。第二步 就是根据name参数来寻找父类中的同名方法,这里的name有可能是constructor,也就是说我们可以利用inherited去调用父类中的构造函数,因此在inherited的实现里做了分情况讨论,一种情况是调用父类的非构造函数,还有一种是调用构造函数。想想在第三章末尾曾讲述过,可以把某继承链的构造器调用顺序设置为manual,这样将会破坏默认的自动调用父类构造函数,用户可以根据自己手动去调用父类的构造函数,用的正是this.inherited('constructor',arguments)....在第二步完成之后,第三步 所做的工作很简单,即决定是返回找到的同名方法还是调用这个同名方法。
function inherited(args, a, f){ var name, chains, bases, caller, meta, base, proto, opf, pos, cache = this._inherited = this._inherited || {}; // 首先是crack arguments // 最后传入的参数f可能是true,也可能是一个替代args的数组,还有可能是默认的undefined if(typeof args == "string"){ name = args; args = a; a = f; } f = 0; //args是子类方法的参数列表,args.callee代表在子类的哪个方法中调用了inherited caller = args.callee; //获取欲调用的父类的方法的名称,如果没有传入name参数,那么就是调用父类中的同名方法 name = name || caller.nom; if(!name){ err("can't deduce a name to call inherited()"); } //这里获取到的是子类型的meta信息,下面接着通过meta信息来进一步获取子类型的MRO链 meta = this.constructor._meta; bases = meta.bases; //第一次调用inherited的时候,由于缺少this._inherited信息, //所以cache是一个空的Object,这里pos是undefined //但是如果第二回及以后用到了inherited //那么在cache中记录了之前一次利用inherited寻找的方法和位置 //注意实际上整个实现中并未利用cache,这里的cache疑似某个实现版本遗留下的痕迹 pos = cache.p; //分情况讨论 //1.要调用的方法不是父类中的构造函数 if(name != cname){ // method if(cache.c !== caller){ // cache bust pos = 0; base = bases[0]; meta = base._meta; if(meta.hidden[name] !== caller){ // error detection chains = meta.chains; if(chains && typeof chains[name] == "string"){ err("calling chained method with inherited: " + name); } // find caller do{ meta = base._meta; proto = base.prototype; if(meta && (proto[name] === caller && proto.hasOwnProperty(name) || meta.hidden[name] === caller)){ break; } }while(base = bases[++pos]); // intentional assignment pos = base ? pos : -1; } } // 在正常情况下,在bases[0]中根据name寻找到的方法就是caller // 因此需要沿着bases继续寻找,有可能会进入while循环,找到的函数放在f中 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 } } //这个写法太高级了....就是在bases中没有去就Object.prototype里找 //不过很可能Object.prototype中依然没有名为name的方法,这时候f就是undefined f = base && f || op[name]; } //2.要调用的方法是父类中的构造函数 else{ if(cache.c !== caller){ //如果name是constructor,依然是沿着bases依次寻找 pos = 0; meta = bases[0]._meta; if(meta && meta.ctor !== caller){ // error detection chains = meta.chains; if(!chains || chains.constructor !== "manual"){ err("calling chained constructor with inherited"); } // find caller while(base = bases[++pos]){ // intentional assignment meta = base._meta; if(meta && meta.ctor === caller){ break; } } pos = base ? pos : -1; } } // 这里找到的父类型的构造函数是base._meta.ctor // 即当初declare该类型时props中自定义的constructor函数 while(base = bases[++pos]){ // intentional assignment meta = base._meta; f = meta ? meta.ctor : base; if(f){ break; } } f = base && f; } // 把f和pos放进cache中,即this._inherited // 这样下次再次利用inherited寻找name方法的时候就很方便了 cache.c = f; cache.p = pos; // 决定是返回f还是调用f if(f){ return a === true ? f : f.apply(this, a || args); } }
上面不仅贴出了整个inherited的实现,也标注了一些关键步骤的解释。其中的cache疑似是一个过期的实现,因为实在想不出会有什么情况下cache.c === caller,除非人为的故意设置成如此,不过幸好即使忽略掉cache的作用也不会影响整段代码的理解。还有一个颇为有趣的地方在于如果发现了一个方法处于chains之中,那么会抛出异常,因为对一个已经chains的方法再去手动调用是毫无意义的。
(三)? isInstanceOf方法
在第二章中已经提及,为了弥补JS自带的instanceof运算符无法判断Dojo中的继承,所以才有了isInstanceOf扩展。该方法由Dojo中类型的实例来调用。
function isInstanceOf(cls){ //获取该类型meta信息中的bases(即MRO的结果) var bases = this.constructor._meta.bases; //遍历bases for(var i = 0, l = bases.length; i < l; ++i){ if(bases[i] === cls){ return true; } } return this instanceof cls; }
整个实现也很清晰很简单,在第三章曾经描述过,不论Dojo中一个继承结构多么的复杂,归根结底还是一个单继承的形式,外加mixin进了许多类型的属性而已。那么在判断instanceof的时候,只要顺着这样一条继承链从低向高处遍历,沿途无论是发现了mixin的class,或者直接就是父类,这里都会返回true。
?
如果对JS中原生instanceof的判断机制感兴趣,可以参考
1. http://www.iteye.com/topic/461096
2. http://ejohn.org/blog/objectgetprototypeof
?
(四)? chainedConstructor方法
第四章中提过,在declare中构建ctor的时候,针对不同的情况分别调用了三个函数。这里只研究其中的chainedConstructor,因为该函数最为复杂,另外两个simpleConstructor和singleConstructor函数不作详细分析。chainedConstructor是当declare的类型存在继承,且未设置constructor="manual"时调用的函数,返回值是一个用以充当declare类型的函数。
?
整个chainedConstructor可以看作三大步。第一步 就是就是执行在继承结构链上所有类型中定义的preamble函数,第二步 是调用所有类型的constructor函数,第三步 是执行当前类型中定义的postscript方法。如果撇开preamble和postscript,那么整个chainedConstructor的实现就只需要调用所有类型的constructor函数,相应的实现也可以被压缩成:
// 遍历bases中的类型,未指明constructor的调用顺序,因此默认是after,即 // 从bases[i]---->bases[0]依次调用constructor for(i = bases.length - 1; i >= 0; --i){ f = bases[i]; m = f._meta; f = m ? m.ctor : f;//可能是raw class if(f){ f.apply(this, arguments); } }
?
可惜必须要面对存在preamble和postscript的情况,而且这两种情况的处理方式还不一样。其实preamble的调用也很简单,就是遍历bases,找出其中定义过的preamble函数,然后依次执行。
function chainedConstructor(bases, ctorSpecial){ return function(){ var a = arguments, args = a, a0 = a[0], f, i, m,l = bases.length, preArgs; //如果不是利用new的时候调用该函数,那要强迫new出实例 if(!(this instanceof a.callee)){ // not called via new, so force it return applyNew(a); } // ctorSpecial=true仅发生在:没有定义chains,或者chains中的constructor // 一旦有了chains并且设置了constructor的顺序,则无需执行preamble // 有两种设置preamble的方式: // 一是在new的时候将preamble作为参数传递给该类型 // 二是在declare一个类型的时候定义好 if(ctorSpecial && (a0 && a0.preamble || this.preamble)){ preArgs = new Array(bases.length); preArgs[0] = a; for(i = 0;;){ // 如果是在参数中定义了preamble并传第给ctor a0 = a[0]; if(a0){ f = a0.preamble; if(f){ a = f.apply(this, a) || a; } } // 如果是在类型的declare中定义preamble f = bases[i].prototype; f = f.hasOwnProperty("preamble") && f.preamble; if(f){ a = f.apply(this, a) || a; } // for循环再遍历完了bases之后会结束 if(++i == l){ break; } //会记录下每次调用preamble之后的arguments //主要是为了防止某个preamble对arguments作出修改 preArgs[i] = a; } } //第二步开始 …… }
上面对第一步preamble的执行作出了一些解释。现在还剩最后一步,只针对当前的类型来调用postscript:
f = this.postscript; if(f){ f.apply(this, args); }
这个实在没什么好讲的。
?
从整个chainedConstructor的三大步骤实现来看,其实dojo的源码写的还是很通俗易懂的,结构也很清楚,是不错的学习材料^_^至此declare.js中比较重要的函数基本都已经讲完了,只缺少一个关于c3mro函数的剖析,但是前面讲mro已经花了大量的篇幅,便不打算再写下去了。以前都是仅仅停留在参阅Dojo的API的说明上,这是我第一次花力气去阅读Dojo的源码,可惜目前的工作中已经没有机会再使用Dojo了。
?