关于Cold的结构和模块载入机制(下)

来自本人博客:http://ilovespringna.com/typecho/index.php/archives/34350/

Cold的基本思路类似于kissy的seed,所有的代码绑定在一个基础性的全局对象Cold上,通过动态绑定的方式给这个Cold对象附加新的子对象,像一个种子一样不断成长。

stuct.png

如图,Cold的命名空间结构和目录结构是基本一致的,划分为core、component、util、task等层次。core是Cold的核心部分,提供了js的基本功能,包括有dom、event、ajax、anim等,每个js文件都定义了一类问题的解决方案。component为组件群,用于解决页面中遇到的实际问题,比如拖放组件,焦点图,图片的延迟载入等等,每个component都不同程度地依赖于core中的某些部分。task是以页面为粒度的JS物理文件集合,是建立在core和component上的页面应用的代码。util则是一些独立的工具方法,一些不涉及页面dom操作的工具类都放在这里,它们一般不依赖于上述任何层次。

Cold.png

对象Cold定义在cold.js中,cold.js提供了框架的基本信息(包括version、debug、cache等),具有loader的功能,并提供了ready方法。基本结构如下:

window['Cold'] = {
	version : '0.0.1',
	debug : true,
	scripts: {
		'loadingNum' : 0,
		'nodes' : {}
	},

	//...

	add : function(namespace, req, doFunc){
		//...
	},
	load : function(namespace, callback){
		//...
	},
	ready : function(){
		//...
	}
};
Cold中使用add方法定义模块,用load方法载入模块,一个loadingNum和nodes对象来管理模块的载入状态,最后用ready来检测模块是否已经成功附加到Cold对象上。

Cold.add(namespace, req, doFunc)

add函数是Cold架构中最重要的函数,被用在每个模块的定义中,它做了下面几件事: 1.接收开发者定义的模块的namespace,利用这个namespace将doFunc的返回值附加到Cold的相应位置。 2.检测模块的依赖模块,依次载入未载入的模块,并定义计数器给依赖模块,当最后一个依赖模块载入时,执行当前模块的定义代码(doFunc)。 3.执行每个模块载入后的回调函数,这个非常重要,待会具体说明。

Cold.load(namespace, callback)

这个函数的思路来自于C/C++中的include,java/php中的import。加入你的页面需要动画效果和ajax组件,那么很简单:
Cold.load('anim');
Cold.load('ajax');
load方法会根据提供的namespace去寻找模块的物理位置。在这里,模块的命名空间,模块名,物理位置实际上是统一的,互相对应的。

Cold.loadingNum和Cold.nodes

这是两个状态寄存器。loadingNum表示当前正在载入的模块数,每一个Cold.load都会加一,载入成功后减一。当这个数为0时则表示所有这个页面需要用到的模块已经完全载入。 Cold.nodes对象以每个模块所代表的script节点为名,以当前的状态为值。而每个模块分为四个状态:

nodestate.png

1.未load前为undefined。 2.执行load函数后,script节点建立,开始读取对应的js文件,此时状态为loading。 3.读取对应js文件完毕后,开始执行文件中add函数定义模块,此时状态为loaded。但是由于依赖模块的存在,这时add的定义函数doFunc可能尚未执行。 4.doFunc在所有依赖模块成功附加后执行,模块定义完成,状态为attached。

Cold.ready(func)

此函数在domReady时开始一个轮询,当loadingNum为0时开始执行ready中的代码。

详述模块依赖的处理

在Cold里,由于模块之间的依赖关系是定义在每个模块的js文件的add函数里的,因此只有当一个模块的js文件载入后才能获知其依赖模块,比如动画组件anim.js:
//anim.js
Cold.add('anim', ['dom'], function(){
	//--[1]--anim定义代码
});
它依赖于dom,当使用Cold.load('anim'),到执行add函数时,[1]处的代码不能立刻执行,因为它依赖于dom,[1]的代码使用了dom里面的内容。所以需要将[1]处的代码做一个延后,使其延后到dom.js中的定义代码执行后执行。 具体的方式是在dom.js的script节点上绑定一个callback属性,用于存储[1]的代码。
//dom.js
Cold.add('dom', ['browser'], function(){
	//--[2]--dom定义代码
});

//browser.js
Cold.add('browser', function(){
	//--[3]--browser定义代码
});

当执行dom.js后,同理,[2]的代码需要和[1]一起延后到browser.js里的定义代码之后进行执行。在browser.js的script节点上绑定一个callback属性,用于存储[2]以及dom.js的callback里存储的代码[1]。 当browser.js执行后,add函数会在执行完browser自身的定义代码[3]后,执行browser.js对应script节点里的callback属性(如图)。从而完成整个依赖调用[3]—[2]—[1]。

yilai1.png

循环依赖(loaded状态在循环依赖中的作用)

假设模块a依赖于b,模块b依赖于a,这就形成了一个循环依赖,导致任何一个模块都无法初始化。那么如何发现循环依赖? 假设我们load模块a,因为a依赖于b,程序开始去载入b的文件,并把a的定义代码放到了b节点的callback中去。a的状态为loaded。 然后执行b时,因为b依赖于a,b这时应该检查a的状态。 1.a为undefined,可以放心读取依赖a; 2.a为loading,将b的定义代码放到a的callback中。 3.a为attached,a已经载入,可以直接执行b的代码。 4.a为loaded,循环依赖。 在读取依赖模块状态时,如果出现loaded的情况,便可以确定循环依赖发生!解决的办法是忽略后一个依赖,直接执行a的定义代码。 !!!2011.2.25修正: 今天重新审了一下,发现本文中关于循环依赖的部分是错误的。假设load模块b,而b依赖于c,所以开始去下载c.js,而此时模块的b的状态为loaded的等待状态。此时,依赖于b的模块a.js下载完毕,它发现b的状态为loaded。很明显这里不是一个循环依赖的关系,而是一条循环链以不同的顺序载入了。由于文件载入的不确定性,这种情况时有发生,所以原有代码中关于循环依赖的解决方案是错误的。 今天重新构思了循环依赖,做出了一个新的解决方案: 使用一个reqList的命名空间来动态建立模块间的依赖关系:
reqList : {
	'a' : ['b', 'c']
}
上面代码表示a依赖于b、c。 思路如下: 当模块add的时候,将当前模块a以及当前模块a对应的reqList一起加到当前模块a的每个依赖模块所对应的reqList中去。即假若a依赖于b、c,则reqList[b] = reqList[c] = ['a'];若b依赖于d,则reqList[d] = ['b', 'a'],表示模块d依赖于模块b和a,如此递进。 而判断循环依赖的方式是:读取模块的依赖项,并到当前模块的reqList中去检查是否与依赖型重复,如果有,则判断出现循环依赖。比如:模块d的依赖于a、e,则将a、e到reqList[d]中去比对,出现重复项a,因此判断为循环依赖。 解决循环依赖的方式是将模块d的重复依赖项a删去。

小结

这里用了一些文字总结了Cold框架到目前为止的一些成果,在写的过程中本人也获益良多,接下来要继续完善和学习有关js框架的各类问题,欢迎交流共勉!

源码:https://github.com/afc163/cold

测试文件:http://www.ilovespringna.com/cold/test.html

posted @ 2011-03-24 17:05  afc163  阅读(264)  评论(0编辑  收藏  举报