標籤:style blog http java 使用 os 檔案 io
背景
NodeJS的一套比較簡潔 Moudles 規範, 使得在伺服器端的模組化變得更加簡單。很長一段時間,很多公司或者項目都有自己的一套模組化機制, 卻未能形成一套統一的標準, NodeJS的Moudles規範如果運用在瀏覽器端會存在一些問題,如
- 伺服器端JS模組檔案就在本地,瀏覽器端則需要通過網路請求
- 伺服器端可以很容易的實現同步或非同步請求模組,瀏覽器端代價會比較大
採用XHR的方式實現同步請求模組,存在明顯的跨域缺陷,而使用script的方式,預設是非同步。 在這樣的背景下, CommonJS的Modules/Wrappings、AMD、CMD等規範應時而生。
Modules/Wrappings 規範的約定如下,更多請參考:http://wiki.commonjs.org/wiki/Modules/Wrappings
- 使用modules.declare定義模組
- declare 只接受一個參數,類型可以是函數也可以是object,參數又稱為module factory
- 如果factory為function,三個參數分別為require、exports、module,factory使用傳回值或exports匯出模組API
- factory如果是物件類型,則將該對象作為模組輸出
A basic wrapped module:
module.declare(function(require, exports, module){ exports.foo = "bar";});AMD 規範
AMD:全稱為非同步模組定義, 是專門為瀏覽器中JavaScript環境設計的規範. 規範本身非常簡單, 概括如下:
define(id?, dependencies?, factory);
id: 模組名
dependencies: 依賴的模組,如果預設,預設為 ["require", "exports", "module"]
factory: 模組工廠,通過排列組合這種模組定義能滿足很多情境的需求
AMD在定義自己的Module規範的同時,也簡單相容了CommonJS的Modules/Wrappings。
匿名模組與具名模組
define(["beta"], function (beta) { exports.verb = function() { return beta.verb(); }}); define(‘alpha‘, ["beta"], function (beta) { exports.verb = function() { return beta.verb(); }});
匿名模組也帶來一些好處,如減少維護成本,使得模組的原始碼與它的標識分離。從而實現在不改變模組代碼的情況下移動源碼檔案的位置等。
Simplified CommonJS wrapping
define(function(require, exports, module){ var query = require("query"); var on = require("on"); ...});
AMD模組載入器將會掃描該工廠函數的require調用,並自動的在運行該Factory 方法之前載入他們, 也是和CMD規範的重要區別, 很多人在討論到AMD、CMD上都會探討這個問題,有許多爭議, 不過這點也正是體現了AMD規範的核心:模組依賴必須在真正執行具體的factory方法前解決。
RequireJS是目前最流行的AMD載入器,從代碼中可以看出,為了支援CommonJS Wrapping, define會解析工廠函數的FunctionBody,去掉注釋,並將require的模組匹配出來, 加到dependencies數組中。
var commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, cjsRequireRegExp = /[^.]\s*require\s*\(\s*["‘]([^‘"\s]+)["‘]\s*\)/g; if (!deps && isFunction(callback)) { deps = []; if (callback.length) { callback .toString() .replace(commentRegExp, ‘‘) .replace(cjsRequireRegExp, function (match, dep) { deps.push(dep); }); deps = (callback.length === 1 ? [‘require‘] : [‘require‘, ‘exports‘, ‘module‘]).concat(deps); }}
由於實際項目中的FunctionBody情況比較複雜,cjsRequireRegExp,這個匹配規則不是很強大,如果在實際項目中發現require有問題的地方, 可以將FunctionBody手動匹配一下這個Regex, 看是否有問題。
exports有什麼好處?
1、相對使用return輸出對象, 由於js語言特性決定,代碼依次執行, 當模板存在循環相依性關係時,這種export匯出就會特別有用。
2、相對於命名空間匯出API,它的好處在於它不會影響全域空間, 而且匯出的API所屬的對象名稱不會和模組代碼強耦合。 這也是YUI的模組API匯出方式採用命名空間的一點局限。
資料或者獨立API模組
對於一些僅僅提供資料或者獨立方法的模組,factory格式變為obj就可以了, 如:
define({ camelCase: function(x) { return string.camelCase(‘abc ABC‘); }});AMD Plugin Dependency
一個外掛程式依賴應該被描述為如下格式:
[Plugin Module ID]![resource ID]
目前流行的RequireJS、curl、Dojo等支援AMD的載入器都支援外掛程式, 如載入各種格式的資料,在domReady 完成後在執行操作等。由於“!”被外掛程式文法佔用,所以在一般資源請求中,請不要使用帶有“!”的URL。 完整的外掛程式清單可以參考http://requirejs.org/docs/download.html#plugins
下面是兩個RequireJS使用插入的例子:
define([ ‘backbone‘, ‘text!templates.html‘], function( Backbone, template ){ // ...}); require([‘domReady!‘], function (doc) { //This function is called once the DOM is ready, //notice the value for ‘domReady!‘ is the current //document.});如果讓自己的模組支援AMD規範
jquery是如何做的?
if ( typeof define === "function" && define.amd && define.amd.jQuery ) { define("jquery", [], function () { return jQuery; } );}
需要在載入中設定:
define.amd = { jQuery: true}; 小結
AMD僅僅提供了模組定義的規則,在實際項目使用中還需要考慮一些模組合并、打包方案等。
目前,實現AMD規範的庫有RequireJS 、curl 、Dojo 、bdLoad 、JSLocalnet 、Nodules等,也有很多庫支援AMD規範,即將自己作為一個模組存在,如MooTools 、jQuery 、qwery 、bonzo、firebug等。 在前端模組化發展如此快的今天,未來應該會有很多庫或者模組會支援作為滿足AMD、CMD or CommonJS Module規範的模組存在。
隨著模組化的發展,高品質的模組會越來越多,為了讓大家有統一的規則來使用模組, 模組正常化就顯得格外重要。 而不管是哪一種模組規範,未來在瀏覽器端前端是否也可以像Nodejs一樣靈活的去使用滿足相同規範,甚至各種不同規範的優秀模組, 這樣就不用將代碼局限同一種架構中, 或許只是自己瞎想。
參考:
http://wiki.commonjs.org/wiki/Modules/1.1.1#Module_Identifiers
https://github.com/amdjs/amdjs-api
https://github.com/requirejs/example-multipage
http://requirejs.org/docs/whyamd.html