2.依赖加载原理
有很多语言都有模块化的结构,比如c/c++的#include语句,Ruby的require语句等等。模块的执行,必然需要其依赖的模块准备就绪才能顺利执行。
c/c++是编译语言,在预编译时,替换#include语句,将依赖的文件内容包含进来,在编译后的执行期,所有的模块才会开始执行;
而Ruby是解释型语言,在模块执行前,并不知道它依赖什么模块,待到执行到require语句时,执行将暂停,从外部读取并执行依赖,然后再回来继续执行当前模块。
JavaScript作为一门解释型语言,在复杂的浏览器环境中,Sea.js是如何处理CMD模块间的依赖的呢?
a.Node的依赖加载原理
node于Ruby类似,当我们使用node usegreet.js来运行这个模块时,实际上node会构建一个运行的上下文,在这个上下文中运行这个模块。运行到require(‘./greet‘)这句话时,会通过注入的API,在新的上下文中解析greet.js这个模块,然后通过注入的exports或module这两个关键字获取该模块的接口,将接口暴露出来给usegreet.js使用,即通过greet这个对象来引用这些接口。
node的模块方案的特点如下:
(1)使用require、exports和module作为模块化组织的关键字;
(2)每个模块只加载一次,作为单例存在于内存中,每次require时使用的是它的接口;
(3)require是同步的,通俗地讲,就是node运行A模块,发现需要B模块,会停止运行A模块,把B模块加载好,获取的B的接口,才继续运行A模块。如果B模块已经加载到内存中了,当然require B可以直接使用B的接口,否则会通过fs模块化同步地将B文件内存,开启新的上下文解析B模块,获取B的API。
注意:实际上node如果通过fs异步的读取文件的话,require也可以是异步的,所以曾经node中有require.async这个API。
b.Sea.js加载原理
由于在浏览器端,采用与node同样的依赖加载方式是不可行的,因为依赖只有在执行期才能知道,但是此时在浏览器端,我们无法像node一样直接同步地读取一个依赖文件并执行!我们只能采用异步的方式。于是Sea.js的做法是,分成两个时期——加载期和执行期;
加载期:即在执行一个模块之前,将其直接或间接依赖的模块从服务器端同步到浏览器端;
执行期:在确认该模块直接或间接依赖的模块都加载完毕之后,执行该模块。
加载期:不难想见,模块间的依赖就像一棵树。启动模块作为根节点,依赖模块作为叶子节点。下面是pixelegos(一个开源项目)的依赖树:
如上图,在页面中通过seajs.use(‘/js/pixelegos‘)
调用,目的是执行pixelegos这个模块。Sea.js并不知道pixelegos还依赖于其他什么模块,只是到服务端加载pixelegos.js,将其加载到浏览器端之后,通过分析发现它还依赖于其他的模块,于是Sea.js又去加载其他的模块。随着更多的模块同步到浏览器端后,一棵依赖树才慢慢地通过递归显现出来。
执行期:在执行期,执行也是从根节点开始,本质上是按照代码的顺序结构,对整棵树进行了遍历。有的模块可能已经EXECUTED,而有的还需要执行获取其exports。由于在执行期时,所有依赖的模块都加载好了,所以与node执行过程有点类似。
文章源自 设计联盟 www.DesignLinks.cn 中国最具影响力的创意设计综合网站