模塊化JavaScript之風早已席卷而來, CommonJS 、 AMD 、 NodeJS 、 RequireJS 、 SeaJS 、 curljs 等模塊化的JavaScript概念及庫撲面而來,不得不承認,對於前端JavaScript代碼的組織編寫是一次偉大的變革。本文主要參考 snandy 的有關 modular js 系列文章,對SeaJS和RequireJS做一個系統的深入分析及對比。
一、我們為什么要用模塊化的JavaScript
相信大家也都經歷了“過程式的JavaScript”、“面向對象的JavaScript”,再到現在的“面向模塊的JavaScript”。這個“面向模塊的JavaScript”到底能走多久,我們拭目以待。
但是我們需要知道,我們為什么要擁抱模塊化的JavaScript,它到底有什么奇妙之處,讀一下這一篇文章吧—— 前端模塊化開發的價值 ,OK,明白了,它解決了兩大問題——
其一、惱人的命名沖突:
這個確實夠煩人,為了解決之,我們動用閉包作用域、我們動用命名空間,再或者...
其二、煩瑣的文件依賴:
這個問題更不必說,我們做前端支持的,常常要替后端同學們解決這類問題,我們自己也常常忽略這類問題。平常的開發中,我們要對我們的JS文件的加載順序要小心加小心。一個字——確實煩。
好,現在,我們有SeaJS了,這兩大問題變得很輕松就能搞定了,開心哪。
針對第一個問題:通過 exports 暴露接口 。這意味着不需要命名空間了,更不需要全局變量。這是一種徹底的命名沖突解決方案。
針對第二個問題:通過 require 引入依賴 。這可以讓依賴內置,開發者只需關心當前模塊的依賴,其他事情 SeaJS 都會自動處理好。對模塊開發者來說,這是一種很好的 關注度分離 ,能讓程序員更多地享受編碼的樂趣。
二、 瀏覽器中的模塊規范AMD是怎么誕生的?
SeaJS我們領教過了,但是對於它的模塊化寫法,到底是咋回事兒?我們先看看module寫法的演變。直接去看 JavaScript中模塊“寫法” ,使用 統一的風格來寫模塊,非nodeJS打頭陣不可, 以下是node中是寫模塊的一個示例。
1> math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) { sum += args[i++]; } return sum; };
2> increment.js
var add = require('math').add; exports.increment = function(val) { return add(val, 1); };
3> main.js,該文件為入口文件
var inc = require('increment').increment; var a = 1; inc(a); // 2
從以上代碼示例可以看到——
1> node要求一個js文件對應一個模塊。可以把該文件中的代碼想象成是包在一個匿名函數中,所有代碼都在匿名函數中,其它模塊不可訪問除exports外的私有變量
2> 使用exports導出API 3> 使用require加載其它模塊
CommonJS module基本要求 如下——
1> 標示符require,為一個函數,它僅有一個參數為字符串,該字符串須遵守Module Identifiers的6點規定 2> require方法返回指定的模塊API 3> 如果存在依賴的其它模塊,那么依次加載 4> require不能返回,則拋異常 5> 僅能使用標示符exports導出API
打頭陣的NodeJS真是享譽甚高,前端的我們是不是也可以使用同樣的模塊寫法寫模塊呢? 當然nodeJS是最好的效仿對象。因為前后端有一個統一的方式寫JS模塊豈不樂哉!可是,事與願違,讀這個文章吧—— Node.js模塊風格在瀏覽器中的嘗試 ,深入理解之,便能明白,為什么NodeJS模塊寫法為什么不適合瀏覽器端。為了解決之,便出現了 Modules/Wrappings ,顧名思義包裹的模塊。該規范約定如下——
1> 定義模塊用module變量,它有一個方法declare 2> declare接受一個函數類型的參數,如稱為factory 3> factory有三個參數分別為require、exports、module 4> factory使用返回值和exports導出API 5> factory如果是對象類型,則將該對象作為模塊輸出
Modules/Wrappings 的出現使得瀏覽器中實現它變得可能,包裹的函數作為回調。即使用script tag作為模塊加載器,script完全下載后去回調,回調中進行模塊定義。
接着,也就產生了AMD( 全稱為異步模塊定義 )規范了(讀 AMD:瀏覽器中的模塊規范 )。 從名稱上看便知它是適合script tag的。也可以說AMD是專門為瀏覽器中JavaScript環境設計的規范。它吸取了CommonJS的一些優點,但又不照搬它的格式。開始AMD作為CommonJS的 transport format 存在,因無法與CommonJS開發者達成一致而獨立出來。
AMD開創性的提出了自己的模塊風格。但后來又做了妥協,兼容了 CommonJS Modules/Wrappings 。
define(function(require, exports, module) { var base = require('base'); exports.show = function() { // todo with module base } });
不考慮多了一層函數外,格式和Node.js是一樣的。使用require獲取依賴模塊,使用exports導出API。
除了define外,AMD還保留一個關鍵字require。 require 作為規范保留的全局標識符,可以實現為 module loader。
目前,實現AMD的庫有 RequireJS 、 curl 、 Dojo 、 bdLoad 、 JSLocalnet 、 Nodules 等。
三、SeaJS與RequireJS的區別
搞不懂SeaJS和 RequireJS define的區別。 它們都有個全局的define,形參都是三個,且對應的形參名也一樣,會誤認為SeaJS也是AMD的實現。 事實上SeaJS和RequireJS的define前兩個參數的確一樣。 id都為字符串,都遵循 Module Identifiers 。deps都是指依賴模塊,類型都為數組。區別僅在於第三個參數factory,雖然類型也都是函數,但factory的參數意義卻不同。
RequireJS中factory的參數有兩種情況。
a、和deps(數組)元素一一對應。即deps有幾個,factory的實參就有幾個。(官方推薦的模塊定義方法)
define(['a', 'b'], function(a, b){ // todo });
b、固定為require,exports, module(modules/wrappings格式)。
define(function(require, exports, module){ // todo });
這種方式是RequireJS后期向 Modules/Wrappings 的妥協,即兼容了它。而SeaJS的define僅支持RequireJS的第二種寫法:Modules/Wrappings。
注意 :SeaJS遵循的是 Modules/Wrappings 和 Modules/1.1.1 。這兩個規范中都沒有提到define關鍵 字,Modules/Wrapping中要求定義模塊使用module.declare而非define。而恰恰只有AMD規范中有define的定義。 即雖然SeaJS不是AMD的實現,但它卻采用了讓人極容易誤解的標識符define。
對於SeaJS和RequireJS的其他差異,可以讀 SeaJS與RequireJS的異同 。
兩者相同之處——
RequireJS 和 SeaJS 都是模塊加載器,倡導模塊化開發理念,核心價值是讓 JavaScript 的模塊化開發變得簡單自然。
兩者不同之處——
1> 定位有差異。RequireJS 想成為瀏覽器端的模塊加載器,同時也想成為 Rhino / Node 等環境的模塊加載器。SeaJS 則專注於 Web 瀏覽器端,同時通過 Node 擴展的方式可以很方便跑在 Node 環境中。 2> 遵循的規范不同。RequireJS 遵循 AMD(異步模塊定義)規范,SeaJS 遵循 CMD (通用模塊定義)規范。規范的不同,導致了兩者 API 不同。SeaJS 更貼近 CommonJS Modules/1.1 和 Node Modules 規范。 3> 推廣理念有差異。RequireJS 在嘗試讓第三方類庫修改自身來支持 RequireJS,目前只有少數社區采納。SeaJS 不強推,采用自主封裝的方式來“海納百川”,目前已有較成熟的封裝策略。 4> 對開發調試的支持有差異。SeaJS 非常關注代碼的開發調試,有 nocache、debug 等用於調試的插件。RequireJS 無這方面的明顯支持。 5> 插件機制不同。RequireJS 采取的是在源碼中預留接口的形式,插件類型比較單一。SeaJS 采取的是通用事件機制,插件類型更豐富。
四、AMD和CMD的區別
接着引深來看,大家都知道SeaJS遵循CMD(通用模塊定義),RequireJS遵循AMD,那么CMD和AMD到底有什么區別?
上面描述過 AMD模塊定義規范 ,接着我們看看 CMD模塊定義規范 ,可大致了解一下CMD到底是怎樣的規范。對於兩者的差異,讀讀玉泊對它的回答吧—— AMD和CMD區別有哪些?
AMD 是 RequireJS 在推廣過程中對模塊定義的規范化產出。
CMD 是 SeaJS 在推廣過程中對模塊定義的規范化產出。
類似的還有 CommonJS Modules/2.0 規范,是 BravoJS 在推廣過程中對模塊定義的規范化產出。
還有不少⋯⋯
這些規范的目的都是為了 JavaScript 的模塊化開發,特別是在瀏覽器端的。
目前這些規范的實現都能達成瀏覽器端模塊化開發的目的。
區別:
1. 對於依賴的模塊,AMD 是提前執行,CMD 是延遲執行。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible. 2. CMD 推崇依賴就近,AMD 推崇依賴前置。看代碼:
// CMD
define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此處略去 100 行 var b = require('./b') // 依賴可以就近書寫 b.doSomething() // ... }) // AMD 默認推薦的是 define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好 a.doSomething() // 此處略去 100 行 b.doSomething() ... })
雖然 AMD 也支持 CMD 的寫法,同時還支持將 require 作為依賴項傳遞,但 RequireJS 的作者默認是最喜歡上面的寫法,也是官方文檔里默認的模塊定義寫法。
3. AMD 的 API 默認是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,沒有全局 require,而是根據模塊系統的完備性,提供 seajs.use 來實現模塊系統的加載啟動。CMD 里,每個 API 都簡單純粹。 4. 還有一些細節差異,具體看這個規范的定義就好,就不多說了。
五、總結
寫了挺多,當然我承認都是摘錄的,不過把模塊化來源的思路搞定,收獲也就有了。我再理順一下——
1> 我們為什么要用模塊化的JavaScript
最主要的是解決命名沖突、解決文件依賴這兩大問題。
2> 瀏覽器中的模塊規范AMD是怎么誕生的?
使用script tag作為模塊加載器,script完全下載后去回調,回調中進行模塊定義。
3> SeaJS與RequireJS的區別
RequireJS 遵循的是 AMD(異步模塊定義)規范,SeaJS 遵循的是 CMD (通用模塊定義)規范。
4> AMD和CMD的區別
(1) 對於依賴的模塊,AMD 是 提前執行 ,CMD 是 延遲執行 。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible.
(2) CMD 推崇 依賴就近 ,AMD 推崇 依賴前置 。(這一點是非常重要的區別)
(3) AMD 的 API 默認是 一個當多個用 ,CMD 的 API 嚴格區分,推崇 職責單一 。
需要記住—— AMD 是 RequireJS 在推廣過程中對模塊定義的規范化產出。 CMD 是 SeaJS 在推廣過程中對模塊定義的規范化產出。
六、引深
本文僅僅講述了前端Javascript的模塊化開發思想,但是對於其他的HTML和CSS,我們是否也可以采用模塊化話的開發思想來引導呢?答案是肯定的。尤其在前端多人合作開發的情況下,這種模塊化的開發模式會讓我們愛不釋手。這里我只細讀過這樣
模塊化JavaScript之風早已席卷而來, CommonJS 、 AMD 、 NodeJS 、 RequireJS 、 SeaJS 、 curljs 等模塊化的JavaScript概念及庫撲面而來,不得不承認,對於前端JavaScript代碼的組織編寫是一次偉大的變革。本文主要參考 snandy 的有關 modular js 系列文章,對SeaJS和RequireJS做一個系統的深入分析及對比。
一、我們為什么要用模塊化的JavaScript
相信大家也都經歷了“過程式的JavaScript”、“面向對象的JavaScript”,再到現在的“面向模塊的JavaScript”。這個“面向模塊的JavaScript”到底能走多久,我們拭目以待。
但是我們需要知道,我們為什么要擁抱模塊化的JavaScript,它到底有什么奇妙之處,讀一下這一篇文章吧—— 前端模塊化開發的價值 ,OK,明白了,它解決了兩大問題——
其一、惱人的命名沖突:
這個確實夠煩人,為了解決之,我們動用閉包作用域、我們動用命名空間,再或者...
其二、煩瑣的文件依賴:
這個問題更不必說,我們做前端支持的,常常要替后端同學們解決這類問題,我們自己也常常忽略這類問題。平常的開發中,我們要對我們的JS文件的加載順序要小心加小心。一個字——確實煩。
好,現在,我們有SeaJS了,這兩大問題變得很輕松就能搞定了,開心哪。
針對第一個問題:通過 exports 暴露接口 。這意味着不需要命名空間了,更不需要全局變量。這是一種徹底的命名沖突解決方案。
針對第二個問題:通過 require 引入依賴 。這可以讓依賴內置,開發者只需關心當前模塊的依賴,其他事情 SeaJS 都會自動處理好。對模塊開發者來說,這是一種很好的 關注度分離 ,能讓程序員更多地享受編碼的樂趣。
二、 瀏覽器中的模塊規范AMD是怎么誕生的?
SeaJS我們領教過了,但是對於它的模塊化寫法,到底是咋回事兒?我們先看看module寫法的演變。直接去看 JavaScript中模塊“寫法” ,使用 統一的風格來寫模塊,非nodeJS打頭陣不可, 以下是node中是寫模塊的一個示例。
1> math.js
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) { sum += args[i++]; } return sum; };
2> increment.js
var add = require('math').add; exports.increment = function(val) { return add(val, 1); };
3> main.js,該文件為入口文件
var inc = require('increment').increment; var a = 1; inc(a); // 2
從以上代碼示例可以看到——
1> node要求一個js文件對應一個模塊。可以把該文件中的代碼想象成是包在一個匿名函數中,所有代碼都在匿名函數中,其它模塊不可訪問除exports外的私有變量
2> 使用exports導出API 3> 使用require加載其它模塊
CommonJS module基本要求 如下——
1> 標示符require,為一個函數,它僅有一個參數為字符串,該字符串須遵守Module Identifiers的6點規定 2> require方法返回指定的模塊API 3> 如果存在依賴的其它模塊,那么依次加載 4> require不能返回,則拋異常 5> 僅能使用標示符exports導出API
打頭陣的NodeJS真是享譽甚高,前端的我們是不是也可以使用同樣的模塊寫法寫模塊呢? 當然nodeJS是最好的效仿對象。因為前后端有一個統一的方式寫JS模塊豈不樂哉!可是,事與願違,讀這個文章吧—— Node.js模塊風格在瀏覽器中的嘗試 ,深入理解之,便能明白,為什么NodeJS模塊寫法為什么不適合瀏覽器端。為了解決之,便出現了 Modules/Wrappings ,顧名思義包裹的模塊。該規范約定如下——
1> 定義模塊用module變量,它有一個方法declare 2> declare接受一個函數類型的參數,如稱為factory 3> factory有三個參數分別為require、exports、module 4> factory使用返回值和exports導出API 5> factory如果是對象類型,則將該對象作為模塊輸出
Modules/Wrappings 的出現使得瀏覽器中實現它變得可能,包裹的函數作為回調。即使用script tag作為模塊加載器,script完全下載后去回調,回調中進行模塊定義。
接着,也就產生了AMD( 全稱為異步模塊定義 )規范了(讀 AMD:瀏覽器中的模塊規范 )。 從名稱上看便知它是適合script tag的。也可以說AMD是專門為瀏覽器中JavaScript環境設計的規范。它吸取了CommonJS的一些優點,但又不照搬它的格式。開始AMD作為CommonJS的 transport format 存在,因無法與CommonJS開發者達成一致而獨立出來。
AMD開創性的提出了自己的模塊風格。但后來又做了妥協,兼容了 CommonJS Modules/Wrappings 。
define(function(require, exports, module) { var base = require('base'); exports.show = function() { // todo with module base } });
不考慮多了一層函數外,格式和Node.js是一樣的。使用require獲取依賴模塊,使用exports導出API。
除了define外,AMD還保留一個關鍵字require。 require 作為規范保留的全局標識符,可以實現為 module loader。
目前,實現AMD的庫有 RequireJS 、 curl 、 Dojo 、 bdLoad 、 JSLocalnet 、 Nodules 等。
三、SeaJS與RequireJS的區別
搞不懂SeaJS和 RequireJS define的區別。 它們都有個全局的define,形參都是三個,且對應的形參名也一樣,會誤認為SeaJS也是AMD的實現。 事實上SeaJS和RequireJS的define前兩個參數的確一樣。 id都為字符串,都遵循 Module Identifiers 。deps都是指依賴模塊,類型都為數組。區別僅在於第三個參數factory,雖然類型也都是函數,但factory的參數意義卻不同。
RequireJS中factory的參數有兩種情況。
a、和deps(數組)元素一一對應。即deps有幾個,factory的實參就有幾個。(官方推薦的模塊定義方法)
define(['a', 'b'], function(a, b){ // todo });
b、固定為require,exports, module(modules/wrappings格式)。
define(function(require, exports, module){ // todo });
這種方式是RequireJS后期向 Modules/Wrappings 的妥協,即兼容了它。而SeaJS的define僅支持RequireJS的第二種寫法:Modules/Wrappings。
注意 :SeaJS遵循的是 Modules/Wrappings 和 Modules/1.1.1 。這兩個規范中都沒有提到define關鍵 字,Modules/Wrapping中要求定義模塊使用module.declare而非define。而恰恰只有AMD規范中有define的定義。 即雖然SeaJS不是AMD的實現,但它卻采用了讓人極容易誤解的標識符define。
對於SeaJS和RequireJS的其他差異,可以讀 SeaJS與RequireJS的異同 。
兩者相同之處——
RequireJS 和 SeaJS 都是模塊加載器,倡導模塊化開發理念,核心價值是讓 JavaScript 的模塊化開發變得簡單自然。
兩者不同之處——
1> 定位有差異。RequireJS 想成為瀏覽器端的模塊加載器,同時也想成為 Rhino / Node 等環境的模塊加載器。SeaJS 則專注於 Web 瀏覽器端,同時通過 Node 擴展的方式可以很方便跑在 Node 環境中。 2> 遵循的規范不同。RequireJS 遵循 AMD(異步模塊定義)規范,SeaJS 遵循 CMD (通用模塊定義)規范。規范的不同,導致了兩者 API 不同。SeaJS 更貼近 CommonJS Modules/1.1 和 Node Modules 規范。 3> 推廣理念有差異。RequireJS 在嘗試讓第三方類庫修改自身來支持 RequireJS,目前只有少數社區采納。SeaJS 不強推,采用自主封裝的方式來“海納百川”,目前已有較成熟的封裝策略。 4> 對開發調試的支持有差異。SeaJS 非常關注代碼的開發調試,有 nocache、debug 等用於調試的插件。RequireJS 無這方面的明顯支持。 5> 插件機制不同。RequireJS 采取的是在源碼中預留接口的形式,插件類型比較單一。SeaJS 采取的是通用事件機制,插件類型更豐富。
四、AMD和CMD的區別
接着引深來看,大家都知道SeaJS遵循CMD(通用模塊定義),RequireJS遵循AMD,那么CMD和AMD到底有什么區別?
上面描述過 AMD模塊定義規范 ,接着我們看看 CMD模塊定義規范 ,可大致了解一下CMD到底是怎樣的規范。對於兩者的差異,讀讀玉泊對它的回答吧—— AMD和CMD區別有哪些?
AMD 是 RequireJS 在推廣過程中對模塊定義的規范化產出。
CMD 是 SeaJS 在推廣過程中對模塊定義的規范化產出。
類似的還有 CommonJS Modules/2.0 規范,是 BravoJS 在推廣過程中對模塊定義的規范化產出。
還有不少⋯⋯
這些規范的目的都是為了 JavaScript 的模塊化開發,特別是在瀏覽器端的。
目前這些規范的實現都能達成瀏覽器端模塊化開發的目的。
區別:
1. 對於依賴的模塊,AMD 是提前執行,CMD 是延遲執行。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible. 2. CMD 推崇依賴就近,AMD 推崇依賴前置。看代碼:
// CMD
define(function(require, exports, module) { var a = require('./a') a.doSomething() // 此處略去 100 行 var b = require('./b') // 依賴可以就近書寫 b.doSomething() // ... }) // AMD 默認推薦的是 define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好 a.doSomething() // 此處略去 100 行 b.doSomething() ... })
雖然 AMD 也支持 CMD 的寫法,同時還支持將 require 作為依賴項傳遞,但 RequireJS 的作者默認是最喜歡上面的寫法,也是官方文檔里默認的模塊定義寫法。
3. AMD 的 API 默認是一個當多個用,CMD 的 API 嚴格區分,推崇職責單一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,沒有全局 require,而是根據模塊系統的完備性,提供 seajs.use 來實現模塊系統的加載啟動。CMD 里,每個 API 都簡單純粹。 4. 還有一些細節差異,具體看這個規范的定義就好,就不多說了。
五、總結
寫了挺多,當然我承認都是摘錄的,不過把模塊化來源的思路搞定,收獲也就有了。我再理順一下——
1> 我們為什么要用模塊化的JavaScript
最主要的是解決命名沖突、解決文件依賴這兩大問題。
2> 瀏覽器中的模塊規范AMD是怎么誕生的?
使用script tag作為模塊加載器,script完全下載后去回調,回調中進行模塊定義。
3> SeaJS與RequireJS的區別
RequireJS 遵循的是 AMD(異步模塊定義)規范,SeaJS 遵循的是 CMD (通用模塊定義)規范。
4> AMD和CMD的區別
(1) 對於依賴的模塊,AMD 是 提前執行 ,CMD 是 延遲執行 。不過 RequireJS 從 2.0 開始,也改成可以延遲執行(根據寫法不同,處理方式不同)。CMD 推崇 as lazy as possible.
(2) CMD 推崇 依賴就近 ,AMD 推崇 依賴前置 。(這一點是非常重要的區別)
(3) AMD 的 API 默認是 一個當多個用 ,CMD 的 API 嚴格區分,推崇 職責單一 。
需要記住—— AMD 是 RequireJS 在推廣過程中對模塊定義的規范化產出。 CMD 是 SeaJS 在推廣過程中對模塊定義的規范化產出。
六、引深
本文僅僅講述了前端Javascript的模塊化開發思想,但是對於其他的HTML和CSS,我們是否也可以采用模塊化話的開發思想來引導呢?答案是肯定的。尤其在前端多人合作開發的情況下,這種模塊化的開發模式會讓我們愛不釋手。這里我只細讀過這樣