譯者:CommonJS Modules/1.0 是目前JavaScript模組化的事實標準,雖然其已經被 CommonJS Modules/1.1 所替代,但是1.0的適用範圍非常廣,支持者也很多,其中包括Flusspferd, GLUEscript, GPSEE, JSBuild, Narwhal (0.1), Persevere, RingoJS, SproutCore 1.1/Tiki, node.js, TeaJS (formerly v8cgi), CouchDB, Smart Platform, Yabble, Wakanda, XULJet等,所以翻譯此規範還是很有必要的,以下為正文。
此規範指出瞭如何編寫可以在同類模組系統中所共用的模組,這類模組系統可以同時在客戶端和服務端,以安全的或者不安全的方式已經被實現了或者通過語法擴充套件可以被未來的系統所支援。這些模組需要提供頂級作用域的私有性,並提供從其他模組匯入單例物件到自身並且可以匯出自身API的能力。含蓄的說,這個規範定義瞭如果一個模組系統要支援共用模組,那麼它需要提供的最少的功能特性。
契約
模組上下文
1.在一個模組中,存在一個自由的變數”require”,它是一個函式。
- 這個”require”函式接收一個模組識別符號。
- “require”返回外部模組所輸出的API。
- 如果出現依賴閉環(dependency cycle),那麼外部模組在被它的傳遞依賴(transitive dependencies)所require的時候可能並沒有執行完成;在這種情況下,”require”返回的物件必須至少包含此外部模組在呼叫require函式(會進入當前模組執行環境)之前就已經準備完畢的輸出。(譯者:如果難理解,看下面的例子。)
- 如果請求的模組不能返回,那麼”require”必須丟擲一個錯誤。
2. 在一個模組中,會存在一個名為”exports”的自由變數,它是一個物件,模組可以在執行的時候把自身的API加入到其中。
3. 模組必須使用”exports”物件來做為輸出的唯一表示。
模組識別符號
- 模組識別符號是一個以正斜槓分隔的多個”term”組成的字串。
- 一個term必須是一個駝峰格式的識別符號,”.”或者”..”。
- 模組識別符號可以不加副檔名,比如”.js”。
- 模組識別符號可以是「相對的」或者「頂級的」(top-level)。如果一個模組識別符號的第一個term是 “.”或者”..”,那麼它是「相對的」。
- 頂級識別符號是概念上的模組名稱空間的根。
- 相對識別符號是相對於在其內部呼叫了”require”的模組的識別符號來進行解析的。
未規範
此規範對如下關於協同工作能力方面的重要內容未進行規範:
- 模組是否可以通過資料庫,檔案系統或者工廠函式進行儲存,或者可以通過連結庫進行內部交換。
- 模組載入器是否應該支援PATH變數用來解析模組識別符號。
單元測試
- Unit Tests at Google Code by Kris Kowal
- Unit Tests Git Mirror by Ash Berlin
例項程式碼
math.js
1 2 3 4 5 6 7 |
exports.add = function() { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) { sum += args[i++]; } return sum; }; |
increment.js
1 2 3 4 |
var add = require('math').add; exports.increment = function(val) { return add(val, 1); }; |
program.js
1 2 3 |
var inc = require('increment').increment; var a = 1; inc(a); // 2 |
依賴閉環解釋(譯者新增)
因為node.js完全實現了CommonJS Modules/1.0規範,那麼我們用其來解釋CommonJS Modules/1.0中的依賴閉環問題。看如下程式碼:
a.js
1 2 3 4 5 6 |
console.log('a starting'); exports.done = false; var b = require('./b.js'); console.log('in a, b.done = %j', b.done); exports.done = true; console.log('a done'); |
b.js
1 2 3 4 5 6 |
console.log('b starting'); exports.done = false; var a = require('./a.js'); console.log('in b, a.done = %j', a.done); exports.done = true; console.log('b done'); |
main.js
1 2 3 4 |
console.log('main starting'); var a = require('./a.js'); var b = require('./b.js'); console.log('in main, a.done=%j, b.done=%j', a.done, b.done); |
當main.js載入a.js的時候,a.js載入b.js,同時,b.js想要載入a.js,這時候就產生了依賴閉環的問題,為了避免無限迴圈,需要打破這個閉環。根據CommonJS Modules/1.0規範中的說明「在這種情況下,”require”返回的物件必須至少包含此外部模組在呼叫require函式(會進入當前模組執行環境)之前就已經準備完畢的輸出。」,有些繞,讓我們從依賴閉環產生的地方跟蹤,b.js需要require a.js,這裡b.js做為當前模組,a.js相對於b.js來說是外部模組,那麼a.js的輸出應該是在其require b.js之前(即「進入當前模組執行環境」)就應該返回,執行過程如下:
a.js
1 2 3 4 5 6 7 |
console.log('a starting'); exports.done = false; // 只執行到這裡,然後exports返回給呼叫模組(b.js),以下被丟棄 var b = require('./b.js'); console.log('in a, b.done = %j', b.done); exports.done = true; console.log('a done'); |
然後b.js繼續執行完成。以下是執行結果:
1 2 3 4 5 6 7 8 9 |
$ node main.js main starting a starting b starting in b, a.done = false b done in a, b.done = true a done in main, a.done=true, b.done=true |
注意,雖然main.js同時require了a.js和b.js,但是根據node.js的模組快取策略,模組只執行一次。