• 我的模块加载系统 v12


    这应该是massFramework发布前的最后一次对模块系统的升级了。本版本最大的改进是,增加对回调函数传参的支持,让模块更加彻底,并且通过对require与内部的resolveCallbacks的重构,对框架对JS文件依赖合并吏加友好。

    本版本有以下改进:

    1. 更改dom["@emitter"] 为 dom["@dispatcher"],因为emitter模块更名为dispatcher了。
    2. 经过Q群上的讨论,移除XMLHttpRequest的判定。
    3. 添加error方法,这只是一个空实现,目的是用于调试,用户可以自行重写它。
    4. 重构log方法
    5. 回调函数将根据依赖列表生成参数,实现模块不依赖命名空间进行消息通信(参考自CommonJS的AMD)。

    下面是我框架的对应实现,详细用法见文档。

    //定义模块
                (function(global,DOC){
                    var dom = global[DOC.URL.replace(/(#.+|\W)/g,'')];
                    dom.define("node", "class,support,query,data",function($){//依赖列表可以是由空格或逗号隔开的字符串或数组,它是可选的
                        dom.log("node模块")
                    });
                })(this,this.document);
    //使用模块
                dom.require("ready,support,class",function(dom,support,factory){
                    dom.log("mix" in dom,true)//判定是否来自dom模块
                    dom.log("cloneAll" in support,true);//判定是否来自support模块
                    dom.log(dom.type(factory),true)//判定是否来自class模块, class模块并没有返回值
                });
    
    //=========================================
    // 模块加载模块(核心模块)2011.10.20 by 司徒正美
    //=========================================
    (function(global,DOC){
        var
        _dom = global.dom,
        w3c = DOC.dispatchEvent, //w3c事件模型
        namespace = DOC.URL.replace( /(#.+|\W)/g,''),
        rreadystate = /loaded|complete|undefine/i,
        HEAD = DOC.head || DOC.getElementsByTagName("head")[0],
        class2type = {
            "[object HTMLDocument]"   : "Document",
            "[object HTMLCollection]" : "NodeList",
            "[object StaticNodeList]" : "NodeList",
            "[object IXMLDOMNodeList]": "NodeList",
            "[object DOMWindow]"      : "Window"  ,
            "[object global]"         : "Window"  ,
            "null"                    : "Null"    ,
            "NaN"                     : "NaN"     ,
            "undefined"               : "Undefined"
        },
        toString = class2type.toString;
        /**
         * 糅杂,为一个对象添加更多成员
         * @param {Object} target 目标对象
         * @param {Object} source 属性包
         * @return  {Object} 目标对象
         */
        function mix(target, source){
            var args = [].slice.call(arguments),key,
            ride = typeof args[args.length - 1] == "boolean" ? args.pop() : true;
            target = target || {};
            for(var i = 1;source = args[i++];){
                for (key in source) {
                    if (ride || !(key in target)) {
                        target[key] = source[key];
                    }
                }
            }
            return target;
        }
        /**
         * @class dom
         * dom Framework拥有两个命名空间,
         * 第一个是DOC.URL.replace(/(\W|(#.+))/g,''),根据页面的地址动态生成
         * 第二个是dom,我们可以使用别名机制重写它
         * @namespace dom
         */
        var dom = function(expr,context){
            if(dom.type(expr,"Function")){ //注意在safari下,typeof nodeList的类型为function,因此必须使用dom.type
                dom.require("ready,lang,event,fx",expr);
            }else{
                if(!dom.fn)
                    throw "must load the 'node' module!"
                return new dom.fn.init(expr,context);
            }
        }
    
        mix(dom,{
            html:DOC.documentElement,
            head:HEAD,
            rword:/[^, ]+/g,
            "@uuid":1,
            "@name":"dom",
            "@debug":true,
            "@dispatcher": w3c ? "addEventListener" : "attachEvent",
            "@path":(function(url, scripts, node){
                scripts = DOC.getElementsByTagName("script");
                node = scripts[scripts.length - 1];
                url = node.hasAttribute ?  node.src : node.getAttribute('src', 4);
                return url.substr( 0, url.lastIndexOf('/'));
            })(),
            /**
             * 别名机制(相当于jquery的noConflict)
             * @param {String} name 新的命名空间
             */
            exports: function(name) {
                _dom && (global.dom = _dom);
                name = name || dom["@name"];
                dom["@name"] = name ;
                global[name] = global[namespace] = this;
            },
            /**
             * 数组化
             * @param {ArrayLike} nodes 要处理的类数组对象
             * @param {Number} start 可选。要抽取的片断的起始下标。如果是负数,从后面取起
             * @param {Number} end  可选。规定从何处结束选取
             * @return {Array}
             */
            slice: function (nodes, start, end) {
                for(var i = 0,n = nodes.length, result = []; i < n; i++){
                    result[i] = nodes[i];
                }
                if (arguments.length > 1) {
                    return result.slice(start , (end || result.length));
                } else {
                    return result;
                }
            },
            noop:function(){} ,
            error:function(){},
            /**
             * 用于取得数据的类型或判定数据的类型
             * @param {Any} obj 要检测的东西
             * @param {String} str 要比较的类型
             * @return {String|Boolean}
             */
            type : function(obj, str){
                var result = class2type[ (obj == null || obj !== obj )? obj :  toString.call(obj)  ] || obj.nodeName || "#";
                if( result.charAt(0) === "#"){//兼容旧式浏览器与处理个别情况,如window.opera
                    //利用IE678 window == document为true,document == window竟然为false的神奇特性
                    if(obj == obj.document && obj.document != obj){
                        result = 'Window'; //返回构造器名字
                    }else if(obj.nodeType === 9) {
                        result = 'Document';//返回构造器名字
                    }else if(  obj.callee ){
                        result = 'Arguments';//返回构造器名字
                    }else if(isFinite(obj.length) && obj.item ){
                        result = 'NodeList'; //处理节点集合
                    }else{
                        result = toString.call(obj).slice(8,-1);
                    }
                }
                if(str){
                    return str === result;
                }
                return result;
            },
            /**
             * 用于调试
             * @param {String} s 要打印的内容
             * @param {Boolean} force 强逼打印到页面上
             */
            log:function(s, force){
                if(force){
                    dom.require("ready",function(){
                        var div =  DOC.createElement("div");
                        div.innerHTML = s;
                        DOC.body.appendChild(div)
                    });
                }else if(global.console ){
                    global.console.log(s);
                }
            },
            /**
             * 生成键值统一的对象,用于高速化判定
             * @param {Array|String} array 如果是字符串,请用","或空格分开
             * @param {Number} val 可选,默认为1
             * @return {Object}
             */
            oneObject : function(array, val){
                if(typeof array == "string"){
                    array = array.match(dom.rword) || [];
                }
                var result = {},value = val !== void 0 ? val :1;
                for(var i=0,n=array.length;i < n;i++){
                    result[array[i]] = value;
                }
                return result;
            }
        });
        "Boolean,Number,String,Function,Array,Date,RegExp,Window,Document,Arguments,NodeList".replace(dom.rword,function(name){
            class2type[ "[object " + name + "]" ] = name;
        });
        var
        rmodule =  /([^(\s]+)\(?([^)]*)\)?/,
        onload= w3c ? "onload" : "onreadystatechange",
        names = [],//需要处理的模块名列表
        rets = {},//用于收集模块的返回值
        cbi = 1;//用于生成回调函数的名字
        var map = dom["@modules"] = {
            "@ready" : { }
        };
    
        /**
         * 用于模拟opera的script onerror
         * @param {String} name 模块名
         * @param {String} url  模块的路径
         * @param {Element} node 为加载这个模块临时生成的script节点
         */
        function fixOperaError(name, url, node){
            var iframe = DOC.createElement("iframe");
            var code = "<script> this[document.URL.replace(/(#.+|\\W)/g,'')] = {define:Date } ;<\/script>"+
            '<script src="'+url+'" onload="this.document.x = 1;"><\/script>';//IE与opera的元素节点都拥有document属性,相当于ownerDocument
            iframe.style.display = "none";
            HEAD.appendChild(iframe);
            var d = iframe.contentDocument;
            d.write(code);
            d.close();
            iframe.onload = function(){
                if(d.x == void 0){
                    removeScript(name, node, true);
                }
                iframe.onload = null;//opera无法在iframe被事件绑定时被移除
                HEAD.removeChild(this);
            };
        }
    
        /**
         * 为加载模块而临时生成一个script节点
         * @param {String} name 模块名
         * @param {String} url  模块的路径
         */
        function appendScript(name, url){
            var node = DOC.createElement("script");
            url = url  || dom["@path"] +"/"+ name.slice(1) + ".js" + (dom["@debug"] ? "?timestamp="+new Date : "");
            node.charset = "utf-8";
            node.async = true;
            node.onerror = function(){//如果是file协议,只有IE safari chrome才能进入此分支
                removeScript(name, this, true);
            }
            node[onload] = function(){//如果是file协议,只有IE safari chrome才能进入此分支
                if (w3c || rreadystate.test(this.readyState) ){
                    resolveCallbacks();
                    removeScript(name, this );
                }
            }
            node.src =  url;//如果在CHM中这样的加载方法无效,必须使用document.write
            global.opera && fixOperaError(name, url, node);
            HEAD.insertBefore(node,HEAD.firstChild);
        }
        /**
         * 移除临时生成的script节点
         * @param {String} name 模块名
         * @param {Element} node 为加载这个模块临时生成的script节点
         * @param {Boolean} error 是否加载失败
         */
        function removeScript(name, node, error){
            var parent = node.parentNode;
            if(parent && parent.nodeType === 1){
                if(error || !map[name].state ){
                    dom.stack(new Function('dom.log("fail to load module [ '+name+' ]")'));
                    dom.stack.fire();//打印错误堆栈
                }
                node.clearAttributes &&  node.clearAttributes();
                node[onload] = node.onerror = null;
                parent.removeChild(node);
            }
        }
    
        //执行并移除所有依赖都具备的模块或回调
        function resolveCallbacks(){
            loop:
            for (var i = names.length,repeat, name; name = names[--i]; ) {
                var  obj = map[name], deps = obj.deps;
                for(var key in deps){
                    if(deps.hasOwnProperty(key) && map[key].state != 2 ){
                        continue loop;
                    }
                }
                //如果deps是空对象或者其依赖的模块的状态都是2
                if( obj.state != 2){
                    names.splice(i,1);//必须先移除再执行,防止在IE下DOM树建完后手动刷新页面,会多次执行最后的回调函数
                    var fn = obj.callback;
                    rets[fn._name] = fn.apply(null,incarnate(obj.args));//只收集模块的返回值
                    obj.state = 2;
                    repeat = true;
                }
            }
        repeat &&  resolveCallbacks();
        }
        function incarnate(args){//传入一组模块名,返回对应模块的返回值
            for(var i = 0,ret = [dom], name; name = args[i++];){
                ret.push(rets[name])
            }
            return ret;
        }
        function deferred(){//一个简单的异步列队
            var list = [],self = function(fn){
                fn && fn.call && list.push(fn);
                return self;
            }
            self.method = "shift";
            self.fire = function(fn){
                while(fn = list[self.method]()){
                    fn();
                }
                return list.length ? self : self.complete();
            }
            self.complete = dom.noop;
            return self;
        }
        var errorstack = dom.stack = deferred();
        errorstack.method = "pop";
        mix(dom, {
            mix:mix,
            //绑定事件(简化版)
            bind : w3c ? function(el, type, fn, phase){
                el.addEventListener(type,fn, !!phase);
                return fn;
            } : function(el, type, fn){
                el.attachEvent("on"+type, fn);
                return fn;
            },
            unbind : w3c ? function(el, type, fn, phase){
                el.removeEventListener(type, fn, !!phase);
            } : function(el, type, fn){
                el.detachEvent("on"+type, fn);
            },
    
            //请求模块
            require:function(deps,callback,errback){//依赖列表,正向回调,负向回调
                var _deps = {}, args = [], dn = 0, cn = 0;
                (deps +"").replace(dom.rword,function(url,name,match){
                    dn++;
                    match = url.match(rmodule);
                    name  = "@"+ match[1];//取得模块名
                    if(!map[name]){ //防止重复生成节点与请求
                        map[name] = { };//state: undefined, 未加载; 1 已加载; 2 : 已执行
                        appendScript(name,match[2]);//加载JS文件
                    }else if(map[name].state === 2){
                        cn++;
                    }
                    if(!_deps[name] ){
                        name !== "@ready" && args.push(name);
                        _deps[name] = "司徒正美";//去重,去掉@ready
                    }
                });
                var cbname = callback._name;
                if(dn === cn ){//在依赖都已执行过或没有依赖的情况下
                    if(cbname && !(cbname in rets)){
                        //如果是使用合并方式,模块会跑进此分支(只会执行一次)
                        map[cbname].state = 2
                        return rets[cbname] = callback.apply(null,incarnate(args));   
                    }else if(!cbname){//普通的回调可执行无数次
                        return callback.apply(null,incarnate(args))
                    }
                }
                cbname = cbname || "@cb"+ (cbi++).toString(32);
                dom.stack(errback);//压入错误堆栈
                map[cbname] = {//创建或更新模块的状态
                    callback:callback,
                    deps:_deps,
                    args: args,
                    state: 1
                };//在正常情况下模块只能通过resolveCallbacks执行
                names.unshift(cbname);
            },
            //定义模块
            define:function(name,deps,callback){//模块名,依赖列表,模块本身
                if(typeof deps == "function"){//处理只有两个参数的情况
                    callback = deps;
                    deps = "";
                }
                callback._name =  "@"+name;  //模块名
                this.require(deps,callback);
            }
        });
        //domReady机制
        var readylist = deferred();
        function fireReady(){
            map["@ready"].state = 2;
            resolveCallbacks();//fix opera没有执行最后的回调
            readylist.complete = function(fn){
                fn && fn.call &&  fn()
            }
            readylist.fire();
            fireReady = dom.noop;
        };
        function doScrollCheck() {
            try {
                dom.html.doScroll("left");
                fireReady();
            } catch(e) {
                setTimeout( doScrollCheck, 1);
            }
        };
        //开始判定页面的加载情况
        if ( DOC.readyState === "complete" ) {
            fireReady();
        }else {
            dom.bind(DOC, (w3c ? "DOMContentLoaded" : "readystatechange"), function(){
                if (w3c || DOC.readyState === "complete") {
                    fireReady();
                }
            });
            if ( dom.html.doScroll && self.eval === top.eval ) {
                doScrollCheck();
            }
        }
        //https://developer.mozilla.org/en/DOM/window.onpopstate
        dom.bind(global,"popstate",function(){
            namespace = DOC.URL.replace(/(#.+|\W)/g,'');
            dom.exports();
        });
        dom.exports();
        
    })(this,this.document);
    /**
     2011.7.11
    @开头的为私有的系统变量,防止人们直接调用,
    dom.check改为dom["@emitter"]
    dom.namespace改为dom["@name"]
    去掉无用的dom.modules
    优化exports方法
    2011.8.4
    强化dom.log,让IE6也能打印日志
    重构fixOperaError与resolveCallbacks
    将provide方法合并到require中去
    2011.8.7
    重构define,require,resolve
    添加"@modules"属性到dom命名空间上
    增强domReady传参的判定
    2011.8.18 应对HTML5 History API带来的“改变URL不刷新页面”技术,让URL改变时让namespace也跟着改变!
    2011.8.20 去掉dom.K,添加更简单dom.noop,用一个简单的异步列队重写dom.ready与错误堆栈dom.stack
    2011.9.5  强化dom.type
    2011.9.19 强化dom.mix
    2011.9.24 简化dom.bind 添加dom.unbind
    2011.9.28 dom.bind 添加返回值
    2011.9.30 更改是否在顶层窗口的判定  global.frameElement == null --> self.eval === top.eval
    2011.10.1
    更改dom.uuid为dom["@uuid"],dom.basePath为dom["@path"],以示它们是系统变量
    修复dom.require BUG 如果所有依赖模块之前都加载执行过,则直接执行回调函数
    移除dom.ready 只提供dom(function(){})这种简捷形式
    2011.10.4 强化对IE window的判定, 修复dom.require BUG dn === cn --> dn === cn && !callback._name
    2011.10.9
    简化fixOperaError中伪dom命名空间对象
    优化截取隐藏命名空间的正则, /(\W|(#.+))/g --〉  /(#.+|\\W)/g
    2011.10.13 dom["@emitter"] -> dom["@dispatcher"]
    2011.10.16 移除XMLHttpRequest的判定,回调函数将根据依赖列表生成参数,实现更彻底的模块机制
    2011.10.20 添加error方法,重构log方法
    */
    
    

    模块间的通信请见附件中的e.js。

    模块加载的示例(115)

    dom Framework的部分文档,还差ajax。

    相关链接:

    我的模块加载系统 v11

    我的模块加载系统 v10

    我的模块加载系统 v9

    我的模块加载系统 v8

    我的模块加载系统 v7

    我的模块加载系统 v6

  • 相关阅读:
    JavaEE——SpringMVC(11)--拦截器
    JavaEE——SpringMVC(10)--文件上传 CommonsMultipartResovler
    codeforces 460A Vasya and Socks 解题报告
    hdu 1541 Stars 解题报告
    hdu 1166 敌兵布阵 解题报告
    poj 2771 Guardian of Decency 解题报告
    hdu 1514 Free Candies 解题报告
    poj 3020 Antenna Placement 解题报告
    BestCoder5 1001 Poor Hanamichi(hdu 4956) 解题报告
    poj 1325 Machine Schedule 解题报告
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2226228.html
Copyright © 2020-2023  润新知