循環相依性是所有語言的載入器都是大頭的問題,除非你的模組都是像spring那麼利用Ioc進行控制反轉,那麼出現循環相依性就是死結。對此,我們唯一能做的事是,在第一個循環相依性出現時就立即通知使用者查看它們的依賴列表。
在AMD大肆普及動態載入的好處後,大家應該隱藏知道有兩個方法幹這事。一個定義模組的define方法,另一個是載入模組的require方法。
define方法的參數為define(name?, deps? ,factroy), 換言之模組名與依賴列表都不是必須的,require參數為require(names , callback)。當我們用require一組模組,將它們的傳回值傳到回調中前,它實質上做了許多事情,其中為了實現跨域載入指令碼,我們使用了script標籤進行載入。但這不重點。script標籤載入回來的指令碼有個嚴格的格式,裡面就是一個define方法。這與JSONP非常相近。為了實現匿名模組,我們通常不使用第1個參數,但第2參數有時就不能省略。不過沒有問題,我們可以從請求的URL中還原回模組名,因此最終還是得到三個參數,第二個參數依賴列表不存在,我們就置為空白數組。當一個模組被載入回來,並且它的依賴列表為空白,我們就可以說它是可用的。若一個模組存在依賴列表,那麼當它的所有模組都可用時,它才可用。當兩個模組存在循環相依性時,就變成你等我,我等你的狀態,因此在每載入一個模組回來時,就檢測它的依賴列表,不斷回溯它的上級依賴,上上級依賴,但這些鏈中是否存在我們當前這個模組名。如果有就是循環相依性,立即拋錯。這個檢測我們也可以做些最佳化,這因為這是個圖,經過的路徑會非常複雜,我們可以跳過所有已發出請求但沒有載入回來的模組或所有可用的模組。
//這些是定義在種子模組(核心模組)中var loadings = []; var modules = {};function define(name, deps, factory){ var el = modules[name] = { deps:deps, name: name, factory:factory, status: !deps.length//是否可用 } if(!el.status){//踢進列隊進行檢測 loadings.push(el); } _checkDeps(); if( _checkCircle(deps, name) ){ throw new Error( name +"模組與之前的某些模組存在循環相依性") }}function _checkCircle(list, nick){//檢測是否存在循環相依性 for(var i = 0, name; name = list[ i++ ];){ var el = modules[name]; if(el ){ if( el.name == nick || el.deps.length && _checkCircle(el.deps, nick)){ return true; } } }}function _checkDeps(){//檢測模組有沒有載入成功 loop: for ( var i = loadings.length, el; el = loadings[ --i ]; ) { var deps = el.deps; var ok = true; for(var j = 0, name; name = deps[j++];){ if(!modules[name] || !modules[name].status){ ok = false; continue loop } } if(ok){ el.status = true; loadings.splice( i, 1 ); _checkDeps() } }}//這是a.js的內容define("a",["b"])//這是b.js的內容define("b",["c"])//這是c.js的內容define("c",["a"])//方便自己用firebug肉眼確認console.log(modules)console.log(loadings)
上面只是判定依賴關係的代碼,實質上模組載入系統非常複雜,分為拆分模組名,別名映射,轉換為URL,建立載入用的iframe與script(iframe是為了更好地在opera與舊式IE判定死鏈),處理載入回來define方法的最後一個參數factory,還原它模組名,判定是否可用,是否循環相依性,如果存在依賴再載入它的依賴等步驟,具體搜我部落格有關模組載入的其他文章。