JS模塊化:CommonJS和AMD(Require.js)


早期的JS中,是沒有模塊化的概念的,這一情況直到09年的Node.js橫空出世時有了好轉,Node.js將JS作為服務端的編程語言,使得JS不得不尋求模塊化的解決方案。

模塊化概念

在JS中的模塊是針對單個文件的,即一個文件是一個模塊,要使用這個模塊就加載該文件即可。

CommonJS

node.js的模塊系統,是參照CommonJS規范實現的。

定義模塊和加載模塊

在CommonJS中,有一個全局性方法require(),用於加載模塊,而module.exports用於導出當前文件的模塊。

假定有一個外部模塊Utils.js,那么該模塊需要這么寫:

 1 // 定義類
 2 function Utils(name) {
 3     this._name = name;
 4 }
 5 
 6 // 定義類方法
 7 Utils.prototype.sayHi = function() {
 8     console.log("Hi, I am " + this._name);
 9 };
10 
11 // 定義靜態方法
12 Utils.add = function(a, b) {
13     return a + b;
14 }
15 
16 // 將類 Utils 作為當前文件的模塊導出
17 module.exports = Utils;

加載模塊並使用的方法如下:

1 // 加載外部模塊文件,位於當前文件夾下的 Utils.js,導入后該模塊放入變量 Utils 中
2 var Utils = require('./Utils.js');
3 
4 var obj = new Utils("Li Lei");
5 obj.sayHi(); // Hi, I am Li Lei
6 
7 console.log(Utils.add(10, 20)); // 30

可以導出為任意類型

我們就上面的例子稍加改動,Utils.js如下:

 1 function Utils(name) {
 2     this._name = name;
 3 }
 4 Utils.prototype.sayHi = function() {
 5     console.log("Hi, I am " + this._name);
 6 };
 7 Utils.add = function(a, b) {
 8     return a + b;
 9 }
10 module.exports = {version: 0.1, utils: Utils};

執行的js代碼如下:

1 var ex = require('./Utils.js');
2 
3 console.log(ex.version); // 0.1
4 
5 var obj = new ex.utils("Li Lei");
6 obj.sayHi(); // Hi, I am Li Lei
7 
8 console.log(ex.utils.add(10, 20)); // 30

我們可以發現可以導出任意的一個對象,而使用require導入的就是導出的那個對象。

不適用於瀏覽器

在Node.js中,require方法是同步執行的,即只有加載並解析之后,代碼才會向下執行,但是在瀏覽器中,加載腳本是異步執行的。

AMD

有了服務器端模塊以后,很自然地,大家就想要客戶端模塊。而且最好兩者能夠兼容,一個模塊不用修改,在服務器和瀏覽器都可以運行。

但是瀏覽器並不適用CommonJS規范,因為服務器端同步加載不是一個問題,所有的模塊都存放在本地硬盤,等待時間就是硬盤的讀取時間。但是,對於瀏覽器,這卻是一個大問題,因為模塊都放在服務器端,等待時間取決於網速的快慢,如果采用同步加載的策略,可能要等很長時間才能加載好文件,這段時間里瀏覽器會處於"假死"狀態。

因此,瀏覽器端的模塊,不能采用"同步加載"(synchronous),只能采用"異步加載"(asynchronous)。這就是AMD規范誕生的背景。

概念

AMD是"Asynchronous Module Definition"的縮寫,意思就是"異步模塊定義"。它采用異步方式加載模塊,模塊的加載不影響它后面語句的運行。所有依賴這個模塊的語句,都定義在一個回調函數中,等到加載完成之后,這個回調函數才會運行。

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

require([module], callback);

第一個參數[module],是一個數組,里面的成員就是要加載的模塊;第二個參數callback,則是加載成功之后的回調函數。

目前,主要有兩個Javascript庫實現了AMD規范:require.js和curl.js。下面我們主要就require.js來看看如何在瀏覽器里實現模塊化。

Require.js

官網地址:http://www.requirejs.cn/

這個是非常流行的一個實現AMD規范的JS庫,我們下載其最新版本(目前是2.1.11),我們下面用Require.js來實現上面CommonJS里的例子。

定義模塊和加載模塊

AMD規范里,定義模塊和加載模塊都和CommonJS規范不同,使用require加載模塊但是帶有回調函數,而定義模塊則使用define函數。

我們先看看Utils模塊的寫法:

 1 define(function (){
 2     // 定義類
 3     function Utils(name) {
 4         this._name = name;
 5     }
 6 
 7     // 定義類方法
 8     Utils.prototype.sayHi = function() {
 9         console.log("Hi, I am " + this._name);
10     };
11 
12     // 定義靜態方法
13     Utils.add = function(a, b) {
14         return a + b;
15     }
16     
17     // 將類 Utils 作為當前文件的模塊返回
18     return Utils;
19 });

加載和使用的方法如下:

 1 <html>
 2 <head>
 3     <title>Test</title>
 4     <!-- 引入require.js文件 -->
 5     <script src="require.js"></script>
 6     <script>
 7     // 配置各個模塊地址
 8     require.config({
 9     paths: {
10       "Utils": "./js/lib/Utils"
11     }
12   });
13 
14     // 如果模塊地址都放在同一個文件夾中,可以用下面的簡寫方式
15     // require.config({
16     //     baseUrl: "./js/lib",
17   //   paths: {
18   //     "Utils": "Utils"
19   //   }
20   // });
21 
22     // 加載指定模塊
23     require(["Utils"], function(Utils) {
24         // 模塊加載完畢之后,模塊中導出的對象會作為參數傳入,再回調中直接使用即可
25         
26         var obj = new Utils("Li Lei");
27         obj.sayHi(); // Hi, I am Li Lei
28 
29         console.log(Utils.add(10, 20)); // 30
30     });
31     </script>
32 </head>
33 <body>
34 </body>
35 </html>

多個模塊的情況

我們定義的模塊如果還依賴其它的模塊,可以這么寫:

1 // 當前定義的模塊依賴 Socket 模塊
2 define(["Socket"], function (){
3 // 當前定義的模塊依賴 Socket 及 Game 模塊
4 define(["Socket", "Game"], function (){

加載模塊時也可以加載多個模塊:

1 // 加載 Utils 和 Game 模塊,加載好之后,對應模塊的導出對象會在回調中作為參數分別傳入
2 require(["Utils", "Game"], function(Utils, Game) {

更多地用法,可以參考官方文檔。

總結一下

我們發現,CommonJS和AMD的寫法無論是定義模塊還是加載模塊,都是存在差異的,但是模塊內部的寫法是基本一致的,所以通過一定的技巧,可以寫出兼容兩種標准的模塊,如下:

 1 (function(golbal, factory){
 2     // AMD
 3     if(typeof define === "function" && define.amd)
 4         define(factory);
 5     // CommonJS
 6     else if(typeof require === "function" && typeof module === "object" && module && module.exports)
 7         module.exports = factory();
 8 })(this, function(){
 9     // 定義類
10     function Utils(name) {
11         this._name = name;
12     }
13 
14     // 定義類方法
15     Utils.prototype.sayHi = function() {
16         console.log("Hi, I am " + this._name);
17     };
18 
19     // 定義靜態方法
20     Utils.add = function(a, b) {
21         return a + b;
22     }
23     
24     // 將類 Utils 作為當前文件的模塊返回
25     return Utils;
26 });

當然,知道了兩種標准的差異之后,我們在使用時,也可以自己修改一個標准的模塊為另一個標准可以支持的寫法了。

另外,大部分類庫都會提供多種模塊格式的代碼,比如AMD和CommonJS等格式,選擇我們需要的格式即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM