我的模塊加載系統 v21


多上stackoverflow總是有好處,昨天發現opera12之前一個比較致命的BUG,觸發條件是script.onreadystatechange = script.onload = function(){},因此連同mass, jQuery, seajs,labjs, headjs,controljs在內都中招了。我們可以稱之為連寫回調引發的血案。這個我正文會詳解。

本版本的第二個改進是IE6的自閉合base標簽回避問題。以前不管是否存在base標簽,所有瀏覽器都插入到head標簽的第一個子節點之前。但這樣倒序可能引發后插入的先解析。因此現在特地對IE6進行處理,如果存在base標簽,不管它是否自閉合,插入到它的前面,那樣也可以保證順序插入。其他瀏覽器則使用head.appendChild(script)。

本版本的第三個改進是FF3.6之前不支持document.readyState問題,這個是用於domReady。以前我們先行判定DOC.readyState === "complete",但FF沒有這東西,就每次進入DOMContentLoaded分支。但DOMContentLoaded事件只會觸發一次,domReady后這分支就不起作用了。解決方案我是從labjs的源碼中讀到的:

	/* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
	   NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?).
	   
	   The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does 
	   proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked 
	   document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready. 
	   For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or 
	   fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs.
	*/ 
	(function(addEvent,domLoaded,handler){
		if (document.readyState == null && document[addEvent]){
			document.readyState = "loading";
			document[addEvent](domLoaded,handler = function(){
				document.removeEventListener(domLoaded,handler,false);
				document.readyState = "complete";
			},false);
		}
	})("addEventListener","DOMContentLoaded");

第四個改進是,框架最開頭前的三個分號的回歸!這是用於合並腳本時用。

好了,回歸第一個問題,我們的大葷。我們推崇使用script加載模塊,是因為它提供事件回調讓我們掌握整個加載狀況。IE6-8是使用onreadystatechange 事件,IE9-10開始支持onload,其他標瀏覽器則支持onload,但opera比較奇葩,onload與onreadystatechange 都支持,並且script也有readyState屬性。如果opera抄襲抄得好也沒所謂,但抄出BUG來。

看下面腳本,第一次加載它時,opera9.64 彈出interactive,loaded。然后我們關閉此頁面,再打開它時,就彈出loaded,loaded(估計是瀏覽器緩存的原因),並且這情況一定沒有改善。隨機抽個版本,如opera10.52,加載個新JS文件,第一次就是彈出loaded,loaded。幸好在opera12.12,它已經不支持onreadystatechange。而IE6-10總是彈出loading,loaded。

window.onload = function(){
    var script = document.createElement("script");
    var array = []
    script.onreadystatechange = function(){
        array.push(this.readyState);
    }
    script.src = "jquery.js"// Opera 9.64
    document.body.appendChild(script);
    setTimeout(function(){
        alert(array)
    },2000)
}

換言之,這種連寫法會讓我們在opera沒有加載完JS前執行用戶回調,導致出錯。解決方案是不要連寫,並在回調中只對IE進行readyState判定!下面是改進后loadJS函數:


    function loadJS( url ){
        var node = DOC.createElement("script")//, IE = node.uniqueID
        node.className = moduleClass;
        node[W3C ? "onload" : "onreadystatechange"] = function(){
            if(W3C || /loaded|complete/i.test(node.readyState) ){
                //mass Framework會在_checkFail把它上面的回調清掉
                var factory = parsings.pop() ;
                factory &&  factory.delay(node.src)
                if( checkFail(node) ){
                    $.log("已成功加載 "+node.src, 7);
                }
            }
        }
        node.onerror = function(){
            checkFail(node, true)
        }
        node.src = url 
   
            head.appendChild(node)

        $.log("正准備加載 "+node.src, 7)
    }

基本上就是這樣。mass Framework許多想法總是跑在世界前面,兼容性巨無細漏,歡迎試用!

;;;(function( global, DOC ){
    var $$ = global.$//保存已有同名變量
    var rmakeid = /(#.+|\W)/g;
    var NsKey = DOC.URL.replace( rmakeid,"")
    var NsVal = global[ NsKey ];//公共命名空間
    var W3C   = DOC.dispatchEvent //w3c事件模型
    var html  = DOC.documentElement;
    var head  = DOC.head || DOC.getElementsByTagName( "head" )[0]
    var base = head.getElementsByTagName("base")[0];
    var loadings = [];//正在加載中的模塊列表
    var parsings = []; //儲存需要綁定ID與factory對應關系的模塊(標准瀏覽器下,先parse的script節點會先onload)
    var mass = 1;//當前框架的版本號
    var postfix = "";//用於強制別名
    var cbi = 1e5 ; //用於生成回調函數的名字
    var all = "lang_fix,lang,support,class,flow,query,data,node,attr,css_fix,css,event_fix,event,ajax,fx"
    var moduleClass = "mass" + -(new Date());
    var 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"
    }
    var toString = class2type.toString, basepath
    function $( expr, context ){//新版本的基石
        if( $.type( expr,"Function" ) ){ //注意在safari下,typeof nodeList的類型為function,因此必須使用$.type
            return  $.require( all+",ready", expr );
        }else{
            if( !$.fn )
                throw "node module is required!"
            return new $.fn.init( expr, context );
        }
    }
    //多版本共存
    if( typeof NsVal !== "function"){
        NsVal = $;//公用命名空間對象
        NsVal.uuid = 1;
    }
    if(NsVal.mass !== mass  ){
        NsVal[ mass ] = $;//保存當前版本的命名空間對象到公用命名空間對象上
        if(NsVal.mass || ($$ && $$.mass == null)) {
            postfix = ( mass + "" ).replace(/\D/g, "" ) ;//是否強制使用多庫共存
        }
    }else{
        return;
    }
    /**
     * 糅雜,為一個對象添加更多成員
     * @param {Object} receiver 接受者
     * @param {Object} supplier 提供者
     * @return  {Object} 目標對象
     */
    var has = Object.prototype.hasOwnProperty
    function mix( receiver, supplier ){
        var args = Array.apply([], arguments ),i = 1, key,//如果最后參數是布爾,判定是否覆寫同名屬性
        ride = typeof args[args.length - 1] == "boolean" ? args.pop() : true;
        if(args.length === 1){//處理$.mix(hash)的情形
            receiver = !this.window ? this : {} ;
            i = 0;
        }
        while((supplier = args[i++])){
            for ( key in supplier ) {//允許對象糅雜,用戶保證都是對象
                if ( has.call(supplier,key) && (ride || !(key in receiver))) {
                    receiver[ key ] = supplier[ key ];
                }
            }
        }
        return receiver;
    }

    mix( $, {//為此版本的命名空間對象添加成員
        html:  html,
        head:  head,
        mix:   mix,
        rword: /[^, ]+/g,
        mass:  mass,//大家都愛用類庫的名字儲存版本號,我也跟風了
        "@bind": W3C ? "addEventListener" : "attachEvent",
        //將內部對象掛到window下,此時可重命名,實現多庫共存  name String 新的命名空間
        exports: function( name ) {
            $$ && ( global.$ = $$ );//多庫共存
            name = name || $.config.nick;//取得當前簡短的命名空間
            $.config.nick = name;
            global[ NsKey ] = NsVal;
            return global[ name ]  = this;
        },
        /**
         * 數組化
         * @param {ArrayLike} nodes 要處理的類數組對象
         * @param {Number} start 可選。要抽取的片斷的起始下標。如果是負數,從后面取起
         * @param {Number} end  可選。規定從何處結束選取
         * @return {Array}
         */
        slice: function ( nodes, start, end ) {
            var ret = [], n = nodes.length;
            if(end === void 0 || typeof end == "number" && isFinite(end)){
                start = parseInt(start,10) || 0;
                end = end == void 0 ? n : parseInt(end, 10);
                if(start < 0){
                    start += n;
                }
                if(end > n){
                    end = n;
                }
                if(end < 0){
                    end += n;
                }
                for (var i = start; i < end; ++i) {
                    ret[i - start] = nodes[i];
                }
            }
            return ret;
        },
        /**
         * 用於取得數據的類型(一個參數的情況下)或判定數據的類型(兩個參數的情況下)
         * @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;
        },
        //$.log(str, showInPage=true, 5 )
        //level Number,通過它來過濾顯示到控制台的日志數量。0為最少,只顯示最致命的錯誤,
        //7則連普通的調試消息也打印出來。 顯示算法為 level <= $.config.level。
        //這個$.colre.level默認為9。下面是level各代表的含義。
        //0 EMERGENCY 致命錯誤,框架崩潰
        //1 ALERT 需要立即采取措施進行修復
        //2 CRITICAL 危急錯誤
        //3 ERROR 異常
        //4 WARNING 警告
        //5 NOTICE 通知用戶已經進行到方法
        //6 INFO 更一般化的通知
        //7 DEBUG 調試消息
        log: function (str){
            var show = true, page = false
            for(var i = 1 ; i < arguments.length; i++){
                var el = arguments[i]
                if(typeof el == "number"){
                    show = el <=  $.config.level
                }else if(el === true){
                    page = true;
                }
            }
            if(show){
                if( page === true ){
                    $.require( "ready", function(){
                        var div =  DOC.createElement( "pre" );
                        div.className = "mass_sys_log";
                        div.innerHTML = str +"";//確保為字符串
                        DOC.body.appendChild(div);
                    });
                }else if( global.console ){
                    global.console.log( str );
                }
            }
            return str
        },
        //主要用於建立一個從元素到數據的引用,具體用於數據緩存,事件綁定,元素去重
        getUid: global.getComputedStyle ? function( obj ){//IE9+,標准瀏覽器
            return obj.uniqueNumber || ( obj.uniqueNumber = NsVal.uuid++ );
        }: function( obj ){
            if(obj.nodeType !== 1){//如果是普通對象,文檔對象,window對象
                return obj.uniqueNumber || ( obj.uniqueNumber = NsVal.uuid++ );
            }//注:舊式IE的XML元素不能通過el.xxx = yyy 設置自定義屬性
            var uid = obj.getAttribute("uniqueNumber");
            if ( !uid ){
                uid = NsVal.uuid++;
                obj.setAttribute( "uniqueNumber", uid );
            }
            return +uid;//確保返回數字
        },
        /**
         * 生成鍵值統一的對象,用於高速化判定
         * @param {Array|String} array 如果是字符串,請用","或空格分開
         * @param {Number} val 可選,默認為1
         * @return {Object}
         */
        oneObject : function( array, val ){
            if( typeof array == "string" ){
                array = array.match( $.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;
        },
        config: function( settings ) {
            var kernel  = $.config;
            for ( var p in settings ) {
                if (!settings.hasOwnProperty( p ))
                    continue
                var prev = kernel[ p ];
                var curr = settings[ p ];
                if (prev && p === "alias") {
                    for (var c in curr) {
                        if (curr.hasOwnProperty( c )) {
                            var prevValue = prev[ c ];
                            var currValue = curr[ c ];
                            if( prevValue && prev !== curr ){
                                throw new Error(c + "不能重命名")
                            }
                            prev[ c ] = currValue;
                        }
                    }
                } else {
                    kernel[ p ] = curr;
                }
            }
            return this
        }
    });
    (function(scripts, cur){
        cur = scripts[ scripts.length - 1 ];
        var url = cur.hasAttribute ?  cur.src : cur.getAttribute( "src", 4 );
        url = url.replace(/[?#].*/, "");
        var a = cur.getAttribute("debug");
        var b = cur.getAttribute("storage");
        var kernel = $.config;
        kernel.debug = a == "true" || a == "1";
        kernel.storage = b == "true"|| b == "1";
        basepath =  kernel.base = url.substr( 0, url.lastIndexOf("/") ) +"/";
        kernel.nick = cur.getAttribute("nick") || "$";
        kernel.erase = cur.getAttribute("erase") || "erase";
        kernel.alias = {};
        kernel.level = 9;

    })(DOC.getElementsByTagName( "script" ));
    $.noop = $.error = function(){};

    "Boolean,Number,String,Function,Array,Date,RegExp,Window,Document,Arguments,NodeList".replace( $.rword, function( name ){
        class2type[ "[object " + name + "]" ] = name;
    });

    function parseURL(url, parent, ret){
        //[]里面,不是開頭的-要轉義,因此要用/^[-a-z0-9_$]{2,}$/i而不是/^[a-z0-9_-$]{2,}
        //別名至少兩個字符;不用漢字是避開字符集的問題
        if( /^(mass|ready)$/.test(url)){//特別處理ready標識符
            return [url, "js"];
        }
        if(/^[-a-z0-9_$]{2,}$/i.test(url) && $.config.alias[url] ){
            ret = $.config.alias[url];
        }else{
            parent = parent.substr( 0, parent.lastIndexOf('/') )
            if(/^(\w+)(\d)?:.*/.test(url)){  //如果用戶路徑包含協議
                ret = url
            }else {
                var tmp = url.charAt(0);
                if( tmp !== "." && tmp != "/"){  //相對於根路徑
                    ret = basepath + url;
                }else if(url.slice(0,2) == "./"){ //相對於兄弟路徑
                    ret = parent + url.substr(1);
                }else if( url.slice(0,2) == ".."){ //相對於父路徑
                    var arr = parent.replace(/\/$/,"").split("/");
                    tmp = url.replace(/\.\.\//g,function(){
                        arr.pop();
                        return "";
                    });
                    ret = arr.join("/")+"/"+tmp;
                }else if(tmp == "/"){
                    ret = parent  + url
                }else{
                    throw new Error("不符合模塊標識規則: "+url)
                }
            }
        }
        var ext = "js";
        tmp = ret.replace(/[?#].*/, "");
        if(/\.(\w+)$/.test( tmp )){
            ext = RegExp.$1;
        }
        if( ext!="css" &&tmp == ret && !/\.js$/.test(ret)){//如果沒有后綴名會補上.js
            ret += ".js";
        }
        return [ret, ext];
    }
  
    $.mix({
        //綁定事件(簡化版)
        bind: W3C ? function( el, type, fn, phase ){
            el.addEventListener( type, fn, !!phase );
            return fn;
        } : function( el, type, fn ){
            el.attachEvent && el.attachEvent( "on"+type, fn );
            return fn;
        },
        unbind: W3C ? function( el, type, fn, phase ){
            el.removeEventListener( type, fn || $.noop, !!phase );
        } : function( el, type, fn ){
            if ( el.detachEvent ) {
                el.detachEvent( "on" + type, fn || $.noop );
            }
        },
        //移除指定或所有本地儲存中的模塊
        erase : function( id, v ){
            if(id == void 0){
                Storage.clear();
            }else{
                var old = Storage.getItem( id+"_version" );
                if(old && (!v || v > Number(old)) ){
                    Storage.removeItem( id );
                    Storage.removeItem( id+"_deps" );
                    Storage.removeItem( id+"_parent" );
                    Storage.removeItem( id+"_version" );
                }
            }
        }
    });
    //================================localStorage===============================
    var Storage = $.oneObject("setItem,getItem,removeItem,clear",$.noop);
    if( global.localStorage){
        Storage = localStorage; 
    }else  if( html.addBehavior){
        html.addBehavior('#default#userData');
        html.save("massdata");
        //https://github.com/marcuswestin/store.js/issues/40#issuecomment-4617842
        //在IE67它對鍵名非常嚴格,不能有特殊字符,否則拋throwed an This name may not contain the '~' character: _key-->~<--
        var rstoragekey = new RegExp("[!\"#$%&'()*+,/\\\\:;<=>?@[\\]^`{|}~]", "g");
        function curry(fn) {
            return function(a, b) {
                html.load("massdata");
                a = String(a).replace(rstoragekey, function(w){
                    return w.charCodeAt(0);
                });
                var result = fn( a, b );
                html.save("massdata");
                return result
            }
        }
        Storage = {
            setItem : curry(function(key, val){
                html.setAttribute(key, val);
            }),
            getItem: curry(function(key){
                return html.getAttribute(key);
            }),
            removeItem: curry(function(key){
                html.removeAttribute(key);
            }),
            clear: function(){
                var attributes = html.XMLDocument.documentElement.attributes
                for (var i=0, attr; attr=attributes[i]; i++) {
                    html.removeAttribute(attr.name)
                }
            }
        }
    }
    var rerase = new RegExp("(?:^| )" + $.config.erase + "(?:(?:=([^;]*))|;|$)")
    var match = String(DOC.cookie).match( rerase );
    //讀取從后端過來的cookie指令,轉換成一個對象,鍵名為模塊的URL,值為版本號(這是一個時間戮)
    if(match && match[1]){
        try{
            var obj = eval("0,"+match[1]);
            for(var i in obj){//$.erase會版本號比現在小的模塊從本地儲存中刪掉
                $.erase(i, obj[i])
            }
        }catch(e){}
    }

    //============================加載系統===========================
    var modules = $.modules =  {
        ready:{ },
        mass: {
            state: 2,
            exports: $
        }
    };
    function getCurrentScript(){
        if(DOC.currentScript){
            return DOC.currentScript.src
        }
        var nodes = DOC.getElementsByTagName("script")
        for (var i = 0, node; node = nodes[i++];) {
            if (!node.pass && node.className == moduleClass && node.readyState === "interactive") {
                return  node.pass = node.src;
            }
        }
    }
    //檢測是否存在循環依賴
    function checkCycle( deps, nick ){
        for(var id in deps){
            if( deps[id] == "司徒正美" && modules[id].state != 2 &&( id == nick || checkCycle(modules[id].deps, nick))){
                return true;
            }
        }
    }
    //檢測此JS模塊的依賴是否都已安裝完畢,是則安裝自身
    function checkDeps(){
        loop:
        for ( var i = loadings.length, id; id = loadings[ --i ]; ) {
            var obj = modules[ id ], deps = obj.deps;
            for( var key in deps ){
                if( deps.hasOwnProperty( key ) && modules[ key ].state != 2 ){
                    continue loop;
                }
            }
            //如果deps是空對象或者其依賴的模塊的狀態都是2
            if( obj.state != 2){
                loadings.splice( i, 1 );//必須先移除再安裝,防止在IE下DOM樹建完后手動刷新頁面,會多次執行它
                fireFactory( obj.id, obj.args, obj.factory );
                checkDeps();
            }
        }
    }
    function checkFail( node, error ){
        var id = node.src;
        node.onload = node.onreadystatechange = node.onerror = null;
        if( error || !modules[ id ].state ){
            //注意,在IE通過!modules[ id ].state檢測可能不精確,這時立即移除節點會出錯
            setTimeout(function(){
                head.removeChild(node)
            }, error ? 0 : 1000 );
            $.log("加載 "+ id +" 失敗", 7);
        }else{
            return true;
        }
    }
    function loadJS( url ){
        var node = DOC.createElement("script")//, IE = node.uniqueID
        node.className = moduleClass;
        node[W3C ? "onload" : "onreadystatechange"] = function(){
            if(W3C || /loaded|complete/i.test(node.readyState) ){
                //mass Framework會在_checkFail把它上面的回調清掉,盡可能釋放回存,盡管DOM0事件寫法在IE6下GC無望
                var factory = parsings.pop() ;
                factory &&  factory.delay(node.src)
                if( checkFail(node) ){
                    $.log("已成功加載 "+node.src, 7);
                }
            }
        }
        node.onerror = function(){
            checkFail(node, true)
        }
        node.src = url 

            head.appendChild(node)

        $.log("正准備加載 "+node.src, 7)
    }
    function loadCSS(url){
        var id = url.replace(rmakeid,"");
        if (DOC.getElementById(id))
            return
        var node     =  DOC.createElement("link");
        node.rel     = "stylesheet";
        node.href    = url;
        node.id      = id;
        head.insertBefore( node, head.firstChild );
    }
    function loadStorage( id ){
        var factory =  Storage.getItem( id );
        if( $.config.storage && factory && !modules[id]){
            var parent = Storage.getItem(id+"_parent");
            var deps = Storage.getItem(id+"_deps");
            deps = deps ?  deps.match( $.rword ) : "";
            modules[ id ] ={
                id: id,
                parent: parent,
                exports: {},
                state: 1
            };
            require(deps, Function("return "+ factory )(), id) //0,1,2 --> 1,2,0
        }
    }

    //請求模塊(依賴列表,模塊工廠,加載失敗時觸發的回調)
    window.require = $.require = function( list, factory, parent ){
        var deps = {},  // 用於檢測它的依賴是否都為2
        args = [],      // 用於依賴列表中的模塊的返回值
        dn = 0,         // 需要安裝的模塊數
        cn = 0,         // 已安裝完的模塊數
        id = parent || "cb"+ ( cbi++ ).toString(32);
        parent = parent || basepath
        String(list).replace( $.rword, function(el){
            var array = parseURL(el, parent ),  url = array[0];
            if(array[1] == "js"){
                dn++
                loadStorage( id )
                if( !modules[ url ]  ){
                    modules[ url ] = {
                        id: url,
                        parent: parent,
                        exports: {}
                    };
                    loadJS( url );
                }else if( modules[ url ].state === 2 ){
                    cn++;
                }
                if( !deps[ url ] ){
                    args.push( url );
                    deps[ url ] = "司徒正美";//去重
                }
            }else if(array[1] === "css"){
                loadCSS( url );
            }
        });
        //創建或更新模塊的狀態
        modules[id] = {
            id: id,
            factory: factory,
            deps: deps,
            args: args,
            state: 1
        }
        if( dn === cn ){//如果需要安裝的等於已安裝好的
            fireFactory( id, args, factory );//裝配到框架中
            checkDeps();
            return
        }
        //在正常情況下模塊只能通過_checkDeps執行
        loadings.unshift( id );
    }
    //定義模塊
    var rcomment =  /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g
    window.define = $.define = function( id, deps, factory ){//模塊名,依賴列表,模塊本身
        var args = Array.apply([],arguments), _id
        if(typeof id == "string"){
            _id = args.shift();
        }
        if( typeof args[0] === "boolean" ){//用於文件合並, 在標准瀏覽器中跳過補丁模塊
            if( args[0] ){
                return;
            }
            args.shift()
        }
        if(typeof args[0] == "function"){
            args.unshift([]);
        }//上線合並后能直接得到模塊ID,否則尋找當前正在解析中的script節點的src作為模塊ID
        //但getCurrentScript方法只對IE6-10,FF4+有效,其他使用onload+delay閉包組合
        id = modules[id] && modules[id].state == 2 ? _id : getCurrentScript();
        factory = args[1];
        factory.id = _id;//用於調試
        factory.delay = function( id ){
            args.push( id );
            if( checkCycle(modules[id].deps, id)){
                throw new Error( id +"模塊與之前的某些模塊存在循環依賴")
            }
            if( $.config.storage && !Storage.getItem( id ) ){
                Storage.setItem( id, factory.toString().replace(rcomment,""));
                Storage.setItem( id+"_deps", args[0]+"");
                Storage.setItem( id+"_parent",  id);
                Storage.setItem( id+"_version", new Date - 0);
            }
            delete factory.delay;//釋放內存
            require.apply(null, args); //0,1,2 --> 1,2,0
        }
        if(id ){
            factory.delay(id,args)
        }else{//先進先出
            parsings.push( factory )
        }
    }
    $.require.amd = modules
    
    //從returns對象取得依賴列表中的各模塊的返回值,執行factory, 完成模塊的安裝
    function fireFactory( id, deps, factory ){
        for ( var i = 0, array = [], d; d = deps[i++]; ) {
            array.push( modules[ d ].exports );
        }
        var module = Object( modules[id] ), ret;
        ret =  factory.apply(global, array);
        module.state = 2;
        if( ret !== void 0 ){
            modules[ id ].exports = ret
        }
        return ret;
    }
    all.replace($.rword,function(a){
        $.config.alias[ "$"+a ] = basepath + a + ".js";
    });
    //domReady機制
    var readyFn, ready =  W3C ? "DOMContentLoaded" : "readystatechange" ;
    function fireReady(){
        modules.ready.state = 2;
        checkDeps();
        if( readyFn ){
            $.unbind( DOC, ready, readyFn );
        }
        fireReady = $.noop;//隋性函數,防止IE9二次調用_checkDeps
    };
    function doScrollCheck() {
        try {
            html.doScroll( "left" ) ;
            fireReady();
        } catch(e) {
            setTimeout( doScrollCheck, 31 );
        }
    };
    //在firefox3.6之前,不存在readyState屬性
    //http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
    if(DOC.readyState == null){
        DOC.readyState = "loading";
        var readyState = true;
    }
    if ( DOC.readyState === "complete" ) {
        fireReady();//如果在domReady之外加載
    }else {
        $.bind( DOC, ready, readyFn = function(){
            if ( W3C || DOC.readyState === "complete" ){
                fireReady();
                if(readyState){//IE下不能改寫DOC.readyState
                    DOC.readyState  = "complete";
                }
            }
        });
        if( html.doScroll && self.eval === parent.eval)
            doScrollCheck();
    }

    global.VBArray && ("abbr,article,aside,audio,bdi,canvas,data,datalist,details,figcaption,figure,footer," +
        "header,hgroup,mark,meter,nav,output,progress,section,summary,time,video").replace( $.rword, function( tag ){
        DOC.createElement(tag);
    });

    //https://developer.mozilla.org/en/DOM/window.onpopstate
    $.bind( global, "popstate", function(){
        NsKey = DOC.URL.replace(rmakeid,"");
        $.exports();
    });
    $.exports( $.config.nick +  postfix );//防止不同版本的命名空間沖突
/*combine modules*/

})( self, self.document );//為了方便在VS系列實現智能提示,把這里的this改成self或window

框架地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM