JS常見模塊化規範(CommonJS/AMD/CMD/UMD/ES6 Module)

爲什麼需要模塊化

在ES6出現之前,JS語言本身並沒有提供模塊化能力,這爲開發帶來了一些問題,其中最重要的兩個問題應當是全局污染依賴管理混亂

// file a.js
var name = 'aaa';
var sayName = function() {
    console.log(name);
};
<!-- file index.html -->
<script src='xxx/xxx/a.js'></script>

<script>
    sayName(); // 'aaa'
    
    // code...
    
    var name = 'bbb';
    
    sayName(); // 'bbb'
</script>

上面的代碼中,我們兩次調用a.js所提供的sayName函數輸出了不同結果,很明顯這是因爲兩個文件都對變量name進行了賦值,因此相互之間造成了影響。當然我們可以在編寫代碼時注意不要定義已存在的變量名,但是當一個頁面引用了10幾個幾百行的文件時,記住所有已經定義過的變量顯然不太現實。

// file a.js
var name = getName();
var sayName = function() {
    console.log(name)
};
// file b.js
var getName = function() {
    return 'timo';
};
<script src='xxx/xxx/b.js'></script>
<script src='xxx/xxx/a.js'></script>

<script>
    sayName(); // 'timo'
</script>
<script src='xxx/xxx/a.js'></script>
<script src='xxx/xxx/b.js'></script>

// Uncaught ReferenceError: getName is not defined

上面的代碼說明,多個文件有依賴關係時,我們需要確保其引入的順序,從而保證運行某個文件時,其依賴已經提前加載,可以想象,面對越大型的項目,我們需要處理的依賴關係也就越多,這既麻煩又容易出錯。

而爲了解決這些,社區中出現了許多爲JS語言提供模塊化能力的規範,藉助這些規範,能讓我們的開發更加方便安全。

常見模塊化方案

CommonJS

CommonJS是由社區提出的模塊化方案中的一種,Node.js遵循了這套方案。

基本寫法

// file a.js
var obj = {
    sayHi: function() {
        console.log('I am timo');
    };
};

module.exports obj;
// file b.js
var Obj = require('xxx/xxx/a.js');

Obj.sayHi(); // 'I am timo'

上面的代碼中,文件a.js是模塊的提供方,文件b.js是模塊調用方。

規範

  1. 每個文件都是一個模塊;
  2. 在模塊內提供module對象,表示當前模塊;
  3. 模塊使用exports對外暴露自身的函數/對象/變量等;
  4. 模塊內通過require()方法導入其他模塊;

CommonJS的規範,簡單來說就是上面4條,可以對照基本寫法中的例子理解一下,在實際實現中,Node.js雖然遵循CommonJS規範,但是仍然對其進行了一些調整。

AMD

AMD是模塊化規範中的一種,RequireJS遵循了這套規範。

基本用法

 // file a.js
 define('module', ['m', './xxx/n.js'], function() {
    // code...
 }) 

上面的代碼中,文件a.js向外導出了模塊;

規範

AMD中,暴露模塊使用define函數

define(moduleName, [], callback);

如上面代碼,define函數共有三個參數

  • moduleName該參數可以省略,表示該模塊的名字,一般作用不大
  • ['name1', 'name2'],第二個參數是一個數組,表示當前模塊依賴的其他模塊,如果沒有依賴模塊,該參數可以省略
  • callback,第三個參數是必傳參數,是一個回調函數,內部是當前模塊的相關代碼

其他

ADM的特點是依賴前置,這是ADM規範與接下來要介紹的CMD規範最大的不同,依賴前置是指:在運行當前加載模塊回調前,會首先將所有依賴包加載完畢,也是就是define函數的第二個參數中指定的依賴包。

CDM

基本寫法

 define(function(require, exports, module) {   
    var a = require('./a')  
    a.doSomething();
    // code... 
    var b = require('./b') 
    // code...
})

上面代碼是CMD規範導出模塊的基本寫法;

規範

從寫法可以看出,CMD的寫法和AMD非常像,其主要區別是對於依賴加載時機的不同,上面已經說過,AMD是依賴前置,而CMD規範推崇就近原則,簡單說就是在模塊運行前並不加載依賴,模塊運行過程中,當需要某個依賴時,再去進行加載。

UMD

CommonJS、AMD、CMD並行的狀態下,就需要一種方案能夠兼容他們,這樣我們在開發時,就不需要再去考慮依賴模塊所遵循的規範了,而UMD的出現就是爲了解決這個問題。

基本寫法

(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 myFunc(){};
    //暴露公共方法
    return myFunc;
}));

上面的代碼是UMD的基本寫法,從代碼就可以看出,其能夠同時支持CommonJS規範和AMD規範。

ES6 module

上面分別介紹了CommonJS、AMD、CMD和UMD,他們都是社區對於JS實現模塊化的貢獻,這個規範其產生的根本原因,都是JS語言自身沒有模塊化能力,而目前,在JS最新的語言規範ES6中,已經爲JS增加了模塊化能力,而JS自身的模塊化方案,完全能夠替代目前社區提出的各類規範,且能夠做到瀏覽器端和Node端通用。

ES6中的模塊化能力由兩個命令構成:exportimportexport命令用於規定模塊的對外接口,import命令用於輸入其他模塊提供的功能。

export命令

ES6中一個文件就是一個模塊,模塊內部的變量/函數等外部是無法訪問的,如果希望將內部的函數/變量等對外暴露,供其他模塊進行使用,就需要通過export命令進行導出

// file a.js
export let a = 1;
export let b = 2;
export let c = 3;
// file b.js
let a = 1;
let b = 2;
let c = 3;

export {a, b, c}
// file c.js
export let add = (a, b) => {
    return a + b;
};

上面三個文件的代碼,都是通過export命令導出模塊內容的示例,其中a.js文件和b.js文件都是導出模塊中的變量,作用完全一致但寫法不同,一般我們更推薦b.js文件中的寫法,原因是這種寫法能夠在文件最底部清楚地知道當前模塊都導出了哪些變量。

import命令

模塊通過export命令導出變量/函數等,是爲了讓其他模塊能夠導入去使用,在ES6中,文件導入其他模塊是通過import命令進行的

// file d.js
import {a, b, c} from './a.js';

上面的代碼中,我們引入了a.js文件中的變量a、b、c,import在引入其他模塊內的函數/變量時,必須與原模塊所暴露出來的函數名/變量名一一對應。
同時,import命令引入的值是隻讀的,如果嘗試對其進行修改,則會報錯

import {a} d from './a.js';
a = 2; // Syntax Error : 'a' is read-only;

export default命令

從上面import的介紹可以看到,當需要引入其他模塊時,需要知道此模塊暴露出的變量名/函數名纔可以,這顯然有些麻煩,因此ES6還提供了一個import default命令

// file a.js

let add = (a, b) => {
    return a+b
};
export default add;
// file b.js
import Add from './a.js';

Add(1, 2); // 3

上面的代碼中,a.js通過export default命令導出了add函數,在b.js文件中引入時,可以隨意指定其名稱

export default命令是默認導出的意思,既然是默認導出,顯然只能有一個,因此每個模塊只能執行一次export default命令,其本質是導出了一個名爲default的變量或函數。

總結

最後再來總結一下, 首先在之前的JS語言中沒有模塊化能力,而隨着網站功能的複雜,開發越來越不方便,因此社區中出現了一批爲JS提供模塊化能力的方案,其中比較主流的就是我們介紹過的CommonJS、AMD、CMD和UMD等,而之後發佈的ES6語言標準,從JS語言自身提供了模塊化能力,因此,隨着ES6的逐步普及,ES6 module應該會逐步取代目前的各類社區規範,但不可否認,在沒有ES6的日子裏,這些社區規範給前端人員提供了巨大的方便,並推動了JS的發展。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章