Ruby's Louvre

每天学习一点点算法

导航

我的模块加载系统 v3

近一年来,外国非常热衷异步加载的研究。为了加快页面的载入速度,无阻塞的动态script注入的应用重新回到舞台的中心。LAB.jsControlJS ,Head JS, yepnopejs, $script.js,NBL JohnnyDepp.js loadrunner还有基于jquery的RequireJS,嘛,它已是另一个层面的东西,还搞了一个术语AMD(Modules/AsynchronousDefinition)来唬人。国内的,暂时只有岁月如歌的 SeaJS 比较有名。说是顺应潮流也好,跟风凑热闹也好,我也升级了我的模块加载系统,取名为并行加载器,作为我的框架的核心模块而存在。

为方便起见,用定时器进行哈希检测代替本来需要的script回调函数验证。require,declare与provide这些方法名听岁月如歌说的某一套标准制定的东西。嘛,我单是觉得名字好用借来用用,并不遵循它的条规行事的。

下面是它的一些API介绍:

dom.require(dependList,callback):请求模块。
 
dependList为依赖列队,可以为字符串或数组。如果在字符串状态下表示多个模块,请用逗号隔开。如果依赖模块的JS文件与核心模块的JS文件不在同一个文件夹下,可以使用“/”隔开,或者后面用小括号指定具体路径,亦即"path/path/moduleName(url),path/path/moduleName(url)"的格式,可以简化为"moduleName(url)", "path/path/moduleName"。moduleName可以包含"."号。callback为回调函数,没有什么好说的。
 
dom.provide(array,hash,callback):提供模块。
 
array为数组,里面为经过处理的模块名,换言之,不再包含path或url。hash,用于注册与验证模块名,标识这个模块已经加载过了。callback为回调函数。
 
dom.declare(moduleName,dependList,callback):声明模块。
 
此方法只是dom.require方法的一个简单封简而已,用于保证其回调函数在依赖模块都加载的情况下安全执行。moduleName为模块名,不能有path与url,dependList、callback这两个参数与dom.require相同。

作为约定,模块名应该与其所在的JS文件名相同,但也允许不一样(到时请用小括号指定具体路径),一个JS文件也可以放置多个模块,这时我抛弃使用script.onload与script.onreadystatechange来验证模块加载成功的原因,它们只在移除script节点本身时发挥效力。

王婆卖瓜,自吹一下优点:

  1. 按需加载
  2. 无阻塞
  3. 实现JS颗粒化管理
  4. 解决Ajax无法处理的跨域问题
  5. 模块自动处理依赖
  6. 以后还考虑文件合并的问题
;;;(function(WIN,DOM,undefined){
    var
    reg_module_name = /(?:^|\/)([^(\/]+)(?=\(|$)/,
    reg_module_url = /\(([^)]+)\)/,
    reg_multi_module = /\s*,\s*/g,
    _dom = WIN.dom,
    dom = {
        mix : function(target, source ,override) {
            var i, ride = (override === void 0) || override;
            for (i in source) {
                if (ride || !(i in target)) {
                    target[i] = source[i];
                }
            }
            return target;
        },
 
        noConflict: function(  ) {//防止命名冲突,请先行引用其他库再引用本框架
            WIN.dom = _dom;//这是别人的
            return dom;//请赋以其一个命名空间
        },
        //请求模块
        require:function(dependList,callback){
            var self = arguments.callee
            var moduleNames = [], i =0, hash = self.loadedModules,
               re = reg_multi_module, reg = reg_module_name ,moduleName, str;
            if(typeof dependList === "string"){
                dependList  = dependList.split(re)
            }
            while(str = dependList[i++]){
                moduleName = str.match(reg)[1];
                if(!hash[moduleName]){
                    moduleNames.push(moduleName);
                    self.appendScript(str);
                }
            }
            this.provide(moduleNames,hash,callback)
        },
        //声明模块
        declare:function(moduleName,dependList,callback){
            var hash = this.require.loadedModules;
            this.require(dependList,function(){
                callback();
                hash[moduleName] = 1
            })
        },
        //提供模块
        provide:function(array,hash,callback){
            var flag = true, i = 0, args = arguments, fn = args.callee, el;
            while(el = array[i++]){
                if(!hash[el]){
                    flag = false;
                    break;
                }
            }
            if(flag){
                callback();
            }else{
                //javascrpt最短时钟间隔为16ms,这里取其倍数
                //http://blog.csdn.net/aimingoo/archive/2006/12/21/1451556.aspx
                setTimeout(function(){
                    fn.apply(null,args)
                },32);
            }
        }
    }
    dom.mix(dom.require, {
        loadedModules:{},
        requestedUrl: {},
        //http://www.cnblogs.com/rubylouvre/archive/2011/02/10/1950940.html
        getBasePath:function(){
            var url;
            try{
                a.b.c()
            }catch(e){
                url = e.fileName || e.sourceURL;//针对firefox与safari
            }
            if(!url){
                var script = (function (e) {
                    if(!e) return null;
                    if(e.nodeName.toLowerCase() == 'script') return e;
                    return arguments.callee(e.lastChild)
                })(DOM);//取得核心模块所在的script标签
                url = script.hasAttribute ?  script.src : script.getAttribute('src', 4);
            }
            url = url.substr( 0, url.lastIndexOf('/'));
            dom.require.getBasePath = function(){
                return url;//缓存结果,第一次之后直接返回,再不用计算
            }
            return url;
        },
        appendScript:function(str){
            var module = str, reg = reg_module_url, url;
            //处理dom.node(http://www.cnblogs.com/rubylouvre/dom/node.js)的情形
            var _u = module.match(reg);
            url = _u && _u[1] ? _u[1] : this.getBasePath()+"/"+ str + ".js";
            if(!this.requestedUrl[url]){
                 this.requestedUrl[url] = 1;
                 var script = DOM.createElement("script");
                 script.charset = "utf-8";
                 script.defer = true;
             //  script.async = true;//不能保证多个script标签的执行顺序
                script.src = url;
            //避开IE6的base标签bug
            //http://www.cnblogs.com/rubylouvre/archive/2010/05/18/1738034.html
                DOM.documentElement.firstChild.insertBefore(script,null);//
                this.removeScript(script);  
            }
            
        },
        removeScript:function(script){//移除临时生成的节点
            var parent = script.parentNode;
            if(parent&&parent.nodeType === 1){
                script.onload = script.onreadystatechange = function(){
                    if (-[1,] || this.readyState === "loaded" || this.readyState === "complete" ){
                        if (this.clearAttributes) {
                            this.clearAttributes();
                        } else {
                            this.onload = this.onreadystatechange = null;
                        }
                        parent.removeChild(this)
                    }
                }
            }
        }
    });
    //先行取得核心模块的URL
    dom.require.getBasePath();
    window.dom = dom;
})(this,this.document);

示例:

<!doctype html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>并行加载器 by 司徒正美</title>
        <script src="dom.js"></script>
        <script>
            dom.require("dom/string,dom/array",function(){
                window.console && window.console.log("这是回调函数")
                dom.query("调用子模块的方法")
            });

        </script>
    </head>
    <body>
        <h1>并行加载器 by 司徒正美</h1>
        <p>请用FF的firebug观察效果</p>
    </body>
</html>

其他依赖模块的内容:

//string.js
dom.declare("string","dom/query",function(){
    window.console && window.console.log("已加载string模块");
});

//array.js
dom.declare("array","dom/node",function(){
    window.console && window.console.log("已加载array模块")
});
//query.js
dom.declare("query",[],function(){
    window.console && window.console.log("已加载query模块")
    dom.query = function(str){
        window.console && window.console.log(str)
    }
});
//node.js
dom.declare("node",[],function(){
    window.console && window.console.log("已加载node模块")
});

文件目录图

加载顺序图

如果对它有兴趣,可在点此下载看一下。

posted on 2011-02-11 09:39  司徒正美  阅读(4779)  评论(14编辑  收藏  举报