Ruby's Louvre

每天学习一点点算法

导航

我的模块加载系统 v17

本版本的重要更新是完全实现AMD规范。整个框架根据此新加载器重写,因此可以方便调用老外用AMD规范写好的JS库了.

  • 日志打印可以通过设置$.core.level排除某些不重要的日志打印。
  • 添加config方法来设置框架的一些重要信息或对模块进行别名。
  • 在VS系列实现智能提示。

有关本模块加载系统的使用,可以到这里详看教程.新加载器是同时支持AMD与玉伯搞的seajs的CMD.如果你在模块定义中的回调带有"require","exports","modules"中的任两个,加载器就视为CMD.不过无视是AMD与CMD,函数里面require, modules, exports都是可用的,完全可以像node.js这样加载模块.

有关require方法的模块标识,可以见这里

有关为什么要用AMD来管理我们的脚本,我想做过一些大项目的人应该心中有数,这不是一个合并JS所能取替的,详见此文


下面揭载我的加载器的运作机理,姑且从一个没有依赖的模块着手,比如

            $.require("$lang_fix", function(){
                console.log("xxxxxxxxxxxxxx")
            })

不用说,进入$.require(希望大家看这东西时,打开源码对照着看。)

 String(list).replace( $.rword, function(el){

  })

el  = "$lang_fix"

进入Module._resolveFilename


url = $.core.base + el + ".js"

初次加载,肯定没有在modules中注册, modules[url]为undefined

于是进入 loadJS( url, id );

  • 创建一个iframe,
  • 在第一个script节点 用nick, Ns, nick分别保存url, $, innerDefine
  • 在第二个script节点 用url去加载目标节点

第二个节点视情况不同分别绑定onreadystatechange, onload, onerror

值得注意的是我们在注册模块时state还是为undefined,它会在innerDefine中修改state。

然后我们通过iframe中的script加载模块,而模块一般是这样的格式
define("lang_fix", function( ){
  //==========略============
})

这里的define实质上是innerDefine

innerDefine里面做了几个很重要的事情(我们现在只需看第一,第二)

  1. 第一个事情把lang_fix这第一个参数换掉,换成nick, nick就是加载它的那个script的src。
  2. 第二个事情是将它对应的模块的状态改为1,也是modules[url].state = 1。
  3. 第三个事情是$, exports, require, module等对象强塞进模块工厂
  4. 第四个事情是转交真正的$.define去处理

在第二事情中,我们将模块的状态修改了,于是节点执行onreadystatechange/onload时

if(/loaded|complete|undefined/i.test(this.readyState) }{
    Ns._checkDeps();
     Ns._checkFail(self.document, nick);
}

Ns._checkFail发挥效力

_checkFail : function(  doc, id, error ){
    doc && (doc.ok = 1);
    if( error || !modules[ id ].state ){
         this.log("Failed to load [[ "+id+" ]]"+modules[ id ].state);
    }
},

如果是死链,那无法调用define函数,也就无法调用innerDefine,状态为undefine

!modules[ id ].state == true,于是打印错误日志,当然以后我们讨论一下,是不是该throw

如果在旧式opera,它是会无法进入onreadystatechange, onload, onerror任一回调

在_checkFail中,我们会修复doc.ok = 1,那么在iframe中onload中我们检测doc.ok不等于1时,

就在_checkFail传入第三个参数true,让它打印错误日志

如果在FF,chrome, IE9,它们就会进入onerror回调,那里的调用代码中


Ns._checkFail(self.document, nick, true)

因此也顺利检测到死链。

反正只要加载失败,我们就立即把对应iframe移出DOM树!

好了,如果成功加载,我们就通过innerDefine到达$.define,并也把对应iframe移出DOM树

$.define干了如下几个事情

  1. 第一个事情,检测第二个参数是否为布尔,是说明其是补丁模块,如果布尔值为true,说明这个补丁模块对这个浏览器是没有用的,直接return,不执行它的模块工厂了。如果为false,比如IE6,补丁模块对它总是有用,先去掉此参数,继续往下执行。
  2. 第二个事情,检测参数个数,如果只有两个,说明只有模块名与模块的回调(亦有可能不是回调),那么我们插入一个空数组作为依赖列表
  3. 第三个就是检测第三个参数是否为函数,不是函数,比如说是个对象,我们要将它塞入一个函数

比如


define({
  aaa:2
})

经上面几次转换,依次变为


define("http://xxxxxxx/aa.js",{
  aaa:2
});
//---->
define("http://xxxxxxx/aa.js" ,[],{
  aaa:2
});

//---->
define("http://xxxxxxx/aa.js" ,[], function(){
 return {
    aaa:2
  }
});

//---->
$.require([], function(){
 return {
    aaa:2
  }
}), "http://xxxxxxx/aa.js")

最后我们将参数的顺序重排一下,再次调用$.require

 //0,1,2 --> 1,2,0
 this.require( args[1], args[2], parent );

由于lang_fix是没有依赖的,因此dn === cn 相当于 0 == 0,执行install( id, args, factory );

install简而言之是将模块工厂执行,将state改为2。

我们还需要注意一下,我们每次调用$.require或加载一个脚本时都执行_checkDeps方法。

当lang_fix的状态改为2后



 $.require("$lang_fix", function(){
      console.log("xxxxxxxxxxxxxx")
 })

这个回调也将执行!

控制台打印xxxxxxxxxxxxxxxx

源码位于这里

posted on 2012-08-30 08:13  司徒正美  阅读(3195)  评论(8编辑  收藏  举报