我的模块加载系统 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里面做了几个很重要的事情(我们现在只需看第一,第二)
- 第一个事情把lang_fix这第一个参数换掉,换成nick, nick就是加载它的那个script的src。
- 第二个事情是将它对应的模块的状态改为1,也是modules[url].state = 1。
- 第三个事情是$, exports, require, module等对象强塞进模块工厂
- 第四个事情是转交真正的$.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干了如下几个事情
- 第一个事情,检测第二个参数是否为布尔,是说明其是补丁模块,如果布尔值为true,说明这个补丁模块对这个浏览器是没有用的,直接return,不执行它的模块工厂了。如果为false,比如IE6,补丁模块对它总是有用,先去掉此参数,继续往下执行。
- 第二个事情,检测参数个数,如果只有两个,说明只有模块名与模块的回调(亦有可能不是回调),那么我们插入一个空数组作为依赖列表
- 第三个就是检测第三个参数是否为函数,不是函数,比如说是个对象,我们要将它塞入一个函数
比如
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
源码位于这里