<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                > 蒙惠者雖知其然,而未必知其所以然也。 寫了這么多,必須證明一下本書并不是一份乏味的使用文檔,我們來深入看看Sea.js,搞清楚它時如何工作的吧! ## CMD規范 要想了解Sea.js的運作機制,就不得不先了解其CMD規范。 Sea.js采用了和Node相似的CMD規范,我覺得它們應該是一樣的。使用require、exports和module來組織模塊。但Sea.js比起Node的不同點在于,前者的運行環境是在瀏覽器中,這就導致A依賴的B模塊不能同步地讀取過來,所以Sea.js比起Node,除了運行之外,還提供了兩個額外的東西: 1. 模塊的管理 2. 模塊從服務端的同步 即Sea.js必須分為模塊加載期和執行期。加載期需要將執行期所有用到的模塊從服務端同步過來,在再執行期按照代碼的邏輯順序解析執行模塊。本身執行期與node的運行期沒什么區別。 所以Sea.js需要三個接口: 1. define用來wrapper模塊,指明依賴,同步依賴; 2. use用來啟動加載期; 3. require關鍵字,實際上是執行期的橋梁。 > 并不太喜歡Sea.js的use API,因為其回調函數并沒有使用與Define一樣的參數列表。 #### 模塊標識(id) 模塊id的標準參考[Module Identifiers](http://wiki.commonjs.org/wiki/Modules/1.1.1#Module_Identifiers),簡單說來就是作為一個模塊的唯一標識。 出于學習的目的,我將它們翻譯引用在這里: 1. 模塊標識由數個被斜杠(/)隔開的詞項組成; 2. 每次詞項必須是小寫的標識、“.”或“..”; 3. 模塊標識并不是必須有像“.js”這樣的文件擴展名; 4. 模塊標識不是相對的,就是頂級的。相對的模塊標識開頭要么是“.”,要么是“..”; 5. 頂級標識根據模塊系統的基礎路徑來解析; 6. 相對的模塊標識被解釋為相對于某模塊的標識,“require”語句是寫在這個模塊中,并在這個模塊中調用的。 #### 模塊(factory) 顧名思義,factory就是工廠,一個可以產生模塊的工廠。node中的工廠就是新的運行時,而在Sea.js中(Tea.js中也同樣),factory就是一個函數。這個函數接受三個參數。 ~~~ function (require, exports, module) { // here is module body } ~~~ 在整個運行時中只有模塊,即只有factory。 #### 依賴(dependencies) 依賴就是一個id的數組,即模塊所依賴模塊的標識。 ## 依賴加載的原理 有很多語言都有模塊化的結構,比如c/c++的`#include`語句,Ruby的`require`語句等等。模塊的執行,必然需要其依賴的模塊準備就緒才能順利執行。 c/c++是編譯語言,在預編譯時,替換`#include`語句,將依賴的文件內容包含進來,在編譯后的執行期,所有的模塊才會開始執行; 而Ruby是解釋型語言,在模塊執行前,并不知道它依賴什么模塊,待到執行到`require`語句時,執行將暫停,從外部讀取并執行依賴,然后再回來繼續執行當前模塊。 JavaScript作為一門解釋型語言,在復雜的瀏覽器環境中,Sea.js是如何處理CMD模塊間的依賴的呢? ## node的方式-同步的`require` 想要解釋這個問題,我們還是從Node模塊說起,node于Ruby類似,用我們之前使用過的一個模塊作為例子: ~~~ // File: usegreet.js var greet = require("./greet"); greet.helloJavaScript(); ~~~ 當我們使用`node usegreet.js`來運行這個模塊時,實際上node會構建一個運行的上下文,在這個上下文中運行這個模塊。運行到`require('./greet')`這句話時,會通過注入的API,在新的上下文中解析greet.js這個模塊,然后通過注入的`exports`或`module`這兩個關鍵字獲取該模塊的接口,將接口暴露出來給usegreet.js使用,即通過`greet`這個對象來引用這些接口。例如,`helloJavaScript`這個函數。詳細細節可以參看node源碼中的[module.js](https://github.com/joyent/node/blob/master/lib/module.js)。 node的模塊方案的特點如下: 1. 使用require、exports和module作為模塊化組織的關鍵字; 2. 每個模塊只加載一次,作為單例存在于內存中,每次require時使用的是它的接口; 3. require是同步的,通俗地講,就是node運行A模塊,發現需要B模塊,會停止運行A模塊,把B模塊加載好,獲取的B的接口,才繼續運行A模塊。如果B模塊已經加載到內存中了,當然require B可以直接使用B的接口,否則會通過fs模塊化同步地將B文件內存,開啟新的上下文解析B模塊,獲取B的API。 實際上node如果通過fs異步的讀取文件的話,require也可以是異步的,所以曾經node中有require.async這個API。 ## Sea.js的方式-加載期與執行期 由于在瀏覽器端,采用與node同樣的依賴加載方式是不可行的,因為依賴只有在執行期才能知道,但是此時在瀏覽器端,我們無法像node一樣直接同步地讀取一個依賴文件并執行!我們只能采用異步的方式。于是Sea.js的做法是,分成兩個時期——加載期和執行期; > 的確,我們可以使用同步的XHR從服務端加載依賴,但是本身就是單進程的JavaScript還需要等待文件的加載,那性能將大打折扣。 * **加載期**:即在執行一個模塊之前,將其直接或間接依賴的模塊從服務器端同步到瀏覽器端; * **執行期**:在確認該模塊直接或間接依賴的模塊都加載完畢之后,執行該模塊。 ### 加載期 不難想見,模塊間的依賴就像一棵樹。啟動模塊作為根節點,依賴模塊作為葉子節點。下面是pixelegos的依賴樹: ![loadingperiod](https://raw.github.com/island205/HelloSea.js/master/images/loadingperiod.png) 如上圖,在頁面中通過`seajs.use('/js/pixelegos')`調用,目的是執行pixelegos這個模塊。Sea.js并不知道pixelegos還依賴于其他什么模塊,只是到服務端加載pixelegos.js,將其加載到瀏覽器端之后,通過分析發現它還依賴于其他的模塊,于是Sea.js又去加載其他的模塊。隨著更多的模塊同步到瀏覽器端后,一棵依賴樹才慢慢地通過遞歸顯現出來。 > 那Sea.js如何確定pixelegos所有依賴的模塊都加載好了呢? 從依賴樹中可以看出,如果pixelegos.js所依賴的直接子節點加載好了(此種加載好,即為節點和其依賴的子節點都加載好),那就表示它就加載好了,于是就可以啟動該模塊。明顯,這種加載完成的過程也是一個遞歸的過程。 從最底層的葉子節點開始(例如undercore),由于沒有再依賴于其他模塊,所以它從服務端同步過來之后,就加載好了。然后開始詢問其父節點backbone是否已經加載好了,即詢問backbone所依賴的所有節點都加載好了。同理對于pixelegos模塊,其子節點menu、tool、canvas都會詢問pixelegos其子節點加載好了沒有。 如果三個依賴都已loading完畢,則pixelgos也加載完成,即其整棵依賴樹都加載好了,然后就可以啟動pixelegos這個模塊了。 ### 執行期 在執行期,執行也是從根節點開始,本質上是按照代碼的順序結構,對整棵樹進行了遍歷。有的模塊可能已經EXECUTED,而有的還需要執行獲取其exports。由于在執行期時,所有依賴的模塊都加載好了,所以與node執行過程有點類似。 pixelegos通過同步的require函數獲取tool、canvas和menu,后三者同樣通過require來執行各自的依賴模塊,于是通過這樣一個遞歸的過程,pixelegos就執行完畢了。 ## 打包模塊的加載過程 在Sea.js中,為了支持模塊的combo,存在一個js文件包含多個模塊的情況。根據依賴情況,使用grunt-cmd-concat可以將一個模塊以及其依賴的子模塊打包成一個js文件。 打包的方式有三種,self,relative和all。 * self,只是自己做了transport * relative,將多有相對路徑的模塊transport,concat * all,包括相對路徑模塊和庫模塊(即在`seajs-modules`文件夾中的),transport,concat 例如,我們`seajs.use('/dist/pixelegos')`,解析為需要加載`http://127.0.0.1:81/dist/pixelegos.js`這個文件,且這個文件是all全打包的。其加載過程如下: #### 加載方式 1. 在use時,定義一個匿名的`use_`模塊,依賴于`/dist/pixelegos`模塊,匿名的`use_`模塊`load`依賴,開始加載`http://127.0.0.1:81/dist/pixelegos.js`模塊; 2. `http://127.0.0.1:81/dist/pixelegos.js`加載執行,所有打包在里面的模塊被`define`; 3. `http://127.0.0.1:81/dist/pixelegos.js`的`onload`回調執行,調用`/dist/pixelegos`模塊的load,加載其依賴模塊,但依賴的模塊都加載好了; 4. 通知匿名的`use_`加載完成,開始執行期。 針對每一次執行期,對應的加載依賴樹與整個模塊依賴樹是有區別的,因為子模塊已經加載好了的模塊,并不在加載樹中。 ## Sea.js的實現 ### 模塊的狀態 由于瀏覽器端與Node的環境差異,模塊存在加載期和執行期,所以Sea.js中為模塊定義了六種狀態。 ~~~ var STATUS = Module.STATUS = { // 1 - The `module.uri` is being fetched FETCHING: 1, // 2 - The meta data has been saved to cachedMods SAVED: 2, // 3 - The `module.dependencies` are being loaded LOADING: 3, // 4 - The module are ready to execute LOADED: 4, // 5 - The module is being executed EXECUTING: 5, // 6 - The `module.exports` is available EXECUTED: 6 } ~~~ 分別為: * FETCHING:開始從服務端加載模塊 * SAVED:模塊加載完成 * LOADING:加載依賴模塊中 * LOADED:依賴模塊加載完成 * EXECUTING:模塊執行中 * EXECUTED:模塊執行完成 [module.js](https://github.com/seajs/seajs/blob/master/src/module.js)是Sea.js的核心,我們來看一下,module.js是如何控制模塊加載過程的。 ### 如何確定整個依賴樹加載好了呢? 1. 定義A模塊,如果有模塊依賴于A,把該模塊加入到等待A的模塊隊列中; 2. 加載A模塊,狀態變為FETCHING 3. A加載完成,獲取A模塊依賴的BCDEFG模塊,發現B模塊沒有定義,而C加載中,D自己已加載好,E加載子模塊中,F加載完成,運行中,G已經解析好,SAVED; 4. 由于FG本身以及子模塊都已加載好,因此A模塊要確定已經加載好了,必須等待BCDE加載好;開始加載必須的子模塊,LOADING; 5. 針對B重復步驟1; 6. 將A加入到CDE的等待隊列中; 7. BCDE加載好之后都會從自己的等待隊列中取出等待自己加載好的模塊,通知A自己已經加載好了; 8. A每次收到子模塊加載好的通知,都看一遍自己依賴的模塊是否狀態都變成了加載完成,如果加載完成,則A加載完成,A通知其等待隊列中的模塊自己已加載完成,LOADED; ### 加載過程 * Sea.use調用Module.use構造一個沒有factory的模塊,該模塊即為這個運行期的根節點。 ~~~ // Use function is equal to load a anonymous module Module.use = function (ids, callback, uri) { var mod = Module.get(uri, isArray(ids) ? ids: [ids]) mod.callback = function () { var exports = [] var uris = mod.resolve() for (var i = 0, len = uris.length; i < len; i++) { exports[i] = cachedMods[uris[i]].exec() } if (callback) { callback.apply(global, exports) } delete mod.callback } mod.load() } ~~~ 模塊構造完成,則調用mod.load()來同步其子模塊;直接跳過fetching這一步;mod.callback也是Sea.js不純粹的一點,在模塊加載完成后,會調用這個callback。 * 在load方法中,獲取子模塊,加載子模塊,在子模塊加載完成后,會觸發mod.onload(): ~~~ // Load module.dependencies and fire onload when all done Module.prototype.load = function () { var mod = this // If the module is being loaded, just wait it onload call if (mod.status >= STATUS.LOADING) { return } mod.status = STATUS.LOADING // Emit `load` event for plugins such as combo plugin var uris = mod.resolve() emit("load", uris) var len = mod._remain = uris.length var m // Initialize modules and register waitings for (var i = 0; i < len; i++) { m = Module.get(uris[i]) if (m.status < STATUS.LOADED) { // Maybe duplicate m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1 } else { mod._remain-- } } if (mod._remain === 0) { mod.onload() return } // Begin parallel loading var requestCache = {} for (i = 0; i < len; i++) { m = cachedMods[uris[i]] if (m.status < STATUS.FETCHING) { m.fetch(requestCache) } else if (m.status === STATUS.SAVED) { m.load() } } // Send all requests at last to avoid cache bug in IE6-9\. Issues#808 for (var requestUri in requestCache) { if (requestCache.hasOwnProperty(requestUri)) { requestCache[requestUri]() } } } ~~~ 模塊的狀態是最關鍵的,模塊狀態的流轉決定了加載的行為; * 是否觸發onload是由模塊的_remian屬性來確定,在load和子模塊的onload函數中都對_remain進行了計算,如果為0,則表示模塊加載完成,調用onload: ~~~ // Call this method when module is loaded Module.prototype.onload = function () { var mod = this mod.status = STATUS.LOADED if (mod.callback) { mod.callback() } // Notify waiting modules to fire onload var waitings = mod._waitings var uri, m for (uri in waitings) { if (waitings.hasOwnProperty(uri)) { m = cachedMods[uri] m._remain -= waitings[uri] if (m._remain === 0) { m.onload() } } } // Reduce memory taken delete mod._waitings delete mod._remain } ~~~ 模塊的_remain和_waitings是兩個非常關鍵的屬性,子模塊通過_waitings獲得父模塊,通過_remain來判斷模塊是否加載完成。 * 當這個沒有factory的根模塊觸發onload之后,會調用其方法callback,callback是這樣的: ~~~ mod.callback = function () { var exports = [] var uris = mod.resolve() for (var i = 0, len = uris.length; i < len; i++) { exports[i] = cachedMods[uris[i]].exec() } if (callback) { callback.apply(global, exports) } delete mod.callback } ~~~ 這預示著加載期結束,開始執行期; * 而執行期相對比較無腦,首先是直接調用根模塊依賴模塊的exec方法獲取其exports,用它們來調用use傳經來的callback。而子模塊在執行時,都是按照標準的模塊解析方式執行的: ~~~ // Execute a module Module.prototype.exec = function () { var mod = this // When module is executed, DO NOT execute it again. When module // is being executed, just return `module.exports` too, for avoiding // circularly calling if (mod.status >= STATUS.EXECUTING) { return mod.exports } mod.status = STATUS.EXECUTING // Create require var uri = mod.uri function require(id) { return Module.get(require.resolve(id)).exec() } require.resolve = function (id) { return Module.resolve(id, uri) } require.async = function (ids, callback) { Module.use(ids, callback, uri + "_async_" + cid()) return require } // Exec factory var factory = mod.factory var exports = isFunction(factory) ? factory(require, mod.exports = {}, mod) : factory if (exports === undefined) { exports = mod.exports } // Emit `error` event if (exports === null && ! IS_CSS_RE.test(uri)) { emit("error", mod) } // Reduce memory leak delete mod.factory mod.exports = exports mod.status = STATUS.EXECUTED // Emit `exec` event emit("exec", mod) return exports } ~~~ > 看到這一行代碼了么?`var exports = isFunction(factory) ? factory(require, mod.exports = {}, mod) : factory`?真的,整個Sea.js就是為了這行代碼能夠完美運行 ### 資源定位 資源定位是Sea.js,或者說模塊加載器中非常關鍵部分。那什么是資源定位呢? 資源定位與模塊標識相關,而在Sea.js中有三種模塊標識。 #### 普通路徑 普通路徑與網頁中超鏈接一樣,相對于當前頁面解析,在Sea.js中,普通路徑包有以下幾種: ~~~ // 假設當前頁面是 http://example.com/path/to/page/index.html // 絕對路徑是普通路徑: require.resolve('http://cdn.com/js/a'); // => http://cdn.com/js/a.js // 根路徑是普通路徑: require.resolve('/js/b'); // => http://example.com/js/b.js // use 中的相對路徑始終是普通路徑: seajs.use('./c'); // => 加載的是 http://example.com/path/to/page/c.js seajs.use('../d'); // => 加載的是 http://example.com/path/to/d.js ~~~ #### 相對標識 在define的factory中的相對路徑(`..`?`.`)是相對標識,相對標識相對當前的URI來解析。 ~~~ // File http://example.com/js/b.js define(function(require) { var a = require('./a'); a.doSomething(); }); // => 加載的是http://example.com/js/a.js ~~~ 這與node模塊中相對路徑的解析一致。 #### 頂級標識 不以`.`或者'/'開頭的模塊標識是頂級標識,相對于Sea.js的base路徑來解析。 ~~~ // 假設 base 路徑是:http://example.com/assets/ // 在模塊代碼里: require.resolve('gallery/jquery/1.9.1/jquery'); // => http://example.com/assets/gallery/jquery/1.9.1/jquery.js ~~~ > 在node中即是在paths中搜索模塊(node_modules文件夾中)。 #### 模塊定位小演 使用seajs.use啟動模塊,如果不是頂級標識或者是絕對路徑,就是相對于頁面定位;如果是頂級標識,就從Sea.js的模塊系統中加載(即base);如果是絕對路徑,直接加載; 之后的模塊加載都是在define的factory中,如果是相對路徑,就是相對標識,相對當前模塊路徑加載;如果是絕對路徑,直接加載; 由此可見,在Sea.js中,模塊的配置被分割成2+x個地方: * 與頁面放在一起; * 與Sea.js放在一起; * 通過絕對路徑添加更多的模塊源。 > 由此可見,Sea.js確實海納百川。 ### 獲取真實的加載路徑 1.在Sea.js中,使用data.cwd來代表當前頁面的目錄,如果當前頁面地址為`http://www.dianping.com/promo/195800`,則cwd為`http://www.dianping.com/promo/`;使用data.base來代表sea.js的加載地址,如果sea.js的路徑為`http://i1.dpfile.com/lib/1.0.0/sea.js`,則base為`http://i1.dpfile.com/lib/`。 > [“當 sea.js 的訪問路徑中含有版本號或其他東西時,base 不會包含 seajs/x.y.z 字串。 當 sea.js 有多個版本時,這樣會很方便”](https://github.com/seajs/seajs/issues/258)。看到這一句,我凌亂了,這Sea.js是多么的人性化!但是我覺得這似乎沒有必要。 2.seajs.use是,除了絕對路徑,其他都是相對于cwd定位,即如果模塊標識為: * './a',則真實加載路徑為[http://www.dianping.com/promo/a.js;](http://www.dianping.com/promo/a.js%EF%BC%9B) * '/a',則為[http://www.dianping.com/a.js;](http://www.dianping.com/a.js%EF%BC%9B) * '../a',則為[http://www.dianping.com/a.js;](http://www.dianping.com/a.js%EF%BC%9B) > 從需求上看,相對頁面地址定位在現實生活中并不太適用,如果頁面地址或者靜態文件的路徑稍微變化下,就跪了。 如果模塊標識為絕對路徑: * '[https://a.alipayobjects.com/ar/a',則加載路徑就是'https://a.alipayobjects.com/ar/a.js'。](https://a.alipayobjects.com/ar/a'%EF%BC%8C%E5%88%99%E5%8A%A0%E8%BD%BD%E8%B7%AF%E5%BE%84%E5%B0%B1%E6%98%AF'https://a.alipayobjects.com/ar/a.js'%E3%80%82) 如果模塊標識是頂級標識,就基于base來加載: * 'jquery',則加載路徑為'[http://i1.dpfile.com/lib/jquery.js'。](http://i1.dpfile.com/lib/jquery.js'%E3%80%82) 3.除此之外,就是factory中的模塊標識了: * '[https://a.alipayobjects.com/ar/b',加載路徑為'https://a.alipayobjects.com/ar/b.js](https://a.alipayobjects.com/ar/b'%EF%BC%8C%E5%8A%A0%E8%BD%BD%E8%B7%AF%E5%BE%84%E4%B8%BA'https://a.alipayobjects.com/ar/b.js)' * '/c',加載路徑為'[http://www.dianping.com/c.js';](http://www.dianping.com/c.js'%EF%BC%9B) * './d',如果父模塊的加載路徑是'[http://i1.dpfile.com/lib/e.js',則加載路徑為'http://i1.dpfile.com/lib/d.js](http://i1.dpfile.com/lib/e.js'%EF%BC%8C%E5%88%99%E5%8A%A0%E8%BD%BD%E8%B7%AF%E5%BE%84%E4%B8%BA'http://i1.dpfile.com/lib/d.js)' > 在模塊系統中使用'/c'絕對路徑是什么意思?Sea.js會將其解析為相對頁面的模塊,有點牛馬不相及的感覺。 #### factory的依賴分析 在Sea.js的API中,`define(factory)`,并沒有指明模塊的依賴項,那Sea.js是如何獲得的呢? 這段是Sea.js的源碼: ~~~ /** * util-deps.js - The parser for dependencies * ref: tests/research/parse-dependencies/test.html */ var REQUIRE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g var SLASH_RE = /\\\\/g function parseDependencies(code) { var ret = [] code.replace(SLASH_RE, "") .replace(REQUIRE_RE, function(m, m1, m2) { if (m2) { ret.push(m2) } }) return ret } ~~~ `REQUIRE_RE`這個碩大無比的正則就是關鍵。推薦使用[regexper](http://www.regexper.com/)來看看這個正則表達式。非native的函數factory我們可以通過的toString()方法獲取源碼,Sea.js就是使用`REQUIRE_RE`在factory的源碼中匹配出該模塊的依賴項。 > 從`REQUIRE_RE`這么長的正則來看,這里坑很多;在CommonJS的wrapper方案中可以使用JS語法分析器來獲取依賴會更準確。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看