在JavaScript模塊一文中介紹瞭如何組織代碼實現模塊化。模塊化能隱藏私有的屬性和方法,只暴露出公共接口。這樣別人就不須要從頭開始造輪子,直接用你的模塊中定義的功能就好了。並且保證了命名空間,不會出現命名衝突。node
但若是沒有一套規範作參照,每一個人都隨本身的喜愛定義模塊,使用別人的模塊就會出現障礙。本篇就介紹一下通用的定義JS模塊的規範:CommonJS和AMDjquery
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(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規範,均可以將咱們自定義的模塊規範化,便於供他人使用。