JavaScript中的AMD和CMD模塊化

編者注:關于JavaScript模塊化規范,你了解多少,你日常工作中是否有用到這些東西,本文作者將帶聊聊關于JavaScript中的各種模塊化規范,帶你了解為什么要模塊化,以及AMD、CMD、UMD和ES6 module的區別和優劣勢對比。 12053LP5-0 前端發展到今天,已經有不少模塊化的方案,比如AMD、CMD、UMD、CommonJS等,當然了,還有es6帶來的模塊系統,這些模塊化規范的核心價值都是讓 JavaScript 的模塊化開發變得簡單和自然,今天就來看看這些規范都是啥。

為什么要模塊化

在模塊化這東西沒出來之前,前端腳本引用大概是這樣的:
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="libraryA.js"></script>
<script src="module3.js"></script>
...
...
...
模塊把接口暴露到全局對象下(比如window),各個模塊可以通過全局對象訪問各個依賴的接口,但是也存在一些問題:
  1. 掛在全局對象下容易產生沖突
  2. 各個腳本加載的必須嚴格按照依賴順序,不然可能就玩不轉
  3. 在一個特別大的項目中,引用的腳本就會特別多,script 標簽就會特別多,并且難以維護。比如:
<script src="module1.js"></script>
<script src="module2.js"></script>
<script src="module3.js"></script>
<script src="module4.js"></script>
<script src="module5.js"></script>
<script src="module6.js"></script>
<script src="module7.js"></script>
<script src="module8.js"></script>
<script src="module9.js"></script>
<script src="module10.js"></script>
<script src="module11.js"></script>
<script src="module12.js"></script>
<script src="module13.js"></script>
<script src="module14.js"></script>
<script src="module15.js"></script>
...
為了解決這些問題,各種模塊化的方案都出來了

CommonJS

這種方式通過一個叫做require的方法,同步加載依賴,然后返導出API供其它模塊使用,一個模塊可以通過exports或者module.exports導出API。CommonJS規范中,一個單獨的文件就是一個模塊。每一個模塊都是一個單獨的作用域,在一個文件中定義的變量,都是私有的,對其他文件是不可見的。
require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;
服務端Node.js就是用的這種方式。

Well

  1. 服務端模塊可以很好的復用
  2. 這種風格的模塊已經很多了,比如npm上基本上都是這種風格的module
  3. 簡單易用

Less Well

  1. 加載模塊是同步的,所以只有加載完成才能執行后面的操作
  2. 多個模塊不能并行加載
Node.js主要用于服務器的編程,加載的模塊文件一般都已經存在本地硬盤,所以加載起來比較快,不用考慮異步加載的方式,所以CommonJS規范比較適用。但如果是瀏覽器環境,要從服務器加載模塊,這是就必須采用異步模式。所以就有了 AMD 、CMD 的解決方案。

AMD: 異步require

AMD === Asynchronous Module Definition 介于上面的說到的問題,于是瀏覽器端的異步版本的require應運而生
require(["module", "../file"], function(module, file) { /* ... */ });
define("mymodule", ["dep1", "dep2"], function(d1, d2) {
 return someExportedValue;
});
AMD 規范中,define 函數有一個公有屬性 define.amd。

Well

  1. 解決了模塊異步加載的問題
  2. 解決了多個腳本并行加載的問題

Less Well

  1. 代碼太過臃腫,不夠優雅,難以閱讀和書寫
  2. 但是似乎又是某種解決方案
AMD被使用的最廣泛的實現方案無疑就是 require.js

CMD表示不服

CMDSeaJS 在推廣過程中對模塊定義的規范化產出 CMD 規范中定義了 define 函數有一個公有屬性 define.cmd。 CMD 模塊中有兩種方式提供對外的接口,一種是 exports.MyModule = ...,一種是使用 return 進行返回。 CMDAMD的區別有以下幾點:
  1. 對于依賴的模塊AMD是提前執行,CMD是延遲執行。(require2.0貌似有變化)
  2. CMD推崇依賴就近,AMD推崇依賴前置。
//AMD
define(['./a','./b'], function (a, b) {

 //依賴一開始就寫好
 a.test();
 b.test();
});

//CMD
define(function (requie, exports, module) {

 //依賴可以就近書寫
 var a = require('./a');
 a.test();

 if (status) {
 var b = requie('./b');
 b.test();
 }
});
雖然AMD也支持CMD寫法,但依賴前置是官方文檔的默認模塊定義寫法。 推薦一篇文章:SeaJS與RequireJS最大的區別

UMD: 通用模塊規范

UMDAMDCommonJS兩者的結合,AMD 瀏覽器第一的原則發展,異步加載模塊。CommonJS 模塊以服務器第一原則發展,同步加載模塊,它的模塊無需包裝。 但是我如果想同時支持兩種風格呢?于是通用模塊規范(UMD)誕生了。這個模式中加入了當前存在哪種規范的判斷,所以能夠“通用”,它兼容了AMDCommonJS,同時還支持老式的“全局”變量規范:
(function (root, factory) {
 if (typeof define === "function" && define.amd) {
// AMD
define(["jquery"], factory);
} else if (typeof exports === "object") {
// Node, CommonJS之類的
module.exports = factory(require("jquery"));
} else {
// 瀏覽器全局變量(root 即 window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
 // 方法
 function test(){}; 

 // 暴露公共方法
 return test;
}));

ES6 modules: 你們都讓開,我才是標準

ES6JavaScript添加了一些語言結構,形成另一個模塊系統。
import "jquery"; export function doStuff() {} module "localModule" {}

Well

  1. 靜態分析非常容易
  2. 未來的標準

Less Well

  1. 目前的瀏覽器大都還不兼容,要想使用這種方式的模塊系統,貌似只能借助于轉譯工具了(比如Babel
  2. 這種模式的module目前還很少

總結

本文主要是介紹了一下 AMD、CMD等規范,較為籠統,下面的擴展閱讀可以更好的幫助你理解模塊化以及各個規范。

拓展閱讀

  1. 模塊系統
  2. 前端模塊化開發的價值
  3. 前端模塊化開發那點歷史
  4. CMD模塊定義規范
  5. SeaJS API快速參考
  6. 從CommonJS到Sea.js
  7. RequireJS和AMD規范
  8. CommonJS規范
  9. Javascript模塊化編程
  10. Javascript模塊化編程
  11. 知乎AMD和CMD的區別有哪些?
  12. JavaScript模塊化開發 - CommonJS規范
  13. JavaScript模塊化開發 - AMD規范
原文來自:JavaScript中的各種模塊化規范