Sea.js是如何工作的?

2020-04-26 10:18 更新

蒙惠者雖知其然,而未必知其所以然也。

寫了這么多,必須證明一下本書并不是一份乏味的使用文檔,我們來深入看看Sea.js,搞清楚它時(shí)如何工作的吧!

CMD規(guī)范

要想了解Sea.js的運(yùn)作機(jī)制,就不得不先了解其CMD規(guī)范。

Sea.js采用了和Node相似的CMD規(guī)范,我覺得它們應(yīng)該是一樣的。使用require、exports和module來組織模塊。但Sea.js比起Node的不同點(diǎn)在于,前者的運(yùn)行環(huán)境是在瀏覽器中,這就導(dǎo)致A依賴的B模塊不能同步地讀取過來,所以Sea.js比起Node,除了運(yùn)行之外,還提供了兩個(gè)額外的東西:

  1. 模塊的管理
  2. 模塊從服務(wù)端的同步

即Sea.js必須分為模塊加載期和執(zhí)行期。加載期需要將執(zhí)行期所有用到的模塊從服務(wù)端同步過來,在再執(zhí)行期按照代碼的邏輯順序解析執(zhí)行模塊。本身執(zhí)行期與node的運(yùn)行期沒什么區(qū)別。

所以Sea.js需要三個(gè)接口:

  1. define用來wrapper模塊,指明依賴,同步依賴;
  2. use用來啟動(dòng)加載期;
  3. require關(guān)鍵字,實(shí)際上是執(zhí)行期的橋梁。

并不太喜歡Sea.js的use API,因?yàn)槠浠卣{(diào)函數(shù)并沒有使用與Define一樣的參數(shù)列表。

模塊標(biāo)識(shí)(id)

模塊id的標(biāo)準(zhǔn)參考Module Identifiers,簡單說來就是作為一個(gè)模塊的唯一標(biāo)識(shí)。

出于學(xué)習(xí)的目的,我將它們翻譯引用在這里:

  1. 模塊標(biāo)識(shí)由數(shù)個(gè)被斜杠(/)隔開的詞項(xiàng)組成;
  2. 每次詞項(xiàng)必須是小寫的標(biāo)識(shí)、“.”或“..”;
  3. 模塊標(biāo)識(shí)并不是必須有像“.js”這樣的文件擴(kuò)展名;
  4. 模塊標(biāo)識(shí)不是相對(duì)的,就是頂級(jí)的。相對(duì)的模塊標(biāo)識(shí)開頭要么是“.”,要么是“..”;
  5. 頂級(jí)標(biāo)識(shí)根據(jù)模塊系統(tǒng)的基礎(chǔ)路徑來解析;
  6. 相對(duì)的模塊標(biāo)識(shí)被解釋為相對(duì)于某模塊的標(biāo)識(shí),“require”語句是寫在這個(gè)模塊中,并在這個(gè)模塊中調(diào)用的。

模塊(factory)

顧名思義,factory就是工廠,一個(gè)可以產(chǎn)生模塊的工廠。node中的工廠就是新的運(yùn)行時(shí),而在Sea.js中(Tea.js中也同樣),factory就是一個(gè)函數(shù)。這個(gè)函數(shù)接受三個(gè)參數(shù)。

function (require, exports, module) {
    // here is module body
}

在整個(gè)運(yùn)行時(shí)中只有模塊,即只有factory。

依賴(dependencies)

依賴就是一個(gè)id的數(shù)組,即模塊所依賴模塊的標(biāo)識(shí)。

依賴加載的原理

有很多語言都有模塊化的結(jié)構(gòu),比如c/c++的#include語句,Ruby的require語句等等。模塊的執(zhí)行,必然需要其依賴的模塊準(zhǔn)備就緒才能順利執(zhí)行。

c/c++是編譯語言,在預(yù)編譯時(shí),替換#include語句,將依賴的文件內(nèi)容包含進(jìn)來,在編譯后的執(zhí)行期,所有的模塊才會(huì)開始執(zhí)行;

而Ruby是解釋型語言,在模塊執(zhí)行前,并不知道它依賴什么模塊,待到執(zhí)行到require語句時(shí),執(zhí)行將暫停,從外部讀取并執(zhí)行依賴,然后再回來繼續(xù)執(zhí)行當(dāng)前模塊。

JavaScript作為一門解釋型語言,在復(fù)雜的瀏覽器環(huán)境中,Sea.js是如何處理CMD模塊間的依賴的呢?

node的方式-同步的require

想要解釋這個(gè)問題,我們還是從Node模塊說起,node于Ruby類似,用我們之前使用過的一個(gè)模塊作為例子:

// File: usegreet.js
var greet = require("./greet");
greet.helloJavaScript();

當(dāng)我們使用node usegreet.js來運(yùn)行這個(gè)模塊時(shí),實(shí)際上node會(huì)構(gòu)建一個(gè)運(yùn)行的上下文,在這個(gè)上下文中運(yùn)行這個(gè)模塊。運(yùn)行到require('./greet')這句話時(shí),會(huì)通過注入的API,在新的上下文中解析greet.js這個(gè)模塊,然后通過注入的exportsmodule這兩個(gè)關(guān)鍵字獲取該模塊的接口,將接口暴露出來給usegreet.js使用,即通過greet這個(gè)對(duì)象來引用這些接口。例如,helloJavaScript這個(gè)函數(shù)。詳細(xì)細(xì)節(jié)可以參看node源碼中的 module.js。

node的模塊方案的特點(diǎn)如下:

  1. 使用require、exports和module作為模塊化組織的關(guān)鍵字;
  2. 每個(gè)模塊只加載一次,作為單例存在于內(nèi)存中,每次require時(shí)使用的是它的接口;
  3. require是同步的,通俗地講,就是node運(yùn)行A模塊,發(fā)現(xiàn)需要B模塊,會(huì)停止運(yùn)行A模塊,把B模塊加載好,獲取的B的接口,才繼續(xù)運(yùn)行A模塊。如果B模塊已經(jīng)加載到內(nèi)存中了,當(dāng)然require B可以直接使用B的接口,否則會(huì)通過fs模塊化同步地將B文件內(nèi)存,開啟新的上下文解析B模塊,獲取B的API。

實(shí)際上node如果通過fs異步的讀取文件的話,require也可以是異步的,所以曾經(jīng)node中有require.async這個(gè)API。

Sea.js的方式-加載期與執(zhí)行期

由于在瀏覽器端,采用與node同樣的依賴加載方式是不可行的,因?yàn)橐蕾囍挥性趫?zhí)行期才能知道,但是此時(shí)在瀏覽器端,我們無法像node一樣直接同步地讀取一個(gè)依賴文件并執(zhí)行!我們只能采用異步的方式。于是Sea.js的做法是,分成兩個(gè)時(shí)期——加載期和執(zhí)行期;

的確,我們可以使用同步的XHR從服務(wù)端加載依賴,但是本身就是單進(jìn)程的JavaScript還需要等待文件的加載,那性能將大打折扣。

  • 加載期:即在執(zhí)行一個(gè)模塊之前,將其直接或間接依賴的模塊從服務(wù)器端同步到瀏覽器端;
  • 執(zhí)行期:在確認(rèn)該模塊直接或間接依賴的模塊都加載完畢之后,執(zhí)行該模塊。

加載期

不難想見,模塊間的依賴就像一棵樹。啟動(dòng)模塊作為根節(jié)點(diǎn),依賴模塊作為葉子節(jié)點(diǎn)。下面是pixelegos的依賴樹:


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)