日期:2014-05-16  浏览次数:20510 次

DOJO中的面向对象__第四章 Dojo/_base/declare.js源码剖析(1)

  declare.js中包含了整个dojo面向对象中最重要的代码,即对类型表达和扩展的一些封装。功能虽然强大,但是幸好文件并不复杂,拥有清晰的脉络。整个declare.js一共定义了15个函数,14个具名函数,1个匿名函数。这14个具名函数中又有一些是declare.js内部使用的函数,外部无法调用,还有一些是由dojo提供的接口,可以供dojo.declare声明的类型来调用。具体函数如下所示:

//declare.js的结构(来源于Dojo1.5版):
(function(){
	//9个内部函数,外部无法调用	
	function err(){...}
	function c3mro(){...}
	function mixOwn(){...}
	function chainedConstructor(){...}
	function singleConstructor(){...}
	function simpleConstructor(){...}
	function chain(){...}
	function forceNew(){...}
	function applyNew(){...}
	//5个对外提供的接口
	function inherited(){...}
	function getInherited(){...}
	function isInstanceOf(){...}
	function extend()	{...}
	function safeMixin(){...}
	//1个匿名函数:Dojo.declare
	d.declare = function(){...}
})();

?

  其中最为核心的即匿名函数,用户在使用dojo.declare的时候即是使用的该函数。该函数负责产生一个实实在在的可以new出实例的JS构造器。本章打算从该函数开始,逐渐分析整个declare.js文件。

(一)??? dojo.declare

1)??? crack? parameters

  先来看declare的参数列表以及函数的开头部分。其中的d在整个declare.js的开始即被赋值为dojo,至于传入的三个参数className,superclass,props曾经在第二章中有过解释。其中className代表要声明的类型的名称,superclass指定了父类型列表,最后的props对象包含了类型的所有构造函数、字段、方法。

d.declare = function(className, superclass, props){
		// crack parameters
		if(typeof className != "string"){
			props = superclass;
			superclass = className;
			className = "";
		}
		props = props || {};
		……
}
?

  由于dojo.declare支持声明一个匿名类型,因此参数列表中的第一个参数className可以省略,所以才有了crack parameters 这一步骤。其实最后一个props参数也可以省略,如果这样dojo会自动用一个空的对象代替。

//正常的类型声明
dojo.declare('A',null,null);
//匿名类,继承自类型A
var B = dojo.declare(A);

?2)??? 计算MRO、指定父类型

  在处理完传入的参数之后,紧接着的一步就是计算MRO继承序列。当然,前提是用户在使用dojo.declare声明的时候传入了superclass。之前一直superclass说是一个包含了多个类型的数组,其实superclass也可以不是数组类型,在单继承的时候,superclass就是唯一的一个父类型,另外在没有继承的时候,superclass一定要写成null。
  所以在代码的实现中,首先判断了superclass是不是一个数组。这里判断数组的时候利用了 Object.prototype.toString函数(这里的opts),去年我在求职的时候不少公司都问到了这个问题:JS中如何检测数组类型,可惜当时没有好好研究过,所以都不能算答上。
  如果这里确定了superclass是一个数组,那么则调用c3mro函数来计算MRO。注意这里返回的MRO数组中第一个保存的并非某类型本身,而是一个偏移量,表示实际上的唯一一个父类在MRO结果中距离顶端的偏移量。有了这个偏移量,自然很方便的可以从MRO结果中获取父类型。

  如果superclass不是一个数组,而是一个构造器,那么肯定这是一个单继承的情况。这里的判断也很有意思,同样是利用的Object.prototype.toString函数,如果返回的结果为object Function,那么superclass肯定是一个函数(构造器)。接下来同样是构造存放MRO的bases数组。

// 如果传入的superclass是一个数组,那么调用c3mro来计算MRO
// 计算出来的结果放在bases数组中
if(opts.call(superclass) == "[object Array]"){
	// bases中保存了根据superclass计算出来的MRO	
	bases = c3mro(superclass);
	// 其中bases[0]保存了真正的那个父类在MRO序列中距离顶端的距离
	t = bases[0];
	mixins = bases.length - t;
	superclass = bases[mixins];
} 
// 如果传入的superclass是不是数组,那么判断它是构造器还是null
// 既不是构造器也不是null则会抛出异常
else{
	bases = [0];
	if(superclass){
		// 如果传入的superclass是一个构造器
		if(opts.call(superclass) == "[object Function]"){
			// 判断superclass是raw class还是Dojo中的类
			t = superclass._meta;
			// 加入superclass的bases
			bases = bases.concat(t ? t.bases : superclass);
		}else{
			err("base class is not a callable constructor.");
		}
	}else if(superclass !== null){
		err("unknown base class. Did you use dojo.require to pull it in?")
	}
}
?

  这里还有一个值得注意的地方是,superclass既可能是一个原生态的JS构造器,也可能是利用dojo.declare声明出来的构造器。如果是Dojo中的构造器,那么可以直接获取superclass._meta.bases,这里记载了superclass的MRO序列。如果仅仅是JS原生的构造器,那么会将superclass本身加入bases。

3)??? 构造单继承链

  有了MRO序列之后就应该构造一条单继承结构,整个过程大概类似于从MRO中的父类开始,逐渐向前取出MRO中的类型,mixin进一条继承链。这个过程之前已经用过一个实例来表示出来,如第三章第四小节中的图所示。

// 构造完整的继承链
if(superclass){
	// 从bases中的父类型开始向前扫描,直到i=0时为止
	for(i = mixins - 1;; --i){
		// superclass是目前已经处理到的类型
		// proto是从superclass.prototype继承来的一个对象
		proto = forceNew(superclass);
		if(!i){
			break;
		}
		// t是bases中位于superclass前的类型
		t = bases[i];
		// 在proto中mixin进t.prototype中的属性
		(t._meta ? mixOwn : mix)(proto, t.prototype);
		// 创建新的superclass,其prototype被设为proto
		// 等于该superclass类型继承自原先的superclass
		ctor = new Function;
		ctor.superclass = superclass;
		ctor.prototype = proto;