CommonJS和AMD

JavaScript模塊一文中介紹瞭如何組織代碼實現模塊化。模塊化能隱藏私有的屬性和方法,只暴露出公共接口。這樣別人就不須要從頭開始造輪子,直接用你的模塊中定義的功能就好了。並且保證了命名空間,不會出現命名衝突。node

但若是沒有一套規範作參照,每一個人都隨本身的喜愛定義模塊,使用別人的模塊就會出現障礙。本篇就介紹一下通用的定義JS模塊的規範:CommonJS和AMDjquery

CommonJS

Nodejs的模塊系統就採用CommonJS模式。CommonJS標準規定,一個單獨的文件就是一個模塊,模塊內將須要對外暴露的變量放到exports對象裏,能夠是任意對象,函數,數組等,未放到exports對象裏的都是私有的。用require方法加載模塊,即讀取模塊文件得到exports對象。npm

例如定義個hi.js:數組

var str = 'Hi';

function sayHi(name) {
  console.log(str + ', ' + name + '!');
}

module.exports = sayHi;

在hi模塊中定義了sayHi函數,用exports將它暴露出去。未被暴露出去的變量str是沒法被外部訪問的。其餘模塊要用這個函數的話,須要先require這個hi模塊:瀏覽器

var Hi = require('./hi');
Hi('Jack');     // Hi, Jack!

第一行裏的變量Hi,其實就是hi.js裏用module.exports = sayHi;輸出的sayHi函數。服務器

注意上面用require加載時寫的是相對路徑,讓Nodejs去指定路徑下加載模塊。若是省略相對路徑,默認就會在node_modules文件夾下找hi模塊,那極可能由於找不到而報錯。若是你加載的是Node內置模塊,或npm下載安裝後的模塊,能夠省略相對路徑,例如:網絡

var net = require('net');
var http = require('http');

JavaScript模塊一文中介紹過JS模塊的寫法,這裏出現的module,exports,require是JS的新語法嗎?不是新語法,只是CommonJS的語法糖。Node會將上述hi.js編譯成:異步

// 準備module對象:
var module = {
    id: 'hi',
    exports: {}
};
var load = function (module) {
    function sayHi(name) {
        console.log('Hi, ' + name + '!');
    }

    module.exports = sayHi;
    return module.exports;
};
var exported = load(module);
// 保存module:
save(module, exported);

上來先定義一個module對象,屬性id是該模塊名即文件名,屬性exports就是最終須要暴露出來的對象。所以咱們在hi.js代碼裏明明沒有var聲明過module對象,但module.exports = sayHi;竟然不會報錯。緣由就是module是Node在加載js文件前已經事先替咱們準備好了。最後Node會用自定義函數save將這個module對象保存起來。模塊化

Node保存了全部的module對象後,當咱們用require()獲取module時,Node會根據module.id找到對應的module,並返回module. exports,這樣就實現了模塊的輸出。函數

CommonJS是同步的,意味着你想調用模塊裏的方法,必須先用require加載模塊。這對服務器端的Nodejs來講不是問題,由於模塊的JS文件都在本地硬盤上,CPU的讀取時間很是快,同步不是問題。

但在客戶端瀏覽器用CommonJS加載模塊將取決於網速,若是採用同步,網絡情緒不穩定時,頁面可能卡住。所以針對客戶端出現了AMD異步模塊定義。

AMD

AMD(Asynchronous Module Definition)異步加載模塊。AMD標準規定,用define來定義模塊,用require來加載模塊:

define(id, [depends], factory);  
require([module], callback);

先看define定義模塊的示例:

define(['module1', 'module2'], function (module1, module2) {
    ……
    return { … };
});

第一個參數id是你的模塊名,上例省略。事實上這個id沒什麼用,就是給開發者看的。

第二個參數[depends]是該模塊所依賴的其餘模塊,上例中該模塊依賴另兩個模塊module1和module2。若是你定義的模塊不依賴其餘任何模塊,該參數能夠省略。

第三個參數factory,生產出(即return出)一個對象供外部使用(我看到有的資料裏將該參數命名爲callback,感受callback語義方面有點曖昧,命名成factory更貼切)。

定義好模塊後,再看用require加載模塊的示例:

require(['yourModule1', 'yourModule2'], function (yourModule1, yourModule2) {
    ……
});

能夠和CommonJS的require方法對比一下,AMD的require多了一個參數callback。

第一個參數是須要加載的模塊名,能夠是一個數組,意思是加載多個模塊。加載模塊時,若是該模塊的define裏有[depends]參數,就會先加載[depends]裏指定的依賴模塊。加載完[depends]裏的依賴模塊後,運行define裏的factory方法獲得該模塊的實例。而後將該實例依次傳入第二參數callback做爲參數。

第二個參數callback回調函數,參數是根據第一個參數,依次加載模塊後獲得的實例對象。等第一個參數指定的模塊所有加載完後,會執行該callback。

require加載過程分幾步:

第一步,將依賴列表的模塊名轉換爲URL,常見的是basePath + moduleID + ".js"。例如:

require(["aaa", "bbb"], function(a, b){});
//將在require內部被轉換成:
//require(["http://1.2.3.4/aaa.js", 
//         "http://1.2.3.4/bbb.js"], function(a, b){});

第二步,從檢測對象數組裏查看該模塊是否被加載過(狀態是否爲2),或正在被加載(狀態是否爲1)。只有歷來沒加載過此節點,纔會進入加載流程。

第三步,將該模塊的狀態設爲1,表示正在被加載。建立script標籤,模塊的URL添加到src裏,並綁定onload,onreadystatechange,onerror事件,將script插入DOM樹中開始加載。瀏覽器加載完後會觸發綁定的on事件,裏面能夠將模塊的狀態改成2,代表已經加載完畢。

第四步,將模塊的URL,依賴列表,狀態等構建一個檢測對象數組,供第二步檢測用。

你能夠將全部須要用到模塊裏變量和方法的代碼,都放到callback中。require下面的代碼將繼續執行,這樣就能避免瀏覽器卡住假死的狀況。

實現了AMD規範的JS庫有:require.js。看一個用require.js的例子:

//myModule1.js
define(function() {
    var m1 = {};
    m1.say = function() {
        console.log('Hi myModule1!');
    }
    return m1;
});

//myModule2.js
define(['myModule3'], function(m3) {
    var m2 = {};
    m2.say = function() {
        m3.say();
        console.log('Hi myModule2!');
    }
    return m2;
});

//myModule3.js
define(function() {
    var m3 = {};
    m3.say = function() {
        console.log('Hi myModule3!');
    }
    return m3;
});

//HTML
console.log("before require");
require(['myModule1','myModule2'], function(m1, m2){         
    m1.say();
    m2.say();
});
console.log("after require");
//before require
//after require
//Hi myModule1!
//Hi myModule3!
//Hi myModule2!

HTML中先執行console.log(「before require」);,打印出第一條。

而後執行require,由於require.js是AMD異步加載,因此執行require後的console.log(「after require」);語句,打印出第二條。

執行require依次加載模塊。先加載myModule1。發現myModule1的define裏無[depends]參數,不依賴其餘模塊。所以運行define裏的factory方法得到myModule1的實例對象m1。

再加載myModule2。發現myModule2依賴myModule3,所以加載myModule3。myModule3無[depends]參數,不依賴其餘模塊。所以運行factory得到myModule3的實例對象m3。

加載完myModule3後,運行myModule2的factory得到myModule2的實例對象m2。

myModule2也加載完畢後,require的全部模塊均加載完畢,運行回調函數。前面得到的實例對象m1和m2做爲參數傳給回調函數。

運行m1.say();打印出第三條

運行m2.say();,根據方法定義,先運行m3.say();打印出第四條,再運行console.log(‘Hi myModule2!’);打印出第五條。

有些庫並無嚴格遵守AMD,用define()函數定義的模塊,此時須要用shim技術(所謂shim就是墊片,即把一個新的API引入到一箇舊環境中,而且僅靠舊環境中已有的手段來實現)。例如require.js就爲require.config定義了shim屬性,咱們用它來加載並無採用AMD規範的underscore和backbone這兩個庫:

require.config({
    shim: {
        'underscore':{
            exports: '_'
        },
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        }
    }
});

固然本篇並非介紹require.js,更多require.js的用法請自行參照官網

總結

不管是被Nodejs採用的服務器端的CommonJS規範,仍是客戶端的AMD規範,均可以將咱們自定義的模塊規範化,便於供他人使用。

做者:張歆琳 連接:http://www.jianshu.com/p/fc858878d891 來源:簡書 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索