我的模塊加載系統 v16


本版本最大的改進就引入強大的調試機制。如果一個框架使用了模塊加載后,迎來的最大問題莫過於調試。由於有了模塊化,因此不需要擔憂體積的問題,因此大放手腳伸入前端各個領域去,JS文件暴漲,也意味着API數量瀑漲,就像jQuery那一點兒API有的人都記不全,更別說像EXT,YUI,dojo這樣的巨無霸了。對於這個方法是在A模塊還是B模塊,我們在調用時還可以查一查,但出錯時,就未必出A模塊或B模塊內,A模塊還可能依賴於C模塊與D模塊,D模塊還有依賴,這樣一級級下去,我們很難追溯到出錯的源頭。特別是,如果這個JS文件是動態加載的,然后又刪掉了,連firebug也查不了!因此強化調試機制勢在必行,這也是本版本最大的亮點!

本版本的其他改進:

  • 升級UUID系統,以便頁面出現多個版本共存時,讓它們共享一個計數器。
  • 簡化_checkFail方法,如果出現死鏈接,直接打印模塊名便是,不用再放入錯誤棧中了。
  • 簡化deferred列隊,統一先進先出。
  • 改進$.mix方法,允許只存在一個參數,直接將屬性添加到$命名空間上。
  • 內部方法assemble更名為setup,並強化調試機制,每加入一個新模塊, 都會遍歷命名空間與原型上的方法,重寫它們,添加try catch邏輯。

好了,我們再回到增強調試機制的話題上。

在mass Framework種子模塊的require方法中這樣一段代碼:

            if( dn === cn ){//在依賴都已執行過或沒有依賴的情況下
                if( token && !( token in transfer ) ){
                    mapper[ token ].state = 2 //如果是使用合並方式,模塊會跑進此分支(只會執行一次)
                    return transfer[ token ] = setup( callback, args,token );
                }else if( !token ){//普通的回調可執行無數次
                    return setup( callback, args,token );
                }
            }

反正如果某個JS文件沒有死鏈,它對應的模塊就肯定進入setup方法。setup 方法有三個參數,第一個模塊名,字符串。第二個是它的依賴列表,一個字符串數組。最后一個是函數,可以是模塊自身,也可以是用戶的回調。情況與$.define方法一模一樣,不同的是,這模塊可能已經被修改過,加了文件夾名。

    //收集依賴列表對應模塊的返回值,傳入目標模塊中執行
    function setup( name, deps, fn ){
        for ( var i = 0,argv = [], d; d = deps[i++]; ) {
            argv.push( transfer[d] );
        }
        var ret = fn.apply( global, argv );
        if($["@debug"]){//如果打開調試機制
            for( i in $){
                debug($, i, name);
            }
            for( i in $.fn){
                debug($.fn, i, name,1);
            }
        }
        return ret;
    }

setup的用法在注釋中已說很清楚了,就行執行模塊自身,以便為命名空間與mass的原型添加新的方法或屬性。比如lang模塊,它本身是沒有依賴(特指標准瀏覽器下,IE下還要加lang_fix模塊),它的模塊函數執行后,$命名空間就多出isPlainObject, isNative, isEmptyObject, isArrayLike, format, tag, quote, dump, parseJS, parseJSON, parseXML, each, map ,isFunction, isArray, lang這些方法。並且返回$.lang這個方法,它會添加到內部的transfer對象,鍵名為其對應的模塊。因此你看到setup方法存在transfer[d]這樣的語句。

如果已打開調試機制(默認是打開的),它就會新加入模塊的方法進行重寫,這個由另一個內部方法去完成。

    var rdebug =  /^(init|constructor|lang|query)$|^is/
    function debug(obj, name, module, p){
        var fn = obj[name];
        if(  typeof fn == "function" && !fn["@debug"]){
            if( rdebug.test( name )){
                fn["@debug"] = name;
            }else{
                var method = obj[name] = function(){
                    try{
                        return  method["@debug"].apply(this,arguments)
                    }catch(e){
                        $.log("[[ "+module+"::"+(p? "$.fn." :"$.")+name+" ]] gone wrong");
                        $.log(e);
                        throw e;
                    }
                }
                for(var i in fn){
                   method[i] = fn[i];
                }
                method["@debug"] = fn;
                method.toString = function(){
                    return fn.toString()
                }
                method.valueOf = function(){
                    return fn.valueOf();
                }
            }
        }
    }

此方法目的是重寫原方法,但為了節約性能,沒有使用curry,而是將原方法放到新方法的一個叫“@debug”的屬性上,使用在try catch中小心地調用它。如果出錯就把預先寫好的調試消息打印出來。為了防止此方法人工添加過各種自定義屬性,我們有必要把這些私有屬性再for in 一下,轉移到新方法上。但for in在IE678下有BUG,因此還需要單獨處理一下valueOf與toString方法。此外,對於一些核心方法與調用異常頻繁的方法就不重寫了, 如isXXX系列,init構造器,query選擇器,lang語言鏈……基本上它們都有足夠強悍的代碼防御,用不着多此一舉!

說了這么多理論,讓我們看看實際效果。比如說我們想調用lang模塊的format方法,它有兩種傳參方式,但無論哪一種,第一個參數總是字符串!

            $.require("ready,lang",function(){
                var a = $.format("Result is #{0},#{1}", 22,33);
                alert(a);//"Result is 22,33"
            })

但你如果誤傳了一個數組進去,立馬報錯,在各瀏覽器顯示如下(這是未開啟調試機制的情況)

            $.require("ready,lang",function(){
                $.format([]);
            })

除了opera可以看出一些眉目外,其他瀏覽器的調試消息基本沒有用。現在只有一行代碼,我們當然猜得出是format出了問題,如果多幾行就慘了。

我們再看看打開調試機制之后的樣子,它就比原來多一行有用的消息。

@lang表示它所在的模塊,按照mass Framework的文件名與模塊名一一對應的機制,不難知道是lang.js這個文件有錯,如果是@more/aaa,則是more目錄下的aaa.js文件有錯。$.format就是出錯的方法名,這個用IDE定位一下立即就找到了,然后根據瀏覽器原有消息與方法源碼,應該很快就找到原因。

//mass Framework的模塊加載,創建元素,插入元素,綁定事件
            $.require("ready,event",function(){
               $("body").append("<div>新節點</div>").on("click","div",function(){
                   alert(this.innerHTML)
               })
            })

基本上就是這樣,源碼放在github上,歡迎試用。



免責聲明!

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



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