1. 程式人生 > >AMD規範:簡單而優雅的動態載入JavaScript程式碼

AMD規範:簡單而優雅的動態載入JavaScript程式碼

CommonJS 提出了一種用於同步或非同步動態載入JavaScript程式碼的API規範,非常簡單卻很優雅,稱之為AMD(Modules/AsynchronousDefinition)。RequireJS和NodeJS的Nodules已經實現了這個API,而Dojo也將馬上完全支援(Dojo1.6)。規範本身非常簡單,甚至只包含了一個API:
define([module-name?], [array-of-dependencies?], [module-factory-or-object]);

通過引數的排列組合,這個簡單的API可以從容應對各種各樣的應用場景,如下所述。

匿名模組

在這種場景下,無需輸入模組名,即省略第一個引數,僅包含後兩個引數:依賴模組的列表以及回撥函式,例如一個簡單的匿名模組可以用如下程式碼定義:

define(["math"], function(math){
  return {
    addTen: function(x){
      return math.add(x, 10);
    }
  };
});
在這裡,第一個引數表示依賴的模組列表,即math模組。一旦所有依賴的模組被載入完成,那麼第三個引數定義的回撥函式將被執行,依賴模組的引用作為引數傳遞給回撥函式。

如例子中所示,如果模組名被省略不寫,那麼這是一個匿名模組。通過這種強大的方式,模組的原始碼與它的標識可以做到不相關。從而可以在不改變模組程式碼的情況下移動原始碼檔案的位置。這個技術遵循了基本的

DRY(Don't Repeat Yourself)原則,避免了模組標識的多次儲存(檔名/路徑資訊不會在程式碼中重複)。這不僅使得模組的開發變得更加容易,而且為模組的重用提供了極大的靈活性。

下面我們看如何從一個Web頁面載入這個模組。我們假設上面的模組儲存在檔案adder.js中。使用RequireJS,我們可以用下面方式來載入這個模組:
<script src="require.js"></script>
<script>
require(["adder"], function(adder){
  // ready to use adder
});
</script> 
一旦程式碼被執行,RequireJS將會自動去呼叫adder模組所有的依賴模組。載入完畢之後,我們就可以通過回撥函式的adder引數來使用前面定義的匿名模組。例子中可以看到,adder.js裡儲存的是定義的匿名模組,實際上我們可以用任何檔案/路徑來包含這個模組,為模組的重用提供了方便(Java中的檔名/路徑和類名/包的必須一致性實際上就為類級別的重用造成了不便)。require函式用於載入任何一個模組,後面將多次使用。

對於匿名模組的使用有一些注意事項。比如每個檔案中只能包含一個匿名模組,而且匿名模組只能被載入器載入,即只能用require來載入。也可以這麼理解,實際上匿名模組並不是沒有名字,而是在使用時進行命名的模組,例子中就是adder。

資料封裝:新的JSON-P

對於一些僅僅提供資料或者獨立方法(不依賴於其它模組的方法)的模組,可以簡單的用如下方式來定義:
define({
  name:"some data"
});
這個和JSON-P非常像,但是卻有一個顯著的優點:它使得JSON-P資料可以現在靜態檔案中,而並不需要動態的回撥過程。這也使得內容是可cache的,而且是CDN友好的。

封裝CommonJS模組

CommonJS也是一套RIA框架,其中的模組可以通過AMD來進行封裝,從而可以用define的方式很容易的進行非同步裝載,在這裡我們可以省略前2個引數,僅包含回撥函式,但回撥函式的第一個引數是require方法,第二個引數是exports物件,它定義了模組本身,回撥函式裡的require的使用將被自動進行動態載入。例如:
define(function(require, exports){
//math是標準CommonJS模組:
  var math = require("math");
  exports.addTen = function(x){
    return math.add(x, 10);
  };
}); 
需要注意這種形式要求模組載入器掃描require函式。require呼叫必須寫成require(“…”)的形式才能被正確識別從而正常工作。這在一些瀏覽器不能正常工作(例如默寫版本的Opera移動版,以及PS3)。當然,如果在部署前對程式碼進行了build,這將完全不成問題。你也可以封裝CommonJS模組,並手動的指定依賴,這種方式使得我們也可以引用CommonJS變數,從而我們可以包含標準的require和exports變數:
define(["require", "exports", "math"], function(require, exports){
// standard CommonJS module:
  var math = require("math");
  exports.addTen = function(x){
    return math.add(x, 10);
  };
}); 
 

完整的模組定義

一個完整的模組定義包含了模組名,依賴,以及回撥函式。這種形式的優點是模組可以包含在另外的檔案中,或者可以用script標記載入的地址中。這是build工具自動生成的規範模式,使得多個依賴可以被打包在同一個檔案中,這種格式的例子如下:
define("adder", ["math"], function(math){
  return {
    addTen: function(x){
      return math.add(x, 10);
    }
  };
});

最後,我們來看有模組id,但沒有模組依賴的情況。這種情況用於你想指定模組id,但是這個模組不依賴於其它模組。這時的引數預設是“require”,“exports”和“module”。從而我們可以這樣建立adder模組。
define("adder", function(require, exports){
  exports.addTen = function(x){
      return x + 10;
  };
});
通過這種方式定義的模組可以被RequireJS載入,也可以作為其它模組的依賴被載入,或者直接用require()的形式載入。

綜上所述,這種API看似簡單,卻提供了一種極其靈活的方式來定義模組,適用於各種應用場景,從可被自由移動的匿名模組,到構建後的可被<script>標記載入的模組。當前RequireJS和Dojo實現了這套規範,而JavaScript的Web Server框架NodeJS的Nodules也實現了這個規範。