本版本最大的改進就引入強大的調試機制。如果一個框架使用了模塊加載后,迎來的最大問題莫過於調試。由於有了模塊化,因此不需要擔憂體積的問題,因此大放手腳伸入前端各個領域去,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上,歡迎試用。