簡單聊聊模組

夜曉宸發表於2019-03-25

模組

JavaScript 中,模組只不過是基於函式某些特性的程式碼組織方式。

在《你不知道的 JavaScript》中,給出了模組模式因具備的兩個必要條件:

  1. 必須有外部的封閉函式,該函式必須至少被呼叫一次(每次呼叫都會建立一個新的模組例項)。
  2. 封閉函式必須返回至少一個內部函式,這樣內部函式才能在私有作用域中形成閉包,並且可以訪問或者修改私有的狀態。

從中我們可以看到一個比較重要的一點,從函式呼叫所返回的只有資料屬性而沒有閉包函式的物件並不是真正的模組。

你看?,理解閉包的重要性再次體現出來了。

從以上要求的兩點來看,只要滿足相應的條件,我們很容易寫出一個模組。

const userModule = ((name = 'module') => {
  let id = 1,moduleName = name;
  const sayName = () => {
      console.log('moduleName: %s', moduleName);
  };
  const sayId = () => {
    console.log('id: %s', id);
  };
  const changeName = value => {
      moduleName = value;
  };
  const changePublicAPI = () => {
    publicAPI.sayIdentification = sayId
  };
  const publicAPI = {
    sayIdentification: sayName,
    changeName,
    changePublicAPI,
  }
  return publicAPI;
})();
複製程式碼

以上在滿足兩個必要的基礎上轉換成了 IIFE(立即執行函式表示式)。同時可以看出,基於函式的模組可以在執行時通過內部保留著公共 API 物件的引用,從而對模組例項進行修改。

模組機制

模組的出現也是為了能夠提高程式碼的複用率,方便程式碼管理。複用模組,自然會出現模組依賴的問題,所以說我們需要一個管理模組依賴的模組。

const moduleManage = (() => {
  let modules = {};
  const define = (name, deps, module) => {
    deps = deps.map(item => modules[item])
    modules[name] = module(...deps);
  };
  const exports = (name) => {
    return modules[name];
  }
  return {
    define,
    exports,
  }
})();

moduleManage.define('a', [], () => {
  const sayName = name => {
    console.log('name: %s', name);
  };
  return {
    sayName,
  }
});
moduleManage.define('b', ['a'], (a) => {
  let name = 'b';
  const sayName = () => {
    a.sayName(name)
  };
  return {
    sayName,
  }
});

var b = moduleManage.exports('b');
b.sayName();
複製程式碼

模組依賴管理器也依然是個模組,這裡的實現其實很簡單。modules[name] = module(...deps),使用 modules 快取各個模組,對於依賴模組的模組,則把依賴作為引數使用。

規範

CommonJS 規範服務於服務端,同步阻塞,在寫法風格上是依賴就近。但是在瀏覽器上,CommonJS 就不好使了,瀏覽器需要從伺服器請求資料,下載完成後才會有下一步的執行。如果採用 CommonJS 的同步方式,指不定什麼時候檔案才會下載完成。

為了推廣到瀏覽器上,AMD 規範採用非同步方式載入模組。先非同步載入模組,載入完成後就可以在回撥中使用依賴模組了。這樣就保證了在使用依賴時,依賴已經載入完成。AMD 規範是早早地下載,早早地執行,在回撥裡 require 的是依賴的引用。在寫法風格上是依賴前置,這種風格已經不同於 CommonJS 了。還有,這裡早早地執行會帶來一個問題,如果存在某個依賴某些條件不成立,導致沒有用上。那麼,這裡的早早地執行豈不是多此一舉了?

CMD 規範是 sea.js 推崇的規範,它採用的也是非同步載入模組的方式,只是在依賴模組的執行時機上有所不同。在寫法風格上,又迴歸到 CommonJS,依賴就近。sea.js 是早早地下載,延遲執行。

到了 ES6,終於從語法上支援模組化了,ES6 模組是編譯時載入,使得在編譯時就能確定模組的依賴關係,而且在將來伺服器和瀏覽器都會支援 ES6 的模組化方案。

相關文章