前端入門22-講講模塊化

聲明

本篇內容梳理自如下來源:javascript

感謝各位大佬的分享,解惑了不少。html

正文-模塊化

如今回過頭來想一想,也許選擇以《JavaScript權威指南》一書來做爲入門有些很差,由於這本書畢竟是很早以前的,書中所講的思想、標準也基本都只是 ES5 及那時代的相關技術。前端

這也就致使了,在書中看到的不少例子,雖然以爲所用到的思想很奇妙,好比臨時命名空間之類的處理,但其實,有些技術到如今已經有了更爲強大的技術替代了。java

就像這篇要講的模塊化,目前,以我看到的各資料後,所收穫的知識是,大概有四種較爲經常使用且熱門的模塊化技術,也許還有更新的技術,也許還有我不知道的技術,無所謂,慢慢來,這篇的內容已經夠我消化了。node

目前四種模塊化技術:git

  • CommonJS規範&node.js
  • AMD規範&Require.js
  • CMD規範&Sea.js
  • ES6標準

前面是規範,規範就是相似於 ES5,ES6 只是提出來做爲一種標準規範,而不一樣規範有具體的實現,好比 nodeJS 實現了 CommonJS 規範。es6

模塊化歷程

在聲明部分中的第2、第三連接裏那兩篇,以時間線介紹了模塊化的相關技術的發展歷程,我以爲頗有必要一看,對於掌握和理解目前的模塊化技術挺有幫助的。github

這裏,就稍微摘抄其中一些內容,詳細內容仍是須要到原文閱讀。web

1.全局變量、全局函數(1999年)npm

這時候的多個 js 腳本文件之間,直接經過全局變量或全局函數的方式進行通訊,這種方式叫作:直接定義依賴。

雖然作的好一些的會對這些 js 文件作一些目錄規劃,將資源歸類整理,但仍沒法解決全局命名空間被大量污染,極其容易致使變量衝突問題。

2.對象做爲命名空間(2002年)

爲了解決遍地的全局變量問題,這時候提出一種命名空間模式的思路,即將本要定義成全局變量、全局函數的這些全都做爲一個對象的屬性存在,這個對象的角色充當命名空間,不一樣模塊的 JS 文件中經過訪問這個對象的屬性來進行通訊。

3.當即執行的函數做爲臨時命名空間 + 閉包(2003年)

雖然提出利用一個對象來做爲命名空間的思路,必定程度解決了大量的全局變量的問題,但仍舊存在不少侷限,好比沒有模塊的隱藏性,因此針對這些問題,這時候又新提出一種思路:利用當即執行的函數來做爲臨時命名空間,這樣就能夠避免污染全局命名空間,同時,結合閉包特性,來實現隱藏細節,只對外暴露指定接口。

雖然這種思路,解決了不少問題,但仍舊有一些侷限,好比,缺少管理者,什麼意思,就是說,在前端裏,開發人員得手動將不一樣 JS 腳本文件按照它們之間的依賴關係,以被依賴在前面的順序來手動書寫 <script> 加載這些文件。

也就是不一樣 <script> 的先後順序實際上表示着它們之間的依賴關係,一旦文件過多,將會很難維護,這是上述方案都存在的問題。

4.動態建立 <script> 工具(2009年)

針對上述問題,也就衍生出了一些加載 js 文件的工具,先看個例子:

$LAB.script("greeting.js").wait()
    .script("x.js")
    .script("y.js").wait()
    .script("run.js");

LAB.js 這類加載工具的原理其實是動態的建立 <script>,達到做爲不一樣 JS 腳本文件的管理者做用,來決定 JS 文件的加載和執行順序。

雖然我沒用過這種工具,但我以爲它的侷限仍是有不少,其實就是將開發人員原本須要手動書寫在 HTML 文檔裏的 <script> 代碼換成寫在 JS 文件中,不一樣 JS 文件之間的依賴關係仍舊須要按照先後順序手動維護。

5.CommonJS規範&node.js(2009年)

中間跳過了一些過程,好比 YUI 的沙箱模式等,由於不熟,想了解更詳細的能夠去原文閱讀。

當 CommonJS 規範出來時,模塊化算是進入了真正的革命,由於在此以前的探索,都是基於語言層面的優化,也就是利用函數特性、對象特性等來在運行期間模擬出模塊的做用,而從這個時候起,模塊化的探索就大量的使用了預編譯。

CommonJS 模塊化規範有以下特色:

  • 全部代碼都運行在模塊做用域,不會污染全局做用域。
  • 模塊能夠屢次加載,可是隻會在第一次加載時運行一次,而後運行結果就被緩存了,之後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
  • 模塊加載的順序,按照其在代碼中出現的順序。

不一樣的模塊間的依賴關係經過 require 來控制,而每一個模塊須要對外暴露的接口經過 exports 來決定。

因爲 CommonJS 規範自己就只是爲了服務端的 node.js 而考慮的,node.js 實現了 CommonJS 規範,因此運行在 node.js 環境中的 js 代碼可使用 requireexports 這兩個命令,但在前端裏,瀏覽器的 js 執行引擎並不認識 require 這些命令,因此須要進行一次轉換工做,後續介紹。

再來看看 require 命令的工做原理:

require 命令是 CommonJS 規範之中,用來加載其餘模塊的命令。它其實不是一個全局命令,而是指向當前模塊的 module.require 命令,然後者又調用 Node 的內部命令 Module._load

Module._load = function(request, parent, isMain) {
  // 1. 檢查 Module._cache,是否緩存之中有指定模塊
  // 2. 若是緩存之中沒有,就建立一個新的Module實例
  // 3. 將它保存到緩存
  // 4. 使用 module.load() 加載指定的模塊文件,
  //    讀取文件內容以後,使用 module.compile() 執行文件代碼
  // 5. 若是加載/解析過程報錯,就從緩存刪除該模塊
  // 6. 返回該模塊的 module.exports
};

Module.prototype._compile = function(content, filename) {
  // 1. 生成一個require函數,指向module.require
  // 2. 加載其餘輔助方法到require
  // 3. 將文件內容放到一個函數之中,該函數可調用 require
  // 4. 執行該函數
};

因此,其實 CommonJS 的模塊化規範之因此能夠實現控制做用域、模塊依賴、模塊通訊,其實本質上就是將模塊內的代碼都放在一個函數內來執行,這過程當中會建立一個對象 Module,而後將模塊的相關信息包括對外的接口信息都添加到對象 Module 中,供其餘模塊使用。

6.AMD規範&Require.js(2009年)

CommonJS 規範加載模塊是同步的,也就是說,只有加載完成,才能執行後面的操做。因爲 Node.js 主要用於服務器編程,模塊文件通常都已經存在於本地硬盤,因此加載起來比較快,不用考慮非同步加載的方式,因此CommonJS 規範比較適用。

可是,若是是瀏覽器環境,這種同步加載文件的模式就會致使瀏覽器陷入卡死狀態,由於網絡緣由是不可控的。因此,針對瀏覽器環境的模塊化,新提出了一種規範:AMD(Asynchronous Modules Definition)異步模塊定義。

也就是說,對於 Node.js,對於服務端而言,模塊化規範就是按照 CommonJS 規範便可。

但對於瀏覽器,對於前端而言,CommonJS 不適用,須要看看 AMD 規範。

AMD 規範中定義:

  • 定義一個模塊時經過 define 命令,經過 return 聲明模塊對外暴露的接口
  • 依賴其餘模塊時,經過 require 命令

而規範終歸只是規範,使用時仍是須要有規範的具體實現,針對 AMD 規範,具體的實現是 Require.js,在前端裏,如何基於 Require.js 來使用 AMD 規範的模塊化技術,後續介紹。

7.CMD規範&Sea.js(2013年)

CMD(Common Module Definition)也是專門針對瀏覽器、針對前端而提出的一種模塊化規範。它跟 AMD 規範都是用途都是同樣,用途解決前端裏的模塊化技術。

但兩種規範各有各的優缺點,各有各的適用場景和侷限性吧,我還沒使用過這兩種規範,因此無從比較,但網上關於這兩種規範比較的文章卻是很多。

CMD 規範中定義:

  • 使用 define 命令定義一個模塊,使用 exports 來暴露模塊對外的接口
  • 使用 require 來同步加載依賴的模塊,但也可以使用 require.async 來異步加載依賴的模塊

總之,雖然兩種規範都是用於解決前端裏的模塊化技術,但實現的本質上仍是有些不一樣,後續介紹。

對於 CMD 規範的具體實現是 Sea.js,前端裏若是想使用 CMD 規範的模塊化技術,須要藉助 Sea.js。

8.ES6標準(2015年)

2015 年發佈的 ES6 標準規範中,新增了 Module 特性,也就是官方在 ES6 中,在語言自己層面上,添加了模塊的機制支持,讓 JavaScript 語言自己終於能夠支持模塊特性了。

在 ES6 以前的各類方案,包括 CommonJS,AMD,CMD,本質上其實都是利用函數具備的本地變量的特性進行封裝從而模擬出模塊機制。也就是說,這些方案都是須要在運行期間,纔會去動態的建立出某個模塊,並暴露模塊的相關接口。這種方案其實也存在一些侷限性。

而 ES6 新增了模塊的機制後,在代碼的解析階段,就可以肯定模塊以及模塊對外的接口,而不用等到運行期。這種本質上的區別,在藉助開發工具寫代碼階段的影響很明顯就是,終於可讓開發工具智能的提示依賴的模塊都對外提供了哪些接口。

但 ES6 因爲是新增的特性,在支持方面目前好像還不是很理想,並非全部環境都支持 ES6 的模塊機制好像,因此會看到某些大佬的文章裏會介紹一些諸如:Babel、Browserify。

Babel 用於將 ES6 轉換爲 ES5 代碼,好讓不支持 ES6 特性的環境能夠運行 ES5 代碼。

Browserify 用於將編譯打包 js,處理 require 這些瀏覽器並不認識的命令。

上面只是簡單介紹了下模塊化的發展歷程,並且中間略過一些階段,想看詳細的能夠去原文閱讀。下面就具體介紹下,目前四個比較穩定的模塊技術:

CommonJS規範

因爲 CommonJS 是針對服務端設計的模塊化規範,對於 Node.js 來講,一個 JS 文件就是一個模塊,因此並不須要相似 AMD 或 CMD 中的 define 來聲明一個模塊。這是個人理解。

exports

既然在 Node.js 中,每一個 JS 文件就是一個模塊,那麼這個模塊文件內的變量都是對外隱藏的,外部無權訪問,只能訪問 exports 對外暴露的接口,如:

//module.js
var name = "dasu";
var wx = "dasuAndroidTv";
var getBlog = function() {
    return "http://www.cnblogs.com/dasusu/";
}
//以上變量和函數,外部都無權訪問,外部只能訪問到下面經過 exports 暴露的接口
module.exports.name = name;
exports.getWx = () => wx;

模塊內,能夠經過對 module.exports 或 exports 添加屬性來達到對外暴露指定接口的目的,固然,從程序上來講,你能夠直接改變 module.exports 的指向,讓它指向一個新對象而不是在本來對象上添加屬性,這個就相似於對構造函數 prototype 屬性的修改。

但建議使用方式仍是儘量在 exports 對象上添加屬性。

若是有想探究它的原理的話,能夠嘗試利用 Browserify 來轉換這段模塊代碼,看看最後生成的是什麼:

function(require,module,exports){
    //module.js
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    //以上變量和函數,外部都無權訪問,外部只能訪問到下面經過 exports 暴露的接口
    module.exports.name = name;
    exports.getWx = () => wx;
}

雖然,對於 Node.js 來講,它其實對待每一個 JS 文件,本質上,會將文件內的代碼都放於一個函數內,若是該模塊首次被其餘模塊引用了,那麼就會去執行這個函數,也就是執行模塊內的代碼,因爲函數自己有三個參數,其中有兩個分別是:module 和 exports,這也是內部爲何能夠直接經過 module.exports 或 exports 來操做的緣由。

Module 對象是 Node.js 會爲每一個模塊文件建立的一個對象,模塊之間的通訊,其實就是經過訪問每一個 Module 對象的屬性來實現。

因此,說白了,CommonJS 模塊化技術的本質,其實就是利用了函數的局部做用域的特性來實現模塊做用域,而後結合一個對象做爲命名空間的方式,來保存模塊內部須要對外暴露的信息方式。最後,經過 require 命令來組織各模塊之間的依賴關係,解決之前方案沒有管理者角色的侷限,解決誰先加載誰後加載的問題。

require

每一個 JS 文件其實都被當作一個模塊處理,也就是文件內的代碼都會被放入到一個函數內,那這個函數何時執行呢?也就是說,模塊何時應該被加載呢?

這就是由 require 命令決定,當某個模塊內使用了 require 命令去加載其餘模塊,那麼這時候,被加載的模塊若是是首次被調用,它是沒有對應的 Module 對象的,因此會去調用它的函數,執行模塊內代碼,這個過程是同步的,這期間會完善這個模塊的 Module 對象信息。以後,其餘模塊若是也引用了這個模塊,由於模塊內代碼已經被執行過了,已經存在對應的 Module 對象,因此此時就不會再重複去加載這個模塊了,直接返回 Module 對象。

以上,基本也就是模塊間依賴的加載處理過程。而 require 命令用法很簡單:

//main.js
var module1 = require("./module");

module1.name;  //輸出=> dasu
module1.getWx(); //輸出 => dasuAndroidTv
//module1.getBlog(); //沒有權限訪問

其實,Node.js 對 main.js 的處理也是認爲它是個模塊,因此文件內的代碼也都放入一個函數內,還記得函數的第一個參數就是 require 麼,這也就是爲何模塊內能夠直接使用 require() 的緣由,require 其實本質上是一個函數,具體的實現是 Node.js 的一個內置方法:Module._load(),主要工做上一節有介紹過了。

說得稍微嚴謹點,Node.js 其實才是做爲各模塊之間的管理者,由它來管控着哪一個模塊先加載,哪一個後加載,維護着各模塊對外暴露的信息。

到這裏再來理解,有些文章中對 Module 對象的介紹:

  • module.id 模塊的識別符,一般是帶有絕對路徑的模塊文件名。
  • module.filename 模塊的文件名,帶有絕對路徑。
  • module.loaded 返回一個布爾值,表示模塊是否已經完成加載。
  • module.parent 返回一個對象,表示調用該模塊的模塊。
  • module.children 返回一個數組,表示該模塊要用到的其餘模塊。
  • module.exports 表示模塊對外輸出的值。

這時,對於 Module 對象內的各屬性用途,理解應該會比較容易點了。

最後說一點,CommonJS 只是一種模塊化的規範,而 Node.js 纔是這個規範的具體實現者,但 Node.js 一般用於服務端的運行環境,對於前端而言,對於瀏覽器而言,由於不存在 Node.js 這東西,因此 require 或 exports 這類在前端裏是沒法運行的,但能夠藉助 Browerify 來進行代碼轉換。

AMD規範

AMD規範和規範實現者:Require.js

前端裏沒有 Node.js 的存在,即便有相似的存在,但因爲 CommonJS 的模塊化規範中,各模塊的加載行爲是同步的,也就是被依賴的模塊必須執行結束,當前模塊才能繼續處理,這對於前端而言,模塊的加載就多了一個下載的過程,而網絡是不可靠的,因此 CommonJS 並不適用於前端的場景。

因此,針對前端,提出了另外一種模塊化規範:AMD,即異步模塊加載,經過增長回調處理的方式,來移除被依賴模塊和當前模塊的先後關聯,兩個模塊可同時下載,執行。當前模塊內,須要依賴於其餘模塊信息的代碼放置於回調函數內,這樣就能夠先行處理當前模塊內其餘代碼。

define

前端裏是經過 <script> 來加載 JS 文件代碼的,不能像 Node.js 那樣從源頭上處理 JS 文件,因此它多了一個 define 來定義模塊,如:

//module.js
define(function(){
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    return {
        name: name,
        getWx: function() {
            return wx;
        }
    }
})

若是定義的模塊又依賴了其餘模塊時,此時 define 須要接收兩個參數,如:

//兩個參數,第一個參數是數組,數組裏是當前模塊依賴的全部模塊,第二個參數是函數,函數須要參數,參數個數跟數組個數一直,也跟數組裏依賴的模塊一一對應,該模塊內部就是經過參數來訪問依賴的模塊。
define(['module2'], function(module2){
    //module2.xxx  使用模塊 module2 提供的接口
    
    //本模塊的內部邏輯
    
    return {
        //對外暴露的接口
    }
})

define 有兩個參數,第一個參數是數組,數組裏是當前模塊依賴的全部模塊,第二個參數是函數,函數須要參數,參數個數跟數組個數一直,也跟數組裏依賴的模塊一一對應,該模塊內部就是經過參數來訪問依賴的模塊。

require

若是其餘地方有須要使用到某個模塊提供的功能時,此時就須要經過 require 來聲明依賴關係,但聲明依賴前,須要先經過 requirejs.config 來配置各個模塊的路徑信息,方便 Require.js 可以得到正確路徑自動去下載。

requirejs.config({
    paths: {
        module1: './module'
    }
})

var module1 = require(['module1'], function(module1){
    console.log(module1.name);    //訪問模塊內的 name 接口
    console.log(module1.getWx()); //訪問模塊內的 getWx 接口
});

//其餘不依賴於模塊的代碼

這種方式的話,require 的第一個數組參數內的值就能夠模塊的別稱,而第二個參數是個函數,一樣,函數的參數就是加載後的模塊,該 JS 文件內須要依賴到模塊信息的代碼均可以放到回調函數中,經過回調函數的參數來訪問依賴的模塊信息。

使用步驟

1.下載 Require.js,並放到項目中

Require.js:https://requirejs.org/docs/download.html#requirejs

2.新建做爲模塊的文件,如 module.js,並經過 define 定義模塊

//module.js
define(function(){
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    return {
        name: name,
        getWx: function() {
            return wx;
        }
    }
})

3.在其餘 js 文件內先配置全部的模塊來源信息

//main.js
requirejs.config({
    paths: {
        module1: './module'
    }
})

4.配置完模塊信息後,經過 require 聲明須要依賴的模塊

//main.js
var module1 = require(['module1'], function(module1){
    console.log(module1.name);    //訪問模塊內的 name 接口
    console.log(module1.getWx()); //訪問模塊內的 getWx 接口
});

//其餘不依賴於模塊的代碼

5.最後也最重要的一步,在 html 中聲明 require.js 和 入口 js 如 main.js 的加載關係

<script src="js/lib/require.js" data-main="js/src/main.js"></script>

固然,這只是基礎用法的步驟,其中第 3 步的模塊初始化步驟也可經過其餘方式,如直接利用 require 的不一樣參數類型來實現等等,但大致上須要這幾個過程,尤爲是最後一步,也是最重要一步,由於 AMD 在前端的具體實現都依靠於 Require.js,因此必須等到 Require.js 下載並執行結束後會開始處理其餘 js。

以上例子的項目結構如圖:

小結

最後小結一下,AMD 規範的具體實現 Require.js 其實從使用上來看,已經比較容易明白它的原理是什麼了。

本質上,也仍是利用了函數的特性,做爲模塊存在的那些代碼自己已經經過 define 規範被定義在函數內了,因此模塊內的代碼天然對外是隱藏的,外部能訪問到的只有函數內 return 的接口,那麼這裏其實也就利用了閉包的特性。

因此,模塊化的實現,無非就是讓函數做爲臨時命名空間結合閉包或者對象做爲命名空間方式, 這種方式即便沒有 CommonJS 規範,沒有 AMD 規範,本身寫代碼很能夠容易的實現。那麼爲何會有這些規範技術的出現呢?

無非就是爲了引入一個管理者的角色,沒有管理者的角色,模塊之間的依賴關係,哪一個文件先加載,哪一個後加載,<script> 的書寫順序都只能依靠人工來維護、管理。

而這些模塊化規範,其實目的就在於解決這些問題,CommonJS 是由 Node.js 做爲管理者角色,來維護、控制各模塊的依賴關係,文件的加載順序。而 AMD 則是由 Require.js 來做爲管理者角色,開發者不用在 HTML 裏寫那麼多 <script>,並且也不必去關心這些文件誰寫前誰寫後,Require.js 會自動根據 require 來維護這些依賴關係,自動根據 requirejs.config 的配置信息去決定先加載哪一個文件後加載哪一個文件。

CMD規範

CMD 規範,相似於 AMD,一樣也是用於解決前端裏的模塊化技術問題。而有了 AMD 爲何還會有 CMD,我我的的理解是,AMD 的適用場景並無覆蓋整個前端裏的需求,或者說 AMD 自己也有一些缺點,致使了新的 CMD 規範的出現。

好比說,從使用方式上,AMD 就有不少跟 CommonJS 規範不一致的地方,對於從 CommonJS 轉過來的這部分人來講,可能就會很不習慣;再好比說,AMD 考慮的場景可能太多,又要適配 jQurey,又要適配 Node 等等;

總之,CMD 規範總有它出現和存在的理由,下面就大概來看看 CMD 的用法:

define&exports

相似於 AMD,CMD 規範中,也是經過 define 定義一個模塊:

//module.js
define(function (require, exports, module) {
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    exports.name = name;
    exports.getWx = () => wx;
})

跟 AMD 不同的地方是,CMD 中 define 只接收一個參數,參數類型是一個函數,函數的參數也很重要,有三個,按順序分別是 require,exports,module,做用就是 CommonJS 規範中三個命令的用途。

若是當前模塊須要依賴其餘模塊,那麼在內部,使用 require 命令便可,因此,函數的三個參數很重要。

當前模塊若是不依賴其餘模塊,也沒有對外提供任何接口,那麼,函數能夠沒有參數,由於有了內部也沒有使用。

而若是當前模塊須要依賴其餘模塊,那麼就須要使用到 require,因此函數第一個參數就是必須的;若是當前模塊須要對外暴露接口,那麼後兩個參數也是須要的;

總之,建議使用 define 定義模塊時,將函數三個參數都帶上,用不用再說,規範一點總沒錯。

require

在有須要使用某個模塊提供的功能時,經過 require 來聲明依賴關係:

//main.js
define(function (require, exports, module) {
    console.log("=====main.js=======");

    var module1 = require("./module");//同步加載模塊
    console.log(module1.name);

    require.async("./module2", function (module2) {//異步加載模塊
        console.log(module2.wx);
    })
})

require 默認是以同步方式加載模塊,若是須要異步加載,須要使用 require.async

使用步驟

1.下載 Sea.js,並放到項目中

Sea.js:https://github.com/seajs/seajs/releases

2.新建做爲模塊的文件,如 module.js,並經過 define 定義模塊

//module.js
define(function (require, exports, module) {
    var name = "dasu";
    var wx = "dasuAndroidTv";
    var getBlog = function() {
        return "http://www.cnblogs.com/dasusu/";
    }
    
    exports.name = name;
    exports.getWx = () => wx;
})

3.其餘須要依賴到該模塊的地方經過 require 聲明

//main.js
define(function (require, exports, module) {
    console.log("=====main.js=======");

    var module1 = require("./module");//同步加載模塊
    console.log(module1.name);

    require.async("./module2", function (module2) {//異步加載模塊
        console.log(module2.wx);
    })
})

4.最後也最重要的一步,在 html 中先加載 sea.js 並指定主模塊的 js

<script src="js/lib/require.js"></script>
<script>
     seajs.use("./js/src/main.js");
</script>

使用步驟跟 AMD 很相似,首先是須要依賴於 Sea.js,因此必須先下載它。

而後定義模塊、依賴模塊、使用模塊的方式就跟 CommonJS 很相似,這幾個操做跟 AMD 會有些不一樣,也許這點也正是 CMD 存在的緣由之一。

最後一步也是最重要一步,須要在 HTML 文檔中加載 sea.js 文檔,並指定入口的 js,注意作的事雖然跟 AMD 同樣,但實現方式不同。

小結

其實,CMD 跟 CommonJS 很相似,甚至在模塊化方面的工做,能夠很通俗的將 sea.js 理解成 node.js 所作的事,只是有些 node.js 能完成但卻沒法經過 sea.js 來負責的工做須要開發人員手動處理,好比定義一個模塊、經過 <script> 加載 sea.js 和指定主入口的 js 的工做。

CommonJS, AMD, CMD 三者間區別

下面分別從適用場景、使用步驟、使用方式、特性等幾個方面來對比這些不一樣的規範:

適用場景

CommonJS 用於服務端,基於 Node.js 的運行環境;

AMD 和 CMD 用於前端,基於瀏覽器的運行環境;

使用方式

CommonJS 經過 require 來依賴其餘模塊,經過 exports 來爲當前模塊暴露接口;

AMD 經過 define 來定義模塊,經過 requirejs.config 來配置模塊路徑信息,經過 require 來依賴其餘模塊,經過 retrurn 來暴露模塊接口;

CMD 經過 define 來定義模塊,經過 require 來依賴其餘模塊,經過 exports 來爲當前模塊暴露接口;

使用步驟

CommonJS 適用的 Node.js 運行環境,無需其餘步驟,正常使用模塊技術便可;

AMD 適用的前端瀏覽器的運行環境沒有 Require.js,因此項目中須要先加載 Require.js,而後再執行主入口的 js 代碼,須要在 HTML 中使用相似以下命令:

<script src="js/lib/require.js" data-main="js/src/main.js"></script>

CMD 適用的前端瀏覽器的運行環境也沒有 Sea.js,因此項目中也須要先加載 Sea.js,而後再執行主入口的 js 代碼,須要在 HTML 中使用相似以下命令:

<script src="js/lib/sea.js"></script>
<script>
    seajs.use("./js/src/main.js");
</script>

特性

AMD:依賴前置、提早執行,如:

require(['module1','module2'], function(m1, m2){
    //...
})
define(['module1','module2'], function(m1, m2){
    //...
})

須要先將全部的依賴的加載完畢後,纔會去處理回調中的代碼,這叫依賴前置、提早執行;

CMD:依賴就近、延遲執行,如:

define(function(require, exports, module){
    //...
    var module1 = require("./module1");
    //...
    require("./module2", function(m2){
        //...
    });
})

等代碼執行到 require 這行代碼時纔去加載對應的模塊

ES6標準

ES6 中新增的模塊特性,在上一篇中已經稍微介紹了點,這裏也不具體展開介紹了,須要的話開頭的聲明部分有給出連接,自行查閱。

這裏就簡單說下,在前端瀏覽器中使用 ES6 模塊特性的步驟:

1.定義模塊,經過指定 <script type="module"> 方式

2.依賴其餘模塊使用 import,模塊對外暴露接口時使用 export;

須要注意的一點是,當 JS 文件內出現 import 或者 export 時,這份 JS 文件必須聲明爲模塊文件,即在 HTML 文檔中經過指定 <script> 標籤的 type 爲 module,這樣 import 或 export 纔可以正常運行。

也就是說,使用其餘模塊的功能時,當前的 JS 文件也必須是模塊。

另外,有一點,ES6 的模塊新特性,全部做爲模塊的文件都須要開發人員手動去 HTML 文檔中聲明並加載,這是與其餘方案不一樣的一點,ES6 中 import 只負責導入指定模塊的接口而已,聲明模塊和加載模塊都須要藉助 <script> 實現。

這裏不詳細講 ES6 的模塊特性,但想來說講,一些轉換工做的配置,由於:

  • 有些瀏覽器不支持 ES6 的語法,寫完 ES6 的代碼後,須要經過 Babel 將 ES6 轉化爲 ES5。
  • 生成了 ES5 以後,裏面仍然有 require 語法,而瀏覽器並不認識 require 這個關鍵字。此時,能夠用 Browserify 編譯打包 js,進行再次轉換。

而我是選擇使用 WebStrom 做爲開發工具的,因此就來說講如何配置

WebStrom 的 Babel 配置

教程部分摘抄自:ES6的介紹和環境配置

1.新建項目
2.經過 npm 初始化項目

在安裝 Babel 以前,須要先用 npm 初始化咱們的項目。打開終端或者經過 cmd 打開命令行工具,進入項目目錄,輸入以下命令: npm init -y,命令執行結束後,會在項目根目錄下生成 package.json 文件

3.(首次)全局安裝 Babel-cli

在終端裏執行以下命令:

npm install -g babel-cli

4.本地安裝 babel-preset-es2015 和 babel-cli

npm install --save-dev babel-preset-es2015 babel-cli

本地是指在項目的根目錄下打開終端執行以上命令,執行結束,項目根目錄的 package.json 文件中會多了 devDependencies 選項

5.新建 .babelrc 文件

在根目錄下新建文件 .babelrc,輸入以下內容:

{
    "presets":[
        "es2015"
    ],
    "plugins":[]
}

6.執行命令轉換

babel js/src/main.js -o js/dist/main.js

-o 前是原文件,後面是轉換後的目標文件

這是個人項目結構:

.json 文件和 node_modules 文件夾都是操做完上述步驟後會自動生成的,最後執行完命令後,會在 dist 目錄下生成目標文件。

7.(可選)若是嫌每次執行的命令太過複雜,可利用 npm 腳本

babel js/src/main.js -o js/dist/main.js 這行代碼複製到 package.json 裏的 scripts 字段中:

之後每次都點一下 build 左邊的三角形按鈕運行一下腳本就能夠了,省去了手動輸命令的時間。

8.(可選)若是還嫌每次手動點擊按鈕運行腳本麻煩,可配置監聽文件改動自動進行轉換

打開 WebStrom 的 Setting -> Tools -> File Watchers,而後點擊 + 按鈕,選擇 Babel 選項,而後進行配置:

9.最後,之後每次新的項目,除了第 3 步不用了以外,其他步驟仍舊須要進行。

WebStrom 的 Browserify 配置

步驟跟上述很相似,區別僅在於一個下載 babel,這裏下載的是 browserify,以及轉換的命令不一樣而已:

1.新建項目
2.經過 npm 初始化項目

打開終端,進入到項目的根目錄,執行 npm init -y,執行結束後會在根目錄生成 package.json 文件

3.(首次)全局安裝 browserify

在終端裏執行以下命令:

npm install browserify -g

4.執行命令轉換

browserify js/src/main.js -o js/dist/main.js --debug

-o 前是原文件,後面是轉換後的目標文件

5.(可選)若是嫌每次執行的命令太過複雜,可利用 npm 腳本

browserify js/src/main.js -o js/dist/main.js --debug 這行代碼複製到 package.json 裏的 scripts 字段中:

之後每次都點一下 build 左邊的三角形按鈕運行一下腳本就能夠了,省去了手動輸命令的時間。

6.(可選)若是還嫌每次手動點擊按鈕運行腳本麻煩,可配置監聽文件改動自動進行轉換

打開 WebStrom 的 Setting -> Tools -> File Watchers,而後點擊 + 按鈕,選擇 <custom> 選項,而後進行配置:

7.最後,之後每次新的項目,除了第 3 步不用了以外,其他步驟仍舊須要進行。


你們好,我是 dasu,歡迎關注個人公衆號(dasuAndroidTv),公衆號中有個人聯繫方式,歡迎有事沒事來嘮嗑一下,若是你以爲本篇內容有幫助到你,能夠轉載但記得要關注,要標明原文哦,謝謝支持~
dasuAndroidTv2.png

相關文章
相關標籤/搜索