CMD規范
要想了解Sea.js的運作機制,就不得不先了解其CMD規范。
Sea.js采用了和Node相似的CMD規范,我覺得它們應該是一樣的。使用require、exports和module來組織模塊。但Sea.js比起Node的不同點在於,前者的運行環境是在瀏覽器中,這就導致A依賴的B模塊不能同步地讀取過來,所以Sea.js比起Node,除了運行之外,還提供了兩個額外的東西:
- 模塊的管理
- 模塊從服務端的同步
即Sea.js必須分為模塊加載期和執行期。加載期需要將執行期所有用到的模塊從服務端同步過來,在再執行期按照代碼的邏輯順序解析執行模塊。本身執行期與node的運行期沒什么區別。
所以Sea.js需要三個接口:
- define用來wrapper模塊,指明依賴,同步依賴;
- use用來啟動加載期;
- require關鍵字,實際上是執行期的橋梁。
並不太喜歡Sea.js的use API,因為其回調函數並沒有使用與Define一樣的參數列表。
模塊標識(id)
模塊id的標准參考Module Identifiers,簡單說來就是作為一個模塊的唯一標識。
出於學習的目的,我將它們翻譯引用在這里:
- 模塊標識由數個被斜杠(/)隔開的詞項組成;
- 每次詞項必須是小寫的標識、“.”或“..”;
- 模塊標識並不是必須有像“.js”這樣的文件擴展名;
- 模塊標識不是相對的,就是頂級的。相對的模塊標識開頭要么是“.”,要么是“..”;
- 頂級標識根據模塊系統的基礎路徑來解析;
- 相對的模塊標識被解釋為相對於某模塊的標識,“require”語句是寫在這個模塊中,並在這個模塊中調用的。
模塊(factory)
顧名思義,factory就是工廠,一個可以產生模塊的工廠。node中的工廠就是新的運行時,而在Sea.js中(Tea.js中也同樣),factory就是一個函數。這個函數接受三個參數。
function (require, exports, module) { // here is module body }
在整個運行時中只有模塊,即只有factory。
依賴(dependencies)
依賴就是一個id的數組,即模塊所依賴模塊的標識。
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。
node的模塊方案的特點如下:
- 使用require、exports和module作為模塊化組織的關鍵字;
- 每個模塊只加載一次,作為單例存在於內存中,每次require時使用的是它的接口;
- require是同步的,通俗地講,就是node運行A模塊,發現需要B模塊,會停止運行A模塊,把B模塊加載好,獲取的B的接口,才繼續運行A模塊。如果B模塊已經加載到內存中了,當然require B可以直接使用B的接口,否則會通過fs模塊化同步地將B文件內存,開啟新的上下文解析B模塊,獲取B的API。
實際上node如果通過fs異步的讀取文件的話,require也可以是異步的,所以曾經node中有require.async這個API。