CommonJS、AMD、CMD

CommonJS

CommonJS規範規定,每個模塊內部,module變量代表當前模塊。這個變量是一個對象,它的exports屬性(即module.exports)是對外的接口。加載某個模塊,其實是加載該模塊的module.exports屬性。
規定:
所有代碼都運行在模塊作用域,不會污染全局作用域。
模塊可以多次加載,但是只會在第一次加載時運行一次,然後運行結果就被緩存了,以後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
模塊加載的順序,按照其在代碼中出現的順序。

Node內部提供一個Module構建函數,所有的模塊都是Module的實例。

 function Module(id, parent) {
  this.id = id;
  this.exports = {};
  this.parent = parent;
  // ...

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

我們首先要知道,CommonJS是一個規範,是爲js模塊化制定的,在之前js只能在瀏覽器端使用的時候,模塊化的概念其實用處不是很大,因爲瀏覽器能處理的邏輯畢竟是有限的,現在,再CommonJS的指導下,Node誕生了,將js的環境由瀏覽器進一步擴展向了服務器端,這時候,模塊化就很重要了。我們看到過在Python、Ruby這些腳本語言中,都有模塊化概念,可以直接import,包括在java中的包,都是在做這一件事情。現在,Node中使用模塊化也可以實現更多複雜邏輯。
其次,模塊化的優勢在於可以不用擔心命名衝突,模塊與模塊之間是沒有直接關係的,內部的變量作用域僅限於模塊內,exports的變量除外,這樣就能更有效的組織代碼,也能更好的團隊協作。
最後,開發者不必手動解析模塊或者庫的依賴項,在之前寫代碼的時候,我們要想使用一個依賴於Jq的插件,那麼我們首先要做的就是先再頁面中通過script標籤把jq引入,然後再引入插件代碼,繼而才能使用,現在有了模塊化概念,我們可以直接在使用的插件代碼中引入jq模塊而不必手動去每次引入。
現在我們搞懂了Module的優勢,然而,因爲CommonJS是爲服務器端制定的規範,所以難免有些考慮不周。不周的地方就是同步加載的問題。我們知道,服務器上的文件,基本都存在本地硬盤或者緩存中,當我們導入一個模塊的時候,能夠很快的從硬盤或者緩存中讀取,這樣一來,即使是同步加載,速度也可以得到保障。
在客戶端情況就不同了。

    var math = require('math');
  math.add(2, 3);

看上面的代碼,你一定發現了,math模塊要想能夠使用,必須在require加載完畢之後才能夠使用,否則將拋出undefined錯誤,那麼問題來了,客戶端要想加載math模塊,就需要向服務器發送請求,其加載的速度與網絡環境息息相關,這就意味着,如果加載速度慢,js腳本會停留在這裏等待直至加載完成,同步加載的問題已經很明顯了。

AMD

這麼強大的功能,前端竟然不能使用,這怎麼行!
AMD應運而生,是“Asynchronous Module Definition”的縮寫,意思就是”異步模塊定義”。它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。所有依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成之後,這個回調函數纔會運行。好吧,看到回調,想必你已經明白了一切!

AMD也採用require()語句加載模塊,但是不同於CommonJS,它要求兩個參數:

require([module], callback);

第一個參數[module],是一個數組,裏面的成員就是要加載的模塊;第二個參數callback,則是加載成功之後的回調函數。如果將前面的代碼改寫成AMD形式,就是下面這樣:

require(['math'], function (math) {
    math.add(2, 3);
  });

math.add()與math模塊加載不是同步的,瀏覽器不會發生假死。AMD規範的主要實踐就是現在前端流行的js庫:require.js。

RequireJS就是實現了AMD規範的呢。

一、爲什麼要用require.js?

最早的時候,所有Javascript代碼都寫在一個文件裏面,只要加載這一個文件就夠了。後來,代碼越來越多,一個文件不夠了,必須分成多個文件,依次加載。下面的網頁代碼,相信很多人都見過。

    <script src="1.js"></script>
  <script src="2.js"></script>
  <script src="3.js"></script>
  <script src="4.js"></script>
  <script src="5.js"></script>
  <script src="6.js"></script>

這樣的寫法有很大的缺點。首先,加載的時候,瀏覽器會停止網頁渲染,加載文件越多,網頁失去響應的時間就會越長;其次,由於js文件之間存在依賴關係,因此必須嚴格保證加載順序(比如上例的1.js要在2.js的前面),依賴性最大的模塊一定要放到最後加載,當依賴關係很複雜的時候,代碼的編寫和維護都會變得困難。

require.js的誕生,就是爲了解決這兩個問題:

(1)實現js文件的異步加載,避免網頁失去響應;   
(2)管理模塊之間的依賴性,便於代碼的編寫和維護。

使用方法,去官網下載require.js,放在js子目錄下。加載到頁面中即可。

 <script src="js/require.js" defer async="true" ></script>

async屬性表明這個文件需要異步加載,避免網頁失去響應。IE不支持這個屬性,只支持defer,所以把defer也寫上。
加載require.js以後,下一步就要加載我們自己的代碼了。假定我們自己的代碼文件是main.js,也放在js目錄下面。那麼,只需要寫成下面這樣就行了:

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

data-main屬性的作用是,指定網頁程序的主模塊。在上例中,就是js目錄下面的main.js,這個文件會第一個被require.js加載。由於require.js默認的文件後綴名是js,所以可以把main.js簡寫成main。
具體mainjs的寫法不再多說,可以去requirejs官網

CMD

CMD 即Common Module Definition通用模塊定義,CMD規範是國內發展出來的,就像AMD有個requireJS,CMD有個瀏覽器的實現SeaJS,SeaJS要解決的問題和requireJS一樣,只不過在模塊定義方式和模塊加載(可以說運行、解析)時機上有所不同

AMD推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊
CMD推崇就近依賴,只有在用到某個模塊的時候再去require
AMD和CMD最大的區別是對依賴模塊的執行時機處理不同,注意不是加載的時機或者方式不同

如下模塊通過SeaJS/RequireJS來加載, 執行結果會是怎樣?

define(function(require, exports, module) {
    console.log('require module: main');

    var mod1 = require('./mod1');
    mod1.hello();
    var mod2 = require('./mod2');
    mod2.hello();

    return {
        hello: function() {
            console.log('hello main');
        }
    };
});

先試試SeaJS的執行結果

require module: main
require module: mod1
hello mod1
require module: mod2
hello mod2
hello main

再來是RequireJS的執行結果

require module: mod1
require module: mod2
require module: main
hello mod1
hello mod2
hello main

可以看到,SeaJS更符合我們的邏輯。這裏要注意是執行而不是加載,二者都是異步加載的,但是SeaJS只有在使用的時候才解析,而RequireJS加載完成後便立刻解析了。

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