關於javascript模組載入的思索2

來源:互聯網
上載者:User

經幾天思考,想到一個叫“檔案與模組”的問題。我們的模組肯定寫在一個JS檔案中,這些模組又可以分為核心模組與外圍模組。核心模組當然寫在主檔案中,它應該包含最重要的邏輯,載入器,列隊,命名空間構造器等等。但如果一個檔案只存在一個模組這也太浪費了,而且會導致請求法過多,因此出現多個模組“共生”於一個檔案的情況。在主檔案的那些非核心模組,我稱之為內圍模組。其他內圍與外圍沒有什麼區別,只是所在檔案不同而已。不地為了方便起見,內圍模組不要依賴外圍模組!

但我們用script標籤引用JS檔案時,它就嘩啦啦地執行裡面的指令碼,最主要的邏輯可以無所顧慮地得到解析。但對於內圍模組,它們的邏輯是放到一個函數體中,控制流程只能從它們上面掠過,觸摸不了它裡面的東西。這個模組名與回呼函數與相關的配置將進入一個處理函數(下文稱之為use),再放入一個處理列隊。如果存在依賴,則檢測相依模組所在的檔案有沒有載入,沒有就負載檔案,如果檔案已載入,則檢測此模組已裝配到架構的命名空間中,最後執行回呼函數。

從上面分析可知,這裡面的操作大體可分為幾類:檔案載入,模組裝配與執行回調,它們只能依次執行。綜觀大多數類庫架構,給出的解決方案就是這兩種:動態script插入與Ajax回調解析。

  • 動態script插入,就是產生一個script節點,設定其目標src,然後插入head節點中。之所以不用document.write,那是插入到body中,而且還有許多缺陷,具體參看我這篇文章。
  • Ajax回調解析,就是利用XMLHttp對象,將請求回來的responseText再全域解析。注意,是全域解析,要實現它就必須用到window.eval(標準瀏覽器)或window.execScript(IE),或者再搞一個script標籤進行解析。可見這方法需要處理許多相容問題,另搭上跨域問題……。

我的立場很明顯了,使用第一種。但script標籤關於回調的處理還是有許多問題。

                    var script = dom.genScriptNode();                                 script.src = url                    dom.head().appendChild(script);                          script.onload = script.onreadystatechange = function(){                        if ((!this.readyState) || this.readyState == "loaded" || this.readyState == "complete" ){                            if(!dom.done[name]){                                alert("載入失敗1")                                dom.head().removeChild(script)                            }                             callback();                        }                    }                    script.onerror = function(){                        script.onload = script.onerror = undefined;                        alert("載入失敗2")                        dom.head().removeChild(script)                    }

如果我們的script標籤所引用的JS檔案不存在時,在一些標準瀏覽器下,會觸發其onerror事件,但在IE下由於沒有onload事件與onerror事件,我們不能判定是已載入成功,我們只有假設如果成功載入目標檔案,dom.done.moduleName為true,如果失敗,當然為undefined,進行!dom.done[name]為true,從而移除這個無用的script標籤。這方法理應很完美,相容IE與標準瀏覽器,可惜標準瀏覽器並不是石頭一塊,它們還是有差異。可恨的opera會在載入失敗時拋出一個致命錯誤,這個連try catch也無回天之力了。因此這個url一定要絕對正確,為此我們要引入真實url機制。

無論是dojo,還是JSAN(早些年最負盛名的模組載入架構),或是YUI,更不用說using.js、require.js、packages.js等小眾的類庫,它們都擁有一種將模組名(包名)轉換為url的機制。如:

"query"====>"http://localhost:3000/javascripts/dom/query.js"

http://localhost:3000/javascripts/我稱之為basePath,它是核心模組所在的JS檔案的路徑,dom是強制添加的,所有外圍模組檔案必須在此,query為模組名。取JS檔案路徑的方法可參看我這一篇博文。為了應該極端情況,有時我們不得不放棄此遊戲規則,架構就無法找到正確的url了,這時我們顯式地指出其路徑,方法是在模組名添加一個小括弧,裡面就是其真實url。

      var module = "dom."+item,url;      //處理dom.node(http://www.cnblogs.com/rubylouvre/dom/node.js)的情形      var _u = module.match(/\(([^)]+)\)/);      url = _u && _u[1] ? _u[1] : dom.getBasePath()+"/"+ module.replace(/\./g, "/") + ".js";      var script = dom.genScriptNode();      script.src = url      dom.head().appendChild(script);      var scope = dom.namespace(module,true)      //..........

因為模組與回呼函數,在我的構思中都是同一個坯子出來的,它們都是同一個方法的回呼函數。我把此方法命名為use,不過兼職YUI3的add與use的職責。比如,這是一個外圍模組query:

//位於單獨檔案/dom/query.js中dom.use("query",function(){    arguments.callee._attached = true;    dom.query = function(selector,context){        context = context || document        try{            var els = context.querySelectorAll(selector);            return dom.filter(els,function(el){                return el.nodeType === 1            })        }catch(e){            alert("你的瀏覽器不支援querySelectorAll")        }    }},{    use:["collection"]});

它依賴於另一個外圍模組collection:

//位於單獨檔案/dom/collection.js中dom.use("collection",function(){    arguments.callee._attached = true;    dom.filter = function(array, fn, scope){        var result = [],ri = 0;        for (var i = 0,n = array.length; i < n; i++){            if(fn.call(scope || array[i],array[i],i,array)){                result[ ri++] = array[i];            }        }        return result;    }    dom.each = function(){/**/}    dom.map = function(){/**/}    dom.keys = function(){/**/}//.....})

在網頁中這樣調用:

      dom.ready(function(){        dom.use("query", function(){          var els =dom.query("p")          alert(els)        });      });

如何區分二者,因為回呼函數是無窮盡地調用,而模組則不可以,否則可能修改了一些重要的配置,它們只能執行一次。我們需要用一些東西來標識它是模組。下面是我想到的一個方法:

dom.use("collection",function(){    arguments.callee._attached = true;    dom.filter = function(){/**/}    dom.each = function(){/**/}    dom.map = function(){/**/}    dom.keys = function(){/**/}//.....})

那麼當這個函數執行一次,它就有一個靜態屬性,如果下次它又出現在列隊,我們檢測它而跳過:

                if(!fn._attached){//如果是模組則只會執行一次                    fn();                }

對於檔案也是這樣,如果此JS檔案已經載入過,我們就不用再載入了,因此我們可以使用一個hash來存放此訊息。

dom.loaded.collection = true;dom.use("collection",function(){    arguments.callee._attached = true;    dom.filter = function(){/**/}    dom.each = function(){/**/}    dom.map = function(){/**/}    dom.keys = function(){/**/}//.....})

基本上就是這樣。我最後回顧一些概念吧。核心模組,架構的重要組成部分,它當然不位於use函數中,相反,use函數,處理列隊,特徵偵測等重要的東西都是它的組成部分。內圍模組,它與核心模組是位於同一個JS檔案中,它不應依賴於外圍模組。外圍模組,它可以依賴於其他外圍模組,由於它肯定是用核心模組與內圍模組的東西組建而成,在這些東西在外圍載入之時已經存在了,因此我們不需要再寫出這些內部依賴。只需列出那些外圍模組即可,因為它們所在的檔案是否已載入還是未知數。處理列隊,只是一個普通的數組,它裡面的元素可以是模組名,模組本身與回呼函數。完。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.